Mirror of the libmdbx repository
Go to file
Leo Yuriev 71ae2aba8d mdbx: refine mdbx_cursor_eof().
Change-Id: I786c5f2eedb273f44fd2ef5065d200f63dfec84b
2017-01-13 00:50:15 +03:00
.gitignore mdbx: yota's test. 2016-03-13 18:24:47 +03:00
.travis.yml mdbx: adds travis-ci. 2016-12-29 00:21:34 +03:00
barriers.h mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
CHANGES mdbx: fix mdb_cursor_last (ITS#8557). 2017-01-13 00:50:07 +03:00
COPYRIGHT mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
Doxyfile mdbx: backport - Doxygen fixes. Use DISTRIBUTE_GROUP_DOC. 2016-12-23 04:45:52 +03:00
intro.doc mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
LICENSE mdbx: move to the OpenLDAP Public License. 2016-09-27 13:44:23 +03:00
lmdb.h mdbx: adds README.md 2017-01-09 00:40:32 +03:00
Makefile mdbx: adds -ffunction-sections for CFLAGS. 2017-01-08 21:47:26 +03:00
mdb_chk.c mdbx: Merge branch 'positive' into 'devel' branch. 2017-01-08 14:27:31 +03:00
mdb_copy.1 mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mdb_copy.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mdb_dump.1 mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mdb_dump.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mdb_load.1 mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mdb_load.c mdbx: backport - ITS#8558 fix mdb_load with escaped plaintext. 2017-01-07 23:36:14 +03:00
mdb_stat.1 mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mdb_stat.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mdb.c mdbx: backport - Tweak cursor_next C_EOF check. 2017-01-13 00:50:15 +03:00
mdbx.c mdbx: refine mdbx_cursor_eof(). 2017-01-13 00:50:15 +03:00
mdbx.h mdbx: Merge branch 'positive' into 'devel' branch. 2017-01-08 14:27:31 +03:00
midl.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
midl.h mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mtest0.c mdbx: adds thread's cleanup test into mtest0. 2017-01-07 23:21:53 +03:00
mtest1.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mtest2.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mtest3.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mtest4.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mtest5.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
mtest6.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
README.md mdbx: minor refine README.md 2017-01-09 02:04:31 +03:00
reopen.h mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
sample-bdb.txt mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
sample-mdb.txt mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
wbench.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
yota_test1.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00
yota_test2.c mdbx: HNY 2017! 2017-01-01 14:56:00 +03:00

libmdbx

Extended LMDB, aka "Расширенная LMDB".

The Future will Positive. Всё будет хорошо.

Build Status

English version by Google is here.

Кратко

libmdbx - это встраиваемый key-value движок хранения со специфическим набором возможностей, которые при правильном применении позволяют создавать уникальные решения с чемпионской производительностью.

libmdbx является форком Symas Lightning Memory-Mapped Database (известной под аббревиатурой LMDB), с рядом существенных доработок, которые перечислены ниже.

Изначально модификация производилась в составе исходного кода проекта ReOpenLDAP. Примерно за год работы внесенные изменения приобрели самостоятельную ценность.

Осенью 2015 доработанный движок был выделен в отдельный проект, который был представлен на конференции Highload++ 2015.

Характеристики и ключевые особенности

Общее для оригинальной LMDB и MDBX

  • Данные хранятся в упорядоченном отображении (ordered map), ключи всегда отсортированы, поддерживается выборка диапазонов (range lookups).

  • Транзакции согласно ACID, посредством MVCC и COW.

  • Чтение без блокировок, без атомарных операций. Мьютексы захватываются только при старте и завершении сеанса работы с БД.

  • Читатели не конкурируют между собой, чтение масштабируется линейно по ядрам CPU.

  • Изменения строго последовательны и не блокируются чтением, конфликты между транзакциями не возможны.

  • Амортизационная стоимость любой операции Olog(N), WAF и RAF также Olog(N).

  • Нет WAL и журнала транзакций, после сбоев не требуется восстановление.

  • Не требуется компактификация или какое-либо периодическое обслуживание.

  • Эффективное хранение дубликатов (ключей с несколькими значениями) с сортировкой значений.

  • Эффективная поддержка ключей фиксированной длины (uint32_t, uint64_t).

  • Поддержка горячего резервного копирования.

  • Файл БД отображается в память кажлого процесса, который работает с БД. К ключам и данным обеспечивается прямой доступ (без копирования), они не меняются до завершения транзакции чтения.

  • Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё необходимое выполняет ядро ОС.

Недостатки и Компромиссы

  1. Единовременно может выполняться не более одной транзакция изменения данных (один писатель). Зато все изменения всегда последовательны, не может быть конфликтов или ошибок при откате транзакций.

  2. Отсутствие WAL обуславливает относительно большой WAF. Поэтому фиксация изменений на диске относительно дорога и является главным ограничителем для производительности по записи. В качестве компромисса предлагается несколько режимов ленивой и/или периодической фиксации. В том числе режим WRITEMAP, при котором изменения происходят только в памяти и асинхронно фиксируются на диске ядром ОС.

  3. COW для реализации MVCC выполняется на уровне страниц в B+ дереве. Поэтому изменение данных амортизационно требует копирования Olog(N) страниц, что расходует пропускную способность оперативной памяти и является основным ограничителем производительности в режиме WRITEMAP.

  4. Проблема долгих чтений (зависших читателей), см. ниже.

  5. Вероятность разрушения БД в режиме WRITEMAP, см ниже.

Проблема долгих чтений

Понимание проблемы требует некоторых пояснений, которые изложены ниже, но могут быть сложны для быстрого восприятия. Поэтому, тезисно:

  • Изменение данных на фоне долгой операции чтения может приводить к исчерпанию места в БД.

  • После чего любая попытка обновить данные будет приводить к ошибке MAP_FULL до завершения долгой операции чтения.

  • Характерными примерами долгих чтений являются горячее резервное копирования и отладка клиентского приложения при активной транзакции чтения.

  • В оригинальной LMDB после этого будет наблюдаться устойчивая деградация производительности всех механизмов обратной записи на диск (в I/O контроллере, в гипервизоре, в ядре ОС).

  • В MDBX предусмотрен механизм аварийного прерывания таких операций, а также режим LIFO RECLAIM устраняющий последующую деградацию производительности.

Операции чтения выполняются в контексте снимка данных (версии БД), который был актуальным на момент старта транзакции чтения. Такой читаемый снимок поддерживается неизменным до завершения операции. В свою очередь, это не позволяет повторно использовать страницы БД в последующих версиях (снимках БД).

Другими словами, если обновление данных выполняется на фоне долгой операции чтения, то вместо повторного использования "старых" ненужных страниц будут выделяться новые, так как "старые" страницы составляют снимок БД, который еще используется долгой операцией чтения.

В результате, при интенсивном изменении данных и достаточно длительной операции чтения, в БД могут быть исчерпаны свободные страницы, что не позволит создавать новые снимки/версии БД. Такая ситуация будет сохраняться до завершения операции чтения, которая использует старый снимок данных и препятствует повторному использованию страниц БД.

Однако, на этом проблемы не заканчиваются. После описанной ситуации, все дополнительные страницы, которые были выделены пока переработка старых была невозможна, будут участвовать в цикле выделения/освобождения до конца жизни экземпляра БД. В оригинальной LMDB этот цикл использования страниц работает по принципу FIFO. Поэтому увеличение количества циркулирующий страниц, с точки зрения механизмов кэширования и/или обратной записи, выглядит как увеличение рабочего набор данных. Проще говоря, однократное попадание в ситуацию "уснувшего читателя" приводит к устойчивому эффекту вымывания I/O кэша при всех последующих изменениях данных.

Для решения описанных проблемы в MDBX сделаны существенные доработки, см. ниже. Иллюстрации к проблеме "долгих чтений" можно найти в слайдах презентации. Там же приведен пример количественной оценки прироста производительности за счет эффективной работы BBWC при включении LIFO RECLAIM в MDBX.

Доработки MDBX

  1. Режим LIFO RECLAIM.

    Для повторного использования выбираются не самые старые, а самые новые страницы из доступных. За счет этого цикл использования страниц всегда имеет минимальную длину и не зависит от общего числа выделенных страниц.

    В результате механизмы кэширования и обратной записи работают с максимально возможной эффективностью. В случае использования контроллера дисков или системы хранения с BBWC возможно многократное увеличение производительности по записи (обновлению данных).

  2. Обработчик OOM-KICK.

    Посредством mdbx_env_set_oomfunc() может быть установлен внешний обработчик (callback), который будет вызван при исчерпания свободных страниц из-за долгой операцией чтения. Обработчику будет передан PID и pthread_id. В свою очередь обработчик может предпринять одно из действий:

    • отправить сигнал kill (#9), если долгое чтение выполняется сторонним процессом;
    • отменить или перезапустить проблемную операцию чтения, если операция выполняется одним из потоков текущего процесса;
    • подождать некоторое время, в расчете что проблемная операция чтения будет штатно завершена;
    • перервать текущую операцию изменения данных с возвратом кода ошибки.
  3. Гарантия сохранности БД в режиме WRITEMAP.

    При работе в режиме WRITEMAP запись измененных страниц выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе приложения, ядро ОС сохранит все изменения.

    Однако, при аварийном отключении питания или сбое в ядре ОС, на диске будет сохранена только часть измененных страниц БД. При этом с большой вероятностью может оказаться так, что будут сохранены мета-страницы со ссылками на страницы с новыми версиями данных, но не сами новые данные. В этом случае БД будет безвозвратна разрушена, даже если до аварии производилась полная синхронизация данных (посредством mdb_env_sync()).

    В MDBX эта проблема решена путем полной переработки пути записи данных:

    • В режиме WRITEMAP MDBX не обновляет мета-страницы непосредственно, а поддерживает их теневые копии с переносом изменений после фиксации данных.

    • При завершении транзакций, в зависимости от состояния синхронности данных между диском и оперативной память, MDBX помечает точки фиксации либо как сильные (strong), либо как слабые (weak). Так например, в режиме WRITEMAP завершаемые транзакции помечаются как слабые, а при явной синхронизации данных как сильные.

    • При открытии БД выполняется автоматический откат к последней сильной фиксации. Этим обеспечивается гарантия сохранности БД.

    К сожалению, такая гарантия надежности не дается бесплатно. Для сохранности данных, страницы формирующие крайний снимок с сильной фиксацией, не должны повторно использоваться (перезаписываться) до формирования следующей сильной точки фиксации. Таким образом, крайняя точки фиксации создает описанный выше эффект "долгого чтения", с разницей в том, что при исчерпании свободных страниц автоматически будет сформирована новая точка сильной фиксации.

    В последующих версиях MDBX будут предусмотрены средства для асинхронной записи данных на диск с формированием сильных точек фиксации.

  4. Возможность автоматического формирования контрольных точек (сброса данных на диск) при накоплении заданного объёма изменений, устанавливаемого функцией mdbx_env_set_syncbytes().

  5. Возможность получить отставание текущей транзакции чтения от последней версии данных в БД посредством mdbx_txn_straggler().

  6. Утилита mdbx_chk для проверки БД и функция mdbx_env_pgwalk() для обхода всех страниц БД.

  7. Управление отладкой и получение отладочных сообщений посредством mdbx_setup_debug().

  8. Возможность связать с каждой завершаемой транзакцией до 3 дополнительных маркеров посредством mdbx_canary_put(), и прочитать их в транзакции чтения посредством mdbx_canary_get().

  9. Возможность узнать есть ли за текущей позицией курсора строка данных посредством mdbx_cursor_eof().

  10. Возможность явно запросить обновление существующей записи, без создания новой посредством флажка MDB_CURRENT для mdb_put().

  11. Возможность обновить или удалить запись с получением предыдущего значения данных посредством mdbx_replace().

  12. Поддержка ключей нулевого размера.

  13. Исправленный вариант mdb_cursor_count(), возвращающий корректное количество дубликатов для всех типов таблиц и любого положения курсора.

  14. Возможность открыть БД в эксклюзивном режиме посредством mdbx_env_open_ex(), например в целях её проверки.

  15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и формирования сильной точки фиксации) посредством mdbx_env_close_ex().

  16. Возможность получить посредством mdbx_env_info() дополнительную информацию, включая номер самой старой версии БД (снимка данных), который используется одним из читателей.