summaryrefslogtreecommitdiff
path: root/libs/libmdbx/src/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'libs/libmdbx/src/README.md')
-rw-r--r--libs/libmdbx/src/README.md714
1 files changed, 412 insertions, 302 deletions
diff --git a/libs/libmdbx/src/README.md b/libs/libmdbx/src/README.md
index 92b6542fa7..7c07de316e 100644
--- a/libs/libmdbx/src/README.md
+++ b/libs/libmdbx/src/README.md
@@ -9,9 +9,21 @@ 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;
@@ -24,19 +36,21 @@ Don't miss [Java Native Interface](https://github.com/castortech/mdbxjni) by [Ca
-----
-Nowadays MDBX intended for Linux, and support Windows (since
-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)
@@ -44,52 +58,58 @@ 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_ 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_ allows multiple processes to read and update several key-value
+tables concurrently, while being
+[ACID](https://en.wikipedia.org/wiki/ACID)-compliant, with minimal
+overhead and operation cost of Olog(N).
_libmdbx_ provides
-[serializability](https://en.wikipedia.org/wiki/Serializability) and consistency of data after crash.
-Read-write transactions don't block read-only transactions and are
-[serialized](https://en.wikipedia.org/wiki/Serializability) by [mutex](https://en.wikipedia.org/wiki/Mutual_exclusion).
-
-_libmdbx_ [wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom) provides parallel read transactions
-without atomic operations or synchronization primitives.
-
-_libmdbx_ uses [B+Trees](https://en.wikipedia.org/wiki/B%2B_tree) and [mmap](https://en.wikipedia.org/wiki/Memory-mapped_file),
-doesn't use [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging). This might have caveats for some workloads.
+[serializability](https://en.wikipedia.org/wiki/Serializability) and
+consistency of data after crash. Read-write transactions don't block
+read-only transactions and are
+[serialized](https://en.wikipedia.org/wiki/Serializability) by
+[mutex](https://en.wikipedia.org/wiki/Mutual_exclusion).
+
+_libmdbx_
+[wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom)
+provides parallel read transactions without atomic operations or
+synchronization primitives.
+
+_libmdbx_ uses [B+Trees](https://en.wikipedia.org/wiki/B%2B_tree) and
+[mmap](https://en.wikipedia.org/wiki/Memory-mapped_file), doesn't use
+[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging). This might
+have caveats for some workloads.
### Comparison with other DBs
-
-Because _libmdbx_ is currently overhauled, I think it's better to just link
-[chapter of Comparison with other databases](https://github.com/coreos/bbolt#comparison-with-other-databases) here.
+Because _libmdbx_ is currently overhauled, I think it's better to just
+link [chapter of Comparison with other
+databases](https://github.com/coreos/bbolt#comparison-with-other-databases)
+here.
### History
-
-The _libmdbx_ design is based on [Lightning Memory-Mapped Database](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
-Initial development was going in [ReOpenLDAP](https://github.com/leo-yuriev/ReOpenLDAP) project, about a year later it
-received separate development effort and in autumn 2015 was isolated to separate project, which was
-[presented at Highload++ 2015 conference](http://www.highload.ru/2015/abstracts/1831.html).
-
-Since early 2017 _libmdbx_ is used in [Fast Positive Tables](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 PositiveTables](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.
-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.
+Martin Hedenfalk <martin@bzero.se> - the author of `btree.c` code, which
+was used for begin development of LMDB.
Main features
@@ -98,365 +118,468 @@ Main features
_libmdbx_ inherits all keys features and characteristics from
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database):
-1. Data is stored in ordered map, keys are always sorted, range lookups are supported.
-
-2. Data is [mmaped](https://en.wikipedia.org/wiki/Memory-mapped_file) to memory of each worker DB process, read transactions are zero-copy.
-
-3. Transactions are [ACID](https://en.wikipedia.org/wiki/ACID)-compliant, thanks to
- [MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) and [CoW](https://en.wikipedia.org/wiki/Copy-on-write).
- Writes are strongly serialized and aren't blocked by reads, transactions can't conflict with each other.
- Reads are guaranteed to get only commited data
- ([relaxing serializability](https://en.wikipedia.org/wiki/Serializability#Relaxing_serializability)).
-
-4. Reads and queries are [non-blocking](https://en.wikipedia.org/wiki/Non-blocking_algorithm),
- don't use [atomic operations](https://en.wikipedia.org/wiki/Linearizability#High-level_atomic_operations).
- Readers don't block each other and aren't blocked by writers. Read performance scales linearly with CPU core count.
- > Though "connect to DB" (start of first read transaction in thread) and "disconnect from DB" (shutdown or thread
- > termination) requires to acquire a lock to register/unregister current thread from "readers table"
-
-5. Keys with multiple values are stored efficiently without key duplication, sorted by value, including integers
- (reasonable for secondary indexes).
-
-6. Efficient operation on short fixed length keys, including integer ones.
-
-7. [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write Amplification Factor) и RAF (Read Amplification Factor)
- are Olog(N).
-
-8. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) and transaction journal.
- In case of a crash no recovery needed. No need for regular maintenance. Backups can be made on the fly on working DB
- without freezing writers.
+1. Data is stored in ordered map, keys are always sorted, range lookups
+are supported.
+
+2. Data is [mmaped](https://en.wikipedia.org/wiki/Memory-mapped_file) to
+memory of each worker DB process, read transactions are zero-copy.
+
+3. Transactions are
+[ACID](https://en.wikipedia.org/wiki/ACID)-compliant, thanks to
+[MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control)
+and [CoW](https://en.wikipedia.org/wiki/Copy-on-write). Writes are
+strongly serialized and aren't blocked by reads, transactions can't
+conflict with each other. Reads are guaranteed to get only commited data
+([relaxing serializability](https://en.wikipedia.org/wiki/Serializability#Relaxing_serializability)).
+
+4. Reads and queries are
+[non-blocking](https://en.wikipedia.org/wiki/Non-blocking_algorithm),
+don't use [atomic
+operations](https://en.wikipedia.org/wiki/Linearizability#High-level_atomic_operations).
+Readers don't block each other and aren't blocked by writers. Read
+performance scales linearly with CPU core count.
+ > Though "connect to DB" (start of first read transaction in thread) and
+ > "disconnect from DB" (shutdown or thread termination) requires to
+ > acquire a lock to register/unregister current thread from "readers
+ > table"
+
+5. Keys with multiple values are stored efficiently without key
+duplication, sorted by value, including integers (reasonable for
+secondary indexes).
+
+6. Efficient operation on short fixed length keys, including integer
+ones.
+
+7. [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write
+Amplification Factor) и RAF (Read Amplification Factor) are Olog(N).
+
+8. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) and
+transaction journal. In case of a crash no recovery needed. No need for
+regular maintenance. Backups can be made on the fly on working DB
+ without freezing writers.
9. No custom memory management, all done with standard OS syscalls.
+--------------------------------------------------------------------------------
-Performance comparison
-=====================
+Improvements over LMDB
+======================
-All benchmarks were done by [IOArena](https://github.com/pmwkaa/ioarena)
-and multiple [scripts](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015)
-runs on Lenovo Carbon-2 laptop, i7-4600U 2.1 GHz, 8 Gb RAM,
-SSD SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Gb.
+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.
-### Integral performance
+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.
-Here showed sum of performance metrics in 3 benchmarks:
+4. Support for keys and values of zero length, including sorted
+duplicates.
- - Read/Search on 4 CPU cores machine;
+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()`.
- - Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD) operations
- in sync-write mode (fdatasync is called after each transaction);
+6. Ability to update or delete record and get previous value via
+`mdbx_replace()` Also can update specific multi-value.
- - Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD) operations
- in lazy-write mode (moment to sync data to persistent storage is decided by OS).
+7. `LIFO RECLAIM` mode:
-*Reasons why asynchronous mode isn't benchmarked here:*
+ 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.
- 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.
+ 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. Performance gap is too high to compare in any meaningful way.
+8. Sequence generation via `mdbx_dbi_sequence()`.
-![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-1.png)
+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:
-### Read Scalability
+ * wait for read transaction to finish normally;
-Summary performance with concurrent read/search queries in 1-2-4-8 threads on 4 CPU cores machine.
+ * kill the offending process (signal 9), if separate process is doing
+ long-time read;
-![Comparison #2: Read Scalability](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-2.png)
+ * abort or restart offending read transaction if it's running in sibling
+ thread;
---------------------------------------------------------------------------------
+ * abort current write transaction with returning error code.
-### Sync-write mode
+10. Ability to open DB in exclusive mode with `MDBX_EXCLUSIVE` flag.
- - Linear scale on left and dark rectangles mean arithmetic mean transactions per second;
+11. Ability to get how far current read-only snapshot is from latest
+version of the DB by `mdbx_txn_straggler()`.
- - 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.
+12. Ability to explicitly request update of present record without
+creating new record. Implemented as `MDBX_CURRENT` flag for
+`mdbx_put()`.
-**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.
+13. Fixed `mdbx_cursor_count()`, which returns correct count of
+duplicated for all table types and any cursor position.
-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.
+14. `mdbx_env_info()` to getting additional info, including number of
+the oldest snapshot of DB, which is used by one of the readers.
-![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-3.png)
+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()`.
-### Lazy-write mode
+17. `mdbx_is_dirty()`to find out if key or value is on dirty page, that
+useful to avoid copy-out before updates.
- - Linear scale on left and dark rectangles mean arithmetic mean of thousands transactions per second;
+18. Correct update of current record in `MDBX_CURRENT` mode of
+`mdbx_cursor_put()`, including sorted duplicated.
- - 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.
+19. Check if there is a row with data after current cursor position via
+`mdbx_cursor_eof()`.
-**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).
+20. Additional error code `MDBX_EMULTIVAL`, which is returned by
+`mdbx_put()` and `mdbx_replace()` in case is ambiguous update or delete.
-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.
+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.
-![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-4.png)
+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()`.
-### Async-write mode
+25. Function `mdbx_env_pgwalk()` for page-walking all pages in DB.
- - Linear scale on left and dark rectangles mean arithmetic mean of thousands transactions per second;
+26. Three meta-pages instead of two, this allows to guarantee
+consistently update weak sync-points without risking to corrupt last
+steady sync-point.
- - 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.
+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).
-**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.
+28. Ability to close DB in "dirty" state (without data flush and
+creation of steady synchronization point) via `mdbx_env_close_ex()`.
-In the benchmark each transaction contains combined CRUD operations (2 inserts, 1 read, 1 update, 1 delete).
-Benchmark starts on empty database and after full run the database contains 10,000 small key-value records.
+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.
-![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-5.png)
+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.
--------------------------------------------------------------------------------
-### Cost comparison
-
-Summary of used resources during lazy-write mode benchmarks:
+## Gotchas
- - Read and write IOPS;
+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.
- - Sum of user CPU time and sys CPU time;
+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.
- - 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.
+#### 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.
-All benchmark data is gathered by [getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) syscall and by
-scanning data directory.
+* Altering data during long read operation may exhaust available space
+on persistent storage.
-![Comparison #6: Cost comparison](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-6.png)
+* 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.
-## Gotchas
+* In _LMDB_ this results in degraded performance of all operations of
+syncing data to persistent storage.
-1. At one moment there can be only one writer. But this allows to serialize writes and eliminate any possibility
- of conflict or logical errors during transaction rollback.
-
-2. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) means relatively
- big [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write Amplification Factor).
- Because of this syncing data to disk might be quite resource intensive and be main performance bottleneck
- during intensive write workload.
- > As compromise _libmdbx_ allows several modes of lazy and/or periodic syncing, including `MAPASYNC` mode, which modificate
- > data in memory and asynchronously syncs data to disk, moment to sync is picked by OS.
- >
- > Although this should be used with care, synchronous transactions in a DB with transaction journal will require 2 IOPS
- > minimum (probably 3-4 in practice) because of filesystem overhead, overhead depends on filesystem, not on record
- > count or record size. In _libmdbx_ IOPS count will grow logarithmically depending on record count in DB (height of B+ tree)
- > and will require at least 2 IOPS per transaction too.
-
-3. [CoW](https://en.wikipedia.org/wiki/Copy-on-write)
- for [MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) is done on memory page level with [B+
- trees](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE).
- Therefore altering data requires to copy about Olog(N) memory pages, which uses [memory bandwidth](https://en.wikipedia.org/wiki/Memory_bandwidth) and is main performance bottleneck in `MAPASYNC` mode.
- > This is unavoidable, but isn't that bad. Syncing data to disk requires much more similar operations which will
- > be done by OS, therefore this is noticeable only if data sync to persistent storage is fully disabled.
- > _libmdbx_ allows to safely save data to persistent storage with minimal performance overhead. If there is no need
- > to save data to persistent storage then it's much more preferable to use `std::map`.
-
-
-4. LMDB has a problem of long-time readers which degrades performance and bloats DB
- > _libmdbx_ addresses that, details below.
+* _libmdbx_ 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_.
-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.
+#### 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:
-#### Long-time read transactions problem
+* 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.
-Garbage collection problem exists in all databases one way or another (e.g. VACUUM in PostgreSQL).
-But in _libmdbx_ and LMDB it's even more important because of high performance and deliberate
-simplification of internals with emphasis on performance.
+* During 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.
-* Altering data during long read operation may exhaust available space on persistent storage.
+* _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.
-* If available space is exhausted then any attempt to update data
- results in `MAP_FULL` error until long read operation ends.
+* During DB open _libmdbx_ rollbacks to the last steady synchronization
+point, this guarantees database integrity.
-* Main examples of long readers is hot backup
- and debugging of client application which actively uses read transactions.
+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.
-* In _LMDB_ this results in degraded performance of all operations
- of syncing data to persistent storage.
+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.
-* _libmdbx_ has a mechanism which aborts such operations and `LIFO RECLAIM`
- mode which addresses performance degradation.
+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.
-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.
+Next version of _libmdbx_ will create steady synchronization points
+automatically in async-write mode.
-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_.
+Performance comparison
+======================
-#### Data safety in async-write mode
+All benchmarks were done by [IOArena](https://github.com/pmwkaa/ioarena)
+and multiple [scripts](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015)
+runs on Lenovo Carbon-2 laptop, i7-4600U 2.1 GHz, 8 Gb RAM,
+SSD SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Gb.
-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:
+### Integral performance
-* 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.
+Here showed sum of performance metrics in 3 benchmarks:
-* 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.
+ - Read/Search on 4 CPU cores machine;
-* _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.
+ - Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD)
+ operations in sync-write mode (fdatasync is called after each
+ transaction);
-* During DB open _libmdbx_ rollbacks to the last steady synchronization point, this guarantees database integrity.
+ - 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).
-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.
+*Reasons why asynchronous mode isn't benchmarked here:*
-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.
+ 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.
-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.
+ 2. Performance gap is too high to compare in any meaningful way.
-Next version of _libmdbx_ will create steady synchronization points automatically in async-write mode.
+![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-1.png)
--------------------------------------------------------------------------------
-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.
+### Read Scalability
-2. `OOM-KICK` callback.
+Summary performance with concurrent read/search queries in 1-2-4-8
+threads on 4 CPU cores machine.
- `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:
+![Comparison #2: Read Scalability](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-2.png)
- * wait for read transaction to finish normally;
+--------------------------------------------------------------------------------
- * kill the offending process (signal 9), if separate process is doing long-time read;
+### Sync-write mode
- * abort or restart offending read transaction if it's running in sibling thread;
+ - Linear scale on left and dark rectangles mean arithmetic mean
+ transactions per second;
- * abort current write transaction with returning error code.
+ - 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.
-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).
+**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.
-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()`).
+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.
-5. Ability to get how far current read-only snapshot is from latest version of the DB by `mdbx_txn_straggler()`.
+![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-3.png)
-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()`.
+### Lazy-write mode
-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()`.
+ - Linear scale on left and dark rectangles mean arithmetic mean of
+ thousands transactions per second;
-9. Check if there is a row with data after current cursor position via `mdbx_cursor_eof()`.
+ - Logarithmic scale on right in seconds and yellow intervals mean
+ execution time of transactions. Each interval shows minimal and maximum
+ execution time, cross marks standard deviation.
-10. Ability to explicitly request update of present record without creating new record. Implemented as `MDBX_CURRENT` flag
- for `mdbx_put()`.
+**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).
-11. Ability to update or delete record and get previous value via `mdbx_replace()` Also can update specific multi-value.
+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.
-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.
+![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-4.png)
-14. Ability to open DB in exclusive mode with `MDBX_EXCLUSIVE` flag, e.g. for integrity check.
+--------------------------------------------------------------------------------
-15. Ability to close DB in "dirty" state (without data flush and creation of steady synchronization point)
- via `mdbx_env_close_ex()`.
+### Async-write mode
-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()`.
+ - Linear scale on left and dark rectangles mean arithmetic mean of
+ thousands transactions per second;
-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.
+ - 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.
-18. Ability to open dbi-table with simultaneous setup of comparators for keys and values, via `mdbx_dbi_open_ex()`.
+**1,000,000 transactions in async-write mode**. In case of a crash all
+data will be consistent and state will be right after one of last
+transactions, but lost transaction count is much higher than in
+lazy-write mode. All DB engines in this mode do as little writes as
+possible on persistent storage. _libmdbx_ uses
+[msync(MS_ASYNC)](https://linux.die.net/man/2/msync) in this mode.
-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()`.
+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.
-20. Correct update of current record in `MDBX_CURRENT` mode of `mdbx_cursor_put()`, including sorted duplicated.
+![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-5.png)
-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.
+### Cost comparison
-23. Ability to get value by key and duplicates count by `mdbx_get_ex()`.
+Summary of used resources during lazy-write mode benchmarks:
-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.
+ - Read and write IOPS;
-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.
+ - Sum of user CPU time and sys CPU time;
-26. Sequence generation via `mdbx_dbi_sequence()`.
+ - 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).
-27. Advanced dynamic control over DB size, including ability to choose page size via `mdbx_env_set_geometry()`,
- including on Windows.
+_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.
-28. Three meta-pages instead of two, this allows to guarantee consistently update weak sync-points without risking to
- corrupt last steady sync-point.
+All benchmark data is gathered by
+[getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html)
+syscall and by scanning data directory.
-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.
+![Comparison #6: Cost comparison](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-6.png)
--------------------------------------------------------------------------------
@@ -474,16 +597,3 @@ 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)
-```