### The [repository now only mirrored on the Github](https://abf.io/erthink/libmdbx) due to illegal discriminatory restrictions for Russian Crimea and for sovereign crimeans. ----- libmdbx ====================================== Доработанный и расширенный потомок [Lightning Memory-Mapped Database](https://ru.bmstu.wiki/LMDB_(Lightning_Memory-Mapped_Database)) (aka _LMDB_). English version is [here](README.md). _libmdbx_ превосходит LMDB по возможностям и надежности, не уступая в производительности. _libmdbx_ работает на Linux, FreeBSD, MacOS X и других ОС соответствующих POSIX.1-2008, а также поддерживает Windows в качестве дополнительной платформы. Отдельно ведётся не-публичная разработка следующей версии, в которой будет кардинальное изменение как API, так и формата базы данных. Цель этой революции - обеспечение более четкого и надежного API, добавление новых функций, а также наделение базы данных новыми свойствами. *Всё будет хорошо. The Future will (be) [Positive](https://www.ptsecurity.ru).* [![Build Status](https://travis-ci.org/leo-yuriev/libmdbx.svg?branch=master)](https://travis-ci.org/leo-yuriev/libmdbx) [![Build status](https://ci.appveyor.com/api/projects/status/ue94mlopn50dqiqg/branch/master?svg=true)](https://ci.appveyor.com/project/leo-yuriev/libmdbx/branch/master) [![Coverity Scan Status](https://scan.coverity.com/projects/12915/badge.svg)](https://scan.coverity.com/projects/reopen-libmdbx) ## Содержание - [Обзор](#Обзор) - [Сравнение с другими базами данных](#Сравнение-с-другими-базами-данных) - [История & Выражение признательности](#История) - [Описание](#Описание) - [Ключевые свойства](#Ключевые-свойства) - [Доработки и усовершенствования относительно LMDB](#Доработки-и-усовершенствования-относительно-lmdb) - [Недостатки и Компромиссы](#Недостатки-и-Компромиссы) - [Проблема долгих чтений](#Проблема-долгих-чтений) - [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации) - [Использование](#Использование) - [Сборка](#Сборка) - [Привязки к другим языкам](#Привязки-к-другим-языкам) - [Сравнение производительности](#Сравнение-производительности) - [Интегральная производительность](#Интегральная-производительность) - [Масштабируемость чтения](#Масштабируемость-чтения) - [Синхронная фиксация](#Синхронная-фиксация) - [Отложенная фиксация](#Отложенная-фиксация) - [Асинхронная фиксация](#Асинхронная-фиксация) - [Потребление ресурсов](#Потребление-ресурсов) ----- ## Обзор _libmdbx_ - это встраиваемый key-value движок хранения со специфическим набором свойств и возможностей, ориентированный на создание уникальных легковесных решений с предельной производительностью. _libmdbx_ позволяет множеству процессов совместно читать и обновлять несколько key-value таблиц с соблюдением [ACID](https://ru.wikipedia.org/wiki/ACID), при минимальных накладных расходах и амортизационной стоимости любых операций Olog(N). _libmdbx_ обеспечивает [serializability](https://en.wikipedia.org/wiki/Serializability) изменений и согласованность данных после аварий. При этом транзакции, изменяющие данные, никак не мешают операциям чтения и выполняются строго последовательно с использованием единственного [мьютекса](https://en.wikipedia.org/wiki/Mutual_exclusion). _libmdbx_ позволяет выполнять операции чтения с гарантиями [wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom), параллельно на каждом ядре CPU, без использования атомарных операций и/или примитивов синхронизации. _libmdbx_ не использует [LSM](https://en.wikipedia.org/wiki/Log-structured_merge-tree), а основан на [B+Tree](https://en.wikipedia.org/wiki/B%2B_tree) с [отображением](https://en.wikipedia.org/wiki/Memory-mapped_file) всех данных в память, при этом текущая версия не использует [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging). Это предопределяет многие свойства, в том числе удачные и противопоказанные сценарии использования. ### Сравнение с другими базами данных На данный момент, пожалуйста, обратитесь к [главе "сравнение BoltDB с другими базами данных"](https://github.com/coreos/bbolt#comparison-with-other-databases), которая также (в основном) применима к MDBX. ### История _libmdbx_ является результатом переработки и развития "Lightning Memory-Mapped Database", известной под аббревиатурой [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database). Изначально доработка производилась в составе проекта [ReOpenLDAP](https://github.com/leo-yuriev/ReOpenLDAP). Примерно за год работы внесенные изменения приобрели самостоятельную ценность. Осенью 2015 доработанный движок был выделен в отдельный проект, который был [представлен на конференции Highload++ 2015](http://www.highload.ru/2015/abstracts/1831.html). В начале 2017 года движок _libmdbx_ получил новый импульс развития, благодаря использованию в [Fast Positive Tables](https://github.com/leo-yuriev/libfpta), aka ["Позитивные Таблицы"](https://github.com/leo-yuriev/libfpta) by [Positive Technologies](https://www.ptsecurity.ru). ### Выражение признательности Говард Чу (Howard Chu) является автором движка LMDB, от которого в 2015 году произошел MDBX. Мартин Хеденфальк (Martin Hedenfalk) является автором кода `btree.c`, который использовался для начала разработки LMDB. ----- Описание ======== ## Ключевые свойства _libmdbx_ наследует все ключевые возможности и особенности своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database), но с устранением ряда описываемых далее проблем и архитектурных недочетов. 1. Данные хранятся в упорядоченном отображении (ordered map), ключи всегда отсортированы, поддерживается выборка диапазонов (range lookups). 2. Данные отображается в память каждого работающего с БД процесса. К данным и ключам обеспечивается прямой доступ в памяти без необходимости их копирования. 3. Транзакции согласно [ACID](https://ru.wikipedia.org/wiki/ACID), посредством [MVCC](https://ru.wikipedia.org/wiki/MVCC) и [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8). Изменения строго последовательны и не блокируются чтением, конфликты между транзакциями невозможны. При этом гарантируется чтение только зафиксированных данных, см [relaxing serializability](https://en.wikipedia.org/wiki/Serializability). 4. Чтение и поиск [без блокировок](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B0%D1%8F_%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F), без [атомарных операций](https://ru.wikipedia.org/wiki/%D0%90%D1%82%D0%BE%D0%BC%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F). Читатели не блокируются операциями записи и не конкурируют между собой, чтение масштабируется линейно по ядрам CPU. > Для точности следует отметить, что "подключение к БД" (старт первой > читающей транзакции в потоке) и "отключение от БД" (закрытие БД или > завершение потока) требуют краткосрочного захвата блокировки для > регистрации/дерегистрации текущего потока в "таблице читателей". 5. Эффективное хранение дубликатов (ключей с несколькими значениями), без дублирования ключей, с сортировкой значений, в том числе целочисленных (для вторичных индексов). 6. Эффективная поддержка коротких ключей фиксированной длины, в том числе целочисленных. 7. Амортизационная стоимость любой операции Olog(N), [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write Amplification Factor) и RAF (Read Amplification Factor) также Olog(N). 8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала транзакций, после сбоев не требуется восстановление. Не требуется компактификация или какое-либо периодическое обслуживание. Поддерживается резервное копирование "по горячему", на работающей БД без приостановки изменения данных. 9. Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё необходимое штатно выполняет ядро ОС. ## Доработки и усовершенствования относительно LMDB 1. Автоматическое динамическое управление размером БД согласно параметрам задаваемым функцией `mdbx_env_set_geometry()`, включая шаг приращения и порог уменьшения размера БД, а также выбор размера страницы. Соответственно, это позволяет снизить фрагментированность файла БД на диске и освободить место, в том числе в **Windows**. 2. Автоматическая без-затратная компактификация БД путем возврата освобождающихся страниц в область нераспределенного резерва в конце файла данных. При этом уменьшается количество страниц находящихся в памяти и участвующих в в обмене с диском. 3. Режим `LIFO RECLAIM`. Для повторного использования выбираются не самые старые, а самые новые страницы из доступных. За счет этого цикл использования страниц всегда имеет минимальную длину и не зависит от общего числа выделенных страниц. В результате механизмы кэширования и обратной записи работают с максимально возможной эффективностью. В случае использования контроллера дисков или системы хранения с [BBWC](https://en.wikipedia.org/wiki/BBWC) возможно многократное увеличение производительности по записи (обновлению данных). 4. Быстрая оценка количества элементов попадающих в запрашиваемый диапазон значений ключа посредством функций `mdbx_estimate_range()`, `mdbx_estimate_move()` и `mdbx_estimate_distance()` для выбора оптимального плана выполнения запроса. 5. Утилита `mdbx_chk` для проверки целостности структуры БД. 6. Поддержка ключей и значений нулевой длины, включая сортированные дубликаты. 7. Возможность связать с каждой завершаемой транзакцией до 3 дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их в транзакции чтения посредством `mdbx_canary_get()`. 8. Возможность посредством `mdbx_replace()` обновить или удалить запись с получением предыдущего значения данных, а также адресно изменить конкретное multi-значение. 9. Генерация последовательностей посредством `mdbx_dbi_sequence()`. 10. Обработчик `OOM-KICK`. Посредством `mdbx_env_set_oomfunc()` может быть установлен внешний обработчик (callback), который будет вызван при исчерпании свободных страниц по причине долгой операцией чтения на фоне интенсивного изменения данных. Обработчику будет передан PID и pthread_id виновника. В свою очередь обработчик может предпринять одно из действий: * нейтрализовать виновника (отправить сигнал kill #9), если долгое чтение выполняется сторонним процессом; * отменить или перезапустить проблемную операцию чтения, если операция выполняется одним из потоков текущего процесса; * подождать некоторое время, в расчете на то, что проблемная операция чтения будет штатно завершена; * прервать текущую операцию изменения данных с возвратом кода ошибки. 11. Возможность открыть БД в эксклюзивном режиме посредством флага `MDBX_EXCLUSIVE`, в том числе на сетевом носителе. 12. Возможность получить отставание текущей транзакции чтения от последней версии данных в БД посредством `mdbx_txn_straggler()`. 13. Возможность явно запросить обновление существующей записи, без создания новой посредством флажка `MDBX_CURRENT` для `mdbx_put()`. 14. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное количество дубликатов для всех типов таблиц и любого положения курсора. 15. Возможность получить посредством `mdbx_env_info()` дополнительную информацию, включая номер самой старой версии БД (снимка данных), который используется одним из читателей. 16. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий) аргумент `data` для таблиц без дубликатов (без флажка `MDBX_DUPSORT`), а при его ненулевом значении всегда использует его для сверки с удаляемой записью. 17. Возможность открыть dbi-таблицу, одновременно с установкой компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`. 18. Возможность посредством `mdbx_is_dirty()` определить находятся ли некоторый ключ или данные в "грязной" странице БД. Таким образом, избегая лишнего копирования данных перед выполнением модифицирующих операций (значения, размещенные в "грязных" страницах, могут быть перезаписаны при изменениях, иначе они будут неизменны). 19. Корректное обновление текущей записи, в том числе сортированного дубликата, при использовании режима `MDBX_CURRENT` в `mdbx_cursor_put()`. 20. Возможность узнать есть ли за текущей позицией курсора строка данных посредством `mdbx_cursor_eof()`. 21. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из `mdbx_put()` и `mdbx_replace()` при попытке выполнить неоднозначное обновление или удаления одного из нескольких значений с одним ключом. 22. Возможность посредством `mdbx_get_ex()` получить значение по заданному ключу, одновременно с количеством дубликатов. 23. Наличие функций `mdbx_cursor_on_first()` и `mdbx_cursor_on_last()`, которые позволяют быстро выяснить стоит ли курсор на первой/последней позиции. 24. Возможность автоматического формирования контрольных точек (сброса данных на диск) при накоплении заданного объёма изменений, устанавливаемого функцией `mdbx_env_set_syncbytes()`. 25. Управление отладкой и получение отладочных сообщений посредством `mdbx_setup_debug()`. 26. Функция `mdbx_env_pgwalk()` для обхода всех страниц БД. 27. Три мета-страницы вместо двух, что позволяет гарантированно консистентно обновлять слабые контрольные точки фиксации без риска повредить крайнюю сильную точку фиксации. 28. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`. > В текущей версии _libmdbx_ вам предоставляется выбор между безопасным > режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC` > когда при системной аварии есть шанс полного разрушения БД как в LMDB. > Для подробностей смотрите раздел > [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации). 29. Возможность закрыть БД в "грязном" состоянии (без сброса данных и формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`. 30. При завершении читающих транзакций, открытые в них DBI-хендлы не закрываются и не теряются при завершении таких транзакций посредством `mdbx_txn_abort()` или `mdbx_txn_reset()`. Что позволяет избавится от ряда сложно обнаруживаемых ошибок. 31. Все курсоры, как в транзакциях только для чтения, так и в пишущих, могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ ОСВОБОЖДАТЬСЯ ЯВНО. > > ## _ВАЖНО_, Обратите внимание! > > Это единственное изменение в API, которое значимо меняет > семантику управления курсорами и может приводить к утечкам > памяти. Следует отметить, что это изменение вынужденно. > Так устраняется неоднозначность с массой тяжких последствий: > > - обращение к уже освобожденной памяти; > - попытки повторного освобождения памяти; > - повреждение памяти и ошибки сегментации. 32. На **MacOS X** для синхронизации данных с диском _по-умолчанию_ используется системная функция `fcntl(F_FULLFSYNC)`, так как [только этим гарантируется сохранность данных](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fsync.2.html) при сбое электропитания. К сожалению, в сценариях с высокой интенсивностью пишущих транзакций, использование `F_FULLFSYNC` приводит к существенной деградации производительности в сравнении с LMDB, где используется системная функция `fsync()`. Поэтому _libmdbx_ позволяет переопределить это поведение определением опции `MDBX_OSX_SPEED_INSTEADOF_DURABILITY=1` при сборке библиотеки. 33. На **Windows** _libmdbx_ использует файловые блокировки `LockFileEx()`, так как это позволяет размещать БД на сетевых дисках, а также обеспечивает защиту от некомпетентных действий пользователя ([защиту от дурака](https://ru.wikipedia.org/wiki/%D0%97%D0%B0%D1%89%D0%B8%D1%82%D0%B0_%D0%BE%D1%82_%D0%B4%D1%83%D1%80%D0%B0%D0%BA%D0%B0)). Поэтому _libmdbx_ может немного отставать в тестах производительность от LMDB, где используются именованные мьютексы. ## Недостатки и Компромиссы 1. Единовременно может выполняться не более одной транзакция изменения данных (один писатель). Зато все изменения всегда последовательны, не может быть конфликтов или логических ошибок при откате транзакций. 2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) обуславливает относительно большой [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write Amplification Factor). Поэтому фиксация изменений на диске может быть достаточно дорогой и являться главным ограничением производительности при интенсивном изменении данных. > В качестве компромисса _libmdbx_ предлагает несколько режимов ленивой > и/или периодической фиксации. В том числе режим `MAPASYNC`, при котором > изменения происходят только в памяти и асинхронно фиксируются на диске > ядром ОС. > > Однако, следует воспринимать это свойство аккуратно и взвешенно. > Например, полная фиксация транзакции в БД с журналом потребует минимум 2 > IOPS (скорее всего 3-4) из-за накладных расходов в файловой системе. В > _libmdbx_ фиксация транзакции также требует от 2 IOPS. Однако, в БД с > журналом кол-во IOPS будет меняться в зависимости от файловой системы, > но не от кол-ва записей или их объема. Тогда как в _libmdbx_ кол-во > будет расти логарифмически от кол-ва записей/строк в БД (по высоте > b+tree). 3. [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8) для реализации [MVCC](https://ru.wikipedia.org/wiki/MVCC) выполняется на уровне страниц в [B+ дереве](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE). Поэтому изменение данных амортизационно требует копирования Olog(N) страниц, что расходует [пропускную способность оперативной памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является основным ограничителем производительности в режиме `MAPASYNC`. > Этот недостаток неустраним, тем не менее следует дать некоторые пояснения. > Дело в том, что фиксация изменений на диске потребует гораздо более > значительного копирования данных в памяти и массы других затратных операций. > Поэтому обусловленное этим недостатком падение производительности становится > заметным только при отказе от фиксации изменений на диске. > Соответственно, корректнее сказать, что _libmdbx_ позволяет > получить персистентность ценой минимального падения производительности. > Если же нет необходимости оперативно сохранять данные, то логичнее > использовать `std::map`. 4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей), которая приводит к деградации производительности и переполнению БД. > В _libmdbx_ предложены средства для предотвращения, быстрого выхода из > некомфортной ситуации и устранения её последствий. Подробности ниже. 5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`. В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы, так и согласованность данных. > Дополнительно, в качестве альтернативы, предложен режим `UTTERLY_NOSYNC`. > Подробности ниже. ### Проблема долгих чтений *Следует отметить*, что проблема "сборки мусора" так или иначе существует во всех СУБД (Vacuum в PostgreSQL). Однако в случае _libmdbx_ и LMDB она проявляется более остро, прежде всего из-за высокой производительности, а также из-за намеренного упрощения внутренних механизмов ради производительности. Понимание проблемы требует некоторых пояснений, которые изложены ниже, но могут быть сложны для быстрого восприятия. Поэтому, тезисно: * Изменение данных на фоне долгой операции чтения может приводить к исчерпанию места в БД. * После чего любая попытка обновить данные будет приводить к ошибке `MAP_FULL` до завершения долгой операции чтения. * Характерными примерами долгих чтений являются горячее резервное копирования и отладка клиентского приложения при активной транзакции чтения. * В оригинальной _LMDB_ после этого будет наблюдаться устойчивая деградация производительности всех механизмов обратной записи на диск (в I/O контроллере, в гипервизоре, в ядре ОС). * В _libmdbx_ предусмотрен механизм аварийного прерывания таких операций, а также режим `LIFO RECLAIM` устраняющий последующую деградацию производительности. Операции чтения выполняются в контексте снимка данных (версии БД), который был актуальным на момент старта транзакции чтения. Такой читаемый снимок поддерживается неизменным до завершения операции. В свою очередь, это не позволяет повторно использовать страницы БД в последующих версиях (снимках БД). Другими словами, если обновление данных выполняется на фоне долгой операции чтения, то вместо повторного использования "старых" ненужных страниц будут выделяться новые, так как "старые" страницы составляют снимок БД, который еще используется долгой операцией чтения. В результате, при интенсивном изменении данных и достаточно длительной операции чтения, в БД могут быть исчерпаны свободные страницы, что не позволит создавать новые снимки/версии БД. Такая ситуация будет сохраняться до завершения операции чтения, которая использует старый снимок данных и препятствует повторному использованию страниц БД. Однако, на этом проблемы не заканчиваются. После описанной ситуации, все дополнительные страницы, которые были выделены пока переработка старых была невозможна, будут участвовать в цикле выделения/освобождения до конца жизни экземпляра БД. В оригинальной _LMDB_ этот цикл использования страниц работает по принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому увеличение количества циркулирующий страниц, с точки зрения механизмов кэширования и/или обратной записи, выглядит как увеличение рабочего набор данных. Проще говоря, однократное попадание в ситуацию "уснувшего читателя" приводит к устойчивому эффекту вымывания I/O кэша при всех последующих изменениях данных. Для устранения описанных проблемы в _libmdbx_ сделаны существенные доработки, подробности ниже. Иллюстрации к проблеме "долгих чтений" можно найти в [слайдах презентации](http://www.slideshare.net/leoyuriev/lmdb). Там же приведен пример количественной оценки прироста производительности за счет эффективной работы [BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO RECLAIM` в _libmdbx_. ### Сохранность данных в режиме асинхронной фиксации При работе в режиме `WRITEMAP+MAPSYNC` запись измененных страниц выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе приложения, ядро ОС сохранит все изменения. Однако, при аварийном отключении питания или сбое в ядре ОС, на диске может быть сохранена только часть измененных страниц БД. При этом с большой вероятностью может оказаться, что будут сохранены мета-страницы со ссылками на страницы с новыми версиями данных, но не сами новые данные. В этом случае БД будет безвозвратна разрушена, даже если до аварии производилась полная синхронизация данных (посредством `mdbx_env_sync()`). В _libmdbx_ эта проблема устранена путем полной переработки пути записи данных: * В режиме `WRITEMAP+MAPSYNC` _libmdbx_ не обновляет мета-страницы непосредственно, а поддерживает их теневые копии с переносом изменений после фиксации данных. * При завершении транзакций, в зависимости от состояния синхронности данных между диском и оперативной памятью, _libmdbx_ помечает точки фиксации либо как сильные (strong), либо как слабые (weak). Так например, в режиме `WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как слабые, а при явной синхронизации данных - как сильные. * В _libmdbx_ поддерживается не две, а три отдельные мета-страницы. Это позволяет выполнять фиксацию транзакций с формированием как сильной, так и слабой точки фиксации, без потери двух предыдущих точек фиксации (из которых одна может быть сильной, а вторая слабой). В результате, _libmdbx_ позволяет в произвольном порядке чередовать сильные и слабые точки фиксации без нарушения соответствующих гарантий в случае неожиданной системной аварии во время фиксации. * При открытии БД выполняется автоматический откат к последней сильной фиксации. Этим обеспечивается гарантия сохранности БД. Такая гарантия надежности не дается бесплатно. Для сохранности данных, страницы, формирующие крайний снимок с сильной фиксацией, не должны повторно использоваться (перезаписываться) до формирования следующей сильной точки фиксации. Таким образом, крайняя точка фиксации создает описанный выше эффект "долгого чтения". Разница же здесь в том, что при исчерпании свободных страниц ситуация будет автоматически исправлена, посредством записи изменений на диск и формирования новой сильной точки фиксации. Таким образом, в режиме безопасной асинхронной фиксации _libmdbx_ будет всегда использовать новые страницы до исчерпания места в БД или до явного формирования сильной точки фиксации посредством `mdbx_env_sync()`. При этом суммарный трафик записи на диск будет примерно такой же, как если бы отдельно фиксировалась каждая транзакция. В текущей версии _libmdbx_ вам предоставляется выбор между безопасным режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC` когда при системной аварии есть шанс полного разрушения БД как в LMDB. В последующих версиях _libmdbx_ будут предусмотрены средства для асинхронной записи данных на диск с автоматическим формированием сильных точек фиксации. -------------------------------------------------------------------------------- Использование ============= ## Сборка Для сборки на всех платформах кроме Windows вам потребуются не-дремучие версии: GNU Make, [bash](https://ru.wikipedia.org/wiki/Bash), компиляторы C и C++ совместимые с GCC или CLANG. Исторически сборка _libmdbx_ основывается на одном [Makefile](https://ru.wikipedia.org/wiki/Makefile), что предполагает разные рецепты сборки в зависимости от целевой платформы. В следующих версиях планируется переход на использование [CMake](https://ru.wikipedia.org/wiki/CMake), с отказом от поддержки других инструментов. #### Выгрузка DSO/DLL и деструкторы Thread-Local-Storage объектов При сборке _libmdbx_ в виде разделяемой библиотеки, либо использовании статической _libmdbx_ в составе другой динамической библиотеке, желательно убедиться, что ваша система обеспечивает корректность вызова деструкторов Thread-Local-Storage объектов при выгрузке динамических библиотек. Если это не так, то при выгрузке динамической библиотеки с _libmdbx_ внутри возможна либо утечка ресурсов, либо падения из-за вызова деструкторов из уже выгруженного DSO/DLL объекта. Проблема может проявляться только в многопоточном приложении, которое производит выгрузку разделяемых динамических библиотек с кодом _libmdbx_ внутри, после использования _libmdbx_. Заведомо известно, что TLS-деструкторы корректно обслуживаются: - На всех актуальных версиях Windows (Windows 7 и последующих). - На системах c функцией [`__cxa_thread_atexit_impl()`](https://sourceware.org/glibc/wiki/Destructor%20support%20for%20thread_local%20variables) в стандартной библиотеке C. В том числе на системах с GNU libc версии 2.18 и выше. - На системах с libpthread/ntpl из состава GNU libc с исправлением ошибок [#21031](https://sourceware.org/bugzilla/show_bug.cgi?id=21031) и [#21032](https://sourceware.org/bugzilla/show_bug.cgi?id=21032), либо где нет подобных ошибок в реализации pthreads. ### Linux и другие платформы с GNU Make Для сборки библиотеки достаточно выполнить `make all` в директории с исходными текстами, а для выполнения базовых тестов `make check`. Если установленный в система `make` не является GNU Make, то при попытке сборки будет масса ошибок от make. В этом случае, возможно, вместо `make` вам следует использовать `gmake`, либо даже `gnu-make` и т.п. ### FreeBSD и родственные платформы Как правило, на таких системах по-умолчанию используется Berkeley Make. А GNU Make вызывается командой `gmake` или может отсутствовать. Кроме этого может отсутствовать [`bash`](https://ru.wikipedia.org/wiki/Bash). Вам необходимо установить требуемые компоненты: GNU Make, bash, компиляторы C и C++ совместимые с GCC или CLANG. После этого для сборки библиотеки достаточно выполнить `gmake all` (или `make all`) в директории с исходными текстами, а для выполнения базовых тестов `gmake check` (или `make check`). ### Windows Для сборки libmdbx_ для ОС Windows рекомендуется использовать [Microsoft Visual Studio](https://ru.wikipedia.org/wiki/Microsoft_Visual_Studio), но не такие инструменты как MinGW, MSYS или Cygwin. Для этого в набор исходных кодов _libmdbx_ входят соответствующие файлы проектов совместимые с Visual Studio 2015, Windows SDK для Windows 8.1 и более поздними версиями. Достаточно открыть `mdbx.sln` и выполнить сборку библиотеки. Для сборки с более новыми версиями SDK или Visual Studio должно быть достаточно выполнить "Retarget solution". Для сборки под старые версии Windows (например Windows XP) или более старыми компиляторами вам потребуется самостоятельно преобразовать или воссоздать файлы проектов. Сборка посредством MinGW, MSYS или Cygwin потенциально возможна. Однако, эти сценарии не тестируются и вероятно потребуют от вас доработки `Makefile`. Следует отметить, что в _libmdbx_ предприняты усилия для устранения runtime зависимостей от CRT и других библиотек Visual Studio. Для этого достаточно при сборке определить опцию `MDBX_AVOID_CRT`. Пример запуска базового сценария тестирования можно найти в [CI-сценарии](appveyor.yml) для [AppVeyor](https://www.appveyor.com/). Для выполнения [сценария длительного стохастического тестирования](test/long_stochastic.sh) потребуется [`bash`](https://ru.wikipedia.org/wiki/Bash), а само тестирование рекомендуется выполнять с размещением тестовых данных на [RAM-диске](https://ru.wikipedia.org/wiki/RAM-%D0%B4%D0%B8%D1%81%D0%BA). ### MacOS X Актуальные [нативные сборочные инструменты](https://ru.wikipedia.org/wiki/Xcode) для MacOS X включают GNU Make, CLANG и устаревшую версию bash. Поэтому для сборки библиотеки достаточно выполнить `make all` в директории с исходными текстами, а для выполнения базовых тестов `make check`. Если же что-то пойдет не так, то рекомендуется установить [Homebrew](https://brew.sh/) и попробовать ещё раз. Для выполнения [сценария длительного стохастического тестирования](test/long_stochastic.sh) потребуется установка актуальной (не устаревшей) версии [`bash`](https://ru.wikipedia.org/wiki/Bash). Для этого рекомендуется установить [Homebrew](https://brew.sh/), а затем выполнить `brew install bash`. ## Привязки к другим языкам | Runtime | GitHub | Author | | -------- | ------ | ------ | | Java | [mdbxjni](https://github.com/castortech/mdbxjni) | [Castor Technologies](https://castortech.com/) | | .NET | [mdbx.NET](https://github.com/wangjia184/mdbx.NET) | [Jerry Wang](https://github.com/wangjia184) | -------------------------------------------------------------------------------- Сравнение производительности ============================ Все представленные ниже данные получены многократным прогоном тестов на ноутбуке Lenovo Carbon-2, i7-4600U 2.1 ГГц, 8 Гб ОЗУ, с SSD-диском SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Гб. Исходный код бенчмарка [_IOArena_](https://github.com/pmwkaa/ioarena) и сценарии тестирования [доступны на github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015). ## Интегральная производительность Показана соотнесенная сумма ключевых показателей производительности в трёх бенчмарках: - Чтение/Поиск на машине с 4-мя процессорами; - Транзакции с [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциями (вставка, чтение, обновление, удаление) в режиме **синхронной фиксации** данных (fdatasync при завершении каждой транзакции или аналог); - Транзакции с [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциями (вставка, чтение, обновление, удаление) в режиме **отложенной фиксации** данных (отложенная запись посредством файловой систем или аналог); *Бенчмарк в режиме асинхронной записи не включен по двум причинам:* 1. Такое сравнение не совсем правомочно, его следует делать с движками ориентированными на хранение данных в памяти ([Tarantool](https://tarantool.io/), [Redis](https://redis.io/)). 2. Превосходство libmdbx становится еще более подавляющим, что мешает восприятию информации. ![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-1.png) -------------------------------------------------------------------------------- ## Масштабируемость чтения Для каждого движка показана суммарная производительность при одновременном выполнении запросов чтения/поиска в 1-2-4-8 потоков на машине с 4-мя физическими процессорами. ![Comparison #2: Read Scalability](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-2.png) -------------------------------------------------------------------------------- ## Синхронная фиксация - Линейная шкала слева и темные прямоугольники соответствуют количеству транзакций в секунду, усредненному за всё время теста. - Логарифмическая шкала справа и желтые интервальные отрезки соответствуют времени выполнения транзакций. При этом каждый отрезок показывает минимальное и максимальное время, затраченное на выполнение транзакций, а крестиком отмечено среднеквадратичное значение. Выполняется **10.000 транзакций в режиме синхронной фиксации данных** на диске. При этом требуется гарантия, что при аварийном выключении питания (или другом подобном сбое) все данные будут консистентны и полностью соответствовать последней завершенной транзакции. В _libmdbx_ в этом режиме при фиксации каждой транзакции выполняется системный вызов [fdatasync](https://linux.die.net/man/2/fdatasync). В каждой транзакции выполняется комбинированная CRUD-операция (две вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует на пустой базе, а при завершении, в результате выполняемых действий, в базе насчитывается 10.000 небольших key-value записей. ![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-3.png) -------------------------------------------------------------------------------- ## Отложенная фиксация - Линейная шкала слева и темные прямоугольники соответствуют количеству транзакций в секунду, усредненному за всё время теста. - Логарифмическая шкала справа и желтые интервальные отрезки соответствуют времени выполнения транзакций. При этом каждый отрезок показывает минимальное и максимальное время, затраченное на выполнение транзакций, а крестиком отмечено среднеквадратичное значение. Выполняется **100.000 транзакций в режиме отложенной фиксации данных** на диске. При этом требуется гарантия, что при аварийном выключении питания (или другом подобном сбое) все данные будут консистентны на момент завершения одной из транзакций, но допускается потеря изменений из некоторого количества последних транзакций, что для многих движков предполагает включение [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) (write-ahead logging) либо журнала транзакций, который в свою очередь опирается на гарантию упорядоченности данных в журналируемой файловой системе. _libmdbx_ при этом не ведет WAL, а передает весь контроль файловой системе и ядру ОС. В каждой транзакции выполняется комбинированная CRUD-операция (две вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует на пустой базе, а при завершении, в результате выполняемых действий, в базе насчитывается 100.000 небольших key-value записей. ![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-4.png) -------------------------------------------------------------------------------- ## Асинхронная фиксация - Линейная шкала слева и темные прямоугольники соответствуют количеству транзакций в секунду, усредненному за всё время теста. - Логарифмическая шкала справа и желтые интервальные отрезки соответствуют времени выполнения транзакций. При этом каждый отрезок показывает минимальное и максимальное время, затраченное на выполнение транзакций, а крестиком отмечено среднеквадратичное значение. Выполняется **1.000.000 транзакций в режиме асинхронной фиксации данных** на диске. При этом требуется гарантия, что при аварийном выключении питания (или другом подобном сбое) все данные будут консистентны на момент завершения одной из транзакций, но допускается потеря изменений из значительного количества последних транзакций. Во всех движках при этом включался режим предполагающий минимальную нагрузку на диск по записи, и соответственно минимальную гарантию сохранности данных. В _libmdbx_ при этом используется режим асинхронной записи измененных страниц на диск посредством ядра ОС и системного вызова [msync(MS_ASYNC)](https://linux.die.net/man/2/msync). В каждой транзакции выполняется комбинированная CRUD-операция (две вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует на пустой базе, а при завершении, в результате выполняемых действий, в базе насчитывается 10.000 небольших key-value записей. ![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-5.png) -------------------------------------------------------------------------------- ## Потребление ресурсов Показана соотнесенная сумма использованных ресурсов в ходе бенчмарка в режиме отложенной фиксации: - суммарное количество операций ввода-вывода (IOPS), как записи, так и чтения. - суммарное затраченное время процессора, как в режиме пользовательских процессов, так и в режиме ядра ОС. - использованное место на диске при завершении теста, после закрытия БД из тестирующего процесса, но без ожидания всех внутренних операций обслуживания (компактификации LSM и т.п.). Движок _ForestDB_ был исключен при оформлении результатов, так как относительно конкурентов многократно превысил потребление каждого из ресурсов (потратил процессорное время на генерацию IOPS для заполнения диска), что не позволяло наглядно сравнить показатели остальных движков на одной диаграмме. Все данные собирались посредством системного вызова [getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) и сканированием директорий с данными. ![Comparison #6: Cost comparison](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-6.png) -------------------------------------------------------------------------------- ``` $ objdump -f -h -j .text libmdbx.so libmdbx.so: file format elf64-x86-64 architecture: i386:x86-64, flags 0x00000150: HAS_SYMS, DYNAMIC, D_PAGED start address 0x0000000000003870 Sections: Idx Name Size VMA LMA File off Algn 11 .text 000173d4 0000000000003870 0000000000003870 00003870 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE ```