ChangeLog
The source code is available on SourceCraft and mirror on GitHub.
Please use the stable branch or the latest release for production environments through staging, and the master branch for development of derivative projects.
Всё будет хорошо!
v0.15.1 at a deep development stage
Cutting-edge release with new core features and internal refactoring.
Important:
- The project website is back online (it was lost in an attack by ukrainian/european bandera terrorists; no source code was damaged) and was significantly expanded:
- a new textbook with about 50 examples, "deep dive" articles on the architecture and internals, and "first steps" guides;
- a more readable API reference layout, with the public C/C++ API documentation completed and refined to pass a strict doxygen audit;
- man pages for the tools (including the new
mdbx_defrag) and a full option reference of every command-line utility.
- The
masterbranch develops v0.15; engineering of the v0.14.x line continues in thestablebranch.
Appreciations:
- Andrea Lanfranchi for extensive bug reporting and assistance with fixing most of the issues below.
- Omar Gylyjov for reporting and fixing the Android database-open failure.
- wangkaiyi for reporting the compacting-copy hang.
- Mohsin Ismail for reporting the preopen-inspection defect.
Backward compatibility breaks:
- The
MDBX_ENABLE_REFUNDbuild option was removed; the zero-cost online auto-compactification it controlled is now always enabled (#193). - The C++
mdbx::env::get_preopen_snapinfo()overloads without an explicit mode argument were removed; the remaining overloads takemode = MDBX_PREOPEN_PROPERLYby default, so ordinary calls keep working, but code relying on the old overload set no longer compiles (#182). - The C++
mdbx::env::mode/mdbx::env::durabilityenums are now scoped (enum class); the formerenv::-level enumerator names are kept as deprecated[[deprecated(...)]]aliases, so existing code keeps compiling while new code should use the scoped form (e.g.mdbx::env::mode::nested_transactionsinstead ofmdbx::env::nested_transactions).
Improvements:
- Introduced a unified bilingual documentation site, six bilingual guides and consolidated engineering documentation.
- Added a new bilingual textbook and integrated it into the documentation site, with the accompanying example programs ported and restructured.
- Added
mdbx_preopen_snapinfo_ex()with an explicit mode (MDBX_PREOPEN_LEGACY,MDBX_PREOPEN_PROPERLY,MDBX_PREOPEN_PESSIMISTIC,MDBX_PREOPEN_OPTIMISTIC) of processing weak checkpoints during a preopen inspection;mdbx_preopen_snapinfo()now corresponds toPROPERLY(#182). - Added the man page for the
mdbx_defragutility and updated the other man pages. - Added the
MDBX_AVOID_FTOKbuild option for the System V IPC locking: it replaces the systemftok()with a key derived from the full file identity, drastically lowering the chance of a key collision (#80). - Added the
-h/--helpoptions to all command-line utilities. - Clarification/refining README and doxygen API descriptions.
- On Windows the attempts to shrink the database file are no longer repeated on every commit when they are doomed to be deferred (while other processes use the database): they are skipped outright and rate-limited by the new
MDBX_opt_shrink_retry_periodoption, which reduces the commit overhead for workloads mixing long-lived readers with the automatic file shrinking (#170). - Added the
MDBX_opt_shrink_retry_periodoption: datafile shrink attempts that are doomed to be deferred are now skipped outright and rate-limited, which reduces the commit overhead for workloads mixing long-lived readers with automatic file shrinking (#170).
Fixes:
-
Data loss and durability:
- A failed disk write could be silently reported as success, and a large write-mapped durable commit could crash on Windows (#47).
- Auxiliary page-write errors were silently ignored, masking a failing disk; on Windows they also clashed with the transaction file lock, so page pre-warming never worked (#203).
- The writer trusted a forged snapshot-age hint from the shared lock file and could reuse pages still referenced by live readers; such a hint is now rejected (#81).
mdbx_drop()accepted the internal free-list handle and destroyed the garbage-collection tree, permanently leaking pages and corrupting accounting; such a call is now rejected (#69).- A failed second in-process open released the first handle's file locks, letting another process take exclusive access (#59).
- A copy (
mdbx_env_copy(),mdbx_txn_copy2pathname()) could delete a pre-existing destination database when its file lock failed, leave an orphan file blocking later copies, or keep the old file's tail after an overwrite (#58, #112).
-
Resistance to corrupted or crafted "poisoned" data:
- A crafted garbage-collection record listing one physical page twice got that page handed out to two b-tree nodes in a single write transaction, silently corrupting data; such records are now rejected (#83).
- A crafted database could drive the engine out of bounds (crash or data corruption) when deleting records, dropping a table, estimating a range, or checking the file; such databases are now detected and rejected (#57, #63, #74, #75).
- Fixed a rare durability bug: a failed disk write during a commit could be silently reported as success, and on Windows a large write-mapped durable transaction could crash the engine while committing (issue #47).
- The defragmentation walk became exponential on a crafted tree with shared sub-trees (a denial of service), and a crafted set of duplicate values could crash or corrupt memory; both are now rejected (#82, #176).
- The shared reader-table occupancy counter from a corrupt lock file drove out-of-bounds scans over the fixed-size reader table; the count is now clamped everywhere (#79, #113).
mdbx_gc_info()looped forever holding the writer lock on a transaction that had already reclaimed a record; the documented visitor stop is now honored as well (#88).mdbx_cursor_scroll()andmdbx_cursor_distance()indexed the cursor stack out of bounds whendeepnesswas aboveINT_MAX(#85).
-
Crashes, hangs and undefined behavior in the core engine:
mdbx_replace()/mdbx_replace_ex()and the C++txn::replace_reserve()used withMDBX_RESERVEcould crash the process or silently lose the new value;MDBX_RESERVEon a table with duplicates is now rejected upfront, asmdbx_put()already did (#166).mdbx_replace_ex()with overlapping buffers could hang or crash the next operation on the same table (#56).mdbx_env_copy()withMDBX_CP_COMPACThung forever when the dedicated writer thread hit a write error (e.g. a closed destination pipe or a full disk); the error is now reported instead (#171).mdbx_txn_rollback()on a nested write transaction could leave the parent unusable or corrupt it when the restart failed; nested transactions are now finalized cleanly (#103, #124).- Cloning a read transaction (
mdbx_txn_clone()) could crash the process on cleanup paths, or yield a clone reading already-recycled pages when a parked origin had been ousted; such clones now fail cleanly withMDBX_OUSTED/MDBX_EINVAL(#128, #148). - Parking and unparking read transactions misbehaved: parking a failed transaction crashed on the later abort, cloning a parked transaction produced an unusable clone, and unparking a broken or ousted transaction reported success; all three now behave as documented (#104).
- A nested-transaction begin failing out-of-memory left the parent transaction in an inconsistent state (#67).
mdbx_cursor_count_ex()wrote past the caller's buffer when given the legacy structure size (#68).- A hardware divide-by-zero in
mdbx_cursor_put(MDBX_MULTIPLE)with a zero element size and a huge count; such a put is now rejected (#73). - Undefined behavior in the JSON-integer key encoding (a negative shift for magnitudes above 2^52, negation of
INT64_MIN, an empty key passed tomemcpy). - An arithmetic overflow miscomputed the maximum database size on 32-bit builds with large RAM.
- The
MDBX_TXN_TRYflag leaked into the transaction state flags. - A forked child treated a failed unlock of an inherited inter-process lock as fatal only at certain log levels; the failure is now always tolerated (#111).
-
Wrong results and API behavior:
mdbx_cursor_get_batch()withMDBX_FIRSTon an already positioned cursor silently returned only the tail of the table instead of rewinding;MDBX_FIRSTnow always starts from the first pair (#151).mdbx_cursor_scroll()andmdbx_cursor_distribute()returned wrong positions or counts on tables with duplicates near the tree height, and distribute corrupted one of the caller's cursors; distribution now uses a private iterator (#84, #123).mdbx_env_stat_ex()under-counted the totals once a named table was used in the transaction (#108).mdbx_estimate_distance()returned a negated result when both cursors were on different duplicates of one key (#86).mdbx_jsonInteger_from_key()decoded the stored integer0asINT64_MAX(#87).mdbx_dbi_rename()stored a stale table record when the handle was in the stale state (e.g. after an aborted transaction), silently corrupting the database (#106).- Table open/rename mishandled the documented pseudo-names and cross-process drops: a crash on
MDBX_CHK_GC/MDBX_CHK_META, success with the old flags instead ofMDBX_INCOMPATIBLE, and success instead ofMDBX_NOTFOUNDfor a table dropped by another process (#107). mdbx_env_chk()leftresult.tablesdangling after the check, and the C++env::chk()threw an internal error for every database with a found problem instead of reporting it (#93).mdbx_strerror()had no message forMDBX_BACKLOG_DEPLETED, so the C++error::what()returned null and crashed typical error handlers (#96).- The non-printf logger passed the full formatted length to the callback even when the message did not fit the application's buffer; the truncated message is now properly terminated (#116).
mdbx_env_warmup()crashed on a created-but-not-opened environment (it now returnsMDBX_EPERM), and setting a sensibleMDBX_opt_gc_time_limitno longer drops the automaticrp_augment_limitto its minimum (#114).- With
MDBX_WRITEMAP,mdbx_txn_info()reported doubled dirty space and lost the spilled pages (the counters shared one memory cell), andmdbx_ratio2digits()returned a string with an uninitialized trailing byte (#117).
-
Windows:
- A long-lived read transaction could deadlock a writer that grew the database file; the file is now grown in place, and a shrink or remap blocked by an active reader is deferred and retried instead of hanging or poisoning the environment (#154).
- Opening a database in exclusive mode switched to the slow flush-based commit path; exclusive-mode commits are now as fast as usual, roughly doubling the exclusive-mode speed (#179).
MDBX_NOMETASYNC/MDBX_SAFE_NOSYNCdegraded to full durable syncs on every commit while a datafile shrink stayed deferred (#154).- The datafile shrink decision depended on discard bookkeeping instead of the real file size (all platforms), so a shrink could be skipped indefinitely (#154).
- A file-lock request that ended after a wait left the granted lock held (blocking later opens) and could corrupt a stack variable; a failed overlapped result also reported success (#110).
MDBX_DBG_*switches given as an empty string were ignored (#109).- A spurious internal assertion failure during page allocation (#52).
- The I/O ring merged only two contiguous write chunks instead of all of them (#111).
-
Tools:
mdbx_chkcrashed on databases with named sub-tables when using the-i/-soptions, and read out of bounds on a corrupted GC record (#49, #76).mdbx_dump/mdbx_loadsilently lost data on round-trip when a key or value started with a space,mdbx_dumpreported success even when writing the dump failed, andmdbx_loadaccepted a truncated dump as a complete database (#61, #70).mdbx_dump -ftruncated the output file before the command line was validated (#71).mdbx_loadfailed with a missing-file error when reading a dump from standard input (#50).- Table names containing control characters: dumping was refused, listing and stat output are now sanitized, and such a name in a
database=header is rejected (#72).
-
C++ API:
- Initializing a non-owning
txn/cursor/envhandle from an owning*_managedtemporary silently dropped ownership — the writer lock, cursor or environment leaked; such moves are now compile-time errors (#77, #121). txn_managed::checkpoint()dropped the handle when the core rejected the transaction, leaking the writer lock until the environment was closed (#89).- The
*_managedwrappers could terminate the process on a failedclose()/abort(), andenv_managed::close()had a use-after-free (#60). - Committing or aborting a parent transaction while its nested child was alive tore the child down and later double-freed its handle; it now fails with
MDBX_BAD_TXN(#92). - Assigning a
std::string_viewtomdbx::bufferwas declarednoexceptalthough it allocates, so an out-of-memory terminated the process instead of throwing a catchable exception (#78). cursor::erase("key")bound string literals to the boolean overloads and erased every value at the cursor position instead of the named key (#90).bufferappend/assign/add_header wrote wrong bytes or read freed storage when the source overlapped the buffer's own storage and it had to grow; such a source is now copied out first (#94, #95, #125, #152).buffer::reserve()shrank the capacity against STL expectations,buffer(txn, slice)rejected user memory and an empty slice, andtxn::extract()/replace()lost the caller's allocator (#99).- A literal
0/NULLpassed as a pointer with a length built a reference to address zero or crashed; it is a compile-time error again, and a null pointer with a non-zero length is rejected withbad_value_sizein everyslice/bufferform, including the reserve methods (#137, #142, #162). - The lookup/scan wrappers threw or returned wrong results in normal cases: an exact match on a multimap, an empty table, the last chunk, and
get_batch()with a small limit (#98, #127). - Several wrappers threw on documented non-error results or returned wrong values:
thread_register()/thread_unregister(),sequence()overflow,commit()of an errored transaction,txn::flags(),env::get_pagesize()under an open reader,amend(true),try_start_write(),setup_debug(), the sync-period setter (#97, #102). - The API did not compile in documented configurations: the experimental-filesystem selector,
buffer(std::to_string(..)),append(basic_string_view),slice(std::span)(#100). - Overload-resolution traps silently picked the wrong function:
if (slice)was always true, a three-argumentcursor::move()misbound its arguments,buffer(p, 3)ranstrleninstead of honoring the length, andhex()/base58()/base64()of a view encoded the pointer bytes instead of the data (#101). - Added the non-throwing
slice::try_as_pod<T>()returning an emptystd::optionalon a size mismatch (#144). - Documentation refined: the
release_all_cursors(false)andunbind_all_cursors()hazards, theenv::chk()tables-pointer lifetime, the single-thread requirement ofenv_managedclose, the throwing behavior of the environment configuration setters, and theappend_producer()precondition (#91, #143, #145, #146).
- Initializing a non-owning
-
Performance:
-
Platform support:
mdbx_env_open()failed withEINVALon Android (f2fs) due to an invalid internal file position (#197).- OpenBSD was excluded from the POSIX-semaphore locking auto-selection, since its
sem_init()always fails for process-shared semaphores and a default build could not open any environment (#115). - The System V IPC locking could destroy another live database's semaphores on a key collision; every semaphore set now carries a signature derived from the database file identity, and the open refuses with an error instead of removing a foreign set (#80, #159).
mdbx_preopen_snapinfo()reported corruption for a database whose newest checkpoint is a weak one of the current boot (e.g. after an unclean shutdown withMDBX_SAFE_NOSYNC); preopen now falls back to the usable steady checkpoint, while read-only opens returnMDBX_WANNA_RECOVERY(#182).
-
Build:
- Fixed MSVC+AddressSanitizer builds failing to compile (#62).
- Fixed linking with compiler-rt for
__builtin_cpu_supports()by Clang. - Conan recipe: added the
mdbx.ltoandmdbx.sanitizeoptions and documented the package binary identity rules.
-
Leaks:
- Four transaction error paths leaked table handles or promoted wrong geometry values: a failed checkpoint or commit-as-read, a write start failing after the snapshot was taken, a nested resize that could not be undone, and a discarded meta write error that hid an I/O failure (#105).
- Fixed the memory leak caused by a table name with zero length.
v0.15.0 at 2026-09-22
Not a release, but a technical tag marking the start of development for a new version branch.
The engineering of subsequent releases of the v0.14.x version line will continue in the stable branch.
In the master branch, development of version v0.15 will begin, with the technical version tag set to v0.15.0.
For early releases and changes see the ChangeLog-NN the git commit history.