libmdbxlibmdbx
документация
Встраиваемая СУБД для надёжных систем

libmdbx

Быстрая и надёжная встраиваемая СУБД формата key-value. Создана для высоконагруженных систем, где важны производительность, устойчивость к сбоям и предсказуемость работы — от встраиваемых устройств до космической телеметрии.

ACID-транзакции
Высокая производительность
Минимальные накладные расходы

Проверено сообществом

libmdbx используется в сотнях проектов с открытым исходным кодом и в инфраструктуре Ethereum (Erigon, Reth и другие), а также вошёл в число победителей конкурса Yandex Open Source среди открытых проектов.

Обзор библиотеки

Что такое libmdbx

libmdbx — исключительно быстрый, компактный и мощный встраиваемый транзакционный key-value движок без WAL. Он позволяет рою многопоточных процессов выполнять ACID-чтение и запись нескольких карт и мультикарт в локально разделяемой базе, обеспечивая экстраординарную производительность при минимальных накладных расходах за счёт отображения в память и операций O(log N) на B+ дереве. Не требует обслуживания и восстановления после сбоя, гарантирует целостность данных и свободно распространяется по лицензии Apache 2.0.

Полный ACID

Атомарные, согласованные, изолированные и долговечные транзакции на основе MVCC и copy-on-write. Целостность данных гарантируется даже после сбоя.

Без WAL и восстановления

Нет журнала упреждающей записи и восстановления после сбоя. Благодаря теневой подкачке страниц база не требует обслуживания и не растёт бесконтрольно.

Высокая производительность

Данные отображаются в память (memory-mapped) и читаются напрямую без копирования. Операции поиска, вставки, обновления и удаления — O(log N) благодаря B+ дереву.

Параллельный доступ

Рой многопоточных процессов выполняет ACID-чтение и запись в общую базу. Читатели не блокируются и не требуют атомарных операций, масштабируясь по ядрам.

Строго последовательные изменения

Изменения вносятся строго последовательно через единственный мьютекс — исключены конфликты транзакций и взаимоблокировки. Писатели не блокируют читателей и наоборот.

Несколько таблиц key-value

Множество таблиц key-value в одном файле данных, включая эффективные multimap: упорядоченные, искомые и обходимые множества значений без дублирования ключей.

Кроссплатформенность

Поддержка Linux, Windows, macOS, Android, iOS, FreeBSD, Solaris и других систем, совместимых с POSIX.1-2008.

Компактность и встраивание

Всего несколько плоских файлов исходного кода, никаких внутренних потоков и серверных процессов. Полностью пригодно для глубокого встраивания.

Возможности и особенности

  • Модель данных key-value, ключи всегда отсортированы
  • Полная поддержка ACID через MVCC и copy-on-write
  • Несколько таблиц key-value в одном файле данных
  • Диапазонные запросы, включая оценку объёма выборки
  • Эффективная работа с короткими ключами фиксированной длины, включая нативные 32/64-битные целые
  • Сверхэффективная поддержка multimap — отсортированные, искомые и обходимые множества значений без дублирования ключей
  • Данные отображаются в память и доступны напрямую, без копирования (zero-copy)
  • Транзакции чтения и записи не блокируют друг друга
  • Нет конфликтов транзакций и взаимоблокировок — изменения вносятся строго последовательно
  • Читатели не блокируются при сохранении snapshot-изоляции
  • Чтение масштабируется линейно по числу ядер CPU
  • Явная дефрагментация и непрерывная компактификация с нулевыми накладными расходами
  • Автоматическая подстройка размера базы на лету
  • Стоимость операций O(log N) благодаря B+ дереву
  • Очень быстрые get(key), ускоренные разделяемым lock-free кэшем
  • Онлайн-горячее резервное копирование
  • Операция append для быстрой пакетной вставки предварительно отсортированных данных
  • Нет WAL и журнала транзакций: не нужны восстановление и обслуживание
  • Гибкий API транзакций с поддержкой вложенности
  • Настраиваемый размер страницы базы

Доработки по сравнению с LMDB

