mirror of
https://github.com/isar/libmdbx.git
synced 2025-08-19 19:39:26 +08:00
mdbx: Merge branch 'devel' (brave new world, forever lost compatibility with LMDB).
Change-Id: Icc359e01c8a00be6b3ac34a3aaad6f73201e39b3
This commit is contained in:
424
README.md
424
README.md
@@ -4,27 +4,39 @@ Extended LMDB, aka "Расширенная LMDB".
|
||||
|
||||
*The Future will Positive. Всё будет хорошо.*
|
||||
[](https://travis-ci.org/ReOpen/libmdbx)
|
||||
[](https://ci.appveyor.com/project/leo-yuriev/libmdbx/branch/master)
|
||||
[](https://scan.coverity.com/projects/reopen-libmdbx)
|
||||
|
||||
English version by Google [is here](https://translate.googleusercontent.com/translate_c?act=url&ie=UTF8&sl=ru&tl=en&u=https://github.com/ReOpen/libmdbx/tree/master).
|
||||
English version [by Google](https://translate.googleusercontent.com/translate_c?act=url&ie=UTF8&sl=ru&tl=en&u=https://github.com/ReOpen/libmdbx/tree/master)
|
||||
and [by Yandex](https://translate.yandex.ru/translate?url=https%3A%2F%2Fgithub.com%2FReOpen%2Flibmdbx%2Ftree%2Fmaster&lang=ru-en).
|
||||
|
||||
|
||||
## Кратко
|
||||
|
||||
_libmdbx_ - это встраиваемый key-value движок хранения со специфическим
|
||||
набором возможностей, которые при правильном применении позволяют
|
||||
создавать уникальные решения с чемпионской производительностью, идеально
|
||||
сочетаясь с технологией [MRAM](https://en.wikipedia.org/wiki/Magnetoresistive_random-access_memory).
|
||||
набором свойств и возможностей, ориентированный на создание уникальных
|
||||
легковесных решений с предельной производительностью.
|
||||
|
||||
_libmdbx_ позволяет множеству процессов совместно читать и обновлять
|
||||
несколько key-value таблиц с соблюдением [ACID](https://ru.wikipedia.org/wiki/ACID),
|
||||
при минимальных накладных расходах и амортизационной стоимости любых операций Olog(N).
|
||||
|
||||
_libmdbx_ обеспечивает
|
||||
[serializability](https://en.wikipedia.org/wiki/Serializability)
|
||||
изменений и согласованность данных после аварий. При этом транзакции
|
||||
изменяющие данные никак не мешают операциям чтения и выполняются строго
|
||||
последовательно с использованием единственного
|
||||
[мьютекса](https://en.wikipedia.org/wiki/Mutual_exclusion).
|
||||
|
||||
_libmdbx_ позволяет выполнять операции чтения с гарантиями
|
||||
[wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom),
|
||||
параллельно на каждом ядре CPU, без использования атомарных операций
|
||||
и/или примитивов синхронизации.
|
||||
|
||||
_libmdbx_ обновляет совместно используемый набор данных, никак не мешая
|
||||
при этом параллельным операциям чтения, не применяя атомарных операций к
|
||||
самим данным, и обеспечивая согласованность при аварийной остановке в
|
||||
любой момент. Поэтому _libmdbx_ позволяя строить системы с линейным
|
||||
масштабированием производительности чтения/поиска по ядрам CPU и
|
||||
амортизационной стоимостью любых операций Olog(N).
|
||||
|
||||
### История
|
||||
|
||||
_libmdbx_ является потомком "Lightning Memory-Mapped Database",
|
||||
_libmdbx_ является развитием "Lightning Memory-Mapped Database",
|
||||
известной под аббревиатурой
|
||||
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
|
||||
Изначально доработка производилась в составе проекта
|
||||
@@ -34,22 +46,26 @@ _libmdbx_ является потомком "Lightning Memory-Mapped Database",
|
||||
[представлен на конференции Highload++
|
||||
2015](http://www.highload.ru/2015/abstracts/1831.html).
|
||||
|
||||
В начале 2017 года движок _libmdbx_ получил новый импульс развития,
|
||||
благодаря использованию в [Fast Positive
|
||||
Tables](https://github.com/leo-yuriev/libfpta), aka ["Позитивные
|
||||
Таблицы"](https://github.com/leo-yuriev/libfpta) by [Positive
|
||||
Technologies](https://www.ptsecurity.ru).
|
||||
|
||||
|
||||
Характеристики и ключевые особенности
|
||||
=====================================
|
||||
|
||||
_libmdbx_ наследует все ключевые возможности и особенности от
|
||||
своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database),
|
||||
с устранением описанных далее проблем и архитектурных недочетов.
|
||||
|
||||
### Общее для оригинальной _LMDB_ и _libmdbx_
|
||||
но с устранением ряда описываемых далее проблем и архитектурных недочетов.
|
||||
|
||||
1. Данные хранятся в упорядоченном отображении (ordered map), ключи всегда
|
||||
отсортированы, поддерживается выборка диапазонов (range lookups).
|
||||
|
||||
2. Данные отображается в память каждого работающего с БД процесса.
|
||||
Ключам и данным обеспечивается прямой доступ без необходимости их
|
||||
копирования, так как они защищены транзакцией чтения и не изменяются.
|
||||
К данным и ключам обеспечивается прямой доступ в памяти без необходимости их
|
||||
копирования.
|
||||
|
||||
3. Транзакции согласно
|
||||
[ACID](https://ru.wikipedia.org/wiki/ACID), посредством
|
||||
@@ -57,20 +73,26 @@ _libmdbx_ наследует все ключевые возможности и
|
||||
[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 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),
|
||||
без [атомарных операций](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. Эффективное хранение дубликатов (ключей с несколькими
|
||||
значениями), без дублирования ключей, с сортировкой значений, в
|
||||
том числе целочисленных (для вторичных индексов).
|
||||
|
||||
6. Эффективная поддержка ключей фиксированной длины, в том числе целочисленных.
|
||||
6. Эффективная поддержка коротких ключей фиксированной длины, в том числе целочисленных.
|
||||
|
||||
7. Амортизационная стоимость любой операции Olog(N),
|
||||
[WAF](https://en.wikipedia.org/wiki/Write_amplification) и RAF также 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) и журнала
|
||||
транзакций, после сбоев не требуется восстановление. Не требуется компактификация
|
||||
@@ -78,45 +100,231 @@ _libmdbx_ наследует все ключевые возможности и
|
||||
"по горячему", на работающей БД без приостановки изменения данных.
|
||||
|
||||
9. Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё
|
||||
необходимое штатно выполняет ядро ОС.
|
||||
необходимое штатно выполняет ядро ОС!
|
||||
|
||||
|
||||
### Недостатки и Компромиссы
|
||||
Сравнение производительности
|
||||
============================
|
||||
Все представленные ниже данные получены многократным прогоном тестов на
|
||||
ноутбуке Lenovo Carbon-2, i7-4600U 2.1 ГГц, 8 Гб ОЗУ, с SSD-диском
|
||||
SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Гб.
|
||||
|
||||
Исходный код бенчмарка [_IOArena_](https://github.com/pmwkaa/ioarena) и
|
||||
сценарии тестирования [доступны на
|
||||
github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
### Интегральная производительность
|
||||

|
||||
|
||||
Показана соотнесенная сумма ключевых показателей производительности в трёх
|
||||
бенчмарках:
|
||||
|
||||
- Чтение/Поиск на машине с 4-мя процессорами;
|
||||
|
||||
- Транзакции с [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциями
|
||||
(вставка, чтение, обновление, удаление) в режиме **синхронной фиксации**
|
||||
данных (fdatasync при завершении каждой транзакции или аналог);
|
||||
|
||||
- Транзакции с [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциями
|
||||
(вставка, чтение, обновление, удаление) в режиме **отложенной фиксации**
|
||||
данных (отложенная запись посредством файловой систем или аналог);
|
||||
|
||||
*Бенчмарк в режиме асинхронной записи не включен по двум причинам:*
|
||||
|
||||
1. Такое сравнение не совсем правомочно, его следует делать с движками
|
||||
ориентированными на хранение данных в памяти ([Tarantool](https://tarantool.io/), [Redis](https://redis.io/)).
|
||||
|
||||
2. Превосходство libmdbx становится еще более подавляющем, что мешает
|
||||
восприятию информации.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
### Масштабируемость чтения
|
||||

|
||||
|
||||
Для каждого движка показана суммарная производительность при
|
||||
одновременном выполнении запросов чтения/поиска в 1-2-4-8 потоков на
|
||||
машине с 4-мя физическими процессорами.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
### Синхронная фиксация
|
||||

|
||||
|
||||
- Линейная шкала слева и темные прямоугольники соответствуют количеству
|
||||
транзакций в секунду, усредненному за всё время теста.
|
||||
|
||||
- Логарифмическая шкала справа и желтые интервальные отрезки
|
||||
соответствуют времени выполнения транзакций. При этом каждый отрезок
|
||||
показывает минимальное и максимальное время затраченное на выполнение
|
||||
транзакций, а крестиком отмечено среднеквадратичное значение.
|
||||
|
||||
Выполняется **10.000 транзакций в режиме синхронной фиксации данных** на
|
||||
диске. При этом требуется гарантия, что при аварийном выключении питания
|
||||
(или другом подобном сбое) все данные будут консистентны и полностью
|
||||
соответствовать последней завершенной транзакции. В _libmdbx_ в этом
|
||||
режиме при фиксации каждой транзакции выполняется системный вызов
|
||||
[fdatasync](https://linux.die.net/man/2/fdatasync).
|
||||
|
||||
В каждой транзакции выполняется комбинированная CRUD-операция (две
|
||||
вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует
|
||||
на пустой базе, а при завершении, в результате выполняемых действий, в
|
||||
базе насчитывается 10.000 небольших key-value записей.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
### Отложенная фиксация
|
||||

|
||||
|
||||
- Линейная шкала слева и темные прямоугольники соответствуют количеству
|
||||
транзакций в секунду, усредненному за всё время теста.
|
||||
|
||||
- Логарифмическая шкала справа и желтые интервальные отрезки
|
||||
соответствуют времени выполнения транзакций. При этом каждый отрезок
|
||||
показывает минимальное и максимальное время затраченное на выполнение
|
||||
транзакций, а крестиком отмечено среднеквадратичное значение.
|
||||
|
||||
Выполняется **100.000 транзакций в режиме отложенной фиксации данных**
|
||||
на диске. При этом требуется гарантия, что при аварийном выключении
|
||||
питания (или другом подобном сбое) все данные будут консистентны на
|
||||
момент завершения одной из транзакций, но допускается потеря изменений
|
||||
из некоторого количества последних транзакций, что для многих движков
|
||||
предполагает включение
|
||||
[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) (write-ahead
|
||||
logging) либо журнала транзакций, который в свою очередь опирается на
|
||||
гарантию упорядоченности данных в журналируемой файловой системе.
|
||||
_libmdbx_ при этом не ведет WAL, а передает весь контроль файловой
|
||||
системе и ядру ОС.
|
||||
|
||||
В каждой транзакции выполняется комбинированная CRUD-операция (две
|
||||
вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует
|
||||
на пустой базе, а при завершении, в результате выполняемых действий, в
|
||||
базе насчитывается 100.000 небольших key-value записей.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
### Асинхронная фиксация
|
||||

|
||||
|
||||
- Линейная шкала слева и темные прямоугольники соответствуют количеству
|
||||
транзакций в секунду, усредненному за всё время теста.
|
||||
|
||||
- Логарифмическая шкала справа и желтые интервальные отрезки
|
||||
соответствуют времени выполнения транзакций. При этом каждый отрезок
|
||||
показывает минимальное и максимальное время затраченное на выполнение
|
||||
транзакций, а крестиком отмечено среднеквадратичное значение.
|
||||
|
||||
Выполняется **1.000.000 транзакций в режиме асинхронной фиксации
|
||||
данных** на диске. При этом требуется гарантия, что при аварийном
|
||||
выключении питания (или другом подобном сбое) все данные будут
|
||||
консистентны на момент завершения одной из транзакций, но допускается
|
||||
потеря изменений из значительного количества последних транзакций. Во
|
||||
всех движках при этом включался режим предполагающий минимальную
|
||||
нагрузку на диск по-записи, и соответственно минимальную гарантию
|
||||
сохранности данных. В _libmdbx_ при этом используется режим асинхронной
|
||||
записи измененных страниц на диск посредством ядра ОС и системного
|
||||
вызова [msync(MS_ASYNC)](https://linux.die.net/man/2/msync).
|
||||
|
||||
В каждой транзакции выполняется комбинированная CRUD-операция (две
|
||||
вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует
|
||||
на пустой базе, а при завершении, в результате выполняемых действий, в
|
||||
базе насчитывается 10.000 небольших key-value записей.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
### Стоимость как потребление ресурсов
|
||||

|
||||
|
||||
Показана соотнесенная сумма использованных ресурсов в ходе бенчмарка в
|
||||
режиме отложенной фиксации:
|
||||
|
||||
- суммарное количество операций ввода-вывода (IOPS), как записи, так и
|
||||
чтения.
|
||||
|
||||
- суммарное затраченное время процессора, как в режиме пользовательских процессов,
|
||||
так и в режиме ядра ОС.
|
||||
|
||||
- использованное место на диске при завершении теста, после закрытия БД из тестирующего процесса,
|
||||
но без ожидания всех внутренних операций обслуживания (компактификации LSM и т.п.).
|
||||
|
||||
Движок _ForestDB_ был исключен при оформлении результатов, так как
|
||||
относительно конкурентов многократно превысил потребление каждого из
|
||||
ресурсов (потратил процессорное время на генерацию IOPS для заполнения
|
||||
диска), что не позволяло наглядно сравнить показатели остальных движков
|
||||
на одной диаграмме.
|
||||
|
||||
Все данные собирались посредством системного вызова
|
||||
[getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) и
|
||||
сканированием директорий с данными.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
## Недостатки и Компромиссы
|
||||
|
||||
1. Единовременно может выполняться не более одной транзакция изменения данных
|
||||
(один писатель). Зато все изменения всегда последовательны, не может быть
|
||||
конфликтов или ошибок при откате транзакций.
|
||||
конфликтов или логических ошибок при откате транзакций.
|
||||
|
||||
2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging)
|
||||
обуславливает относительно большой
|
||||
[WAF](https://en.wikipedia.org/wiki/Write_amplification). Поэтому фиксация
|
||||
изменений на диске может быть дорогой и является главным ограничителем для
|
||||
производительности по записи. В качестве компромисса предлагается несколько
|
||||
режимов ленивой и/или периодической фиксации. В том числе режим `MAPASYNC`,
|
||||
при котором изменения происходят только в памяти и асинхронно фиксируются на
|
||||
диске ядром ОС.
|
||||
[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`.
|
||||
уровне страниц в [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_ предложены средства для предотвращения, выхода из проблемной
|
||||
ситуации и устранения её последствий. Подробности ниже.
|
||||
> В _libmdbx_ предложены средства для предотвращения, быстрого выхода из
|
||||
> некомфортной ситуации и устранения её последствий. Подробности ниже.
|
||||
|
||||
5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`.
|
||||
В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы,
|
||||
так и согласованность данных. При этом также, в качестве альтернативы,
|
||||
предложен режим `UTTERLY_NOSYNC`. Подробности ниже.
|
||||
так и согласованность данных.
|
||||
> Дополнительно, в качестве альтернативы, предложен режим `UTTERLY_NOSYNC`.
|
||||
> Подробности ниже.
|
||||
|
||||
|
||||
#### Проблема долгих чтений
|
||||
|
||||
*Следует отметить*, что проблема "сборки мусора" так или иначе
|
||||
существует во всех СУБД (Vacuum в PostgreSQL). Однако в случае _libmdbx_
|
||||
и LMDB она проявляется более остро, прежде всего из-за высокой
|
||||
производительности, а также из-за намеренного прощения внутренних
|
||||
механизмов ради производительности.
|
||||
|
||||
Понимание проблемы требует некоторых пояснений, которые
|
||||
изложены ниже, но могут быть сложны для быстрого восприятия.
|
||||
Поэтому, тезисно:
|
||||
@@ -141,66 +349,62 @@ _libmdbx_ наследует все ключевые возможности и
|
||||
деградацию производительности.
|
||||
|
||||
Операции чтения выполняются в контексте снимка данных (версии
|
||||
БД), который был актуальным на момент старта транзакции чтения.
|
||||
Такой читаемый снимок поддерживается неизменным до завершения
|
||||
операции. В свою очередь, это не позволяет повторно
|
||||
использовать страницы БД в последующих версиях (снимках БД).
|
||||
БД), который был актуальным на момент старта транзакции чтения. Такой
|
||||
читаемый снимок поддерживается неизменным до завершения операции. В свою
|
||||
очередь, это не позволяет повторно использовать страницы БД в
|
||||
последующих версиях (снимках БД).
|
||||
|
||||
Другими словами, если обновление данных выполняется на фоне
|
||||
долгой операции чтения, то вместо повторного использования
|
||||
"старых" ненужных страниц будут выделяться новые, так как
|
||||
"старые" страницы составляют снимок БД, который еще
|
||||
используется долгой операцией чтения.
|
||||
Другими словами, если обновление данных выполняется на фоне долгой
|
||||
операции чтения, то вместо повторного использования "старых" ненужных
|
||||
страниц будут выделяться новые, так как "старые" страницы составляют
|
||||
снимок БД, который еще используется долгой операцией чтения.
|
||||
|
||||
В результате, при интенсивном изменении данных и достаточно
|
||||
длительной операции чтения, в БД могут быть исчерпаны свободные
|
||||
страницы, что не позволит создавать новые снимки/версии БД.
|
||||
Такая ситуация будет сохраняться до завершения операции чтения,
|
||||
которая использует старый снимок данных и препятствует
|
||||
повторному использованию страниц БД.
|
||||
В результате, при интенсивном изменении данных и достаточно длительной
|
||||
операции чтения, в БД могут быть исчерпаны свободные страницы, что не
|
||||
позволит создавать новые снимки/версии БД. Такая ситуация будет
|
||||
сохраняться до завершения операции чтения, которая использует старый
|
||||
снимок данных и препятствует повторному использованию страниц БД.
|
||||
|
||||
Однако, на этом проблемы не заканчиваются. После описанной
|
||||
ситуации, все дополнительные страницы, которые были выделены
|
||||
пока переработка старых была невозможна, будут участвовать в
|
||||
цикле выделения/освобождения до конца жизни экземпляра БД. В
|
||||
оригинальной _LMDB_ этот цикл использования страниц работает по
|
||||
принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому
|
||||
увеличение количества циркулирующий страниц, с точки зрения
|
||||
механизмов кэширования и/или обратной записи, выглядит как
|
||||
увеличение рабочего набор данных. Проще говоря, однократное
|
||||
попадание в ситуацию "уснувшего читателя" приводит к
|
||||
устойчивому эффекту вымывания I/O кэша при всех последующих
|
||||
изменениях данных.
|
||||
Однако, на этом проблемы не заканчиваются. После описанной ситуации, все
|
||||
дополнительные страницы, которые были выделены пока переработка старых
|
||||
была невозможна, будут участвовать в цикле выделения/освобождения до
|
||||
конца жизни экземпляра БД. В оригинальной _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_.
|
||||
Для устранения описанных проблемы в _libmdbx_ сделаны существенные
|
||||
доработки, подробности ниже. Иллюстрации к проблеме "долгих чтений"
|
||||
можно найти в [слайдах презентации](http://www.slideshare.net/leoyuriev/lmdb).
|
||||
|
||||
Там же приведен пример количественной оценки прироста производительности
|
||||
за счет эффективной работы [BBWC](https://en.wikipedia.org/wiki/BBWC)
|
||||
при включении `LIFO RECLAIM` в _libmdbx_.
|
||||
|
||||
|
||||
#### Вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`
|
||||
|
||||
При работе в режиме `WRITEMAP+MAPSYNC` запись измененных
|
||||
страниц выполняется ядром ОС, что имеет ряд преимуществ. Так
|
||||
например, при крахе приложения, ядро ОС сохранит все изменения.
|
||||
При работе в режиме `WRITEMAP+MAPSYNC` запись измененных страниц
|
||||
выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе
|
||||
приложения, ядро ОС сохранит все изменения.
|
||||
|
||||
Однако, при аварийном отключении питания или сбое в ядре ОС, на
|
||||
диске будет сохранена только часть измененных страниц БД. При
|
||||
этом с большой вероятностью может оказаться так, что будут
|
||||
сохранены мета-страницы со ссылками на страницы с новыми
|
||||
версиями данных, но не сами новые данные. В этом случае БД
|
||||
будет безвозвратна разрушена, даже если до аварии производилась
|
||||
полная синхронизация данных (посредством `mdb_env_sync()`).
|
||||
Однако, при аварийном отключении питания или сбое в ядре ОС, на диске
|
||||
будет сохранена только часть измененных страниц БД. При этом с большой
|
||||
вероятностью может оказаться так, что будут сохранены мета-страницы со
|
||||
ссылками на страницы с новыми версиями данных, но не сами новые данные.
|
||||
В этом случае БД будет безвозвратна разрушена, даже если до аварии
|
||||
производилась полная синхронизация данных (посредством
|
||||
`mdbx_env_sync()`).
|
||||
|
||||
В _libmdbx_ эта проблема устранена, подробности ниже.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Доработки _libmdbx_
|
||||
===================
|
||||
Дополнительные "фичи" _libmdbx_ относительно LMDB
|
||||
=================================================
|
||||
|
||||
1. Режим `LIFO RECLAIM`.
|
||||
|
||||
@@ -221,11 +425,11 @@ RECLAIM` в _libmdbx_.
|
||||
Посредством `mdbx_env_set_oomfunc()` может быть установлен
|
||||
внешний обработчик (callback), который будет вызван при
|
||||
исчерпания свободных страниц из-за долгой операцией чтения.
|
||||
Обработчику будет передан PID и pthread_id. В свою очередь
|
||||
обработчик может предпринять одно из действий:
|
||||
Обработчику будет передан PID и pthread_id виновника.
|
||||
В свою очередь обработчик может предпринять одно из действий:
|
||||
|
||||
* отправить сигнал kill (#9), если долгое чтение выполняется
|
||||
сторонним процессом;
|
||||
* нейтрализовать виновника (отправить сигнал kill #9), если
|
||||
долгое чтение выполняется сторонним процессом;
|
||||
|
||||
* отменить или перезапустить проблемную операцию чтения, если
|
||||
операция выполняется одним из потоков текущего процесса;
|
||||
@@ -248,7 +452,7 @@ RECLAIM` в _libmdbx_.
|
||||
сохранены мета-страницы со ссылками на страницы с новыми
|
||||
версиями данных, но не сами новые данные. В этом случае БД
|
||||
будет безвозвратна разрушена, даже если до аварии производилась
|
||||
полная синхронизация данных (посредством `mdb_env_sync()`).
|
||||
полная синхронизация данных (посредством `mdbx_env_sync()`).
|
||||
|
||||
В _libmdbx_ эта проблема устранена путем полной переработки
|
||||
пути записи данных:
|
||||
@@ -264,6 +468,14 @@ RECLAIM` в _libmdbx_.
|
||||
`WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как
|
||||
слабые, а при явной синхронизации данных как сильные.
|
||||
|
||||
* В _libmdbx_ поддерживается не две, а три отдельные мета-страницы.
|
||||
Это позволяет выполнять фиксацию транзакций с формированием как
|
||||
сильной, так и слабой точки фиксации, без потери двух предыдущих
|
||||
точек фиксации (из которых одна может быть сильной, а вторая слабой).
|
||||
В результате, _libmdbx_ позволяет в произвольном порядке чередовать
|
||||
сильные и слабые точки фиксации без нарушения соответствующих
|
||||
гарантий в случае неожиданной системной аварии во время фиксации.
|
||||
|
||||
* При открытии БД выполняется автоматический откат к последней
|
||||
сильной фиксации. Этим обеспечивается гарантия сохранности БД.
|
||||
|
||||
@@ -302,14 +514,14 @@ RECLAIM` в _libmdbx_.
|
||||
посредством `mdbx_cursor_eof()`.
|
||||
|
||||
10. Возможность явно запросить обновление существующей записи, без
|
||||
создания новой посредством флажка `MDB_CURRENT` для `mdbx_put()`.
|
||||
создания новой посредством флажка `MDBX_CURRENT` для `mdbx_put()`.
|
||||
|
||||
11. Возможность обновить или удалить запись с получением предыдущего
|
||||
значения данных посредством `mdbx_replace()`.
|
||||
11. Возможность посредством `mdbx_replace()` обновить или удалить запись
|
||||
с получением предыдущего значения данных, а также адресно изменить
|
||||
конкретное multi-значение.
|
||||
|
||||
12. Поддержка ключей и значений нулевой длины. Включая сортированные
|
||||
дубликаты, в том числе вне зависимости от порядка их добавления или
|
||||
обновления.
|
||||
12. Поддержка ключей и значений нулевой длины, включая сортированные
|
||||
дубликаты.
|
||||
|
||||
13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное
|
||||
количество дубликатов для всех типов таблиц и любого положения курсора.
|
||||
@@ -325,7 +537,7 @@ RECLAIM` в _libmdbx_.
|
||||
который используется одним из читателей.
|
||||
|
||||
17. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий)
|
||||
аргумент `data` для таблиц без дубликатов (без флажка `MDB_DUPSORT`), а
|
||||
аргумент `data` для таблиц без дубликатов (без флажка `MDBX_DUPSORT`), а
|
||||
при его ненулевом значении всегда использует его для сверки с удаляемой
|
||||
записью.
|
||||
|
||||
@@ -333,13 +545,14 @@ RECLAIM` в _libmdbx_.
|
||||
компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`.
|
||||
|
||||
19. Возможность посредством `mdbx_is_dirty()` определить находятся ли
|
||||
некоторый ключ или данные в "грязной" странице БД. Таким образом избегаю
|
||||
лишнего копирования данных перед выполнением модифицирующих операций
|
||||
(значения в размещенные "грязных" страницах могут быть перезаписаны при
|
||||
изменениях, иначе они будут неизменны).
|
||||
некоторый ключ или данные в "грязной" странице БД. Таким образом,
|
||||
избегая лишнего копирования данных перед выполнением модифицирующих
|
||||
операций (значения в размещенные "грязных" страницах могут быть
|
||||
перезаписаны при изменениях, иначе они будут неизменны).
|
||||
|
||||
20. Корректное обновление текущей записи, в том числе сортированного
|
||||
дубликата, при использовании режима `MDB_CURRENT` в `mdbx_cursor_put()`.
|
||||
дубликата, при использовании режима `MDBX_CURRENT` в
|
||||
`mdbx_cursor_put()`.
|
||||
|
||||
21. Все курсоры, как в транзакциях только для чтения, так и в пишущих,
|
||||
могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ
|
||||
@@ -371,5 +584,14 @@ RECLAIM` в _libmdbx_.
|
||||
|
||||
25. При завершении читающих транзакций, открытые в них DBI-хендлы не
|
||||
закрываются и не теряются при завершении таких транзакций посредством
|
||||
mdb_txn_abort() или mdb_txn_reset(). Что позволяет избавится от ряда
|
||||
mdbx_txn_abort() или mdbx_txn_reset(). Что позволяет избавится от ряда
|
||||
сложно обнаруживаемых ошибок.
|
||||
|
||||
26. Генерация последовательностей посредством `mdbx_dbi_sequence()`.
|
||||
|
||||
27. Расширенное динамическое управление размером БД, включая выбор
|
||||
размера страницы посредством `mdbx_env_set_geometry()`.
|
||||
|
||||
28. Три мета-страницы вместо двух, что позволяет гарантированно
|
||||
консистентно обновлять слабые контрольные точки фиксации без риска
|
||||
повредить крайнюю сильную точку фиксации.
|
||||
|
Reference in New Issue
Block a user