diff --git a/README.md b/README.md index 930699ca..d11e1ad7 100644 --- a/README.md +++ b/README.md @@ -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) -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 -Database](https://symas.com/products/lightning-memory-mapped-database/) -(известной под аббревиатурой -[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database)), с -рядом существенных доработок, которые перечислены ниже. +_libmdbx_ обновляет совместно используемый набор данных, никак +не мешая при этом параллельным операциям чтения, не применяя +атомарных операций к самим данным, и обеспечивая +согласованность при аварийной остановке в любой момент. Поэтому +_libmdbx_ позволяя строить системы с линейным масштабированием +производительности чтения/поиска по ядрам CPU и амортизационной +стоимостью любых операций Olog(N). -Изначально модификация производилась в составе исходного кода проекта -[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за год работы -внесенные изменения приобрели самостоятельную ценность. - -Осенью 2015 доработанный движок был выделен в отдельный проект, который был -[представлен на конференции Highload++ -2015](http://www.highload.ru/2015/abstracts/1831.html). +### История +_libmdbx_ является потомком "Lightning Memory-Mapped Database", +известной под аббревиатурой +[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database). +Изначально доработка производилась в составе проекта +[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за +год работы внесенные изменения приобрели самостоятельную +ценность. Осенью 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), ключи всегда - отсортированы, поддерживается выборка диапазонов (range lookups). +### Общее для оригинальной _LMDB_ и _libmdbx_ -* Транзакции согласно [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). +1. Данные хранятся в упорядоченном отображении (ordered map), ключи всегда + отсортированы, поддерживается выборка диапазонов (range lookups). -* Чтение [без - блокировок](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). - Мьютексы захватываются только при старте и завершении сеанса работы с БД. +2. Данные отображается в память каждого работающего с БД процесса. + Ключам и данным обеспечивается прямой доступ без необходимости их + копирования, так как они защищены транзакцией чтения и не изменяются. -* Читатели не конкурируют между собой, чтение масштабируется линейно по ядрам 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), - [WAF](https://en.wikipedia.org/wiki/Write_amplification) и RAF также Olog(N). +5. Эффективное хранение дубликатов (ключей с несколькими + значениями), без дублирования ключей, с сортировкой значений, в + том числе целочисленных (для вторичных индексов). -* Нет [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) обуславливает относительно большой [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) страниц, что расходует [пропускную способность оперативной памяти](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 после этого будет наблюдаться устойчивая деградация - производительности всех механизмов обратной записи на диск (в I/O контроллере, - в гипервизоре, в ядре ОС). +* В оригинальной _LMDB_ после этого будет наблюдаться + устойчивая деградация производительности всех механизмов + обратной записи на диск (в I/O контроллере, в гипервизоре, + в ядре ОС). -* В MDBX предусмотрен механизм аварийного прерывания таких операций, а также - режим `LIFO RECLAIM` устраняющий последующую деградацию производительности. +* В _libmdbx_ предусмотрен механизм аварийного прерывания таких + операций, а также режим `LIFO RECLAIM` устраняющий последующую + деградацию производительности. -Операции чтения выполняются в контексте снимка данных (версии БД), который был -актуальным на момент старта транзакции чтения. Такой читаемый снимок -поддерживается неизменным до завершения операции. В свою очередь, это не -позволяет повторно использовать страницы БД в последующих версиях (снимках -БД). +Операции чтения выполняются в контексте снимка данных (версии +БД), который был актуальным на момент старта транзакции чтения. +Такой читаемый снимок поддерживается неизменным до завершения +операции. В свою очередь, это не позволяет повторно +использовать страницы БД в последующих версиях (снимках БД). -Другими словами, если обновление данных выполняется на фоне долгой операции -чтения, то вместо повторного использования "старых" ненужных страниц будут -выделяться новые, так как "старые" страницы составляют снимок БД, который еще +Другими словами, если обновление данных выполняется на фоне +долгой операции чтения, то вместо повторного использования +"старых" ненужных страниц будут выделяться новые, так как +"старые" страницы составляют снимок БД, который еще используется долгой операцией чтения. -В результате, при интенсивном изменении данных и достаточно длительной -операции чтения, в БД могут быть исчерпаны свободные страницы, что не позволит -создавать новые снимки/версии БД. Такая ситуация будет сохраняться до -завершения операции чтения, которая использует старый снимок данных и -препятствует повторному использованию страниц БД. +В результате, при интенсивном изменении данных и достаточно +длительной операции чтения, в БД могут быть исчерпаны свободные +страницы, что не позволит создавать новые снимки/версии БД. +Такая ситуация будет сохраняться до завершения операции чтения, +которая использует старый снимок данных и препятствует +повторному использованию страниц БД. -Однако, на этом проблемы не заканчиваются. После описанной ситуации, все -дополнительные страницы, которые были выделены пока переработка старых была -невозможна, будут участвовать в цикле выделения/освобождения до конца жизни -экземпляра БД. В оригинальной LMDB этот цикл использования страниц работает по -принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому увеличение -количества циркулирующий страниц, с точки зрения механизмов кэширования и/или -обратной записи, выглядит как увеличение рабочего набор данных. Проще говоря, -однократное попадание в ситуацию "уснувшего читателя" приводит к устойчивому -эффекту вымывания I/O кэша при всех последующих изменениях данных. +Однако, на этом проблемы не заканчиваются. После описанной +ситуации, все дополнительные страницы, которые были выделены +пока переработка старых была невозможна, будут участвовать в +цикле выделения/освобождения до конца жизни экземпляра БД. В +оригинальной _LMDB_ этот цикл использования страниц работает по +принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому +увеличение количества циркулирующий страниц, с точки зрения +механизмов кэширования и/или обратной записи, выглядит как +увеличение рабочего набор данных. Проще говоря, однократное +попадание в ситуацию "уснувшего читателя" приводит к +устойчивому эффекту вымывания I/O кэша при всех последующих +изменениях данных. -Для решения описанных проблемы в MDBX сделаны существенные доработки, см. -ниже. Иллюстрации к проблеме "долгих чтений" можно найти в [слайдах -презентации](http://www.slideshare.net/leoyuriev/lmdb). Там же приведен пример -количественной оценки прироста производительности за счет эффективной работы -[BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO RECLAIM` в -MDBX. +Для устранения описанных проблемы в _libmdbx_ сделаны +существенные доработки, подробности ниже. Иллюстрации к +проблеме "долгих чтений" можно найти в [слайдах +презентации](http://www.slideshare.net/leoyuriev/lmdb). +Там же приведен пример количественной оценки прироста +производительности за счет эффективной работы +[BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO +RECLAIM` в _libmdbx_. -## Доработки MDBX +#### Вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC` + +При работе в режиме `WRITEMAP+MAPSYNC` запись измененных +страниц выполняется ядром ОС, что имеет ряд преимуществ. Так +например, при крахе приложения, ядро ОС сохранит все изменения. + +Однако, при аварийном отключении питания или сбое в ядре ОС, на +диске будет сохранена только часть измененных страниц БД. При +этом с большой вероятностью может оказаться так, что будут +сохранены мета-страницы со ссылками на страницы с новыми +версиями данных, но не сами новые данные. В этом случае БД +будет безвозвратна разрушена, даже если до аварии производилась +полная синхронизация данных (посредством `mdb_env_sync()`). + +В _libmdbx_ эта проблема устранена, подробности ниже. + + +Доработки _libmdbx_ +=================== 1. Режим `LIFO RECLAIM`. - Для повторного использования выбираются не самые старые, а самые новые - страницы из доступных. За счет этого цикл использования страниц всегда - имеет минимальную длину и не зависит от общего числа выделенных страниц. + Для повторного использования выбираются не самые старые, а + самые новые страницы из доступных. За счет этого цикл + использования страниц всегда имеет минимальную длину и не + зависит от общего числа выделенных страниц. - В результате механизмы кэширования и обратной записи работают с - максимально возможной эффективностью. В случае использования контроллера - дисков или системы хранения с [BBWC](https://en.wikipedia.org/wiki/BBWC) - возможно многократное увеличение производительности по записи - (обновлению данных). + В результате механизмы кэширования и обратной записи работают с + максимально возможной эффективностью. В случае использования + контроллера дисков или системы хранения с + [BBWC](https://en.wikipedia.org/wiki/BBWC) возможно + многократное увеличение производительности по записи + (обновлению данных). 2. Обработчик `OOM-KICK`. - Посредством `mdbx_env_set_oomfunc()` может быть - установлен внешний обработчик (callback), который будет вызван при исчерпания - свободных страниц из-за долгой операцией чтения. Обработчику будет передан PID - и pthread_id. В свою очередь обработчик может предпринять одно из действий: + Посредством `mdbx_env_set_oomfunc()` может быть установлен + внешний обработчик (callback), который будет вызван при + исчерпания свободных страниц из-за долгой операцией чтения. + Обработчику будет передан 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()`. -5. Возможность получить отставание текущей транзакции чтения от последней -версии данных в БД посредством `mdbx_txn_straggler()`. +5. Возможность получить отставание текущей транзакции чтения от +последней версии данных в БД посредством +`mdbx_txn_straggler()`. -6. Утилита mdbx_chk для проверки БД и функция `mdbx_env_pgwalk()` для обхода -всех страниц БД. +6. Утилита mdbx_chk для проверки БД и функция +`mdbx_env_pgwalk()` для обхода всех страниц БД. -7. Управление отладкой и получение отладочных сообщений посредством -`mdbx_setup_debug()`. +7. Управление отладкой и получение отладочных сообщений +посредством `mdbx_setup_debug()`. -8. Возможность связать с каждой завершаемой транзакцией до 3 дополнительных -маркеров посредством `mdbx_canary_put()`, и прочитать их в транзакции чтения -посредством `mdbx_canary_get()`. +8. Возможность связать с каждой завершаемой транзакцией до 3 +дополнительных маркеров посредством `mdbx_canary_put()`, и +прочитать их в транзакции чтения посредством +`mdbx_canary_get()`. -9. Возможность узнать есть ли за текущей позицией курсора строка данных -посредством `mdbx_cursor_eof()`. +9. Возможность узнать есть ли за текущей позицией курсора +строка данных посредством `mdbx_cursor_eof()`. -10. Возможность явно запросить обновление существующей записи, без создания -новой посредством флажка `MDB_CURRENT` для `mdb_put()`. +10. Возможность явно запросить обновление существующей записи, +без создания новой посредством флажка `MDB_CURRENT` для +`mdb_put()`. -11. Возможность обновить или удалить запись с получением предыдущего значения -данных посредством `mdbx_replace()`. +11. Возможность обновить или удалить запись с получением +предыдущего значения данных посредством `mdbx_replace()`. 12. Поддержка ключей нулевого размера. -13. Исправленный вариант `mdb_cursor_count()`, возвращающий корректное -количество дубликатов для всех типов таблиц и любого положения курсора. +13. Исправленный вариант `mdb_cursor_count()`, возвращающий +корректное количество дубликатов для всех типов таблиц и любого +положения курсора. 14. Возможность открыть БД в эксклюзивном режиме посредством `mdbx_env_open_ex()`, например в целях её проверки. -15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и -формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`. +15. Возможность закрыть БД в "грязном" состоянии (без сброса +данных и формирования сильной точки фиксации) посредством +`mdbx_env_close_ex()`. -16. Возможность получить посредством `mdbx_env_info()` дополнительную -информацию, включая номер самой старой версии БД (снимка данных), который -используется одним из читателей. +16. Возможность получить посредством `mdbx_env_info()` +дополнительную информацию, включая номер самой старой версии БД +(снимка данных), который используется одним из читателей.