libmdbx — глубоко переработанный и расширенный наследник легендарной Lightning Memory-Mapped Database. Он наследует все преимущества LMDB, но устраняет ряд проблем и добавляет большой набор доработок. В сравнении с LMDB libmdbx заставляет вещи «просто работать» идеально и из коробки, а не тихо и катастрофически ломаться.

  • Ключи могут быть более чем в 2 раза длиннее, чем в LMDB, с поддержкой ключей и значений нулевой длины
  • До 30% быстрее LMDB в CRUD-бенчмарках
  • Автоматическая подстройка размера базы на лету — и увеличение, и уменьшение
  • Непрерывная компактификация базы с нулевыми накладными расходами
  • Единый формат файла базы для 32- и 64-битных сборок
  • Возможность «Big Foot», решающая проблемы производительности с гигантскими транзакциями и сверхбольшими списками номеров страниц
  • Политика LIFO для переработки страниц сборщиком мусора — заметный рост скорости записи
  • Парковка read-транзакций с вытеснением и автоперезапуском и колбэк Handle-Slow-Readers
  • Быстрая оценка объёма результата диапазонного запроса
  • API проверки целостности базы и автономная утилита mdbx_chk
  • Открытие баз в эксклюзивном режиме, в том числе на сетевых шарах
  • Расширенная информация о базе целиком, таблицах key-value, транзакциях и списке читателей
  • Возможность «get-cached» с лёгким прозрачным кэшем
  • Клонирование read-транзакций и «воскрешение» транзакции после fork
  • Автоматическая регулярная синхронизация на диск по порогам и/или таймауту через дешёвый polling
  • Расширенные операции обновления и быстрого удаления
  • Возможность определить, находится ли конкретный элемент на грязной странице
  • Генерация последовательностей и три постоянных 64-битных маркера типа vector-clock
  • Полезные runtime-опции для тонкой настройки движка
Документация и справка

Обзор API

Краткий обзор главных сущностей API libmdbx и указатели на подробную документацию. Библиотека построена вокруг пяти базовых хэндлов, связанных в строгую иерархию: окружение → транзакция → таблица → курсор, и пара ключ/значение.

Базовые сущности API

Тезисный обзор шести блоков C API: пять базовых сущностей и функции статистики и дополнительных операций. Каждый блок свёрнут в двухуровневые спойлеры — от общего к деталям, с точной ссылкой на соответствующую страницу документации в /doxygen.

C++ API

Рекомендуемый нативный интерфейс наравне с C API. Построен поверх C API и добавляет строгую типизацию, RAII-управление ресурсами и исключения вместо кодов ошибок. Подключается одним заголовком mdbx.h++.

  • Типобезопасные срезы mdbx::slice / mdbx::buffer вместо сырых MDBX_val
  • Управляемые RAII-объекты: mdbx::env_managed, txn_managed, cursor_managed
  • Исключения mdbx::error и декларативные режимы ключей и значений

Возможности C++ API, отсутствующие в C API

Типизированная обёртка поверх C API добавляет управление буферами и перекодирование информации, которых нет в C API. Точные ссылки ведут на соответствующие разделы /doxygen.

Подробнее в документации

Примеры

Основные сценарии показаны на C++ API — родном и рекомендуемом интерфейсе libmdbx: базовый CRUD и транзакции ACID с обходом курсором. Все примеры используют RAII и не требуют ручного освобождения ресурсов. Ниже также приведены аналогичные примеры на C и Rust API.

C++

Окружение и базовый CRUD

Окружение создаётся конструктором env_managed и закрывается само. В write-транзакции открывается таблица и сохраняются пары ключ–значение, затем в read-only транзакции значение читается. Перечисление put_mode выбирает режим записи: upsert перезаписывает значение, если ключ уже есть.

C++
#include <mdbx.h++>
#include <iostream>
#include <string>

int main() {
  // RAII: окружение закроется само при выходе из области видимости
  mdbx::env_managed env("mydb.mdb",
      mdbx::env_managed::operate_parameters{});

  // write-транзакция фиксируется при выходе из блока
  {
    auto txn = env.start_write();
    auto db  = txn.open_map("records");
    txn.put(db, "answer",  "42",             mdbx::put_mode::upsert);
    txn.put(db, "greeting", "hello, cosmos", mdbx::put_mode::upsert);
    txn.commit();
  }

  // read-only транзакция даёт согласованный снимок данных
  auto txn  = env.start_read();
  auto db   = txn.open_map("records");
  auto data = txn.get(db, "answer");
  std::cout << "answer = "
            << std::string(data.char_ptr(), data.length()) << "\n";
}

C++

Транзакции ACID и обход курсором

Все изменения применяются атомарно: явный вызов abort() откатывает пакет целиком, commit() фиксирует обе записи. Для чтения используется read-only транзакция и курсор, который перебирает записи в отсортированном порядке. cursor_managed закрывается автоматически — явно освобождать его не нужно.

