mdbx: refine README.

This commit is contained in:
Leo Yuriev 2017-01-18 16:40:56 +03:00
parent ad7113419b
commit c4142c9a35

399
README.md
View File

@ -6,71 +6,81 @@ Extended LMDB, aka "Расширенная LMDB".
[![Build Status](https://travis-ci.org/ReOpen/libmdbx.svg?branch=devel)](https://travis-ci.org/ReOpen/libmdbx) [![Build Status](https://travis-ci.org/ReOpen/libmdbx.svg?branch=devel)](https://travis-ci.org/ReOpen/libmdbx)
English version by Google [is
here](https://translate.googleusercontent.com/translate_c?act=url&depth=1&hl=ru&ie=UTF8&prev=_t&rurl=translate.google.com&sl=ru&tl=en&u=https://github.com/ReOpen/libmdbx/tree/devel). English version by Google [is here](https://translate.googleusercontent.com/translate_c?act=url&depth=1&hl=ru&ie=UTF8&prev=_t&rurl=translate.google.com&sl=ru&tl=en&u=https://github.com/ReOpen/libmdbx/tree/devel).
## Кратко ## Кратко
libmdbx - это встраиваемый key-value движок хранения со специфическим _libmdbx_ - это встраиваемый key-value движок хранения со
набором возможностей, которые при правильном применении позволяют создавать специфическим набором возможностей, которые при правильном
уникальные решения с чемпионской производительностью. применении позволяют создавать уникальные решения с чемпионской
производительностью, идеально сочетаясь с технологией
[MRAM](https://en.wikipedia.org/wiki/Magnetoresistive_random-access_memory).
libmdbx является форком [Symas Lightning Memory-Mapped _libmdbx_ обновляет совместно используемый набор данных, никак
Database](https://symas.com/products/lightning-memory-mapped-database/) не мешая при этом параллельным операциям чтения, не применяя
(известной под аббревиатурой атомарных операций к самим данным, и обеспечивая
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database)), с согласованность при аварийной остановке в любой момент. Поэтому
рядом существенных доработок, которые перечислены ниже. _libmdbx_ позволяя строить системы с линейным масштабированием
производительности чтения/поиска по ядрам CPU и амортизационной
стоимостью любых операций Olog(N).
Изначально модификация производилась в составе исходного кода проекта ### История
[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за год работы _libmdbx_ является потомком "Lightning Memory-Mapped Database",
внесенные изменения приобрели самостоятельную ценность. известной под аббревиатурой
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
Осенью 2015 доработанный движок был выделен в отдельный проект, который был Изначально доработка производилась в составе проекта
[представлен на конференции Highload++ [ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за
2015](http://www.highload.ru/2015/abstracts/1831.html). год работы внесенные изменения приобрели самостоятельную
ценность. Осенью 2015 доработанный движок был выделен в
отдельный проект, который был [представлен на конференции
Highload++ 2015](http://www.highload.ru/2015/abstracts/1831.html).
## Характеристики и ключевые особенности Характеристики и ключевые особенности
=====================================
### Общее для оригинальной LMDB и MDBX _libmdbx_ наследует все ключевые возможности и особенности от
своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database),
с устранением описанных далее проблем и архитектурных недочетов.
* Данные хранятся в упорядоченном отображении (ordered map), ключи всегда ### Общее для оригинальной _LMDB_ и _libmdbx_
отсортированы, поддерживается выборка диапазонов (range lookups).
* Транзакции согласно [ACID](https://ru.wikipedia.org/wiki/ACID), посредством 1. Данные хранятся в упорядоченном отображении (ordered map), ключи всегда
[MVCC](https://ru.wikipedia.org/wiki/MVCC) отсортированы, поддерживается выборка диапазонов (range lookups).
и [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).
* Чтение [без 2. Данные отображается в память каждого работающего с БД процесса.
блокировок](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. 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).
Изменения строго последовательны и не блокируются чтением,
конфликты между транзакциями не возможны.
* Изменения строго последовательны и не блокируются чтением, конфликты между 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.
* Амортизационная стоимость любой операции Olog(N), 5. Эффективное хранение дубликатов (ключей с несколькими
[WAF](https://en.wikipedia.org/wiki/Write_amplification) и RAF также Olog(N). значениями), без дублирования ключей, с сортировкой значений, в
том числе целочисленных (для вторичных индексов).
* Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала 6. Эффективная поддержка ключей фиксированной длины, в том числе целочисленных.
транзакций, после сбоев не требуется восстановление.
* Не требуется компактификация или какое-либо периодическое обслуживание. 7. Амортизационная стоимость любой операции Olog(N),
[WAF](https://en.wikipedia.org/wiki/Write_amplification) и RAF также Olog(N).
* Эффективное хранение дубликатов (ключей с несколькими значениями) с 8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала
сортировкой значений. транзакций, после сбоев не требуется восстановление. Не требуется компактификация
или какое-либо периодическое обслуживание. Поддерживается резервное копирование
"по горячему", на работающей БД без приостановки изменения данных.
* Эффективная поддержка ключей фиксированной длины (uint32_t, uint64_t). 9. Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё
необходимое штатно выполняет ядро ОС.
* Поддержка горячего резервного копирования.
* Файл БД отображается в память кажлого процесса, который работает с БД. К
ключам и данным обеспечивается прямой доступ (без копирования), они не
меняются до завершения транзакции чтения.
* Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё
необходимое выполняет ядро ОС.
### Недостатки и Компромиссы ### Недостатки и Компромиссы
@ -82,9 +92,9 @@ Database](https://symas.com/products/lightning-memory-mapped-database/)
2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) 2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging)
обуславливает относительно большой обуславливает относительно большой
[WAF](https://en.wikipedia.org/wiki/Write_amplification). Поэтому фиксация [WAF](https://en.wikipedia.org/wiki/Write_amplification). Поэтому фиксация
изменений на диске относительно дорога и является главным ограничителем для изменений на диске может быть дорогой и является главным ограничителем для
производительности по записи. В качестве компромисса предлагается несколько производительности по записи. В качестве компромисса предлагается несколько
режимов ленивой и/или периодической фиксации. В том числе режим `WRITEMAP`, режимов ленивой и/или периодической фиксации. В том числе режим `MAPASYNC`,
при котором изменения происходят только в памяти и асинхронно фиксируются на при котором изменения происходят только в памяти и асинхронно фиксируются на
диске ядром ОС. диске ядром ОС.
@ -94,173 +104,228 @@ Database](https://symas.com/products/lightning-memory-mapped-database/)
Поэтому изменение данных амортизационно требует копирования Olog(N) страниц, Поэтому изменение данных амортизационно требует копирования Olog(N) страниц,
что расходует [пропускную способность оперативной что расходует [пропускную способность оперативной
памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является основным памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является основным
ограничителем производительности в режиме `WRITEMAP`. ограничителем производительности в режиме `MAPASYNC`.
4. Проблема долгих чтений (зависших читателей), см. ниже. 4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей),
которая приводит к деградации производительности и переполнению БД.
В _libmdbx_ предложены средства для предотвращения, выхода из проблемной
ситуации и устранения её последствий. Подробности ниже.
5. Вероятность разрушения БД в режиме `WRITEMAP`, см ниже. 5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`.
В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы,
так и согласованность данных. При этом также, в качестве альтернативы,
предложен режим `UTTERLY_NOSYNC`. Подробности ниже.
#### Проблема долгих чтений #### Проблема долгих чтений
Понимание проблемы требует некоторых пояснений, которые изложены ниже, но Понимание проблемы требует некоторых пояснений, которые
могут быть сложны для быстрого восприятия. Поэтому, тезисно: изложены ниже, но могут быть сложны для быстрого восприятия.
Поэтому, тезисно:
* Изменение данных на фоне долгой операции чтения может приводить к исчерпанию * Изменение данных на фоне долгой операции чтения может
места в БД. приводить к исчерпанию места в БД.
* После чего любая попытка обновить данные будет приводить к ошибке `MAP_FULL` * После чего любая попытка обновить данные будет приводить к
до завершения долгой операции чтения. ошибке `MAP_FULL` до завершения долгой операции чтения.
* Характерными примерами долгих чтений являются горячее резервное копирования * Характерными примерами долгих чтений являются горячее
и отладка клиентского приложения при активной транзакции чтения. резервное копирования и отладка клиентского приложения при
активной транзакции чтения.
* В оригинальной LMDB после этого будет наблюдаться устойчивая деградация * В оригинальной _LMDB_ после этого будет наблюдаться
производительности всех механизмов обратной записи на диск (в I/O контроллере, устойчивая деградация производительности всех механизмов
в гипервизоре, в ядре ОС). обратной записи на диск (в I/O контроллере, в гипервизоре,
в ядре ОС).
* В MDBX предусмотрен механизм аварийного прерывания таких операций, а также * В _libmdbx_ предусмотрен механизм аварийного прерывания таких
режим `LIFO RECLAIM` устраняющий последующую деградацию производительности. операций, а также режим `LIFO RECLAIM` устраняющий последующую
деградацию производительности.
Операции чтения выполняются в контексте снимка данных (версии БД), который был Операции чтения выполняются в контексте снимка данных (версии
актуальным на момент старта транзакции чтения. Такой читаемый снимок БД), который был актуальным на момент старта транзакции чтения.
поддерживается неизменным до завершения операции. В свою очередь, это не Такой читаемый снимок поддерживается неизменным до завершения
позволяет повторно использовать страницы БД в последующих версиях (снимках операции. В свою очередь, это не позволяет повторно
БД). использовать страницы БД в последующих версиях (снимках БД).
Другими словами, если обновление данных выполняется на фоне долгой операции Другими словами, если обновление данных выполняется на фоне
чтения, то вместо повторного использования "старых" ненужных страниц будут долгой операции чтения, то вместо повторного использования
выделяться новые, так как "старые" страницы составляют снимок БД, который еще "старых" ненужных страниц будут выделяться новые, так как
"старые" страницы составляют снимок БД, который еще
используется долгой операцией чтения. используется долгой операцией чтения.
В результате, при интенсивном изменении данных и достаточно длительной В результате, при интенсивном изменении данных и достаточно
операции чтения, в БД могут быть исчерпаны свободные страницы, что не позволит длительной операции чтения, в БД могут быть исчерпаны свободные
создавать новые снимки/версии БД. Такая ситуация будет сохраняться до страницы, что не позволит создавать новые снимки/версии БД.
завершения операции чтения, которая использует старый снимок данных и Такая ситуация будет сохраняться до завершения операции чтения,
препятствует повторному использованию страниц БД. которая использует старый снимок данных и препятствует
повторному использованию страниц БД.
Однако, на этом проблемы не заканчиваются. После описанной ситуации, все Однако, на этом проблемы не заканчиваются. После описанной
дополнительные страницы, которые были выделены пока переработка старых была ситуации, все дополнительные страницы, которые были выделены
невозможна, будут участвовать в цикле выделения/освобождения до конца жизни пока переработка старых была невозможна, будут участвовать в
экземпляра БД. В оригинальной LMDB этот цикл использования страниц работает по цикле выделения/освобождения до конца жизни экземпляра БД. В
принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому увеличение оригинальной _LMDB_ этот цикл использования страниц работает по
количества циркулирующий страниц, с точки зрения механизмов кэширования и/или принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому
обратной записи, выглядит как увеличение рабочего набор данных. Проще говоря, увеличение количества циркулирующий страниц, с точки зрения
однократное попадание в ситуацию "уснувшего читателя" приводит к устойчивому механизмов кэширования и/или обратной записи, выглядит как
эффекту вымывания I/O кэша при всех последующих изменениях данных. увеличение рабочего набор данных. Проще говоря, однократное
попадание в ситуацию "уснувшего читателя" приводит к
устойчивому эффекту вымывания I/O кэша при всех последующих
изменениях данных.
Для решения описанных проблемы в MDBX сделаны существенные доработки, см. Для устранения описанных проблемы в _libmdbx_ сделаны
ниже. Иллюстрации к проблеме "долгих чтений" можно найти в [слайдах существенные доработки, подробности ниже. Иллюстрации к
презентации](http://www.slideshare.net/leoyuriev/lmdb). Там же приведен пример проблеме "долгих чтений" можно найти в [слайдах
количественной оценки прироста производительности за счет эффективной работы презентации](http://www.slideshare.net/leoyuriev/lmdb).
[BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO RECLAIM` в Там же приведен пример количественной оценки прироста
MDBX. производительности за счет эффективной работы
[BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO
RECLAIM` в _libmdbx_.
## Доработки MDBX #### Вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`
При работе в режиме `WRITEMAP+MAPSYNC` запись измененных
страниц выполняется ядром ОС, что имеет ряд преимуществ. Так
например, при крахе приложения, ядро ОС сохранит все изменения.
Однако, при аварийном отключении питания или сбое в ядре ОС, на
диске будет сохранена только часть измененных страниц БД. При
этом с большой вероятностью может оказаться так, что будут
сохранены мета-страницы со ссылками на страницы с новыми
версиями данных, но не сами новые данные. В этом случае БД
будет безвозвратна разрушена, даже если до аварии производилась
полная синхронизация данных (посредством `mdb_env_sync()`).
В _libmdbx_ эта проблема устранена, подробности ниже.
Доработки _libmdbx_
===================
1. Режим `LIFO RECLAIM`. 1. Режим `LIFO RECLAIM`.
Для повторного использования выбираются не самые старые, а самые новые Для повторного использования выбираются не самые старые, а
страницы из доступных. За счет этого цикл использования страниц всегда самые новые страницы из доступных. За счет этого цикл
имеет минимальную длину и не зависит от общего числа выделенных страниц. использования страниц всегда имеет минимальную длину и не
зависит от общего числа выделенных страниц.
В результате механизмы кэширования и обратной записи работают с В результате механизмы кэширования и обратной записи работают с
максимально возможной эффективностью. В случае использования контроллера максимально возможной эффективностью. В случае использования
дисков или системы хранения с [BBWC](https://en.wikipedia.org/wiki/BBWC) контроллера дисков или системы хранения с
возможно многократное увеличение производительности по записи [BBWC](https://en.wikipedia.org/wiki/BBWC) возможно
(обновлению данных). многократное увеличение производительности по записи
(обновлению данных).
2. Обработчик `OOM-KICK`. 2. Обработчик `OOM-KICK`.
Посредством `mdbx_env_set_oomfunc()` может быть Посредством `mdbx_env_set_oomfunc()` может быть установлен
установлен внешний обработчик (callback), который будет вызван при исчерпания внешний обработчик (callback), который будет вызван при
свободных страниц из-за долгой операцией чтения. Обработчику будет передан PID исчерпания свободных страниц из-за долгой операцией чтения.
и pthread_id. В свою очередь обработчик может предпринять одно из действий: Обработчику будет передан PID и pthread_id. В свою очередь
обработчик может предпринять одно из действий:
* отправить сигнал kill (#9), если долгое чтение выполняется сторонним процессом; * отправить сигнал kill (#9), если долгое чтение выполняется
* отменить или перезапустить проблемную операцию чтения, если операция сторонним процессом;
выполняется одним из потоков текущего процесса;
* подождать некоторое время, в расчете что проблемная операция чтения будет
штатно завершена;
* перервать текущую операцию изменения данных с возвратом кода ошибки.
3. Гарантия сохранности БД в режиме `WRITEMAP`. * отменить или перезапустить проблемную операцию чтения, если
операция выполняется одним из потоков текущего процесса;
При работе в режиме `WRITEMAP` запись измененных страниц выполняется ядром ОС, * подождать некоторое время, в расчете что проблемная операция
что имеет ряд преимуществ. Так например, при крахе приложения, ядро ОС чтения будет штатно завершена;
сохранит все изменения.
Однако, при аварийном отключении питания или сбое в ядре ОС, на диске будет * перервать текущую операцию изменения данных с возвратом кода
сохранена только часть измененных страниц БД. При этом с большой вероятностью ошибки.
может оказаться так, что будут сохранены мета-страницы со ссылками на страницы
с новыми версиями данных, но не сами новые данные. В этом случае БД будет
безвозвратна разрушена, даже если до аварии производилась полная синхронизация
данных (посредством `mdb_env_sync()`).
В MDBX эта проблема решена путем полной переработки пути записи данных: 3. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`.
* В режиме `WRITEMAP` MDBX не обновляет мета-страницы непосредственно, При работе в режиме `WRITEMAP+MAPSYNC` запись измененных
а поддерживает их теневые копии с переносом изменений после фиксации страниц выполняется ядром ОС, что имеет ряд преимуществ. Так
данных. например, при крахе приложения, ядро ОС сохранит все изменения.
* При завершении транзакций, в зависимости от состояния Однако, при аварийном отключении питания или сбое в ядре ОС, на
синхронности данных между диском и оперативной память, MDBX помечает диске будет сохранена только часть измененных страниц БД. При
точки фиксации либо как сильные (strong), либо как слабые (weak). Так этом с большой вероятностью может оказаться так, что будут
например, в режиме `WRITEMAP` завершаемые транзакции помечаются как сохранены мета-страницы со ссылками на страницы с новыми
слабые, а при явной синхронизации данных как сильные. версиями данных, но не сами новые данные. В этом случае БД
будет безвозвратна разрушена, даже если до аварии производилась
полная синхронизация данных (посредством `mdb_env_sync()`).
* При открытии БД В _libmdbx_ эта проблема устранена путем полной переработки
выполняется автоматический откат к последней сильной фиксации. Этим пути записи данных:
обеспечивается гарантия сохранности БД.
К сожалению, такая гарантия надежности не дается бесплатно. Для сохранности * В режиме `WRITEMAP+MAPSYNC` _libmdbx_ не обновляет
данных, страницы формирующие крайний снимок с сильной фиксацией, не должны мета-страницы непосредственно, а поддерживает их теневые копии
повторно использоваться (перезаписываться) до формирования следующей сильной с переносом изменений после фиксации данных.
точки фиксации. Таким образом, крайняя точки фиксации создает описанный выше
эффект "долгого чтения", с разницей в том, что при исчерпании свободных
страниц автоматически будет сформирована новая точка сильной фиксации.
В последующих версиях MDBX будут предусмотрены средства для асинхронной записи * При завершении транзакций, в зависимости от состояния
данных на диск с формированием сильных точек фиксации. синхронности данных между диском и оперативной память,
_libmdbx_ помечает точки фиксации либо как сильные (strong),
либо как слабые (weak). Так например, в режиме
`WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как
слабые, а при явной синхронизации данных как сильные.
4. Возможность автоматического формирования контрольных точек (сброса данных * При открытии БД выполняется автоматический откат к последней
на диск) при накоплении заданного объёма изменений, устанавливаемого функцией сильной фиксации. Этим обеспечивается гарантия сохранности БД.
К сожалению, такая гарантия надежности не дается бесплатно. Для
сохранности данных, страницы формирующие крайний снимок с
сильной фиксацией, не должны повторно использоваться
(перезаписываться) до формирования следующей сильной точки
фиксации. Таким образом, крайняя точка фиксации создает
описанный выше эффект "долгого чтения". Разница же здесь в том,
что при исчерпании свободных страниц ситуация будет
автоматически исправлена, посредством записи изменений на диск
и формированием новой сильной точки фиксации.
В последующих версиях _libmdbx_ будут предусмотрены средства
для асинхронной записи данных на диск с автоматическим
формированием сильных точек фиксации.
4. Возможность автоматического формирования контрольных точек
(сброса данных на диск) при накоплении заданного объёма
изменений, устанавливаемого функцией
`mdbx_env_set_syncbytes()`. `mdbx_env_set_syncbytes()`.
5. Возможность получить отставание текущей транзакции чтения от последней 5. Возможность получить отставание текущей транзакции чтения от
версии данных в БД посредством `mdbx_txn_straggler()`. последней версии данных в БД посредством
`mdbx_txn_straggler()`.
6. Утилита mdbx_chk для проверки БД и функция `mdbx_env_pgwalk()` для обхода 6. Утилита mdbx_chk для проверки БД и функция
всех страниц БД. `mdbx_env_pgwalk()` для обхода всех страниц БД.
7. Управление отладкой и получение отладочных сообщений посредством 7. Управление отладкой и получение отладочных сообщений
`mdbx_setup_debug()`. посредством `mdbx_setup_debug()`.
8. Возможность связать с каждой завершаемой транзакцией до 3 дополнительных 8. Возможность связать с каждой завершаемой транзакцией до 3
маркеров посредством `mdbx_canary_put()`, и прочитать их в транзакции чтения дополнительных маркеров посредством `mdbx_canary_put()`, и
посредством `mdbx_canary_get()`. прочитать их в транзакции чтения посредством
`mdbx_canary_get()`.
9. Возможность узнать есть ли за текущей позицией курсора строка данных 9. Возможность узнать есть ли за текущей позицией курсора
посредством `mdbx_cursor_eof()`. строка данных посредством `mdbx_cursor_eof()`.
10. Возможность явно запросить обновление существующей записи, без создания 10. Возможность явно запросить обновление существующей записи,
новой посредством флажка `MDB_CURRENT` для `mdb_put()`. без создания новой посредством флажка `MDB_CURRENT` для
`mdb_put()`.
11. Возможность обновить или удалить запись с получением предыдущего значения 11. Возможность обновить или удалить запись с получением
данных посредством `mdbx_replace()`. предыдущего значения данных посредством `mdbx_replace()`.
12. Поддержка ключей нулевого размера. 12. Поддержка ключей нулевого размера.
13. Исправленный вариант `mdb_cursor_count()`, возвращающий корректное 13. Исправленный вариант `mdb_cursor_count()`, возвращающий
количество дубликатов для всех типов таблиц и любого положения курсора. корректное количество дубликатов для всех типов таблиц и любого
положения курсора.
14. Возможность открыть БД в эксклюзивном режиме посредством 14. Возможность открыть БД в эксклюзивном режиме посредством
`mdbx_env_open_ex()`, например в целях её проверки. `mdbx_env_open_ex()`, например в целях её проверки.
15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и 15. Возможность закрыть БД в "грязном" состоянии (без сброса
формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`. данных и формирования сильной точки фиксации) посредством
`mdbx_env_close_ex()`.
16. Возможность получить посредством `mdbx_env_info()` дополнительную 16. Возможность получить посредством `mdbx_env_info()`
информацию, включая номер самой старой версии БД (снимка данных), который дополнительную информацию, включая номер самой старой версии БД
используется одним из читателей. (снимка данных), который используется одним из читателей.