Compare commits

..

56 Commits

Author SHA1 Message Date
moneromooo-monero
a3aa2b5a57 mdbx-doc: import - mdb_cursor_del does not invalidate the cursor (ITS#8857). 2018-09-24 19:35:39 +03:00
Howard Chu
02276500c9 mdbx-doc: import - GET_MULTIPLE etc don't return the key (ITS#8908).
Unnecessary since these are DUPs, the key will always be the same
2018-09-24 19:35:36 +03:00
Leonid Yuriev
de44ecccd1 mdbx: backport - update MAX_PAGENO and MAX_MAPSIZE64. 2018-09-23 18:07:29 +03:00
Leonid Yuriev
5049c86517 mdbx: backport - avoid empty and unneeded large/overflow pages (squashed). 2018-09-23 18:07:29 +03:00
Leonid Yuriev
d2854e0760 mdbx: backport - refine mdbx_chk (squashed).
- refine 'mismatch idl length' error message.
 - add/fix printf-format checking.
 - refine dbi-structure.
2018-09-23 18:07:29 +03:00
Leonid Yuriev
5a29214ad9 mdbx-test: backport - update 'gc.sh' script (squashed). 2018-09-23 15:39:56 +03:00
Leonid Yuriev
b51d92d449 mdbx-test: backport - update test (squashed).
- add support for 'default' options values.
 - add min/max cases for option values.
 - add support for db-geometry params.
 - fix int-types for 32-bit builds (minor).
 - fix key/value generation for long-length cases.
 - fix update_flags for non-MDBX_DUPSORT.
 - 'none' for config-verbs.
 - check commandline length under Windows.
 - workaround for QueryFullProcessImageNameA() bug.
 - add setloglevel().
 - workaroung for MSVC bug.
 - avoid extra 'jitter' testcase loops.
 - cleanup DUPSORT flags.
 - refine key/value min/max handling.
 - dump keygen params.
 - fix/refine keygen.
 - alter keygen defaults (rotate 3, offset 41).
 - default test-db size 4mb or 256mb.
 - fix/refine keygen for non-MDBX_DUPSORT.
 - seeding keygen with actor_id for better spreading.
2018-09-23 15:39:56 +03:00
Leonid Yuriev
6da477d37f mdbx-ci: backport - refines for Windows (squashed).
- push logs to appveyor separately.
 - rename 'test.exe' to 'mdbx_test.exe'.
 - add test.db to appveyor artefacts (windows).
2018-09-23 15:39:56 +03:00
Leonid Yuriev
6150a8c903 mdbx: backport - fix/refine mdbx_update_gc() (squashed). 2018-09-23 15:39:56 +03:00
Leonid Yuriev
f3e9731da4 mdbx: backport - move macros/inlines to fix Windows builds.
Change-Id: I48aaf6b77466bb8b13294b84de73fb6063c88190
2018-09-23 15:39:56 +03:00
Leonid Yuriev
353b6b8af0 mdbx: backport - refine assections (minor).
Change-Id: Ic924988b8ce043d6106df381c996dd2c8ff9ca1f
2018-09-23 15:39:56 +03:00
Leonid Yuriev
3f10e58df2 mdbx: backport - re-define assert macro via mdbx_assert.
Change-Id: I317801ba4200bdf1aa5cacf75d21a8e633fbc48a
2018-09-23 15:39:56 +03:00
Leonid Yuriev
d232737087 mdbx: backport - add MDBX_FORCE_ASSERT.
Change-Id: I68a9f7b42663ea157c7c0a5a58797c94127b45ed
2018-09-23 15:39:56 +03:00
Leo Yuriev
e32ca55258 mdbx: backport - fix tracking around mdbx_cursor_del(). 2018-09-23 15:39:56 +03:00
Leonid Yuriev
f57ffc987c mdbx: backport - drop inherited broken audit (will be fixed in the master branch).
Internal self-audit (inherited from LMDB) is invalid and useless
for sub-db and dupsort cases.
2018-09-23 15:39:56 +03:00
Leonid Yuriev
cdd510d20e mdbx: backport - prevent DB corruption due rebalance bugs.
Won't fix https://github.com/leo-yuriev/libmdbx/issues/38 in the 'stable/0.1' branch,
but add checks to prevent DB corruption.
2018-09-23 15:39:56 +03:00
Leo Yuriev
d757ba1266 mdbx: backport - fix MDBX_CORRUPTED due open/shrink collision. 2018-09-23 15:39:56 +03:00
Leonid Yuriev
337f7589f8 mdbx: backport - fix mdbx_pnl_search(). 2018-09-23 15:39:56 +03:00
Leonid Yuriev
912728a322 mdbx: backport - fix mdbx_replace().
Change-Id: I2af00f101017795ca2b967479f86e5ea7e8ad37b
2018-09-23 15:39:56 +03:00
Leonid Yuriev
204b5a532d mdbx: backport - shorten maxkeysize (will be fixed in the master branch).
Change-Id: I660b1b3e454d9b51a24d3b4cc987c8e2980bd435
2018-09-23 15:39:56 +03:00
Leonid Yuriev
014be165c3 mdbx: backport - allow GC's PNL be partially unused. 2018-09-23 15:39:56 +03:00
Leonid Yuriev
a9244f807b mdbx: backport - setup mdbx_cmp_memn() as data-comparator for safety. 2018-09-23 15:39:56 +03:00
Leonid Yuriev
6d438605dd mdbx: backport - check comparator for MDBX_GET_BOTH and MDBX_GET_BOTH_RANGE. 2018-09-23 15:39:56 +03:00
Leo Yuriev
34300150a1 mdbx: backport - don't touch mm_psize and mm_flags while provoking bad readers (debug-only). 2018-09-23 15:39:56 +03:00
Leo Yuriev
1b2b98234f mdbx: backport - fix concurrent opening with custom pagesize (get pagesize from meta-page early). 2018-09-23 15:39:56 +03:00
Leonid Yuriev
bd672a5583 mdbx: backport - add mdbx_limits_xyz() (squashed).
Change-Id: I56c79704c59386a0c4d84b001020484c23925e6c
2018-09-23 15:39:56 +03:00
Leonid Yuriev
888003c072 mdbx: backport - fix comments typos (squashed). 2018-09-23 14:40:31 +03:00
Leo Yuriev
bc6db4e4d7 mdbx: backport - allow mdbx_env_compact() to fix page leaks.
Don't treat fixing page leaks as an error while copy DB with compactification.

Change-Id: I2a575ff9e2b24610172aaca939b5f6957c26ec77
2018-09-23 14:40:31 +03:00
Leonid Yuriev
57655583e5 mdbx: backport - fix/rewrite mdbx_update_gc().
Change-Id: I580a1ff0cbeeb529e2bcbd50d97bfba7bcf5a546
2018-08-13 23:30:39 +03:00
Leonid Yuriev
b91e645919 mdbx-test: add 'gc.sh' script.
Change-Id: I633c93c0865b0d2609688713e986edf51ce6547d
2018-08-13 21:44:43 +03:00
Leonid Yuriev
652bb08f8c mdbx-test: backport - use strtoull() and retry with base=10.
Change-Id: Ica846ed0a13eb4468a45620518b9ccf85e77a764
2018-08-13 21:44:10 +03:00
Leonid Yuriev
59026d5f84 mdbx-test: backport - fix minor typos.
Change-Id: I4889a0e698bdfdda7eed257a5cd29e8b8089d102
2018-08-13 21:43:07 +03:00
Leonid Yuriev
e18551061e mdbx-test: backport - fix keylen/datalen min/max ranges checking.
Change-Id: Iee5d2f71ad22ec6e86167f5181deff54f0b5b518
2018-08-13 21:42:44 +03:00
Leonid Yuriev
e054ad2ebb mdbx-test: backport - add 'strikethrough' for bitmask-options.
Change-Id: I86dd2f8cdbd5a32a0471a5eee1e2b3a5857541ac
2018-08-13 21:42:22 +03:00
Leonid Yuriev
bff6aa460a mdbx: backport - fix MDBX_EKEYMISMATCH while update multi-value with MDBX_CURRENT.
Change-Id: I3095620a94f694fb2c29b9c4faab9ea02b9bd7b7
2018-08-13 21:41:17 +03:00
Leonid Yuriev
3979ba4784 mdbx: backport - fix assertions.
Change-Id: I95c43ef1ea2da55a124dc43f03890cf1d96f2e61
2018-08-13 21:40:48 +03:00
Leonid Yuriev
d2fcbf5f82 mdbx: backport - fix assert-condition inside mdbx_pnl_xappend().
Change-Id: Id5ac89c85b7e673c44d60a626c805fe666d221bc
2018-08-10 09:36:44 +03:00
Leo Yuriev
38067c4566 mdbx: backport - fix 'db_dummy' inside mdbx_dbi_open_ex().
Change-Id: I70a21c9b77a43c5af749da5723fa965487a056b0
2018-08-09 13:07:02 +03:00
Leo Yuriev
d4bfc17818 mdbx: backport - add fallback2shared for mdbx_lck_exclusive(). 2018-08-09 13:07:02 +03:00
Leo Yuriev
eb99480253 mdbx: backport - drop unused mdbx_lck_upgrade(). 2018-08-09 13:07:02 +03:00
Leo Yuriev
c9790b28d0 mdbx-cmake: fix so-version.
Change-Id: I427d2f27f9092d65a0ffd11353ca466070e98618
2018-08-09 13:07:02 +03:00
Leo Yuriev
9a1ef8acfb mdbx-cmake: remove warning-message.
Change-Id: Icf9e4f7a96916cf9ab04613344867217be04827a
2018-08-09 13:07:02 +03:00
Leo Yuriev
e442395cbd mdbx: bump version to v0.1.6
Change-Id: I95d45a815008e2cc9a8785a8c762310a1e907e21
2018-07-31 11:43:41 +03:00
Leo Yuriev
e57e521609 mdbx: backport - fix nasty suspend_and_append() bug.
Change-Id: I043adcff2e6c040426a51b5d4b15bac849e6dd9f
2018-07-31 11:43:35 +03:00
Leo Yuriev
d1809e6e2d mdbx: backport - minor fix to avoid Valgrind false-positive issue.
Change-Id: Ifa4dc51b500ff42a88182d750e22572aa5b2155b
2018-07-01 17:43:04 +03:00
Leo Yuriev
c579b974a2 mdbx: backport - avoid weak meta inside mdbx_init_metas().
Change-Id: Ib9c5ab04ad8cff3ad43d94a288cecec45d7ef37d
2018-06-30 02:43:10 +03:00
Leo Yuriev
de43ab0d21 mdbx-ci: migrate to Circle-CI 2.0
Change-Id: Id86af9e033d64a4dc2043db33cd8e7ae173feb22
2018-06-21 19:45:42 +03:00
Leo Yuriev
db50fb8726 mdbx: backport - fix Coverity warning (minor, paranoia).
Change-Id: I232377a03244dc33beb4f332c0024b454027f659
2018-06-21 18:26:31 +03:00
Leo Yuriev
408848a425 mdbx: Merge branch 'master' into stable/0.1
Change-Id: Ide2fbcd1b0b6bacbc4f07049633df81fede397eb
2018-06-21 17:57:06 +03:00
Leo Yuriev
e880e734ce mdbx: Merge branch 'master' into stable/0.1 2018-06-19 14:46:39 +03:00
Leo Yuriev
61b2a7fc54 mdbx: Merge branch 'master' into stable/0.1
Change-Id: I80b5afe1d227009a42096b5c6f2bfa9e5eb09036
2018-06-15 03:46:17 +03:00
Leo Yuriev
e1e17fd6a4 mdbx: Merge branch 'master' into stable/0.1 2018-06-14 13:54:01 +03:00
Leo Yuriev
17d3e7190c mdbx: Merge branch 'master' into stable/0.1
Change-Id: I7460e6a8b42c9bbeadfdf20d326c31c1d4a98969
2018-06-01 16:42:48 +03:00
Leo Yuriev
22d8b0b2e1 mdbx: Merge branch 'master' into stable/0.1
Change-Id: Idd1af2c07b48bfb1f335aa31a52bed4bd68514ac
2018-05-29 03:14:07 +03:00
Leo Yuriev
9384d0efa6 mdbx: Merge branch 'master' into stable/0.1
Change-Id: I3318b330e4aa5fd0db4642af6ef81e69814f0cc5
2018-05-22 12:03:57 +03:00
Leo Yuriev
ffafd5be10 mdbx: disable warning #5045 for MSVC (minor). 2018-05-21 16:36:41 +03:00
28 changed files with 2575 additions and 4009 deletions

View File

@@ -3,8 +3,8 @@ set(TARGET mdbx)
project(${TARGET})
set(MDBX_VERSION_MAJOR 0)
set(MDBX_VERSION_MINOR 2)
set(MDBX_VERSION_RELEASE 0)
set(MDBX_VERSION_MINOR 1)
set(MDBX_VERSION_RELEASE 6)
set(MDBX_VERSION_REVISION 0)
set(MDBX_VERSION_STRING ${MDBX_VERSION_MAJOR}.${MDBX_VERSION_MINOR}.${MDBX_VERSION_RELEASE})

View File

@@ -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 _активно перерабатывается_** и к середине 2018
ожидается большое изменение как API, так и формата базы данных.
К сожалению, обновление приведет к потере совместимости с
предыдущими версиями.
В настоящее время MDBX предназначена для Linux, а также поддерживает
Windows (начиная с Windows Server 2008) в качестве дополнительной
платформы. Поддержка других ОС может быть обеспечена на коммерческой
основе. Однако такие усовершенствования (т. е. pull-requests) могут быть
приняты в мейнстрим только в том случае, если будет доступен
соответствующий публичный и бесплатный сервис непрерывной интеграции
(aka Continuous Integration).
Цель этой революции - обеспечение более четкого надежного
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,410 +102,63 @@ 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),
но с устранением ряда описываемых далее проблем и архитектурных
недочетов.
_libmdbx_ наследует все ключевые возможности и особенности
своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database),
но с устранением ряда описываемых далее проблем и архитектурных недочетов.
1. Данные хранятся в упорядоченном отображении (ordered map), ключи
всегда отсортированы, поддерживается выборка диапазонов (range lookups).
1. Данные хранятся в упорядоченном отображении (ordered map), ключи всегда
отсортированы, поддерживается выборка диапазонов (range lookups).
2. Данные отображается в память каждого работающего с БД процесса. К
данным и ключам обеспечивается прямой доступ в памяти без необходимости
их копирования.
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).
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.
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).
8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и
журнала транзакций, после сбоев не требуется восстановление. Не
требуется компактификация или какое-либо периодическое обслуживание.
Поддерживается резервное копирование "по горячему", на работающей БД без
приостановки изменения данных.
9. Отсутствует какое-либо внутреннее управление памятью или
кэшированием. Всё необходимое штатно выполняет ядро ОС.
Доработки и усовершенствования относительно LMDB
================================================
1. Утилита `mdbx_chk` для проверки целостности структуры БД.
2. Автоматическое динамическое управление размером БД согласно
параметрам задаваемым функцией `mdbx_env_set_geometry()`, включая шаг
приращения и порог уменьшения размера БД, а также выбор размера
страницы. Соответственно, это позволяет снизить фрагментированность
файла БД на диске и освободить место, в том числе в **Windows**.
3. Автоматическая без-затратная компактификация БД путем возврата
освобождающихся страниц в область нераспределенного резерва в конце
файла данных. При этом уменьшается количество страниц находящихся в
памяти и участвующих в в обмене с диском.
4. Поддержка ключей и значений нулевой длины, включая сортированные
дубликаты.
5. Возможность связать с каждой завершаемой транзакцией до 3
дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их
в транзакции чтения посредством `mdbx_canary_get()`.
6. Возможность посредством `mdbx_replace()` обновить или удалить запись
с получением предыдущего значения данных, а также адресно изменить
конкретное multi-значение.
7. Режим `LIFO RECLAIM`.
Для повторного использования выбираются не самые старые, а
самые новые страницы из доступных. За счет этого цикл
использования страниц всегда имеет минимальную длину и не
зависит от общего числа выделенных страниц.
В результате механизмы кэширования и обратной записи работают с
максимально возможной эффективностью. В случае использования
контроллера дисков или системы хранения с
[BBWC](https://en.wikipedia.org/wiki/BBWC) возможно
многократное увеличение производительности по записи
(обновлению данных).
8. Генерация последовательностей посредством `mdbx_dbi_sequence()`.
9. Обработчик `OOM-KICK`.
Посредством `mdbx_env_set_oomfunc()` может быть установлен
внешний обработчик (callback), который будет вызван при
исчерпании свободных страниц по причине долгой операцией чтения
на фоне интенсивного изменения данных.
Обработчику будет передан PID и pthread_id виновника.
В свою очередь обработчик может предпринять одно из действий:
* нейтрализовать виновника (отправить сигнал kill #9), если
долгое чтение выполняется сторонним процессом;
* отменить или перезапустить проблемную операцию чтения, если
операция выполняется одним из потоков текущего процесса;
* подождать некоторое время, в расчете на то, что проблемная операция
чтения будет штатно завершена;
* прервать текущую операцию изменения данных с возвратом кода
ошибки.
10. Возможность открыть БД в эксклюзивном режиме посредством флага
`MDBX_EXCLUSIVE`.
11. Возможность получить отставание текущей транзакции чтения от
последней версии данных в БД посредством `mdbx_txn_straggler()`.
12. Возможность явно запросить обновление существующей записи, без
создания новой посредством флажка `MDBX_CURRENT` для `mdbx_put()`.
13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное
количество дубликатов для всех типов таблиц и любого положения курсора.
14. Возможность получить посредством `mdbx_env_info()` дополнительную
информацию, включая номер самой старой версии БД (снимка данных),
который используется одним из читателей.
15. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий)
аргумент `data` для таблиц без дубликатов (без флажка `MDBX_DUPSORT`), а
при его ненулевом значении всегда использует его для сверки с удаляемой
записью.
16. Возможность открыть dbi-таблицу, одновременно с установкой
компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`.
17. Возможность посредством `mdbx_is_dirty()` определить находятся ли
некоторый ключ или данные в "грязной" странице БД. Таким образом,
избегая лишнего копирования данных перед выполнением модифицирующих
операций (значения, размещенные в "грязных" страницах, могут быть
перезаписаны при изменениях, иначе они будут неизменны).
18. Корректное обновление текущей записи, в том числе сортированного
дубликата, при использовании режима `MDBX_CURRENT` в
`mdbx_cursor_put()`.
19. Возможность узнать есть ли за текущей позицией курсора строка данных
посредством `mdbx_cursor_eof()`.
20. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из
`mdbx_put()` и `mdbx_replace()` при попытке выполнить неоднозначное
обновление или удаления одного из нескольких значений с одним ключом.
21. Возможность посредством `mdbx_get_ex()` получить значение по
заданному ключу, одновременно с количеством дубликатов.
22. Наличие функций `mdbx_cursor_on_first()` и `mdbx_cursor_on_last()`,
которые позволяют быстро выяснить стоит ли курсор на первой/последней
позиции.
23. Возможность автоматического формирования контрольных точек (сброса
данных на диск) при накоплении заданного объёма изменений,
устанавливаемого функцией `mdbx_env_set_syncbytes()`.
24. Управление отладкой и получение отладочных сообщений посредством
`mdbx_setup_debug()`.
25. Функция `mdbx_env_pgwalk()` для обхода всех страниц БД.
26. Три мета-страницы вместо двух, что позволяет гарантированно
консистентно обновлять слабые контрольные точки фиксации без риска
повредить крайнюю сильную точку фиксации.
27. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`.
> В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
> режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC`
> когда при системной аварии есть шанс полного разрушения БД как в LMDB.
> Для подробностей смотрите раздел
> [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации).
28. Возможность закрыть БД в "грязном" состоянии (без сброса данных и
формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`.
29. При завершении читающих транзакций, открытые в них DBI-хендлы не
закрываются и не теряются при завершении таких транзакций посредством
`mdbx_txn_abort()` или `mdbx_txn_reset()`. Что позволяет избавится от ряда
сложно обнаруживаемых ошибок.
30. Все курсоры, как в транзакциях только для чтения, так и в пишущих,
могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ
ОСВОБОЖДАТЬСЯ ЯВНО.
>
> ## _ВАЖНО_, Обратите внимание!
>
> Это единственное изменение в API, которое значимо меняет
> семантику управления курсорами и может приводить к утечкам
> памяти. Следует отметить, что это изменение вынужденно.
> Так устраняется неоднозначность с массой тяжких последствий:
>
> - обращение к уже освобожденной памяти;
> - попытки повторного освобождения памяти;
> - повреждение памяти и ошибки сегментации.
--------------------------------------------------------------------------------
## Недостатки и Компромиссы
1. Единовременно может выполняться не более одной транзакция изменения данных
(один писатель). Зато все изменения всегда последовательны, не может быть
конфликтов или логических ошибок при откате транзакций.
2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging)
обуславливает относительно большой
[WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write
Amplification Factor). Поэтому фиксация изменений на диске может быть
достаточно дорогой и являться главным ограничением производительности
при интенсивном изменении данных.
> В качестве компромисса _libmdbx_ предлагает несколько режимов ленивой
> и/или периодической фиксации. В том числе режим `MAPASYNC`, при котором
> изменения происходят только в памяти и асинхронно фиксируются на диске
> ядром ОС.
>
> Однако, следует воспринимать это свойство аккуратно и взвешенно.
> Например, полная фиксация транзакции в БД с журналом потребует минимум 2
> IOPS (скорее всего 3-4) из-за накладных расходов в файловой системе. В
> _libmdbx_ фиксация транзакции также требует от 2 IOPS. Однако, в БД с
> журналом кол-во IOPS будет меняться в зависимости от файловой системы,
> но не от кол-ва записей или их объема. Тогда как в _libmdbx_ кол-во
> будет расти логарифмически от кол-ва записей/строк в БД (по высоте
> b+tree).
Amplification Factor) и RAF (Read Amplification Factor) также Olog(N).
3. [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8)
для реализации [MVCC](https://ru.wikipedia.org/wiki/MVCC) выполняется на
уровне страниц в [B+
дереве](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE).
Поэтому изменение данных амортизационно требует копирования Olog(N)
страниц, что расходует [пропускную способность оперативной
памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является
основным ограничителем производительности в режиме `MAPASYNC`.
> Этот недостаток неустраним, тем не менее следует дать некоторые пояснения.
> Дело в том, что фиксация изменений на диске потребует гораздо более
> значительного копирования данных в памяти и массы других затратных операций.
> Поэтому обусловленное этим недостатком падение производительности становится
> заметным только при отказе от фиксации изменений на диске.
> Соответственно, корректнее сказать, что _libmdbx_ позволяет
> получить персистентность ценой минимального падения производительности.
> Если же нет необходимости оперативно сохранять данные, то логичнее
> использовать `std::map`.
8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала
транзакций, после сбоев не требуется восстановление. Не требуется компактификация
или какое-либо периодическое обслуживание. Поддерживается резервное копирование
"по горячему", на работающей БД без приостановки изменения данных.
4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей),
которая приводит к деградации производительности и переполнению БД.
> В _libmdbx_ предложены средства для предотвращения, быстрого выхода из
> некомфортной ситуации и устранения её последствий. Подробности ниже.
9. Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё
необходимое штатно выполняет ядро ОС!
5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`.
В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы,
так и согласованность данных.
> Дополнительно, в качестве альтернативы, предложен режим `UTTERLY_NOSYNC`.
> Подробности ниже.
#### Проблема долгих чтений
*Следует отметить*, что проблема "сборки мусора" так или иначе
существует во всех СУБД (Vacuum в PostgreSQL). Однако в случае _libmdbx_
и LMDB она проявляется более остро, прежде всего из-за высокой
производительности, а также из-за намеренного упрощения внутренних
механизмов ради производительности.
Понимание проблемы требует некоторых пояснений, которые
изложены ниже, но могут быть сложны для быстрого восприятия.
Поэтому, тезисно:
* Изменение данных на фоне долгой операции чтения может
приводить к исчерпанию места в БД.
* После чего любая попытка обновить данные будет приводить к
ошибке `MAP_FULL` до завершения долгой операции чтения.
* Характерными примерами долгих чтений являются горячее
резервное копирования и отладка клиентского приложения при
активной транзакции чтения.
* В оригинальной _LMDB_ после этого будет наблюдаться
устойчивая деградация производительности всех механизмов
обратной записи на диск (в I/O контроллере, в гипервизоре,
в ядре ОС).
* В _libmdbx_ предусмотрен механизм аварийного прерывания таких
операций, а также режим `LIFO RECLAIM` устраняющий последующую
деградацию производительности.
Операции чтения выполняются в контексте снимка данных (версии
БД), который был актуальным на момент старта транзакции чтения. Такой
читаемый снимок поддерживается неизменным до завершения операции. В свою
очередь, это не позволяет повторно использовать страницы БД в
последующих версиях (снимках БД).
Другими словами, если обновление данных выполняется на фоне долгой
операции чтения, то вместо повторного использования "старых" ненужных
страниц будут выделяться новые, так как "старые" страницы составляют
снимок БД, который еще используется долгой операцией чтения.
В результате, при интенсивном изменении данных и достаточно длительной
операции чтения, в БД могут быть исчерпаны свободные страницы, что не
позволит создавать новые снимки/версии БД. Такая ситуация будет
сохраняться до завершения операции чтения, которая использует старый
снимок данных и препятствует повторному использованию страниц БД.
Однако, на этом проблемы не заканчиваются. После описанной ситуации, все
дополнительные страницы, которые были выделены пока переработка старых
была невозможна, будут участвовать в цикле выделения/освобождения до
конца жизни экземпляра БД. В оригинальной _LMDB_ этот цикл использования
страниц работает по принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO).
Поэтому увеличение количества циркулирующий страниц, с точки зрения
механизмов кэширования и/или обратной записи, выглядит как увеличение
рабочего набор данных. Проще говоря, однократное попадание в ситуацию
"уснувшего читателя" приводит к устойчивому эффекту вымывания I/O кэша
при всех последующих изменениях данных.
Для устранения описанных проблемы в _libmdbx_ сделаны существенные
доработки, подробности ниже. Иллюстрации к проблеме "долгих чтений"
можно найти в [слайдах презентации](http://www.slideshare.net/leoyuriev/lmdb).
Там же приведен пример количественной оценки прироста производительности
за счет эффективной работы [BBWC](https://en.wikipedia.org/wiki/BBWC)
при включении `LIFO RECLAIM` в _libmdbx_.
#### Сохранность данных в режиме асинхронной фиксации
При работе в режиме `WRITEMAP+MAPSYNC` запись измененных страниц
выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе
приложения, ядро ОС сохранит все изменения.
Однако, при аварийном отключении питания или сбое в ядре ОС, на диске
может быть сохранена только часть измененных страниц БД. При этом с
большой вероятностью может оказаться, что будут сохранены мета-страницы
со ссылками на страницы с новыми версиями данных, но не сами новые
данные. В этом случае БД будет безвозвратна разрушена, даже если до
аварии производилась полная синхронизация данных (посредством
`mdbx_env_sync()`).
В _libmdbx_ эта проблема устранена путем полной переработки
пути записи данных:
* В режиме `WRITEMAP+MAPSYNC` _libmdbx_ не обновляет
мета-страницы непосредственно, а поддерживает их теневые копии
с переносом изменений после фиксации данных.
* При завершении транзакций, в зависимости от состояния
синхронности данных между диском и оперативной памятью,
_libmdbx_ помечает точки фиксации либо как сильные (strong),
либо как слабые (weak). Так например, в режиме
`WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как
слабые, а при явной синхронизации данных - как сильные.
* В _libmdbx_ поддерживается не две, а три отдельные мета-страницы.
Это позволяет выполнять фиксацию транзакций с формированием как
сильной, так и слабой точки фиксации, без потери двух предыдущих
точек фиксации (из которых одна может быть сильной, а вторая слабой).
В результате, _libmdbx_ позволяет в произвольном порядке чередовать
сильные и слабые точки фиксации без нарушения соответствующих
гарантий в случае неожиданной системной аварии во время фиксации.
* При открытии БД выполняется автоматический откат к последней
сильной фиксации. Этим обеспечивается гарантия сохранности БД.
Такая гарантия надежности не дается бесплатно. Для сохранности данных,
страницы, формирующие крайний снимок с сильной фиксацией, не должны
повторно использоваться (перезаписываться) до формирования следующей
сильной точки фиксации. Таким образом, крайняя точка фиксации создает
описанный выше эффект "долгого чтения". Разница же здесь в том, что при
исчерпании свободных страниц ситуация будет автоматически исправлена,
посредством записи изменений на диск и формирования новой сильной точки
фиксации.
Таким образом, в режиме безопасной асинхронной фиксации _libmdbx_ будет
всегда использовать новые страницы до исчерпания места в БД или до
явного формирования сильной точки фиксации посредством
`mdbx_env_sync()`. При этом суммарный трафик записи на диск будет
примерно такой же, как если бы отдельно фиксировалась каждая транзакция.
В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC`
когда при системной аварии есть шанс полного разрушения БД как в LMDB.
В последующих версиях _libmdbx_ будут предусмотрены средства для
асинхронной записи данных на диск с автоматическим формированием сильных
точек фиксации.
--------------------------------------------------------------------------------
Сравнение производительности
============================
@@ -652,15 +302,14 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
Показана соотнесенная сумма использованных ресурсов в ходе бенчмарка в
режиме отложенной фиксации:
- суммарное количество операций ввода-вывода (IOPS), как записи, так и
чтения.
- суммарное количество операций ввода-вывода (IOPS), как записи, так и
чтения.
- суммарное затраченное время процессора, как в режиме пользовательских
процессов, так и в режиме ядра ОС.
- суммарное затраченное время процессора, как в режиме пользовательских процессов,
так и в режиме ядра ОС.
- использованное место на диске при завершении теста, после закрытия БД
из тестирующего процесса, но без ожидания всех внутренних операций
обслуживания (компактификации LSM и т.п.).
- использованное место на диске при завершении теста, после закрытия БД из тестирующего процесса,
но без ожидания всех внутренних операций обслуживания (компактификации LSM и т.п.).
Движок _ForestDB_ был исключен при оформлении результатов, так как
относительно конкурентов многократно превысил потребление каждого из
@@ -676,6 +325,352 @@ _libmdbx_ при этом не ведет WAL, а передает весь ко
--------------------------------------------------------------------------------
## Недостатки и Компромиссы
1. Единовременно может выполняться не более одной транзакция изменения данных
(один писатель). Зато все изменения всегда последовательны, не может быть
конфликтов или логических ошибок при откате транзакций.
2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging)
обуславливает относительно большой
[WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write
Amplification Factor). Поэтому фиксация изменений на диске может быть
достаточно дорогой и являться главным ограничением производительности
при интенсивном изменении данных.
> В качестве компромисса _libmdbx_ предлагает несколько режимов ленивой
> и/или периодической фиксации. В том числе режим `MAPASYNC`, при котором
> изменения происходят только в памяти и асинхронно фиксируются на диске
> ядром ОС.
>
> Однако, следует воспринимать это свойство аккуратно и взвешенно.
> Например, полная фиксация транзакции в БД с журналом потребует минимум 2
> IOPS (скорее всего 3-4) из-за накладных расходов в файловой системе. В
> _libmdbx_ фиксация транзакции также требует от 2 IOPS. Однако, в БД с
> журналом кол-во IOPS будет меняться в зависимости от файловой системы,
> но не от кол-ва записей или их объема. Тогда как в _libmdbx_ кол-во
> будет расти логарифмически от кол-ва записей/строк в БД (по высоте
> b+tree).
3. [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8)
для реализации [MVCC](https://ru.wikipedia.org/wiki/MVCC) выполняется на
уровне страниц в [B+
дереве](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE).
Поэтому изменение данных амортизационно требует копирования Olog(N)
страниц, что расходует [пропускную способность оперативной
памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является
основным ограничителем производительности в режиме `MAPASYNC`.
> Этот недостаток неустраним, тем не менее следует дать некоторые пояснения.
> Дело в том, что фиксация изменений на диске потребует гораздо более
> значительного копирования данных в памяти и массы других затратных операций.
> Поэтому обусловленное этим недостатком падение производительности становится
> заметным только при отказе от фиксации изменений на диске.
> Соответственно, корректнее сказать, что _libmdbx_ позволяет
> получить персистентность ценой минимального падения производительности.
> Если же нет необходимости оперативно сохранять данные, то логичнее
> использовать `std::map`.
4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей),
которая приводит к деградации производительности и переполнению БД.
> В _libmdbx_ предложены средства для предотвращения, быстрого выхода из
> некомфортной ситуации и устранения её последствий. Подробности ниже.
5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`.
В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы,
так и согласованность данных.
> Дополнительно, в качестве альтернативы, предложен режим `UTTERLY_NOSYNC`.
> Подробности ниже.
#### Проблема долгих чтений
*Следует отметить*, что проблема "сборки мусора" так или иначе
существует во всех СУБД (Vacuum в PostgreSQL). Однако в случае _libmdbx_
и LMDB она проявляется более остро, прежде всего из-за высокой
производительности, а также из-за намеренного упрощения внутренних
механизмов ради производительности.
Понимание проблемы требует некоторых пояснений, которые
изложены ниже, но могут быть сложны для быстрого восприятия.
Поэтому, тезисно:
* Изменение данных на фоне долгой операции чтения может
приводить к исчерпанию места в БД.
* После чего любая попытка обновить данные будет приводить к
ошибке `MAP_FULL` до завершения долгой операции чтения.
* Характерными примерами долгих чтений являются горячее
резервное копирования и отладка клиентского приложения при
активной транзакции чтения.
* В оригинальной _LMDB_ после этого будет наблюдаться
устойчивая деградация производительности всех механизмов
обратной записи на диск (в I/O контроллере, в гипервизоре,
в ядре ОС).
* В _libmdbx_ предусмотрен механизм аварийного прерывания таких
операций, а также режим `LIFO RECLAIM` устраняющий последующую
деградацию производительности.
Операции чтения выполняются в контексте снимка данных (версии
БД), который был актуальным на момент старта транзакции чтения. Такой
читаемый снимок поддерживается неизменным до завершения операции. В свою
очередь, это не позволяет повторно использовать страницы БД в
последующих версиях (снимках БД).
Другими словами, если обновление данных выполняется на фоне долгой
операции чтения, то вместо повторного использования "старых" ненужных
страниц будут выделяться новые, так как "старые" страницы составляют
снимок БД, который еще используется долгой операцией чтения.
В результате, при интенсивном изменении данных и достаточно длительной
операции чтения, в БД могут быть исчерпаны свободные страницы, что не
позволит создавать новые снимки/версии БД. Такая ситуация будет
сохраняться до завершения операции чтения, которая использует старый
снимок данных и препятствует повторному использованию страниц БД.
Однако, на этом проблемы не заканчиваются. После описанной ситуации, все
дополнительные страницы, которые были выделены пока переработка старых
была невозможна, будут участвовать в цикле выделения/освобождения до
конца жизни экземпляра БД. В оригинальной _LMDB_ этот цикл использования
страниц работает по принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO).
Поэтому увеличение количества циркулирующий страниц, с точки зрения
механизмов кэширования и/или обратной записи, выглядит как увеличение
рабочего набор данных. Проще говоря, однократное попадание в ситуацию
"уснувшего читателя" приводит к устойчивому эффекту вымывания I/O кэша
при всех последующих изменениях данных.
Для устранения описанных проблемы в _libmdbx_ сделаны существенные
доработки, подробности ниже. Иллюстрации к проблеме "долгих чтений"
можно найти в [слайдах презентации](http://www.slideshare.net/leoyuriev/lmdb).
Там же приведен пример количественной оценки прироста производительности
за счет эффективной работы [BBWC](https://en.wikipedia.org/wiki/BBWC)
при включении `LIFO RECLAIM` в _libmdbx_.
#### Сохранность данных в режиме асинхронной фиксации
При работе в режиме `WRITEMAP+MAPSYNC` запись измененных страниц
выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе
приложения, ядро ОС сохранит все изменения.
Однако, при аварийном отключении питания или сбое в ядре ОС, на диске
может быть сохранена только часть измененных страниц БД. При этом с большой
вероятностью может оказаться, что будут сохранены мета-страницы со
ссылками на страницы с новыми версиями данных, но не сами новые данные.
В этом случае БД будет безвозвратна разрушена, даже если до аварии
производилась полная синхронизация данных (посредством
`mdbx_env_sync()`).
В _libmdbx_ эта проблема устранена путем полной переработки
пути записи данных:
* В режиме `WRITEMAP+MAPSYNC` _libmdbx_ не обновляет
мета-страницы непосредственно, а поддерживает их теневые копии
с переносом изменений после фиксации данных.
* При завершении транзакций, в зависимости от состояния
синхронности данных между диском и оперативной памятью,
_libmdbx_ помечает точки фиксации либо как сильные (strong),
либо как слабые (weak). Так например, в режиме
`WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как
слабые, а при явной синхронизации данных - как сильные.
* В _libmdbx_ поддерживается не две, а три отдельные мета-страницы.
Это позволяет выполнять фиксацию транзакций с формированием как
сильной, так и слабой точки фиксации, без потери двух предыдущих
точек фиксации (из которых одна может быть сильной, а вторая слабой).
В результате, _libmdbx_ позволяет в произвольном порядке чередовать
сильные и слабые точки фиксации без нарушения соответствующих
гарантий в случае неожиданной системной аварии во время фиксации.
* При открытии БД выполняется автоматический откат к последней
сильной фиксации. Этим обеспечивается гарантия сохранности БД.
Такая гарантия надежности не дается бесплатно. Для
сохранности данных, страницы, формирующие крайний снимок с
сильной фиксацией, не должны повторно использоваться
(перезаписываться) до формирования следующей сильной точки
фиксации. Таким образом, крайняя точка фиксации создает
описанный выше эффект "долгого чтения". Разница же здесь в том,
что при исчерпании свободных страниц ситуация будет
автоматически исправлена, посредством записи изменений на диск
и формирования новой сильной точки фиксации.
Таким образом, в режиме безопасной асинхронной фиксации _libmdbx_ будет
всегда использовать новые страницы до исчерпания места в БД или до явного
формирования сильной точки фиксации посредством `mdbx_env_sync()`.
При этом суммарный трафик записи на диск будет примерно такой же,
как если бы отдельно фиксировалась каждая транзакция.
В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC` когда
при системной аварии есть шанс полного разрушения БД как в LMDB.
В последующих версиях _libmdbx_ будут предусмотрены средства
для асинхронной записи данных на диск с автоматическим
формированием сильных точек фиксации.
--------------------------------------------------------------------------------
Доработки и усовершенствования относительно LMDB
================================================
1. Режим `LIFO RECLAIM`.
Для повторного использования выбираются не самые старые, а
самые новые страницы из доступных. За счет этого цикл
использования страниц всегда имеет минимальную длину и не
зависит от общего числа выделенных страниц.
В результате механизмы кэширования и обратной записи работают с
максимально возможной эффективностью. В случае использования
контроллера дисков или системы хранения с
[BBWC](https://en.wikipedia.org/wiki/BBWC) возможно
многократное увеличение производительности по записи
(обновлению данных).
2. Обработчик `OOM-KICK`.
Посредством `mdbx_env_set_oomfunc()` может быть установлен
внешний обработчик (callback), который будет вызван при
исчерпании свободных страниц из-за долгой операцией чтения.
Обработчику будет передан PID и pthread_id виновника.
В свою очередь обработчик может предпринять одно из действий:
* нейтрализовать виновника (отправить сигнал kill #9), если
долгое чтение выполняется сторонним процессом;
* отменить или перезапустить проблемную операцию чтения, если
операция выполняется одним из потоков текущего процесса;
* подождать некоторое время, в расчете на то, что проблемная операция
чтения будет штатно завершена;
* прервать текущую операцию изменения данных с возвратом кода
ошибки.
3. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`.
> В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
> режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC`
> когда при системной аварии есть шанс полного разрушения БД как в LMDB.
> Для подробностей смотрите раздел
> [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации).
4. Возможность автоматического формирования контрольных точек
(сброса данных на диск) при накоплении заданного объёма изменений,
устанавливаемого функцией `mdbx_env_set_syncbytes()`.
5. Возможность получить отставание текущей транзакции чтения от
последней версии данных в БД посредством `mdbx_txn_straggler()`.
6. Утилита mdbx_chk для проверки БД и функция `mdbx_env_pgwalk()` для
обхода всех страниц БД.
7. Управление отладкой и получение отладочных сообщений посредством
`mdbx_setup_debug()`.
8. Возможность связать с каждой завершаемой транзакцией до 3
дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их
в транзакции чтения посредством `mdbx_canary_get()`.
9. Возможность узнать есть ли за текущей позицией курсора строка данных
посредством `mdbx_cursor_eof()`.
10. Возможность явно запросить обновление существующей записи, без
создания новой посредством флажка `MDBX_CURRENT` для `mdbx_put()`.
11. Возможность посредством `mdbx_replace()` обновить или удалить запись
с получением предыдущего значения данных, а также адресно изменить
конкретное multi-значение.
12. Поддержка ключей и значений нулевой длины, включая сортированные
дубликаты.
13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное
количество дубликатов для всех типов таблиц и любого положения курсора.
14. Возможность открыть БД в эксклюзивном режиме посредством
`mdbx_env_open_ex()`, например в целях её проверки.
15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и
формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`.
16. Возможность получить посредством `mdbx_env_info()` дополнительную
информацию, включая номер самой старой версии БД (снимка данных),
который используется одним из читателей.
17. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий)
аргумент `data` для таблиц без дубликатов (без флажка `MDBX_DUPSORT`), а
при его ненулевом значении всегда использует его для сверки с удаляемой
записью.
18. Возможность открыть dbi-таблицу, одновременно с установкой
компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`.
19. Возможность посредством `mdbx_is_dirty()` определить находятся ли
некоторый ключ или данные в "грязной" странице БД. Таким образом,
избегая лишнего копирования данных перед выполнением модифицирующих
операций (значения, размещенные в "грязных" страницах, могут быть
перезаписаны при изменениях, иначе они будут неизменны).
20. Корректное обновление текущей записи, в том числе сортированного
дубликата, при использовании режима `MDBX_CURRENT` в
`mdbx_cursor_put()`.
21. Все курсоры, как в транзакциях только для чтения, так и в пишущих,
могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ
ОСВОБОЖДАТЬСЯ ЯВНО.
>
> ## _ВАЖНО_, Обратите внимание!
>
> Это единственное изменение в API, которое значимо меняет
> семантику управления курсорами и может приводить к утечкам
> памяти. Следует отметить, что это изменение вынужденно.
> Так устраняется неоднозначность с массой тяжких последствий:
>
> - обращение к уже освобожденной памяти;
> - попытки повторного освобождения памяти;
> - повреждение памяти и ошибки сегментации.
22. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из
`mdbx_put()` и `mdbx_replace()` при попытке выполнить неоднозначное
обновление или удаления одного из нескольких значений с одним ключом.
23. Возможность посредством `mdbx_get_ex()` получить значение по
заданному ключу, одновременно с количеством дубликатов.
24. Наличие функций `mdbx_cursor_on_first()` и `mdbx_cursor_on_last()`,
которые позволяют быстро выяснить стоит ли курсор на первой/последней
позиции.
25. При завершении читающих транзакций, открытые в них DBI-хендлы не
закрываются и не теряются при завершении таких транзакций посредством
`mdbx_txn_abort()` или `mdbx_txn_reset()`. Что позволяет избавится от ряда
сложно обнаруживаемых ошибок.
26. Генерация последовательностей посредством `mdbx_dbi_sequence()`.
27. Расширенное динамическое управление размером БД, включая выбор
размера страницы посредством `mdbx_env_set_geometry()`,
в том числе в **Windows**
28. Три мета-страницы вместо двух, что позволяет гарантированно
консистентно обновлять слабые контрольные точки фиксации без риска
повредить крайнюю сильную точку фиксации.
29. В _libmdbx_ реализован автоматический возврат освобождающихся
страниц в область нераспределенного резерва в конце файла данных. При
этом уменьшается количество страниц загруженных в память и участвующих в
цикле обновления данных и записи на диск. Фактически _libmdbx_ выполняет
постоянную компактификацию данных, но не затрачивая на это
дополнительных ресурсов, а только освобождая их. При освобождении места
в БД и установке соответствующих параметров геометрии базы данных, также будет
уменьшаться размер файла на диске, в том числе в **Windows**.
--------------------------------------------------------------------------------
```
$ objdump -f -h -j .text libmdbx.so
@@ -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)
```

758
README.md
View File

@@ -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).
[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_ [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.
_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,331 +98,39 @@ 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.
1. Data is stored in ordered map, keys are always sorted, range lookups are supported.
2. Data is [mmaped](https://en.wikipedia.org/wiki/Memory-mapped_file) to
memory of each worker DB process, read transactions are zero-copy.
2. Data is [mmaped](https://en.wikipedia.org/wiki/Memory-mapped_file) to memory of each worker DB process, read transactions are zero-copy.
3. Transactions are
[ACID](https://en.wikipedia.org/wiki/ACID)-compliant, thanks to
[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)).
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"
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).
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.
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).
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.
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
======================
1. `mdbx_chk` tool for DB integrity check.
2. Automatic dynamic DB size management according to the parameters
specified by `mdbx_env_set_geometry()` function. Including including
growth step and truncation threshold, as well as the choice of page
size.
3. Automatic returning of freed pages into unallocated space at the end
of database file with optionally automatic shrinking it. This reduces
amount of pages resides in RAM and circulated in disk I/O. In fact
_libmdbx_ constantly performs DB compactification, without spending
additional resources for that.
4. Support for keys and values of zero length, including sorted
duplicates.
5. Ability to assign up to 3 markers to commiting transaction with
`mdbx_canary_put()` and then get them in read transaction by
`mdbx_canary_get()`.
6. Ability to update or delete record and get previous value via
`mdbx_replace()` Also can update specific multi-value.
7. `LIFO RECLAIM` mode:
The newest pages are picked for reuse instead of the oldest. This allows
to minimize reclaim loop and make it execution time independent of total
page count.
This results in OS kernel cache mechanisms working with maximum
efficiency. In case of using disk controllers or storages with
[BBWC](https://en.wikipedia.org/wiki/Disk_buffer#Write_acceleration)
this may greatly improve write performance.
8. Sequence generation via `mdbx_dbi_sequence()`.
9. `OOM-KICK` callback.
`mdbx_env_set_oomfunc()` allows to set a callback, which will be called
in the event of DB space exhausting during long-time read transaction in
parallel with extensive updating. Callback will be invoked with PID and
pthread_id of offending thread as parameters. Callback can do any of
these things to remedy the problem:
* wait for read transaction to finish normally;
* kill the offending process (signal 9), if separate process is doing
long-time read;
* abort or restart offending read transaction if it's running in sibling
thread;
* abort current write transaction with returning error code.
10. Ability to open DB in exclusive mode with `MDBX_EXCLUSIVE` flag.
11. Ability to get how far current read-only snapshot is from latest
version of the DB by `mdbx_txn_straggler()`.
12. Ability to explicitly request update of present record without
creating new record. Implemented as `MDBX_CURRENT` flag for
`mdbx_put()`.
13. Fixed `mdbx_cursor_count()`, which returns correct count of
duplicated for all table types and any cursor position.
14. `mdbx_env_info()` to getting additional info, including number of
the oldest snapshot of DB, which is used by one of the readers.
15. `mdbx_del()` doesn't ignore additional argument (specifier) `data`
for tables without duplicates (without flag `MDBX_DUPSORT`), if `data`
is not null then always uses it to verify record, which is being
deleted.
16. Ability to open dbi-table with simultaneous setup of comparators for
keys and values, via `mdbx_dbi_open_ex()`.
17. `mdbx_is_dirty()`to find out if key or value is on dirty page, that
useful to avoid copy-out before updates.
18. Correct update of current record in `MDBX_CURRENT` mode of
`mdbx_cursor_put()`, including sorted duplicated.
19. Check if there is a row with data after current cursor position via
`mdbx_cursor_eof()`.
20. Additional error code `MDBX_EMULTIVAL`, which is returned by
`mdbx_put()` and `mdbx_replace()` in case is ambiguous update or delete.
21. Ability to get value by key and duplicates count by `mdbx_get_ex()`.
22. Functions `mdbx_cursor_on_first()` and `mdbx_cursor_on_last()`,
which allows to know if cursor is currently on first or last position
respectively.
23. Automatic creation of synchronization points (flush changes to
persistent storage) when changes reach set threshold (threshold can be
set by `mdbx_env_set_syncbytes()`).
24. Control over debugging and receiving of debugging messages via
`mdbx_setup_debug()`.
25. Function `mdbx_env_pgwalk()` for page-walking all pages in DB.
26. Three meta-pages instead of two, this allows to guarantee
consistently update weak sync-points without risking to corrupt last
steady sync-point.
27. Guarantee of DB integrity in `WRITEMAP+MAPSYNC` mode:
> Current _libmdbx_ gives a choice of safe async-write mode (default)
> and `UTTERLY_NOSYNC` mode which may result in full
> DB corruption during system crash as with LMDB. For details see
> [Data safety in async-write mode](#data-safety-in-async-write-mode).
28. Ability to close DB in "dirty" state (without data flush and
creation of steady synchronization point) via `mdbx_env_close_ex()`.
29. If read transaction is aborted via `mdbx_txn_abort()` or
`mdbx_txn_reset()` then DBI-handles, which were opened in it, aren't
closed or deleted. This allows to avoid several types of hard-to-debug
errors.
30. All cursors in all read and write transactions can be reused by
`mdbx_cursor_renew()` and MUST be freed explicitly.
> ## Caution, please pay attention!
>
> This is the only change of API, which changes semantics of cursor management
> and can lead to memory leaks on misuse. This is a needed change as it eliminates ambiguity
> which helps to avoid such errors as:
> - use-after-free;
> - double-free;
> - memory corruption and segfaults.
--------------------------------------------------------------------------------
## Gotchas
1. At one moment there can be only one writer. But this allows to
serialize writes and eliminate any possibility of conflict or logical
errors during transaction rollback.
2. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) means
relatively big [WAF](https://en.wikipedia.org/wiki/Write_amplification)
(Write Amplification Factor). Because of this syncing data to disk might
be quite resource intensive and be main performance bottleneck during
intensive write workload.
> As compromise _libmdbx_ allows several modes of lazy and/or periodic
> syncing, including `MAPASYNC` mode, which modificate data in memory and
> asynchronously syncs data to disk, moment to sync is picked by OS.
>
> Although this should be used with care, synchronous transactions in a DB
> with transaction journal will require 2 IOPS minimum (probably 3-4 in
> practice) because of filesystem overhead, overhead depends on
> filesystem, not on record count or record size. In _libmdbx_ IOPS count
> will grow logarithmically depending on record count in DB (height of B+
> tree) and will require at least 2 IOPS per transaction too.
3. [CoW](https://en.wikipedia.org/wiki/Copy-on-write) for
[MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control)
is done on memory page level with
[B+trees](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE).
Therefore altering data requires to copy about Olog(N) memory pages,
which uses [memory bandwidth](https://en.wikipedia.org/wiki/Memory_bandwidth) and is main
performance bottleneck in `MAPASYNC` mode.
> This is unavoidable, but isn't that bad. Syncing data to disk requires
> much more similar operations which will be done by OS, therefore this is
> noticeable only if data sync to persistent storage is fully disabled.
> _libmdbx_ allows to safely save data to persistent storage with minimal
> performance overhead. If there is no need to save data to persistent
> storage then it's much more preferable to use `std::map`.
4. LMDB has a problem of long-time readers which degrades performance
and bloats DB.
> _libmdbx_ addresses that, details below.
5. _LMDB_ is susceptible to DB corruption in `WRITEMAP+MAPASYNC` mode.
_libmdbx_ in `WRITEMAP+MAPASYNC` guarantees DB integrity and consistency
of data.
> Additionally there is an alternative: `UTTERLY_NOSYNC` mode.
> Details below.
#### Long-time read transactions problem
Garbage collection problem exists in all databases one way or another
(e.g. VACUUM in PostgreSQL). But in _libmdbx_ and LMDB it's even more
important because of high performance and deliberate simplification of
internals with emphasis on performance.
* Altering data during long read operation may exhaust available space
on persistent storage.
* If available space is exhausted then any attempt to update data
results in `MAP_FULL` error until long read operation ends.
* Main examples of long readers is hot backup and debugging of client
application which actively uses read transactions.
* In _LMDB_ this results in degraded performance of all operations of
syncing data to persistent storage.
* _libmdbx_ has a mechanism which aborts such operations and `LIFO RECLAIM`
mode which addresses performance degradation.
Read operations operate only over snapshot of DB which is consistent on
the moment when read transaction started. This snapshot doesn't change
throughout the transaction but this leads to inability to reclaim the
pages until read transaction ends.
In _LMDB_ this leads to a problem that memory pages, allocated for
operations during long read, will be used for operations and won't be
reclaimed until DB process terminates. In _LMDB_ they are used in
[FIFO](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics))
manner, which causes increased page count and less chance of cache hit
during I/O. In other words: one long-time reader can impact performance
of all database until it'll be reopened.
_libmdbx_ addresses the problem, details below. Illustrations to this
problem can be found in the
[presentation](http://www.slideshare.net/leoyuriev/lmdb). There is also
example of performance increase thanks to
[BBWC](https://en.wikipedia.org/wiki/Disk_buffer#Write_acceleration)
when `LIFO RECLAIM` enabled in _libmdbx_.
#### Data safety in async-write mode
In `WRITEMAP+MAPSYNC` mode dirty pages are written to persistent storage
by kernel. This means that in case of application crash OS kernel will
write all dirty data to disk and nothing will be lost. But in case of
hardware malfunction or OS kernel fatal error only some dirty data might
be synced to disk, and there is high probability that pages with
metadata saved, will point to non-saved, hence non-existent, data pages.
In such situation, DB is completely corrupted and can't be repaired even
if there was full sync before the crash via `mdbx_env_sync().
_libmdbx_ addresses this by fully reimplementing write path of data:
* In `WRITEMAP+MAPSYNC` mode meta-data pages aren't updated in place,
instead their shadow copies are used and their updates are synced after
data is flushed to disk.
* During transaction commit _libmdbx_ marks synchronization points as
steady or weak depending on how much synchronization needed between RAM
and persistent storage, e.g. in `WRITEMAP+MAPSYNC` commited transactions
are marked as weak, but during explicit data synchronization - as
steady.
* _libmdbx_ maintains three separate meta-pages instead of two. This
allows to commit transaction with steady or weak synchronization point
without losing two previous synchronization points (one of them can be
steady, and second - weak). This allows to order weak and steady
synchronization points in any order without losing consistency in case
of system crash.
* During DB open _libmdbx_ rollbacks to the last steady synchronization
point, this guarantees database integrity.
For data safety pages which form database snapshot with steady
synchronization point must not be updated until next steady
synchronization point. So last steady synchronization point creates
"long-time read" effect. The only difference that in case of memory
exhaustion the problem will be immediately addressed by flushing changes
to persistent storage and forming new steady synchronization point.
So in async-write mode _libmdbx_ will always use new pages until memory
is exhausted or `mdbx_env_sync()` is invoked. Total disk usage will be
almost the same as in sync-write mode.
Current _libmdbx_ gives a choice of safe async-write mode (default) and
`UTTERLY_NOSYNC` mode which may result in full DB corruption during
system crash as with LMDB.
Next version of _libmdbx_ will create steady synchronization points
automatically in async-write mode.
--------------------------------------------------------------------------------
Performance comparison
======================
=====================
All benchmarks were done by [IOArena](https://github.com/pmwkaa/ioarena)
and multiple [scripts](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015)
@@ -455,21 +143,18 @@ SSD SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Gb.
Here showed sum of performance metrics in 3 benchmarks:
- Read/Search on 4 CPU cores machine;
- Read/Search on 4 CPU cores machine;
- Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD)
operations in sync-write mode (fdatasync is called after each
transaction);
- Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD) operations
in sync-write mode (fdatasync is called after each transaction);
- 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).
- 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).
*Reasons why asynchronous mode isn't benchmarked here:*
1. It doesn't make sense as it has to be done with DB engines, oriented
for keeping data in memory e.g. [Tarantool](https://tarantool.io/),
[Redis](https://redis.io/)), etc.
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.
2. Performance gap is too high to compare in any meaningful way.
@@ -479,8 +164,7 @@ Here showed sum of performance metrics in 3 benchmarks:
### Read Scalability
Summary performance with concurrent read/search queries in 1-2-4-8
threads on 4 CPU cores machine.
Summary performance with concurrent read/search queries in 1-2-4-8 threads on 4 CPU cores machine.
![Comparison #2: Read Scalability](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-2.png)
@@ -488,21 +172,15 @@ threads on 4 CPU cores machine.
### Sync-write mode
- Linear scale on left and dark rectangles mean arithmetic mean
transactions per second;
- Linear scale on left and dark rectangles mean arithmetic mean transactions per second;
- Logarithmic scale on right is in seconds and yellow intervals mean
execution time of transactions. Each interval shows minimal and maximum
execution time, cross marks standard deviation.
- 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.
**10,000 transactions in sync-write mode**. In case of a crash all data
is consistent and state is right after last successful transaction.
[fdatasync](https://linux.die.net/man/2/fdatasync) syscall is used after
each write transaction in this mode.
**10,000 transactions in sync-write mode**. In case of a crash all data is consistent and state is right after last successful transaction. [fdatasync](https://linux.die.net/man/2/fdatasync) syscall is used after each write transaction in this mode.
In the benchmark each transaction contains combined CRUD operations (2
inserts, 1 read, 1 update, 1 delete). Benchmark starts on empty database
and after full run the database contains 10,000 small key-value records.
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.
![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-3.png)
@@ -510,25 +188,18 @@ and after full run the database contains 10,000 small key-value records.
### Lazy-write mode
- Linear scale on left and dark rectangles mean arithmetic mean of
thousands transactions per second;
- Linear scale on left and dark rectangles mean arithmetic mean of thousands transactions per second;
- Logarithmic scale on right in seconds and yellow intervals mean
execution time of transactions. Each interval shows minimal and maximum
execution time, cross marks standard deviation.
- Logarithmic scale on right in seconds and yellow intervals mean execution time of transactions. Each interval shows minimal and maximum execution time, cross marks standard deviation.
**100,000 transactions in lazy-write mode**. 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
**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).
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.
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.
![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-4.png)
@@ -537,23 +208,14 @@ records.
### Async-write mode
- Linear scale on left and dark rectangles mean arithmetic mean of
thousands transactions per second;
- Linear scale on left and dark rectangles mean arithmetic mean of thousands transactions per second;
- Logarithmic scale on right in seconds and yellow intervals mean
execution time of transactions. Each interval shows minimal and maximum
execution time, cross marks standard deviation.
- Logarithmic scale on right in seconds and yellow intervals mean execution time of transactions. Each interval shows minimal and maximum execution time, cross marks standard deviation.
**1,000,000 transactions in async-write mode**. In case of a crash all
data will be consistent and state will be right after one of last
transactions, but lost transaction count is much higher than in
lazy-write mode. All DB engines in this mode do as little writes as
possible on persistent storage. _libmdbx_ uses
[msync(MS_ASYNC)](https://linux.die.net/man/2/msync) in this mode.
**1,000,000 transactions in async-write mode**. In case of a crash all data will be consistent and state will be right after one of last transactions, but lost transaction count is much higher than in lazy-write mode. All DB engines in this mode do as little writes as possible on persistent storage. _libmdbx_ uses [msync(MS_ASYNC)](https://linux.die.net/man/2/msync) in this mode.
In the benchmark each transaction contains combined CRUD operations (2
inserts, 1 read, 1 update, 1 delete). Benchmark starts on empty database
and after full run the database contains 10,000 small key-value records.
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.
![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-5.png)
@@ -567,22 +229,237 @@ Summary of used resources during lazy-write mode benchmarks:
- Sum of user CPU time and sys CPU time;
- Used space on persistent storage after the test and closed DB, but not
waiting for the end of all internal housekeeping operations (LSM
compactification, etc).
- 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).
_ForestDB_ is excluded because benchmark showed it's resource
consumption for each resource (CPU, IOPS) much higher than other engines
which prevents to meaningfully compare it with them.
_ForestDB_ is excluded because benchmark showed it's resource consumption for each resource (CPU, IOPS) much higher than other engines which prevents to meaningfully compare it with them.
All benchmark data is gathered by
[getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html)
syscall and by scanning data directory.
All benchmark data is gathered by [getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) syscall and by
scanning data directory.
![Comparison #6: Cost comparison](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-6.png)
--------------------------------------------------------------------------------
## Gotchas
1. At one moment there can be only one writer. But this allows to serialize writes and eliminate any possibility
of conflict or logical errors during transaction rollback.
2. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) means relatively
big [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write Amplification Factor).
Because of this syncing data to disk might be quite resource intensive and be main performance bottleneck
during intensive write workload.
> As compromise _libmdbx_ allows several modes of lazy and/or periodic syncing, including `MAPASYNC` mode, which modificate
> data in memory and asynchronously syncs data to disk, moment to sync is picked by OS.
>
> Although this should be used with care, synchronous transactions in a DB with transaction journal will require 2 IOPS
> minimum (probably 3-4 in practice) because of filesystem overhead, overhead depends on filesystem, not on record
> count or record size. In _libmdbx_ IOPS count will grow logarithmically depending on record count in DB (height of B+ tree)
> and will require at least 2 IOPS per transaction too.
3. [CoW](https://en.wikipedia.org/wiki/Copy-on-write)
for [MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) is done on memory page level with [B+
trees](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE).
Therefore altering data requires to copy about Olog(N) memory pages, which uses [memory bandwidth](https://en.wikipedia.org/wiki/Memory_bandwidth) and is main performance bottleneck in `MAPASYNC` mode.
> This is unavoidable, but isn't that bad. Syncing data to disk requires much more similar operations which will
> be done by OS, therefore this is noticeable only if data sync to persistent storage is fully disabled.
> _libmdbx_ allows to safely save data to persistent storage with minimal performance overhead. If there is no need
> to save data to persistent storage then it's much more preferable to use `std::map`.
4. LMDB has a problem of long-time readers which degrades performance and bloats DB
> _libmdbx_ addresses that, details below.
5. _LMDB_ is susceptible to DB corruption in `WRITEMAP+MAPASYNC` mode.
_libmdbx_ in `WRITEMAP+MAPASYNC` guarantees DB integrity and consistency of data.
> Additionally there is an alternative: `UTTERLY_NOSYNC` mode. Details below.
#### Long-time read transactions problem
Garbage collection problem exists in all databases one way or another (e.g. VACUUM in PostgreSQL).
But in _libmdbx_ and LMDB it's even more important because of high performance and deliberate
simplification of internals with emphasis on performance.
* Altering data during long read operation may exhaust available space on persistent storage.
* If available space is exhausted then any attempt to update data
results in `MAP_FULL` error until long read operation ends.
* Main examples of long readers is hot backup
and debugging of client application which actively uses read transactions.
* In _LMDB_ this results in degraded performance of all operations
of syncing data to persistent storage.
* _libmdbx_ has a mechanism which aborts such operations and `LIFO RECLAIM`
mode which addresses performance degradation.
Read operations operate only over snapshot of DB which is consistent on the moment when read transaction started.
This snapshot doesn't change throughout the transaction but this leads to inability to reclaim the pages until
read transaction ends.
In _LMDB_ this leads to a problem that memory pages, allocated for operations during long read, will be used for operations
and won't be reclaimed until DB process terminates. In _LMDB_ they are used in
[FIFO](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)) manner, which causes increased page count
and less chance of cache hit during I/O. In other words: one long-time reader can impact performance of all database
until it'll be reopened.
_libmdbx_ addresses the problem, details below. Illustrations to this problem can be found in the
[presentation](http://www.slideshare.net/leoyuriev/lmdb). There is also example of performance increase thanks to
[BBWC](https://en.wikipedia.org/wiki/Disk_buffer#Write_acceleration) when `LIFO RECLAIM` enabled in _libmdbx_.
#### Data safety in async-write mode
In `WRITEMAP+MAPSYNC` mode dirty pages are written to persistent storage by kernel. This means that in case of application
crash OS kernel will write all dirty data to disk and nothing will be lost. But in case of hardware malfunction or OS kernel
fatal error only some dirty data might be synced to disk, and there is high probability that pages with metadata saved,
will point to non-saved, hence non-existent, data pages. In such situation, DB is completely corrupted and can't be
repaired even if there was full sync before the crash via `mdbx_env_sync().
_libmdbx_ addresses this by fully reimplementing write path of data:
* In `WRITEMAP+MAPSYNC` mode meta-data pages aren't updated in place, instead their shadow copies are used and their updates
are synced after data is flushed to disk.
* During transaction commit _libmdbx_ marks synchronization points as steady or weak depending on how much synchronization
needed between RAM and persistent storage, e.g. in `WRITEMAP+MAPSYNC` commited transactions are marked as weak,
but during explicit data synchronization - as steady.
* _libmdbx_ maintains three separate meta-pages instead of two. This allows to commit transaction with steady or
weak synchronization point without losing two previous synchronization points (one of them can be steady, and second - weak).
This allows to order weak and steady synchronization points in any order without losing consistency in case of system crash.
* During DB open _libmdbx_ rollbacks to the last steady synchronization point, this guarantees database integrity.
For data safety pages which form database snapshot with steady synchronization point must not be updated until next steady
synchronization point. So last steady synchronization point creates "long-time read" effect. The only difference that in case
of memory exhaustion the problem will be immediately addressed by flushing changes to persistent storage and forming new steady
synchronization point.
So in async-write mode _libmdbx_ will always use new pages until memory is exhausted or `mdbx_env_sync()` is invoked. Total
disk usage will be almost the same as in sync-write mode.
Current _libmdbx_ gives a choice of safe async-write mode (default) and `UTTERLY_NOSYNC` mode which may result in full DB
corruption during system crash as with LMDB.
Next version of _libmdbx_ will create steady synchronization points automatically in async-write mode.
--------------------------------------------------------------------------------
Improvements over LMDB
================================================
1. `LIFO RECLAIM` mode:
The newest pages are picked for reuse instead of the oldest.
This allows to minimize reclaim loop and make it execution time independent of total page count.
This results in OS kernel cache mechanisms working with maximum efficiency.
In case of using disk controllers or storages with
[BBWC](https://en.wikipedia.org/wiki/Disk_buffer#Write_acceleration) this may greatly improve
write performance.
2. `OOM-KICK` callback.
`mdbx_env_set_oomfunc()` allows to set a callback, which will be called
in the event of memory exhausting during long-time read transaction.
Callback will be invoked with PID and pthread_id of offending thread as parameters.
Callback can do any of these things to remedy the problem:
* wait for read transaction to finish normally;
* kill the offending process (signal 9), if separate process is doing long-time read;
* abort or restart offending read transaction if it's running in sibling thread;
* abort current write transaction with returning error code.
3. Guarantee of DB integrity in `WRITEMAP+MAPSYNC` mode:
> Current _libmdbx_ gives a choice of safe async-write mode (default)
> and `UTTERLY_NOSYNC` mode which may result in full
> DB corruption during system crash as with LMDB. For details see
> [Data safety in async-write mode](#data-safety-in-async-write-mode).
4. Automatic creation of synchronization points (flush changes to persistent storage)
when changes reach set threshold (threshold can be set by `mdbx_env_set_syncbytes()`).
5. Ability to get how far current read-only snapshot is from latest version of the DB by `mdbx_txn_straggler()`.
6. `mdbx_chk` tool for DB checking and `mdbx_env_pgwalk()` for page-walking all pages in DB.
7. Control over debugging and receiving of debugging messages via `mdbx_setup_debug()`.
8. Ability to assign up to 3 markers to commiting transaction with `mdbx_canary_put()` and then get them in read transaction
by `mdbx_canary_get()`.
9. Check if there is a row with data after current cursor position via `mdbx_cursor_eof()`.
10. Ability to explicitly request update of present record without creating new record. Implemented as `MDBX_CURRENT` flag
for `mdbx_put()`.
11. Ability to update or delete record and get previous value via `mdbx_replace()` Also can update specific multi-value.
12. Support for keys and values of zero length, including sorted duplicates.
13. Fixed `mdbx_cursor_count()`, which returns correct count of duplicated for all table types and any cursor position.
14. Ability to open DB in exclusive mode via `mdbx_env_open_ex()`, e.g. for integrity check.
15. Ability to close DB in "dirty" state (without data flush and creation of steady synchronization point)
via `mdbx_env_close_ex()`.
16. Ability to get additional info, including number of the oldest snapshot of DB, which is used by one of the readers.
Implemented via `mdbx_env_info()`.
17. `mdbx_del()` doesn't ignore additional argument (specifier) `data`
for tables without duplicates (without flag `MDBX_DUPSORT`), if `data` is not zero then always uses it to verify
record, which is being deleted.
18. Ability to open dbi-table with simultaneous setup of comparators for keys and values, via `mdbx_dbi_open_ex()`.
19. Ability to find out if key or value is in dirty page. This may be useful to make a decision to avoid
excessive CoW before updates. Implemented via `mdbx_is_dirty()`.
20. Correct update of current record in `MDBX_CURRENT` mode of `mdbx_cursor_put()`, including sorted duplicated.
21. All cursors in all read and write transactions can be reused by `mdbx_cursor_renew()` and MUST be freed explicitly.
> ## Caution, please pay attention!
>
> This is the only change of API, which changes semantics of cursor management
> and can lead to memory leaks on misuse. This is a needed change as it eliminates ambiguity
> which helps to avoid such errors as:
> - use-after-free;
> - double-free;
> - memory corruption and segfaults.
22. Additional error code `MDBX_EMULTIVAL`, which is returned by `mdbx_put()` and
`mdbx_replace()` in case is ambiguous update or delete.
23. Ability to get value by key and duplicates count by `mdbx_get_ex()`.
24. Functions `mdbx_cursor_on_first() and mdbx_cursor_on_last(), which allows to know if cursor is currently on first or
last position respectively.
25. If read transaction is aborted via `mdbx_txn_abort()` or `mdbx_txn_reset()` then DBI-handles, which were opened in it,
aren't closed or deleted. This allows to avoid several types of hard-to-debug errors.
26. Sequence generation via `mdbx_dbi_sequence()`.
27. Advanced dynamic control over DB size, including ability to choose page size via `mdbx_env_set_geometry()`,
including on Windows.
28. Three meta-pages instead of two, this allows to guarantee consistently update weak sync-points without risking to
corrupt last steady sync-point.
29. Automatic reclaim of freed pages to specific reserved space at the end of database file. This lowers amount of pages,
loaded to memory, used in update/flush loop. In fact _libmdbx_ constantly performs compactification of data,
but doesn't use additional resources for that. Space reclaim of DB and setup of database geometry parameters also decreases
size of the database on disk, including on Windows.
--------------------------------------------------------------------------------
```
$ objdump -f -h -j .text libmdbx.so
@@ -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)
```

89
TODO.md Normal file
View File

@@ -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] Инкрементальное приращение размера (колбэк стратегии?).

View File

@@ -1,4 +1,4 @@
version: 0.2.0.{build}
version: 0.1.6.{build}
environment:
matrix:

58
mdbx.h
View File

@@ -100,7 +100,6 @@ typedef DWORD mdbx_tid_t;
#define MDBX_EIO ERROR_WRITE_FAULT
#define MDBX_EPERM ERROR_INVALID_FUNCTION
#define MDBX_EINTR ERROR_CANCELLED
#define MDBX_ENOFILE ERROR_FILE_NOT_FOUND
#else
@@ -121,8 +120,6 @@ typedef pthread_t mdbx_tid_t;
#define MDBX_EIO EIO
#define MDBX_EPERM EPERM
#define MDBX_EINTR EINTR
#define MDBX_ENOFILE ENOENT
#endif
#ifdef _MSC_VER
@@ -168,7 +165,7 @@ typedef pthread_t mdbx_tid_t;
/*--------------------------------------------------------------------------*/
#define MDBX_VERSION_MAJOR 0
#define MDBX_VERSION_MINOR 2
#define MDBX_VERSION_MINOR 1
#if defined(LIBMDBX_EXPORTS)
#define LIBMDBX_API __dll_export
@@ -206,24 +203,6 @@ typedef struct mdbx_build_info {
extern LIBMDBX_API const mdbx_version_info mdbx_version;
extern LIBMDBX_API const mdbx_build_info mdbx_build;
#if defined(_WIN32) || defined(_WIN64)
/* Dll initialization callback for ability to dynamically load MDBX DLL by
* LoadLibrary() on Windows versions before Windows Vista. This function MUST be
* called once from DllMain() for each reason (DLL_PROCESS_ATTACH,
* DLL_PROCESS_DETACH, DLL_THREAD_ATTACH and DLL_THREAD_DETACH). Do this
* carefully and ONLY when actual Windows version don't support initialization
* via "TLS Directory" (e.g .CRT$XL[A-Z] sections in executable or dll file). */
#ifndef MDBX_CONFIG_MANUAL_TLS_CALLBACK
#define MDBX_CONFIG_MANUAL_TLS_CALLBACK 0
#endif
#if MDBX_CONFIG_MANUAL_TLS_CALLBACK
void LIBMDBX_API NTAPI mdbx_dll_callback(PVOID module, DWORD reason,
PVOID reserved);
#endif /* MDBX_CONFIG_MANUAL_TLS_CALLBACK */
#endif /* Windows */
/* The name of the lock file in the DB environment */
#define MDBX_LOCKNAME "/mdbx.lck"
/* The name of the data file in the DB environment */
@@ -291,8 +270,9 @@ typedef int(MDBX_cmp_func)(const MDBX_val *a, const MDBX_val *b);
#define MDBX_MAPASYNC 0x100000u
/* tie reader locktable slots to MDBX_txn objects instead of to threads */
#define MDBX_NOTLS 0x200000u
/* open DB in exclusive/monopolistic mode. */
#define MDBX_EXCLUSIVE 0x400000u
/* don't do any locking, caller must manage their own locks
* WARNING: libmdbx don't support this mode. */
#define MDBX_NOLOCK__UNSUPPORTED 0x400000u
/* don't do readahead */
#define MDBX_NORDAHEAD 0x800000u
/* don't initialize malloc'd memory before writing to datafile */
@@ -672,6 +652,8 @@ LIBMDBX_API int mdbx_env_create(MDBX_env **penv);
* - MDBX_EAGAIN - the environment was locked by another process. */
LIBMDBX_API int mdbx_env_open(MDBX_env *env, const char *path, unsigned flags,
mode_t mode);
LIBMDBX_API int mdbx_env_open_ex(MDBX_env *env, const char *path,
unsigned flags, mode_t mode, int *exclusive);
/* Copy an MDBX environment to the specified path, with options.
*
@@ -698,8 +680,7 @@ LIBMDBX_API int mdbx_env_open(MDBX_env *env, const char *path, unsigned flags,
* NOTE: Currently it fails if the environment has suffered a page leak.
*
* Returns A non-zero error value on failure and 0 on success. */
LIBMDBX_API int mdbx_env_copy(MDBX_env *env, const char *dest_path,
unsigned flags);
LIBMDBX_API int mdbx_env_copy(MDBX_env *env, const char *path, unsigned flags);
/* Copy an MDBX environment to the specified file descriptor,
* with options.
@@ -919,6 +900,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(intptr_t pagesize);
/* Set application information associated with the MDBX_env.
*
@@ -1246,8 +1228,6 @@ LIBMDBX_API int mdbx_drop(MDBX_txn *txn, MDBX_dbi dbi, int del);
* - MDBX_EINVAL - an invalid parameter was specified. */
LIBMDBX_API int mdbx_get(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key,
MDBX_val *data);
LIBMDBX_API int mdbx_get2(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key,
MDBX_val *data);
/* Store items into a database.
*
@@ -1653,22 +1633,10 @@ typedef void MDBX_debug_func(int type, const char *function, int line,
LIBMDBX_API int mdbx_setup_debug(int flags, MDBX_debug_func *logger);
typedef enum {
MDBX_page_void,
MDBX_page_meta,
MDBX_page_large,
MDBX_page_branch,
MDBX_page_leaf,
MDBX_page_dupfixed_leaf,
MDBX_subpage_leaf,
MDBX_subpage_dupfixed_leaf
} MDBX_page_type_t;
typedef int MDBX_pgvisitor_func(uint64_t pgno, unsigned number, void *ctx,
int deep, const char *dbi, size_t page_size,
MDBX_page_type_t type, size_t nentries,
size_t payload_bytes, size_t header_bytes,
size_t unused_bytes);
typedef int MDBX_pgvisitor_func(uint64_t pgno, unsigned pgnumber, void *ctx,
const char *dbi, const char *type,
size_t nentries, size_t payload_bytes,
size_t header_bytes, size_t unused_bytes);
LIBMDBX_API int mdbx_env_pgwalk(MDBX_txn *txn, MDBX_pgvisitor_func *visitor,
void *ctx);
@@ -1712,8 +1680,6 @@ 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 */

View File

@@ -34,9 +34,6 @@
#endif
#ifdef _MSC_VER
# if _MSC_VER < 1400
# error "Microsoft Visual C++ 8.0 (Visual Studio 2005) or later version is required"
# endif
# ifndef _CRT_SECURE_NO_WARNINGS
# define _CRT_SECURE_NO_WARNINGS
# endif
@@ -120,12 +117,6 @@
/* *INDENT-ON* */
/* clang-format on */
#if UINTPTR_MAX > 0xffffFFFFul || ULONG_MAX > 0xffffFFFFul
#define MDBX_WORDBITS 64
#else
#define MDBX_WORDBITS 32
#endif /* MDBX_WORDBITS */
/*----------------------------------------------------------------------------*/
/* Basic constants and types */
@@ -400,11 +391,7 @@ typedef struct MDBX_page {
#endif
#define MAX_MAPSIZE64 (MAX_PAGENO * (uint64_t)MAX_PAGESIZE)
#if MDBX_WORDBITS >= 64
#define MAX_MAPSIZE MAX_MAPSIZE64
#else
#define MAX_MAPSIZE MAX_MAPSIZE32
#endif /* MDBX_WORDBITS */
#define MAX_MAPSIZE ((sizeof(size_t) < 8) ? MAX_MAPSIZE32 : MAX_MAPSIZE64)
/* The header for the reader table (a memory-mapped lock file). */
typedef struct MDBX_lockinfo {
@@ -479,10 +466,6 @@ typedef struct MDBX_lockinfo {
#define MDBX_LOCK_MAGIC ((MDBX_MAGIC << 8) + MDBX_LOCK_VERSION)
#ifndef MDBX_ASSUME_MALLOC_OVERHEAD
#define MDBX_ASSUME_MALLOC_OVERHEAD (sizeof(void *) * 2u)
#endif /* MDBX_ASSUME_MALLOC_OVERHEAD */
/*----------------------------------------------------------------------------*/
/* Two kind lists of pages (aka PNL) */
@@ -504,46 +487,35 @@ typedef pgno_t *MDBX_PNL;
/* List of txnid, only for MDBX_env.mt_lifo_reclaimed */
typedef txnid_t *MDBX_TXL;
/* An Dirty-Page list item is an pgno/pointer pair. */
typedef union MDBX_DP {
struct {
pgno_t pgno;
void *ptr;
};
struct {
pgno_t unused;
unsigned length;
};
} MDBX_DP;
/* An ID2 is an ID/pointer pair. */
typedef struct MDBX_ID2 {
pgno_t mid; /* The ID */
void *mptr; /* The pointer */
} MDBX_ID2;
/* An DPL (dirty-page list) is a sorted array of MDBX_DPs.
* The first element's length member is a count of how many actual
* elements are in the array. */
typedef MDBX_DP *MDBX_DPL;
/* An ID2L is an ID2 List, a sorted array of ID2s.
* The first element's mid member is a count of how many actual
* elements are in the array. The mptr member of the first element is
* unused. The array is sorted in ascending order by mid. */
typedef MDBX_ID2 *MDBX_ID2L;
/* PNL sizes - likely should be even bigger */
#define MDBX_PNL_GRANULATE 1024
#define MDBX_PNL_INITIAL \
(MDBX_PNL_GRANULATE - 2 - MDBX_ASSUME_MALLOC_OVERHEAD / sizeof(pgno_t))
#define MDBX_PNL_MAX \
((1u << 24) - 2 - MDBX_ASSUME_MALLOC_OVERHEAD / sizeof(pgno_t))
#define MDBX_DPL_TXNFULL (MDBX_PNL_MAX / 4)
/* PNL sizes - likely should be even bigger
* limiting factors: sizeof(pgno_t), thread stack size */
#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_TXL_GRANULATE 32
#define MDBX_TXL_INITIAL \
(MDBX_TXL_GRANULATE - 2 - MDBX_ASSUME_MALLOC_OVERHEAD / sizeof(txnid_t))
#define MDBX_TXL_MAX \
((1u << 17) - 2 - MDBX_ASSUME_MALLOC_OVERHEAD / sizeof(txnid_t))
#define MDBX_PNL_DB_MAX (MDBX_PNL_DB_SIZE - 1)
#define MDBX_PNL_UM_MAX (MDBX_PNL_UM_SIZE - 1)
#define MDBX_PNL_ALLOCLEN(pl) ((pl)[-1])
#define MDBX_PNL_SIZE(pl) ((pl)[0])
#define MDBX_PNL_SIZEOF(pl) (((pl)[0] + 1) * sizeof(pgno_t))
#define MDBX_PNL_IS_ZERO(pl) ((pl)[0] == 0)
#define MDBX_PNL_CPY(dst, src) (memcpy(dst, src, MDBX_PNL_SIZEOF(src)))
#define MDBX_PNL_FIRST(pl) ((pl)[1])
#define MDBX_PNL_LAST(pl) ((pl)[MDBX_PNL_SIZE(pl)])
#define MDBX_PNL_BEGIN(pl) (&(pl)[1])
#define MDBX_PNL_END(pl) (&(pl)[MDBX_PNL_SIZE(pl) + 1])
#define MDBX_PNL_LAST(pl) ((pl)[(pl)[0]])
#define MDBX_PNL_SIZEOF(pl) ((MDBX_PNL_SIZE(pl) + 1) * sizeof(pgno_t))
#define MDBX_PNL_IS_EMPTY(pl) (MDBX_PNL_SIZE(pl) == 0)
/* Current max length of an mdbx_pnl_alloc()ed PNL */
#define MDBX_PNL_ALLOCLEN(pl) ((pl)[-1])
/*----------------------------------------------------------------------------*/
/* Internal structures */
@@ -587,7 +559,7 @@ struct MDBX_txn {
MDBX_PNL mt_spill_pages;
union {
/* For write txns: Modified pages. Sorted when not MDBX_WRITEMAP. */
MDBX_DPL mt_rw_dirtylist;
MDBX_ID2L mt_rw_dirtylist;
/* For read txns: This thread/txn's reader table slot, or NULL. */
MDBX_reader *mt_ro_reader;
};
@@ -685,9 +657,8 @@ struct MDBX_cursor {
#define C_EOF 0x02 /* No more data */
#define C_SUB 0x04 /* Cursor is a sub-cursor */
#define C_DEL 0x08 /* last op was a cursor_del */
#define C_UNTRACK 0x10 /* Un-track cursor when closing */
#define C_RECLAIMING 0x20 /* FreeDB lookup is prohibited */
#define C_GCFREEZE 0x40 /* me_reclaimed_pglist must not be updated */
#define C_UNTRACK 0x40 /* Un-track cursor when closing */
#define C_RECLAIMING 0x80 /* FreeDB lookup is prohibited */
unsigned mc_flags; /* see mdbx_cursor */
MDBX_page *mc_pg[CURSOR_STACK]; /* stack of pushed pages */
indx_t mc_ki[CURSOR_STACK]; /* stack of page indices */
@@ -708,11 +679,6 @@ typedef struct MDBX_xcursor {
uint8_t mx_dbflag;
} MDBX_xcursor;
typedef struct MDBX_cursor_couple {
MDBX_cursor outer;
MDBX_xcursor inner;
} MDBX_cursor_couple;
/* Check if there is an inited xcursor, so XCURSOR_REFRESH() is proper */
#define XCURSOR_INITED(mc) \
((mc)->mc_xcursor && ((mc)->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED))
@@ -763,17 +729,14 @@ struct MDBX_env {
/* Max MDBX_lockinfo.mti_numreaders of interest to mdbx_env_close() */
unsigned me_close_readers;
mdbx_fastmutex_t me_dbi_lock;
MDBX_dbi me_numdbs; /* number of DBs opened */
MDBX_dbi me_maxdbs; /* size of the DB table */
mdbx_pid_t me_pid; /* process ID of this env */
mdbx_thread_key_t me_txkey; /* thread-key for readers */
char *me_path; /* path to the DB files */
void *me_pbuf; /* scratch area for DUPSORT put() */
MDBX_txn *me_txn; /* current write transaction */
MDBX_txn *me_txn0; /* prealloc'd write transaction */
#ifdef MDBX_OSAL_LOCK
MDBX_OSAL_LOCK *me_wmutex; /* write-txn mutex */
#endif
MDBX_dbi me_numdbs; /* number of DBs opened */
MDBX_dbi me_maxdbs; /* size of the DB table */
mdbx_pid_t me_pid; /* process ID of this env */
mdbx_thread_key_t me_txkey; /* thread-key for readers */
char *me_path; /* path to the DB files */
void *me_pbuf; /* scratch area for DUPSORT put() */
MDBX_txn *me_txn; /* current write transaction */
MDBX_txn *me_txn0; /* prealloc'd write transaction */
MDBX_dbx *me_dbxs; /* array of static DB info */
uint16_t *me_dbflags; /* array of flags from MDBX_db.md_flags */
unsigned *me_dbiseqs; /* array of dbi sequence numbers */
@@ -784,8 +747,8 @@ 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;
/* MDBX_DP of pages written during a write txn. Length MDBX_DPL_TXNFULL. */
MDBX_DPL me_dirtylist;
/* 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 size of a node on a page */
@@ -814,11 +777,10 @@ struct MDBX_env {
} me_dbgeo; /* */
#if defined(_WIN32) || defined(_WIN64)
MDBX_srwlock me_remap_guard;
SRWLOCK me_remap_guard;
/* Workaround for LockFileEx and WriteFile multithread bug */
CRITICAL_SECTION me_windowsbug_lock;
#else
mdbx_fastmutex_t me_lckless_wmutex;
mdbx_fastmutex_t me_remap_guard;
#endif
};
@@ -853,14 +815,11 @@ void mdbx_panic(const char *fmt, ...)
#define mdbx_assert_enabled() unlikely(mdbx_runtime_flags &MDBX_DBG_ASSERT)
#define mdbx_audit_enabled() unlikely(mdbx_runtime_flags &MDBX_DBG_AUDIT)
#define mdbx_debug_enabled(type) \
unlikely(mdbx_runtime_flags &(type & (MDBX_DBG_TRACE | MDBX_DBG_EXTRA)))
#else
#define mdbx_debug_enabled(type) (0)
#define mdbx_audit_enabled() (0)
#if !defined(NDEBUG) || defined(MDBX_FORCE_ASSERT)
#define mdbx_assert_enabled() (1)
#else
@@ -1031,7 +990,7 @@ void mdbx_rthc_thread_dtor(void *ptr);
#define NUMKEYS(p) ((unsigned)(p)->mp_lower >> 1)
/* The amount of space remaining in the page */
#define SIZELEFT(p) ((indx_t)((p)->mp_upper - (p)->mp_lower))
#define SIZELEFT(p) (indx_t)((p)->mp_upper - (p)->mp_lower)
/* The percentage of space used in the page, in tenths of a percent. */
#define PAGEFILL(env, p) \
@@ -1042,17 +1001,15 @@ void mdbx_rthc_thread_dtor(void *ptr);
#define FILL_THRESHOLD 256
/* Test if a page is a leaf page */
#define IS_LEAF(p) (((p)->mp_flags & P_LEAF) != 0)
#define IS_LEAF(p) F_ISSET((p)->mp_flags, P_LEAF)
/* Test if a page is a LEAF2 page */
#define IS_LEAF2(p) unlikely(((p)->mp_flags & P_LEAF2) != 0)
#define IS_LEAF2(p) F_ISSET((p)->mp_flags, P_LEAF2)
/* Test if a page is a branch page */
#define IS_BRANCH(p) (((p)->mp_flags & P_BRANCH) != 0)
#define IS_BRANCH(p) F_ISSET((p)->mp_flags, P_BRANCH)
/* Test if a page is an overflow page */
#define IS_OVERFLOW(p) unlikely(((p)->mp_flags & P_OVERFLOW) != 0)
#define IS_OVERFLOW(p) unlikely(F_ISSET((p)->mp_flags, P_OVERFLOW))
/* Test if a page is a sub page */
#define IS_SUBP(p) (((p)->mp_flags & P_SUBP) != 0)
/* Test if a page is dirty */
#define IS_DIRTY(p) (((p)->mp_flags & P_DIRTY) != 0)
#define IS_SUBP(p) F_ISSET((p)->mp_flags, P_SUBP)
#define PAGETYPE(p) ((p)->mp_flags & (P_BRANCH | P_LEAF | P_LEAF2 | P_OVERFLOW))

View File

@@ -103,6 +103,10 @@
/*----------------------------------------------------------------------------*/
#if !defined(__thread) && (defined(_MSC_VER) || defined(__DMC__))
# define __thread __declspec(thread)
#endif /* __thread */
#ifndef __alwaysinline
# if defined(__GNUC__) || __has_attribute(always_inline)
# define __alwaysinline __inline __attribute__((always_inline))
@@ -327,13 +331,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)

View File

@@ -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;
@@ -94,17 +94,14 @@ static __inline int mdbx_lck_shared(int lfd) {
}
int mdbx_lck_downgrade(MDBX_env *env, bool complete) {
assert(env->me_lfd != INVALID_HANDLE_VALUE);
return complete ? mdbx_lck_shared(env->me_lfd) : MDBX_SUCCESS;
}
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);
}
int mdbx_rpid_clear(MDBX_env *env) {
assert(env->me_lfd != INVALID_HANDLE_VALUE);
return mdbx_lck_op(env->me_lfd, F_SETLKW, F_UNLCK, env->me_pid, 1);
}
@@ -115,7 +112,6 @@ int mdbx_rpid_clear(MDBX_env *env) {
* MDBX_RESULT_FALSE, if pid is dead (lock acquired)
* or otherwise the errcode. */
int mdbx_rpid_check(MDBX_env *env, mdbx_pid_t pid) {
assert(env->me_lfd != INVALID_HANDLE_VALUE);
int rc = mdbx_lck_op(env->me_lfd, F_GETLK, F_WRLCK, pid, 1);
if (rc == 0)
return MDBX_RESULT_FALSE;
@@ -128,7 +124,7 @@ int mdbx_rpid_check(MDBX_env *env, mdbx_pid_t pid) {
static int mdbx_mutex_failed(MDBX_env *env, pthread_mutex_t *mutex, int rc);
int __cold mdbx_lck_init(MDBX_env *env) {
int mdbx_lck_init(MDBX_env *env) {
pthread_mutexattr_t ma;
int rc = pthread_mutexattr_init(&ma);
if (rc)
@@ -156,10 +152,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;
@@ -170,7 +162,7 @@ bailout:
return rc;
}
void __cold mdbx_lck_destroy(MDBX_env *env) {
void 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) {
@@ -180,8 +172,7 @@ void __cold mdbx_lck_destroy(MDBX_env *env) {
rc = pthread_mutex_destroy(&env->me_lck->mti_wmutex);
assert(rc == 0);
(void)rc;
/* file locks would be released (by kernel)
* while the me_lfd will be closed */
/* lock would be released (by kernel) while the me_lfd will be closed */
}
}
}
@@ -224,21 +215,21 @@ void mdbx_rdt_unlock(MDBX_env *env) {
int mdbx_txn_lock(MDBX_env *env, bool dontwait) {
mdbx_trace(">>");
int rc = dontwait ? mdbx_robust_trylock(env, env->me_wmutex)
: mdbx_robust_lock(env, env->me_wmutex);
int rc = dontwait ? mdbx_robust_trylock(env, &env->me_lck->mti_wmutex)
: mdbx_robust_lock(env, &env->me_lck->mti_wmutex);
mdbx_trace("<< rc %d", rc);
return MDBX_IS_ERROR(rc) ? rc : MDBX_SUCCESS;
}
void mdbx_txn_unlock(MDBX_env *env) {
mdbx_trace(">>");
int rc = mdbx_robust_unlock(env, env->me_wmutex);
int rc = mdbx_robust_unlock(env, &env->me_lck->mti_wmutex);
mdbx_trace("<< rc %d", rc);
if (unlikely(MDBX_IS_ERROR(rc)))
mdbx_panic("%s() failed: errcode %d\n", mdbx_func_, rc);
}
static int __cold internal_seize_lck(int lfd) {
static int internal_seize_lck(int lfd) {
assert(lfd != INVALID_HANDLE_VALUE);
/* try exclusive access */
@@ -264,19 +255,17 @@ static int __cold internal_seize_lck(int lfd) {
return rc;
}
int __cold mdbx_lck_seize(MDBX_env *env) {
int mdbx_lck_seize(MDBX_env *env) {
assert(env->me_fd != INVALID_HANDLE_VALUE);
if (env->me_lfd == INVALID_HANDLE_VALUE) {
/* LY: without-lck mode (e.g. exclusive or on read-only filesystem) */
int rc = mdbx_lck_op(env->me_fd, F_SETLK,
(env->me_flags & MDBX_RDONLY) ? F_RDLCK : F_WRLCK, 0,
LCK_WHOLE);
/* LY: without-lck mode (e.g. on read-only filesystem) */
int rc = mdbx_lck_op(env->me_fd, F_SETLK, F_RDLCK, 0, LCK_WHOLE);
if (rc != 0) {
mdbx_error("%s(%s) failed: errcode %u", mdbx_func_, "without-lck", rc);
return rc;
}
return MDBX_RESULT_TRUE;
return MDBX_RESULT_FALSE;
}
if ((env->me_flags & MDBX_RDONLY) == 0) {
@@ -302,7 +291,7 @@ static int __cold mdbx_mutex_failed(MDBX_env *env, pthread_mutex_t *mutex,
if (rc == EOWNERDEAD) {
/* We own the mutex. Clean up after dead previous owner. */
int rlocked = (env->me_lck && mutex == &env->me_lck->mti_rmutex);
int rlocked = (mutex == &env->me_lck->mti_rmutex);
rc = MDBX_SUCCESS;
if (!rlocked) {
if (unlikely(env->me_txn)) {

View File

@@ -24,17 +24,13 @@
* LY
*/
static void mdbx_winnt_import(void);
/*----------------------------------------------------------------------------*/
/* rthc */
#if !MDBX_CONFIG_MANUAL_TLS_CALLBACK
static
#endif /* !MDBX_CONFIG_MANUAL_TLS_CALLBACK */
void NTAPI
mdbx_dll_callback(PVOID module, DWORD reason, PVOID reserved) {
static void NTAPI tls_callback(PVOID module, DWORD reason, PVOID reserved) {
(void)reserved;
switch (reason) {
case DLL_PROCESS_ATTACH:
mdbx_winnt_import();
mdbx_rthc_global_init();
break;
case DLL_PROCESS_DETACH:
@@ -49,7 +45,6 @@ static
}
}
#if !MDBX_CONFIG_MANUAL_TLS_CALLBACK
/* *INDENT-OFF* */
/* clang-format off */
#if defined(_MSC_VER)
@@ -60,7 +55,7 @@ static
/* kick a linker to create the TLS directory if not already done */
# pragma comment(linker, "/INCLUDE:_tls_used")
/* Force some symbol references. */
# pragma comment(linker, "/INCLUDE:mdbx_tls_anchor")
# pragma comment(linker, "/INCLUDE:mdbx_tls_callback")
/* specific const-segment for WIN64 */
# pragma const_seg(".CRT$XLB")
const
@@ -68,12 +63,12 @@ static
/* kick a linker to create the TLS directory if not already done */
# pragma comment(linker, "/INCLUDE:__tls_used")
/* Force some symbol references. */
# pragma comment(linker, "/INCLUDE:_mdbx_tls_anchor")
# pragma comment(linker, "/INCLUDE:_mdbx_tls_callback")
/* specific data-segment for WIN32 */
# pragma data_seg(".CRT$XLB")
# endif
__declspec(allocate(".CRT$XLB")) PIMAGE_TLS_CALLBACK mdbx_tls_anchor = mdbx_dll_callback;
PIMAGE_TLS_CALLBACK mdbx_tls_callback = tls_callback;
# pragma data_seg(pop)
# pragma const_seg(pop)
@@ -81,13 +76,13 @@ static
# ifdef _WIN64
const
# endif
PIMAGE_TLS_CALLBACK mdbx_tls_anchor __attribute__((section(".CRT$XLB"), used)) = mdbx_dll_callback;
PIMAGE_TLS_CALLBACK mdbx_tls_callback __attribute__((section(".CRT$XLB"), used))
= tls_callback;
#else
# error FIXME
#endif
/* *INDENT-ON* */
/* clang-format on */
#endif /* !MDBX_CONFIG_MANUAL_TLS_CALLBACK */
/*----------------------------------------------------------------------------*/
@@ -132,8 +127,7 @@ int mdbx_txn_lock(MDBX_env *env, bool dontwait) {
EnterCriticalSection(&env->me_windowsbug_lock);
}
if ((env->me_flags & MDBX_EXCLUSIVE) ||
flock(env->me_fd,
if (flock(env->me_fd,
dontwait ? (LCK_EXCLUSIVE | LCK_DONTWAIT)
: (LCK_EXCLUSIVE | LCK_WAITFOR),
LCK_BODY))
@@ -144,8 +138,7 @@ int mdbx_txn_lock(MDBX_env *env, bool dontwait) {
}
void mdbx_txn_unlock(MDBX_env *env) {
int rc =
(env->me_flags & MDBX_EXCLUSIVE) ? TRUE : funlock(env->me_fd, LCK_BODY);
int rc = funlock(env->me_fd, LCK_BODY);
LeaveCriticalSection(&env->me_windowsbug_lock);
if (!rc)
mdbx_panic("%s failed: errcode %u", mdbx_func_, GetLastError());
@@ -163,28 +156,26 @@ void mdbx_txn_unlock(MDBX_env *env) {
#define LCK_UPPER LCK_UP_OFFSET, LCK_UP_LEN
int mdbx_rdt_lock(MDBX_env *env) {
mdbx_srwlock_AcquireShared(&env->me_remap_guard);
AcquireSRWLockShared(&env->me_remap_guard);
if (env->me_lfd == INVALID_HANDLE_VALUE)
return MDBX_SUCCESS; /* readonly database in readonly filesystem */
/* transite from S-? (used) to S-E (locked), e.g. exclusive lock upper-part */
if ((env->me_flags & MDBX_EXCLUSIVE) ||
flock(env->me_lfd, LCK_EXCLUSIVE | LCK_WAITFOR, LCK_UPPER))
if (flock(env->me_lfd, LCK_EXCLUSIVE | LCK_WAITFOR, LCK_UPPER))
return MDBX_SUCCESS;
int rc = GetLastError();
mdbx_srwlock_ReleaseShared(&env->me_remap_guard);
ReleaseSRWLockShared(&env->me_remap_guard);
return rc;
}
void mdbx_rdt_unlock(MDBX_env *env) {
if (env->me_lfd != INVALID_HANDLE_VALUE) {
/* transite from S-E (locked) to S-? (used), e.g. unlock upper-part */
if ((env->me_flags & MDBX_EXCLUSIVE) == 0 &&
!funlock(env->me_lfd, LCK_UPPER))
if (!funlock(env->me_lfd, LCK_UPPER))
mdbx_panic("%s failed: errcode %u", mdbx_func_, GetLastError());
}
mdbx_srwlock_ReleaseShared(&env->me_remap_guard);
ReleaseSRWLockShared(&env->me_remap_guard);
}
static int suspend_and_append(mdbx_handle_array_t **array,
@@ -255,8 +246,7 @@ int mdbx_suspend_threads_before_remap(MDBX_env *env,
} else {
/* Without LCK (i.e. read-only mode).
* Walk thougth a snapshot of all running threads */
mdbx_assert(env,
env->me_txn0 == NULL || (env->me_flags & MDBX_EXCLUSIVE) != 0);
mdbx_assert(env, env->me_txn0 == NULL);
const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return GetLastError();
@@ -379,9 +369,6 @@ int mdbx_lck_seize(MDBX_env *env) {
int rc;
assert(env->me_fd != INVALID_HANDLE_VALUE);
if (env->me_flags & MDBX_EXCLUSIVE)
return MDBX_RESULT_TRUE /* files were must be opened non-shareable */;
if (env->me_lfd == INVALID_HANDLE_VALUE) {
/* LY: without-lck mode (e.g. on read-only filesystem) */
mdbx_jitter4testing(false);
@@ -424,9 +411,6 @@ int mdbx_lck_downgrade(MDBX_env *env, bool complete) {
assert(env->me_fd != INVALID_HANDLE_VALUE);
assert(env->me_lfd != INVALID_HANDLE_VALUE);
if (env->me_flags & MDBX_EXCLUSIVE)
return MDBX_SUCCESS /* files were must be opened non-shareable */;
/* 1) must be at E-E (exclusive-write) */
if (!complete) {
/* transite from E-E to E_? (exclusive-read) */
@@ -550,122 +534,3 @@ int mdbx_rpid_check(MDBX_env *env, mdbx_pid_t pid) {
return rc;
}
}
//----------------------------------------------------------------------------
// Stub for slim read-write lock
// Copyright (C) 1995-2002 Brad Wilson
static void WINAPI stub_srwlock_Init(MDBX_srwlock *srwl) {
srwl->readerCount = srwl->writerCount = 0;
}
static void WINAPI stub_srwlock_AcquireShared(MDBX_srwlock *srwl) {
while (true) {
assert(srwl->writerCount >= 0 && srwl->readerCount >= 0);
// If there's a writer already, spin without unnecessarily
// interlocking the CPUs
if (srwl->writerCount != 0) {
YieldProcessor();
continue;
}
// Add to the readers list
_InterlockedIncrement(&srwl->readerCount);
// Check for writers again (we may have been pre-empted). If
// there are no writers writing or waiting, then we're done.
if (srwl->writerCount == 0)
break;
// Remove from the readers list, spin, try again
_InterlockedDecrement(&srwl->readerCount);
YieldProcessor();
}
}
static void WINAPI stub_srwlock_ReleaseShared(MDBX_srwlock *srwl) {
assert(srwl->readerCount > 0);
_InterlockedDecrement(&srwl->readerCount);
}
static void WINAPI stub_srwlock_AcquireExclusive(MDBX_srwlock *srwl) {
while (true) {
assert(srwl->writerCount >= 0 && srwl->readerCount >= 0);
// If there's a writer already, spin without unnecessarily
// interlocking the CPUs
if (srwl->writerCount != 0) {
YieldProcessor();
continue;
}
// See if we can become the writer (expensive, because it inter-
// locks the CPUs, so writing should be an infrequent process)
if (_InterlockedExchange(&srwl->writerCount, 1) == 0)
break;
}
// Now we're the writer, but there may be outstanding readers.
// Spin until there aren't any more; new readers will wait now
// that we're the writer.
while (srwl->readerCount != 0) {
assert(srwl->writerCount >= 0 && srwl->readerCount >= 0);
YieldProcessor();
}
}
static void WINAPI stub_srwlock_ReleaseExclusive(MDBX_srwlock *srwl) {
assert(srwl->writerCount == 1 && srwl->readerCount >= 0);
srwl->writerCount = 0;
}
MDBX_srwlock_function mdbx_srwlock_Init, mdbx_srwlock_AcquireShared,
mdbx_srwlock_ReleaseShared, mdbx_srwlock_AcquireExclusive,
mdbx_srwlock_ReleaseExclusive;
/*----------------------------------------------------------------------------*/
MDBX_GetFileInformationByHandleEx mdbx_GetFileInformationByHandleEx;
MDBX_GetVolumeInformationByHandleW mdbx_GetVolumeInformationByHandleW;
MDBX_GetFinalPathNameByHandleW mdbx_GetFinalPathNameByHandleW;
MDBX_NtFsControlFile mdbx_NtFsControlFile;
static void mdbx_winnt_import(void) {
const HINSTANCE hKernel32dll = GetModuleHandleA("kernel32.dll");
const MDBX_srwlock_function init =
(MDBX_srwlock_function)GetProcAddress(hKernel32dll, "InitializeSRWLock");
if (init != NULL) {
mdbx_srwlock_Init = init;
mdbx_srwlock_AcquireShared = (MDBX_srwlock_function)GetProcAddress(
hKernel32dll, "AcquireSRWLockShared");
mdbx_srwlock_ReleaseShared = (MDBX_srwlock_function)GetProcAddress(
hKernel32dll, "ReleaseSRWLockShared");
mdbx_srwlock_AcquireExclusive = (MDBX_srwlock_function)GetProcAddress(
hKernel32dll, "AcquireSRWLockExclusive");
mdbx_srwlock_ReleaseExclusive = (MDBX_srwlock_function)GetProcAddress(
hKernel32dll, "ReleaseSRWLockExclusive");
} else {
mdbx_srwlock_Init = stub_srwlock_Init;
mdbx_srwlock_AcquireShared = stub_srwlock_AcquireShared;
mdbx_srwlock_ReleaseShared = stub_srwlock_ReleaseShared;
mdbx_srwlock_AcquireExclusive = stub_srwlock_AcquireExclusive;
mdbx_srwlock_ReleaseExclusive = stub_srwlock_ReleaseExclusive;
}
mdbx_GetFileInformationByHandleEx =
(MDBX_GetFileInformationByHandleEx)GetProcAddress(
hKernel32dll, "GetFileInformationByHandleEx");
mdbx_GetVolumeInformationByHandleW =
(MDBX_GetVolumeInformationByHandleW)GetProcAddress(
hKernel32dll, "GetVolumeInformationByHandleW");
mdbx_GetFinalPathNameByHandleW =
(MDBX_GetFinalPathNameByHandleW)GetProcAddress(
hKernel32dll, "GetFinalPathNameByHandleW");
const HINSTANCE hNtdll = GetModuleHandleA("ntdll.dll");
mdbx_NtFsControlFile =
(MDBX_NtFsControlFile)GetProcAddress(hNtdll, "NtFsControlFile");
}

3644
src/mdbx.c

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,7 @@
#include "./bits.h"
#if defined(_WIN32) || defined(_WIN64)
#include <winternl.h>
static int waitstatus2errcode(DWORD result) {
switch (result) {
@@ -104,31 +105,13 @@ extern NTSTATUS NTAPI NtFreeVirtualMemory(IN HANDLE ProcessHandle,
IN OUT PSIZE_T RegionSize,
IN ULONG FreeType);
#ifndef WOF_CURRENT_VERSION
typedef struct _WOF_EXTERNAL_INFO {
DWORD Version;
DWORD Provider;
} WOF_EXTERNAL_INFO, *PWOF_EXTERNAL_INFO;
#endif /* WOF_CURRENT_VERSION */
#ifndef WIM_PROVIDER_CURRENT_VERSION
#define WIM_PROVIDER_HASH_SIZE 20
typedef struct _WIM_PROVIDER_EXTERNAL_INFO {
DWORD Version;
DWORD Flags;
LARGE_INTEGER DataSourceId;
BYTE ResourceHash[WIM_PROVIDER_HASH_SIZE];
} WIM_PROVIDER_EXTERNAL_INFO, *PWIM_PROVIDER_EXTERNAL_INFO;
#endif /* WIM_PROVIDER_CURRENT_VERSION */
#ifndef FILE_PROVIDER_CURRENT_VERSION
typedef struct _FILE_PROVIDER_EXTERNAL_INFO_V1 {
ULONG Version;
ULONG Algorithm;
ULONG Flags;
} FILE_PROVIDER_EXTERNAL_INFO_V1, *PFILE_PROVIDER_EXTERNAL_INFO_V1;
#endif /* FILE_PROVIDER_CURRENT_VERSION */
#endif
#ifndef STATUS_OBJECT_NOT_EXTERNALLY_BACKED
#define STATUS_OBJECT_NOT_EXTERNALLY_BACKED ((NTSTATUS)0xC000046DL)
@@ -137,6 +120,14 @@ typedef struct _FILE_PROVIDER_EXTERNAL_INFO_V1 {
#define STATUS_INVALID_DEVICE_REQUEST ((NTSTATUS)0xC0000010L)
#endif
extern NTSTATUS
NtFsControlFile(IN HANDLE FileHandle, IN OUT HANDLE Event,
IN OUT PVOID /* PIO_APC_ROUTINE */ ApcRoutine,
IN OUT PVOID ApcContext, OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG FsControlCode, IN OUT PVOID InputBuffer,
IN ULONG InputBufferLength, OUT OPTIONAL PVOID OutputBuffer,
IN ULONG OutputBufferLength);
#endif /* _WIN32 || _WIN64 */
/*----------------------------------------------------------------------------*/
@@ -408,28 +399,20 @@ int mdbx_fastmutex_release(mdbx_fastmutex_t *fastmutex) {
/*----------------------------------------------------------------------------*/
int mdbx_removefile(const char *pathname) {
#if defined(_WIN32) || defined(_WIN64)
return DeleteFileA(pathname) ? MDBX_SUCCESS : GetLastError();
#else
return unlink(pathname) ? errno : MDBX_SUCCESS;
#endif
}
int mdbx_openfile(const char *pathname, int flags, mode_t mode,
mdbx_filehandle_t *fd, bool exclusive) {
mdbx_filehandle_t *fd) {
*fd = INVALID_HANDLE_VALUE;
#if defined(_WIN32) || defined(_WIN64)
(void)mode;
DWORD DesiredAccess, ShareMode;
DWORD DesiredAccess;
DWORD ShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
DWORD FlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
switch (flags & (O_RDONLY | O_WRONLY | O_RDWR)) {
default:
return ERROR_INVALID_PARAMETER;
case O_RDONLY:
DesiredAccess = GENERIC_READ;
ShareMode =
exclusive ? FILE_SHARE_READ : (FILE_SHARE_READ | FILE_SHARE_WRITE);
break;
case O_WRONLY: /* assume for MDBX_env_copy() and friends output */
DesiredAccess = GENERIC_WRITE;
@@ -438,7 +421,6 @@ int mdbx_openfile(const char *pathname, int flags, mode_t mode,
break;
case O_RDWR:
DesiredAccess = GENERIC_READ | GENERIC_WRITE;
ShareMode = exclusive ? 0 : (FILE_SHARE_READ | FILE_SHARE_WRITE);
break;
}
@@ -477,7 +459,7 @@ int mdbx_openfile(const char *pathname, int flags, mode_t mode,
}
}
#else
(void)exclusive;
#ifdef O_CLOEXEC
flags |= O_CLOEXEC;
#endif
@@ -753,118 +735,92 @@ int mdbx_msync(mdbx_mmap_t *map, size_t offset, size_t length, int async) {
#endif
}
int mdbx_check4nonlocal(mdbx_filehandle_t handle, int flags) {
#if defined(_WIN32) || defined(_WIN64)
if (GetFileType(handle) != FILE_TYPE_DISK)
return ERROR_FILE_OFFLINE;
if (mdbx_GetFileInformationByHandleEx) {
FILE_REMOTE_PROTOCOL_INFO RemoteProtocolInfo;
if (mdbx_GetFileInformationByHandleEx(handle, FileRemoteProtocolInfo,
&RemoteProtocolInfo,
sizeof(RemoteProtocolInfo))) {
if ((RemoteProtocolInfo.Flags & REMOTE_PROTOCOL_INFO_FLAG_OFFLINE) &&
!(flags & MDBX_RDONLY))
return ERROR_FILE_OFFLINE;
if (!(RemoteProtocolInfo.Flags & REMOTE_PROTOCOL_INFO_FLAG_LOOPBACK) &&
!(flags & MDBX_EXCLUSIVE))
return ERROR_REMOTE_STORAGE_MEDIA_ERROR;
}
}
if (mdbx_NtFsControlFile) {
NTSTATUS rc;
struct {
WOF_EXTERNAL_INFO wof_info;
union {
WIM_PROVIDER_EXTERNAL_INFO wim_info;
FILE_PROVIDER_EXTERNAL_INFO_V1 file_info;
};
size_t reserved_for_microsoft_madness[42];
} GetExternalBacking_OutputBuffer;
IO_STATUS_BLOCK StatusBlock;
rc = mdbx_NtFsControlFile(handle, NULL, NULL, NULL, &StatusBlock,
FSCTL_GET_EXTERNAL_BACKING, NULL, 0,
&GetExternalBacking_OutputBuffer,
sizeof(GetExternalBacking_OutputBuffer));
if (NT_SUCCESS(rc)) {
if (!(flags & MDBX_EXCLUSIVE))
return ERROR_REMOTE_STORAGE_MEDIA_ERROR;
} else if (rc != STATUS_OBJECT_NOT_EXTERNALLY_BACKED &&
rc != STATUS_INVALID_DEVICE_REQUEST)
return ntstatus2errcode(rc);
}
if (mdbx_GetVolumeInformationByHandleW && mdbx_GetFinalPathNameByHandleW) {
WCHAR PathBuffer[INT16_MAX];
DWORD VolumeSerialNumber, FileSystemFlags;
if (!mdbx_GetVolumeInformationByHandleW(handle, PathBuffer, INT16_MAX,
&VolumeSerialNumber, NULL,
&FileSystemFlags, NULL, 0))
return GetLastError();
if ((flags & MDBX_RDONLY) == 0) {
if (FileSystemFlags & (FILE_SEQUENTIAL_WRITE_ONCE |
FILE_READ_ONLY_VOLUME | FILE_VOLUME_IS_COMPRESSED))
return ERROR_REMOTE_STORAGE_MEDIA_ERROR;
}
if (!mdbx_GetFinalPathNameByHandleW(handle, PathBuffer, INT16_MAX,
FILE_NAME_NORMALIZED | VOLUME_NAME_NT))
return GetLastError();
if (_wcsnicmp(PathBuffer, L"\\Device\\Mup\\", 12) == 0) {
if (!(flags & MDBX_EXCLUSIVE))
return ERROR_REMOTE_STORAGE_MEDIA_ERROR;
} else if (mdbx_GetFinalPathNameByHandleW(handle, PathBuffer, INT16_MAX,
FILE_NAME_NORMALIZED |
VOLUME_NAME_DOS)) {
UINT DriveType = GetDriveTypeW(PathBuffer);
if (DriveType == DRIVE_NO_ROOT_DIR &&
wcsncmp(PathBuffer, L"\\\\?\\", 4) == 0 &&
wcsncmp(PathBuffer + 5, L":\\", 2) == 0) {
PathBuffer[7] = 0;
DriveType = GetDriveTypeW(PathBuffer + 4);
}
switch (DriveType) {
case DRIVE_CDROM:
if (flags & MDBX_RDONLY)
break;
// fall through
case DRIVE_UNKNOWN:
case DRIVE_NO_ROOT_DIR:
case DRIVE_REMOTE:
default:
if (!(flags & MDBX_EXCLUSIVE))
return ERROR_REMOTE_STORAGE_MEDIA_ERROR;
// fall through
case DRIVE_REMOVABLE:
case DRIVE_FIXED:
case DRIVE_RAMDISK:
break;
}
}
}
#else
(void)handle;
/* TODO: check for NFS handle ? */
(void)flags;
#endif
return MDBX_SUCCESS;
}
int mdbx_mmap(int flags, mdbx_mmap_t *map, size_t size, size_t limit) {
assert(size <= limit);
#if defined(_WIN32) || defined(_WIN64)
NTSTATUS rc;
map->length = 0;
map->current = 0;
map->section = NULL;
map->address = nullptr;
NTSTATUS rc = mdbx_check4nonlocal(map->fd, flags);
if (rc != MDBX_SUCCESS)
return rc;
if (GetFileType(map->fd) != FILE_TYPE_DISK)
return ERROR_FILE_OFFLINE;
FILE_REMOTE_PROTOCOL_INFO RemoteProtocolInfo;
if (GetFileInformationByHandleEx(map->fd, FileRemoteProtocolInfo,
&RemoteProtocolInfo,
sizeof(RemoteProtocolInfo))) {
if ((RemoteProtocolInfo.Flags & (REMOTE_PROTOCOL_INFO_FLAG_LOOPBACK |
REMOTE_PROTOCOL_INFO_FLAG_OFFLINE)) !=
REMOTE_PROTOCOL_INFO_FLAG_LOOPBACK)
return ERROR_FILE_OFFLINE;
}
#if defined(_WIN64) && defined(WOF_CURRENT_VERSION)
struct {
WOF_EXTERNAL_INFO wof_info;
union {
WIM_PROVIDER_EXTERNAL_INFO wim_info;
FILE_PROVIDER_EXTERNAL_INFO_V1 file_info;
};
size_t reserved_for_microsoft_madness[42];
} GetExternalBacking_OutputBuffer;
IO_STATUS_BLOCK StatusBlock;
rc = NtFsControlFile(map->fd, NULL, NULL, NULL, &StatusBlock,
FSCTL_GET_EXTERNAL_BACKING, NULL, 0,
&GetExternalBacking_OutputBuffer,
sizeof(GetExternalBacking_OutputBuffer));
if (rc != STATUS_OBJECT_NOT_EXTERNALLY_BACKED &&
rc != STATUS_INVALID_DEVICE_REQUEST)
return NT_SUCCESS(rc) ? ERROR_FILE_OFFLINE : ntstatus2errcode(rc);
#endif
WCHAR PathBuffer[INT16_MAX];
DWORD VolumeSerialNumber, FileSystemFlags;
if (!GetVolumeInformationByHandleW(map->fd, PathBuffer, INT16_MAX,
&VolumeSerialNumber, NULL,
&FileSystemFlags, NULL, 0))
return GetLastError();
if ((flags & MDBX_RDONLY) == 0) {
if (FileSystemFlags & (FILE_SEQUENTIAL_WRITE_ONCE | FILE_READ_ONLY_VOLUME |
FILE_VOLUME_IS_COMPRESSED))
return ERROR_FILE_OFFLINE;
}
if (!GetFinalPathNameByHandleW(map->fd, PathBuffer, INT16_MAX,
FILE_NAME_NORMALIZED | VOLUME_NAME_NT))
return GetLastError();
if (_wcsnicmp(PathBuffer, L"\\Device\\Mup\\", 12) == 0)
return ERROR_FILE_OFFLINE;
if (GetFinalPathNameByHandleW(map->fd, PathBuffer, INT16_MAX,
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS)) {
UINT DriveType = GetDriveTypeW(PathBuffer);
if (DriveType == DRIVE_NO_ROOT_DIR &&
wcsncmp(PathBuffer, L"\\\\?\\", 4) == 0 &&
wcsncmp(PathBuffer + 5, L":\\", 2) == 0) {
PathBuffer[7] = 0;
DriveType = GetDriveTypeW(PathBuffer + 4);
}
switch (DriveType) {
case DRIVE_CDROM:
if (flags & MDBX_RDONLY)
break;
// fall through
case DRIVE_UNKNOWN:
case DRIVE_NO_ROOT_DIR:
case DRIVE_REMOTE:
default:
return ERROR_FILE_OFFLINE;
case DRIVE_REMOVABLE:
case DRIVE_FIXED:
case DRIVE_RAMDISK:
break;
}
}
rc = mdbx_filesize(map->fd, &map->filesize);
if (rc != MDBX_SUCCESS)

View File

@@ -65,11 +65,9 @@
/* Systems includes */
#if defined(_WIN32) || defined(_WIN64)
#define WIN32_LEAN_AND_MEAN
#include <tlhelp32.h>
#include <windows.h>
#include <winnt.h>
#include <winternl.h>
#define HAVE_SYS_STAT_H
#define HAVE_SYS_TYPES_H
typedef HANDLE mdbx_thread_t;
@@ -477,9 +475,8 @@ int mdbx_filesize_sync(mdbx_filehandle_t fd);
int mdbx_ftruncate(mdbx_filehandle_t fd, uint64_t length);
int mdbx_filesize(mdbx_filehandle_t fd, uint64_t *length);
int mdbx_openfile(const char *pathname, int flags, mode_t mode,
mdbx_filehandle_t *fd, bool exclusive);
mdbx_filehandle_t *fd);
int mdbx_closefile(mdbx_filehandle_t fd);
int mdbx_removefile(const char *pathname);
typedef struct mdbx_mmap_param {
union {
@@ -511,7 +508,6 @@ int mdbx_suspend_threads_before_remap(MDBX_env *env,
int mdbx_resume_threads_after_remap(mdbx_handle_array_t *array);
#endif /* Windows */
int mdbx_msync(mdbx_mmap_t *map, size_t offset, size_t length, int async);
int mdbx_check4nonlocal(mdbx_filehandle_t handle, int flags);
static __inline mdbx_pid_t mdbx_getpid(void) {
#if defined(_WIN32) || defined(_WIN64)
@@ -563,53 +559,6 @@ LIBMDBX_API void mdbx_txn_unlock(MDBX_env *env);
int mdbx_rpid_set(MDBX_env *env);
int mdbx_rpid_clear(MDBX_env *env);
#if defined(_WIN32) || defined(_WIN64)
typedef struct MDBX_srwlock {
union {
struct {
long volatile readerCount;
long volatile writerCount;
};
RTL_SRWLOCK native;
};
} MDBX_srwlock;
typedef void(WINAPI *MDBX_srwlock_function)(MDBX_srwlock *);
extern MDBX_srwlock_function mdbx_srwlock_Init, mdbx_srwlock_AcquireShared,
mdbx_srwlock_ReleaseShared, mdbx_srwlock_AcquireExclusive,
mdbx_srwlock_ReleaseExclusive;
typedef BOOL(WINAPI *MDBX_GetFileInformationByHandleEx)(
_In_ HANDLE hFile, _In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
_Out_ LPVOID lpFileInformation, _In_ DWORD dwBufferSize);
extern MDBX_GetFileInformationByHandleEx mdbx_GetFileInformationByHandleEx;
typedef BOOL(WINAPI *MDBX_GetVolumeInformationByHandleW)(
_In_ HANDLE hFile, _Out_opt_ LPWSTR lpVolumeNameBuffer,
_In_ DWORD nVolumeNameSize, _Out_opt_ LPDWORD lpVolumeSerialNumber,
_Out_opt_ LPDWORD lpMaximumComponentLength,
_Out_opt_ LPDWORD lpFileSystemFlags,
_Out_opt_ LPWSTR lpFileSystemNameBuffer, _In_ DWORD nFileSystemNameSize);
extern MDBX_GetVolumeInformationByHandleW mdbx_GetVolumeInformationByHandleW;
typedef DWORD(WINAPI *MDBX_GetFinalPathNameByHandleW)(_In_ HANDLE hFile,
_Out_ LPWSTR lpszFilePath,
_In_ DWORD cchFilePath,
_In_ DWORD dwFlags);
extern MDBX_GetFinalPathNameByHandleW mdbx_GetFinalPathNameByHandleW;
typedef NTSTATUS(NTAPI *MDBX_NtFsControlFile)(
IN HANDLE FileHandle, IN OUT HANDLE Event,
IN OUT PVOID /* PIO_APC_ROUTINE */ ApcRoutine, IN OUT PVOID ApcContext,
OUT PIO_STATUS_BLOCK IoStatusBlock, IN ULONG FsControlCode,
IN OUT PVOID InputBuffer, IN ULONG InputBufferLength,
OUT OPTIONAL PVOID OutputBuffer, IN ULONG OutputBufferLength);
extern MDBX_NtFsControlFile mdbx_NtFsControlFile;
#endif /* Windows */
/* Checks reader by pid.
*
* Returns:

View File

@@ -64,9 +64,8 @@ static void signal_handler(int sig) {
typedef struct {
const char *name;
struct {
uint64_t branch, large_count, large_volume, leaf;
uint64_t subleaf_dupsort, leaf_dupfixed, subleaf_dupfixed;
uint64_t total, empty, other;
uint64_t total;
uint64_t empty;
} pages;
uint64_t payload_bytes;
uint64_t lost_bytes;
@@ -79,19 +78,16 @@ struct {
uint64_t pgcount;
} walk;
#define dbi_free walk.dbi[FREE_DBI]
#define dbi_main walk.dbi[MAIN_DBI]
#define dbi_meta walk.dbi[CORE_DBS]
uint64_t total_unused_bytes;
int envflags = MDBX_RDONLY | MDBX_EXCLUSIVE;
int exclusive = 2;
int envflags = MDBX_RDONLY;
MDBX_env *env;
MDBX_txn *txn;
MDBX_envinfo envinfo;
MDBX_stat envstat;
size_t maxkeysize, userdb_count, skipped_subdb;
uint64_t reclaimable_pages, gc_pages, lastpgno, unused_pages;
uint64_t reclaimable_pages, freedb_pages, lastpgno;
unsigned verbose, quiet;
const char *only_subdb;
@@ -131,7 +127,6 @@ static void
fflush(stdout);
va_start(args, msg);
fputs(" ! ", stderr);
vfprintf(stderr, msg, args);
va_end(args);
fflush(NULL);
@@ -139,7 +134,9 @@ static void
}
static void pagemap_cleanup(void) {
for (int i = CORE_DBS; ++i < MAX_DBI;) {
int i;
for (i = 1; i < MAX_DBI; ++i) {
if (walk.dbi[i].name) {
free((void *)walk.dbi[i].name);
walk.dbi[i].name = NULL;
@@ -150,22 +147,22 @@ static void pagemap_cleanup(void) {
walk.pagemap = NULL;
}
static walk_dbi_t *pagemap_lookup_dbi(const char *dbi_name, bool silent) {
static walk_dbi_t *pagemap_lookup_dbi(const char *dbi_name) {
static walk_dbi_t *last;
if (last && strcmp(last->name, dbi_name) == 0)
return last;
walk_dbi_t *dbi = walk.dbi + CORE_DBS;
for (dbi = walk.dbi + CORE_DBS; (++dbi)->name;) {
walk_dbi_t *dbi = walk.dbi + 1;
while (dbi->name) {
if (strcmp(dbi->name, dbi_name) == 0)
return last = dbi;
if (dbi == walk.dbi + MAX_DBI)
if (++dbi == walk.dbi + MAX_DBI)
return NULL;
}
dbi->name = strdup(dbi_name);
if (verbose > 1 && !silent) {
if (verbose > 1) {
print(" - found '%s' area\n", dbi_name);
fflush(NULL);
}
@@ -244,140 +241,85 @@ static uint64_t problems_pop(struct problem *list) {
return count;
}
static int pgvisitor(uint64_t pgno, unsigned pgnumber, void *ctx, int deep,
const char *dbi_name, size_t page_size,
MDBX_page_type_t pagetype, size_t nentries,
static int pgvisitor(uint64_t pgno, unsigned pgnumber, void *ctx,
const char *dbi_name, const char *type, size_t nentries,
size_t payload_bytes, size_t header_bytes,
size_t unused_bytes) {
(void)ctx;
if (pagetype == MDBX_page_void)
return MDBX_SUCCESS;
walk_dbi_t fake, *dbi = &fake;
if (deep > 0) {
dbi = pagemap_lookup_dbi(dbi_name, false);
if (type) {
uint64_t page_bytes = payload_bytes + header_bytes + unused_bytes;
size_t page_size = (size_t)pgnumber * envstat.ms_psize;
walk_dbi_t *dbi = pagemap_lookup_dbi(dbi_name);
if (!dbi)
return MDBX_ENOMEM;
} else if (deep == 0 && strcmp(dbi_name, dbi_main.name) == 0)
dbi = &dbi_main;
else if (deep == -1 && strcmp(dbi_name, dbi_free.name) == 0)
dbi = &dbi_free;
else if (deep == -2 && strcmp(dbi_name, dbi_meta.name) == 0)
dbi = &dbi_meta;
else
problem_add("deep", deep, "unknown area", "%s", dbi_name);
const uint64_t page_bytes = payload_bytes + header_bytes + unused_bytes;
walk.pgcount += pgnumber;
const char *pagetype_caption;
switch (pagetype) {
default:
problem_add("page", pgno, "unknown page-type", "%u", (unsigned)pagetype);
pagetype_caption = "unknown";
dbi->pages.other += pgnumber;
break;
case MDBX_page_meta:
pagetype_caption = "meta";
dbi->pages.other += pgnumber;
break;
case MDBX_page_large:
pagetype_caption = "large";
dbi->pages.large_volume += pgnumber;
dbi->pages.large_count += 1;
break;
case MDBX_page_branch:
pagetype_caption = "branch";
dbi->pages.branch += pgnumber;
break;
case MDBX_page_leaf:
pagetype_caption = "leaf";
dbi->pages.leaf += pgnumber;
break;
case MDBX_page_dupfixed_leaf:
pagetype_caption = "leaf-dupfixed";
dbi->pages.leaf_dupfixed += pgnumber;
break;
case MDBX_subpage_leaf:
pagetype_caption = "subleaf-dupsort";
dbi->pages.subleaf_dupsort += 1;
break;
case MDBX_subpage_dupfixed_leaf:
pagetype_caption = "subleaf-dupfixed";
dbi->pages.subleaf_dupfixed += 1;
break;
}
if (pgnumber) {
if (verbose > 3 && (!only_subdb || strcmp(only_subdb, dbi_name) == 0)) {
if (verbose > 2 && (!only_subdb || strcmp(only_subdb, dbi_name) == 0)) {
if (pgnumber == 1)
print(" %s-page %" PRIu64, pagetype_caption, pgno);
print(" %s-page %" PRIu64, type, pgno);
else
print(" %s-span %" PRIu64 "[%u]", pagetype_caption, pgno, pgnumber);
print(" %s-span %" PRIu64 "[%u]", type, pgno, pgnumber);
print(" of %s: header %" PRIiPTR ", payload %" PRIiPTR
", unused %" PRIiPTR "\n",
dbi_name, header_bytes, payload_bytes, unused_bytes);
}
}
if (unused_bytes > page_size)
problem_add("page", pgno, "illegal unused-bytes",
"%s-page: %u < %" PRIuPTR " < %u", pagetype_caption, 0,
unused_bytes, envstat.ms_psize);
walk.pgcount += pgnumber;
if (header_bytes < (int)sizeof(long) ||
(size_t)header_bytes >= envstat.ms_psize - sizeof(long))
problem_add("page", pgno, "illegal header-length",
"%s-page: %" PRIuPTR " < %" PRIuPTR " < %" PRIuPTR,
pagetype_caption, sizeof(long), header_bytes,
envstat.ms_psize - sizeof(long));
if (payload_bytes < 1) {
if (nentries > 1) {
problem_add("page", pgno, "zero size-of-entry",
"%s-page: payload %" PRIuPTR " bytes, %" PRIuPTR " entries",
pagetype_caption, payload_bytes, nentries);
/* if ((size_t)header_bytes + unused_bytes < page_size) {
// LY: hush a misuse error
page_bytes = page_size;
} */
} else {
problem_add("page", pgno, "empty",
"%s-page: payload %" PRIuPTR " bytes, %" PRIuPTR " entries",
pagetype_caption, payload_bytes, nentries);
dbi->pages.empty += 1;
if (unused_bytes > page_size)
problem_add("page", pgno, "illegal unused-bytes",
"%u < %" PRIuPTR " < %u", 0, unused_bytes, envstat.ms_psize);
if (header_bytes < (int)sizeof(long) ||
(size_t)header_bytes >= envstat.ms_psize - sizeof(long))
problem_add("page", pgno, "illegal header-length",
"%" PRIuPTR " < %" PRIuPTR " < %" PRIuPTR, sizeof(long),
header_bytes, envstat.ms_psize - sizeof(long));
if (payload_bytes < 1) {
if (nentries > 1) {
problem_add("page", pgno, "zero size-of-entry",
"payload %" PRIuPTR " bytes, %" PRIuPTR " entries",
payload_bytes, nentries);
if ((size_t)header_bytes + unused_bytes < page_size) {
/* LY: hush a misuse error */
page_bytes = page_size;
}
} else {
problem_add("page", pgno, "empty",
"payload %" PRIuPTR " bytes, %" PRIuPTR " entries",
payload_bytes, nentries);
dbi->pages.empty += 1;
}
}
}
if (pgnumber) {
if (page_bytes != page_size) {
problem_add("page", pgno, "misused",
"%s-page: %" PRIu64 " != %" PRIu64 " (%" PRIuPTR
"h + %" PRIuPTR "p + %" PRIuPTR "u)",
pagetype_caption, page_size, page_bytes, header_bytes,
payload_bytes, unused_bytes);
"%" PRIu64 " != %" PRIu64 " (%" PRIuPTR "h + %" PRIuPTR
"p + %" PRIuPTR "u)",
page_size, page_bytes, header_bytes, payload_bytes,
unused_bytes);
if (page_size > page_bytes)
dbi->lost_bytes += page_size - page_bytes;
} else {
dbi->payload_bytes += payload_bytes + header_bytes;
walk.total_payload_bytes += payload_bytes + header_bytes;
}
}
if (pgnumber) {
do {
if (pgno >= lastpgno)
problem_add("page", pgno, "wrong page-no",
"%s-page: %" PRIu64 " > %" PRIu64, pagetype_caption, pgno,
lastpgno);
else if (walk.pagemap[pgno])
problem_add("page", pgno, "already used", "%s-page: by %s",
pagetype_caption, walk.dbi[walk.pagemap[pgno] - 1].name);
else {
walk.pagemap[pgno] = (short)(dbi - walk.dbi + 1);
dbi->pages.total += 1;
}
++pgno;
} while (--pgnumber);
if (pgnumber) {
do {
if (pgno >= lastpgno)
problem_add("page", pgno, "wrong page-no",
"%" PRIu64 " > %" PRIu64 "", pgno, lastpgno);
else if (walk.pagemap[pgno])
problem_add("page", pgno, "already used", "in %s",
walk.dbi[walk.pagemap[pgno]].name);
else {
walk.pagemap[pgno] = (short)(dbi - walk.dbi);
dbi->pages.total += 1;
}
++pgno;
} while (--pgnumber);
}
}
return user_break ? MDBX_EINTR : MDBX_SUCCESS;
@@ -385,8 +327,7 @@ static int pgvisitor(uint64_t pgno, unsigned pgnumber, void *ctx, int deep,
typedef int(visitor)(const uint64_t record_number, const MDBX_val *key,
const MDBX_val *data);
static int process_db(MDBX_dbi dbi_handle, char *dbi_name, visitor *handler,
bool silent);
static int process_db(MDBX_dbi dbi, char *name, visitor *handler, bool silent);
static int handle_userdb(const uint64_t record_number, const MDBX_val *key,
const MDBX_val *data) {
@@ -404,79 +345,71 @@ static int handle_freedb(const uint64_t record_number, const MDBX_val *key,
if (key->iov_len != sizeof(txnid_t))
problem_add("entry", record_number, "wrong txn-id size",
"key-size %" PRIiPTR, key->iov_len);
"key-size %" PRIiPTR "", key->iov_len);
else if (txnid < 1 || txnid > envinfo.mi_recent_txnid)
problem_add("entry", record_number, "wrong txn-id", "%" PRIaTXN, txnid);
problem_add("entry", record_number, "wrong txn-id", "%" PRIaTXN "", txnid);
if (data->iov_len < sizeof(pgno_t) || data->iov_len % sizeof(pgno_t))
problem_add("entry", txnid, "wrong idl size", "%" PRIuPTR "",
data->iov_len);
else {
if (data->iov_len < sizeof(pgno_t) || data->iov_len % sizeof(pgno_t))
problem_add("entry", txnid, "wrong idl size", "%" PRIuPTR, data->iov_len);
size_t number = (data->iov_len >= sizeof(pgno_t)) ? *iptr++ : 0;
if (number < 1 || number > MDBX_PNL_MAX)
problem_add("entry", txnid, "wrong idl length", "%" PRIuPTR, number);
else if ((number + 1) * sizeof(pgno_t) > data->iov_len) {
const pgno_t number = *iptr++;
if (number < 1 || number >= INT_MAX / 2)
problem_add("entry", txnid, "wrong idl length", "%" PRIaPGNO, number);
else if ((number + 1) * sizeof(pgno_t) > data->iov_len)
problem_add("entry", txnid, "trimmed idl",
"%" PRIuSIZE " > %" PRIuSIZE " (corruption)",
(number + 1) * sizeof(pgno_t), data->iov_len);
number = data->iov_len / sizeof(pgno_t) - 1;
} else if (data->iov_len - (number + 1) * sizeof(pgno_t) >=
/* LY: allow gap upto one page. it is ok
* and better than shink-and-retry inside mdbx_update_gc() */
envstat.ms_psize)
else if (data->iov_len - (number + 1) * sizeof(pgno_t) >=
/* LY: allow gap upto one page. it is ok
* and better than shink-and-retry inside mdbx_update_gc() */
envstat.ms_psize)
problem_add("entry", txnid, "extra idl space",
"%" PRIuSIZE " < %" PRIuSIZE " (minor, not a trouble)",
(number + 1) * sizeof(pgno_t), data->iov_len);
else {
freedb_pages += number;
if (envinfo.mi_latter_reader_txnid > txnid)
reclaimable_pages += number;
gc_pages += number;
if (envinfo.mi_latter_reader_txnid > txnid)
reclaimable_pages += number;
pgno_t prev =
MDBX_PNL_ASCENDING ? NUM_METAS - 1 : (pgno_t)envinfo.mi_last_pgno + 1;
pgno_t span = 1;
for (unsigned i = 0; i < number; ++i) {
const pgno_t pgno = iptr[i];
if (pgno < NUM_METAS || pgno > envinfo.mi_last_pgno)
problem_add("entry", txnid, "wrong idl entry",
"%u < %" PRIaPGNO " < %" PRIu64, NUM_METAS, pgno,
envinfo.mi_last_pgno);
else {
if (MDBX_PNL_DISORDERED(prev, pgno)) {
pgno_t prev =
MDBX_PNL_ASCENDING ? NUM_METAS - 1 : (pgno_t)envinfo.mi_last_pgno + 1;
pgno_t span = 1;
for (unsigned i = 0; i < number; ++i) {
const pgno_t pg = iptr[i];
if (pg < NUM_METAS || pg > envinfo.mi_last_pgno)
problem_add("entry", txnid, "wrong idl entry",
"%u < %" PRIaPGNO " < %" PRIu64 "", NUM_METAS, pg,
envinfo.mi_last_pgno);
else if (MDBX_PNL_DISORDERED(prev, pg)) {
bad = " [bad sequence]";
problem_add("entry", txnid, "bad sequence",
"%" PRIaPGNO " <> %" PRIaPGNO, prev, pgno);
}
if (walk.pagemap && walk.pagemap[pgno]) {
if (walk.pagemap[pgno] > 0)
problem_add("page", pgno, "already used", "by %s",
walk.dbi[walk.pagemap[pgno] - 1].name);
else
problem_add("page", pgno, "already listed in GC", nullptr);
walk.pagemap[pgno] = -1;
"%" PRIaPGNO " <> %" PRIaPGNO "", prev, pg);
}
prev = pg;
while (i + span < number &&
iptr[i + span] == (MDBX_PNL_ASCENDING ? pgno_add(pg, span)
: pgno_sub(pg, span)))
++span;
}
prev = pgno;
while (i + span < number &&
iptr[i + span] == (MDBX_PNL_ASCENDING ? pgno_add(pgno, span)
: pgno_sub(pgno, span)))
++span;
}
if (verbose > 3 && !only_subdb) {
print(" transaction %" PRIaTXN ", %" PRIuPTR
" pages, maxspan %" PRIaPGNO "%s\n",
txnid, number, span, bad);
if (verbose > 4) {
for (unsigned i = 0; i < number; i += span) {
const pgno_t pgno = iptr[i];
for (span = 1;
i + span < number &&
iptr[i + span] == (MDBX_PNL_ASCENDING ? pgno_add(pgno, span)
: pgno_sub(pgno, span));
++span)
;
if (span > 1) {
print(" %9" PRIaPGNO "[%" PRIaPGNO "]\n", pgno, span);
} else
print(" %9" PRIaPGNO "\n", pgno);
if (verbose > 2 && !only_subdb) {
print(" transaction %" PRIaTXN ", %" PRIaPGNO
" pages, maxspan %" PRIaPGNO "%s\n",
txnid, number, span, bad);
if (verbose > 3) {
for (unsigned i = 0; i < number; i += span) {
const pgno_t pg = iptr[i];
for (span = 1;
i + span < number &&
iptr[i + span] == (MDBX_PNL_ASCENDING ? pgno_add(pg, span)
: pgno_sub(pg, span));
++span)
;
if (span > 1) {
print(" %9" PRIaPGNO "[%" PRIaPGNO "]\n", pg, span);
} else
print(" %9" PRIaPGNO "\n", pg);
}
}
}
}
@@ -510,8 +443,7 @@ static int handle_maindb(const uint64_t record_number, const MDBX_val *key,
return handle_userdb(record_number, key, data);
}
static int process_db(MDBX_dbi dbi_handle, char *dbi_name, visitor *handler,
bool silent) {
static int process_db(MDBX_dbi dbi, char *name, visitor *handler, bool silent) {
MDBX_cursor *mc;
MDBX_stat ms;
MDBX_val key, data;
@@ -524,23 +456,22 @@ static int process_db(MDBX_dbi dbi_handle, char *dbi_name, visitor *handler,
uint64_t record_count = 0, dups = 0;
uint64_t key_bytes = 0, data_bytes = 0;
if (dbi_handle == ~0u) {
rc = mdbx_dbi_open(txn, dbi_name, 0, &dbi_handle);
if (dbi == ~0u) {
rc = mdbx_dbi_open(txn, name, 0, &dbi);
if (rc) {
if (!dbi_name ||
if (!name ||
rc !=
MDBX_INCOMPATIBLE) /* LY: mainDB's record is not a user's DB. */ {
error("mdbx_open '%s' failed, error %d %s\n",
dbi_name ? dbi_name : "main", rc, mdbx_strerror(rc));
error(" - mdbx_open '%s' failed, error %d %s\n", name ? name : "main",
rc, mdbx_strerror(rc));
}
return rc;
}
}
if (dbi_handle >= CORE_DBS && dbi_name && only_subdb &&
strcmp(only_subdb, dbi_name)) {
if (dbi >= CORE_DBS && name && only_subdb && strcmp(only_subdb, name)) {
if (verbose) {
print("Skip processing '%s'...\n", dbi_name);
print("Skip processing '%s'...\n", name);
fflush(NULL);
}
skipped_subdb++;
@@ -548,24 +479,24 @@ static int process_db(MDBX_dbi dbi_handle, char *dbi_name, visitor *handler,
}
if (!silent && verbose) {
print("Processing '%s'...\n", dbi_name ? dbi_name : "@MAIN");
print("Processing '%s'...\n", name ? name : "main");
fflush(NULL);
}
rc = mdbx_dbi_flags(txn, dbi_handle, &flags);
rc = mdbx_dbi_flags(txn, dbi, &flags);
if (rc) {
error("mdbx_dbi_flags failed, error %d %s\n", rc, mdbx_strerror(rc));
error(" - mdbx_dbi_flags failed, error %d %s\n", rc, mdbx_strerror(rc));
return rc;
}
rc = mdbx_dbi_stat(txn, dbi_handle, &ms, sizeof(ms));
rc = mdbx_dbi_stat(txn, dbi, &ms, sizeof(ms));
if (rc) {
error("mdbx_dbi_stat failed, error %d %s\n", rc, mdbx_strerror(rc));
error(" - mdbx_dbi_stat failed, error %d %s\n", rc, mdbx_strerror(rc));
return rc;
}
if (!silent && verbose) {
print(" - dbi-id %d, flags:", dbi_handle);
print(" - dbi-id %d, flags:", dbi);
if (!flags)
print(" none");
else {
@@ -584,32 +515,9 @@ static int process_db(MDBX_dbi dbi_handle, char *dbi_name, visitor *handler,
}
}
walk_dbi_t *dbi = (dbi_handle < CORE_DBS)
? &walk.dbi[dbi_handle]
: pagemap_lookup_dbi(dbi_name, true);
if (!dbi) {
error("too many DBIs or out of memory\n");
return MDBX_ENOMEM;
}
const uint64_t subtotal_pages =
ms.ms_branch_pages + ms.ms_leaf_pages + ms.ms_overflow_pages;
if (subtotal_pages != dbi->pages.total)
error("%s pages mismatch (%" PRIu64 " != walked %" PRIu64 ")\n", "subtotal",
subtotal_pages, dbi->pages.total);
if (ms.ms_branch_pages != dbi->pages.branch)
error("%s pages mismatch (%" PRIu64 " != walked %" PRIu64 ")\n", "branch",
ms.ms_branch_pages, dbi->pages.branch);
const uint64_t allleaf_pages = dbi->pages.leaf + dbi->pages.leaf_dupfixed;
if (ms.ms_leaf_pages != allleaf_pages)
error("%s pages mismatch (%" PRIu64 " != walked %" PRIu64 ")\n", "all-leaf",
ms.ms_leaf_pages, allleaf_pages);
if (ms.ms_overflow_pages != dbi->pages.large_volume)
error("%s pages mismatch (%" PRIu64 " != walked %" PRIu64 ")\n",
"large/overlow", ms.ms_overflow_pages, dbi->pages.large_volume);
rc = mdbx_cursor_open(txn, dbi_handle, &mc);
rc = mdbx_cursor_open(txn, dbi, &mc);
if (rc) {
error("mdbx_cursor_open failed, error %d %s\n", rc, mdbx_strerror(rc));
error(" - mdbx_cursor_open failed, error %d %s\n", rc, mdbx_strerror(rc));
return rc;
}
@@ -643,11 +551,11 @@ static int process_db(MDBX_dbi dbi_handle, char *dbi_name, visitor *handler,
if (prev_key.iov_base) {
if ((flags & MDBX_DUPFIXED) && prev_data.iov_len != data.iov_len) {
problem_add("entry", record_count, "different data length",
"%" PRIuPTR " != %" PRIuPTR, prev_data.iov_len,
"%" PRIuPTR " != %" PRIuPTR "", prev_data.iov_len,
data.iov_len);
}
int cmp = mdbx_cmp(txn, dbi_handle, &prev_key, &key);
int cmp = mdbx_cmp(txn, dbi, &prev_key, &key);
if (cmp > 0) {
problem_add("entry", record_count, "broken ordering of entries", NULL);
} else if (cmp == 0) {
@@ -655,7 +563,7 @@ static int process_db(MDBX_dbi dbi_handle, char *dbi_name, visitor *handler,
if (!(flags & MDBX_DUPSORT))
problem_add("entry", record_count, "duplicated entries", NULL);
else if (flags & MDBX_INTEGERDUP) {
cmp = mdbx_dcmp(txn, dbi_handle, &prev_data, &data);
cmp = mdbx_dcmp(txn, dbi, &prev_data, &data);
if (cmp > 0)
problem_add("entry", record_count,
"broken ordering of multi-values", NULL);
@@ -683,13 +591,13 @@ static int process_db(MDBX_dbi dbi_handle, char *dbi_name, visitor *handler,
rc = mdbx_cursor_get(mc, &key, &data, MDBX_NEXT);
}
if (rc != MDBX_NOTFOUND)
error("mdbx_cursor_get failed, error %d %s\n", rc, mdbx_strerror(rc));
error(" - mdbx_cursor_get failed, error %d %s\n", rc, mdbx_strerror(rc));
else
rc = 0;
if (record_count != ms.ms_entries)
problem_add("entry", record_count, "differentent number of entries",
"%" PRIuPTR " != %" PRIuPTR, record_count, ms.ms_entries);
"%" PRIuPTR " != %" PRIuPTR "", record_count, ms.ms_entries);
bailout:
problems_count = problems_pop(saved_list);
if (!silent && verbose) {
@@ -817,7 +725,7 @@ void verbose_meta(int num, txnid_t txnid, uint64_t sign) {
print(", stay");
if (txnid > envinfo.mi_recent_txnid &&
(envflags & (MDBX_EXCLUSIVE | MDBX_RDONLY)) == MDBX_EXCLUSIVE)
(exclusive || (envflags & MDBX_RDONLY) == 0))
print(", rolled-back %" PRIu64 " (%" PRIu64 " >>> %" PRIu64 ")",
txnid - envinfo.mi_recent_txnid, txnid, envinfo.mi_recent_txnid);
print("\n");
@@ -827,7 +735,7 @@ static int check_meta_head(bool steady) {
switch (meta_recent(steady)) {
default:
assert(false);
error("unexpected internal error (%s)\n",
error(" - unexpected internal error (%s)\n",
steady ? "meta_steady_head" : "meta_weak_head");
__fallthrough;
case 0:
@@ -889,9 +797,7 @@ int main(int argc, char *argv[]) {
}
#endif
dbi_meta.name = "@META";
dbi_free.name = "@GC";
dbi_main.name = "@MAIN";
walk.dbi[FREE_DBI].name = "@gc";
atexit(pagemap_cleanup);
if (argc < 2) {
@@ -918,7 +824,7 @@ int main(int argc, char *argv[]) {
envflags &= ~MDBX_RDONLY;
break;
case 'c':
envflags &= ~MDBX_EXCLUSIVE;
exclusive = 0;
break;
case 'd':
dont_traversal = 1;
@@ -966,19 +872,7 @@ int main(int argc, char *argv[]) {
goto bailout;
}
rc = mdbx_env_open(env, envname, envflags, 0664);
if ((envflags & MDBX_EXCLUSIVE) &&
(rc == MDBX_BUSY ||
#if defined(_WIN32) || defined(_WIN64)
rc == ERROR_LOCK_VIOLATION || rc == ERROR_SHARING_VIOLATION
#else
rc == EBUSY
#endif
)) {
envflags &= ~MDBX_EXCLUSIVE;
rc = mdbx_env_open(env, envname, envflags, 0664);
}
rc = mdbx_env_open_ex(env, envname, envflags, 0664, &exclusive);
if (rc) {
error("mdbx_env_open failed, error %d %s\n", rc, mdbx_strerror(rc));
if (rc == MDBX_WANNA_RECOVERY && (envflags & MDBX_RDONLY))
@@ -986,8 +880,7 @@ int main(int argc, char *argv[]) {
goto bailout;
}
if (verbose)
print(" - %s mode\n",
(envflags & MDBX_EXCLUSIVE) ? "monopolistic" : "cooperative");
print(" - %s mode\n", exclusive ? "monopolistic" : "cooperative");
if ((envflags & MDBX_RDONLY) == 0) {
rc = mdbx_txn_lock(env, false);
@@ -1072,7 +965,7 @@ int main(int argc, char *argv[]) {
++problems_meta;
}
if (envflags & MDBX_EXCLUSIVE) {
if (exclusive > 1) {
if (verbose)
print(" - performs full check recent-txn-id with meta-pages\n");
problems_meta += check_meta_head(true);
@@ -1116,44 +1009,25 @@ int main(int argc, char *argv[]) {
for (uint64_t n = 0; n < lastpgno; ++n)
if (!walk.pagemap[n])
unused_pages += 1;
walk.dbi[FREE_DBI].pages.total += 1;
empty_pages = lost_bytes = 0;
for (walk_dbi_t *dbi = &dbi_main; dbi < walk.dbi + MAX_DBI && dbi->name;
++dbi) {
for (walk_dbi_t *dbi = walk.dbi; ++dbi < walk.dbi + MAX_DBI && dbi->name;) {
empty_pages += dbi->pages.empty;
lost_bytes += dbi->lost_bytes;
}
if (verbose) {
uint64_t total_page_bytes = walk.pgcount * envstat.ms_psize;
print(" - pages: total %" PRIu64 ", unused %" PRIu64 "\n", walk.pgcount,
unused_pages);
if (verbose > 1) {
for (walk_dbi_t *dbi = walk.dbi; dbi < walk.dbi + MAX_DBI && dbi->name;
++dbi) {
print(" %s: subtotal %" PRIu64, dbi->name, dbi->pages.total);
if (dbi->pages.other && dbi->pages.other != dbi->pages.total)
print(", other %" PRIu64, dbi->pages.other);
if (dbi->pages.branch)
print(", branch %" PRIu64, dbi->pages.branch);
if (dbi->pages.large_count)
print(", large %" PRIu64, dbi->pages.large_count);
uint64_t all_leaf = dbi->pages.leaf + dbi->pages.leaf_dupfixed;
if (all_leaf) {
print(", leaf %" PRIu64, all_leaf);
if (verbose > 2)
print(" (usual %" PRIu64 ", sub-dupsort %" PRIu64
", dupfixed %" PRIu64 ", sub-dupfixed %" PRIu64 ")",
dbi->pages.leaf, dbi->pages.subleaf_dupsort,
dbi->pages.leaf_dupfixed, dbi->pages.subleaf_dupfixed);
}
print("\n");
}
}
print(" - dbi pages: %" PRIu64 " total", walk.pgcount);
if (verbose > 1)
print(" - usage: total %" PRIu64 " bytes, payload %" PRIu64
for (walk_dbi_t *dbi = walk.dbi;
++dbi < walk.dbi + MAX_DBI && dbi->name;)
print(", %s %" PRIu64, dbi->name, dbi->pages.total);
print(", %s %" PRIu64 "\n", walk.dbi[FREE_DBI].name,
walk.dbi[FREE_DBI].pages.total);
if (verbose > 1) {
print(" - space info: total %" PRIu64 " bytes, payload %" PRIu64
" (%.1f%%), unused "
"%" PRIu64 " (%.1f%%)\n",
total_page_bytes, walk.total_payload_bytes,
@@ -1161,9 +1035,8 @@ int main(int argc, char *argv[]) {
total_page_bytes - walk.total_payload_bytes,
(total_page_bytes - walk.total_payload_bytes) * 100.0 /
total_page_bytes);
if (verbose > 2) {
for (walk_dbi_t *dbi = walk.dbi; dbi < walk.dbi + MAX_DBI && dbi->name;
++dbi) {
for (walk_dbi_t *dbi = walk.dbi;
++dbi < walk.dbi + MAX_DBI && dbi->name;) {
uint64_t dbi_bytes = dbi->pages.total * envstat.ms_psize;
print(" %s: subtotal %" PRIu64 " bytes (%.1f%%),"
" payload %" PRIu64 " (%.1f%%), unused %" PRIu64 " (%.1f%%)",
@@ -1194,12 +1067,12 @@ int main(int argc, char *argv[]) {
if (!verbose)
print("Iterating DBIs...\n");
problems_maindb = process_db(~0u, /* MAIN_DBI */ NULL, NULL, false);
problems_freedb = process_db(FREE_DBI, "@GC", handle_freedb, false);
problems_freedb = process_db(FREE_DBI, "free", handle_freedb, false);
if (verbose) {
uint64_t value = envinfo.mi_mapsize / envstat.ms_psize;
double percent = value / 100.0;
print(" - space: %" PRIu64 " total pages", value);
print(" - pages info: %" PRIu64 " total", value);
value = envinfo.mi_geo.current / envinfo.mi_dxb_pagesize;
print(", backed %" PRIu64 " (%.1f%%)", value, value / percent);
print(", allocated %" PRIu64 " (%.1f%%)", lastpgno, lastpgno / percent);
@@ -1208,12 +1081,12 @@ int main(int argc, char *argv[]) {
value = envinfo.mi_mapsize / envstat.ms_psize - lastpgno;
print(", remained %" PRIu64 " (%.1f%%)", value, value / percent);
value = lastpgno - gc_pages;
value = lastpgno - freedb_pages;
print(", used %" PRIu64 " (%.1f%%)", value, value / percent);
print(", gc %" PRIu64 " (%.1f%%)", gc_pages, gc_pages / percent);
print(", gc %" PRIu64 " (%.1f%%)", freedb_pages, freedb_pages / percent);
value = gc_pages - reclaimable_pages;
value = freedb_pages - reclaimable_pages;
print(", detained %" PRIu64 " (%.1f%%)", value, value / percent);
print(", reclaimable %" PRIu64 " (%.1f%%)", reclaimable_pages,
@@ -1226,15 +1099,14 @@ int main(int argc, char *argv[]) {
}
if (problems_maindb == 0 && problems_freedb == 0) {
if (!dont_traversal &&
(envflags & (MDBX_EXCLUSIVE | MDBX_RDONLY)) != MDBX_RDONLY) {
if (walk.pgcount != lastpgno - gc_pages) {
if (!dont_traversal && (exclusive || (envflags & MDBX_RDONLY) == 0)) {
if (walk.pgcount != lastpgno - freedb_pages) {
error("used pages mismatch (%" PRIu64 " != %" PRIu64 ")\n",
walk.pgcount, lastpgno - gc_pages);
walk.pgcount, lastpgno - freedb_pages);
}
if (unused_pages != gc_pages) {
error("gc pages mismatch (%" PRIu64 " != %" PRIu64 ")\n", unused_pages,
gc_pages);
if (walk.dbi[FREE_DBI].pages.total != freedb_pages) {
error("gc pages mismatch (%" PRIu64 " != %" PRIu64 ")\n",
walk.dbi[FREE_DBI].pages.total, freedb_pages);
}
} else if (verbose) {
print(" - skip check used and gc pages (btree-traversal with "
@@ -1276,8 +1148,8 @@ bailout:
total_problems += problems_meta;
if (total_problems || problems_maindb || problems_freedb) {
print("Total %" PRIu64 " error%s detected, elapsed %.3f seconds.\n",
total_problems, (total_problems > 1) ? "s are" : " is", elapsed);
print("Total %" PRIu64 " error(s) is detected, elapsed %.3f seconds.\n",
total_problems, elapsed);
if (problems_meta || problems_maindb || problems_freedb)
return EXIT_FAILURE_CHECK_MAJOR;
return EXIT_FAILURE_CHECK_MINOR;

View File

@@ -136,7 +136,7 @@ static void readhdr(void) {
ptr = memchr(dbuf.iov_base, '\n', dbuf.iov_len);
if (ptr)
*ptr = '\0';
i = sscanf((char *)dbuf.iov_base + STRLENOF("mapsize="), "%" PRIu64,
i = sscanf((char *)dbuf.iov_base + STRLENOF("mapsize="), "%" PRIu64 "",
&envinfo.mi_mapsize);
if (i != 1) {
fprintf(stderr, "%s: line %" PRIiSIZE ": invalid mapsize %s\n", prog,

View File

@@ -14,12 +14,12 @@
#include "./bits.h"
#if MDBX_VERSION_MAJOR != 0 || MDBX_VERSION_MINOR != 2
#if MDBX_VERSION_MAJOR != 0 || MDBX_VERSION_MINOR != 1
#error "API version mismatch!"
#endif
#define MDBX_VERSION_RELEASE 0
#define MDBX_VERSION_REVISION 2
#define MDBX_VERSION_RELEASE 6
#define MDBX_VERSION_REVISION 1
/*LIBMDBX_EXPORTS*/ const mdbx_version_info mdbx_version = {
MDBX_VERSION_MAJOR,

View File

@@ -80,10 +80,6 @@
#include "../src/defs.h"
#include "../src/osal.h"
#if !defined(__thread) && (defined(_MSC_VER) || defined(__DMC__))
#define __thread __declspec(thread)
#endif /* __thread */
#ifdef _MSC_VER
#pragma warning(pop)
#pragma warning(disable : 4201) /* nonstandard extension used : \

View File

@@ -556,7 +556,7 @@ unsigned actor_params::mdbx_keylen_min() const {
unsigned actor_params::mdbx_keylen_max() const {
return (table_flags & MDBX_INTEGERKEY)
? 8
: std::min((unsigned)mdbx_limits_keysize_max(pagesize),
: std::min((unsigned)mdbx_get_maxkeysize(pagesize),
(unsigned)UINT16_MAX);
}
@@ -568,7 +568,7 @@ unsigned actor_params::mdbx_datalen_max() const {
return (table_flags & MDBX_INTEGERDUP)
? 8
: std::min((table_flags & MDBX_DUPSORT)
? (unsigned)mdbx_limits_keysize_max(pagesize)
? (unsigned)mdbx_get_maxkeysize(pagesize)
: (unsigned)MDBX_MAXDATASIZE,
(unsigned)UINT16_MAX);
}

View File

@@ -24,7 +24,7 @@ function probe {
echo "${caption}: $*"
rm -f ${TESTDB_PREFIX}* \
&& ./mdbx_test --pathname=${TESTDB_PREFIX}db "$@" | lz4 > ${TESTDB_PREFIX}log.lz4 \
&& ./mdbx_chk -nvvv ${TESTDB_PREFIX}db | tee ${TESTDB_PREFIX}chk \
&& ./mdbx_chk -nvv ${TESTDB_PREFIX}db | tee ${TESTDB_PREFIX}chk \
|| (echo "FAILED"; exit 1)
}
@@ -52,11 +52,11 @@ for nops in {2..7}; do
caption="Probe #$((++count)) w/o-dups, repeat ${rep} of ${loops}" probe \
--pagesize=min --size=6G --table=-data.dups --keylen.min=min --keylen.max=max --datalen.min=min --datalen.max=1111 \
--nops=$( rep9 $nops ) --batch.write=$( rep9 $wbatch ) --mode=$(bits2list options $bits) \
--keygen.seed=${seed} basic
--keygen.seed=${seed} --hill
caption="Probe #$((++count)) with-dups, repeat ${rep} of ${loops}" probe \
--pagesize=min --size=6G --table=+data.dups --keylen.min=min --keylen.max=max --datalen.min=min --datalen.max=max \
--nops=$( rep9 $nops ) --batch.write=$( rep9 $wbatch ) --mode=$(bits2list options $bits) \
--keygen.seed=${seed} basic
--keygen.seed=${seed} --hill
done
done
done

View File

@@ -114,7 +114,6 @@ bool testcase_hill::run() {
log_trace("uphill: update-a (age %" PRIu64 "->0) %" PRIu64, age_shift,
a_serial);
generate_pair(a_serial, a_key, a_data_0, 0);
checkdata("uphill: update-a", dbi, a_key->value, a_data_1->value);
rc = mdbx_replace(txn_guard.get(), dbi, &a_key->value, &a_data_0->value,
&a_data_1->value, update_flags);
if (unlikely(rc != MDBX_SUCCESS))
@@ -127,7 +126,6 @@ bool testcase_hill::run() {
// удаляем вторую запись
log_trace("uphill: delete-b %" PRIu64, b_serial);
checkdata("uphill: delete-b", dbi, b_key->value, b_data->value);
rc = mdbx_del(txn_guard.get(), dbi, &b_key->value, &b_data->value);
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_del(b)", rc);
@@ -160,7 +158,6 @@ 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);
checkdata("downhill: update-a", dbi, a_key->value, a_data_0->value);
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))
@@ -187,7 +184,6 @@ bool testcase_hill::run() {
// удаляем первую запись
log_trace("downhill: delete-a (age %" PRIu64 ") %" PRIu64, age_shift,
a_serial);
checkdata("downhill: delete-a", dbi, a_key->value, a_data_1->value);
rc = mdbx_del(txn_guard.get(), dbi, &a_key->value, &a_data_1->value);
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_del(a)", rc);
@@ -199,7 +195,6 @@ bool testcase_hill::run() {
// удаляем вторую запись
log_trace("downhill: delete-b %" PRIu64, b_serial);
checkdata("downhill: delete-b", dbi, b_key->value, b_data->value);
rc = mdbx_del(txn_guard.get(), dbi, &b_key->value, &b_data->value);
if (unlikely(rc != MDBX_SUCCESS))
failure_perror("mdbx_del(b)", rc);

View File

@@ -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);

View File

@@ -1,15 +0,0 @@
@echo off
del test.db test.db-lck
:loop
mdbx_test.exe --pathname=test.db --dont-cleanup-after basic > test.log
if errorlevel 1 goto fail
mdbx_chk.exe -nvvv test.db > chk.log
if errorlevel 1 goto fail
goto loop
:fail
echo FAILED

View File

@@ -410,16 +410,6 @@ void testcase::db_table_close(MDBX_dbi handle) {
log_trace("<< testcase::db_table_close");
}
void testcase::checkdata(const char *step, MDBX_dbi handle, MDBX_val key2check,
MDBX_val expected_valued) {
MDBX_val actual_value = expected_valued;
int rc = mdbx_get2(txn_guard.get(), handle, &key2check, &actual_value);
if (unlikely(rc != MDBX_SUCCESS))
failure_perror(step, rc);
if (!is_samedata(&actual_value, &expected_valued))
failure("%s data mismatch", step);
}
//-----------------------------------------------------------------------------
bool test_execute(const actor_config &config) {

View File

@@ -112,8 +112,6 @@ protected:
void fetch_canary();
void update_canary(uint64_t increment);
void kick_progress(bool active) const;
void checkdata(const char *step, MDBX_dbi handle, MDBX_val key2check,
MDBX_val expected_valued);
MDBX_dbi db_table_open(bool create);
void db_table_drop(MDBX_dbi handle);

View File

@@ -91,11 +91,6 @@ bool hex2data(const char *hex_begin, const char *hex_end, void *ptr,
return true;
}
bool is_samedata(const MDBX_val *a, const MDBX_val *b) {
return a->iov_len == b->iov_len &&
memcmp(a->iov_base, b->iov_base, a->iov_len) == 0;
}
//-----------------------------------------------------------------------------
/* TODO: replace my 'libmera' from t1ha. */

View File

@@ -317,7 +317,7 @@ struct simple_checksum {
std::string data2hex(const void *ptr, size_t bytes, simple_checksum &checksum);
bool hex2data(const char *hex_begin, const char *hex_end, void *ptr,
size_t bytes, simple_checksum &checksum);
bool is_samedata(const MDBX_val *a, const MDBX_val *b);
std::string format(const char *fmt, ...);
uint64_t entropy_ticks(void);