mirror of
https://github.com/isar/libmdbx.git
synced 2024-10-29 23:19:20 +08:00
mdbx: refine README.
Change-Id: Ib548c994753ab619ea8c813531b81a562f9d21fd
This commit is contained in:
parent
dbf66ec0bb
commit
dd4da591af
860
README-RU.md
860
README-RU.md
@ -12,31 +12,31 @@ and [by Yandex](https://translate.yandex.ru/translate?url=https%3A%2F%2Fgithub.c
|
|||||||
|
|
||||||
### Project Status
|
### Project Status
|
||||||
|
|
||||||
|
**Сейчас MDBX _активно перерабатывается_** и к середине 2018 ожидается
|
||||||
|
большое изменение как API, так и формата базы данных. К сожалению,
|
||||||
|
обновление приведет к потере совместимости с предыдущими версиями.
|
||||||
|
|
||||||
**Сейчас MDBX _активно перерабатывается_** и к середине 2018
|
Цель этой революции - обеспечение более четкого надежного API и
|
||||||
ожидается большое изменение как API, так и формата базы данных.
|
добавление новых функции, а также наделение базы данных новыми
|
||||||
К сожалению, обновление приведет к потере совместимости с
|
свойствами.
|
||||||
предыдущими версиями.
|
|
||||||
|
|
||||||
Цель этой революции - обеспечение более четкого надежного
|
В настоящее время MDBX предназначена для Linux, а также поддерживает
|
||||||
API и добавление новых функции, а также наделение базы данных
|
Windows (начиная с Windows Server 2008) в качестве дополнительной
|
||||||
новыми свойствами.
|
платформы. Поддержка других ОС может быть обеспечена на коммерческой
|
||||||
|
основе. Однако такие усовершенствования (т. е. pull-requests) могут быть
|
||||||
В настоящее время MDBX предназначена для Linux, а также
|
приняты в мейнстрим только в том случае, если будет доступен
|
||||||
поддерживает Windows (начиная с Windows Server 2008) в качестве
|
соответствующий публичный и бесплатный сервис непрерывной интеграции
|
||||||
дополнительной платформы. Поддержка других ОС может быть
|
(aka Continuous Integration).
|
||||||
обеспечена на коммерческой основе. Однако такие
|
|
||||||
усовершенствования (т. е. pull-requests) могут быть приняты в
|
|
||||||
мейнстрим только в том случае, если будет доступен
|
|
||||||
соответствующий публичный и бесплатный сервис непрерывной
|
|
||||||
интеграции (aka Continuous Integration).
|
|
||||||
|
|
||||||
## Содержание
|
## Содержание
|
||||||
|
|
||||||
- [Обзор](#Обзор)
|
- [Обзор](#Обзор)
|
||||||
- [Сравнение с другими СУБД](#Сравнение-с-другими-СУБД)
|
- [Сравнение с другими СУБД](#Сравнение-с-другими-СУБД)
|
||||||
- [История & Acknowledgments](#История)
|
- [История & Acknowledgments](#История)
|
||||||
- [Основные свойства](#Основные-свойства)
|
- [Основные свойства](#Основные-свойства)
|
||||||
|
- [Доработки и усовершенствования относительно LMDB](#Доработки-и-усовершенствования-относительно-lmdb)
|
||||||
|
- [Недостатки и Компромиссы](#Недостатки-и-Компромиссы)
|
||||||
|
- [Проблема долгих чтений](#Проблема-долгих-чтений)
|
||||||
|
- [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации)
|
||||||
- [Сравнение производительности](#Сравнение-производительности)
|
- [Сравнение производительности](#Сравнение-производительности)
|
||||||
- [Интегральная производительность](#Интегральная-производительность)
|
- [Интегральная производительность](#Интегральная-производительность)
|
||||||
- [Масштабируемость чтения](#Масштабируемость-чтения)
|
- [Масштабируемость чтения](#Масштабируемость-чтения)
|
||||||
@ -44,21 +44,18 @@ API и добавление новых функции, а также надел
|
|||||||
- [Отложенная фиксация](#Отложенная-фиксация)
|
- [Отложенная фиксация](#Отложенная-фиксация)
|
||||||
- [Асинхронная фиксация](#Асинхронная-фиксация)
|
- [Асинхронная фиксация](#Асинхронная-фиксация)
|
||||||
- [Потребление ресурсов](#Потребление-ресурсов)
|
- [Потребление ресурсов](#Потребление-ресурсов)
|
||||||
- [Недостатки и Компромиссы](#Недостатки-и-Компромиссы)
|
|
||||||
- [Проблема долгих чтений](#Проблема-долгих-чтений)
|
|
||||||
- [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации)
|
|
||||||
- [Доработки и усовершенствования относительно LMDB](#Доработки-и-усовершенствования-относительно-lmdb)
|
|
||||||
|
|
||||||
|
|
||||||
## Обзор
|
## Обзор
|
||||||
|
|
||||||
_libmdbx_ - это встраиваемый key-value движок хранения со специфическим
|
_libmdbx_ - это встраиваемый key-value движок хранения со специфическим
|
||||||
набором свойств и возможностей, ориентированный на создание уникальных
|
набором свойств и возможностей, ориентированный на создание уникальных
|
||||||
легковесных решений с предельной производительностью под Linux и Windows.
|
легковесных решений с предельной производительностью под Linux и
|
||||||
|
Windows.
|
||||||
|
|
||||||
_libmdbx_ позволяет множеству процессов совместно читать и обновлять
|
_libmdbx_ позволяет множеству процессов совместно читать и обновлять
|
||||||
несколько key-value таблиц с соблюдением [ACID](https://ru.wikipedia.org/wiki/ACID),
|
несколько key-value таблиц с соблюдением
|
||||||
при минимальных накладных расходах и амортизационной стоимости любых операций Olog(N).
|
[ACID](https://ru.wikipedia.org/wiki/ACID), при минимальных накладных
|
||||||
|
расходах и амортизационной стоимости любых операций Olog(N).
|
||||||
|
|
||||||
_libmdbx_ обеспечивает
|
_libmdbx_ обеспечивает
|
||||||
[serializability](https://en.wikipedia.org/wiki/Serializability)
|
[serializability](https://en.wikipedia.org/wiki/Serializability)
|
||||||
@ -72,20 +69,26 @@ _libmdbx_ позволяет выполнять операции чтения с
|
|||||||
параллельно на каждом ядре CPU, без использования атомарных операций
|
параллельно на каждом ядре CPU, без использования атомарных операций
|
||||||
и/или примитивов синхронизации.
|
и/или примитивов синхронизации.
|
||||||
|
|
||||||
_libmdbx_ не использует [LSM](https://en.wikipedia.org/wiki/Log-structured_merge-tree), а основан на [B+Tree](https://en.wikipedia.org/wiki/B%2B_tree) с [отображением](https://en.wikipedia.org/wiki/Memory-mapped_file) всех данных в память,
|
_libmdbx_ не использует
|
||||||
при этом текущая версия не использует [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging).
|
[LSM](https://en.wikipedia.org/wiki/Log-structured_merge-tree), а
|
||||||
Это предопределяет многие свойства, в том числе удачные и противопоказанные сценарии использования.
|
основан на [B+Tree](https://en.wikipedia.org/wiki/B%2B_tree) с
|
||||||
|
[отображением](https://en.wikipedia.org/wiki/Memory-mapped_file) всех
|
||||||
|
данных в память, при этом текущая версия не использует
|
||||||
|
[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging). Это
|
||||||
|
предопределяет многие свойства, в том числе удачные и противопоказанные
|
||||||
|
сценарии использования.
|
||||||
|
|
||||||
|
|
||||||
### Сравнение с другими СУБД
|
### Сравнение с другими СУБД
|
||||||
|
Ввиду того, что в _libmdbx_ сейчас происходит революция, я посчитал
|
||||||
Ввиду того, что в _libmdbx_ сейчас происходит революция, я посчитал лучшим решением
|
лучшим решением ограничится здесь ссылкой на [главу Comparison with
|
||||||
ограничится здесь ссылкой на [главу Comparison with other databases](https://github.com/coreos/bbolt#comparison-with-other-databases) в описании _BoltDB_.
|
other databases](https://github.com/coreos/bbolt#comparison-with-other-databases)
|
||||||
|
в описании _BoltDB_.
|
||||||
|
|
||||||
|
|
||||||
### История
|
### История
|
||||||
|
_libmdbx_ является результатом переработки и развития "Lightning
|
||||||
_libmdbx_ является результатом переработки и развития "Lightning Memory-Mapped Database",
|
Memory-Mapped Database", известной под аббревиатурой
|
||||||
известной под аббревиатурой
|
|
||||||
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
|
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
|
||||||
Изначально доработка производилась в составе проекта
|
Изначально доработка производилась в составе проекта
|
||||||
[ReOpenLDAP](https://github.com/leo-yuriev/ReOpenLDAP). Примерно за год
|
[ReOpenLDAP](https://github.com/leo-yuriev/ReOpenLDAP). Примерно за год
|
||||||
@ -102,63 +105,410 @@ Technologies](https://www.ptsecurity.ru).
|
|||||||
|
|
||||||
|
|
||||||
#### Acknowledgments
|
#### Acknowledgments
|
||||||
|
Howard Chu (Symas Corporation) - the author of LMDB, from which
|
||||||
|
originated the MDBX in 2015.
|
||||||
|
|
||||||
Howard Chu (Symas Corporation) - the author of LMDB,
|
Martin Hedenfalk <martin@bzero.se> - the author of `btree.c` code, which
|
||||||
from which originated the MDBX in 2015.
|
was used for begin development of LMDB.
|
||||||
|
|
||||||
Martin Hedenfalk <martin@bzero.se> - the author of `btree.c` code,
|
|
||||||
which was used for begin development of LMDB.
|
|
||||||
|
|
||||||
|
|
||||||
Основные свойства
|
Основные свойства
|
||||||
=================
|
=================
|
||||||
|
|
||||||
_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. Транзакции согласно [ACID](https://ru.wikipedia.org/wiki/ACID),
|
||||||
[ACID](https://ru.wikipedia.org/wiki/ACID), посредством
|
посредством [MVCC](https://ru.wikipedia.org/wiki/MVCC) и
|
||||||
[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).
|
||||||
[COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8).
|
Изменения строго последовательны и не блокируются чтением, конфликты
|
||||||
Изменения строго последовательны и не блокируются чтением,
|
между транзакциями невозможны. При этом гарантируется чтение только
|
||||||
конфликты между транзакциями невозможны.
|
зафиксированных данных, см [relaxing
|
||||||
При этом гарантируется чтение только зафиксированных данных, см [relaxing serializability](https://en.wikipedia.org/wiki/Serializability).
|
serializability](https://en.wikipedia.org/wiki/Serializability).
|
||||||
|
|
||||||
4. Чтение и поиск [без блокировок](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B0%D1%8F_%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F),
|
4. Чтение и поиск [без
|
||||||
без [атомарных операций](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%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),
|
||||||
Читатели не блокируются операциями записи и не конкурируют
|
без [атомарных
|
||||||
между собой, чтение масштабируется линейно по ядрам CPU.
|
операций](https://ru.wikipedia.org/wiki/%D0%90%D1%82%D0%BE%D0%BC%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F).
|
||||||
|
Читатели не блокируются операциями записи и не конкурируют между собой,
|
||||||
|
чтение масштабируется линейно по ядрам CPU.
|
||||||
> Для точности следует отметить, что "подключение к БД" (старт первой
|
> Для точности следует отметить, что "подключение к БД" (старт первой
|
||||||
> читающей транзакции в потоке) и "отключение от БД" (закрытие БД или
|
> читающей транзакции в потоке) и "отключение от БД" (закрытие БД или
|
||||||
> завершение потока) требуют краткосрочного захвата блокировки для
|
> завершение потока) требуют краткосрочного захвата блокировки для
|
||||||
> регистрации/дерегистрации текущего потока в "таблице читателей".
|
> регистрации/дерегистрации текущего потока в "таблице читателей".
|
||||||
|
|
||||||
5. Эффективное хранение дубликатов (ключей с несколькими
|
5. Эффективное хранение дубликатов (ключей с несколькими значениями),
|
||||||
значениями), без дублирования ключей, с сортировкой значений, в
|
без дублирования ключей, с сортировкой значений, в том числе
|
||||||
том числе целочисленных (для вторичных индексов).
|
целочисленных (для вторичных индексов).
|
||||||
|
|
||||||
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).
|
||||||
|
|
||||||
|
8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и
|
||||||
|
журнала транзакций, после сбоев не требуется восстановление. Не
|
||||||
|
требуется компактификация или какое-либо периодическое обслуживание.
|
||||||
|
Поддерживается резервное копирование "по горячему", на работающей БД без
|
||||||
|
приостановки изменения данных.
|
||||||
|
|
||||||
|
9. Отсутствует какое-либо внутреннее управление памятью или
|
||||||
|
кэшированием. Всё необходимое штатно выполняет ядро ОС.
|
||||||
|
|
||||||
|
|
||||||
|
Доработки и усовершенствования относительно LMDB
|
||||||
|
================================================
|
||||||
|
|
||||||
|
1. Утилита `mdbx_chk` для проверки целостности структуры БД.
|
||||||
|
|
||||||
|
2. Автоматическое динамическое управление размером БД согласно
|
||||||
|
параметрам задаваемым функцией `mdbx_env_set_geometry()`, включая шаг
|
||||||
|
приращения и порог уменьшения размера БД, а также выбор размера
|
||||||
|
страницы. Соответственно, это позволяет снизить фрагментированность
|
||||||
|
файла БД на диске и освободить место, в том числе в **Windows**.
|
||||||
|
|
||||||
|
3. Автоматическая без-затратная компактификация БД путем возврата
|
||||||
|
освобождающихся страниц в область нераспределенного резерва в конце
|
||||||
|
файла данных. При этом уменьшается количество страниц находящихся в
|
||||||
|
памяти и участвующих в в обмене с диском.
|
||||||
|
|
||||||
|
4. Поддержка ключей и значений нулевой длины, включая сортированные
|
||||||
|
дубликаты.
|
||||||
|
|
||||||
|
5. Возможность связать с каждой завершаемой транзакцией до 3
|
||||||
|
дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их
|
||||||
|
в транзакции чтения посредством `mdbx_canary_get()`.
|
||||||
|
|
||||||
|
6. Возможность посредством `mdbx_replace()` обновить или удалить запись
|
||||||
|
с получением предыдущего значения данных, а также адресно изменить
|
||||||
|
конкретное multi-значение.
|
||||||
|
|
||||||
|
7. Режим `LIFO RECLAIM`.
|
||||||
|
|
||||||
|
Для повторного использования выбираются не самые старые, а
|
||||||
|
самые новые страницы из доступных. За счет этого цикл
|
||||||
|
использования страниц всегда имеет минимальную длину и не
|
||||||
|
зависит от общего числа выделенных страниц.
|
||||||
|
|
||||||
|
В результате механизмы кэширования и обратной записи работают с
|
||||||
|
максимально возможной эффективностью. В случае использования
|
||||||
|
контроллера дисков или системы хранения с
|
||||||
|
[BBWC](https://en.wikipedia.org/wiki/BBWC) возможно
|
||||||
|
многократное увеличение производительности по записи
|
||||||
|
(обновлению данных).
|
||||||
|
|
||||||
|
8. Генерация последовательностей посредством `mdbx_dbi_sequence()`.
|
||||||
|
|
||||||
|
9. Обработчик `OOM-KICK`.
|
||||||
|
|
||||||
|
Посредством `mdbx_env_set_oomfunc()` может быть установлен
|
||||||
|
внешний обработчик (callback), который будет вызван при
|
||||||
|
исчерпании свободных страниц по причине долгой операцией чтения
|
||||||
|
на фоне интенсивного изменения данных.
|
||||||
|
Обработчику будет передан PID и pthread_id виновника.
|
||||||
|
В свою очередь обработчик может предпринять одно из действий:
|
||||||
|
|
||||||
|
* нейтрализовать виновника (отправить сигнал kill #9), если
|
||||||
|
долгое чтение выполняется сторонним процессом;
|
||||||
|
|
||||||
|
* отменить или перезапустить проблемную операцию чтения, если
|
||||||
|
операция выполняется одним из потоков текущего процесса;
|
||||||
|
|
||||||
|
* подождать некоторое время, в расчете на то, что проблемная операция
|
||||||
|
чтения будет штатно завершена;
|
||||||
|
|
||||||
|
* прервать текущую операцию изменения данных с возвратом кода
|
||||||
|
ошибки.
|
||||||
|
|
||||||
|
10. Возможность открыть БД в эксклюзивном режиме посредством флага
|
||||||
|
`MDBX_EXCLUSIVE`.
|
||||||
|
|
||||||
|
11. Возможность получить отставание текущей транзакции чтения от
|
||||||
|
последней версии данных в БД посредством `mdbx_txn_straggler()`.
|
||||||
|
|
||||||
|
12. Возможность явно запросить обновление существующей записи, без
|
||||||
|
создания новой посредством флажка `MDBX_CURRENT` для `mdbx_put()`.
|
||||||
|
|
||||||
|
13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное
|
||||||
|
количество дубликатов для всех типов таблиц и любого положения курсора.
|
||||||
|
|
||||||
|
14. Возможность получить посредством `mdbx_env_info()` дополнительную
|
||||||
|
информацию, включая номер самой старой версии БД (снимка данных),
|
||||||
|
который используется одним из читателей.
|
||||||
|
|
||||||
|
15. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий)
|
||||||
|
аргумент `data` для таблиц без дубликатов (без флажка `MDBX_DUPSORT`), а
|
||||||
|
при его ненулевом значении всегда использует его для сверки с удаляемой
|
||||||
|
записью.
|
||||||
|
|
||||||
|
16. Возможность открыть dbi-таблицу, одновременно с установкой
|
||||||
|
компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`.
|
||||||
|
|
||||||
|
17. Возможность посредством `mdbx_is_dirty()` определить находятся ли
|
||||||
|
некоторый ключ или данные в "грязной" странице БД. Таким образом,
|
||||||
|
избегая лишнего копирования данных перед выполнением модифицирующих
|
||||||
|
операций (значения, размещенные в "грязных" страницах, могут быть
|
||||||
|
перезаписаны при изменениях, иначе они будут неизменны).
|
||||||
|
|
||||||
|
18. Корректное обновление текущей записи, в том числе сортированного
|
||||||
|
дубликата, при использовании режима `MDBX_CURRENT` в
|
||||||
|
`mdbx_cursor_put()`.
|
||||||
|
|
||||||
|
19. Возможность узнать есть ли за текущей позицией курсора строка данных
|
||||||
|
посредством `mdbx_cursor_eof()`.
|
||||||
|
|
||||||
|
20. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из
|
||||||
|
`mdbx_put()` и `mdbx_replace()` при попытке выполнить неоднозначное
|
||||||
|
обновление или удаления одного из нескольких значений с одним ключом.
|
||||||
|
|
||||||
|
21. Возможность посредством `mdbx_get_ex()` получить значение по
|
||||||
|
заданному ключу, одновременно с количеством дубликатов.
|
||||||
|
|
||||||
|
22. Наличие функций `mdbx_cursor_on_first()` и `mdbx_cursor_on_last()`,
|
||||||
|
которые позволяют быстро выяснить стоит ли курсор на первой/последней
|
||||||
|
позиции.
|
||||||
|
|
||||||
|
23. Возможность автоматического формирования контрольных точек (сброса
|
||||||
|
данных на диск) при накоплении заданного объёма изменений,
|
||||||
|
устанавливаемого функцией `mdbx_env_set_syncbytes()`.
|
||||||
|
|
||||||
|
24. Управление отладкой и получение отладочных сообщений посредством
|
||||||
|
`mdbx_setup_debug()`.
|
||||||
|
|
||||||
|
25. Функция `mdbx_env_pgwalk()` для обхода всех страниц БД.
|
||||||
|
|
||||||
|
26. Три мета-страницы вместо двух, что позволяет гарантированно
|
||||||
|
консистентно обновлять слабые контрольные точки фиксации без риска
|
||||||
|
повредить крайнюю сильную точку фиксации.
|
||||||
|
|
||||||
|
27. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`.
|
||||||
|
> В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
|
||||||
|
> режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC`
|
||||||
|
> когда при системной аварии есть шанс полного разрушения БД как в LMDB.
|
||||||
|
> Для подробностей смотрите раздел
|
||||||
|
> [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации).
|
||||||
|
|
||||||
|
28. Возможность закрыть БД в "грязном" состоянии (без сброса данных и
|
||||||
|
формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`.
|
||||||
|
|
||||||
|
29. При завершении читающих транзакций, открытые в них DBI-хендлы не
|
||||||
|
закрываются и не теряются при завершении таких транзакций посредством
|
||||||
|
`mdbx_txn_abort()` или `mdbx_txn_reset()`. Что позволяет избавится от ряда
|
||||||
|
сложно обнаруживаемых ошибок.
|
||||||
|
|
||||||
|
30. Все курсоры, как в транзакциях только для чтения, так и в пишущих,
|
||||||
|
могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ
|
||||||
|
ОСВОБОЖДАТЬСЯ ЯВНО.
|
||||||
|
>
|
||||||
|
> ## _ВАЖНО_, Обратите внимание!
|
||||||
|
>
|
||||||
|
> Это единственное изменение в API, которое значимо меняет
|
||||||
|
> семантику управления курсорами и может приводить к утечкам
|
||||||
|
> памяти. Следует отметить, что это изменение вынужденно.
|
||||||
|
> Так устраняется неоднозначность с массой тяжких последствий:
|
||||||
|
>
|
||||||
|
> - обращение к уже освобожденной памяти;
|
||||||
|
> - попытки повторного освобождения памяти;
|
||||||
|
> - повреждение памяти и ошибки сегментации.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## Недостатки и Компромиссы
|
||||||
|
|
||||||
|
1. Единовременно может выполняться не более одной транзакция изменения данных
|
||||||
|
(один писатель). Зато все изменения всегда последовательны, не может быть
|
||||||
|
конфликтов или логических ошибок при откате транзакций.
|
||||||
|
|
||||||
|
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) и RAF (Read Amplification Factor) также Olog(N).
|
Amplification Factor). Поэтому фиксация изменений на диске может быть
|
||||||
|
достаточно дорогой и являться главным ограничением производительности
|
||||||
|
при интенсивном изменении данных.
|
||||||
|
> В качестве компромисса _libmdbx_ предлагает несколько режимов ленивой
|
||||||
|
> и/или периодической фиксации. В том числе режим `MAPASYNC`, при котором
|
||||||
|
> изменения происходят только в памяти и асинхронно фиксируются на диске
|
||||||
|
> ядром ОС.
|
||||||
|
>
|
||||||
|
> Однако, следует воспринимать это свойство аккуратно и взвешенно.
|
||||||
|
> Например, полная фиксация транзакции в БД с журналом потребует минимум 2
|
||||||
|
> IOPS (скорее всего 3-4) из-за накладных расходов в файловой системе. В
|
||||||
|
> _libmdbx_ фиксация транзакции также требует от 2 IOPS. Однако, в БД с
|
||||||
|
> журналом кол-во IOPS будет меняться в зависимости от файловой системы,
|
||||||
|
> но не от кол-ва записей или их объема. Тогда как в _libmdbx_ кол-во
|
||||||
|
> будет расти логарифмически от кол-ва записей/строк в БД (по высоте
|
||||||
|
> b+tree).
|
||||||
|
|
||||||
8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала
|
3. [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8)
|
||||||
транзакций, после сбоев не требуется восстановление. Не требуется компактификация
|
для реализации [MVCC](https://ru.wikipedia.org/wiki/MVCC) выполняется на
|
||||||
или какое-либо периодическое обслуживание. Поддерживается резервное копирование
|
уровне страниц в [B+
|
||||||
"по горячему", на работающей БД без приостановки изменения данных.
|
дереве](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE).
|
||||||
|
Поэтому изменение данных амортизационно требует копирования Olog(N)
|
||||||
|
страниц, что расходует [пропускную способность оперативной
|
||||||
|
памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является
|
||||||
|
основным ограничителем производительности в режиме `MAPASYNC`.
|
||||||
|
> Этот недостаток неустраним, тем не менее следует дать некоторые пояснения.
|
||||||
|
> Дело в том, что фиксация изменений на диске потребует гораздо более
|
||||||
|
> значительного копирования данных в памяти и массы других затратных операций.
|
||||||
|
> Поэтому обусловленное этим недостатком падение производительности становится
|
||||||
|
> заметным только при отказе от фиксации изменений на диске.
|
||||||
|
> Соответственно, корректнее сказать, что _libmdbx_ позволяет
|
||||||
|
> получить персистентность ценой минимального падения производительности.
|
||||||
|
> Если же нет необходимости оперативно сохранять данные, то логичнее
|
||||||
|
> использовать `std::map`.
|
||||||
|
|
||||||
9. Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё
|
4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей),
|
||||||
необходимое штатно выполняет ядро ОС!
|
которая приводит к деградации производительности и переполнению БД.
|
||||||
|
> В _libmdbx_ предложены средства для предотвращения, быстрого выхода из
|
||||||
|
> некомфортной ситуации и устранения её последствий. Подробности ниже.
|
||||||
|
|
||||||
|
5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`.
|
||||||
|
В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы,
|
||||||
|
так и согласованность данных.
|
||||||
|
> Дополнительно, в качестве альтернативы, предложен режим `UTTERLY_NOSYNC`.
|
||||||
|
> Подробности ниже.
|
||||||
|
|
||||||
|
|
||||||
|
#### Проблема долгих чтений
|
||||||
|
*Следует отметить*, что проблема "сборки мусора" так или иначе
|
||||||
|
существует во всех СУБД (Vacuum в PostgreSQL). Однако в случае _libmdbx_
|
||||||
|
и LMDB она проявляется более остро, прежде всего из-за высокой
|
||||||
|
производительности, а также из-за намеренного упрощения внутренних
|
||||||
|
механизмов ради производительности.
|
||||||
|
|
||||||
|
Понимание проблемы требует некоторых пояснений, которые
|
||||||
|
изложены ниже, но могут быть сложны для быстрого восприятия.
|
||||||
|
Поэтому, тезисно:
|
||||||
|
|
||||||
|
* Изменение данных на фоне долгой операции чтения может
|
||||||
|
приводить к исчерпанию места в БД.
|
||||||
|
|
||||||
|
* После чего любая попытка обновить данные будет приводить к
|
||||||
|
ошибке `MAP_FULL` до завершения долгой операции чтения.
|
||||||
|
|
||||||
|
* Характерными примерами долгих чтений являются горячее
|
||||||
|
резервное копирования и отладка клиентского приложения при
|
||||||
|
активной транзакции чтения.
|
||||||
|
|
||||||
|
* В оригинальной _LMDB_ после этого будет наблюдаться
|
||||||
|
устойчивая деградация производительности всех механизмов
|
||||||
|
обратной записи на диск (в I/O контроллере, в гипервизоре,
|
||||||
|
в ядре ОС).
|
||||||
|
|
||||||
|
* В _libmdbx_ предусмотрен механизм аварийного прерывания таких
|
||||||
|
операций, а также режим `LIFO RECLAIM` устраняющий последующую
|
||||||
|
деградацию производительности.
|
||||||
|
|
||||||
|
Операции чтения выполняются в контексте снимка данных (версии
|
||||||
|
БД), который был актуальным на момент старта транзакции чтения. Такой
|
||||||
|
читаемый снимок поддерживается неизменным до завершения операции. В свою
|
||||||
|
очередь, это не позволяет повторно использовать страницы БД в
|
||||||
|
последующих версиях (снимках БД).
|
||||||
|
|
||||||
|
Другими словами, если обновление данных выполняется на фоне долгой
|
||||||
|
операции чтения, то вместо повторного использования "старых" ненужных
|
||||||
|
страниц будут выделяться новые, так как "старые" страницы составляют
|
||||||
|
снимок БД, который еще используется долгой операцией чтения.
|
||||||
|
|
||||||
|
В результате, при интенсивном изменении данных и достаточно длительной
|
||||||
|
операции чтения, в БД могут быть исчерпаны свободные страницы, что не
|
||||||
|
позволит создавать новые снимки/версии БД. Такая ситуация будет
|
||||||
|
сохраняться до завершения операции чтения, которая использует старый
|
||||||
|
снимок данных и препятствует повторному использованию страниц БД.
|
||||||
|
|
||||||
|
Однако, на этом проблемы не заканчиваются. После описанной ситуации, все
|
||||||
|
дополнительные страницы, которые были выделены пока переработка старых
|
||||||
|
была невозможна, будут участвовать в цикле выделения/освобождения до
|
||||||
|
конца жизни экземпляра БД. В оригинальной _LMDB_ этот цикл использования
|
||||||
|
страниц работает по принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO).
|
||||||
|
Поэтому увеличение количества циркулирующий страниц, с точки зрения
|
||||||
|
механизмов кэширования и/или обратной записи, выглядит как увеличение
|
||||||
|
рабочего набор данных. Проще говоря, однократное попадание в ситуацию
|
||||||
|
"уснувшего читателя" приводит к устойчивому эффекту вымывания I/O кэша
|
||||||
|
при всех последующих изменениях данных.
|
||||||
|
|
||||||
|
Для устранения описанных проблемы в _libmdbx_ сделаны существенные
|
||||||
|
доработки, подробности ниже. Иллюстрации к проблеме "долгих чтений"
|
||||||
|
можно найти в [слайдах презентации](http://www.slideshare.net/leoyuriev/lmdb).
|
||||||
|
|
||||||
|
Там же приведен пример количественной оценки прироста производительности
|
||||||
|
за счет эффективной работы [BBWC](https://en.wikipedia.org/wiki/BBWC)
|
||||||
|
при включении `LIFO RECLAIM` в _libmdbx_.
|
||||||
|
|
||||||
|
#### Сохранность данных в режиме асинхронной фиксации
|
||||||
|
При работе в режиме `WRITEMAP+MAPSYNC` запись измененных страниц
|
||||||
|
выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе
|
||||||
|
приложения, ядро ОС сохранит все изменения.
|
||||||
|
|
||||||
|
Однако, при аварийном отключении питания или сбое в ядре ОС, на диске
|
||||||
|
может быть сохранена только часть измененных страниц БД. При этом с
|
||||||
|
большой вероятностью может оказаться, что будут сохранены мета-страницы
|
||||||
|
со ссылками на страницы с новыми версиями данных, но не сами новые
|
||||||
|
данные. В этом случае БД будет безвозвратна разрушена, даже если до
|
||||||
|
аварии производилась полная синхронизация данных (посредством
|
||||||
|
`mdbx_env_sync()`).
|
||||||
|
|
||||||
|
В _libmdbx_ эта проблема устранена путем полной переработки
|
||||||
|
пути записи данных:
|
||||||
|
|
||||||
|
* В режиме `WRITEMAP+MAPSYNC` _libmdbx_ не обновляет
|
||||||
|
мета-страницы непосредственно, а поддерживает их теневые копии
|
||||||
|
с переносом изменений после фиксации данных.
|
||||||
|
|
||||||
|
* При завершении транзакций, в зависимости от состояния
|
||||||
|
синхронности данных между диском и оперативной памятью,
|
||||||
|
_libmdbx_ помечает точки фиксации либо как сильные (strong),
|
||||||
|
либо как слабые (weak). Так например, в режиме
|
||||||
|
`WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как
|
||||||
|
слабые, а при явной синхронизации данных - как сильные.
|
||||||
|
|
||||||
|
* В _libmdbx_ поддерживается не две, а три отдельные мета-страницы.
|
||||||
|
Это позволяет выполнять фиксацию транзакций с формированием как
|
||||||
|
сильной, так и слабой точки фиксации, без потери двух предыдущих
|
||||||
|
точек фиксации (из которых одна может быть сильной, а вторая слабой).
|
||||||
|
В результате, _libmdbx_ позволяет в произвольном порядке чередовать
|
||||||
|
сильные и слабые точки фиксации без нарушения соответствующих
|
||||||
|
гарантий в случае неожиданной системной аварии во время фиксации.
|
||||||
|
|
||||||
|
* При открытии БД выполняется автоматический откат к последней
|
||||||
|
сильной фиксации. Этим обеспечивается гарантия сохранности БД.
|
||||||
|
|
||||||
|
Такая гарантия надежности не дается бесплатно. Для сохранности данных,
|
||||||
|
страницы, формирующие крайний снимок с сильной фиксацией, не должны
|
||||||
|
повторно использоваться (перезаписываться) до формирования следующей
|
||||||
|
сильной точки фиксации. Таким образом, крайняя точка фиксации создает
|
||||||
|
описанный выше эффект "долгого чтения". Разница же здесь в том, что при
|
||||||
|
исчерпании свободных страниц ситуация будет автоматически исправлена,
|
||||||
|
посредством записи изменений на диск и формирования новой сильной точки
|
||||||
|
фиксации.
|
||||||
|
|
||||||
|
Таким образом, в режиме безопасной асинхронной фиксации _libmdbx_ будет
|
||||||
|
всегда использовать новые страницы до исчерпания места в БД или до
|
||||||
|
явного формирования сильной точки фиксации посредством
|
||||||
|
`mdbx_env_sync()`. При этом суммарный трафик записи на диск будет
|
||||||
|
примерно такой же, как если бы отдельно фиксировалась каждая транзакция.
|
||||||
|
|
||||||
|
В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
|
||||||
|
режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC`
|
||||||
|
когда при системной аварии есть шанс полного разрушения БД как в LMDB.
|
||||||
|
|
||||||
|
В последующих версиях _libmdbx_ будут предусмотрены средства для
|
||||||
|
асинхронной записи данных на диск с автоматическим формированием сильных
|
||||||
|
точек фиксации.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
Сравнение производительности
|
Сравнение производительности
|
||||||
============================
|
============================
|
||||||
@ -302,14 +652,15 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
|
|||||||
Показана соотнесенная сумма использованных ресурсов в ходе бенчмарка в
|
Показана соотнесенная сумма использованных ресурсов в ходе бенчмарка в
|
||||||
режиме отложенной фиксации:
|
режиме отложенной фиксации:
|
||||||
|
|
||||||
- суммарное количество операций ввода-вывода (IOPS), как записи, так и
|
- суммарное количество операций ввода-вывода (IOPS), как записи, так и
|
||||||
чтения.
|
чтения.
|
||||||
|
|
||||||
- суммарное затраченное время процессора, как в режиме пользовательских процессов,
|
- суммарное затраченное время процессора, как в режиме пользовательских
|
||||||
так и в режиме ядра ОС.
|
процессов, так и в режиме ядра ОС.
|
||||||
|
|
||||||
- использованное место на диске при завершении теста, после закрытия БД из тестирующего процесса,
|
- использованное место на диске при завершении теста, после закрытия БД
|
||||||
но без ожидания всех внутренних операций обслуживания (компактификации LSM и т.п.).
|
из тестирующего процесса, но без ожидания всех внутренних операций
|
||||||
|
обслуживания (компактификации LSM и т.п.).
|
||||||
|
|
||||||
Движок _ForestDB_ был исключен при оформлении результатов, так как
|
Движок _ForestDB_ был исключен при оформлении результатов, так как
|
||||||
относительно конкурентов многократно превысил потребление каждого из
|
относительно конкурентов многократно превысил потребление каждого из
|
||||||
@ -325,352 +676,6 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
## Недостатки и Компромиссы
|
|
||||||
|
|
||||||
1. Единовременно может выполняться не более одной транзакция изменения данных
|
|
||||||
(один писатель). Зато все изменения всегда последовательны, не может быть
|
|
||||||
конфликтов или логических ошибок при откате транзакций.
|
|
||||||
|
|
||||||
2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging)
|
|
||||||
обуславливает относительно большой
|
|
||||||
[WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write
|
|
||||||
Amplification Factor). Поэтому фиксация изменений на диске может быть
|
|
||||||
достаточно дорогой и являться главным ограничением производительности
|
|
||||||
при интенсивном изменении данных.
|
|
||||||
> В качестве компромисса _libmdbx_ предлагает несколько режимов ленивой
|
|
||||||
> и/или периодической фиксации. В том числе режим `MAPASYNC`, при котором
|
|
||||||
> изменения происходят только в памяти и асинхронно фиксируются на диске
|
|
||||||
> ядром ОС.
|
|
||||||
>
|
|
||||||
> Однако, следует воспринимать это свойство аккуратно и взвешенно.
|
|
||||||
> Например, полная фиксация транзакции в БД с журналом потребует минимум 2
|
|
||||||
> IOPS (скорее всего 3-4) из-за накладных расходов в файловой системе. В
|
|
||||||
> _libmdbx_ фиксация транзакции также требует от 2 IOPS. Однако, в БД с
|
|
||||||
> журналом кол-во IOPS будет меняться в зависимости от файловой системы,
|
|
||||||
> но не от кол-ва записей или их объема. Тогда как в _libmdbx_ кол-во
|
|
||||||
> будет расти логарифмически от кол-ва записей/строк в БД (по высоте
|
|
||||||
> b+tree).
|
|
||||||
|
|
||||||
3. [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8)
|
|
||||||
для реализации [MVCC](https://ru.wikipedia.org/wiki/MVCC) выполняется на
|
|
||||||
уровне страниц в [B+
|
|
||||||
дереве](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE).
|
|
||||||
Поэтому изменение данных амортизационно требует копирования Olog(N)
|
|
||||||
страниц, что расходует [пропускную способность оперативной
|
|
||||||
памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является
|
|
||||||
основным ограничителем производительности в режиме `MAPASYNC`.
|
|
||||||
> Этот недостаток неустраним, тем не менее следует дать некоторые пояснения.
|
|
||||||
> Дело в том, что фиксация изменений на диске потребует гораздо более
|
|
||||||
> значительного копирования данных в памяти и массы других затратных операций.
|
|
||||||
> Поэтому обусловленное этим недостатком падение производительности становится
|
|
||||||
> заметным только при отказе от фиксации изменений на диске.
|
|
||||||
> Соответственно, корректнее сказать, что _libmdbx_ позволяет
|
|
||||||
> получить персистентность ценой минимального падения производительности.
|
|
||||||
> Если же нет необходимости оперативно сохранять данные, то логичнее
|
|
||||||
> использовать `std::map`.
|
|
||||||
|
|
||||||
4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей),
|
|
||||||
которая приводит к деградации производительности и переполнению БД.
|
|
||||||
> В _libmdbx_ предложены средства для предотвращения, быстрого выхода из
|
|
||||||
> некомфортной ситуации и устранения её последствий. Подробности ниже.
|
|
||||||
|
|
||||||
5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`.
|
|
||||||
В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы,
|
|
||||||
так и согласованность данных.
|
|
||||||
> Дополнительно, в качестве альтернативы, предложен режим `UTTERLY_NOSYNC`.
|
|
||||||
> Подробности ниже.
|
|
||||||
|
|
||||||
|
|
||||||
#### Проблема долгих чтений
|
|
||||||
|
|
||||||
*Следует отметить*, что проблема "сборки мусора" так или иначе
|
|
||||||
существует во всех СУБД (Vacuum в PostgreSQL). Однако в случае _libmdbx_
|
|
||||||
и LMDB она проявляется более остро, прежде всего из-за высокой
|
|
||||||
производительности, а также из-за намеренного упрощения внутренних
|
|
||||||
механизмов ради производительности.
|
|
||||||
|
|
||||||
Понимание проблемы требует некоторых пояснений, которые
|
|
||||||
изложены ниже, но могут быть сложны для быстрого восприятия.
|
|
||||||
Поэтому, тезисно:
|
|
||||||
|
|
||||||
* Изменение данных на фоне долгой операции чтения может
|
|
||||||
приводить к исчерпанию места в БД.
|
|
||||||
|
|
||||||
* После чего любая попытка обновить данные будет приводить к
|
|
||||||
ошибке `MAP_FULL` до завершения долгой операции чтения.
|
|
||||||
|
|
||||||
* Характерными примерами долгих чтений являются горячее
|
|
||||||
резервное копирования и отладка клиентского приложения при
|
|
||||||
активной транзакции чтения.
|
|
||||||
|
|
||||||
* В оригинальной _LMDB_ после этого будет наблюдаться
|
|
||||||
устойчивая деградация производительности всех механизмов
|
|
||||||
обратной записи на диск (в I/O контроллере, в гипервизоре,
|
|
||||||
в ядре ОС).
|
|
||||||
|
|
||||||
* В _libmdbx_ предусмотрен механизм аварийного прерывания таких
|
|
||||||
операций, а также режим `LIFO RECLAIM` устраняющий последующую
|
|
||||||
деградацию производительности.
|
|
||||||
|
|
||||||
Операции чтения выполняются в контексте снимка данных (версии
|
|
||||||
БД), который был актуальным на момент старта транзакции чтения. Такой
|
|
||||||
читаемый снимок поддерживается неизменным до завершения операции. В свою
|
|
||||||
очередь, это не позволяет повторно использовать страницы БД в
|
|
||||||
последующих версиях (снимках БД).
|
|
||||||
|
|
||||||
Другими словами, если обновление данных выполняется на фоне долгой
|
|
||||||
операции чтения, то вместо повторного использования "старых" ненужных
|
|
||||||
страниц будут выделяться новые, так как "старые" страницы составляют
|
|
||||||
снимок БД, который еще используется долгой операцией чтения.
|
|
||||||
|
|
||||||
В результате, при интенсивном изменении данных и достаточно длительной
|
|
||||||
операции чтения, в БД могут быть исчерпаны свободные страницы, что не
|
|
||||||
позволит создавать новые снимки/версии БД. Такая ситуация будет
|
|
||||||
сохраняться до завершения операции чтения, которая использует старый
|
|
||||||
снимок данных и препятствует повторному использованию страниц БД.
|
|
||||||
|
|
||||||
Однако, на этом проблемы не заканчиваются. После описанной ситуации, все
|
|
||||||
дополнительные страницы, которые были выделены пока переработка старых
|
|
||||||
была невозможна, будут участвовать в цикле выделения/освобождения до
|
|
||||||
конца жизни экземпляра БД. В оригинальной _LMDB_ этот цикл использования
|
|
||||||
страниц работает по принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO).
|
|
||||||
Поэтому увеличение количества циркулирующий страниц, с точки зрения
|
|
||||||
механизмов кэширования и/или обратной записи, выглядит как увеличение
|
|
||||||
рабочего набор данных. Проще говоря, однократное попадание в ситуацию
|
|
||||||
"уснувшего читателя" приводит к устойчивому эффекту вымывания I/O кэша
|
|
||||||
при всех последующих изменениях данных.
|
|
||||||
|
|
||||||
Для устранения описанных проблемы в _libmdbx_ сделаны существенные
|
|
||||||
доработки, подробности ниже. Иллюстрации к проблеме "долгих чтений"
|
|
||||||
можно найти в [слайдах презентации](http://www.slideshare.net/leoyuriev/lmdb).
|
|
||||||
|
|
||||||
Там же приведен пример количественной оценки прироста производительности
|
|
||||||
за счет эффективной работы [BBWC](https://en.wikipedia.org/wiki/BBWC)
|
|
||||||
при включении `LIFO RECLAIM` в _libmdbx_.
|
|
||||||
|
|
||||||
|
|
||||||
#### Сохранность данных в режиме асинхронной фиксации
|
|
||||||
|
|
||||||
При работе в режиме `WRITEMAP+MAPSYNC` запись измененных страниц
|
|
||||||
выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе
|
|
||||||
приложения, ядро ОС сохранит все изменения.
|
|
||||||
|
|
||||||
Однако, при аварийном отключении питания или сбое в ядре ОС, на диске
|
|
||||||
может быть сохранена только часть измененных страниц БД. При этом с большой
|
|
||||||
вероятностью может оказаться, что будут сохранены мета-страницы со
|
|
||||||
ссылками на страницы с новыми версиями данных, но не сами новые данные.
|
|
||||||
В этом случае БД будет безвозвратна разрушена, даже если до аварии
|
|
||||||
производилась полная синхронизация данных (посредством
|
|
||||||
`mdbx_env_sync()`).
|
|
||||||
|
|
||||||
В _libmdbx_ эта проблема устранена путем полной переработки
|
|
||||||
пути записи данных:
|
|
||||||
|
|
||||||
* В режиме `WRITEMAP+MAPSYNC` _libmdbx_ не обновляет
|
|
||||||
мета-страницы непосредственно, а поддерживает их теневые копии
|
|
||||||
с переносом изменений после фиксации данных.
|
|
||||||
|
|
||||||
* При завершении транзакций, в зависимости от состояния
|
|
||||||
синхронности данных между диском и оперативной памятью,
|
|
||||||
_libmdbx_ помечает точки фиксации либо как сильные (strong),
|
|
||||||
либо как слабые (weak). Так например, в режиме
|
|
||||||
`WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как
|
|
||||||
слабые, а при явной синхронизации данных - как сильные.
|
|
||||||
|
|
||||||
* В _libmdbx_ поддерживается не две, а три отдельные мета-страницы.
|
|
||||||
Это позволяет выполнять фиксацию транзакций с формированием как
|
|
||||||
сильной, так и слабой точки фиксации, без потери двух предыдущих
|
|
||||||
точек фиксации (из которых одна может быть сильной, а вторая слабой).
|
|
||||||
В результате, _libmdbx_ позволяет в произвольном порядке чередовать
|
|
||||||
сильные и слабые точки фиксации без нарушения соответствующих
|
|
||||||
гарантий в случае неожиданной системной аварии во время фиксации.
|
|
||||||
|
|
||||||
* При открытии БД выполняется автоматический откат к последней
|
|
||||||
сильной фиксации. Этим обеспечивается гарантия сохранности БД.
|
|
||||||
|
|
||||||
Такая гарантия надежности не дается бесплатно. Для
|
|
||||||
сохранности данных, страницы, формирующие крайний снимок с
|
|
||||||
сильной фиксацией, не должны повторно использоваться
|
|
||||||
(перезаписываться) до формирования следующей сильной точки
|
|
||||||
фиксации. Таким образом, крайняя точка фиксации создает
|
|
||||||
описанный выше эффект "долгого чтения". Разница же здесь в том,
|
|
||||||
что при исчерпании свободных страниц ситуация будет
|
|
||||||
автоматически исправлена, посредством записи изменений на диск
|
|
||||||
и формирования новой сильной точки фиксации.
|
|
||||||
|
|
||||||
Таким образом, в режиме безопасной асинхронной фиксации _libmdbx_ будет
|
|
||||||
всегда использовать новые страницы до исчерпания места в БД или до явного
|
|
||||||
формирования сильной точки фиксации посредством `mdbx_env_sync()`.
|
|
||||||
При этом суммарный трафик записи на диск будет примерно такой же,
|
|
||||||
как если бы отдельно фиксировалась каждая транзакция.
|
|
||||||
|
|
||||||
В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
|
|
||||||
режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC` когда
|
|
||||||
при системной аварии есть шанс полного разрушения БД как в LMDB.
|
|
||||||
|
|
||||||
В последующих версиях _libmdbx_ будут предусмотрены средства
|
|
||||||
для асинхронной записи данных на диск с автоматическим
|
|
||||||
формированием сильных точек фиксации.
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Доработки и усовершенствования относительно LMDB
|
|
||||||
================================================
|
|
||||||
|
|
||||||
1. Режим `LIFO RECLAIM`.
|
|
||||||
|
|
||||||
Для повторного использования выбираются не самые старые, а
|
|
||||||
самые новые страницы из доступных. За счет этого цикл
|
|
||||||
использования страниц всегда имеет минимальную длину и не
|
|
||||||
зависит от общего числа выделенных страниц.
|
|
||||||
|
|
||||||
В результате механизмы кэширования и обратной записи работают с
|
|
||||||
максимально возможной эффективностью. В случае использования
|
|
||||||
контроллера дисков или системы хранения с
|
|
||||||
[BBWC](https://en.wikipedia.org/wiki/BBWC) возможно
|
|
||||||
многократное увеличение производительности по записи
|
|
||||||
(обновлению данных).
|
|
||||||
|
|
||||||
2. Обработчик `OOM-KICK`.
|
|
||||||
|
|
||||||
Посредством `mdbx_env_set_oomfunc()` может быть установлен
|
|
||||||
внешний обработчик (callback), который будет вызван при
|
|
||||||
исчерпании свободных страниц из-за долгой операцией чтения.
|
|
||||||
Обработчику будет передан PID и pthread_id виновника.
|
|
||||||
В свою очередь обработчик может предпринять одно из действий:
|
|
||||||
|
|
||||||
* нейтрализовать виновника (отправить сигнал kill #9), если
|
|
||||||
долгое чтение выполняется сторонним процессом;
|
|
||||||
|
|
||||||
* отменить или перезапустить проблемную операцию чтения, если
|
|
||||||
операция выполняется одним из потоков текущего процесса;
|
|
||||||
|
|
||||||
* подождать некоторое время, в расчете на то, что проблемная операция
|
|
||||||
чтения будет штатно завершена;
|
|
||||||
|
|
||||||
* прервать текущую операцию изменения данных с возвратом кода
|
|
||||||
ошибки.
|
|
||||||
|
|
||||||
3. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`.
|
|
||||||
> В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
|
|
||||||
> режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC`
|
|
||||||
> когда при системной аварии есть шанс полного разрушения БД как в LMDB.
|
|
||||||
> Для подробностей смотрите раздел
|
|
||||||
> [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации).
|
|
||||||
|
|
||||||
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. Возможность явно запросить обновление существующей записи, без
|
|
||||||
создания новой посредством флажка `MDBX_CURRENT` для `mdbx_put()`.
|
|
||||||
|
|
||||||
11. Возможность посредством `mdbx_replace()` обновить или удалить запись
|
|
||||||
с получением предыдущего значения данных, а также адресно изменить
|
|
||||||
конкретное multi-значение.
|
|
||||||
|
|
||||||
12. Поддержка ключей и значений нулевой длины, включая сортированные
|
|
||||||
дубликаты.
|
|
||||||
|
|
||||||
13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное
|
|
||||||
количество дубликатов для всех типов таблиц и любого положения курсора.
|
|
||||||
|
|
||||||
14. Возможность открыть БД в эксклюзивном режиме посредством флага
|
|
||||||
`MDBX_EXCLUSIVE`, например в целях её проверки.
|
|
||||||
|
|
||||||
15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и
|
|
||||||
формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`.
|
|
||||||
|
|
||||||
16. Возможность получить посредством `mdbx_env_info()` дополнительную
|
|
||||||
информацию, включая номер самой старой версии БД (снимка данных),
|
|
||||||
который используется одним из читателей.
|
|
||||||
|
|
||||||
17. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий)
|
|
||||||
аргумент `data` для таблиц без дубликатов (без флажка `MDBX_DUPSORT`), а
|
|
||||||
при его ненулевом значении всегда использует его для сверки с удаляемой
|
|
||||||
записью.
|
|
||||||
|
|
||||||
18. Возможность открыть dbi-таблицу, одновременно с установкой
|
|
||||||
компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`.
|
|
||||||
|
|
||||||
19. Возможность посредством `mdbx_is_dirty()` определить находятся ли
|
|
||||||
некоторый ключ или данные в "грязной" странице БД. Таким образом,
|
|
||||||
избегая лишнего копирования данных перед выполнением модифицирующих
|
|
||||||
операций (значения, размещенные в "грязных" страницах, могут быть
|
|
||||||
перезаписаны при изменениях, иначе они будут неизменны).
|
|
||||||
|
|
||||||
20. Корректное обновление текущей записи, в том числе сортированного
|
|
||||||
дубликата, при использовании режима `MDBX_CURRENT` в
|
|
||||||
`mdbx_cursor_put()`.
|
|
||||||
|
|
||||||
21. Все курсоры, как в транзакциях только для чтения, так и в пишущих,
|
|
||||||
могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ
|
|
||||||
ОСВОБОЖДАТЬСЯ ЯВНО.
|
|
||||||
>
|
|
||||||
> ## _ВАЖНО_, Обратите внимание!
|
|
||||||
>
|
|
||||||
> Это единственное изменение в API, которое значимо меняет
|
|
||||||
> семантику управления курсорами и может приводить к утечкам
|
|
||||||
> памяти. Следует отметить, что это изменение вынужденно.
|
|
||||||
> Так устраняется неоднозначность с массой тяжких последствий:
|
|
||||||
>
|
|
||||||
> - обращение к уже освобожденной памяти;
|
|
||||||
> - попытки повторного освобождения памяти;
|
|
||||||
> - повреждение памяти и ошибки сегментации.
|
|
||||||
|
|
||||||
22. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из
|
|
||||||
`mdbx_put()` и `mdbx_replace()` при попытке выполнить неоднозначное
|
|
||||||
обновление или удаления одного из нескольких значений с одним ключом.
|
|
||||||
|
|
||||||
23. Возможность посредством `mdbx_get_ex()` получить значение по
|
|
||||||
заданному ключу, одновременно с количеством дубликатов.
|
|
||||||
|
|
||||||
24. Наличие функций `mdbx_cursor_on_first()` и `mdbx_cursor_on_last()`,
|
|
||||||
которые позволяют быстро выяснить стоит ли курсор на первой/последней
|
|
||||||
позиции.
|
|
||||||
|
|
||||||
25. При завершении читающих транзакций, открытые в них DBI-хендлы не
|
|
||||||
закрываются и не теряются при завершении таких транзакций посредством
|
|
||||||
`mdbx_txn_abort()` или `mdbx_txn_reset()`. Что позволяет избавится от ряда
|
|
||||||
сложно обнаруживаемых ошибок.
|
|
||||||
|
|
||||||
26. Генерация последовательностей посредством `mdbx_dbi_sequence()`.
|
|
||||||
|
|
||||||
27. Расширенное динамическое управление размером БД, включая выбор
|
|
||||||
размера страницы посредством `mdbx_env_set_geometry()`,
|
|
||||||
в том числе в **Windows**
|
|
||||||
|
|
||||||
28. Три мета-страницы вместо двух, что позволяет гарантированно
|
|
||||||
консистентно обновлять слабые контрольные точки фиксации без риска
|
|
||||||
повредить крайнюю сильную точку фиксации.
|
|
||||||
|
|
||||||
29. В _libmdbx_ реализован автоматический возврат освобождающихся
|
|
||||||
страниц в область нераспределенного резерва в конце файла данных. При
|
|
||||||
этом уменьшается количество страниц загруженных в память и участвующих в
|
|
||||||
цикле обновления данных и записи на диск. Фактически _libmdbx_ выполняет
|
|
||||||
постоянную компактификацию данных, но не затрачивая на это
|
|
||||||
дополнительных ресурсов, а только освобождая их. При освобождении места
|
|
||||||
в БД и установке соответствующих параметров геометрии базы данных, также будет
|
|
||||||
уменьшаться размер файла на диске, в том числе в **Windows**.
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ objdump -f -h -j .text libmdbx.so
|
$ objdump -f -h -j .text libmdbx.so
|
||||||
|
|
||||||
@ -685,16 +690,3 @@ Idx Name Size VMA LMA File off Algn
|
|||||||
CONTENTS, ALLOC, LOAD, READONLY, CODE
|
CONTENTS, ALLOC, LOAD, READONLY, CODE
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
|
||||||
$ gcc -v
|
|
||||||
Using built-in specs.
|
|
||||||
COLLECT_GCC=gcc
|
|
||||||
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
|
|
||||||
OFFLOAD_TARGET_NAMES=nvptx-none
|
|
||||||
OFFLOAD_TARGET_DEFAULT=1
|
|
||||||
Target: x86_64-linux-gnu
|
|
||||||
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.2.0-8ubuntu3' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
|
|
||||||
Thread model: posix
|
|
||||||
gcc version 7.2.0 (Ubuntu 7.2.0-8ubuntu3)
|
|
||||||
```
|
|
||||||
|
758
README.md
758
README.md
@ -9,9 +9,21 @@ libmdbx
|
|||||||
|
|
||||||
## Project Status for now
|
## Project Status for now
|
||||||
|
|
||||||
- The stable versions ([_stable/0.0_](https://github.com/leo-yuriev/libmdbx/tree/stable/0.0) and [_stable/0.1_](https://github.com/leo-yuriev/libmdbx/tree/stable/0.1) branches) of _MDBX_ are frozen, i.e. no new features or API changes, but only bug fixes.
|
- The stable versions
|
||||||
- The next version ([_devel_](https://github.com/leo-yuriev/libmdbx/tree/devel) branch) **is under active non-public development**, i.e. current API and set of features are extreme volatile.
|
([_stable/0.0_](https://github.com/leo-yuriev/libmdbx/tree/stable/0.0)
|
||||||
- The immediate goal of development is formation of the stable API and the stable internal database format, which allows realise all PLANNED FEATURES:
|
and
|
||||||
|
[_stable/0.1_](https://github.com/leo-yuriev/libmdbx/tree/stable/0.1)
|
||||||
|
branches) of _MDBX_ are frozen, i.e. no new features or API changes, but
|
||||||
|
only bug fixes.
|
||||||
|
|
||||||
|
- The next version
|
||||||
|
([_devel_](https://github.com/leo-yuriev/libmdbx/tree/devel) branch)
|
||||||
|
**is under active non-public development**, i.e. current API and set of
|
||||||
|
features are extreme volatile.
|
||||||
|
|
||||||
|
- The immediate goal of development is formation of the stable API and
|
||||||
|
the stable internal database format, which allows realise all PLANNED
|
||||||
|
FEATURES:
|
||||||
1. Integrity check by [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree);
|
1. Integrity check by [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree);
|
||||||
2. Support for [raw block devices](https://en.wikipedia.org/wiki/Raw_device);
|
2. Support for [raw block devices](https://en.wikipedia.org/wiki/Raw_device);
|
||||||
3. Separate place (HDD) for large data items;
|
3. Separate place (HDD) for large data items;
|
||||||
@ -24,19 +36,21 @@ Don't miss [Java Native Interface](https://github.com/castortech/mdbxjni) by [Ca
|
|||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Nowadays MDBX intended for Linux, and support Windows (since
|
Nowadays MDBX intended for Linux, and support Windows (since Windows
|
||||||
Windows Server 2008) as a complementary platform. Support for
|
Server 2008) as a complementary platform. Support for other OS could be
|
||||||
other OS could be implemented on commercial basis. However such
|
implemented on commercial basis. However such enhancements (i.e. pull
|
||||||
enhancements (i.e. pull requests) could be accepted in
|
requests) could be accepted in mainstream only when corresponding public
|
||||||
mainstream only when corresponding public and free Continuous
|
and free Continuous Integration service will be available.
|
||||||
Integration service will be available.
|
|
||||||
|
|
||||||
## Contents
|
## Contents
|
||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Comparison with other DBs](#comparison-with-other-dbs)
|
- [Comparison with other DBs](#comparison-with-other-dbs)
|
||||||
- [History & Acknowledgments](#history)
|
- [History & Acknowledgments](#history)
|
||||||
- [Main features](#main-features)
|
- [Main features](#main-features)
|
||||||
|
- [Improvements over LMDB](#improvements-over-lmdb)
|
||||||
|
- [Gotchas](#gotchas)
|
||||||
|
- [Long-time read transactions problem](#long-time-read-transactions-problem)
|
||||||
|
- [Data safety in async-write-mode](#data-safety-in-async-write-mode)
|
||||||
- [Performance comparison](#performance-comparison)
|
- [Performance comparison](#performance-comparison)
|
||||||
- [Integral performance](#integral-performance)
|
- [Integral performance](#integral-performance)
|
||||||
- [Read scalability](#read-scalability)
|
- [Read scalability](#read-scalability)
|
||||||
@ -44,52 +58,58 @@ Integration service will be available.
|
|||||||
- [Lazy-write mode](#lazy-write-mode)
|
- [Lazy-write mode](#lazy-write-mode)
|
||||||
- [Async-write mode](#async-write-mode)
|
- [Async-write mode](#async-write-mode)
|
||||||
- [Cost comparison](#cost-comparison)
|
- [Cost comparison](#cost-comparison)
|
||||||
- [Gotchas](#gotchas)
|
|
||||||
- [Long-time read transactions problem](#long-time-read-transactions-problem)
|
|
||||||
- [Data safety in async-write-mode](#data-safety-in-async-write-mode)
|
|
||||||
- [Improvements over LMDB](#improvements-over-lmdb)
|
|
||||||
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
_libmdbx_ is an embedded lightweight key-value database engine oriented
|
||||||
|
for performance under Linux and Windows.
|
||||||
|
|
||||||
_libmdbx_ is an embedded lightweight key-value database engine oriented for performance under Linux and Windows.
|
_libmdbx_ allows multiple processes to read and update several key-value
|
||||||
|
tables concurrently, while being
|
||||||
_libmdbx_ allows multiple processes to read and update several key-value tables concurrently,
|
[ACID](https://en.wikipedia.org/wiki/ACID)-compliant, with minimal
|
||||||
while being [ACID](https://en.wikipedia.org/wiki/ACID)-compliant, with minimal overhead and operation cost of Olog(N).
|
overhead and operation cost of Olog(N).
|
||||||
|
|
||||||
_libmdbx_ provides
|
_libmdbx_ provides
|
||||||
[serializability](https://en.wikipedia.org/wiki/Serializability) and consistency of data after crash.
|
[serializability](https://en.wikipedia.org/wiki/Serializability) and
|
||||||
Read-write transactions don't block read-only transactions and are
|
consistency of data after crash. Read-write transactions don't block
|
||||||
[serialized](https://en.wikipedia.org/wiki/Serializability) by [mutex](https://en.wikipedia.org/wiki/Mutual_exclusion).
|
read-only transactions and are
|
||||||
|
[serialized](https://en.wikipedia.org/wiki/Serializability) by
|
||||||
|
[mutex](https://en.wikipedia.org/wiki/Mutual_exclusion).
|
||||||
|
|
||||||
_libmdbx_ [wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom) provides parallel read transactions
|
_libmdbx_
|
||||||
without atomic operations or synchronization primitives.
|
[wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom)
|
||||||
|
provides parallel read transactions without atomic operations or
|
||||||
|
synchronization primitives.
|
||||||
|
|
||||||
_libmdbx_ uses [B+Trees](https://en.wikipedia.org/wiki/B%2B_tree) and [mmap](https://en.wikipedia.org/wiki/Memory-mapped_file),
|
_libmdbx_ uses [B+Trees](https://en.wikipedia.org/wiki/B%2B_tree) and
|
||||||
doesn't use [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging). This might have caveats for some workloads.
|
[mmap](https://en.wikipedia.org/wiki/Memory-mapped_file), doesn't use
|
||||||
|
[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging). This might
|
||||||
|
have caveats for some workloads.
|
||||||
|
|
||||||
### Comparison with other DBs
|
### Comparison with other DBs
|
||||||
|
Because _libmdbx_ is currently overhauled, I think it's better to just
|
||||||
Because _libmdbx_ is currently overhauled, I think it's better to just link
|
link [chapter of Comparison with other
|
||||||
[chapter of Comparison with other databases](https://github.com/coreos/bbolt#comparison-with-other-databases) here.
|
databases](https://github.com/coreos/bbolt#comparison-with-other-databases)
|
||||||
|
here.
|
||||||
|
|
||||||
### History
|
### History
|
||||||
|
The _libmdbx_ design is based on [Lightning Memory-Mapped
|
||||||
|
Database](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
|
||||||
|
Initial development was going in
|
||||||
|
[ReOpenLDAP](https://github.com/leo-yuriev/ReOpenLDAP) project, about a
|
||||||
|
year later it received separate development effort and in autumn 2015
|
||||||
|
was isolated to separate project, which was [presented at Highload++
|
||||||
|
2015 conference](http://www.highload.ru/2015/abstracts/1831.html).
|
||||||
|
|
||||||
The _libmdbx_ design is based on [Lightning Memory-Mapped Database](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
|
Since early 2017 _libmdbx_ is used in [Fast PositiveTables](https://github.com/leo-yuriev/libfpta),
|
||||||
Initial development was going in [ReOpenLDAP](https://github.com/leo-yuriev/ReOpenLDAP) project, about a year later it
|
|
||||||
received separate development effort and in autumn 2015 was isolated to separate project, which was
|
|
||||||
[presented at Highload++ 2015 conference](http://www.highload.ru/2015/abstracts/1831.html).
|
|
||||||
|
|
||||||
Since early 2017 _libmdbx_ is used in [Fast Positive Tables](https://github.com/leo-yuriev/libfpta),
|
|
||||||
by [Positive Technologies](https://www.ptsecurity.com).
|
by [Positive Technologies](https://www.ptsecurity.com).
|
||||||
|
|
||||||
#### Acknowledgments
|
#### Acknowledgments
|
||||||
|
Howard Chu (Symas Corporation) - the author of LMDB, from which
|
||||||
|
originated the MDBX in 2015.
|
||||||
|
|
||||||
Howard Chu (Symas Corporation) - the author of LMDB,
|
Martin Hedenfalk <martin@bzero.se> - the author of `btree.c` code, which
|
||||||
from which originated the MDBX in 2015.
|
was used for begin development of LMDB.
|
||||||
|
|
||||||
Martin Hedenfalk <martin@bzero.se> - the author of `btree.c` code,
|
|
||||||
which was used for begin development of LMDB.
|
|
||||||
|
|
||||||
|
|
||||||
Main features
|
Main features
|
||||||
@ -98,39 +118,331 @@ Main features
|
|||||||
_libmdbx_ inherits all keys features and characteristics from
|
_libmdbx_ inherits all keys features and characteristics from
|
||||||
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database):
|
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database):
|
||||||
|
|
||||||
1. Data is stored in ordered map, keys are always sorted, range lookups are supported.
|
1. Data is stored in ordered map, keys are always sorted, range lookups
|
||||||
|
are supported.
|
||||||
|
|
||||||
2. Data is [mmaped](https://en.wikipedia.org/wiki/Memory-mapped_file) to memory of each worker DB process, read transactions are zero-copy.
|
2. Data is [mmaped](https://en.wikipedia.org/wiki/Memory-mapped_file) to
|
||||||
|
memory of each worker DB process, read transactions are zero-copy.
|
||||||
|
|
||||||
3. Transactions are [ACID](https://en.wikipedia.org/wiki/ACID)-compliant, thanks to
|
3. Transactions are
|
||||||
[MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) and [CoW](https://en.wikipedia.org/wiki/Copy-on-write).
|
[ACID](https://en.wikipedia.org/wiki/ACID)-compliant, thanks to
|
||||||
Writes are strongly serialized and aren't blocked by reads, transactions can't conflict with each other.
|
[MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control)
|
||||||
Reads are guaranteed to get only commited data
|
and [CoW](https://en.wikipedia.org/wiki/Copy-on-write). Writes are
|
||||||
([relaxing serializability](https://en.wikipedia.org/wiki/Serializability#Relaxing_serializability)).
|
strongly serialized and aren't blocked by reads, transactions can't
|
||||||
|
conflict with each other. Reads are guaranteed to get only commited data
|
||||||
|
([relaxing serializability](https://en.wikipedia.org/wiki/Serializability#Relaxing_serializability)).
|
||||||
|
|
||||||
4. Reads and queries are [non-blocking](https://en.wikipedia.org/wiki/Non-blocking_algorithm),
|
4. Reads and queries are
|
||||||
don't use [atomic operations](https://en.wikipedia.org/wiki/Linearizability#High-level_atomic_operations).
|
[non-blocking](https://en.wikipedia.org/wiki/Non-blocking_algorithm),
|
||||||
Readers don't block each other and aren't blocked by writers. Read performance scales linearly with CPU core count.
|
don't use [atomic
|
||||||
> Though "connect to DB" (start of first read transaction in thread) and "disconnect from DB" (shutdown or thread
|
operations](https://en.wikipedia.org/wiki/Linearizability#High-level_atomic_operations).
|
||||||
> termination) requires to acquire a lock to register/unregister current thread from "readers table"
|
Readers don't block each other and aren't blocked by writers. Read
|
||||||
|
performance scales linearly with CPU core count.
|
||||||
|
> Though "connect to DB" (start of first read transaction in thread) and
|
||||||
|
> "disconnect from DB" (shutdown or thread termination) requires to
|
||||||
|
> acquire a lock to register/unregister current thread from "readers
|
||||||
|
> table"
|
||||||
|
|
||||||
5. Keys with multiple values are stored efficiently without key duplication, sorted by value, including integers
|
5. Keys with multiple values are stored efficiently without key
|
||||||
(reasonable for secondary indexes).
|
duplication, sorted by value, including integers (reasonable for
|
||||||
|
secondary indexes).
|
||||||
|
|
||||||
6. Efficient operation on short fixed length keys, including integer ones.
|
6. Efficient operation on short fixed length keys, including integer
|
||||||
|
ones.
|
||||||
|
|
||||||
7. [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write Amplification Factor) и RAF (Read Amplification Factor)
|
7. [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write
|
||||||
are Olog(N).
|
Amplification Factor) и RAF (Read Amplification Factor) are Olog(N).
|
||||||
|
|
||||||
8. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) and transaction journal.
|
8. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) and
|
||||||
In case of a crash no recovery needed. No need for regular maintenance. Backups can be made on the fly on working DB
|
transaction journal. In case of a crash no recovery needed. No need for
|
||||||
without freezing writers.
|
regular maintenance. Backups can be made on the fly on working DB
|
||||||
|
without freezing writers.
|
||||||
|
|
||||||
9. No custom memory management, all done with standard OS syscalls.
|
9. No custom memory management, all done with standard OS syscalls.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Improvements over LMDB
|
||||||
|
======================
|
||||||
|
|
||||||
|
1. `mdbx_chk` tool for DB integrity check.
|
||||||
|
|
||||||
|
2. Automatic dynamic DB size management according to the parameters
|
||||||
|
specified by `mdbx_env_set_geometry()` function. Including including
|
||||||
|
growth step and truncation threshold, as well as the choice of page
|
||||||
|
size.
|
||||||
|
|
||||||
|
3. Automatic returning of freed pages into unallocated space at the end
|
||||||
|
of database file with optionally automatic shrinking it. This reduces
|
||||||
|
amount of pages resides in RAM and circulated in disk I/O. In fact
|
||||||
|
_libmdbx_ constantly performs DB compactification, without spending
|
||||||
|
additional resources for that.
|
||||||
|
|
||||||
|
4. Support for keys and values of zero length, including sorted
|
||||||
|
duplicates.
|
||||||
|
|
||||||
|
5. Ability to assign up to 3 markers to commiting transaction with
|
||||||
|
`mdbx_canary_put()` and then get them in read transaction by
|
||||||
|
`mdbx_canary_get()`.
|
||||||
|
|
||||||
|
6. Ability to update or delete record and get previous value via
|
||||||
|
`mdbx_replace()` Also can update specific multi-value.
|
||||||
|
|
||||||
|
7. `LIFO RECLAIM` mode:
|
||||||
|
|
||||||
|
The newest pages are picked for reuse instead of the oldest. This allows
|
||||||
|
to minimize reclaim loop and make it execution time independent of total
|
||||||
|
page count.
|
||||||
|
|
||||||
|
This results in OS kernel cache mechanisms working with maximum
|
||||||
|
efficiency. In case of using disk controllers or storages with
|
||||||
|
[BBWC](https://en.wikipedia.org/wiki/Disk_buffer#Write_acceleration)
|
||||||
|
this may greatly improve write performance.
|
||||||
|
|
||||||
|
8. Sequence generation via `mdbx_dbi_sequence()`.
|
||||||
|
|
||||||
|
9. `OOM-KICK` callback.
|
||||||
|
|
||||||
|
`mdbx_env_set_oomfunc()` allows to set a callback, which will be called
|
||||||
|
in the event of DB space exhausting during long-time read transaction in
|
||||||
|
parallel with extensive updating. Callback will be invoked with PID and
|
||||||
|
pthread_id of offending thread as parameters. Callback can do any of
|
||||||
|
these things to remedy the problem:
|
||||||
|
|
||||||
|
* wait for read transaction to finish normally;
|
||||||
|
|
||||||
|
* kill the offending process (signal 9), if separate process is doing
|
||||||
|
long-time read;
|
||||||
|
|
||||||
|
* abort or restart offending read transaction if it's running in sibling
|
||||||
|
thread;
|
||||||
|
|
||||||
|
* abort current write transaction with returning error code.
|
||||||
|
|
||||||
|
10. Ability to open DB in exclusive mode with `MDBX_EXCLUSIVE` flag.
|
||||||
|
|
||||||
|
11. Ability to get how far current read-only snapshot is from latest
|
||||||
|
version of the DB by `mdbx_txn_straggler()`.
|
||||||
|
|
||||||
|
12. Ability to explicitly request update of present record without
|
||||||
|
creating new record. Implemented as `MDBX_CURRENT` flag for
|
||||||
|
`mdbx_put()`.
|
||||||
|
|
||||||
|
13. Fixed `mdbx_cursor_count()`, which returns correct count of
|
||||||
|
duplicated for all table types and any cursor position.
|
||||||
|
|
||||||
|
14. `mdbx_env_info()` to getting additional info, including number of
|
||||||
|
the oldest snapshot of DB, which is used by one of the readers.
|
||||||
|
|
||||||
|
15. `mdbx_del()` doesn't ignore additional argument (specifier) `data`
|
||||||
|
for tables without duplicates (without flag `MDBX_DUPSORT`), if `data`
|
||||||
|
is not null then always uses it to verify record, which is being
|
||||||
|
deleted.
|
||||||
|
|
||||||
|
16. Ability to open dbi-table with simultaneous setup of comparators for
|
||||||
|
keys and values, via `mdbx_dbi_open_ex()`.
|
||||||
|
|
||||||
|
17. `mdbx_is_dirty()`to find out if key or value is on dirty page, that
|
||||||
|
useful to avoid copy-out before updates.
|
||||||
|
|
||||||
|
18. Correct update of current record in `MDBX_CURRENT` mode of
|
||||||
|
`mdbx_cursor_put()`, including sorted duplicated.
|
||||||
|
|
||||||
|
19. Check if there is a row with data after current cursor position via
|
||||||
|
`mdbx_cursor_eof()`.
|
||||||
|
|
||||||
|
20. Additional error code `MDBX_EMULTIVAL`, which is returned by
|
||||||
|
`mdbx_put()` and `mdbx_replace()` in case is ambiguous update or delete.
|
||||||
|
|
||||||
|
21. Ability to get value by key and duplicates count by `mdbx_get_ex()`.
|
||||||
|
|
||||||
|
22. Functions `mdbx_cursor_on_first()` and `mdbx_cursor_on_last()`,
|
||||||
|
which allows to know if cursor is currently on first or last position
|
||||||
|
respectively.
|
||||||
|
|
||||||
|
23. Automatic creation of synchronization points (flush changes to
|
||||||
|
persistent storage) when changes reach set threshold (threshold can be
|
||||||
|
set by `mdbx_env_set_syncbytes()`).
|
||||||
|
|
||||||
|
24. Control over debugging and receiving of debugging messages via
|
||||||
|
`mdbx_setup_debug()`.
|
||||||
|
|
||||||
|
25. Function `mdbx_env_pgwalk()` for page-walking all pages in DB.
|
||||||
|
|
||||||
|
26. Three meta-pages instead of two, this allows to guarantee
|
||||||
|
consistently update weak sync-points without risking to corrupt last
|
||||||
|
steady sync-point.
|
||||||
|
|
||||||
|
27. Guarantee of DB integrity in `WRITEMAP+MAPSYNC` mode:
|
||||||
|
> Current _libmdbx_ gives a choice of safe async-write mode (default)
|
||||||
|
> and `UTTERLY_NOSYNC` mode which may result in full
|
||||||
|
> DB corruption during system crash as with LMDB. For details see
|
||||||
|
> [Data safety in async-write mode](#data-safety-in-async-write-mode).
|
||||||
|
|
||||||
|
28. Ability to close DB in "dirty" state (without data flush and
|
||||||
|
creation of steady synchronization point) via `mdbx_env_close_ex()`.
|
||||||
|
|
||||||
|
29. If read transaction is aborted via `mdbx_txn_abort()` or
|
||||||
|
`mdbx_txn_reset()` then DBI-handles, which were opened in it, aren't
|
||||||
|
closed or deleted. This allows to avoid several types of hard-to-debug
|
||||||
|
errors.
|
||||||
|
|
||||||
|
30. All cursors in all read and write transactions can be reused by
|
||||||
|
`mdbx_cursor_renew()` and MUST be freed explicitly.
|
||||||
|
> ## Caution, please pay attention!
|
||||||
|
>
|
||||||
|
> This is the only change of API, which changes semantics of cursor management
|
||||||
|
> and can lead to memory leaks on misuse. This is a needed change as it eliminates ambiguity
|
||||||
|
> which helps to avoid such errors as:
|
||||||
|
> - use-after-free;
|
||||||
|
> - double-free;
|
||||||
|
> - memory corruption and segfaults.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
1. At one moment there can be only one writer. But this allows to
|
||||||
|
serialize writes and eliminate any possibility of conflict or logical
|
||||||
|
errors during transaction rollback.
|
||||||
|
|
||||||
|
2. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) means
|
||||||
|
relatively big [WAF](https://en.wikipedia.org/wiki/Write_amplification)
|
||||||
|
(Write Amplification Factor). Because of this syncing data to disk might
|
||||||
|
be quite resource intensive and be main performance bottleneck during
|
||||||
|
intensive write workload.
|
||||||
|
> As compromise _libmdbx_ allows several modes of lazy and/or periodic
|
||||||
|
> syncing, including `MAPASYNC` mode, which modificate data in memory and
|
||||||
|
> asynchronously syncs data to disk, moment to sync is picked by OS.
|
||||||
|
>
|
||||||
|
> Although this should be used with care, synchronous transactions in a DB
|
||||||
|
> with transaction journal will require 2 IOPS minimum (probably 3-4 in
|
||||||
|
> practice) because of filesystem overhead, overhead depends on
|
||||||
|
> filesystem, not on record count or record size. In _libmdbx_ IOPS count
|
||||||
|
> will grow logarithmically depending on record count in DB (height of B+
|
||||||
|
> tree) and will require at least 2 IOPS per transaction too.
|
||||||
|
|
||||||
|
3. [CoW](https://en.wikipedia.org/wiki/Copy-on-write) for
|
||||||
|
[MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control)
|
||||||
|
is done on memory page level with
|
||||||
|
[B+trees](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE).
|
||||||
|
Therefore altering data requires to copy about Olog(N) memory pages,
|
||||||
|
which uses [memory bandwidth](https://en.wikipedia.org/wiki/Memory_bandwidth) and is main
|
||||||
|
performance bottleneck in `MAPASYNC` mode.
|
||||||
|
> This is unavoidable, but isn't that bad. Syncing data to disk requires
|
||||||
|
> much more similar operations which will be done by OS, therefore this is
|
||||||
|
> noticeable only if data sync to persistent storage is fully disabled.
|
||||||
|
> _libmdbx_ allows to safely save data to persistent storage with minimal
|
||||||
|
> performance overhead. If there is no need to save data to persistent
|
||||||
|
> storage then it's much more preferable to use `std::map`.
|
||||||
|
|
||||||
|
|
||||||
|
4. LMDB has a problem of long-time readers which degrades performance
|
||||||
|
and bloats DB.
|
||||||
|
> _libmdbx_ addresses that, details below.
|
||||||
|
|
||||||
|
5. _LMDB_ is susceptible to DB corruption in `WRITEMAP+MAPASYNC` mode.
|
||||||
|
_libmdbx_ in `WRITEMAP+MAPASYNC` guarantees DB integrity and consistency
|
||||||
|
of data.
|
||||||
|
> Additionally there is an alternative: `UTTERLY_NOSYNC` mode.
|
||||||
|
> Details below.
|
||||||
|
|
||||||
|
|
||||||
|
#### Long-time read transactions problem
|
||||||
|
Garbage collection problem exists in all databases one way or another
|
||||||
|
(e.g. VACUUM in PostgreSQL). But in _libmdbx_ and LMDB it's even more
|
||||||
|
important because of high performance and deliberate simplification of
|
||||||
|
internals with emphasis on performance.
|
||||||
|
|
||||||
|
* Altering data during long read operation may exhaust available space
|
||||||
|
on persistent storage.
|
||||||
|
|
||||||
|
* If available space is exhausted then any attempt to update data
|
||||||
|
results in `MAP_FULL` error until long read operation ends.
|
||||||
|
|
||||||
|
* Main examples of long readers is hot backup and debugging of client
|
||||||
|
application which actively uses read transactions.
|
||||||
|
|
||||||
|
* In _LMDB_ this results in degraded performance of all operations of
|
||||||
|
syncing data to persistent storage.
|
||||||
|
|
||||||
|
* _libmdbx_ has a mechanism which aborts such operations and `LIFO RECLAIM`
|
||||||
|
mode which addresses performance degradation.
|
||||||
|
|
||||||
|
Read operations operate only over snapshot of DB which is consistent on
|
||||||
|
the moment when read transaction started. This snapshot doesn't change
|
||||||
|
throughout the transaction but this leads to inability to reclaim the
|
||||||
|
pages until read transaction ends.
|
||||||
|
|
||||||
|
In _LMDB_ this leads to a problem that memory pages, allocated for
|
||||||
|
operations during long read, will be used for operations and won't be
|
||||||
|
reclaimed until DB process terminates. In _LMDB_ they are used in
|
||||||
|
[FIFO](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics))
|
||||||
|
manner, which causes increased page count and less chance of cache hit
|
||||||
|
during I/O. In other words: one long-time reader can impact performance
|
||||||
|
of all database until it'll be reopened.
|
||||||
|
|
||||||
|
_libmdbx_ addresses the problem, details below. Illustrations to this
|
||||||
|
problem can be found in the
|
||||||
|
[presentation](http://www.slideshare.net/leoyuriev/lmdb). There is also
|
||||||
|
example of performance increase thanks to
|
||||||
|
[BBWC](https://en.wikipedia.org/wiki/Disk_buffer#Write_acceleration)
|
||||||
|
when `LIFO RECLAIM` enabled in _libmdbx_.
|
||||||
|
|
||||||
|
#### Data safety in async-write mode
|
||||||
|
In `WRITEMAP+MAPSYNC` mode dirty pages are written to persistent storage
|
||||||
|
by kernel. This means that in case of application crash OS kernel will
|
||||||
|
write all dirty data to disk and nothing will be lost. But in case of
|
||||||
|
hardware malfunction or OS kernel fatal error only some dirty data might
|
||||||
|
be synced to disk, and there is high probability that pages with
|
||||||
|
metadata saved, will point to non-saved, hence non-existent, data pages.
|
||||||
|
In such situation, DB is completely corrupted and can't be repaired even
|
||||||
|
if there was full sync before the crash via `mdbx_env_sync().
|
||||||
|
|
||||||
|
_libmdbx_ addresses this by fully reimplementing write path of data:
|
||||||
|
|
||||||
|
* In `WRITEMAP+MAPSYNC` mode meta-data pages aren't updated in place,
|
||||||
|
instead their shadow copies are used and their updates are synced after
|
||||||
|
data is flushed to disk.
|
||||||
|
|
||||||
|
* During transaction commit _libmdbx_ marks synchronization points as
|
||||||
|
steady or weak depending on how much synchronization needed between RAM
|
||||||
|
and persistent storage, e.g. in `WRITEMAP+MAPSYNC` commited transactions
|
||||||
|
are marked as weak, but during explicit data synchronization - as
|
||||||
|
steady.
|
||||||
|
|
||||||
|
* _libmdbx_ maintains three separate meta-pages instead of two. This
|
||||||
|
allows to commit transaction with steady or weak synchronization point
|
||||||
|
without losing two previous synchronization points (one of them can be
|
||||||
|
steady, and second - weak). This allows to order weak and steady
|
||||||
|
synchronization points in any order without losing consistency in case
|
||||||
|
of system crash.
|
||||||
|
|
||||||
|
* During DB open _libmdbx_ rollbacks to the last steady synchronization
|
||||||
|
point, this guarantees database integrity.
|
||||||
|
|
||||||
|
For data safety pages which form database snapshot with steady
|
||||||
|
synchronization point must not be updated until next steady
|
||||||
|
synchronization point. So last steady synchronization point creates
|
||||||
|
"long-time read" effect. The only difference that in case of memory
|
||||||
|
exhaustion the problem will be immediately addressed by flushing changes
|
||||||
|
to persistent storage and forming new steady synchronization point.
|
||||||
|
|
||||||
|
So in async-write mode _libmdbx_ will always use new pages until memory
|
||||||
|
is exhausted or `mdbx_env_sync()` is invoked. Total disk usage will be
|
||||||
|
almost the same as in sync-write mode.
|
||||||
|
|
||||||
|
Current _libmdbx_ gives a choice of safe async-write mode (default) and
|
||||||
|
`UTTERLY_NOSYNC` mode which may result in full DB corruption during
|
||||||
|
system crash as with LMDB.
|
||||||
|
|
||||||
|
Next version of _libmdbx_ will create steady synchronization points
|
||||||
|
automatically in async-write mode.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
Performance comparison
|
Performance comparison
|
||||||
=====================
|
======================
|
||||||
|
|
||||||
All benchmarks were done by [IOArena](https://github.com/pmwkaa/ioarena)
|
All benchmarks were done by [IOArena](https://github.com/pmwkaa/ioarena)
|
||||||
and multiple [scripts](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015)
|
and multiple [scripts](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015)
|
||||||
@ -143,18 +455,21 @@ SSD SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Gb.
|
|||||||
|
|
||||||
Here showed sum of performance metrics in 3 benchmarks:
|
Here showed sum of performance metrics in 3 benchmarks:
|
||||||
|
|
||||||
- Read/Search on 4 CPU cores machine;
|
- Read/Search on 4 CPU cores machine;
|
||||||
|
|
||||||
- Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD) operations
|
- Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD)
|
||||||
in sync-write mode (fdatasync is called after each transaction);
|
operations in sync-write mode (fdatasync is called after each
|
||||||
|
transaction);
|
||||||
|
|
||||||
- Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD) operations
|
- Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD)
|
||||||
in lazy-write mode (moment to sync data to persistent storage is decided by OS).
|
operations in lazy-write mode (moment to sync data to persistent storage
|
||||||
|
is decided by OS).
|
||||||
|
|
||||||
*Reasons why asynchronous mode isn't benchmarked here:*
|
*Reasons why asynchronous mode isn't benchmarked here:*
|
||||||
|
|
||||||
1. It doesn't make sense as it has to be done with DB engines, oriented for keeping data in memory e.g.
|
1. It doesn't make sense as it has to be done with DB engines, oriented
|
||||||
[Tarantool](https://tarantool.io/), [Redis](https://redis.io/)), etc.
|
for keeping data in memory e.g. [Tarantool](https://tarantool.io/),
|
||||||
|
[Redis](https://redis.io/)), etc.
|
||||||
|
|
||||||
2. Performance gap is too high to compare in any meaningful way.
|
2. Performance gap is too high to compare in any meaningful way.
|
||||||
|
|
||||||
@ -164,7 +479,8 @@ Here showed sum of performance metrics in 3 benchmarks:
|
|||||||
|
|
||||||
### Read Scalability
|
### Read Scalability
|
||||||
|
|
||||||
Summary performance with concurrent read/search queries in 1-2-4-8 threads on 4 CPU cores machine.
|
Summary performance with concurrent read/search queries in 1-2-4-8
|
||||||
|
threads on 4 CPU cores machine.
|
||||||
|
|
||||||
![Comparison #2: Read Scalability](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-2.png)
|
![Comparison #2: Read Scalability](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-2.png)
|
||||||
|
|
||||||
@ -172,15 +488,21 @@ Summary performance with concurrent read/search queries in 1-2-4-8 threads on 4
|
|||||||
|
|
||||||
### Sync-write mode
|
### Sync-write mode
|
||||||
|
|
||||||
- Linear scale on left and dark rectangles mean arithmetic mean transactions per second;
|
- Linear scale on left and dark rectangles mean arithmetic mean
|
||||||
|
transactions per second;
|
||||||
|
|
||||||
- Logarithmic scale on right is in seconds and yellow intervals mean execution time of transactions.
|
- Logarithmic scale on right is in seconds and yellow intervals mean
|
||||||
Each interval shows minimal and maximum execution time, cross marks standard deviation.
|
execution time of transactions. Each interval shows minimal and maximum
|
||||||
|
execution time, cross marks standard deviation.
|
||||||
|
|
||||||
**10,000 transactions in sync-write mode**. In case of a crash all data is consistent and state is right after last successful transaction. [fdatasync](https://linux.die.net/man/2/fdatasync) syscall is used after each write transaction in this mode.
|
**10,000 transactions in sync-write mode**. In case of a crash all data
|
||||||
|
is consistent and state is right after last successful transaction.
|
||||||
|
[fdatasync](https://linux.die.net/man/2/fdatasync) syscall is used after
|
||||||
|
each write transaction in this mode.
|
||||||
|
|
||||||
In the benchmark each transaction contains combined CRUD operations (2 inserts, 1 read, 1 update, 1 delete).
|
In the benchmark each transaction contains combined CRUD operations (2
|
||||||
Benchmark starts on empty database and after full run the database contains 10,000 small key-value records.
|
inserts, 1 read, 1 update, 1 delete). Benchmark starts on empty database
|
||||||
|
and after full run the database contains 10,000 small key-value records.
|
||||||
|
|
||||||
![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-3.png)
|
![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-3.png)
|
||||||
|
|
||||||
@ -188,18 +510,25 @@ Benchmark starts on empty database and after full run the database contains 10,0
|
|||||||
|
|
||||||
### Lazy-write mode
|
### Lazy-write mode
|
||||||
|
|
||||||
- Linear scale on left and dark rectangles mean arithmetic mean of thousands transactions per second;
|
- Linear scale on left and dark rectangles mean arithmetic mean of
|
||||||
|
thousands transactions per second;
|
||||||
|
|
||||||
- Logarithmic scale on right in seconds and yellow intervals mean execution time of transactions. Each interval shows minimal and maximum execution time, cross marks standard deviation.
|
- Logarithmic scale on right in seconds and yellow intervals mean
|
||||||
|
execution time of transactions. Each interval shows minimal and maximum
|
||||||
|
execution time, cross marks standard deviation.
|
||||||
|
|
||||||
**100,000 transactions in lazy-write mode**.
|
**100,000 transactions in lazy-write mode**. In case of a crash all data
|
||||||
In case of a crash all data is consistent and state is right after one of last transactions, but transactions after it
|
is consistent and state is right after one of last transactions, but
|
||||||
will be lost. Other DB engines use [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) or transaction journal for that,
|
transactions after it will be lost. Other DB engines use
|
||||||
which in turn depends on order of operations in journaled filesystem. _libmdbx_ doesn't use WAL and hands I/O operations
|
[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) or transaction
|
||||||
|
journal for that, which in turn depends on order of operations in
|
||||||
|
journaled filesystem. _libmdbx_ doesn't use WAL and hands I/O operations
|
||||||
to filesystem and OS kernel (mmap).
|
to filesystem and OS kernel (mmap).
|
||||||
|
|
||||||
In the benchmark each transaction contains combined CRUD operations (2 inserts, 1 read, 1 update, 1 delete).
|
In the benchmark each transaction contains combined CRUD operations (2
|
||||||
Benchmark starts on empty database and after full run the database contains 100,000 small key-value records.
|
inserts, 1 read, 1 update, 1 delete). Benchmark starts on empty database
|
||||||
|
and after full run the database contains 100,000 small key-value
|
||||||
|
records.
|
||||||
|
|
||||||
|
|
||||||
![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-4.png)
|
![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-4.png)
|
||||||
@ -208,14 +537,23 @@ Benchmark starts on empty database and after full run the database contains 100,
|
|||||||
|
|
||||||
### Async-write mode
|
### Async-write mode
|
||||||
|
|
||||||
- Linear scale on left and dark rectangles mean arithmetic mean of thousands transactions per second;
|
- Linear scale on left and dark rectangles mean arithmetic mean of
|
||||||
|
thousands transactions per second;
|
||||||
|
|
||||||
- Logarithmic scale on right in seconds and yellow intervals mean execution time of transactions. Each interval shows minimal and maximum execution time, cross marks standard deviation.
|
- Logarithmic scale on right in seconds and yellow intervals mean
|
||||||
|
execution time of transactions. Each interval shows minimal and maximum
|
||||||
|
execution time, cross marks standard deviation.
|
||||||
|
|
||||||
**1,000,000 transactions in async-write mode**. In case of a crash all data will be consistent and state will be right after one of last transactions, but lost transaction count is much higher than in lazy-write mode. All DB engines in this mode do as little writes as possible on persistent storage. _libmdbx_ uses [msync(MS_ASYNC)](https://linux.die.net/man/2/msync) in this mode.
|
**1,000,000 transactions in async-write mode**. In case of a crash all
|
||||||
|
data will be consistent and state will be right after one of last
|
||||||
|
transactions, but lost transaction count is much higher than in
|
||||||
|
lazy-write mode. All DB engines in this mode do as little writes as
|
||||||
|
possible on persistent storage. _libmdbx_ uses
|
||||||
|
[msync(MS_ASYNC)](https://linux.die.net/man/2/msync) in this mode.
|
||||||
|
|
||||||
In the benchmark each transaction contains combined CRUD operations (2 inserts, 1 read, 1 update, 1 delete).
|
In the benchmark each transaction contains combined CRUD operations (2
|
||||||
Benchmark starts on empty database and after full run the database contains 10,000 small key-value records.
|
inserts, 1 read, 1 update, 1 delete). Benchmark starts on empty database
|
||||||
|
and after full run the database contains 10,000 small key-value records.
|
||||||
|
|
||||||
![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-5.png)
|
![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-5.png)
|
||||||
|
|
||||||
@ -229,237 +567,22 @@ Summary of used resources during lazy-write mode benchmarks:
|
|||||||
|
|
||||||
- Sum of user CPU time and sys CPU time;
|
- Sum of user CPU time and sys CPU time;
|
||||||
|
|
||||||
- Used space on persistent storage after the test and closed DB, but not waiting for the end of all internal
|
- Used space on persistent storage after the test and closed DB, but not
|
||||||
housekeeping operations (LSM compactification, etc).
|
waiting for the end of all internal housekeeping operations (LSM
|
||||||
|
compactification, etc).
|
||||||
|
|
||||||
_ForestDB_ is excluded because benchmark showed it's resource consumption for each resource (CPU, IOPS) much higher than other engines which prevents to meaningfully compare it with them.
|
_ForestDB_ is excluded because benchmark showed it's resource
|
||||||
|
consumption for each resource (CPU, IOPS) much higher than other engines
|
||||||
|
which prevents to meaningfully compare it with them.
|
||||||
|
|
||||||
All benchmark data is gathered by [getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) syscall and by
|
All benchmark data is gathered by
|
||||||
scanning data directory.
|
[getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html)
|
||||||
|
syscall and by scanning data directory.
|
||||||
|
|
||||||
![Comparison #6: Cost comparison](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-6.png)
|
![Comparison #6: Cost comparison](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-6.png)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
## Gotchas
|
|
||||||
|
|
||||||
1. At one moment there can be only one writer. But this allows to serialize writes and eliminate any possibility
|
|
||||||
of conflict or logical errors during transaction rollback.
|
|
||||||
|
|
||||||
2. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) means relatively
|
|
||||||
big [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write Amplification Factor).
|
|
||||||
Because of this syncing data to disk might be quite resource intensive and be main performance bottleneck
|
|
||||||
during intensive write workload.
|
|
||||||
> As compromise _libmdbx_ allows several modes of lazy and/or periodic syncing, including `MAPASYNC` mode, which modificate
|
|
||||||
> data in memory and asynchronously syncs data to disk, moment to sync is picked by OS.
|
|
||||||
>
|
|
||||||
> Although this should be used with care, synchronous transactions in a DB with transaction journal will require 2 IOPS
|
|
||||||
> minimum (probably 3-4 in practice) because of filesystem overhead, overhead depends on filesystem, not on record
|
|
||||||
> count or record size. In _libmdbx_ IOPS count will grow logarithmically depending on record count in DB (height of B+ tree)
|
|
||||||
> and will require at least 2 IOPS per transaction too.
|
|
||||||
|
|
||||||
3. [CoW](https://en.wikipedia.org/wiki/Copy-on-write)
|
|
||||||
for [MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) is done on memory page level with [B+
|
|
||||||
trees](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE).
|
|
||||||
Therefore altering data requires to copy about Olog(N) memory pages, which uses [memory bandwidth](https://en.wikipedia.org/wiki/Memory_bandwidth) and is main performance bottleneck in `MAPASYNC` mode.
|
|
||||||
> This is unavoidable, but isn't that bad. Syncing data to disk requires much more similar operations which will
|
|
||||||
> be done by OS, therefore this is noticeable only if data sync to persistent storage is fully disabled.
|
|
||||||
> _libmdbx_ allows to safely save data to persistent storage with minimal performance overhead. If there is no need
|
|
||||||
> to save data to persistent storage then it's much more preferable to use `std::map`.
|
|
||||||
|
|
||||||
|
|
||||||
4. LMDB has a problem of long-time readers which degrades performance and bloats DB
|
|
||||||
> _libmdbx_ addresses that, details below.
|
|
||||||
|
|
||||||
5. _LMDB_ is susceptible to DB corruption in `WRITEMAP+MAPASYNC` mode.
|
|
||||||
_libmdbx_ in `WRITEMAP+MAPASYNC` guarantees DB integrity and consistency of data.
|
|
||||||
> Additionally there is an alternative: `UTTERLY_NOSYNC` mode. Details below.
|
|
||||||
|
|
||||||
|
|
||||||
#### Long-time read transactions problem
|
|
||||||
|
|
||||||
Garbage collection problem exists in all databases one way or another (e.g. VACUUM in PostgreSQL).
|
|
||||||
But in _libmdbx_ and LMDB it's even more important because of high performance and deliberate
|
|
||||||
simplification of internals with emphasis on performance.
|
|
||||||
|
|
||||||
* Altering data during long read operation may exhaust available space on persistent storage.
|
|
||||||
|
|
||||||
* If available space is exhausted then any attempt to update data
|
|
||||||
results in `MAP_FULL` error until long read operation ends.
|
|
||||||
|
|
||||||
* Main examples of long readers is hot backup
|
|
||||||
and debugging of client application which actively uses read transactions.
|
|
||||||
|
|
||||||
* In _LMDB_ this results in degraded performance of all operations
|
|
||||||
of syncing data to persistent storage.
|
|
||||||
|
|
||||||
* _libmdbx_ has a mechanism which aborts such operations and `LIFO RECLAIM`
|
|
||||||
mode which addresses performance degradation.
|
|
||||||
|
|
||||||
Read operations operate only over snapshot of DB which is consistent on the moment when read transaction started.
|
|
||||||
This snapshot doesn't change throughout the transaction but this leads to inability to reclaim the pages until
|
|
||||||
read transaction ends.
|
|
||||||
|
|
||||||
In _LMDB_ this leads to a problem that memory pages, allocated for operations during long read, will be used for operations
|
|
||||||
and won't be reclaimed until DB process terminates. In _LMDB_ they are used in
|
|
||||||
[FIFO](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)) manner, which causes increased page count
|
|
||||||
and less chance of cache hit during I/O. In other words: one long-time reader can impact performance of all database
|
|
||||||
until it'll be reopened.
|
|
||||||
|
|
||||||
_libmdbx_ addresses the problem, details below. Illustrations to this problem can be found in the
|
|
||||||
[presentation](http://www.slideshare.net/leoyuriev/lmdb). There is also example of performance increase thanks to
|
|
||||||
[BBWC](https://en.wikipedia.org/wiki/Disk_buffer#Write_acceleration) when `LIFO RECLAIM` enabled in _libmdbx_.
|
|
||||||
|
|
||||||
#### Data safety in async-write mode
|
|
||||||
|
|
||||||
In `WRITEMAP+MAPSYNC` mode dirty pages are written to persistent storage by kernel. This means that in case of application
|
|
||||||
crash OS kernel will write all dirty data to disk and nothing will be lost. But in case of hardware malfunction or OS kernel
|
|
||||||
fatal error only some dirty data might be synced to disk, and there is high probability that pages with metadata saved,
|
|
||||||
will point to non-saved, hence non-existent, data pages. In such situation, DB is completely corrupted and can't be
|
|
||||||
repaired even if there was full sync before the crash via `mdbx_env_sync().
|
|
||||||
|
|
||||||
_libmdbx_ addresses this by fully reimplementing write path of data:
|
|
||||||
|
|
||||||
* In `WRITEMAP+MAPSYNC` mode meta-data pages aren't updated in place, instead their shadow copies are used and their updates
|
|
||||||
are synced after data is flushed to disk.
|
|
||||||
|
|
||||||
* During transaction commit _libmdbx_ marks synchronization points as steady or weak depending on how much synchronization
|
|
||||||
needed between RAM and persistent storage, e.g. in `WRITEMAP+MAPSYNC` commited transactions are marked as weak,
|
|
||||||
but during explicit data synchronization - as steady.
|
|
||||||
|
|
||||||
* _libmdbx_ maintains three separate meta-pages instead of two. This allows to commit transaction with steady or
|
|
||||||
weak synchronization point without losing two previous synchronization points (one of them can be steady, and second - weak).
|
|
||||||
This allows to order weak and steady synchronization points in any order without losing consistency in case of system crash.
|
|
||||||
|
|
||||||
* During DB open _libmdbx_ rollbacks to the last steady synchronization point, this guarantees database integrity.
|
|
||||||
|
|
||||||
For data safety pages which form database snapshot with steady synchronization point must not be updated until next steady
|
|
||||||
synchronization point. So last steady synchronization point creates "long-time read" effect. The only difference that in case
|
|
||||||
of memory exhaustion the problem will be immediately addressed by flushing changes to persistent storage and forming new steady
|
|
||||||
synchronization point.
|
|
||||||
|
|
||||||
So in async-write mode _libmdbx_ will always use new pages until memory is exhausted or `mdbx_env_sync()` is invoked. Total
|
|
||||||
disk usage will be almost the same as in sync-write mode.
|
|
||||||
|
|
||||||
Current _libmdbx_ gives a choice of safe async-write mode (default) and `UTTERLY_NOSYNC` mode which may result in full DB
|
|
||||||
corruption during system crash as with LMDB.
|
|
||||||
|
|
||||||
Next version of _libmdbx_ will create steady synchronization points automatically in async-write mode.
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Improvements over LMDB
|
|
||||||
================================================
|
|
||||||
|
|
||||||
1. `LIFO RECLAIM` mode:
|
|
||||||
|
|
||||||
The newest pages are picked for reuse instead of the oldest.
|
|
||||||
This allows to minimize reclaim loop and make it execution time independent of total page count.
|
|
||||||
|
|
||||||
This results in OS kernel cache mechanisms working with maximum efficiency.
|
|
||||||
In case of using disk controllers or storages with
|
|
||||||
[BBWC](https://en.wikipedia.org/wiki/Disk_buffer#Write_acceleration) this may greatly improve
|
|
||||||
write performance.
|
|
||||||
|
|
||||||
2. `OOM-KICK` callback.
|
|
||||||
|
|
||||||
`mdbx_env_set_oomfunc()` allows to set a callback, which will be called
|
|
||||||
in the event of memory exhausting during long-time read transaction.
|
|
||||||
Callback will be invoked with PID and pthread_id of offending thread as parameters.
|
|
||||||
Callback can do any of these things to remedy the problem:
|
|
||||||
|
|
||||||
* wait for read transaction to finish normally;
|
|
||||||
|
|
||||||
* kill the offending process (signal 9), if separate process is doing long-time read;
|
|
||||||
|
|
||||||
* abort or restart offending read transaction if it's running in sibling thread;
|
|
||||||
|
|
||||||
* abort current write transaction with returning error code.
|
|
||||||
|
|
||||||
3. Guarantee of DB integrity in `WRITEMAP+MAPSYNC` mode:
|
|
||||||
> Current _libmdbx_ gives a choice of safe async-write mode (default)
|
|
||||||
> and `UTTERLY_NOSYNC` mode which may result in full
|
|
||||||
> DB corruption during system crash as with LMDB. For details see
|
|
||||||
> [Data safety in async-write mode](#data-safety-in-async-write-mode).
|
|
||||||
|
|
||||||
4. Automatic creation of synchronization points (flush changes to persistent storage)
|
|
||||||
when changes reach set threshold (threshold can be set by `mdbx_env_set_syncbytes()`).
|
|
||||||
|
|
||||||
5. Ability to get how far current read-only snapshot is from latest version of the DB by `mdbx_txn_straggler()`.
|
|
||||||
|
|
||||||
6. `mdbx_chk` tool for DB checking and `mdbx_env_pgwalk()` for page-walking all pages in DB.
|
|
||||||
|
|
||||||
7. Control over debugging and receiving of debugging messages via `mdbx_setup_debug()`.
|
|
||||||
|
|
||||||
8. Ability to assign up to 3 markers to commiting transaction with `mdbx_canary_put()` and then get them in read transaction
|
|
||||||
by `mdbx_canary_get()`.
|
|
||||||
|
|
||||||
9. Check if there is a row with data after current cursor position via `mdbx_cursor_eof()`.
|
|
||||||
|
|
||||||
10. Ability to explicitly request update of present record without creating new record. Implemented as `MDBX_CURRENT` flag
|
|
||||||
for `mdbx_put()`.
|
|
||||||
|
|
||||||
11. Ability to update or delete record and get previous value via `mdbx_replace()` Also can update specific multi-value.
|
|
||||||
|
|
||||||
12. Support for keys and values of zero length, including sorted duplicates.
|
|
||||||
|
|
||||||
13. Fixed `mdbx_cursor_count()`, which returns correct count of duplicated for all table types and any cursor position.
|
|
||||||
|
|
||||||
14. Ability to open DB in exclusive mode with `MDBX_EXCLUSIVE` flag, e.g. for integrity check.
|
|
||||||
|
|
||||||
15. Ability to close DB in "dirty" state (without data flush and creation of steady synchronization point)
|
|
||||||
via `mdbx_env_close_ex()`.
|
|
||||||
|
|
||||||
16. Ability to get additional info, including number of the oldest snapshot of DB, which is used by one of the readers.
|
|
||||||
Implemented via `mdbx_env_info()`.
|
|
||||||
|
|
||||||
17. `mdbx_del()` doesn't ignore additional argument (specifier) `data`
|
|
||||||
for tables without duplicates (without flag `MDBX_DUPSORT`), if `data` is not zero then always uses it to verify
|
|
||||||
record, which is being deleted.
|
|
||||||
|
|
||||||
18. Ability to open dbi-table with simultaneous setup of comparators for keys and values, via `mdbx_dbi_open_ex()`.
|
|
||||||
|
|
||||||
19. Ability to find out if key or value is in dirty page. This may be useful to make a decision to avoid
|
|
||||||
excessive CoW before updates. Implemented via `mdbx_is_dirty()`.
|
|
||||||
|
|
||||||
20. Correct update of current record in `MDBX_CURRENT` mode of `mdbx_cursor_put()`, including sorted duplicated.
|
|
||||||
|
|
||||||
21. All cursors in all read and write transactions can be reused by `mdbx_cursor_renew()` and MUST be freed explicitly.
|
|
||||||
> ## Caution, please pay attention!
|
|
||||||
>
|
|
||||||
> This is the only change of API, which changes semantics of cursor management
|
|
||||||
> and can lead to memory leaks on misuse. This is a needed change as it eliminates ambiguity
|
|
||||||
> which helps to avoid such errors as:
|
|
||||||
> - use-after-free;
|
|
||||||
> - double-free;
|
|
||||||
> - memory corruption and segfaults.
|
|
||||||
|
|
||||||
22. Additional error code `MDBX_EMULTIVAL`, which is returned by `mdbx_put()` and
|
|
||||||
`mdbx_replace()` in case is ambiguous update or delete.
|
|
||||||
|
|
||||||
23. Ability to get value by key and duplicates count by `mdbx_get_ex()`.
|
|
||||||
|
|
||||||
24. Functions `mdbx_cursor_on_first() and mdbx_cursor_on_last(), which allows to know if cursor is currently on first or
|
|
||||||
last position respectively.
|
|
||||||
|
|
||||||
25. If read transaction is aborted via `mdbx_txn_abort()` or `mdbx_txn_reset()` then DBI-handles, which were opened in it,
|
|
||||||
aren't closed or deleted. This allows to avoid several types of hard-to-debug errors.
|
|
||||||
|
|
||||||
26. Sequence generation via `mdbx_dbi_sequence()`.
|
|
||||||
|
|
||||||
27. Advanced dynamic control over DB size, including ability to choose page size via `mdbx_env_set_geometry()`,
|
|
||||||
including on Windows.
|
|
||||||
|
|
||||||
28. Three meta-pages instead of two, this allows to guarantee consistently update weak sync-points without risking to
|
|
||||||
corrupt last steady sync-point.
|
|
||||||
|
|
||||||
29. Automatic reclaim of freed pages to specific reserved space at the end of database file. This lowers amount of pages,
|
|
||||||
loaded to memory, used in update/flush loop. In fact _libmdbx_ constantly performs compactification of data,
|
|
||||||
but doesn't use additional resources for that. Space reclaim of DB and setup of database geometry parameters also decreases
|
|
||||||
size of the database on disk, including on Windows.
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ objdump -f -h -j .text libmdbx.so
|
$ objdump -f -h -j .text libmdbx.so
|
||||||
|
|
||||||
@ -474,16 +597,3 @@ Idx Name Size VMA LMA File off Algn
|
|||||||
CONTENTS, ALLOC, LOAD, READONLY, CODE
|
CONTENTS, ALLOC, LOAD, READONLY, CODE
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
|
||||||
$ gcc -v
|
|
||||||
Using built-in specs.
|
|
||||||
COLLECT_GCC=gcc
|
|
||||||
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
|
|
||||||
OFFLOAD_TARGET_NAMES=nvptx-none
|
|
||||||
OFFLOAD_TARGET_DEFAULT=1
|
|
||||||
Target: x86_64-linux-gnu
|
|
||||||
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.2.0-8ubuntu3' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
|
|
||||||
Thread model: posix
|
|
||||||
gcc version 7.2.0 (Ubuntu 7.2.0-8ubuntu3)
|
|
||||||
```
|
|
||||||
|
Loading…
Reference in New Issue
Block a user