C++
#include <mdbx.h++>
#include <iostream>
#include <string>

void apply_batch(mdbx::env_managed& env) {
  auto txn = env.start_write();
  auto db  = txn.open_map("records");

  txn.put(db, "alpha", "1", mdbx::put_mode::upsert);
  txn.put(db, "beta",  "2", mdbx::put_mode::upsert);

  if (something_failed) {
    txn.abort();  // откат: ни одна запись не применится
  } else {
    txn.commit(); // фиксация: обе записи видны атомарно
  }
}

void dump(mdbx::env_managed& env) {
  // read-only транзакция + курсор: записи в отсортированном порядке
  auto txn    = env.start_read();
  auto db     = txn.open_map("records");
  auto cursor = txn.open_cursor(db); // RAII, закроется сам

  for (cursor.to_first(); !cursor.eof(); cursor.to_next()) {
    auto key   = cursor.key();
    auto value = cursor.value();
    std::cout << std::string(key.char_ptr(), key.length())
              << " = "
              << std::string(value.char_ptr(), value.length()) << "\n";
  }
}

Примеры на C API

Те же сценарии на классическом C API: родном низкоуровневом интерфейсе libmdbx. Здесь ресурсы управляются вручную — транзакции и курсоры нужно закрывать явно.

Открыть, записать, прочитать

Базовый сценарий на C. Создаётся окружение базы, в write-транзакции открывается таблица и сохраняется пара ключ–значение, затем в read-only транзакции значение читается. Транзакция обязательна даже для чтения — она даёт согласованный снимок данных.

C
#include <mdbx.h>
#include <stdio.h>

int main(void) {
  MDBX_env *env;
  MDBX_txn *txn;
  MDBX_dbi dbi;

  mdbx_env_create(&env);
  /* MDBX_NOSUBDIR: путь — это сам файл данных */
  mdbx_env_open(env, "./mydb.mdb", MDBX_NOSUBDIR, 0664);

  /* Транзакция обязательна, даже для чтения */
  mdbx_txn_begin(env, NULL, MDBX_TXN_READWRITE, &txn);
  mdbx_dbi_open(txn, NULL, MDBX_CREATE, &dbi);

  MDBX_val key = { "answer", 6 };
  MDBX_val val = { "42", 2 };
  mdbx_put(txn, dbi, &key, &val, 0); /* 0 = UPSERT */

  mdbx_txn_commit(txn);

  MDBX_val out = { 0 };
  mdbx_txn_begin(env, NULL, MDBX_TXN_READONLY, &txn);
  mdbx_get(txn, dbi, &key, &out);
  printf("answer = %.*s\n", (int)out.iov_len, (char *)out.iov_base);
  mdbx_txn_abort(txn);

  mdbx_env_close(env);
  return 0;
}
Транзакции ACID и курсоры

Все изменения внутри транзакции применяются атомарно: при ошибке она откатывается целиком. Курсор перебирает записи в отсортированном порядке. В libmdbx курсор всегда закрывается явно — это исключает утечки и ошибки повторного использования.

C
#include <mdbx.h>
#include <stdio.h>

/* Атомарная групповая запись */
mdbx_txn_begin(env, NULL, MDBX_TXN_READWRITE, &txn);

MDBX_val a = { "alpha", 5 }, va = { "1", 1 };
MDBX_val b = { "beta",  4 }, vb = { "2", 1 };
mdbx_put(txn, dbi, &a, &va, 0);
mdbx_put(txn, dbi, &b, &vb, 0);

if (something_failed) {
  mdbx_txn_abort(txn);  /* откат: ни одна запись не применится */
} else {
  mdbx_txn_commit(txn); /* фиксация: обе записи видны атомарно */
}

/* Обход всех записей курсором в отсортированном порядке */
MDBX_cursor *cur;
mdbx_txn_begin(env, NULL, MDBX_TXN_READONLY, &txn);
mdbx_cursor_open(txn, dbi, &cur);

MDBX_val key, val;
while (mdbx_cursor_get(cur, &key, &val, MDBX_NEXT) == MDBX_SUCCESS) {
  printf("%.*s = %.*s\n",
         (int)key.iov_len, (char *)key.iov_base,
         (int)val.iov_len,  (char *)val.iov_base);
}
/* В libmdbx курсор всегда закрывается явно */
mdbx_cursor_close(cur);
mdbx_txn_abort(txn);

Примеры на Rust API

