mdbx: refine README.

Change-Id: I91192a5ac1464677432956a0dfd7038bac9b021f
This commit is contained in:
Leo Yuriev 2017-07-21 01:59:05 +03:00
parent c8a5df650b
commit 109be210b4
2 changed files with 183 additions and 131 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
*[~#] *[~#]
*.[ao] *.[ao]
*.bak *.bak
.le.ini
core core
core.* core.*
*.exe *.exe

313
README.md
View File

@ -14,21 +14,29 @@ and [by Yandex](https://translate.yandex.ru/translate?url=https%3A%2F%2Fgithub.c
## Кратко ## Кратко
_libmdbx_ - это встраиваемый key-value движок хранения со специфическим _libmdbx_ - это встраиваемый key-value движок хранения со специфическим
набором возможностей, которые при правильном применении позволяют набором свойств и возможностей, ориентированный на создание уникальных
создавать уникальные решения с чемпионской производительностью, идеально легковесных решений с предельной производительностью.
сочетаясь с технологией
[MRAM](https://en.wikipedia.org/wiki/Magnetoresistive_random-access_memory). _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_ умеет обновлять совместно используемый набор данных, никак не
мешая при этом параллельным операциям чтения, не применяя атомарных
операций к самим данным, и обеспечивая согласованность при аварийной
остановке в любой момент. Поэтому _libmdbx_ позволяя строить системы с
линейным масштабированием производительности чтения/поиска по ядрам CPU
и амортизационной стоимостью любых операций Olog(N).
### История ### История
_libmdbx_ является потомком "Lightning Memory-Mapped Database", _libmdbx_ является развитием "Lightning Memory-Mapped Database",
известной под аббревиатурой известной под аббревиатурой
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database). [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
Изначально доработка производилась в составе проекта Изначально доработка производилась в составе проекта
@ -50,13 +58,13 @@ Technologies](https://www.ptsecurity.ru).
_libmdbx_ наследует все ключевые возможности и особенности от _libmdbx_ наследует все ключевые возможности и особенности от
своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database), своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database),
с устранением описанных далее проблем и архитектурных недочетов. но с устранением ряда описываемых далее проблем и архитектурных недочетов.
1. Данные хранятся в упорядоченном отображении (ordered map), ключи всегда 1. Данные хранятся в упорядоченном отображении (ordered map), ключи всегда
отсортированы, поддерживается выборка диапазонов (range lookups). отсортированы, поддерживается выборка диапазонов (range lookups).
2. Данные отображается в память каждого работающего с БД процесса. 2. Данные отображается в память каждого работающего с БД процесса.
К данным и ключам обеспечивается прямой доступ без необходимости их К данным и ключам обеспечивается прямой доступ в памяти без необходимости их
копирования. копирования.
3. Транзакции согласно 3. Транзакции согласно
@ -71,6 +79,10 @@ _libmdbx_ наследует все ключевые возможности и
без [атомарных операций](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). без [атомарных операций](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. между собой, чтение масштабируется линейно по ядрам CPU.
> Для точности следует отметить, что "подключение к БД" (старт первой
> читающей транзакции в потоке) и "отключение от БД" (закрытие БД или
> завершение потока) требуют краткосрочного захвата блокировки для
> регистрации/дерегистрации текущего потока в "таблице читателей".
5. Эффективное хранение дубликатов (ключей с несколькими 5. Эффективное хранение дубликатов (ключей с несколькими
значениями), без дублирования ключей, с сортировкой значений, в значениями), без дублирования ключей, с сортировкой значений, в
@ -79,7 +91,8 @@ _libmdbx_ наследует все ключевые возможности и
6. Эффективная поддержка коротких ключей фиксированной длины, в том числе целочисленных. 6. Эффективная поддержка коротких ключей фиксированной длины, в том числе целочисленных.
7. Амортизационная стоимость любой операции Olog(N), 7. Амортизационная стоимость любой операции Olog(N),
[WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write Amplification Factor) и RAF (Read Amplification Factor) также 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) и журнала 8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала
транзакций, после сбоев не требуется восстановление. Не требуется компактификация транзакций, после сбоев не требуется восстановление. Не требуется компактификация
@ -92,9 +105,9 @@ _libmdbx_ наследует все ключевые возможности и
Сравнение производительности Сравнение производительности
============================ ============================
Все данные получены многократным прогоном тестов на ноутбуке Lenovo Все представленные ниже данные получены многократным прогоном тестов на
Carbon-2, i7-4600U 2.1 ГГц, 8 Гб ОЗУ, с SSD-диском SAMSUNG ноутбуке Lenovo Carbon-2, i7-4600U 2.1 ГГц, 8 Гб ОЗУ, с SSD-диском
MZNTD512HAGL-000L1 (DXT23L0Q) 512 Гб. SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Гб.
Исходный код бенчмарка [_IOArena_](https://github.com/pmwkaa/ioarena) и Исходный код бенчмарка [_IOArena_](https://github.com/pmwkaa/ioarena) и
сценарии тестирования [доступны на сценарии тестирования [доступны на
@ -105,7 +118,7 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
### Интегральная производительность ### Интегральная производительность
![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/ReOpen/libmdbx/img/perf-slide-1.png) ![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/ReOpen/libmdbx/img/perf-slide-1.png)
Показана соотнесенная сумма показателей производительности в трёх Показана соотнесенная сумма ключевых показателей производительности в трёх
бенчмарках: бенчмарках:
- Чтение/Поиск на машине с 4-мя процессорами; - Чтение/Поиск на машине с 4-мя процессорами;
@ -121,7 +134,7 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
*Бенчмарк в режиме асинхронной записи не включен по двум причинам:* *Бенчмарк в режиме асинхронной записи не включен по двум причинам:*
1. Такое сравнение не совсем правомочно, его следует делать с движками 1. Такое сравнение не совсем правомочно, его следует делать с движками
ориентированными на хранение данных в памяти (Tarantool, Redis). ориентированными на хранение данных в памяти ([Tarantool](https://tarantool.io/), [Redis](https://redis.io/)).
2. Превосходство libmdbx становится еще более подавляющем, что мешает 2. Превосходство libmdbx становится еще более подавляющем, что мешает
восприятию информации. восприятию информации.
@ -133,7 +146,7 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
Для каждого движка показана суммарная производительность при Для каждого движка показана суммарная производительность при
одновременном выполнении запросов чтения/поиска в 1-2-4-8 потоков на одновременном выполнении запросов чтения/поиска в 1-2-4-8 потоков на
машине с 4-мя процессорами. машине с 4-мя физическими процессорами.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -141,12 +154,12 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/ReOpen/libmdbx/img/perf-slide-3.png) ![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/ReOpen/libmdbx/img/perf-slide-3.png)
- Линейная шкала слева и темные прямоугольники соответствуют количеству - Линейная шкала слева и темные прямоугольники соответствуют количеству
транзакций в секунду, усредненному за все время теста. транзакций в секунду, усредненному за всё время теста.
- Логарифмическая шкала справа и желтые интервальные отрезки - Логарифмическая шкала справа и желтые интервальные отрезки
соответствуют времени выполнения транзакций. При этом каждый отрезок соответствуют времени выполнения транзакций. При этом каждый отрезок
показывает минимальное и максимальное время затраченной на выполнения показывает минимальное и максимальное время затраченное на выполнение
транзакций, а крестик показывает среднеквадратичное значение. транзакций, а крестиком отмечено среднеквадратичное значение.
Выполняется **10.000 транзакций в режиме синхронной фиксации данных** на Выполняется **10.000 транзакций в режиме синхронной фиксации данных** на
диске. При этом требуется гарантия, что при аварийном выключении питания диске. При этом требуется гарантия, что при аварийном выключении питания
@ -155,23 +168,40 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
режиме при фиксации каждой транзакции выполняется системный вызов режиме при фиксации каждой транзакции выполняется системный вызов
[fdatasync](https://linux.die.net/man/2/fdatasync). [fdatasync](https://linux.die.net/man/2/fdatasync).
В каждой транзакции выполняется CRUD-операция (две вставки, одной В каждой транзакции выполняется комбинированная CRUD-операция (две
чтение, одно обновление, одно удаление). Бенчмарк стартует на пустой вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует
базе и в результате выполняемых действий при завершении в базе на пустой базе, а при завершении, в результате выполняемых действий, в
насчитывается 10.000 небольших key-value записей. базе насчитывается 10.000 небольших key-value записей.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
### Отложенная фиксация ### Отложенная фиксация
![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/ReOpen/libmdbx/img/perf-slide-4.png) ![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/ReOpen/libmdbx/img/perf-slide-4.png)
- Линейная шкала слева и темные прямоугольники соответствуют количеству транзакций в секунду, усредненному за все время теста. - Линейная шкала слева и темные прямоугольники соответствуют количеству
- Логарифмическая шкала справа и желтые интервальные отрезки соответствуют времени выполнения транзакций. При этом каждый отрезок показывает минимальное и максимальное время затраченной на выполнения транзакций, а крестик показывает среднеквадратичное значение. транзакций в секунду, усредненному за всё время теста.
Выполняется **100.000 транзакций в режиме отложенной фиксации данных** на диске. При этом требуется гарантия, что при аварийном выключении питания (или другом подобном сбое) все данные будут консистентны на момент завершения одной из транзакций, но допускается потеря изменений из некоторого количества последних транзакций, что для многих движков предполагает включение [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) (write-ahead logging) либо журнала транзакций, который в свою очередь опирается на гарантию упорядоченности данных в журналируемой файловой системе. _libmdbx_ при этом не ведет WAL, а передает весь контроль файловой системе и ядру ОС. - Логарифмическая шкала справа и желтые интервальные отрезки
соответствуют времени выполнения транзакций. При этом каждый отрезок
показывает минимальное и максимальное время затраченное на выполнение
транзакций, а крестиком отмечено среднеквадратичное значение.
В каждой транзакции выполняется CRUD-операция (две вставки, одной чтение, одно обновление, одно удаление). Выполняется **100.000 транзакций в режиме отложенной фиксации данных**
Бенчмарк стартует на пустой базе и в результате выполняемых действий при завершении в базе насчитывается 100.000 небольших key-value записей. на диске. При этом требуется гарантия, что при аварийном выключении
питания (или другом подобном сбое) все данные будут консистентны на
момент завершения одной из транзакций, но допускается потеря изменений
из некоторого количества последних транзакций, что для многих движков
предполагает включение
[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) (write-ahead
logging) либо журнала транзакций, который в свою очередь опирается на
гарантию упорядоченности данных в журналируемой файловой системе.
_libmdbx_ при этом не ведет WAL, а передает весь контроль файловой
системе и ядру ОС.
В каждой транзакции выполняется комбинированная CRUD-операция (две
вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует
на пустой базе, а при завершении, в результате выполняемых действий, в
базе насчитывается 100.000 небольших key-value записей.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -179,28 +209,28 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/ReOpen/libmdbx/img/perf-slide-5.png) ![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/ReOpen/libmdbx/img/perf-slide-5.png)
- Линейная шкала слева и темные прямоугольники соответствуют количеству - Линейная шкала слева и темные прямоугольники соответствуют количеству
транзакций в секунду, усредненному за все время теста. транзакций в секунду, усредненному за всё время теста.
- Логарифмическая шкала справа и желтые интервальные отрезки - Логарифмическая шкала справа и желтые интервальные отрезки
соответствуют времени выполнения транзакций. При этом каждый отрезок соответствуют времени выполнения транзакций. При этом каждый отрезок
показывает минимальное и максимальное время затраченной на выполнения показывает минимальное и максимальное время затраченное на выполнение
транзакций, а крестик показывает среднеквадратичное значение. транзакций, а крестиком отмечено среднеквадратичное значение.
Выполняется **1.000.000 транзакций в режиме асинхронной фиксации Выполняется **1.000.000 транзакций в режиме асинхронной фиксации
данных** на диске. При этом требуется гарантия, что при аварийном данных** на диске. При этом требуется гарантия, что при аварийном
выключении питания (или другом подобном сбое) все данные будут выключении питания (или другом подобном сбое) все данные будут
консистентны на момент завершения одной из транзакций, но допускается консистентны на момент завершения одной из транзакций, но допускается
потеря изменений из любого количества последних транзакций. Во всех потеря изменений из значительного количества последних транзакций. Во
движках при этом включался режим предполагающий минимальную нагрузку всех движках при этом включался режим предполагающий минимальную
записи на диск, и соответственно минимальную гарантию сохранности нагрузку на диск по-записи, и соответственно минимальную гарантию
данных. В _libmdbx_ при этом используется режим асинхронной записи сохранности данных. В _libmdbx_ при этом используется режим асинхронной
измененных страниц на диск силами ядра ОС посредством системрго вызова записи измененных страниц на диск посредством ядра ОС и системного
[msync(MS_ASYNC)](https://linux.die.net/man/2/msync). вызова [msync(MS_ASYNC)](https://linux.die.net/man/2/msync).
В каждой транзакции выполняется CRUD-операция (две вставки, одной В каждой транзакции выполняется комбинированная CRUD-операция (две
чтение, одно обновление, одно удаление). Бенчмарк стартует на пустой вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует
базе и в результате выполняемых действий при завершении в базе на пустой базе, а при завершении, в результате выполняемых действий, в
насчитывается 1.000.000 небольших key-value записей. базе насчитывается 10.000 небольших key-value записей.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -213,19 +243,19 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
- суммарное количество операций ввода-вывода (IOPS), как записи, так и - суммарное количество операций ввода-вывода (IOPS), как записи, так и
чтения. чтения.
- суммарное затраченное время процессора, как в режиме пользователя, - суммарное затраченное время процессора, как в режиме пользовательских процессов,
так и в режиме ядра ОС. так и в режиме ядра ОС.
- максимальный объем места на диске. который требовался во время работы - использованное место на диске при завершении теста, после закрытия БД из тестирующего процесса,
теста. но без ожидания всех внутренних операций обслуживания (компактификации LSM и т.п.).
Движок _ForestDB_ был исключен при окончательном формировании диаграммы, Движок _ForestDB_ был исключен при оформлении результатов, так как
так как многократно превысил потребление каждого из ресурсов (потратил относительно конкурентов многократно превысил потребление каждого из
процессорное время на генерацию IOPS для заполнения диска). Что не ресурсов (потратил процессорное время на генерацию IOPS для заполнения
позволяло наглядно сравнить показатели остальных движков на одной диска), что не позволяло наглядно сравнить показатели остальных движков
диаграмме. на одной диаграмме.
Все данные собирались посредством системного вывова Все данные собирались посредством системного вызова
[getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) и [getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) и
сканированием директорий с данными. сканированием директорий с данными.
@ -235,17 +265,27 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
1. Единовременно может выполняться не более одной транзакция изменения данных 1. Единовременно может выполняться не более одной транзакция изменения данных
(один писатель). Зато все изменения всегда последовательны, не может быть (один писатель). Зато все изменения всегда последовательны, не может быть
конфликтов или ошибок при откате транзакций. конфликтов или логических ошибок при откате транзакций.
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) (Write [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write
Amplification Factor). Поэтому фиксация изменений на диске может быть Amplification Factor). Поэтому фиксация изменений на диске может быть
дорогой и является главным ограничителем для производительности по достаточно дорогой и являться главным ограничением производительности
записи. В качестве компромисса предлагается несколько режимов ленивой при интенсивном изменении данных.
и/или периодической фиксации. В том числе режим `MAPASYNC`, при котором > В качестве компромисса _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) 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) выполняется на для реализации [MVCC](https://ru.wikipedia.org/wiki/MVCC) выполняется на
@ -255,16 +295,26 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
страниц, что расходует [пропускную способность оперативной страниц, что расходует [пропускную способность оперативной
памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является
основным ограничителем производительности в режиме `MAPASYNC`. основным ограничителем производительности в режиме `MAPASYNC`.
> Этот недостаток неустраним, тем не менее следует дать некоторые пояснения.
> Дело в том, что фиксация изменений на диске потребует гораздо более
> значительного копирования данных в памяти и массы других затратных операций.
> Поэтому обусловленное этим недостатком падение производительности становится
> заметным только при отказе от фиксации изменений на диске.
> Соответственно, корректнее сказать что _libmdbx_ позволяет
> получить персистентность ценой минимального падения производительности.
> Если же нет необходимости оперативно сохранять данные, то логичнее
> использовать `std::map`.
4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей), 4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей),
которая приводит к деградации производительности и переполнению БД. которая приводит к деградации производительности и переполнению БД.
В _libmdbx_ предложены средства для предотвращения, выхода из проблемной > В _libmdbx_ предложены средства для предотвращения, быстрого выхода из
ситуации и устранения её последствий. Подробности ниже. > некомфортной ситуации и устранения её последствий. Подробности ниже.
5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`. 5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`.
В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы, В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы,
так и согласованность данных. При этом также, в качестве альтернативы, так и согласованность данных.
предложен режим `UTTERLY_NOSYNC`. Подробности ниже. > Дополнительно, в качестве альтернативы, предложен режим `UTTERLY_NOSYNC`.
> Подробности ниже.
#### Проблема долгих чтений #### Проблема долгих чтений
@ -299,60 +349,55 @@ github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
деградацию производительности. деградацию производительности.
Операции чтения выполняются в контексте снимка данных (версии Операции чтения выполняются в контексте снимка данных (версии
БД), который был актуальным на момент старта транзакции чтения. БД), который был актуальным на момент старта транзакции чтения. Такой
Такой читаемый снимок поддерживается неизменным до завершения читаемый снимок поддерживается неизменным до завершения операции. В свою
операции. В свою очередь, это не позволяет повторно очередь, это не позволяет повторно использовать страницы БД в
использовать страницы БД в последующих версиях (снимках БД). последующих версиях (снимках БД).
Другими словами, если обновление данных выполняется на фоне Другими словами, если обновление данных выполняется на фоне долгой
долгой операции чтения, то вместо повторного использования операции чтения, то вместо повторного использования "старых" ненужных
"старых" ненужных страниц будут выделяться новые, так как страниц будут выделяться новые, так как "старые" страницы составляют
"старые" страницы составляют снимок БД, который еще снимок БД, который еще используется долгой операцией чтения.
используется долгой операцией чтения.
В результате, при интенсивном изменении данных и достаточно В результате, при интенсивном изменении данных и достаточно длительной
длительной операции чтения, в БД могут быть исчерпаны свободные операции чтения, в БД могут быть исчерпаны свободные страницы, что не
страницы, что не позволит создавать новые снимки/версии БД. позволит создавать новые снимки/версии БД. Такая ситуация будет
Такая ситуация будет сохраняться до завершения операции чтения, сохраняться до завершения операции чтения, которая использует старый
которая использует старый снимок данных и препятствует снимок данных и препятствует повторному использованию страниц БД.
повторному использованию страниц БД.
Однако, на этом проблемы не заканчиваются. После описанной Однако, на этом проблемы не заканчиваются. После описанной ситуации, все
ситуации, все дополнительные страницы, которые были выделены дополнительные страницы, которые были выделены пока переработка старых
пока переработка старых была невозможна, будут участвовать в была невозможна, будут участвовать в цикле выделения/освобождения до
цикле выделения/освобождения до конца жизни экземпляра БД. В конца жизни экземпляра БД. В оригинальной _LMDB_ этот цикл использования
оригинальной _LMDB_ этот цикл использования страниц работает по страниц работает по принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO).
принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому Поэтому увеличение количества циркулирующий страниц, с точки зрения
увеличение количества циркулирующий страниц, с точки зрения механизмов кэширования и/или обратной записи, выглядит как увеличение
механизмов кэширования и/или обратной записи, выглядит как рабочего набор данных. Проще говоря, однократное попадание в ситуацию
увеличение рабочего набор данных. Проще говоря, однократное "уснувшего читателя" приводит к устойчивому эффекту вымывания I/O кэша
попадание в ситуацию "уснувшего читателя" приводит к при всех последующих изменениях данных.
устойчивому эффекту вымывания I/O кэша при всех последующих
изменениях данных.
Для устранения описанных проблемы в _libmdbx_ сделаны Для устранения описанных проблемы в _libmdbx_ сделаны существенные
существенные доработки, подробности ниже. Иллюстрации к доработки, подробности ниже. Иллюстрации к проблеме "долгих чтений"
проблеме "долгих чтений" можно найти в [слайдах можно найти в [слайдах презентации](http://www.slideshare.net/leoyuriev/lmdb).
презентации](http://www.slideshare.net/leoyuriev/lmdb).
Там же приведен пример количественной оценки прироста Там же приведен пример количественной оценки прироста производительности
производительности за счет эффективной работы за счет эффективной работы [BBWC](https://en.wikipedia.org/wiki/BBWC)
[BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO при включении `LIFO RECLAIM` в _libmdbx_.
RECLAIM` в _libmdbx_.
#### Вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC` #### Вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`
При работе в режиме `WRITEMAP+MAPSYNC` запись измененных При работе в режиме `WRITEMAP+MAPSYNC` запись измененных страниц
страниц выполняется ядром ОС, что имеет ряд преимуществ. Так выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе
например, при крахе приложения, ядро ОС сохранит все изменения. приложения, ядро ОС сохранит все изменения.
Однако, при аварийном отключении питания или сбое в ядре ОС, на Однако, при аварийном отключении питания или сбое в ядре ОС, на диске
диске будет сохранена только часть измененных страниц БД. При будет сохранена только часть измененных страниц БД. При этом с большой
этом с большой вероятностью может оказаться так, что будут вероятностью может оказаться так, что будут сохранены мета-страницы со
сохранены мета-страницы со ссылками на страницы с новыми ссылками на страницы с новыми версиями данных, но не сами новые данные.
версиями данных, но не сами новые данные. В этом случае БД В этом случае БД будет безвозвратна разрушена, даже если до аварии
будет безвозвратна разрушена, даже если до аварии производилась производилась полная синхронизация данных (посредством
полная синхронизация данных (посредством `mdbx_env_sync()`). `mdbx_env_sync()`).
В _libmdbx_ эта проблема устранена, подробности ниже. В _libmdbx_ эта проблема устранена, подробности ниже.
@ -380,11 +425,11 @@ RECLAIM` в _libmdbx_.
Посредством `mdbx_env_set_oomfunc()` может быть установлен Посредством `mdbx_env_set_oomfunc()` может быть установлен
внешний обработчик (callback), который будет вызван при внешний обработчик (callback), который будет вызван при
исчерпания свободных страниц из-за долгой операцией чтения. исчерпания свободных страниц из-за долгой операцией чтения.
Обработчику будет передан PID и pthread_id. В свою очередь Обработчику будет передан PID и pthread_id виновника.
обработчик может предпринять одно из действий: В свою очередь обработчик может предпринять одно из действий:
* отправить сигнал kill (#9), если долгое чтение выполняется * нейтрализовать виновника (отправить сигнал kill #9), если
сторонним процессом; долгое чтение выполняется сторонним процессом;
* отменить или перезапустить проблемную операцию чтения, если * отменить или перезапустить проблемную операцию чтения, если
операция выполняется одним из потоков текущего процесса; операция выполняется одним из потоков текущего процесса;
@ -423,6 +468,14 @@ RECLAIM` в _libmdbx_.
`WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как `WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как
слабые, а при явной синхронизации данных как сильные. слабые, а при явной синхронизации данных как сильные.
* В _libmdbx_ поддерживается не две, а три отдельные мета-страницы.
Это позволяет выполнять фиксацию транзакций с формированием как
сильной, так и слабой точки фиксации, без потери двух предыдущих
точек фиксации (из которых одна может быть сильной, а вторая слабой).
В результате, _libmdbx_ позволяет в произвольном порядке чередовать
сильные и слабые точки фиксации без нарушения соответствующих
гарантий в случае неожиданной системной аварии во время фиксации.
* При открытии БД выполняется автоматический откат к последней * При открытии БД выполняется автоматический откат к последней
сильной фиксации. Этим обеспечивается гарантия сохранности БД. сильной фиксации. Этим обеспечивается гарантия сохранности БД.
@ -463,12 +516,12 @@ RECLAIM` в _libmdbx_.
10. Возможность явно запросить обновление существующей записи, без 10. Возможность явно запросить обновление существующей записи, без
создания новой посредством флажка `MDBX_CURRENT` для `mdbx_put()`. создания новой посредством флажка `MDBX_CURRENT` для `mdbx_put()`.
11. Возможность обновить или удалить запись с получением предыдущего 11. Возможность посредством `mdbx_replace()` обновить или удалить запись
значения данных посредством `mdbx_replace()`. с получением предыдущего значения данных, а также адресно изменить
конкретное multi-значение.
12. Поддержка ключей и значений нулевой длины. Включая сортированные 12. Поддержка ключей и значений нулевой длины, включая сортированные
дубликаты, в том числе вне зависимости от порядка их добавления или дубликаты.
обновления.
13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное 13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное
количество дубликатов для всех типов таблиц и любого положения курсора. количество дубликатов для всех типов таблиц и любого положения курсора.
@ -492,13 +545,14 @@ RECLAIM` в _libmdbx_.
компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`. компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`.
19. Возможность посредством `mdbx_is_dirty()` определить находятся ли 19. Возможность посредством `mdbx_is_dirty()` определить находятся ли
некоторый ключ или данные в "грязной" странице БД. Таким образом избегаю некоторый ключ или данные в "грязной" странице БД. Таким образом,
лишнего копирования данных перед выполнением модифицирующих операций избегая лишнего копирования данных перед выполнением модифицирующих
(значения в размещенные "грязных" страницах могут быть перезаписаны при операций (значения в размещенные "грязных" страницах могут быть
изменениях, иначе они будут неизменны). перезаписаны при изменениях, иначе они будут неизменны).
20. Корректное обновление текущей записи, в том числе сортированного 20. Корректное обновление текущей записи, в том числе сортированного
дубликата, при использовании режима `MDBX_CURRENT` в `mdbx_cursor_put()`. дубликата, при использовании режима `MDBX_CURRENT` в
`mdbx_cursor_put()`.
21. Все курсоры, как в транзакциях только для чтения, так и в пишущих, 21. Все курсоры, как в транзакциях только для чтения, так и в пишущих,
могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ
@ -535,12 +589,9 @@ mdbx_txn_abort() или mdbx_txn_reset(). Что позволяет избави
26. Генерация последовательностей посредством `mdbx_dbi_sequence()`. 26. Генерация последовательностей посредством `mdbx_dbi_sequence()`.
27. Обновление данных с одновременным получением старых значений, 27. Расширенное динамическое управление размером БД, включая выбор
а также адресное изменение конкретного multi-значения посредством `mdbx_replace()`. размера страницы посредством `mdbx_env_set_geometry()`.
28. Расширенное динамическое управление размером БД, включая выбор размера страницы 28. Три мета-страницы вместо двух, что позволяет гарантированно
посредством `mdbx_env_set_geometry()`. консистентно обновлять слабые контрольные точки фиксации без риска
повредить крайнюю сильную точку фиксации.
29. Три мета-страницы вместо двух, что позволяет гарантированно консистентно
обновлять слабые контрольные точки фиксации без риска повредить крайнюю сильную
точку фиксации.