diff options
author | George Hazan <ghazan@miranda.im> | 2018-08-26 15:21:43 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2018-08-26 15:21:43 +0300 |
commit | cf57ec609dc7b02eb44433514af60bb74951e990 (patch) | |
tree | 27269734504985f30368b5ae86f902ada590dc41 /libs/libmdbx | |
parent | 1f711c47cd7b4388b2b8af1cd60893e843ac617f (diff) |
Revert GC-related changes in libmdbx, till the time when it works
Diffstat (limited to 'libs/libmdbx')
24 files changed, 1208 insertions, 1566 deletions
diff --git a/libs/libmdbx/src/packages/rpm/CMakeLists.txt b/libs/libmdbx/src/CMakeLists.txt index b664075556..b664075556 100644 --- a/libs/libmdbx/src/packages/rpm/CMakeLists.txt +++ b/libs/libmdbx/src/CMakeLists.txt diff --git a/libs/libmdbx/src/README-RU.md b/libs/libmdbx/src/README-RU.md index 5dd062c1a6..f4ae5e8f14 100644 --- a/libs/libmdbx/src/README-RU.md +++ b/libs/libmdbx/src/README-RU.md @@ -12,31 +12,31 @@ and [by Yandex](https://translate.yandex.ru/translate?url=https%3A%2F%2Fgithub.c ### Project Status -**Сейчас MDBX _активно перерабатывается_** и к середине 2018 ожидается -большое изменение как API, так и формата базы данных. К сожалению, -обновление приведет к потере совместимости с предыдущими версиями. - -Цель этой революции - обеспечение более четкого надежного API и -добавление новых функции, а также наделение базы данных новыми -свойствами. - -В настоящее время MDBX предназначена для Linux, а также поддерживает -Windows (начиная с Windows Server 2008) в качестве дополнительной -платформы. Поддержка других ОС может быть обеспечена на коммерческой -основе. Однако такие усовершенствования (т. е. pull-requests) могут быть -приняты в мейнстрим только в том случае, если будет доступен -соответствующий публичный и бесплатный сервис непрерывной интеграции -(aka Continuous Integration). + +**Сейчас MDBX _активно перерабатывается_** и к середине 2018 +ожидается большое изменение как API, так и формата базы данных. +К сожалению, обновление приведет к потере совместимости с +предыдущими версиями. + +Цель этой революции - обеспечение более четкого надежного +API и добавление новых функции, а также наделение базы данных +новыми свойствами. + +В настоящее время MDBX предназначена для Linux, а также +поддерживает Windows (начиная с Windows Server 2008) в качестве +дополнительной платформы. Поддержка других ОС может быть +обеспечена на коммерческой основе. Однако такие +усовершенствования (т. е. pull-requests) могут быть приняты в +мейнстрим только в том случае, если будет доступен +соответствующий публичный и бесплатный сервис непрерывной +интеграции (aka Continuous Integration). ## Содержание + - [Обзор](#Обзор) - [Сравнение с другими СУБД](#Сравнение-с-другими-СУБД) - [История & Acknowledgments](#История) - [Основные свойства](#Основные-свойства) -- [Доработки и усовершенствования относительно LMDB](#Доработки-и-усовершенствования-относительно-lmdb) -- [Недостатки и Компромиссы](#Недостатки-и-Компромиссы) - - [Проблема долгих чтений](#Проблема-долгих-чтений) - - [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации) - [Сравнение производительности](#Сравнение-производительности) - [Интегральная производительность](#Интегральная-производительность) - [Масштабируемость чтения](#Масштабируемость-чтения) @@ -44,18 +44,21 @@ Windows (начиная с Windows Server 2008) в качестве дополн - [Отложенная фиксация](#Отложенная-фиксация) - [Асинхронная фиксация](#Асинхронная-фиксация) - [Потребление ресурсов](#Потребление-ресурсов) +- [Недостатки и Компромиссы](#Недостатки-и-Компромиссы) + - [Проблема долгих чтений](#Проблема-долгих-чтений) + - [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации) +- [Доработки и усовершенствования относительно LMDB](#Доработки-и-усовершенствования-относительно-lmdb) ## Обзор + _libmdbx_ - это встраиваемый key-value движок хранения со специфическим набором свойств и возможностей, ориентированный на создание уникальных -легковесных решений с предельной производительностью под Linux и -Windows. +легковесных решений с предельной производительностью под Linux и Windows. _libmdbx_ позволяет множеству процессов совместно читать и обновлять -несколько key-value таблиц с соблюдением -[ACID](https://ru.wikipedia.org/wiki/ACID), при минимальных накладных -расходах и амортизационной стоимости любых операций Olog(N). +несколько key-value таблиц с соблюдением [ACID](https://ru.wikipedia.org/wiki/ACID), +при минимальных накладных расходах и амортизационной стоимости любых операций Olog(N). _libmdbx_ обеспечивает [serializability](https://en.wikipedia.org/wiki/Serializability) @@ -69,26 +72,20 @@ _libmdbx_ позволяет выполнять операции чтения с параллельно на каждом ядре 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) всех -данных в память, при этом текущая версия не использует -[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging). Это -предопределяет многие свойства, в том числе удачные и противопоказанные -сценарии использования. - +_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) всех данных в память, +при этом текущая версия не использует [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging). +Это предопределяет многие свойства, в том числе удачные и противопоказанные сценарии использования. ### Сравнение с другими СУБД -Ввиду того, что в _libmdbx_ сейчас происходит революция, я посчитал -лучшим решением ограничится здесь ссылкой на [главу Comparison with -other databases](https://github.com/coreos/bbolt#comparison-with-other-databases) -в описании _BoltDB_. + +Ввиду того, что в _libmdbx_ сейчас происходит революция, я посчитал лучшим решением +ограничится здесь ссылкой на [главу Comparison with other databases](https://github.com/coreos/bbolt#comparison-with-other-databases) в описании _BoltDB_. ### История -_libmdbx_ является результатом переработки и развития "Lightning -Memory-Mapped Database", известной под аббревиатурой + +_libmdbx_ является результатом переработки и развития "Lightning Memory-Mapped Database", +известной под аббревиатурой [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database). Изначально доработка производилась в составе проекта [ReOpenLDAP](https://github.com/leo-yuriev/ReOpenLDAP). Примерно за год @@ -105,223 +102,226 @@ Technologies](https://www.ptsecurity.ru). #### Acknowledgments -Howard Chu (Symas Corporation) - the author of LMDB, from which -originated the MDBX in 2015. -Martin Hedenfalk <martin@bzero.se> - the author of `btree.c` code, which -was used for begin development of LMDB. +Howard Chu (Symas Corporation) - the author of LMDB, +from which originated the MDBX in 2015. + +Martin Hedenfalk <martin@bzero.se> - the author of `btree.c` code, +which was used for begin development of LMDB. Основные свойства ================= -_libmdbx_ наследует все ключевые возможности и особенности своего -прародителя -[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database), -но с устранением ряда описываемых далее проблем и архитектурных -недочетов. - -1. Данные хранятся в упорядоченном отображении (ordered map), ключи -всегда отсортированы, поддерживается выборка диапазонов (range lookups). - -2. Данные отображается в память каждого работающего с БД процесса. К -данным и ключам обеспечивается прямой доступ в памяти без необходимости -их копирования. - -3. Транзакции согласно [ACID](https://ru.wikipedia.org/wiki/ACID), -посредством [MVCC](https://ru.wikipedia.org/wiki/MVCC) и -[COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8). -Изменения строго последовательны и не блокируются чтением, конфликты -между транзакциями невозможны. При этом гарантируется чтение только -зафиксированных данных, см [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. +_libmdbx_ наследует все ключевые возможности и особенности +своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database), +но с устранением ряда описываемых далее проблем и архитектурных недочетов. + +1. Данные хранятся в упорядоченном отображении (ordered map), ключи всегда + отсортированы, поддерживается выборка диапазонов (range lookups). + +2. Данные отображается в память каждого работающего с БД процесса. + К данным и ключам обеспечивается прямой доступ в памяти без необходимости их + копирования. + +3. Транзакции согласно + [ACID](https://ru.wikipedia.org/wiki/ACID), посредством + [MVCC](https://ru.wikipedia.org/wiki/MVCC) и + [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8). + Изменения строго последовательны и не блокируются чтением, + конфликты между транзакциями невозможны. + При этом гарантируется чтение только зафиксированных данных, см [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. Эффективное хранение дубликатов (ключей с несколькими значениями), -без дублирования ключей, с сортировкой значений, в том числе -целочисленных (для вторичных индексов). +5. Эффективное хранение дубликатов (ключей с несколькими + значениями), без дублирования ключей, с сортировкой значений, в + том числе целочисленных (для вторичных индексов). -6. Эффективная поддержка коротких ключей фиксированной длины, в том -числе целочисленных. +6. Эффективная поддержка коротких ключей фиксированной длины, в том числе целочисленных. 7. Амортизационная стоимость любой операции Olog(N), -[WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write -Amplification Factor) и RAF (Read Amplification Factor) также Olog(N). + [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write + Amplification Factor) и RAF (Read Amplification Factor) также Olog(N). -8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и -журнала транзакций, после сбоев не требуется восстановление. Не -требуется компактификация или какое-либо периодическое обслуживание. -Поддерживается резервное копирование "по горячему", на работающей БД без -приостановки изменения данных. +8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала + транзакций, после сбоев не требуется восстановление. Не требуется компактификация + или какое-либо периодическое обслуживание. Поддерживается резервное копирование + "по горячему", на работающей БД без приостановки изменения данных. -9. Отсутствует какое-либо внутреннее управление памятью или -кэшированием. Всё необходимое штатно выполняет ядро ОС. +9. Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё + необходимое штатно выполняет ядро ОС! -Доработки и усовершенствования относительно LMDB -================================================ +Сравнение производительности +============================ -1. Утилита `mdbx_chk` для проверки целостности структуры БД. +Все представленные ниже данные получены многократным прогоном тестов на +ноутбуке Lenovo Carbon-2, i7-4600U 2.1 ГГц, 8 Гб ОЗУ, с SSD-диском +SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Гб. -2. Автоматическое динамическое управление размером БД согласно -параметрам задаваемым функцией `mdbx_env_set_geometry()`, включая шаг -приращения и порог уменьшения размера БД, а также выбор размера -страницы. Соответственно, это позволяет снизить фрагментированность -файла БД на диске и освободить место, в том числе в **Windows**. +Исходный код бенчмарка [_IOArena_](https://github.com/pmwkaa/ioarena) и +сценарии тестирования [доступны на +github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015). -3. Автоматическая без-затратная компактификация БД путем возврата -освобождающихся страниц в область нераспределенного резерва в конце -файла данных. При этом уменьшается количество страниц находящихся в -памяти и участвующих в в обмене с диском. +-------------------------------------------------------------------------------- -4. Поддержка ключей и значений нулевой длины, включая сортированные -дубликаты. +### Интегральная производительность -5. Возможность связать с каждой завершаемой транзакцией до 3 -дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их -в транзакции чтения посредством `mdbx_canary_get()`. +Показана соотнесенная сумма ключевых показателей производительности в трёх +бенчмарках: -6. Возможность посредством `mdbx_replace()` обновить или удалить запись -с получением предыдущего значения данных, а также адресно изменить -конкретное multi-значение. + - Чтение/Поиск на машине с 4-мя процессорами; -7. Режим `LIFO RECLAIM`. + - Транзакции с [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциями + (вставка, чтение, обновление, удаление) в режиме **синхронной фиксации** + данных (fdatasync при завершении каждой транзакции или аналог); - Для повторного использования выбираются не самые старые, а - самые новые страницы из доступных. За счет этого цикл - использования страниц всегда имеет минимальную длину и не - зависит от общего числа выделенных страниц. + - Транзакции с [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциями + (вставка, чтение, обновление, удаление) в режиме **отложенной фиксации** + данных (отложенная запись посредством файловой систем или аналог); - В результате механизмы кэширования и обратной записи работают с - максимально возможной эффективностью. В случае использования - контроллера дисков или системы хранения с - [BBWC](https://en.wikipedia.org/wiki/BBWC) возможно - многократное увеличение производительности по записи - (обновлению данных). +*Бенчмарк в режиме асинхронной записи не включен по двум причинам:* -8. Генерация последовательностей посредством `mdbx_dbi_sequence()`. + 1. Такое сравнение не совсем правомочно, его следует делать с движками + ориентированными на хранение данных в памяти ([Tarantool](https://tarantool.io/), [Redis](https://redis.io/)). -9. Обработчик `OOM-KICK`. + 2. Превосходство libmdbx становится еще более подавляющим, что мешает + восприятию информации. - Посредством `mdbx_env_set_oomfunc()` может быть установлен - внешний обработчик (callback), который будет вызван при - исчерпании свободных страниц по причине долгой операцией чтения - на фоне интенсивного изменения данных. - Обработчику будет передан PID и pthread_id виновника. - В свою очередь обработчик может предпринять одно из действий: +![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-1.png) - * нейтрализовать виновника (отправить сигнал kill #9), если - долгое чтение выполняется сторонним процессом; +-------------------------------------------------------------------------------- - * отменить или перезапустить проблемную операцию чтения, если - операция выполняется одним из потоков текущего процесса; +### Масштабируемость чтения - * подождать некоторое время, в расчете на то, что проблемная операция - чтения будет штатно завершена; +Для каждого движка показана суммарная производительность при +одновременном выполнении запросов чтения/поиска в 1-2-4-8 потоков на +машине с 4-мя физическими процессорами. - * прервать текущую операцию изменения данных с возвратом кода - ошибки. +![Comparison #2: Read Scalability](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-2.png) -10. Возможность открыть БД в эксклюзивном режиме посредством флага -`MDBX_EXCLUSIVE`. +-------------------------------------------------------------------------------- -11. Возможность получить отставание текущей транзакции чтения от -последней версии данных в БД посредством `mdbx_txn_straggler()`. +### Синхронная фиксация -12. Возможность явно запросить обновление существующей записи, без -создания новой посредством флажка `MDBX_CURRENT` для `mdbx_put()`. + - Линейная шкала слева и темные прямоугольники соответствуют количеству + транзакций в секунду, усредненному за всё время теста. -13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное -количество дубликатов для всех типов таблиц и любого положения курсора. + - Логарифмическая шкала справа и желтые интервальные отрезки + соответствуют времени выполнения транзакций. При этом каждый отрезок + показывает минимальное и максимальное время, затраченное на выполнение + транзакций, а крестиком отмечено среднеквадратичное значение. -14. Возможность получить посредством `mdbx_env_info()` дополнительную -информацию, включая номер самой старой версии БД (снимка данных), -который используется одним из читателей. +Выполняется **10.000 транзакций в режиме синхронной фиксации данных** на +диске. При этом требуется гарантия, что при аварийном выключении питания +(или другом подобном сбое) все данные будут консистентны и полностью +соответствовать последней завершенной транзакции. В _libmdbx_ в этом +режиме при фиксации каждой транзакции выполняется системный вызов +[fdatasync](https://linux.die.net/man/2/fdatasync). -15. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий) -аргумент `data` для таблиц без дубликатов (без флажка `MDBX_DUPSORT`), а -при его ненулевом значении всегда использует его для сверки с удаляемой -записью. +В каждой транзакции выполняется комбинированная CRUD-операция (две +вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует +на пустой базе, а при завершении, в результате выполняемых действий, в +базе насчитывается 10.000 небольших key-value записей. -16. Возможность открыть dbi-таблицу, одновременно с установкой -компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`. +![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-3.png) -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()` получить значение по -заданному ключу, одновременно с количеством дубликатов. +Выполняется **100.000 транзакций в режиме отложенной фиксации данных** +на диске. При этом требуется гарантия, что при аварийном выключении +питания (или другом подобном сбое) все данные будут консистентны на +момент завершения одной из транзакций, но допускается потеря изменений +из некоторого количества последних транзакций, что для многих движков +предполагает включение +[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) (write-ahead +logging) либо журнала транзакций, который в свою очередь опирается на +гарантию упорядоченности данных в журналируемой файловой системе. +_libmdbx_ при этом не ведет WAL, а передает весь контроль файловой +системе и ядру ОС. -22. Наличие функций `mdbx_cursor_on_first()` и `mdbx_cursor_on_last()`, -которые позволяют быстро выяснить стоит ли курсор на первой/последней -позиции. +В каждой транзакции выполняется комбинированная CRUD-операция (две +вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует +на пустой базе, а при завершении, в результате выполняемых действий, в +базе насчитывается 100.000 небольших key-value записей. -23. Возможность автоматического формирования контрольных точек (сброса -данных на диск) при накоплении заданного объёма изменений, -устанавливаемого функцией `mdbx_env_set_syncbytes()`. +![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-4.png) -24. Управление отладкой и получение отладочных сообщений посредством -`mdbx_setup_debug()`. +-------------------------------------------------------------------------------- -25. Функция `mdbx_env_pgwalk()` для обхода всех страниц БД. +### Асинхронная фиксация -26. Три мета-страницы вместо двух, что позволяет гарантированно -консистентно обновлять слабые контрольные точки фиксации без риска -повредить крайнюю сильную точку фиксации. + - Линейная шкала слева и темные прямоугольники соответствуют количеству + транзакций в секунду, усредненному за всё время теста. -27. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`. - > В текущей версии _libmdbx_ вам предоставляется выбор между безопасным - > режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC` - > когда при системной аварии есть шанс полного разрушения БД как в LMDB. - > Для подробностей смотрите раздел - > [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации). + - Логарифмическая шкала справа и желтые интервальные отрезки + соответствуют времени выполнения транзакций. При этом каждый отрезок + показывает минимальное и максимальное время, затраченное на выполнение + транзакций, а крестиком отмечено среднеквадратичное значение. -28. Возможность закрыть БД в "грязном" состоянии (без сброса данных и -формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`. +Выполняется **1.000.000 транзакций в режиме асинхронной фиксации +данных** на диске. При этом требуется гарантия, что при аварийном +выключении питания (или другом подобном сбое) все данные будут +консистентны на момент завершения одной из транзакций, но допускается +потеря изменений из значительного количества последних транзакций. Во +всех движках при этом включался режим предполагающий минимальную +нагрузку на диск по записи, и соответственно минимальную гарантию +сохранности данных. В _libmdbx_ при этом используется режим асинхронной +записи измененных страниц на диск посредством ядра ОС и системного +вызова [msync(MS_ASYNC)](https://linux.die.net/man/2/msync). -29. При завершении читающих транзакций, открытые в них DBI-хендлы не -закрываются и не теряются при завершении таких транзакций посредством -`mdbx_txn_abort()` или `mdbx_txn_reset()`. Что позволяет избавится от ряда -сложно обнаруживаемых ошибок. +В каждой транзакции выполняется комбинированная CRUD-операция (две +вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует +на пустой базе, а при завершении, в результате выполняемых действий, в +базе насчитывается 10.000 небольших key-value записей. -30. Все курсоры, как в транзакциях только для чтения, так и в пишущих, -могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ -ОСВОБОЖДАТЬСЯ ЯВНО. - > - > ## _ВАЖНО_, Обратите внимание! - > - > Это единственное изменение в API, которое значимо меняет - > семантику управления курсорами и может приводить к утечкам - > памяти. Следует отметить, что это изменение вынужденно. - > Так устраняется неоднозначность с массой тяжких последствий: - > - > - обращение к уже освобожденной памяти; - > - попытки повторного освобождения памяти; - > - повреждение памяти и ошибки сегментации. +![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-5.png) + +-------------------------------------------------------------------------------- + +### Потребление ресурсов + +Показана соотнесенная сумма использованных ресурсов в ходе бенчмарка в +режиме отложенной фиксации: + + - суммарное количество операций ввода-вывода (IOPS), как записи, так и + чтения. + + - суммарное затраченное время процессора, как в режиме пользовательских процессов, + так и в режиме ядра ОС. + + - использованное место на диске при завершении теста, после закрытия БД из тестирующего процесса, + но без ожидания всех внутренних операций обслуживания (компактификации LSM и т.п.). + +Движок _ForestDB_ был исключен при оформлении результатов, так как +относительно конкурентов многократно превысил потребление каждого из +ресурсов (потратил процессорное время на генерацию IOPS для заполнения +диска), что не позволяло наглядно сравнить показатели остальных движков +на одной диаграмме. + +Все данные собирались посредством системного вызова +[getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) и +сканированием директорий с данными. + +![Comparison #6: Cost comparison](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-6.png) -------------------------------------------------------------------------------- @@ -382,6 +382,7 @@ Amplification Factor) и RAF (Read Amplification Factor) также Olog(N). #### Проблема долгих чтений + *Следует отметить*, что проблема "сборки мусора" так или иначе существует во всех СУБД (Vacuum в PostgreSQL). Однако в случае _libmdbx_ и LMDB она проявляется более остро, прежде всего из-за высокой @@ -447,17 +448,19 @@ Amplification Factor) и RAF (Read Amplification Factor) также Olog(N). за счет эффективной работы [BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO RECLAIM` в _libmdbx_. + #### Сохранность данных в режиме асинхронной фиксации + При работе в режиме `WRITEMAP+MAPSYNC` запись измененных страниц выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе приложения, ядро ОС сохранит все изменения. Однако, при аварийном отключении питания или сбое в ядре ОС, на диске -может быть сохранена только часть измененных страниц БД. При этом с -большой вероятностью может оказаться, что будут сохранены мета-страницы -со ссылками на страницы с новыми версиями данных, но не сами новые -данные. В этом случае БД будет безвозвратна разрушена, даже если до -аварии производилась полная синхронизация данных (посредством +может быть сохранена только часть измененных страниц БД. При этом с большой +вероятностью может оказаться, что будут сохранены мета-страницы со +ссылками на страницы с новыми версиями данных, но не сами новые данные. +В этом случае БД будет безвозвратна разрушена, даже если до аварии +производилась полная синхронизация данных (посредством `mdbx_env_sync()`). В _libmdbx_ эта проблема устранена путем полной переработки @@ -485,194 +488,186 @@ Amplification Factor) и RAF (Read Amplification Factor) также Olog(N). * При открытии БД выполняется автоматический откат к последней сильной фиксации. Этим обеспечивается гарантия сохранности БД. -Такая гарантия надежности не дается бесплатно. Для сохранности данных, -страницы, формирующие крайний снимок с сильной фиксацией, не должны -повторно использоваться (перезаписываться) до формирования следующей -сильной точки фиксации. Таким образом, крайняя точка фиксации создает -описанный выше эффект "долгого чтения". Разница же здесь в том, что при -исчерпании свободных страниц ситуация будет автоматически исправлена, -посредством записи изменений на диск и формирования новой сильной точки -фиксации. +Такая гарантия надежности не дается бесплатно. Для +сохранности данных, страницы, формирующие крайний снимок с +сильной фиксацией, не должны повторно использоваться +(перезаписываться) до формирования следующей сильной точки +фиксации. Таким образом, крайняя точка фиксации создает +описанный выше эффект "долгого чтения". Разница же здесь в том, +что при исчерпании свободных страниц ситуация будет +автоматически исправлена, посредством записи изменений на диск +и формирования новой сильной точки фиксации. Таким образом, в режиме безопасной асинхронной фиксации _libmdbx_ будет -всегда использовать новые страницы до исчерпания места в БД или до -явного формирования сильной точки фиксации посредством -`mdbx_env_sync()`. При этом суммарный трафик записи на диск будет -примерно такой же, как если бы отдельно фиксировалась каждая транзакция. +всегда использовать новые страницы до исчерпания места в БД или до явного +формирования сильной точки фиксации посредством `mdbx_env_sync()`. +При этом суммарный трафик записи на диск будет примерно такой же, +как если бы отдельно фиксировалась каждая транзакция. В текущей версии _libmdbx_ вам предоставляется выбор между безопасным -режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC` -когда при системной аварии есть шанс полного разрушения БД как в LMDB. - -В последующих версиях _libmdbx_ будут предусмотрены средства для -асинхронной записи данных на диск с автоматическим формированием сильных -точек фиксации. - --------------------------------------------------------------------------------- - -Сравнение производительности -============================ - -Все представленные ниже данные получены многократным прогоном тестов на -ноутбуке Lenovo Carbon-2, i7-4600U 2.1 ГГц, 8 Гб ОЗУ, с SSD-диском -SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Гб. +режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC` когда +при системной аварии есть шанс полного разрушения БД как в LMDB. -Исходный код бенчмарка [_IOArena_](https://github.com/pmwkaa/ioarena) и -сценарии тестирования [доступны на -github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015). +В последующих версиях _libmdbx_ будут предусмотрены средства +для асинхронной записи данных на диск с автоматическим +формированием сильных точек фиксации. -------------------------------------------------------------------------------- -### Интегральная производительность - -Показана соотнесенная сумма ключевых показателей производительности в трёх -бенчмарках: +Доработки и усовершенствования относительно LMDB +================================================ - - Чтение/Поиск на машине с 4-мя процессорами; +1. Режим `LIFO RECLAIM`. - - Транзакции с [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциями - (вставка, чтение, обновление, удаление) в режиме **синхронной фиксации** - данных (fdatasync при завершении каждой транзакции или аналог); + Для повторного использования выбираются не самые старые, а + самые новые страницы из доступных. За счет этого цикл + использования страниц всегда имеет минимальную длину и не + зависит от общего числа выделенных страниц. - - Транзакции с [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциями - (вставка, чтение, обновление, удаление) в режиме **отложенной фиксации** - данных (отложенная запись посредством файловой систем или аналог); + В результате механизмы кэширования и обратной записи работают с + максимально возможной эффективностью. В случае использования + контроллера дисков или системы хранения с + [BBWC](https://en.wikipedia.org/wiki/BBWC) возможно + многократное увеличение производительности по записи + (обновлению данных). -*Бенчмарк в режиме асинхронной записи не включен по двум причинам:* +2. Обработчик `OOM-KICK`. - 1. Такое сравнение не совсем правомочно, его следует делать с движками - ориентированными на хранение данных в памяти ([Tarantool](https://tarantool.io/), [Redis](https://redis.io/)). + Посредством `mdbx_env_set_oomfunc()` может быть установлен + внешний обработчик (callback), который будет вызван при + исчерпании свободных страниц из-за долгой операцией чтения. + Обработчику будет передан PID и pthread_id виновника. + В свою очередь обработчик может предпринять одно из действий: - 2. Превосходство libmdbx становится еще более подавляющим, что мешает - восприятию информации. + * нейтрализовать виновника (отправить сигнал kill #9), если + долгое чтение выполняется сторонним процессом; -![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-1.png) + * отменить или перезапустить проблемную операцию чтения, если + операция выполняется одним из потоков текущего процесса; --------------------------------------------------------------------------------- + * подождать некоторое время, в расчете на то, что проблемная операция + чтения будет штатно завершена; -### Масштабируемость чтения + * прервать текущую операцию изменения данных с возвратом кода + ошибки. -Для каждого движка показана суммарная производительность при -одновременном выполнении запросов чтения/поиска в 1-2-4-8 потоков на -машине с 4-мя физическими процессорами. - -![Comparison #2: Read Scalability](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-2.png) - --------------------------------------------------------------------------------- - -### Синхронная фиксация - - - Линейная шкала слева и темные прямоугольники соответствуют количеству - транзакций в секунду, усредненному за всё время теста. - - - Логарифмическая шкала справа и желтые интервальные отрезки - соответствуют времени выполнения транзакций. При этом каждый отрезок - показывает минимальное и максимальное время, затраченное на выполнение - транзакций, а крестиком отмечено среднеквадратичное значение. +3. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`. + > В текущей версии _libmdbx_ вам предоставляется выбор между безопасным + > режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC` + > когда при системной аварии есть шанс полного разрушения БД как в LMDB. + > Для подробностей смотрите раздел + > [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации). -Выполняется **10.000 транзакций в режиме синхронной фиксации данных** на -диске. При этом требуется гарантия, что при аварийном выключении питания -(или другом подобном сбое) все данные будут консистентны и полностью -соответствовать последней завершенной транзакции. В _libmdbx_ в этом -режиме при фиксации каждой транзакции выполняется системный вызов -[fdatasync](https://linux.die.net/man/2/fdatasync). +4. Возможность автоматического формирования контрольных точек +(сброса данных на диск) при накоплении заданного объёма изменений, +устанавливаемого функцией `mdbx_env_set_syncbytes()`. -В каждой транзакции выполняется комбинированная CRUD-операция (две -вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует -на пустой базе, а при завершении, в результате выполняемых действий, в -базе насчитывается 10.000 небольших key-value записей. +5. Возможность получить отставание текущей транзакции чтения от +последней версии данных в БД посредством `mdbx_txn_straggler()`. -![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-3.png) +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()`. -Выполняется **100.000 транзакций в режиме отложенной фиксации данных** -на диске. При этом требуется гарантия, что при аварийном выключении -питания (или другом подобном сбое) все данные будут консистентны на -момент завершения одной из транзакций, но допускается потеря изменений -из некоторого количества последних транзакций, что для многих движков -предполагает включение -[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) (write-ahead -logging) либо журнала транзакций, который в свою очередь опирается на -гарантию упорядоченности данных в журналируемой файловой системе. -_libmdbx_ при этом не ведет WAL, а передает весь контроль файловой -системе и ядру ОС. +11. Возможность посредством `mdbx_replace()` обновить или удалить запись +с получением предыдущего значения данных, а также адресно изменить +конкретное multi-значение. -В каждой транзакции выполняется комбинированная CRUD-операция (две -вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует -на пустой базе, а при завершении, в результате выполняемых действий, в -базе насчитывается 100.000 небольших key-value записей. +12. Поддержка ключей и значений нулевой длины, включая сортированные +дубликаты. -![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-4.png) +13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное +количество дубликатов для всех типов таблиц и любого положения курсора. --------------------------------------------------------------------------------- +14. Возможность открыть БД в эксклюзивном режиме посредством флага +`MDBX_EXCLUSIVE`, например в целях её проверки. -### Асинхронная фиксация +15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и +формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`. - - Линейная шкала слева и темные прямоугольники соответствуют количеству - транзакций в секунду, усредненному за всё время теста. +16. Возможность получить посредством `mdbx_env_info()` дополнительную +информацию, включая номер самой старой версии БД (снимка данных), +который используется одним из читателей. - - Логарифмическая шкала справа и желтые интервальные отрезки - соответствуют времени выполнения транзакций. При этом каждый отрезок - показывает минимальное и максимальное время, затраченное на выполнение - транзакций, а крестиком отмечено среднеквадратичное значение. +17. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий) +аргумент `data` для таблиц без дубликатов (без флажка `MDBX_DUPSORT`), а +при его ненулевом значении всегда использует его для сверки с удаляемой +записью. -Выполняется **1.000.000 транзакций в режиме асинхронной фиксации -данных** на диске. При этом требуется гарантия, что при аварийном -выключении питания (или другом подобном сбое) все данные будут -консистентны на момент завершения одной из транзакций, но допускается -потеря изменений из значительного количества последних транзакций. Во -всех движках при этом включался режим предполагающий минимальную -нагрузку на диск по записи, и соответственно минимальную гарантию -сохранности данных. В _libmdbx_ при этом используется режим асинхронной -записи измененных страниц на диск посредством ядра ОС и системного -вызова [msync(MS_ASYNC)](https://linux.die.net/man/2/msync). +18. Возможность открыть dbi-таблицу, одновременно с установкой +компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`. -В каждой транзакции выполняется комбинированная CRUD-операция (две -вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует -на пустой базе, а при завершении, в результате выполняемых действий, в -базе насчитывается 10.000 небольших key-value записей. +19. Возможность посредством `mdbx_is_dirty()` определить находятся ли +некоторый ключ или данные в "грязной" странице БД. Таким образом, +избегая лишнего копирования данных перед выполнением модифицирующих +операций (значения, размещенные в "грязных" страницах, могут быть +перезаписаны при изменениях, иначе они будут неизменны). -![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-5.png) +20. Корректное обновление текущей записи, в том числе сортированного +дубликата, при использовании режима `MDBX_CURRENT` в +`mdbx_cursor_put()`. --------------------------------------------------------------------------------- +21. Все курсоры, как в транзакциях только для чтения, так и в пишущих, +могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ +ОСВОБОЖДАТЬСЯ ЯВНО. + > + > ## _ВАЖНО_, Обратите внимание! + > + > Это единственное изменение в API, которое значимо меняет + > семантику управления курсорами и может приводить к утечкам + > памяти. Следует отметить, что это изменение вынужденно. + > Так устраняется неоднозначность с массой тяжких последствий: + > + > - обращение к уже освобожденной памяти; + > - попытки повторного освобождения памяти; + > - повреждение памяти и ошибки сегментации. -### Потребление ресурсов +22. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из +`mdbx_put()` и `mdbx_replace()` при попытке выполнить неоднозначное +обновление или удаления одного из нескольких значений с одним ключом. -Показана соотнесенная сумма использованных ресурсов в ходе бенчмарка в -режиме отложенной фиксации: +23. Возможность посредством `mdbx_get_ex()` получить значение по +заданному ключу, одновременно с количеством дубликатов. - - суммарное количество операций ввода-вывода (IOPS), как записи, так и - чтения. +24. Наличие функций `mdbx_cursor_on_first()` и `mdbx_cursor_on_last()`, +которые позволяют быстро выяснить стоит ли курсор на первой/последней +позиции. - - суммарное затраченное время процессора, как в режиме пользовательских - процессов, так и в режиме ядра ОС. +25. При завершении читающих транзакций, открытые в них DBI-хендлы не +закрываются и не теряются при завершении таких транзакций посредством +`mdbx_txn_abort()` или `mdbx_txn_reset()`. Что позволяет избавится от ряда +сложно обнаруживаемых ошибок. - - использованное место на диске при завершении теста, после закрытия БД - из тестирующего процесса, но без ожидания всех внутренних операций - обслуживания (компактификации LSM и т.п.). +26. Генерация последовательностей посредством `mdbx_dbi_sequence()`. -Движок _ForestDB_ был исключен при оформлении результатов, так как -относительно конкурентов многократно превысил потребление каждого из -ресурсов (потратил процессорное время на генерацию IOPS для заполнения -диска), что не позволяло наглядно сравнить показатели остальных движков -на одной диаграмме. +27. Расширенное динамическое управление размером БД, включая выбор +размера страницы посредством `mdbx_env_set_geometry()`, +в том числе в **Windows** -Все данные собирались посредством системного вызова -[getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) и -сканированием директорий с данными. +28. Три мета-страницы вместо двух, что позволяет гарантированно +консистентно обновлять слабые контрольные точки фиксации без риска +повредить крайнюю сильную точку фиксации. -![Comparison #6: Cost comparison](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-6.png) +29. В _libmdbx_ реализован автоматический возврат освобождающихся +страниц в область нераспределенного резерва в конце файла данных. При +этом уменьшается количество страниц загруженных в память и участвующих в +цикле обновления данных и записи на диск. Фактически _libmdbx_ выполняет +постоянную компактификацию данных, но не затрачивая на это +дополнительных ресурсов, а только освобождая их. При освобождении места +в БД и установке соответствующих параметров геометрии базы данных, также будет +уменьшаться размер файла на диске, в том числе в **Windows**. -------------------------------------------------------------------------------- @@ -690,3 +685,16 @@ Idx Name Size VMA LMA File off Algn 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) +``` diff --git a/libs/libmdbx/src/README.md b/libs/libmdbx/src/README.md index 7c07de316e..92b6542fa7 100644 --- a/libs/libmdbx/src/README.md +++ b/libs/libmdbx/src/README.md @@ -9,21 +9,9 @@ libmdbx ## 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 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: + - 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 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); 2. Support for [raw block devices](https://en.wikipedia.org/wiki/Raw_device); 3. Separate place (HDD) for large data items; @@ -36,21 +24,19 @@ Don't miss [Java Native Interface](https://github.com/castortech/mdbxjni) by [Ca ----- -Nowadays MDBX intended for Linux, and support Windows (since Windows -Server 2008) as a complementary platform. Support for other OS could be -implemented on commercial basis. However such enhancements (i.e. pull -requests) could be accepted in mainstream only when corresponding public -and free Continuous Integration service will be available. +Nowadays MDBX intended for Linux, and support Windows (since +Windows Server 2008) as a complementary platform. Support for +other OS could be implemented on commercial basis. However such +enhancements (i.e. pull requests) could be accepted in +mainstream only when corresponding public and free Continuous +Integration service will be available. ## Contents + - [Overview](#overview) - [Comparison with other DBs](#comparison-with-other-dbs) - [History & Acknowledgments](#history) - [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) - [Integral performance](#integral-performance) - [Read scalability](#read-scalability) @@ -58,58 +44,52 @@ and free Continuous Integration service will be available. - [Lazy-write mode](#lazy-write-mode) - [Async-write mode](#async-write-mode) - [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 -_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 -[ACID](https://en.wikipedia.org/wiki/ACID)-compliant, with minimal -overhead and operation cost of Olog(N). +_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 [ACID](https://en.wikipedia.org/wiki/ACID)-compliant, with minimal overhead and operation cost of Olog(N). _libmdbx_ provides -[serializability](https://en.wikipedia.org/wiki/Serializability) and -consistency of data after crash. Read-write transactions don't block -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 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), doesn't use -[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging). This might -have caveats for some workloads. +[serializability](https://en.wikipedia.org/wiki/Serializability) and consistency of data after crash. +Read-write transactions don't block 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 +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), +doesn't use [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging). This might have caveats for some workloads. ### Comparison with other DBs -Because _libmdbx_ is currently overhauled, I think it's better to just -link [chapter of Comparison with other -databases](https://github.com/coreos/bbolt#comparison-with-other-databases) -here. + +Because _libmdbx_ is currently overhauled, I think it's better to just link +[chapter of Comparison with other databases](https://github.com/coreos/bbolt#comparison-with-other-databases) here. ### 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). - -Since early 2017 _libmdbx_ is used in [Fast PositiveTables](https://github.com/leo-yuriev/libfpta), + +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). + +Since early 2017 _libmdbx_ is used in [Fast Positive Tables](https://github.com/leo-yuriev/libfpta), by [Positive Technologies](https://www.ptsecurity.com). #### Acknowledgments -Howard Chu (Symas Corporation) - the author of LMDB, from which -originated the MDBX in 2015. -Martin Hedenfalk <martin@bzero.se> - the author of `btree.c` code, which -was used for begin development of LMDB. +Howard Chu (Symas Corporation) - the author of LMDB, +from which originated the MDBX in 2015. + +Martin Hedenfalk <martin@bzero.se> - the author of `btree.c` code, +which was used for begin development of LMDB. Main features @@ -118,468 +98,365 @@ Main features _libmdbx_ inherits all keys features and characteristics from [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. - -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 -[MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) -and [CoW](https://en.wikipedia.org/wiki/Copy-on-write). Writes are -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), -don't use [atomic -operations](https://en.wikipedia.org/wiki/Linearizability#High-level_atomic_operations). -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 (reasonable for -secondary indexes). - -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) are Olog(N). - -8. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) and -transaction journal. In case of a crash no recovery needed. No need for -regular maintenance. Backups can be made on the fly on working DB - without freezing writers. +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. + +3. Transactions are [ACID](https://en.wikipedia.org/wiki/ACID)-compliant, thanks to + [MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) and [CoW](https://en.wikipedia.org/wiki/Copy-on-write). + Writes are 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), + don't use [atomic operations](https://en.wikipedia.org/wiki/Linearizability#High-level_atomic_operations). + 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 + (reasonable for secondary indexes). + +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) + are Olog(N). + +8. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) and transaction journal. + In case of a crash no recovery needed. No need for 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. --------------------------------------------------------------------------------- -Improvements over LMDB -====================== +Performance comparison +===================== -1. `mdbx_chk` tool for DB integrity check. +All benchmarks were done by [IOArena](https://github.com/pmwkaa/ioarena) +and multiple [scripts](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015) +runs on Lenovo Carbon-2 laptop, i7-4600U 2.1 GHz, 8 Gb RAM, +SSD SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Gb. -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. +### Integral performance -4. Support for keys and values of zero length, including sorted -duplicates. +Here showed sum of performance metrics in 3 benchmarks: -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()`. + - Read/Search on 4 CPU cores machine; -6. Ability to update or delete record and get previous value via -`mdbx_replace()` Also can update specific multi-value. + - Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD) operations + in sync-write mode (fdatasync is called after each transaction); -7. `LIFO RECLAIM` mode: + - Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD) operations + in lazy-write mode (moment to sync data to persistent storage is decided by OS). - 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. +*Reasons why asynchronous mode isn't benchmarked here:* - 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. + 1. It doesn't make sense as it has to be done with DB engines, oriented for keeping data in memory e.g. + [Tarantool](https://tarantool.io/), [Redis](https://redis.io/)), etc. -8. Sequence generation via `mdbx_dbi_sequence()`. + 2. Performance gap is too high to compare in any meaningful way. -9. `OOM-KICK` callback. +![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-1.png) - `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; +### Read Scalability - * kill the offending process (signal 9), if separate process is doing - long-time read; +Summary performance with concurrent read/search queries in 1-2-4-8 threads on 4 CPU cores machine. - * abort or restart offending read transaction if it's running in sibling - thread; +![Comparison #2: Read Scalability](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-2.png) - * abort current write transaction with returning error code. +-------------------------------------------------------------------------------- -10. Ability to open DB in exclusive mode with `MDBX_EXCLUSIVE` flag. +### Sync-write mode -11. Ability to get how far current read-only snapshot is from latest -version of the DB by `mdbx_txn_straggler()`. + - Linear scale on left and dark rectangles mean arithmetic mean transactions per second; -12. Ability to explicitly request update of present record without -creating new record. Implemented as `MDBX_CURRENT` flag for -`mdbx_put()`. + - Logarithmic scale on right is in seconds and yellow intervals mean execution time of transactions. + Each interval shows minimal and maximum execution time, cross marks standard deviation. -13. Fixed `mdbx_cursor_count()`, which returns correct count of -duplicated for all table types and any cursor position. +**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. -14. `mdbx_env_info()` to getting additional info, including number of -the oldest snapshot of DB, which is used by one of the readers. +In the benchmark each transaction contains combined CRUD operations (2 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. -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. +![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-3.png) -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. +### Lazy-write mode -18. Correct update of current record in `MDBX_CURRENT` mode of -`mdbx_cursor_put()`, including sorted duplicated. + - Linear scale on left and dark rectangles mean arithmetic mean of thousands transactions per second; -19. Check if there is a row with data after current cursor position via -`mdbx_cursor_eof()`. + - 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. -20. Additional error code `MDBX_EMULTIVAL`, which is returned by -`mdbx_put()` and `mdbx_replace()` in case is ambiguous update or delete. +**100,000 transactions in lazy-write mode**. +In case of a crash all data is consistent and state is right after one of last transactions, but transactions after it +will be lost. Other DB engines use [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). -21. Ability to get value by key and duplicates count by `mdbx_get_ex()`. +In the benchmark each transaction contains combined CRUD operations (2 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. -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()`). +![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-4.png) -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. +### Async-write mode -26. Three meta-pages instead of two, this allows to guarantee -consistently update weak sync-points without risking to corrupt last -steady sync-point. + - Linear scale on left and dark rectangles mean arithmetic mean of thousands transactions per second; -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). + - 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. -28. Ability to close DB in "dirty" state (without data flush and -creation of steady synchronization point) via `mdbx_env_close_ex()`. +**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. -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. +In the benchmark each transaction contains combined CRUD operations (2 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. -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. +![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-5.png) -------------------------------------------------------------------------------- -## Gotchas +### Cost comparison -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. +Summary of used resources during lazy-write mode benchmarks: -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. + - Read and write IOPS; + - Sum of user CPU time and sys CPU time; -#### 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. + - Used space on persistent storage after the test and closed DB, but not waiting for the end of all internal + housekeeping operations (LSM compactification, etc). -* Altering data during long read operation may exhaust available space -on persistent storage. +_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. -* If available space is exhausted then any attempt to update data -results in `MAP_FULL` error until long read operation ends. +All benchmark data is gathered by [getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) syscall and by +scanning data directory. -* Main examples of long readers is hot backup and debugging of client -application which actively uses read transactions. +![Comparison #6: Cost comparison](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-6.png) -* 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_. +## Gotchas -#### 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(). +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. -_libmdbx_ addresses this by fully reimplementing write path of data: +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. -* 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. +#### Long-time read transactions problem -* _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. +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. -* During DB open _libmdbx_ rollbacks to the last steady synchronization -point, this guarantees database integrity. +* Altering data during long read operation may exhaust available space on persistent storage. -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. +* If available space is exhausted then any attempt to update data + results in `MAP_FULL` error until long read operation ends. -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. +* Main examples of long readers is hot backup + and debugging of client application which actively uses read transactions. -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. +* In _LMDB_ this results in degraded performance of all operations + of syncing data to persistent storage. -Next version of _libmdbx_ will create steady synchronization points -automatically in async-write mode. +* _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. -Performance comparison -====================== +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. -All benchmarks were done by [IOArena](https://github.com/pmwkaa/ioarena) -and multiple [scripts](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015) -runs on Lenovo Carbon-2 laptop, i7-4600U 2.1 GHz, 8 Gb RAM, -SSD SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Gb. +_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 -### Integral performance +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(). -Here showed sum of performance metrics in 3 benchmarks: +_libmdbx_ addresses this by fully reimplementing write path of data: - - Read/Search on 4 CPU cores machine; +* 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. - - Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD) - operations in sync-write mode (fdatasync is called after each - transaction); +* 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. - - Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD) - operations in lazy-write mode (moment to sync data to persistent storage - is decided by OS). +* _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. -*Reasons why asynchronous mode isn't benchmarked here:* +* During DB open _libmdbx_ rollbacks to the last steady synchronization point, this guarantees database integrity. - 1. It doesn't make sense as it has to be done with DB engines, oriented - for keeping data in memory e.g. [Tarantool](https://tarantool.io/), - [Redis](https://redis.io/)), etc. +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. - 2. Performance gap is too high to compare in any meaningful way. +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. -![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-1.png) +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. -------------------------------------------------------------------------------- -### Read Scalability +Improvements over LMDB +================================================ -Summary performance with concurrent read/search queries in 1-2-4-8 -threads on 4 CPU cores machine. +1. `LIFO RECLAIM` mode: -![Comparison #2: Read Scalability](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-2.png) + 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. -### Sync-write mode +2. `OOM-KICK` callback. - - Linear scale on left and dark rectangles mean arithmetic mean - transactions per second; + `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: - - Logarithmic scale on right is in seconds and yellow intervals mean - execution time of transactions. Each interval shows minimal and maximum - execution time, cross marks standard deviation. + * wait for read transaction to finish normally; -**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. + * kill the offending process (signal 9), if separate process is doing long-time read; -In the benchmark each transaction contains combined CRUD operations (2 -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. + * abort or restart offending read transaction if it's running in sibling thread; -![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-3.png) + * 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). -### Lazy-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()`). - - Linear scale on left and dark rectangles mean arithmetic mean of - thousands transactions per second; +5. Ability to get how far current read-only snapshot is from latest version of the DB by `mdbx_txn_straggler()`. - - 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. +6. `mdbx_chk` tool for DB checking and `mdbx_env_pgwalk()` for page-walking all pages in DB. -**100,000 transactions in lazy-write mode**. In case of a crash all data -is consistent and state is right after one of last transactions, but -transactions after it will be lost. Other DB engines use -[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). +7. Control over debugging and receiving of debugging messages via `mdbx_setup_debug()`. -In the benchmark each transaction contains combined CRUD operations (2 -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. +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()`. -![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-4.png) +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. -### Async-write mode +12. Support for keys and values of zero length, including sorted duplicates. - - Linear scale on left and dark rectangles mean arithmetic mean of - thousands transactions per second; +13. Fixed `mdbx_cursor_count()`, which returns correct count of duplicated for all table types and any cursor position. - - 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. +14. Ability to open DB in exclusive mode with `MDBX_EXCLUSIVE` flag, e.g. for integrity check. -**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. +15. Ability to close DB in "dirty" state (without data flush and creation of steady synchronization point) + via `mdbx_env_close_ex()`. -In the benchmark each transaction contains combined CRUD operations (2 -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. +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()`. -![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-5.png) +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()`. -### Cost comparison +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()`. -Summary of used resources during lazy-write mode benchmarks: +20. Correct update of current record in `MDBX_CURRENT` mode of `mdbx_cursor_put()`, including sorted duplicated. - - Read and write IOPS; +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. - - Sum of user CPU time and sys CPU time; +22. Additional error code `MDBX_EMULTIVAL`, which is returned by `mdbx_put()` and + `mdbx_replace()` in case is ambiguous update or delete. - - Used space on persistent storage after the test and closed DB, but not - waiting for the end of all internal housekeeping operations (LSM - compactification, etc). +23. Ability to get value by key and duplicates count by `mdbx_get_ex()`. -_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. +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. -All benchmark data is gathered by -[getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) -syscall and by scanning data directory. +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. -![Comparison #6: Cost comparison](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-6.png) +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. -------------------------------------------------------------------------------- @@ -597,3 +474,16 @@ Idx Name Size VMA LMA File off Algn 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) +``` diff --git a/libs/libmdbx/src/TODO.md b/libs/libmdbx/src/TODO.md new file mode 100644 index 0000000000..810b18dc4a --- /dev/null +++ b/libs/libmdbx/src/TODO.md @@ -0,0 +1,89 @@ +Допеределки +=========== +- [ ] Перевод mdbx-tools на С++ и сборка для Windows. +- [ ] Переход на CMake, замена заглушек mdbx_version и mdbx_build. +- [ ] Актуализация README.md +- [ ] Переход на C++11, добавление #pramga detect_mismatch(). +- [ ] Убрать MDB_DEBUG (всегда: логирование важный ситуаций и ошибок, опционально: включение ассертов и трассировка). +- [ ] Заменить mdbx_debug на mdbx_trace, и почистить... +- [ ] Заметить максимум assert() на mdbx_assert(env, ...). + +Качество и CI +============= +- [ ] Добавить в CI linux сборки для 32-битных таргетов. + +Доработки API +============= +- [ ] Поправить/Добавить описание нового API. +- [ ] Добавить возможность "подбора" режима для mdbx_env_open(). +- [ ] Переименовать в API: env->db, db->tbl. + +Тесты +===== +- [ ] Тестирование поддержки lockless-режима. +- [x] Додумать имя и размещение тестовой БД по-умолчанию. +- [ ] Реализовать cleanup в тесте. +- [ ] usage для теста. +- [ ] Логирование в файл, плюс более полный progress bar. +- [ ] Опция игнорирования (пропуска части теста) при переполнении БД. +- [ ] Базовый бенчмарк. + +Развитие +======== +- [ ] Отслеживание времени жизни DBI-хендлов. +- [ ] Отрефакторить mdbx_freelist_save(). +- [ ] Хранить "свободный хвост" не связанный с freeDB в META. +- [x] Возврат выделенных страниц в unallocated tail-pool. +- [ ] Валидатор страниц БД по номеру транзакции: + ~0 при переработке и номер транзакции при выделении, + проверять что этот номер больше головы реклайминга и не-больше текущей транзакции. +- [ ] Размещение overflow-pages в отдельном mmap/файле с собственной геометрией. +- [ ] Зафиксировать формат БД. +- [ ] Валидатор страниц по CRC32, плюс контроль номер транзакии под модулю 2^32. +- [ ] Валидатор страниц по t1ha c контролем снимков/версий БД на основе Merkle Tree. +- [ ] Возможность хранения ключей внутри data (libfptu). +- [ ] Асинхронная фиксация (https://github.com/leo-yuriev/libmdbx/issues/5). +- [ ] (Пере)Выделять память под IDL-списки с учетом реального кол-ва страниц, т.е. max(MDB_IDL_UM_MAX/MDB_IDL_UM_MAX, npages). + +----------------------------------------------------------------------- + +Сделано +======= +- [x] разделение errno и GetLastError(). +- [x] CI посредством AppVeyor. +- [x] тест конкурентного доступа. +- [x] тест основного функционала (заменить текущий треш). +- [x] uint32/uint64 в структурах. +- [x] Завершить переименование. +- [x] Макросы версионности, сделать как в fpta (cmake?). +- [x] Попробовать убрать yield (или что там с местом?). +- [x] trinity для copy/compaction. +- [x] trinity для mdbx_chk и mdbx_stat. +- [x] проверки с mdbx_meta_eq. +- [x] Не проверять режим при открытии в readonly. +- [x] Поправить выбор tail в mdbx_chk. +- [x] Там-же проверять позицию реклайминга. +- [x] поправить проблему открытия после READ-ONLY. +- [x] static-assertы на размер/выравнивание lck, meta и т.п. +- [x] Зачистить size_t. +- [x] Добавить локи вокруг dbi. +- [x] Привести в порядок volatile. +- [x] контроль meta.mapsize. +- [x] переработка формата: заголовки страниц, meta, clk... +- [x] зачистка Doxygen и бесполезных комментариев. +- [x] Добавить поле типа контрольной суммы. +- [x] Добавить поле/флаг размера pgno_t. +- [x] Поменять сигнатуры. +- [x] Добавить мета-страницы в coredump, проверить lck. +- [x] Сделать список для txnid_t, кода sizeof(txnid_t) > sizeof(pgno_t) и вернуть размер pgno_t. +- [x] Избавиться от умножения на размер страницы (заменить на сдвиг). +- [x] Устранение всех предупреждений (в том числе под Windows). +- [x] Добавить 'mti_reader_finished_flag'. +- [x] Погасить все level4-warnings от MSVC, включить /WX. +- [x] Проверка посредством Coverity с гашением всех дефектов. +- [x] Полная матрица Windows-сборок (2013/2015/2017). +- [x] Дать возможность задавать размер страницы при создании БД. +- [x] Изменение mapsize через API с блокировкой и увеличением txn. +- [x] Контроль размера страницы полного размера и кол-ва страниц при создании и обновлении. +- [x] Инкрементальный mmap. +- [x] Инкрементальное приращение размера (колбэк стратегии?). diff --git a/libs/libmdbx/src/packages/rpm/build.sh b/libs/libmdbx/src/build.sh index 5170882265..5170882265 100644 --- a/libs/libmdbx/src/packages/rpm/build.sh +++ b/libs/libmdbx/src/build.sh diff --git a/libs/libmdbx/src/mdbx.h b/libs/libmdbx/src/mdbx.h index 57efd875cf..35faed8488 100644 --- a/libs/libmdbx/src/mdbx.h +++ b/libs/libmdbx/src/mdbx.h @@ -924,6 +924,7 @@ LIBMDBX_API int mdbx_env_set_maxdbs(MDBX_env *env, MDBX_dbi dbs); * * Returns The maximum size of a key we can write. */ LIBMDBX_API int mdbx_env_get_maxkeysize(MDBX_env *env); +LIBMDBX_API int mdbx_get_maxkeysize(size_t pagesize); /* Set application information associated with the MDBX_env. * @@ -1696,13 +1697,6 @@ LIBMDBX_API int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr); LIBMDBX_API int mdbx_dbi_sequence(MDBX_txn *txn, MDBX_dbi dbi, uint64_t *result, uint64_t increment); -LIBMDBX_API int mdbx_limits_pgsize_min(void); -LIBMDBX_API int mdbx_limits_pgsize_max(void); -LIBMDBX_API intptr_t mdbx_limits_dbsize_min(intptr_t pagesize); -LIBMDBX_API intptr_t mdbx_limits_dbsize_max(intptr_t pagesize); -LIBMDBX_API intptr_t mdbx_limits_keysize_max(intptr_t pagesize); -LIBMDBX_API intptr_t mdbx_limits_txnsize_max(intptr_t pagesize); - /*----------------------------------------------------------------------------*/ /* attribute support functions for Nexenta */ typedef uint_fast64_t mdbx_attr_t; diff --git a/libs/libmdbx/src/packages/rpm/package.sh b/libs/libmdbx/src/package.sh index d7f9ab297a..d7f9ab297a 100644 --- a/libs/libmdbx/src/packages/rpm/package.sh +++ b/libs/libmdbx/src/package.sh diff --git a/libs/libmdbx/src/src/bits.h b/libs/libmdbx/src/src/bits.h index 559435b49d..955a583264 100644 --- a/libs/libmdbx/src/src/bits.h +++ b/libs/libmdbx/src/src/bits.h @@ -508,7 +508,12 @@ typedef MDBX_ID2 *MDBX_ID2L; /* PNL sizes - likely should be even bigger * limiting factors: sizeof(pgno_t), thread stack size */ -#define MDBX_LIST_MAX ((1 << 24) - 1) +#define MDBX_PNL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */ +#define MDBX_PNL_DB_SIZE (1 << MDBX_PNL_LOGN) +#define MDBX_PNL_UM_SIZE (1 << (MDBX_PNL_LOGN + 1)) + +#define MDBX_PNL_DB_MAX (MDBX_PNL_DB_SIZE - 1) +#define MDBX_PNL_UM_MAX (MDBX_PNL_UM_SIZE - 1) #define MDBX_PNL_SIZEOF(pl) (((pl)[0] + 1) * sizeof(pgno_t)) #define MDBX_PNL_IS_ZERO(pl) ((pl)[0] == 0) @@ -752,10 +757,10 @@ struct MDBX_env { MDBX_page *me_dpages; /* list of malloc'd blocks for re-use */ /* PNL of pages that became unused in a write txn */ MDBX_PNL me_free_pgs; - /* ID2L of pages written during a write txn. Length MDBX_LIST_MAX. */ + /* ID2L of pages written during a write txn. Length MDBX_PNL_UM_SIZE. */ MDBX_ID2L me_dirtylist; - /* Number of freelist items that can fit in a single overflow page */ - unsigned me_maxgc_ov1page; + /* Max number of freelist items that can fit in a single overflow page */ + unsigned me_maxfree_1pg; /* Max size of a node on a page */ unsigned me_nodemax; unsigned me_maxkey_limit; /* max size of a key */ diff --git a/libs/libmdbx/src/src/defs.h b/libs/libmdbx/src/src/defs.h index 4b045efc1d..b6076cc1b3 100644 --- a/libs/libmdbx/src/src/defs.h +++ b/libs/libmdbx/src/src/defs.h @@ -327,13 +327,6 @@ # define mdbx_func_ "<mdbx_unknown>" #endif -#if defined(__GNUC__) || __has_attribute(format) -#define __printf_args(format_index, first_arg) \ - __attribute__((format(printf, format_index, first_arg))) -#else -#define __printf_args(format_index, first_arg) -#endif - /*----------------------------------------------------------------------------*/ #if defined(USE_VALGRIND) diff --git a/libs/libmdbx/src/src/lck-posix.c b/libs/libmdbx/src/src/lck-posix.c index 0aa9d85078..869b98c054 100644 --- a/libs/libmdbx/src/src/lck-posix.c +++ b/libs/libmdbx/src/src/lck-posix.c @@ -48,7 +48,7 @@ static __cold __attribute__((destructor)) void mdbx_global_destructor(void) { #endif #define LCK_WHOLE OFF_T_MAX -static int mdbx_lck_op(mdbx_filehandle_t fd, int op, short lck, off_t offset, +static int mdbx_lck_op(mdbx_filehandle_t fd, int op, int lck, off_t offset, off_t len) { for (;;) { int rc; @@ -68,19 +68,11 @@ static int mdbx_lck_op(mdbx_filehandle_t fd, int op, short lck, off_t offset, } } -static __inline int mdbx_lck_exclusive(int lfd, bool fallback2shared) { +static __inline int mdbx_lck_exclusive(int lfd) { assert(lfd != INVALID_HANDLE_VALUE); if (flock(lfd, LOCK_EX | LOCK_NB)) return errno; - int rc = mdbx_lck_op(lfd, F_SETLK, F_WRLCK, 0, 1); - if (rc != 0 && fallback2shared) { - while (flock(lfd, LOCK_SH)) { - int rc = errno; - if (rc != EINTR) - return rc; - } - } - return rc; + return mdbx_lck_op(lfd, F_SETLK, F_WRLCK, 0, 1); } static __inline int mdbx_lck_shared(int lfd) { @@ -98,6 +90,8 @@ int mdbx_lck_downgrade(MDBX_env *env, bool complete) { return complete ? mdbx_lck_shared(env->me_lfd) : MDBX_SUCCESS; } +int mdbx_lck_upgrade(MDBX_env *env) { return mdbx_lck_exclusive(env->me_lfd); } + int mdbx_rpid_set(MDBX_env *env) { assert(env->me_lfd != INVALID_HANDLE_VALUE); return mdbx_lck_op(env->me_lfd, F_SETLK, F_WRLCK, env->me_pid, 1); @@ -156,10 +150,6 @@ int __cold mdbx_lck_init(MDBX_env *env) { goto bailout; #endif /* PTHREAD_PRIO_INHERIT */ - rc = pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_ERRORCHECK); - if (rc) - goto bailout; - rc = pthread_mutex_init(&env->me_lck->mti_rmutex, &ma); if (rc) goto bailout; @@ -173,7 +163,7 @@ bailout: void __cold mdbx_lck_destroy(MDBX_env *env) { if (env->me_lfd != INVALID_HANDLE_VALUE) { /* try get exclusive access */ - if (env->me_lck && mdbx_lck_exclusive(env->me_lfd, false) == 0) { + if (env->me_lck && mdbx_lck_exclusive(env->me_lfd) == 0) { mdbx_info("%s: got exclusive, drown mutexes", mdbx_func_); int rc = pthread_mutex_destroy(&env->me_lck->mti_rmutex); if (rc == 0) @@ -242,7 +232,7 @@ static int __cold internal_seize_lck(int lfd) { assert(lfd != INVALID_HANDLE_VALUE); /* try exclusive access */ - int rc = mdbx_lck_exclusive(lfd, false); + int rc = mdbx_lck_exclusive(lfd); if (rc == 0) /* got exclusive */ return MDBX_RESULT_TRUE; @@ -251,7 +241,7 @@ static int __cold internal_seize_lck(int lfd) { rc = mdbx_lck_shared(lfd); if (rc == 0) { /* got shared, try exclusive again */ - rc = mdbx_lck_exclusive(lfd, true); + rc = mdbx_lck_exclusive(lfd); if (rc == 0) /* now got exclusive */ return MDBX_RESULT_TRUE; diff --git a/libs/libmdbx/src/src/lck-windows.c b/libs/libmdbx/src/src/lck-windows.c index 7da0755916..02b074e9fc 100644 --- a/libs/libmdbx/src/src/lck-windows.c +++ b/libs/libmdbx/src/src/lck-windows.c @@ -457,6 +457,51 @@ int mdbx_lck_downgrade(MDBX_env *env, bool complete) { return MDBX_SUCCESS /* 7) now at S-? (used), done */; } +int mdbx_lck_upgrade(MDBX_env *env) { + /* Transite from locked state (S-E) to exclusive-write (E-E) */ + assert(env->me_fd != INVALID_HANDLE_VALUE); + assert(env->me_lfd != INVALID_HANDLE_VALUE); + assert((env->me_flags & MDBX_EXCLUSIVE) == 0); + + if (env->me_flags & MDBX_EXCLUSIVE) + return MDBX_RESULT_TRUE /* files were must be opened non-shareable */; + + /* 1) must be at S-E (locked), transite to ?_E (middle) */ + if (!funlock(env->me_lfd, LCK_LOWER)) + mdbx_panic("%s(%s) failed: errcode %u", mdbx_func_, + "S-E(locked) >> ?-E(middle)", GetLastError()); + + /* 3) now on ?-E (middle), try E-E (exclusive-write) */ + mdbx_jitter4testing(false); + if (flock(env->me_lfd, LCK_EXCLUSIVE | LCK_DONTWAIT, LCK_LOWER)) + return MDBX_RESULT_TRUE; /* 4) got E-E (exclusive-write), done */ + + /* 5) still on ?-E (middle) */ + int rc = GetLastError(); + mdbx_jitter4testing(false); + if (rc != ERROR_SHARING_VIOLATION && rc != ERROR_LOCK_VIOLATION) { + /* 6) something went wrong, report but continue */ + mdbx_error("%s(%s) failed: errcode %u", mdbx_func_, + "?-E(middle) >> E-E(exclusive-write)", rc); + } + + /* 7) still on ?-E (middle), try restore S-E (locked) */ + mdbx_jitter4testing(false); + rc = flock(env->me_lfd, LCK_SHARED | LCK_DONTWAIT, LCK_LOWER) + ? MDBX_RESULT_FALSE + : GetLastError(); + + mdbx_jitter4testing(false); + if (rc != MDBX_RESULT_FALSE) { + mdbx_fatal("%s(%s) failed: errcode %u", mdbx_func_, + "?-E(middle) >> S-E(locked)", rc); + return rc; + } + + /* 8) now on S-E (locked) */ + return MDBX_RESULT_FALSE; +} + void mdbx_lck_destroy(MDBX_env *env) { int rc; diff --git a/libs/libmdbx/src/src/mdbx.c b/libs/libmdbx/src/src/mdbx.c index cafaefc19c..57d6ec1928 100644 --- a/libs/libmdbx/src/src/mdbx.c +++ b/libs/libmdbx/src/src/mdbx.c @@ -1,4 +1,4 @@ -/* +/* * Copyright 2015-2018 Leonid Yuriev <leo@yuriev.ru> * and other libmdbx authors: please see AUTHORS file. * All rights reserved. @@ -501,7 +501,6 @@ __cold void mdbx_rthc_remove(const mdbx_thread_key_t key) { * Allocates memory for an PNL of the given size. * Returns PNL on success, NULL on failure. */ static MDBX_PNL mdbx_pnl_alloc(size_t size) { - assert(size <= MDBX_LIST_MAX); MDBX_PNL pl = malloc((size + 2) * sizeof(pgno_t)); if (likely(pl)) { *pl++ = (pgno_t)size; @@ -537,15 +536,12 @@ static void mdbx_txl_free(MDBX_TXL list) { /* Append ID to PNL. The PNL must be big enough. */ static __inline void mdbx_pnl_xappend(MDBX_PNL pl, pgno_t id) { - assert(pl[0] + (size_t)1 <= MDBX_PNL_ALLOCLEN(pl)); + assert(pl[0] + (size_t)1 < MDBX_PNL_ALLOCLEN(pl)); pl[pl[0] += 1] = id; } -static bool mdbx_pnl_check(MDBX_PNL pl, bool allocated) { +static bool mdbx_pnl_check(MDBX_PNL pl) { if (pl) { - if (allocated) { - assert(pl[0] <= MDBX_LIST_MAX && pl[0] <= pl[-1]); - } for (const pgno_t *ptr = pl + pl[0]; --ptr > pl;) { assert(MDBX_PNL_ORDERED(ptr[0], ptr[1])); assert(ptr[0] >= NUM_METAS); @@ -633,7 +629,7 @@ static void __hot mdbx_pnl_sort(MDBX_PNL pnl) { } #undef PNL_SMALL #undef PNL_SWAP - assert(mdbx_pnl_check(pnl, false)); + assert(mdbx_pnl_check(pnl)); } /* Search for an ID in an PNL. @@ -641,7 +637,7 @@ static void __hot mdbx_pnl_sort(MDBX_PNL pnl) { * [in] id The ID to search for. * Returns The index of the first ID greater than or equal to id. */ static unsigned __hot mdbx_pnl_search(MDBX_PNL pnl, pgno_t id) { - assert(mdbx_pnl_check(pnl, true)); + assert(mdbx_pnl_check(pnl)); /* binary search of id in pl * if found, returns position of id @@ -678,12 +674,12 @@ static unsigned __hot mdbx_pnl_search(MDBX_PNL pnl, pgno_t id) { * [in,out] ppl Address of the PNL to shrink. */ static void mdbx_pnl_shrink(MDBX_PNL *ppl) { MDBX_PNL pl = *ppl - 1; - if (unlikely(*pl > MDBX_LIST_MAX)) { - /* shrink to MDBX_LIST_MAX */ - pl = realloc(pl, (MDBX_LIST_MAX + 2) * sizeof(pgno_t)); + if (unlikely(*pl > MDBX_PNL_UM_MAX)) { + /* shrink to MDBX_PNL_UM_MAX */ + pl = realloc(pl, (MDBX_PNL_UM_MAX + 2) * sizeof(pgno_t)); if (likely(pl)) { - *pl = MDBX_LIST_MAX; - *ppl = pl + 1; + *pl++ = MDBX_PNL_UM_MAX; + *ppl = pl; } } } @@ -691,80 +687,104 @@ static void mdbx_pnl_shrink(MDBX_PNL *ppl) { /* Grow an PNL. * Return the PNL to the size growed by given number. * [in,out] ppl Address of the PNL to grow. */ -static int __must_check_result mdbx_pnl_grow(MDBX_PNL *ppl, size_t num) { - MDBX_PNL pl = *ppl - 1; - assert(pl[1] <= MDBX_LIST_MAX && pl[1] <= pl[0]); - assert(num <= MDBX_LIST_MAX); - num += pl[0]; - if (unlikely(num > MDBX_LIST_MAX)) - return MDBX_TXN_FULL; +static int mdbx_pnl_grow(MDBX_PNL *ppl, size_t num) { + MDBX_PNL idn = *ppl - 1; /* grow it */ - pl = realloc(pl, (num + 2) * sizeof(pgno_t)); - if (unlikely(!pl)) + idn = realloc(idn, (*idn + num + 2) * sizeof(pgno_t)); + if (unlikely(!idn)) return MDBX_ENOMEM; - *pl = (pgno_t)num; - *ppl = pl + 1; - return MDBX_SUCCESS; + *idn++ += (pgno_t)num; + *ppl = idn; + return 0; +} + +static int mdbx_txl_grow(MDBX_TXL *ptr, size_t num) { + MDBX_TXL list = *ptr - 1; + /* grow it */ + list = realloc(list, ((size_t)*list + num + 2) * sizeof(txnid_t)); + if (unlikely(!list)) + return MDBX_ENOMEM; + *list++ += num; + *ptr = list; + return 0; } /* Make room for num additional elements in an PNL. * [in,out] ppl Address of the PNL. * [in] num Number of elements to make room for. * Returns 0 on success, MDBX_ENOMEM on failure. */ -static int __must_check_result mdbx_pnl_need(MDBX_PNL *ppl, size_t num) { - MDBX_PNL pl = *ppl - 1; - assert(pl[1] <= MDBX_LIST_MAX && pl[1] <= pl[0]); - assert(num <= MDBX_LIST_MAX); - num += pl[1]; - if (unlikely(num > pl[0])) { - if (unlikely(num > MDBX_LIST_MAX)) - return MDBX_TXN_FULL; - num = (num + num / 4 + (256 + 2)) & ~255u; - num = (num < MDBX_LIST_MAX + 2) ? num : MDBX_LIST_MAX + 2; - pl = realloc(pl, num * sizeof(pgno_t)); +static int mdbx_pnl_need(MDBX_PNL *ppl, size_t num) { + MDBX_PNL pl = *ppl; + num += pl[0]; + if (unlikely(num > pl[-1])) { + num = (num + num / 4 + (256 + 2)) & -256; + pl = realloc(pl - 1, num * sizeof(pgno_t)); if (unlikely(!pl)) return MDBX_ENOMEM; - *pl = (pgno_t)num - 2; - *ppl = pl + 1; + *pl++ = (pgno_t)num - 2; + *ppl = pl; } - return MDBX_SUCCESS; + return 0; } /* Append an ID onto an PNL. * [in,out] ppl Address of the PNL to append to. * [in] id The ID to append. * Returns 0 on success, MDBX_ENOMEM if the PNL is too large. */ -static int __must_check_result mdbx_pnl_append(MDBX_PNL *ppl, pgno_t id) { +static int mdbx_pnl_append(MDBX_PNL *ppl, pgno_t id) { MDBX_PNL pl = *ppl; /* Too big? */ if (unlikely(pl[0] >= pl[-1])) { - int rc = mdbx_pnl_grow(ppl, MDBX_LIST_MAX); - if (unlikely(rc != MDBX_SUCCESS)) - return rc; + if (mdbx_pnl_grow(ppl, MDBX_PNL_UM_MAX)) + return MDBX_ENOMEM; pl = *ppl; } pl[0]++; pl[pl[0]] = id; - return MDBX_SUCCESS; + return 0; +} + +static int mdbx_txl_append(MDBX_TXL *ptr, txnid_t id) { + MDBX_TXL list = *ptr; + /* Too big? */ + if (unlikely(list[0] >= list[-1])) { + if (mdbx_txl_grow(ptr, (size_t)list[0])) + return MDBX_ENOMEM; + list = *ptr; + } + list[0]++; + list[list[0]] = id; + return 0; } /* Append an PNL onto an PNL. * [in,out] ppl Address of the PNL to append to. * [in] app The PNL to append. * Returns 0 on success, MDBX_ENOMEM if the PNL is too large. */ -static int __must_check_result mdbx_pnl_append_list(MDBX_PNL *ppl, - MDBX_PNL app) { +static int mdbx_pnl_append_list(MDBX_PNL *ppl, MDBX_PNL app) { MDBX_PNL pnl = *ppl; /* Too big? */ if (unlikely(pnl[0] + app[0] >= pnl[-1])) { - int rc = mdbx_pnl_grow(ppl, app[0]); - if (unlikely(rc != MDBX_SUCCESS)) - return rc; + if (mdbx_pnl_grow(ppl, app[0])) + return MDBX_ENOMEM; pnl = *ppl; } memcpy(&pnl[pnl[0] + 1], &app[1], app[0] * sizeof(pgno_t)); pnl[0] += app[0]; - return MDBX_SUCCESS; + return 0; +} + +static int mdbx_txl_append_list(MDBX_TXL *ptr, MDBX_TXL append) { + MDBX_TXL list = *ptr; + /* Too big? */ + if (unlikely(list[0] + append[0] >= list[-1])) { + if (mdbx_txl_grow(ptr, (size_t)append[0])) + return MDBX_ENOMEM; + list = *ptr; + } + memcpy(&list[list[0] + 1], &append[1], (size_t)append[0] * sizeof(txnid_t)); + list[0] += append[0]; + return 0; } /* Append an ID range onto an PNL. @@ -772,29 +792,27 @@ static int __must_check_result mdbx_pnl_append_list(MDBX_PNL *ppl, * [in] id The lowest ID to append. * [in] n Number of IDs to append. * Returns 0 on success, MDBX_ENOMEM if the PNL is too large. */ -static int __must_check_result mdbx_pnl_append_range(MDBX_PNL *ppl, pgno_t id, - size_t n) { +static int mdbx_pnl_append_range(MDBX_PNL *ppl, pgno_t id, size_t n) { pgno_t *pnl = *ppl, len = pnl[0]; /* Too big? */ if (unlikely(len + n > pnl[-1])) { - int rc = mdbx_pnl_grow(ppl, n | MDBX_LIST_MAX); - if (unlikely(rc != MDBX_SUCCESS)) - return rc; + if (mdbx_pnl_grow(ppl, n | MDBX_PNL_UM_MAX)) + return MDBX_ENOMEM; pnl = *ppl; } pnl[0] = len + (pgno_t)n; pnl += len; while (n) pnl[n--] = id++; - return MDBX_SUCCESS; + return 0; } /* Merge an PNL onto an PNL. The destination PNL must be big enough. * [in] pl The PNL to merge into. * [in] merge The PNL to merge. */ static void __hot mdbx_pnl_xmerge(MDBX_PNL pnl, MDBX_PNL merge) { - assert(mdbx_pnl_check(pnl, true)); - assert(mdbx_pnl_check(merge, false)); + assert(mdbx_pnl_check(pnl)); + assert(mdbx_pnl_check(merge)); pgno_t old_id, merge_id, i = merge[0], j = pnl[0], k = i + j, total = k; pnl[0] = MDBX_PNL_ASCENDING ? 0 : ~(pgno_t)0; /* delimiter for pl scan below */ @@ -806,58 +824,7 @@ static void __hot mdbx_pnl_xmerge(MDBX_PNL pnl, MDBX_PNL merge) { pnl[k--] = merge_id; } pnl[0] = total; - assert(mdbx_pnl_check(pnl, true)); -} - -static int __must_check_result mdbx_txl_grow(MDBX_TXL *ptr, size_t num) { - MDBX_TXL list = *ptr - 1; - /* grow it */ - list = realloc(list, ((size_t)*list + num + 2) * sizeof(txnid_t)); - if (unlikely(!list)) - return MDBX_ENOMEM; - *list += num; - *ptr = list + 1; - return MDBX_SUCCESS; -} - -static int mdbx_txl_cmp(const void *pa, const void *pb) { - const txnid_t a = *(MDBX_TXL)pa; - const txnid_t b = *(MDBX_TXL)pb; - return mdbx_cmp2int(b, a); -} - -static void mdbx_txl_sort(MDBX_TXL ptr) { - /* LY: temporary */ - qsort(ptr + 1, (size_t)ptr[0], sizeof(*ptr), mdbx_txl_cmp); -} - -static int __must_check_result mdbx_txl_append(MDBX_TXL *ptr, txnid_t id) { - MDBX_TXL list = *ptr; - /* Too big? */ - if (unlikely(list[0] >= list[-1])) { - int rc = mdbx_txl_grow(ptr, (size_t)list[0]); - if (unlikely(rc != MDBX_SUCCESS)) - return rc; - list = *ptr; - } - list[0]++; - list[list[0]] = id; - return MDBX_SUCCESS; -} - -static int __must_check_result mdbx_txl_append_list(MDBX_TXL *ptr, - MDBX_TXL append) { - MDBX_TXL list = *ptr; - /* Too big? */ - if (unlikely(list[0] + append[0] >= list[-1])) { - int rc = mdbx_txl_grow(ptr, (size_t)append[0]); - if (unlikely(rc != MDBX_SUCCESS)) - return rc; - list = *ptr; - } - memcpy(&list[list[0] + 1], &append[1], (size_t)append[0] * sizeof(txnid_t)); - list[0] += append[0]; - return MDBX_SUCCESS; + assert(mdbx_pnl_check(pnl)); } /* Search for an ID in an ID2L. @@ -906,45 +873,45 @@ static unsigned __hot mdbx_mid2l_search(MDBX_ID2L pnl, pgno_t id) { * [in,out] pnl The ID2L to insert into. * [in] id The ID2 to insert. * Returns 0 on success, -1 if the ID was already present in the ID2L. */ -static int __must_check_result mdbx_mid2l_insert(MDBX_ID2L pnl, MDBX_ID2 *id) { +static int mdbx_mid2l_insert(MDBX_ID2L pnl, MDBX_ID2 *id) { unsigned x = mdbx_mid2l_search(pnl, id->mid); if (unlikely(x < 1)) - return /* internal error */ MDBX_PROBLEM; + return /* internal error */ -2; - if (unlikely(pnl[x].mid == id->mid && x <= pnl[0].mid)) - return /* duplicate */ MDBX_PROBLEM; + if (x <= pnl[0].mid && pnl[x].mid == id->mid) + return /* duplicate */ -1; - if (unlikely(pnl[0].mid >= MDBX_LIST_MAX)) - return /* too big */ MDBX_TXN_FULL; + if (unlikely(pnl[0].mid >= MDBX_PNL_UM_MAX)) + return /* too big */ -2; /* insert id */ pnl[0].mid++; for (unsigned i = (unsigned)pnl[0].mid; i > x; i--) pnl[i] = pnl[i - 1]; pnl[x] = *id; - return MDBX_SUCCESS; + return 0; } /* Append an ID2 into a ID2L. * [in,out] pnl The ID2L to append into. * [in] id The ID2 to append. * Returns 0 on success, -2 if the ID2L is too big. */ -static int __must_check_result mdbx_mid2l_append(MDBX_ID2L pnl, MDBX_ID2 *id) { +static int mdbx_mid2l_append(MDBX_ID2L pnl, MDBX_ID2 *id) { #if MDBX_DEBUG for (unsigned i = pnl[0].mid; i > 0; --i) { assert(pnl[i].mid != id->mid); if (unlikely(pnl[i].mid == id->mid)) - return MDBX_PROBLEM; + return -1; } #endif /* Too big? */ - if (unlikely(pnl[0].mid >= MDBX_LIST_MAX)) - return /* too big */ MDBX_TXN_FULL; + if (unlikely(pnl[0].mid >= MDBX_PNL_UM_MAX)) + return -2; pnl[0].mid++; pnl[pnl[0].mid] = *id; - return MDBX_SUCCESS; + return 0; } /*----------------------------------------------------------------------------*/ @@ -1709,7 +1676,7 @@ static int mdbx_page_spill(MDBX_cursor *m0, MDBX_val *key, MDBX_val *data) { return MDBX_SUCCESS; if (!txn->mt_spill_pages) { - txn->mt_spill_pages = mdbx_pnl_alloc(MDBX_LIST_MAX); + txn->mt_spill_pages = mdbx_pnl_alloc(MDBX_PNL_UM_MAX); if (unlikely(!txn->mt_spill_pages)) return MDBX_ENOMEM; } else { @@ -1734,8 +1701,8 @@ static int mdbx_page_spill(MDBX_cursor *m0, MDBX_val *key, MDBX_val *data) { * of those pages will need to be used again. So now we spill only 1/8th * of the dirty pages. Testing revealed this to be a good tradeoff, * better than 1/2, 1/4, or 1/10. */ - if (need < MDBX_LIST_MAX / 8) - need = MDBX_LIST_MAX / 8; + if (need < MDBX_PNL_UM_MAX / 8) + need = MDBX_PNL_UM_MAX / 8; /* Save the page IDs of all the pages we're flushing */ /* flush from the tail forward, this saves a lot of shifting later on. */ @@ -2007,7 +1974,7 @@ static txnid_t mdbx_find_oldest(MDBX_txn *txn) { } /* Add a page to the txn's dirty list */ -static int __must_check_result mdbx_page_dirty(MDBX_txn *txn, MDBX_page *mp) { +static void mdbx_page_dirty(MDBX_txn *txn, MDBX_page *mp) { MDBX_ID2 mid; int rc, (*insert)(MDBX_ID2L, MDBX_ID2 *); @@ -2019,12 +1986,8 @@ static int __must_check_result mdbx_page_dirty(MDBX_txn *txn, MDBX_page *mp) { mid.mid = mp->mp_pgno; mid.mptr = mp; rc = insert(txn->mt_rw_dirtylist, &mid); - if (unlikely(rc != MDBX_SUCCESS)) { - txn->mt_flags |= MDBX_TXN_ERROR; - return rc; - } + mdbx_tassert(txn, rc == 0); txn->mt_dirtyroom--; - return MDBX_SUCCESS; } static int mdbx_mapresize(MDBX_env *env, const pgno_t size_pgno, @@ -2181,7 +2144,7 @@ static int mdbx_page_alloc(MDBX_cursor *mc, unsigned num, MDBX_page **mp, if (likely(flags & MDBX_ALLOC_GC)) { flags |= env->me_flags & (MDBX_COALESCE | MDBX_LIFORECLAIM); if (unlikely(mc->mc_flags & C_RECLAIMING)) { - /* If mc is updating the freeDB, then the befree-list cannot play + /* If mc is updating the freeDB, then the freelist cannot play * catch-up with itself by growing while trying to save it. */ flags &= ~(MDBX_ALLOC_GC | MDBX_ALLOC_KICK | MDBX_COALESCE | MDBX_LIFORECLAIM); @@ -2208,7 +2171,7 @@ static int mdbx_page_alloc(MDBX_cursor *mc, unsigned num, MDBX_page **mp, } } - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); pgno_t pgno, *repg_list = env->me_reclaimed_pglist; unsigned repg_pos = 0, repg_len = repg_list ? repg_list[0] : 0; txnid_t oldest = 0, last = 0; @@ -2228,7 +2191,7 @@ static int mdbx_page_alloc(MDBX_cursor *mc, unsigned num, MDBX_page **mp, /* Seek a big enough contiguous page range. * Prefer pages with lower pgno. */ - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); if (likely(flags & MDBX_ALLOC_CACHE) && repg_len > wanna_range && (!(flags & MDBX_COALESCE) || op == MDBX_FIRST)) { #if MDBX_PNL_ASCENDING @@ -2342,7 +2305,7 @@ static int mdbx_page_alloc(MDBX_cursor *mc, unsigned num, MDBX_page **mp, pgno_t *re_pnl = (pgno_t *)data.iov_base; mdbx_tassert(txn, re_pnl[0] == 0 || data.iov_len == (re_pnl[0] + 1) * sizeof(pgno_t)); - mdbx_tassert(txn, mdbx_pnl_check(re_pnl, false)); + mdbx_tassert(txn, mdbx_pnl_check(re_pnl)); repg_pos = re_pnl[0]; if (!repg_list) { if (unlikely(!(env->me_reclaimed_pglist = repg_list = @@ -2412,16 +2375,16 @@ static int mdbx_page_alloc(MDBX_cursor *mc, unsigned num, MDBX_page **mp, mdbx_info("refunded %" PRIaPGNO " pages: %" PRIaPGNO " -> %" PRIaPGNO, tail - txn->mt_next_pgno, tail, txn->mt_next_pgno); txn->mt_next_pgno = tail; - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); } } /* Don't try to coalesce too much. */ - if (unlikely(repg_len > MDBX_LIST_MAX / 2)) + if (repg_len > MDBX_PNL_UM_SIZE / 2) break; if (flags & MDBX_COALESCE) { - if (repg_len /* current size */ >= env->me_maxgc_ov1page || - repg_pos /* prev size */ >= env->me_maxgc_ov1page / 2) + if (repg_len /* current size */ >= env->me_maxfree_1pg / 2 || + repg_pos /* prev size */ >= env->me_maxfree_1pg / 4) flags &= ~MDBX_COALESCE; } } @@ -2522,7 +2485,7 @@ static int mdbx_page_alloc(MDBX_cursor *mc, unsigned num, MDBX_page **mp, } fail: - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); if (mp) { *mp = NULL; txn->mt_flags |= MDBX_TXN_ERROR; @@ -2553,7 +2516,7 @@ done: repg_list[0] = repg_len -= num; for (unsigned i = repg_pos - num; i < repg_len;) repg_list[++i] = repg_list[++repg_pos]; - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); } else { txn->mt_next_pgno = pgno + num; mdbx_assert(env, txn->mt_next_pgno <= txn->mt_end_pgno); @@ -2567,12 +2530,10 @@ done: np->mp_leaf2_ksize = 0; np->mp_flags = 0; np->mp_pages = num; - rc = mdbx_page_dirty(txn, np); - if (unlikely(rc != MDBX_SUCCESS)) - goto fail; + mdbx_page_dirty(txn, np); *mp = np; - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); return MDBX_SUCCESS; } @@ -2607,8 +2568,7 @@ static void mdbx_page_copy(MDBX_page *dst, MDBX_page *src, unsigned psize) { * [in] mp the page being referenced. It must not be dirty. * [out] ret the writable page, if any. * ret is unchanged if mp wasn't spilled. */ -static int __must_check_result mdbx_page_unspill(MDBX_txn *txn, MDBX_page *mp, - MDBX_page **ret) { +static int mdbx_page_unspill(MDBX_txn *txn, MDBX_page *mp, MDBX_page **ret) { MDBX_env *env = txn->mt_env; const MDBX_txn *tx2; unsigned x; @@ -2647,10 +2607,7 @@ static int __must_check_result mdbx_page_unspill(MDBX_txn *txn, MDBX_page *mp, } /* otherwise, if belonging to a parent txn, the * page remains spilled until child commits */ - int rc = mdbx_page_dirty(txn, np); - if (unlikely(rc != MDBX_SUCCESS)) - return rc; - + mdbx_page_dirty(txn, np); np->mp_flags |= P_DIRTY; *ret = np; break; @@ -2712,26 +2669,23 @@ static int mdbx_page_touch(MDBX_cursor *mc) { " in the dirtylist[%d], expecting %p", dl[x].mptr, pgno, x, mp); mc->mc_flags &= ~(C_INITIALIZED | C_EOF); - rc = MDBX_PROBLEM; - goto fail; + txn->mt_flags |= MDBX_TXN_ERROR; + return MDBX_PROBLEM; } return MDBX_SUCCESS; } } mdbx_debug("clone db %d page %" PRIaPGNO, DDBI(mc), mp->mp_pgno); - mdbx_cassert(mc, dl[0].mid < MDBX_LIST_MAX); + mdbx_cassert(mc, dl[0].mid < MDBX_PNL_UM_MAX); /* No - copy it */ np = mdbx_page_malloc(txn, 1); - if (unlikely(!np)) { - rc = MDBX_ENOMEM; - goto fail; - } + if (unlikely(!np)) + return MDBX_ENOMEM; mid.mid = pgno; mid.mptr = np; rc = mdbx_mid2l_insert(dl, &mid); - if (unlikely(rc)) - goto fail; + mdbx_cassert(mc, rc == 0); } else { return MDBX_SUCCESS; } @@ -2967,6 +2921,7 @@ static int mdbx_txn_renew0(MDBX_txn *txn, unsigned flags) { return MDBX_BAD_RSLOT; } else if (env->me_lck) { unsigned slot, nreaders; + const mdbx_pid_t pid = env->me_pid; const mdbx_tid_t tid = mdbx_thread_self(); mdbx_assert(env, env->me_lck->mti_magic_and_version == MDBX_LOCK_MAGIC); mdbx_assert(env, env->me_lck->mti_os_and_format == MDBX_LOCK_FORMAT); @@ -2976,13 +2931,13 @@ static int mdbx_txn_renew0(MDBX_txn *txn, unsigned flags) { return rc; rc = MDBX_SUCCESS; - if (unlikely(env->me_live_reader != env->me_pid)) { + if (unlikely(env->me_live_reader != pid)) { rc = mdbx_rpid_set(env); if (unlikely(rc != MDBX_SUCCESS)) { mdbx_rdt_unlock(env); return rc; } - env->me_live_reader = env->me_pid; + env->me_live_reader = pid; } while (1) { @@ -3015,13 +2970,11 @@ static int mdbx_txn_renew0(MDBX_txn *txn, unsigned flags) { env->me_lck->mti_numreaders = ++nreaders; if (env->me_close_readers < nreaders) env->me_close_readers = nreaders; - r->mr_pid = env->me_pid; + r->mr_pid = pid; mdbx_rdt_unlock(env); - if (likely(env->me_flags & MDBX_ENV_TXKEY)) { - assert(env->me_live_reader == env->me_pid); + if (likely(env->me_flags & MDBX_ENV_TXKEY)) mdbx_thread_rthc_set(env->me_txkey, r); - } } while (1) { @@ -3089,7 +3042,7 @@ static int mdbx_txn_renew0(MDBX_txn *txn, unsigned flags) { txn->mt_child = NULL; txn->mt_loose_pages = NULL; txn->mt_loose_count = 0; - txn->mt_dirtyroom = MDBX_LIST_MAX; + txn->mt_dirtyroom = MDBX_PNL_UM_MAX; txn->mt_rw_dirtylist = env->me_dirtylist; txn->mt_rw_dirtylist[0].mid = 0; txn->mt_befree_pages = env->me_free_pgs; @@ -3242,9 +3195,9 @@ int mdbx_txn_begin(MDBX_env *env, MDBX_txn *parent, unsigned flags, unsigned i; txn->mt_cursors = (MDBX_cursor **)(txn->mt_dbs + env->me_maxdbs); txn->mt_dbiseqs = parent->mt_dbiseqs; - txn->mt_rw_dirtylist = malloc(sizeof(MDBX_ID2) * (MDBX_LIST_MAX + 1)); + txn->mt_rw_dirtylist = malloc(sizeof(MDBX_ID2) * MDBX_PNL_UM_SIZE); if (!txn->mt_rw_dirtylist || - !(txn->mt_befree_pages = mdbx_pnl_alloc(MDBX_LIST_MAX))) { + !(txn->mt_befree_pages = mdbx_pnl_alloc(MDBX_PNL_UM_MAX))) { free(txn->mt_rw_dirtylist); free(txn); return MDBX_ENOMEM; @@ -3532,109 +3485,88 @@ static int mdbx_prep_backlog(MDBX_txn *txn, MDBX_cursor *mc) { return MDBX_SUCCESS; } -/* Cleanup reclaimed GC records, than save the befree-list as of this - * transaction to GC (aka freeDB). This recursive changes the reclaimed-list - * loose-list and befree-list. Keep trying until it stabilizes. */ -static int mdbx_update_gc(MDBX_txn *txn) { +/* Save the freelist as of this transaction to the freeDB. + * This changes the freelist. Keep trying until it stabilizes. */ +static int mdbx_freelist_save(MDBX_txn *txn) { /* env->me_reclaimed_pglist[] can grow and shrink during this call. - * env->me_last_reclaimed and txn->mt_befree_pages[] can only grow. - * Page numbers cannot disappear from txn->mt_befree_pages[]. */ - MDBX_env *const env = txn->mt_env; + * env->me_last_reclaimed and txn->mt_free_pages[] can only grow. + * Page numbers cannot disappear from txn->mt_free_pages[]. */ + MDBX_cursor mc; + MDBX_env *env = txn->mt_env; + int rc, more = 1; + txnid_t cleanup_reclaimed_id = 0, head_id = 0; + pgno_t befree_count = 0; + intptr_t head_room = 0, total_room = 0; + unsigned cleanup_reclaimed_pos = 0, refill_reclaimed_pos = 0; const bool lifo = (env->me_flags & MDBX_LIFORECLAIM) != 0; - MDBX_cursor mc; - int rc = mdbx_cursor_init(&mc, txn, FREE_DBI, NULL); + rc = mdbx_cursor_init(&mc, txn, FREE_DBI, NULL); if (unlikely(rc != MDBX_SUCCESS)) return rc; - const char *dbg_prefix_mode = lifo ? " lifo" : " fifo"; - mdbx_trace("\n>>> @%" PRIaTXN, txn->mt_txnid); - (void)dbg_prefix_mode; - unsigned befree_stored = 0, loop = 0; - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); - -retry: - mdbx_trace(" >> restart"); - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); - unsigned placed = 0, cleaned_gc_slot = 0, reused_gc_slots = 0, - filled_gc_slot = ~0u; - txnid_t cleaned_gc_id = 0, head_gc_id = env->me_last_reclaimed - ? env->me_last_reclaimed - : ~(txnid_t)0; - - if (unlikely(/* paranoia */ ++loop > 42)) { - mdbx_error("too more loops %u, bailout", loop); - rc = MDBX_PROBLEM; - goto bailout; - } + /* MDBX_RESERVE cancels meminit in ovpage malloc (when no WRITEMAP) */ + const intptr_t clean_limit = + (env->me_flags & (MDBX_NOMEMINIT | MDBX_WRITEMAP)) ? SSIZE_MAX + : env->me_maxfree_1pg; + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); +again_on_freelist_change: + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); while (1) { - /* Come back here after each Put() in case befree-list changed */ + /* Come back here after each Put() in case freelist changed */ MDBX_val key, data; - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); if (!lifo) { /* If using records from freeDB which we have not yet deleted, * now delete them and any we reserved for me_reclaimed_pglist. */ - while (cleaned_gc_id < env->me_last_reclaimed) { + while (cleanup_reclaimed_id < env->me_last_reclaimed) { rc = mdbx_cursor_first(&mc, &key, NULL); - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc)) goto bailout; rc = mdbx_prep_backlog(txn, &mc); - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc)) goto bailout; - cleaned_gc_id = head_gc_id = *(txnid_t *)key.iov_base; - mdbx_tassert(txn, cleaned_gc_id < *env->me_oldest); - mdbx_tassert(txn, cleaned_gc_id <= env->me_last_reclaimed); + cleanup_reclaimed_id = head_id = *(txnid_t *)key.iov_base; + total_room = head_room = 0; + more = 1; + mdbx_tassert(txn, cleanup_reclaimed_id <= env->me_last_reclaimed); mc.mc_flags |= C_RECLAIMING; - mdbx_trace("%s.cleanup-reclaimed-id %" PRIaTXN, dbg_prefix_mode, - cleaned_gc_id); rc = mdbx_cursor_del(&mc, 0); mc.mc_flags ^= C_RECLAIMING; - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc)) goto bailout; - placed = 0; } - } else if (txn->mt_lifo_reclaimed && - cleaned_gc_slot < txn->mt_lifo_reclaimed[0]) { + } else if (txn->mt_lifo_reclaimed) { /* LY: cleanup reclaimed records. */ - do { - cleaned_gc_id = txn->mt_lifo_reclaimed[++cleaned_gc_slot]; - assert(cleaned_gc_slot > 0 && cleaned_gc_id < *env->me_oldest); - head_gc_id = (head_gc_id > cleaned_gc_id) ? cleaned_gc_id : head_gc_id; - key.iov_base = &cleaned_gc_id; - key.iov_len = sizeof(cleaned_gc_id); + while (cleanup_reclaimed_pos < txn->mt_lifo_reclaimed[0]) { + cleanup_reclaimed_id = txn->mt_lifo_reclaimed[++cleanup_reclaimed_pos]; + key.iov_base = &cleanup_reclaimed_id; + key.iov_len = sizeof(cleanup_reclaimed_id); rc = mdbx_cursor_get(&mc, &key, NULL, MDBX_SET); - if (rc == MDBX_NOTFOUND) - continue; - if (unlikely(rc != MDBX_SUCCESS)) - goto bailout; - rc = mdbx_prep_backlog(txn, &mc); - if (unlikely(rc != MDBX_SUCCESS)) - goto bailout; - mdbx_tassert(txn, cleaned_gc_id < *env->me_oldest); - mc.mc_flags |= C_RECLAIMING; - mdbx_trace("%s.cleanup-reclaimed-id [%u]%" PRIaTXN, dbg_prefix_mode, - cleaned_gc_slot, cleaned_gc_id); - rc = mdbx_cursor_del(&mc, 0); - mc.mc_flags ^= C_RECLAIMING; - if (unlikely(rc != MDBX_SUCCESS)) - goto bailout; - } while (cleaned_gc_slot < txn->mt_lifo_reclaimed[0]); - mdbx_txl_sort(txn->mt_lifo_reclaimed); - assert(txn->mt_lifo_reclaimed[0] == 0 || - txn->mt_lifo_reclaimed[txn->mt_lifo_reclaimed[0]] == head_gc_id); + if (likely(rc != MDBX_NOTFOUND)) { + if (unlikely(rc)) + goto bailout; + rc = mdbx_prep_backlog(txn, &mc); + if (unlikely(rc)) + goto bailout; + mc.mc_flags |= C_RECLAIMING; + rc = mdbx_cursor_del(&mc, 0); + mc.mc_flags ^= C_RECLAIMING; + if (unlikely(rc)) + goto bailout; + } + } } - // handle loose pages - put ones into the reclaimed- or befree-list - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); if (txn->mt_loose_pages) { /* Return loose page numbers to me_reclaimed_pglist, * though usually none are left at this point. * The pages themselves remain in dirtylist. */ if (unlikely(!env->me_reclaimed_pglist) && !(lifo && env->me_last_reclaimed > 1)) { - /* Put loose page numbers in mt_befree_pages, + /* Put loose page numbers in mt_free_pages, * since unable to return them to me_reclaimed_pglist. */ if (unlikely((rc = mdbx_pnl_need(&txn->mt_befree_pages, txn->mt_loose_count)) != 0)) @@ -3643,9 +3575,8 @@ retry: mdbx_pnl_xappend(txn->mt_befree_pages, mp->mp_pgno); } else { /* Room for loose pages + temp PNL with same */ - rc = mdbx_pnl_need(&env->me_reclaimed_pglist, - 2 * txn->mt_loose_count + 1); - if (unlikely(rc != MDBX_SUCCESS)) + if ((rc = mdbx_pnl_need(&env->me_reclaimed_pglist, + 2 * txn->mt_loose_count + 1)) != 0) goto bailout; MDBX_PNL loose = env->me_reclaimed_pglist + MDBX_PNL_ALLOCLEN(env->me_reclaimed_pglist) - @@ -3681,9 +3612,9 @@ retry: txn->mt_loose_count = 0; } - // handle reclaimed pages - return suitable into unallocated space - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); if (env->me_reclaimed_pglist) { + /* Refund suitable pages into "unallocated" space */ pgno_t tail = txn->mt_next_pgno; pgno_t *const begin = env->me_reclaimed_pglist + 1; pgno_t *const end = begin + env->me_reclaimed_pglist[0]; @@ -3709,78 +3640,73 @@ retry: mdbx_info("refunded %" PRIaPGNO " pages: %" PRIaPGNO " -> %" PRIaPGNO, tail - txn->mt_next_pgno, tail, txn->mt_next_pgno); txn->mt_next_pgno = tail; - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); } } - // handle befree-list - store ones into singe gc-record - if (befree_stored < txn->mt_befree_pages[0]) { - if (unlikely(!befree_stored)) { - /* Make sure last page of freeDB is touched and on befree-list */ + /* Save the PNL of pages freed by this txn, to a single record */ + if (befree_count < txn->mt_befree_pages[0]) { + if (unlikely(!befree_count)) { + /* Make sure last page of freeDB is touched and on freelist */ rc = mdbx_page_search(&mc, NULL, MDBX_PS_LAST | MDBX_PS_MODIFY); - if (unlikely(rc != MDBX_SUCCESS && rc != MDBX_NOTFOUND)) + if (unlikely(rc && rc != MDBX_NOTFOUND)) goto bailout; } + pgno_t *befree_pages = txn->mt_befree_pages; /* Write to last page of freeDB */ key.iov_len = sizeof(txn->mt_txnid); key.iov_base = &txn->mt_txnid; do { - data.iov_len = MDBX_PNL_SIZEOF(txn->mt_befree_pages); + befree_count = befree_pages[0]; + data.iov_len = MDBX_PNL_SIZEOF(befree_pages); rc = mdbx_cursor_put(&mc, &key, &data, MDBX_RESERVE); - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc)) goto bailout; - /* Retry if mt_befree_pages[] grew during the Put() */ - } while (data.iov_len < MDBX_PNL_SIZEOF(txn->mt_befree_pages)); + /* Retry if mt_free_pages[] grew during the Put() */ + befree_pages = txn->mt_befree_pages; + } while (befree_count < befree_pages[0]); - befree_stored = (unsigned)txn->mt_befree_pages[0]; - mdbx_pnl_sort(txn->mt_befree_pages); - memcpy(data.iov_base, txn->mt_befree_pages, data.iov_len); - - mdbx_trace("%s.put-befree #%u @ %" PRIaTXN, dbg_prefix_mode, - befree_stored, txn->mt_txnid); + mdbx_pnl_sort(befree_pages); + memcpy(data.iov_base, befree_pages, data.iov_len); if (mdbx_debug_enabled(MDBX_DBG_EXTRA)) { - unsigned i = befree_stored; + unsigned i = (unsigned)befree_pages[0]; mdbx_debug_extra("PNL write txn %" PRIaTXN " root %" PRIaPGNO " num %u, PNL", txn->mt_txnid, txn->mt_dbs[FREE_DBI].md_root, i); for (; i; i--) - mdbx_debug_extra_print(" %" PRIaPGNO "", txn->mt_befree_pages[i]); + mdbx_debug_extra_print(" %" PRIaPGNO "", befree_pages[i]); mdbx_debug_extra_print("\n"); } continue; } - // handle reclaimed and loost pages - merge and store both into gc - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); - mdbx_tassert(txn, txn->mt_loose_count == 0); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); + const intptr_t rpl_len = + (env->me_reclaimed_pglist ? env->me_reclaimed_pglist[0] : 0) + + txn->mt_loose_count; + if (rpl_len && refill_reclaimed_pos == 0) + refill_reclaimed_pos = 1; - mdbx_trace(" >> reserving"); - const unsigned amount = - env->me_reclaimed_pglist ? env->me_reclaimed_pglist[0] : 0; - const unsigned left = amount - placed; - mdbx_trace("%s: amount %u, placed %d, left %d", dbg_prefix_mode, amount, - placed, (int)left); - if (0 >= (int)left) - break; + /* Reserve records for me_reclaimed_pglist[]. Split it if multi-page, + * to avoid searching freeDB for a page range. Use keys in + * range [1,me_last_reclaimed]: Smaller than txnid of oldest reader. */ + if (total_room >= rpl_len) { + if (total_room == rpl_len || --more < 0) + break; + } else if (head_room >= (intptr_t)env->me_maxfree_1pg && head_id > 1) { + /* Keep current record (overflow page), add a new one */ + head_id--; + refill_reclaimed_pos++; + head_room = 0; + } - txnid_t reservation_gc_id; if (lifo) { - assert(txn->mt_lifo_reclaimed != NULL); - if (unlikely(!txn->mt_lifo_reclaimed)) { - txn->mt_lifo_reclaimed = mdbx_txl_alloc(); - if (unlikely(!txn->mt_lifo_reclaimed)) { - rc = MDBX_ENOMEM; - goto bailout; - } - } - - if (head_gc_id > 1 && txn->mt_lifo_reclaimed[0] < INT16_MAX && - left > ((unsigned)txn->mt_lifo_reclaimed[0] - reused_gc_slots) * - env->me_maxgc_ov1page) { + if (refill_reclaimed_pos > + (txn->mt_lifo_reclaimed ? txn->mt_lifo_reclaimed[0] : 0)) { /* LY: need just a txn-id for save page list. */ rc = mdbx_page_alloc(&mc, 0, NULL, MDBX_ALLOC_GC | MDBX_ALLOC_KICK); - if (likely(rc == MDBX_SUCCESS)) + if (likely(rc == 0)) /* LY: ok, reclaimed from freedb. */ continue; if (unlikely(rc != MDBX_NOTFOUND)) @@ -3788,240 +3714,160 @@ retry: goto bailout; /* LY: freedb is empty, will look any free txn-id in high2low order. */ - do { - --head_gc_id; - assert(txn->mt_lifo_reclaimed[txn->mt_lifo_reclaimed[0]] > - head_gc_id); - rc = mdbx_txl_append(&txn->mt_lifo_reclaimed, head_gc_id); - if (unlikely(rc != MDBX_SUCCESS)) - goto bailout; - cleaned_gc_slot += 1 /* mark GC cleanup is not needed. */; - - mdbx_trace("%s: append @%" PRIaTXN - " to lifo-reclaimed, cleaned-gc-slot = %u", - dbg_prefix_mode, head_gc_id, cleaned_gc_slot); - } while (head_gc_id > 1 && txn->mt_lifo_reclaimed[0] < INT16_MAX && - left > - ((unsigned)txn->mt_lifo_reclaimed[0] - reused_gc_slots) * - env->me_maxgc_ov1page); - } - - if ((unsigned)txn->mt_lifo_reclaimed[0] <= reused_gc_slots) { - mdbx_notice("** restart: reserve depleted (reused_gc_slot %u >= " - "lifo_reclaimed %u" PRIaTXN, - reused_gc_slots, (unsigned)txn->mt_lifo_reclaimed[0]); - goto retry; - } - const unsigned i = (unsigned)txn->mt_lifo_reclaimed[0] - reused_gc_slots; - assert(i > 0 && i <= txn->mt_lifo_reclaimed[0]); - reservation_gc_id = txn->mt_lifo_reclaimed[i]; - mdbx_trace("%s: take @%" PRIaTXN " from lifo-reclaimed[%u]", - dbg_prefix_mode, reservation_gc_id, i); - } else { - mdbx_tassert(txn, txn->mt_lifo_reclaimed == NULL); - reservation_gc_id = head_gc_id--; - mdbx_trace("%s: take @%" PRIaTXN " from head-gc-id", dbg_prefix_mode, - reservation_gc_id); - } - - ++reused_gc_slots; - assert(txn->mt_lifo_reclaimed == NULL || - txn->mt_lifo_reclaimed[0] <= INT16_MAX); - unsigned chunk = left; - if (unlikely(chunk > env->me_maxgc_ov1page)) { - const unsigned avail_gs_slots = - lifo ? (unsigned)txn->mt_lifo_reclaimed[0] - reused_gc_slots - : (head_gc_id < INT16_MAX) ? (unsigned)head_gc_id : INT16_MAX; - if (avail_gs_slots > 1) { - if (chunk < env->me_maxgc_ov1page * 2) - chunk /= 2; - else { - const unsigned threshold = env->me_maxgc_ov1page * avail_gs_slots; - if (left < threshold) - chunk = env->me_maxgc_ov1page; - else { - const unsigned tail = left - threshold + env->me_maxgc_ov1page + 1; - unsigned span = 1; - unsigned avail = (unsigned)((pgno2bytes(env, span) - PAGEHDRSZ) / - sizeof(pgno_t)) /*- 1 + span */; - if (tail > avail) { - for (unsigned i = env->me_reclaimed_pglist[0] - span; i > 0; - --i) { - if (MDBX_PNL_ASCENDING - ? (env->me_reclaimed_pglist[i] + span) - : (env->me_reclaimed_pglist[i] - span) == - env->me_reclaimed_pglist[i + span]) { - span += 1; - avail = (unsigned)((pgno2bytes(env, span) - PAGEHDRSZ) / - sizeof(pgno_t)) - - 1 + span; - if (avail >= tail) - break; - } - } - } + if (unlikely(env->me_last_reclaimed < 1)) { + /* LY: not any txn in the past of freedb. */ + rc = MDBX_MAP_FULL; + goto bailout; + } - chunk = (avail >= tail) - ? tail - span - : (avail_gs_slots > 3 && reused_gc_slots < 42) - ? avail - span - : tail; + if (unlikely(!txn->mt_lifo_reclaimed)) { + txn->mt_lifo_reclaimed = mdbx_txl_alloc(); + if (unlikely(!txn->mt_lifo_reclaimed)) { + rc = MDBX_ENOMEM; + goto bailout; } } + /* LY: append the list. */ + rc = mdbx_txl_append(&txn->mt_lifo_reclaimed, + env->me_last_reclaimed - 1); + if (unlikely(rc)) + goto bailout; + --env->me_last_reclaimed; + /* LY: note that freeDB cleanup is not needed. */ + ++cleanup_reclaimed_pos; } + mdbx_tassert(txn, txn->mt_lifo_reclaimed != NULL); + head_id = txn->mt_lifo_reclaimed[refill_reclaimed_pos]; + } else { + mdbx_tassert(txn, txn->mt_lifo_reclaimed == NULL); } - assert(chunk > 0); - - mdbx_trace("%s: head_gc_id %" PRIaTXN ", reused_gc_slot %u, reservation-id " - "%" PRIaTXN, - dbg_prefix_mode, head_gc_id, reused_gc_slots, reservation_gc_id); - mdbx_trace("%s: chunk %u, gc-per-ovpage %u", dbg_prefix_mode, chunk, - env->me_maxgc_ov1page); - - mdbx_tassert(txn, reservation_gc_id < *env->me_oldest); - if (unlikely(reservation_gc_id < 1 || - reservation_gc_id >= *env->me_oldest)) { - /* LY: not any txn in the past of freedb. */ - rc = MDBX_PROBLEM; - goto bailout; + /* (Re)write {key = head_id, PNL length = head_room} */ + total_room -= head_room; + head_room = rpl_len - total_room; + if (head_room > (intptr_t)env->me_maxfree_1pg && head_id > 1) { + /* Overflow multi-page for part of me_reclaimed_pglist */ + head_room /= (head_id < INT16_MAX) ? (pgno_t)head_id + : INT16_MAX; /* amortize page sizes */ + head_room += env->me_maxfree_1pg - head_room % (env->me_maxfree_1pg + 1); + } else if (head_room < 0) { + /* Rare case, not bothering to delete this record */ + head_room = 0; + continue; } - - key.iov_len = sizeof(reservation_gc_id); - key.iov_base = &reservation_gc_id; - data.iov_len = (chunk + 1) * sizeof(pgno_t); - mdbx_trace("%s.reserve: %u [%u...%u] @%" PRIaTXN, dbg_prefix_mode, chunk, - placed + 1, placed + chunk + 1, reservation_gc_id); - rc = mdbx_cursor_put(&mc, &key, &data, MDBX_RESERVE | MDBX_NOOVERWRITE); - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); - if (unlikely(rc != MDBX_SUCCESS)) + key.iov_len = sizeof(head_id); + key.iov_base = &head_id; + data.iov_len = (head_room + 1) * sizeof(pgno_t); + rc = mdbx_cursor_put(&mc, &key, &data, MDBX_RESERVE); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); + if (unlikely(rc)) goto bailout; /* PNL is initially empty, zero out at least the length */ - memset(data.iov_base, 0, sizeof(pgno_t)); - placed += chunk; - mdbx_trace("%s.placed %u (+%u), continue", dbg_prefix_mode, placed, chunk); + pgno_t *pgs = (pgno_t *)data.iov_base; + intptr_t i = head_room > clean_limit ? head_room : 0; + do { + pgs[i] = 0; + } while (--i >= 0); + total_room += head_room; continue; } mdbx_tassert(txn, - cleaned_gc_slot == + cleanup_reclaimed_pos == (txn->mt_lifo_reclaimed ? txn->mt_lifo_reclaimed[0] : 0)); - mdbx_trace(" >> filling"); - /* Fill in the reserved records */ - filled_gc_slot = txn->mt_lifo_reclaimed - ? (unsigned)txn->mt_lifo_reclaimed[0] - reused_gc_slots - : reused_gc_slots; + /* Fill in the reserved me_reclaimed_pglist records */ rc = MDBX_SUCCESS; - mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist, true)); + mdbx_tassert(txn, mdbx_pnl_check(env->me_reclaimed_pglist)); if (env->me_reclaimed_pglist && env->me_reclaimed_pglist[0]) { MDBX_val key, data; key.iov_len = data.iov_len = 0; /* avoid MSVC warning */ key.iov_base = data.iov_base = NULL; - unsigned left = env->me_reclaimed_pglist[0]; - pgno_t *end = env->me_reclaimed_pglist + left; - if (txn->mt_lifo_reclaimed == nullptr) { + size_t rpl_left = env->me_reclaimed_pglist[0]; + pgno_t *rpl_end = env->me_reclaimed_pglist + rpl_left; + if (txn->mt_lifo_reclaimed == 0) { mdbx_tassert(txn, lifo == 0); rc = mdbx_cursor_first(&mc, &key, &data); - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc)) goto bailout; } else { mdbx_tassert(txn, lifo != 0); } while (1) { - txnid_t fill_gc_id; - mdbx_trace("%s: left %u of %u", dbg_prefix_mode, left, - (unsigned)env->me_reclaimed_pglist[0]); - if (txn->mt_lifo_reclaimed == nullptr) { + txnid_t id; + if (txn->mt_lifo_reclaimed == 0) { mdbx_tassert(txn, lifo == 0); - fill_gc_id = *(txnid_t *)key.iov_base; - if (filled_gc_slot-- == 0 || fill_gc_id > env->me_last_reclaimed) { - mdbx_notice( - "** restart: reserve depleted (filled_slot %u, fill_id %" PRIaTXN - " > last_reclaimed %" PRIaTXN, - filled_gc_slot, fill_gc_id, env->me_last_reclaimed); - goto retry; - } + id = *(txnid_t *)key.iov_base; + mdbx_tassert(txn, id <= env->me_last_reclaimed); } else { mdbx_tassert(txn, lifo != 0); - if (++filled_gc_slot > (unsigned)txn->mt_lifo_reclaimed[0]) { - mdbx_notice("** restart: reserve depleted (filled_gc_slot %u > " - "lifo_reclaimed %u" PRIaTXN, - filled_gc_slot, (unsigned)txn->mt_lifo_reclaimed[0]); - goto retry; - } - fill_gc_id = txn->mt_lifo_reclaimed[filled_gc_slot]; - mdbx_trace("%s.seek-reservaton @%" PRIaTXN " at lifo_reclaimed[%u]", - dbg_prefix_mode, fill_gc_id, filled_gc_slot); - key.iov_base = &fill_gc_id; - key.iov_len = sizeof(fill_gc_id); + mdbx_tassert(txn, + refill_reclaimed_pos > 0 && + refill_reclaimed_pos <= txn->mt_lifo_reclaimed[0]); + id = txn->mt_lifo_reclaimed[refill_reclaimed_pos--]; + key.iov_base = &id; + key.iov_len = sizeof(id); rc = mdbx_cursor_get(&mc, &key, &data, MDBX_SET); - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc)) goto bailout; } mdbx_tassert( - txn, cleaned_gc_slot == + txn, cleanup_reclaimed_pos == (txn->mt_lifo_reclaimed ? txn->mt_lifo_reclaimed[0] : 0)); mdbx_tassert(txn, data.iov_len >= sizeof(pgno_t) * 2); - const size_t space = (data.iov_len / sizeof(pgno_t)) - 1; - const unsigned chunk = (space > left) ? left : (unsigned)space; - data.iov_len = (chunk + 1) * sizeof(pgno_t); - mdbx_tassert(txn, fill_gc_id > 0 && fill_gc_id < *env->me_oldest); - key.iov_base = &fill_gc_id; - key.iov_len = sizeof(fill_gc_id); - - end -= chunk; - data.iov_base = end; - pgno_t save = end[0]; - end[0] = (pgno_t)chunk; - mdbx_tassert(txn, mdbx_pnl_check(end, false)); + size_t chunk_len = (data.iov_len / sizeof(pgno_t)) - 1; + if (chunk_len > rpl_left) + chunk_len = rpl_left; + data.iov_len = (chunk_len + 1) * sizeof(pgno_t); + key.iov_base = &id; + key.iov_len = sizeof(id); + + rpl_end -= chunk_len; + data.iov_base = rpl_end; + pgno_t save = rpl_end[0]; + rpl_end[0] = (pgno_t)chunk_len; + mdbx_tassert(txn, mdbx_pnl_check(rpl_end)); mc.mc_flags |= C_RECLAIMING; rc = mdbx_cursor_put(&mc, &key, &data, MDBX_CURRENT); mc.mc_flags ^= C_RECLAIMING; - mdbx_tassert(txn, mdbx_pnl_check(end, false)); + mdbx_tassert(txn, mdbx_pnl_check(rpl_end)); mdbx_tassert( - txn, cleaned_gc_slot == + txn, cleanup_reclaimed_pos == (txn->mt_lifo_reclaimed ? txn->mt_lifo_reclaimed[0] : 0)); - pgno_t *from = end + 1, *to = end + end[0]; - mdbx_trace("%s.fill: %u [ %u:%" PRIaPGNO "...%u:%" PRIaPGNO - "] @%" PRIaTXN, - dbg_prefix_mode, (unsigned)end[0], - (unsigned)(from - env->me_reclaimed_pglist), *from, - (unsigned)(to - env->me_reclaimed_pglist), *to, fill_gc_id); - end[0] = save; - if (unlikely(rc != MDBX_SUCCESS)) + rpl_end[0] = save; + if (unlikely(rc)) goto bailout; - left -= chunk; - if (left == 0) { - rc = MDBX_SUCCESS; + rpl_left -= chunk_len; + if (rpl_left == 0) break; - } if (!lifo) { rc = mdbx_cursor_next(&mc, &key, &data, MDBX_NEXT); - if (unlikely(rc != MDBX_SUCCESS)) + if (unlikely(rc)) goto bailout; } } } - mdbx_tassert(txn, rc == MDBX_SUCCESS); - if (unlikely( - filled_gc_slot != - (txn->mt_lifo_reclaimed ? (unsigned)txn->mt_lifo_reclaimed[0] : 0))) { - mdbx_notice("** restart: reserve excess (filled-slot %u)", filled_gc_slot); - goto retry; - } - bailout: if (txn->mt_lifo_reclaimed) { - mdbx_tassert(txn, rc != MDBX_SUCCESS || - cleaned_gc_slot == txn->mt_lifo_reclaimed[0]); + mdbx_tassert(txn, rc || cleanup_reclaimed_pos == txn->mt_lifo_reclaimed[0]); + if (rc == MDBX_SUCCESS && + cleanup_reclaimed_pos != txn->mt_lifo_reclaimed[0]) { + mdbx_tassert(txn, cleanup_reclaimed_pos < txn->mt_lifo_reclaimed[0]); + /* LY: zeroed cleanup_idx to force cleanup + * and refill created freeDB records. */ + cleanup_reclaimed_pos = 0; + /* LY: restart filling */ + total_room = head_room = refill_reclaimed_pos = 0; + more = 1; + goto again_on_freelist_change; + } txn->mt_lifo_reclaimed[0] = 0; if (txn != env->me_txn0) { mdbx_txl_free(txn->mt_lifo_reclaimed); @@ -4029,7 +3875,6 @@ bailout: } } - mdbx_trace("<<< rc = %d", rc); return rc; } @@ -4152,7 +3997,7 @@ static __cold bool mdbx_txn_import_dbi(MDBX_txn *txn, MDBX_dbi dbi) { (env->me_dbflags[i] & MDBX_VALID)) { txn->mt_dbs[i].md_flags = env->me_dbflags[i] & PERSISTENT_FLAGS; txn->mt_dbflags[i] = DB_VALID | DB_USRVALID | DB_STALE; - mdbx_tassert(txn, txn->mt_dbxs[i].md_cmp != NULL); + assert(txn->mt_dbxs[i].md_cmp != NULL); } } txn->mt_numdbs = snap_numdbs; @@ -4313,7 +4158,7 @@ int mdbx_txn_commit(MDBX_txn *txn) { } } } else { /* Simplify the above for single-ancestor case */ - len = MDBX_LIST_MAX - txn->mt_dirtyroom; + len = MDBX_PNL_UM_MAX - txn->mt_dirtyroom; } /* Merge our dirty list with parent's */ y = src[0].mid; @@ -4396,7 +4241,7 @@ int mdbx_txn_commit(MDBX_txn *txn) { } } - rc = mdbx_update_gc(txn); + rc = mdbx_freelist_save(txn); if (unlikely(rc != MDBX_SUCCESS)) goto fail; @@ -5002,8 +4847,19 @@ int __cold mdbx_env_get_maxkeysize(MDBX_env *env) { #define mdbx_maxkey(nodemax) ((nodemax) - (NODESIZE + sizeof(MDBX_db))) -#define mdbx_maxgc_ov1page(pagesize) \ - (((pagesize)-PAGEHDRSZ) / sizeof(pgno_t) - 1) +#define mdbx_maxfree1pg(pagesize) (((pagesize)-PAGEHDRSZ) / sizeof(pgno_t) - 1) + +int mdbx_get_maxkeysize(size_t pagesize) { + if (pagesize == 0) + pagesize = mdbx_syspagesize(); + + intptr_t nodemax = mdbx_nodemax(pagesize); + if (nodemax < 0) + return -MDBX_EINVAL; + + intptr_t maxkey = mdbx_maxkey(nodemax); + return (maxkey > 0 && maxkey < INT_MAX) ? (int)maxkey : -MDBX_EINVAL; +} static void __cold mdbx_setup_pagesize(MDBX_env *env, const size_t pagesize) { STATIC_ASSERT(SSIZE_MAX > MAX_MAPSIZE); @@ -5013,11 +4869,11 @@ static void __cold mdbx_setup_pagesize(MDBX_env *env, const size_t pagesize) { mdbx_ensure(env, pagesize <= MAX_PAGESIZE); env->me_psize = (unsigned)pagesize; - STATIC_ASSERT(mdbx_maxgc_ov1page(MIN_PAGESIZE) > 42); - STATIC_ASSERT(mdbx_maxgc_ov1page(MAX_PAGESIZE) < MDBX_LIST_MAX); - const intptr_t maxgc_ov1page = (pagesize - PAGEHDRSZ) / sizeof(pgno_t) - 1; - mdbx_ensure(env, maxgc_ov1page > 42 && maxgc_ov1page < MDBX_LIST_MAX); - env->me_maxgc_ov1page = (unsigned)maxgc_ov1page; + STATIC_ASSERT(mdbx_maxfree1pg(MIN_PAGESIZE) > 42); + STATIC_ASSERT(mdbx_maxfree1pg(MAX_PAGESIZE) < MDBX_PNL_DB_MAX); + const intptr_t maxfree_1pg = (pagesize - PAGEHDRSZ) / sizeof(pgno_t) - 1; + mdbx_ensure(env, maxfree_1pg > 42 && maxfree_1pg < MDBX_PNL_DB_MAX); + env->me_maxfree_1pg = (unsigned)maxfree_1pg; STATIC_ASSERT(mdbx_nodemax(MIN_PAGESIZE) > 42); STATIC_ASSERT(mdbx_nodemax(MAX_PAGESIZE) < UINT16_MAX); @@ -5929,8 +5785,8 @@ int __cold mdbx_env_open(MDBX_env *env, const char *path, unsigned flags, flags &= ~(MDBX_WRITEMAP | MDBX_MAPASYNC | MDBX_NOSYNC | MDBX_NOMETASYNC | MDBX_COALESCE | MDBX_LIFORECLAIM | MDBX_NOMEMINIT); } else { - if (!((env->me_free_pgs = mdbx_pnl_alloc(MDBX_LIST_MAX)) && - (env->me_dirtylist = calloc(MDBX_LIST_MAX + 1, sizeof(MDBX_ID2))))) + if (!((env->me_free_pgs = mdbx_pnl_alloc(MDBX_PNL_UM_MAX)) && + (env->me_dirtylist = calloc(MDBX_PNL_UM_SIZE, sizeof(MDBX_ID2))))) rc = MDBX_ENOMEM; } @@ -6089,8 +5945,6 @@ static void __cold mdbx_env_close0(MDBX_env *env) { if (env->me_flags & MDBX_ENV_TXKEY) mdbx_rthc_remove(env->me_txkey); - if (env->me_live_reader) - (void)mdbx_rpid_clear(env); if (env->me_map) { mdbx_munmap(&env->me_dxb_mmap); @@ -6498,14 +6352,9 @@ static int mdbx_page_get(MDBX_cursor *mc, pgno_t pgno, MDBX_page **ret, mapped: p = pgno2page(env, pgno); + /* TODO: check p->mp_validator here */ done: - if ((p->mp_flags & P_OVERFLOW) == 0 && - unlikely(p->mp_upper < p->mp_lower || - PAGEHDRSZ + p->mp_upper > env->me_psize)) - return MDBX_CORRUPTED; - /* TODO: more checks here, including p->mp_validator */ - *ret = p; if (lvl) *lvl = level; @@ -7643,7 +7492,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, DVAL((flags & MDBX_RESERVE) ? nullptr : data), data->iov_len); int dupdata_flag = 0; - if ((flags & MDBX_CURRENT) != 0 && (mc->mc_flags & C_SUB) == 0) { + if (flags & MDBX_CURRENT) { /* Опция MDBX_CURRENT означает, что запрошено обновление текущей записи, * на которой сейчас стоит курсор. Проверяем что переданный ключ совпадает * со значением в текущей позиции курсора. @@ -7864,7 +7713,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, offset *= 4; /* space for 4 more */ break; } - /* FALLTHRU: Big enough MDBX_DUPFIXED sub-page */ + /* FALLTHRU: Big enough MDBX_DUPFIXaED sub-page */ __fallthrough; case MDBX_CURRENT | MDBX_NODUPDATA: case MDBX_CURRENT: @@ -7951,8 +7800,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, ovpages = omp->mp_pages; /* Is the ov page large enough? */ - if (ovpages == - /* LY: add configuragle theshold to keep reserve space */ dpages) { + if (ovpages >= dpages) { if (!(omp->mp_flags & P_DIRTY) && (level || (env->me_flags & MDBX_WRITEMAP))) { rc = mdbx_page_unspill(mc->mc_txn, omp, &omp); @@ -7975,10 +7823,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, id2.mptr = np; /* Note - this page is already counted in parent's dirtyroom */ rc2 = mdbx_mid2l_insert(mc->mc_txn->mt_rw_dirtylist, &id2); - if (unlikely(rc2 != MDBX_SUCCESS)) { - rc = rc2; - goto fail; - } + mdbx_cassert(mc, rc2 == 0); /* Currently we make the page look as with put() in the * parent txn, in case the user peeks at MDBX_RESERVEd @@ -8015,6 +7860,8 @@ int mdbx_cursor_put(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, memcpy(olddata.iov_base, data->iov_base, data->iov_len); else { mdbx_cassert(mc, NUMKEYS(mc->mc_pg[mc->mc_top]) == 1); + mdbx_cassert(mc, mc->mc_pg[mc->mc_top]->mp_upper == + mc->mc_pg[mc->mc_top]->mp_lower); mdbx_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top]) && !IS_LEAF2(mc->mc_pg[mc->mc_top])); mdbx_cassert(mc, NODEDSZ(leaf) == 0); @@ -8022,7 +7869,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, mdbx_cassert(mc, key->iov_len < UINT16_MAX); leaf->mn_ksize = (uint16_t)key->iov_len; memcpy(NODEKEY(leaf), key->iov_base, key->iov_len); - assert((char *)NODEKEY(leaf) + NODEDSZ(leaf) < + assert((char *)NODEDATA(leaf) + NODEDSZ(leaf) < (char *)(mc->mc_pg[mc->mc_top]) + env->me_psize); goto fix_parent; } @@ -8167,7 +8014,6 @@ new_sub: /* should not happen, we deleted that item */ rc = MDBX_PROBLEM; } -fail: mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; return rc; } @@ -11068,10 +10914,10 @@ int mdbx_dbi_open_ex(MDBX_txn *txn, const char *table_name, unsigned user_flags, } unsigned dbflag = DB_FRESH | DB_VALID | DB_USRVALID; - MDBX_db db_dummy; if (unlikely(rc)) { /* MDBX_NOTFOUND and MDBX_CREATE: Create new DB */ assert(rc == MDBX_NOTFOUND); + MDBX_db db_dummy; memset(&db_dummy, 0, sizeof(db_dummy)); db_dummy.md_root = P_INVALID; db_dummy.md_flags = user_flags & PERSISTENT_FLAGS; @@ -12384,58 +12230,6 @@ int mdbx_dbi_sequence(MDBX_txn *txn, MDBX_dbi dbi, uint64_t *result, } /*----------------------------------------------------------------------------*/ - -__cold intptr_t mdbx_limits_keysize_max(intptr_t pagesize) { - if (pagesize < 1) - pagesize = (intptr_t)mdbx_syspagesize(); - else if (unlikely(pagesize < (intptr_t)MIN_PAGESIZE || - pagesize > (intptr_t)MAX_PAGESIZE || - !mdbx_is_power2((size_t)pagesize))) - return -MDBX_EINVAL; - - return mdbx_maxkey(mdbx_nodemax(pagesize)); -} - -__cold int mdbx_limits_pgsize_min(void) { return MIN_PAGESIZE; } - -__cold int mdbx_limits_pgsize_max(void) { return MAX_PAGESIZE; } - -__cold intptr_t mdbx_limits_dbsize_min(intptr_t pagesize) { - if (pagesize < 1) - pagesize = (intptr_t)mdbx_syspagesize(); - else if (unlikely(pagesize < (intptr_t)MIN_PAGESIZE || - pagesize > (intptr_t)MAX_PAGESIZE || - !mdbx_is_power2((size_t)pagesize))) - return -MDBX_EINVAL; - - return MIN_PAGENO * pagesize; -} - -__cold intptr_t mdbx_limits_dbsize_max(intptr_t pagesize) { - if (pagesize < 1) - pagesize = (intptr_t)mdbx_syspagesize(); - else if (unlikely(pagesize < (intptr_t)MIN_PAGESIZE || - pagesize > (intptr_t)MAX_PAGESIZE || - !mdbx_is_power2((size_t)pagesize))) - return -MDBX_EINVAL; - - const uint64_t limit = MAX_PAGENO * (uint64_t)pagesize; - return (limit < (intptr_t)MAX_MAPSIZE) ? (intptr_t)limit - : (intptr_t)MAX_PAGESIZE; -} - -__cold intptr_t mdbx_limits_txnsize_max(intptr_t pagesize) { - if (pagesize < 1) - pagesize = (intptr_t)mdbx_syspagesize(); - else if (unlikely(pagesize < (intptr_t)MIN_PAGESIZE || - pagesize > (intptr_t)MAX_PAGESIZE || - !mdbx_is_power2((size_t)pagesize))) - return -MDBX_EINVAL; - - return pagesize * (MDBX_LIST_MAX - 1); -} - -/*----------------------------------------------------------------------------*/ /* attribute support functions for Nexenta */ static __inline int mdbx_attr_peek(MDBX_val *data, mdbx_attr_t *attrptr) { diff --git a/libs/libmdbx/src/src/osal.h b/libs/libmdbx/src/src/osal.h index a1feb998d2..daa79064f9 100644 --- a/libs/libmdbx/src/src/osal.h +++ b/libs/libmdbx/src/src/osal.h @@ -552,6 +552,7 @@ int mdbx_lck_init(MDBX_env *env); int mdbx_lck_seize(MDBX_env *env); int mdbx_lck_downgrade(MDBX_env *env, bool complete); +int mdbx_lck_upgrade(MDBX_env *env); void mdbx_lck_destroy(MDBX_env *env); int mdbx_rdt_lock(MDBX_env *env); diff --git a/libs/libmdbx/src/src/tools/mdbx_chk.c b/libs/libmdbx/src/src/tools/mdbx_chk.c index 8fec23e3a8..51096c4053 100644 --- a/libs/libmdbx/src/src/tools/mdbx_chk.c +++ b/libs/libmdbx/src/src/tools/mdbx_chk.c @@ -340,16 +340,12 @@ static int handle_freedb(const uint64_t record_number, const MDBX_val *key, data->iov_len); else { const pgno_t number = *iptr++; - if (number < 1 || number > MDBX_LIST_MAX) + if (number >= MDBX_PNL_UM_MAX) problem_add("entry", record_number, "wrong idl length", "%" PRIiPTR "", number); - else if ((number + 1) * sizeof(pgno_t) > data->iov_len) - problem_add("entry", record_number, "trimmed idl", - "%" PRIuSIZE " > %" PRIuSIZE " (corruption)", - (number + 1) * sizeof(pgno_t), data->iov_len); - else if ((number + 1) * sizeof(pgno_t) < data->iov_len) - problem_add("entry", record_number, "extra idl space", - "%" PRIuSIZE " < %" PRIuSIZE " (minor, not a trouble)", + else if ((number + 1) * sizeof(pgno_t) != data->iov_len) + problem_add("entry", record_number, "mismatch idl length", + "%" PRIuSIZE " != %" PRIuSIZE "", (number + 1) * sizeof(pgno_t), data->iov_len); else { freedb_pages += number; diff --git a/libs/libmdbx/src/test/config.cc b/libs/libmdbx/src/test/config.cc index c17c0d71e0..cbff68ce4e 100644 --- a/libs/libmdbx/src/test/config.cc +++ b/libs/libmdbx/src/test/config.cc @@ -43,11 +43,6 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option, if (narg + 1 < argc && strncmp("--", argv[narg + 1], 2) != 0) { *value = argv[narg + 1]; - if (strcmp(*value, "default") == 0) { - if (!default_value) - failure("Option '--%s' doen't accept default value\n", option); - *value = default_value; - } ++narg; return true; } @@ -62,15 +57,9 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option, bool parse_option(int argc, char *const argv[], int &narg, const char *option, std::string &value, bool allow_empty) { - return parse_option(argc, argv, narg, option, value, allow_empty, - allow_empty ? "" : nullptr); -} - -bool parse_option(int argc, char *const argv[], int &narg, const char *option, - std::string &value, bool allow_empty, - const char *default_value) { const char *value_cstr; - if (!parse_option(argc, argv, narg, option, &value_cstr, default_value)) + if (!parse_option(argc, argv, narg, option, &value_cstr, + allow_empty ? "" : nullptr)) return false; if (!allow_empty && strlen(value_cstr) == 0) @@ -86,7 +75,7 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option, if (!parse_option(argc, argv, narg, option, &list)) return false; - unsigned clear = 0; + mask = 0; while (*list) { if (*list == ',' || *list == ' ' || *list == '\t') { ++list; @@ -94,21 +83,14 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option, } const char *const comma = strchr(list, ','); - const bool strikethrough = *list == '-' || *list == '~'; - if (strikethrough || *list == '+') - ++list; - else - mask = clear; const size_t len = (comma) ? comma - list : strlen(list); const option_verb *scan = verbs; - while (true) { if (!scan->verb) failure("Unknown verb '%.*s', for option '==%s'\n", (int)len, list, option); if (strlen(scan->verb) == len && strncmp(list, scan->verb, len) == 0) { - mask = strikethrough ? mask & ~scan->mask : mask | scan->mask; - clear = strikethrough ? clear & ~scan->mask : clear | scan->mask; + mask |= scan->mask; list += len; break; } @@ -121,36 +103,15 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option, bool parse_option(int argc, char *const argv[], int &narg, const char *option, uint64_t &value, const scale_mode scale, - const uint64_t minval, const uint64_t maxval, - const uint64_t default_value) { + const uint64_t minval, const uint64_t maxval) { const char *value_cstr; if (!parse_option(argc, argv, narg, option, &value_cstr)) return false; - if (default_value && strcmp(value_cstr, "default") == 0) { - value = default_value; - return true; - } - - if (strcmp(value_cstr, "min") == 0 || strcmp(value_cstr, "minimal") == 0) { - value = minval; - return true; - } - - if (strcmp(value_cstr, "max") == 0 || strcmp(value_cstr, "maximal") == 0) { - value = maxval; - return true; - } - char *suffix = nullptr; errno = 0; - unsigned long long raw = strtoull(value_cstr, &suffix, 0); - if ((suffix && *suffix) || errno) { - suffix = nullptr; - errno = 0; - raw = strtoull(value_cstr, &suffix, 10); - } + unsigned long raw = strtoul(value_cstr, &suffix, 0); if (errno) failure("Option '--%s' expects a numeric value (%s)\n", option, test_strerror(errno)); @@ -206,58 +167,28 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option, bool parse_option(int argc, char *const argv[], int &narg, const char *option, unsigned &value, const scale_mode scale, - const unsigned minval, const unsigned maxval, - const unsigned default_value) { + const unsigned minval, const unsigned maxval) { uint64_t huge; - if (!parse_option(argc, argv, narg, option, huge, scale, minval, maxval, - default_value)) + if (!parse_option(argc, argv, narg, option, huge, scale, minval, maxval)) return false; value = (unsigned)huge; return true; } bool parse_option(int argc, char *const argv[], int &narg, const char *option, - uint8_t &value, const uint8_t minval, const uint8_t maxval, - const uint8_t default_value) { + uint8_t &value, const uint8_t minval, const uint8_t maxval) { uint64_t huge; - if (!parse_option(argc, argv, narg, option, huge, no_scale, minval, maxval, - default_value)) + if (!parse_option(argc, argv, narg, option, huge, no_scale, minval, maxval)) return false; value = (uint8_t)huge; return true; } bool parse_option(int argc, char *const argv[], int &narg, const char *option, - int64_t &value, const int64_t minval, const int64_t maxval, - const int64_t default_value) { - uint64_t proxy = (uint64_t)value; - if (parse_option(argc, argv, narg, option, proxy, config::binary, - (uint64_t)minval, (uint64_t)maxval, - (uint64_t)default_value)) { - value = (int64_t)proxy; - return true; - } - return false; -} - -bool parse_option(int argc, char *const argv[], int &narg, const char *option, - int32_t &value, const int32_t minval, const int32_t maxval, - const int32_t default_value) { - uint64_t proxy = (uint64_t)value; - if (parse_option(argc, argv, narg, option, proxy, config::binary, - (uint64_t)minval, (uint64_t)maxval, - (uint64_t)default_value)) { - value = (int32_t)proxy; - return true; - } - return false; -} - -bool parse_option(int argc, char *const argv[], int &narg, const char *option, bool &value) { - const char *value_cstr = nullptr; + const char *value_cstr = NULL; if (!parse_option(argc, argv, narg, option, &value_cstr, "yes")) { const char *current = argv[narg]; if (strncmp(current, "--no-", 5) == 0 && strcmp(current + 5, option) == 0) { @@ -357,12 +288,8 @@ void dump(const char *title) { : i->params.pathname_log.c_str()); } - log_info("database: %s, size %" PRIuPTR "[%" PRIiPTR "..%" PRIiPTR - ", %i %i, %i]\n", - i->params.pathname_db.c_str(), i->params.size_now, - i->params.size_lower, i->params.size_upper, - i->params.shrink_threshold, i->params.growth_step, - i->params.pagesize); + log_info("database: %s, size %" PRIu64 "\n", i->params.pathname_db.c_str(), + i->params.size); dump_verbs("mode", i->params.mode_flags, mode_bits); dump_verbs("table", i->params.table_flags, table_bits); diff --git a/libs/libmdbx/src/test/config.h b/libs/libmdbx/src/test/config.h index e8dc87577d..86f37fbed8 100644 --- a/libs/libmdbx/src/test/config.h +++ b/libs/libmdbx/src/test/config.h @@ -63,10 +63,6 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option, std::string &value, bool allow_empty = false); bool parse_option(int argc, char *const argv[], int &narg, const char *option, - std::string &value, bool allow_empty, - const char *default_value); - -bool parse_option(int argc, char *const argv[], int &narg, const char *option, bool &value); struct option_verb { @@ -79,25 +75,16 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option, bool parse_option(int argc, char *const argv[], int &narg, const char *option, uint64_t &value, const scale_mode scale, - const uint64_t minval = 0, const uint64_t maxval = INT64_MAX, - const uint64_t default_value = 0); + const uint64_t minval = 0, const uint64_t maxval = INT64_MAX); bool parse_option(int argc, char *const argv[], int &narg, const char *option, unsigned &value, const scale_mode scale, - const unsigned minval = 0, const unsigned maxval = INT32_MAX, - const unsigned default_value = 0); + const unsigned minval = 0, const unsigned maxval = INT32_MAX); bool parse_option(int argc, char *const argv[], int &narg, const char *option, uint8_t &value, const uint8_t minval = 0, - const uint8_t maxval = 255, const uint8_t default_value = 0); + const uint8_t maxval = 255); -bool parse_option(int argc, char *const argv[], int &narg, const char *option, - int64_t &value, const int64_t minval, const int64_t maxval, - const int64_t default_value = -1); - -bool parse_option(int argc, char *const argv[], int &narg, const char *option, - int32_t &value, const int32_t minval, const int32_t maxval, - const int32_t default_value = -1); //----------------------------------------------------------------------------- #pragma pack(push, 1) @@ -216,12 +203,7 @@ struct actor_params_pod { unsigned mode_flags; unsigned table_flags; - intptr_t size_lower; - intptr_t size_now; - intptr_t size_upper; - int shrink_threshold; - int growth_step; - int pagesize; + uint64_t size; unsigned test_duration; unsigned test_nops; diff --git a/libs/libmdbx/src/test/gc.sh b/libs/libmdbx/src/test/gc.sh deleted file mode 100644 index 84f31a50e7..0000000000 --- a/libs/libmdbx/src/test/gc.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -set -euo pipefail -TESTDB_PREFIX=${1:-/dev/shm/mdbx-gc-test} - -function rep9 { printf "%*s" $1 '' | tr ' ' '9'; } -function join { local IFS="$1"; shift; echo "$*"; } -function bit2option { local -n arr=$1; (( ($2&(1<<$3)) != 0 )) && echo -n '+' || echo -n '-'; echo "${arr[$3]}"; } - -options=(writemap coalesce lifo) - -function bits2list { - local -n arr=$1 - local i - local list=() - for ((i=0; i<${#arr[@]}; ++i)) do - list[$i]=$(bit2option $1 $2 $i) - done - join , "${list[@]}" -} - -for nops in {7..1}; do - for ((wbatch=nops; wbatch > 0; --wbatch)); do - for ((bits=2**${#options[@]}; --bits >= 0; )); do - echo "=================================== $(date)" - rm -f ${TESTDB_PREFIX}* - echo --nops=$( rep9 $nops ) --batch.write=$( rep9 $wbatch ) --mode=$(bits2list options $bits) - ./mdbx_test --pathname=${TESTDB_PREFIX} --pagesize=min --size=8G --keylen.min=1 --keylen.max=250 --datalen.min=1 --datalen.max=1500 \ - --nops=$( rep9 $nops ) --batch.write=$( rep9 $wbatch ) --mode=$(bits2list options $bits) \ - --keygen.seed=$(date +%N) --hill | bzip2 -c > ${TESTDB_PREFIX}.log.bz2 - ./mdbx_chk -nvv ${TESTDB_PREFIX} | tee ${TESTDB_PREFIX}-chk.log - done - done -done - -echo "=== ALL DONE ====================== $(date)" diff --git a/libs/libmdbx/src/test/hill.cc b/libs/libmdbx/src/test/hill.cc index 0d609b8692..c9115784d4 100644 --- a/libs/libmdbx/src/test/hill.cc +++ b/libs/libmdbx/src/test/hill.cc @@ -156,6 +156,8 @@ bool testcase_hill::run() { a_serial); generate_pair(a_serial, a_key, a_data_0, 0); generate_pair(a_serial, a_key, a_data_1, age_shift); + if (a_serial == 808) + log_trace("!!!"); int rc = mdbx_replace(txn_guard.get(), dbi, &a_key->value, &a_data_1->value, &a_data_0->value, update_flags); if (unlikely(rc != MDBX_SUCCESS)) diff --git a/libs/libmdbx/src/test/keygen.cc b/libs/libmdbx/src/test/keygen.cc index 1b18fa0033..99b46f2976 100644 --- a/libs/libmdbx/src/test/keygen.cc +++ b/libs/libmdbx/src/test/keygen.cc @@ -122,16 +122,16 @@ void maker::setup(const config::actor_params_pod &actor, unsigned thread_number) { key_essentials.flags = actor.table_flags & (MDBX_INTEGERKEY | MDBX_REVERSEKEY); - assert(actor.keylen_min <= UINT8_MAX); + assert(actor.keylen_min < UINT8_MAX); key_essentials.minlen = (uint8_t)actor.keylen_min; - assert(actor.keylen_max <= UINT16_MAX); + assert(actor.keylen_max < UINT16_MAX); key_essentials.maxlen = (uint16_t)actor.keylen_max; value_essentials.flags = actor.table_flags & (MDBX_INTEGERDUP | MDBX_REVERSEDUP); - assert(actor.datalen_min <= UINT8_MAX); + assert(actor.datalen_min < UINT8_MAX); value_essentials.minlen = (uint8_t)actor.datalen_min; - assert(actor.datalen_max <= UINT16_MAX); + assert(actor.datalen_max < UINT16_MAX); value_essentials.maxlen = (uint16_t)actor.datalen_max; assert(thread_number < 2); diff --git a/libs/libmdbx/src/test/log.h b/libs/libmdbx/src/test/log.h index 3e9f9483ab..e97e954cea 100644 --- a/libs/libmdbx/src/test/log.h +++ b/libs/libmdbx/src/test/log.h @@ -17,7 +17,16 @@ #include "base.h" void __noreturn usage(void); + +#ifdef __GNUC__ +#define __printf_args(format_index, first_arg) \ + __attribute__((format(printf, format_index, first_arg))) +#else +#define __printf_args(format_index, first_arg) +#endif + void __noreturn __printf_args(1, 2) failure(const char *fmt, ...); + void __noreturn failure_perror(const char *what, int errnum); const char *test_strerror(int errnum); diff --git a/libs/libmdbx/src/test/main.cc b/libs/libmdbx/src/test/main.cc index 1cac3506a3..bc3198ed3a 100644 --- a/libs/libmdbx/src/test/main.cc +++ b/libs/libmdbx/src/test/main.cc @@ -1,4 +1,4 @@ -/* +/* * Copyright 2017-2018 Leonid Yuriev <leo@yuriev.ru> * and other libmdbx authors: please see AUTHORS file. * All rights reserved. @@ -35,13 +35,7 @@ void actor_params::set_defaults(const std::string &tmpdir) { mode_flags = MDBX_NOSUBDIR | MDBX_WRITEMAP | MDBX_MAPASYNC | MDBX_NORDAHEAD | MDBX_NOMEMINIT | MDBX_COALESCE | MDBX_LIFORECLAIM; table_flags = MDBX_DUPSORT; - - size_lower = -1; - size_now = 1024 * 1024 * 4; - size_upper = -1; - shrink_threshold = -1; - growth_step = -1; - pagesize = -1; + size = 1024 * 1024 * 4; keygen.seed = 1; keygen.keycase = kc_random; @@ -156,34 +150,8 @@ int main(int argc, char *const argv[]) { if (config::parse_option(argc, argv, narg, "table", params.table_flags, config::table_bits)) continue; - - if (config::parse_option(argc, argv, narg, "pagesize", params.pagesize, - mdbx_limits_pgsize_min(), - mdbx_limits_pgsize_max())) - continue; - if (config::parse_option(argc, argv, narg, "size-lower", params.size_lower, - mdbx_limits_dbsize_min(params.pagesize), - mdbx_limits_dbsize_max(params.pagesize))) - continue; - if (config::parse_option(argc, argv, narg, "size", params.size_now, - mdbx_limits_dbsize_min(params.pagesize), - mdbx_limits_dbsize_max(params.pagesize))) - continue; - if (config::parse_option(argc, argv, narg, "size-upper", params.size_upper, - mdbx_limits_dbsize_min(params.pagesize), - mdbx_limits_dbsize_max(params.pagesize))) - continue; - if (config::parse_option( - argc, argv, narg, "shrink-threshold", params.shrink_threshold, 0, - (int)std::min((intptr_t)INT_MAX, - mdbx_limits_dbsize_max(params.pagesize) - - mdbx_limits_dbsize_min(params.pagesize)))) - continue; - if (config::parse_option( - argc, argv, narg, "growth-step", params.growth_step, 0, - (int)std::min((intptr_t)INT_MAX, - mdbx_limits_dbsize_max(params.pagesize) - - mdbx_limits_dbsize_min(params.pagesize)))) + if (config::parse_option(argc, argv, narg, "size", params.size, + config::binary, 4096 * 4)) continue; if (config::parse_option(argc, argv, narg, "keygen.width", @@ -220,33 +188,20 @@ int main(int argc, char *const argv[]) { config::duration, 1)) continue; if (config::parse_option(argc, argv, narg, "keylen.min", params.keylen_min, - config::no_scale, 0, UINT8_MAX)) { - if (params.keylen_max < params.keylen_min) - params.keylen_max = params.keylen_min; + config::no_scale, 0, params.keylen_max)) continue; - } if (config::parse_option(argc, argv, narg, "keylen.max", params.keylen_max, - config::no_scale, 0, - std::min((unsigned)mdbx_limits_keysize_max(0), - (unsigned)UINT16_MAX))) { - if (params.keylen_min > params.keylen_max) - params.keylen_min = params.keylen_max; + config::no_scale, params.keylen_min, + mdbx_get_maxkeysize(0))) continue; - } if (config::parse_option(argc, argv, narg, "datalen.min", params.datalen_min, config::no_scale, 0, - UINT8_MAX)) { - if (params.datalen_max < params.datalen_min) - params.datalen_max = params.datalen_min; + params.datalen_max)) continue; - } if (config::parse_option(argc, argv, narg, "datalen.max", - params.datalen_max, config::no_scale, 0, - std::min((int)UINT16_MAX, MDBX_MAXDATASIZE))) { - if (params.datalen_min > params.datalen_max) - params.datalen_min = params.datalen_max; + params.datalen_max, config::no_scale, + params.datalen_min, MDBX_MAXDATASIZE)) continue; - } if (config::parse_option(argc, argv, narg, "batch.read", params.batch_read, config::no_scale, 1)) continue; diff --git a/libs/libmdbx/src/test/osal-windows.cc b/libs/libmdbx/src/test/osal-windows.cc index b8cdb53513..109c835a96 100644 --- a/libs/libmdbx/src/test/osal-windows.cc +++ b/libs/libmdbx/src/test/osal-windows.cc @@ -53,7 +53,7 @@ void osal_wait4barrier(void) { } } -static HANDLE make_inheritable(HANDLE hHandle) { +static HANDLE make_inharitable(HANDLE hHandle) { assert(hHandle != NULL && hHandle != INVALID_HANDLE_VALUE); if (!DuplicateHandle(GetCurrentProcess(), hHandle, GetCurrentProcess(), &hHandle, 0, TRUE, @@ -71,7 +71,7 @@ void osal_setup(const std::vector<actor_config> &actors) { HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!hEvent) failure_perror("CreateEvent()", GetLastError()); - hEvent = make_inheritable(hEvent); + hEvent = make_inharitable(hEvent); log_trace("osal_setup: event %" PRIuPTR " -> %p", i, hEvent); events[i] = hEvent; } @@ -79,12 +79,12 @@ void osal_setup(const std::vector<actor_config> &actors) { hBarrierSemaphore = CreateSemaphore(NULL, 0, (LONG)actors.size(), NULL); if (!hBarrierSemaphore) failure_perror("CreateSemaphore(BarrierSemaphore)", GetLastError()); - hBarrierSemaphore = make_inheritable(hBarrierSemaphore); + hBarrierSemaphore = make_inharitable(hBarrierSemaphore); hBarrierEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!hBarrierEvent) failure_perror("CreateEvent(BarrierEvent)", GetLastError()); - hBarrierEvent = make_inheritable(hBarrierEvent); + hBarrierEvent = make_inharitable(hBarrierEvent); } void osal_broadcast(unsigned id) { diff --git a/libs/libmdbx/src/test/test.cc b/libs/libmdbx/src/test/test.cc index 80694827a6..3750af525f 100644 --- a/libs/libmdbx/src/test/test.cc +++ b/libs/libmdbx/src/test/test.cc @@ -149,10 +149,7 @@ void testcase::db_prepare() { if (unlikely(rc != MDBX_SUCCESS)) failure_perror("mdbx_env_set_oomfunc()", rc); - rc = mdbx_env_set_geometry( - env, config.params.size_lower, config.params.size_now, - config.params.size_upper, config.params.growth_step, - config.params.shrink_threshold, config.params.pagesize); + rc = mdbx_env_set_mapsize(env, (size_t)config.params.size); if (unlikely(rc != MDBX_SUCCESS)) failure_perror("mdbx_env_set_mapsize()", rc); diff --git a/libs/libmdbx/src/test/utils.cc b/libs/libmdbx/src/test/utils.cc index 53a750e314..0855c7eef3 100644 --- a/libs/libmdbx/src/test/utils.cc +++ b/libs/libmdbx/src/test/utils.cc @@ -93,7 +93,7 @@ bool hex2data(const char *hex_begin, const char *hex_end, void *ptr, //----------------------------------------------------------------------------- -/* TODO: replace my 'libmera' from t1ha. */ +/* TODO: replace my 'libmera' fomr t1ha. */ uint64_t entropy_ticks(void) { #if defined(EMSCRIPTEN) return (uint64_t)emscripten_get_now(); |