Те же сценарии через высокоуровневые безопасные привязки Rust — идиоматичную и безопасную обёртку над libmdbx. Здесь нет unsafe-блоков: ресурсы управляются через RAII, а ошибки возвращаются через Result.

Открыть, записать, прочитать

Базовый сценарий на Rust через привязки: создаётся окружение базы, в write-транзакции открывается таблица и сохраняются пары ключ–значение, затем в read-only транзакции значение читается. Даже чтение требует транзакции — она даёт согласованный снимок данных.

Rust
use libmdbx::*; // Используем привязки проекта libmdbx-rs, https://github.com/vorot93/libmdbx-rs

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Открываем (или создаём) окружение базы данных
    let db = Database::<NoWriteMap>::open("mydb.mdb")?;

    // write-транзакция: создаём таблицу и сохраняем пары ключ–значение
    {
        let txn   = db.begin_rw_txn()?;
        let table = txn.create_table(Some("records"), TableFlags::empty())?;
        txn.put(&table, "answer", "42", WriteFlags::empty())?;
        txn.put(&table, "greeting", "hello, cosmos", WriteFlags::empty())?;
        txn.commit()?;
    }

    // read-only транзакция даёт согласованный снимок данных
    let txn   = db.begin_ro_txn()?;
    let table = txn.open_table(Some("records"))?;
    if let Some(value) = txn.get::<Vec<u8>>(&table, b"answer")? {
        println!("answer = {}", String::from_utf8_lossy(&value));
    }
    Ok(())
}
Транзакции ACID и курсоры

Все изменения применяются атомарно: без commit транзакция откатится целиком, commit() фиксирует обе записи. Курсор перебирает записи в отсортированном порядке. Пример также построен на привязках libmdbx-rs (https://github.com/vorot93/libmdbx-rs): ресурсы закрываются автоматически, а ошибки обрабатываются через Result.

Rust
use libmdbx::*;

// Атомарная групповая запись; без commit изменения откатываются
fn apply_batch(db: &Database<NoWriteMap>) -> Result<(), libmdbx::Error> {
    let txn   = db.begin_rw_txn()?;
    let table = txn.open_table(Some("records"))?;

    txn.put(&table, "alpha", "1", WriteFlags::empty())?;
    txn.put(&table, "beta",  "2", WriteFlags::empty())?;

    if something_failed {
        // без commit транзакция откатится: ни одна запись не применится
        return Ok(());
    }
    txn.commit()?; // фиксация: обе записи видны атомарно
    Ok(())
}

// Обход всех записей курсором в отсортированном порядке
fn dump(db: &Database<NoWriteMap>) -> Result<(), libmdbx::Error> {
    let txn   = db.begin_ro_txn()?;
    let table = txn.open_table(Some("records"))?;
    let mut cursor = txn.cursor(&table)?;

    for item in cursor.iter_start::<Vec<u8>, Vec<u8>>() {
        let (key, value) = item?;
        println!("{} = {}",
                 String::from_utf8_lossy(&key),
                 String::from_utf8_lossy(&value));
    }
    Ok(())
}
Ссылки и ресурсы

Ресурсы libmdbx

Быстрые ссылки на исходный код, загрузку и внешние ресурсы библиотеки. Скачивайте релизы, изучайте документацию и подключайтесь к сообществу.

Репозиторий

Исходный код библиотеки и релизы на основной площадке и в зеркале.

Загрузка

Стабильные релизы, архивы исходников и история изменений для загрузки.

Документация

Официальное руководство и справочники C/C++ API на сайте проекта.

Сообщество и связь

Каналы обратной связи: вопросы, предложения и улучшения библиотеки.

Вопросы и ответы

Вопросы и ответы

Полные и аргументированные ответы на ключевые вопросы о libmdbx, собранные на основе закрытых issues на GitHub, архива переписки в Telegram, ChangeLog и ответов автора библиотеки. Ответы раскрываются по клику и приведены в структурном виде со всеми аргументами и альтернативными точками зрения.

Советы

Советы

Практические советы по использованию libmdbx, собранные из базы знаний (knowledge-base/tips). Советы отсортированы по востребованности и важности (demand × importance): сначала — то, что влияет на большинство проектов.

Правила и чек-листы

Правила и чек-листы

Правила и чек-листы, собранные из базы знаний (knowledge-base/rules-and-checklist). Они ранжированы по востребованности и важности (demand × importance); чек-листы — пошаговые списки действий, правила — жёсткие ограничения модели libmdbx.