summaryrefslogtreecommitdiff
path: root/libs/libmdbx
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2018-08-26 15:21:43 +0300
committerGeorge Hazan <ghazan@miranda.im>2018-08-26 15:21:43 +0300
commitcf57ec609dc7b02eb44433514af60bb74951e990 (patch)
tree27269734504985f30368b5ae86f902ada590dc41 /libs/libmdbx
parent1f711c47cd7b4388b2b8af1cd60893e843ac617f (diff)
Revert GC-related changes in libmdbx, till the time when it works
Diffstat (limited to 'libs/libmdbx')
-rw-r--r--libs/libmdbx/src/CMakeLists.txt (renamed from libs/libmdbx/src/packages/rpm/CMakeLists.txt)0
-rw-r--r--libs/libmdbx/src/README-RU.md708
-rw-r--r--libs/libmdbx/src/README.md714
-rw-r--r--libs/libmdbx/src/TODO.md89
-rw-r--r--libs/libmdbx/src/build.sh (renamed from libs/libmdbx/src/packages/rpm/build.sh)0
-rw-r--r--libs/libmdbx/src/mdbx.h8
-rw-r--r--libs/libmdbx/src/package.sh (renamed from libs/libmdbx/src/packages/rpm/package.sh)0
-rw-r--r--libs/libmdbx/src/src/bits.h13
-rw-r--r--libs/libmdbx/src/src/defs.h7
-rw-r--r--libs/libmdbx/src/src/lck-posix.c26
-rw-r--r--libs/libmdbx/src/src/lck-windows.c45
-rw-r--r--libs/libmdbx/src/src/mdbx.c892
-rw-r--r--libs/libmdbx/src/src/osal.h1
-rw-r--r--libs/libmdbx/src/src/tools/mdbx_chk.c12
-rw-r--r--libs/libmdbx/src/test/config.cc99
-rw-r--r--libs/libmdbx/src/test/config.h26
-rw-r--r--libs/libmdbx/src/test/gc.sh35
-rw-r--r--libs/libmdbx/src/test/hill.cc2
-rw-r--r--libs/libmdbx/src/test/keygen.cc8
-rw-r--r--libs/libmdbx/src/test/log.h9
-rw-r--r--libs/libmdbx/src/test/main.cc65
-rw-r--r--libs/libmdbx/src/test/osal-windows.cc8
-rw-r--r--libs/libmdbx/src/test/test.cc5
-rw-r--r--libs/libmdbx/src/test/utils.cc2
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();