Skip to content

Том VI. Привязки (bindings)

Уровень: для разработчиков, работающих на языках, отличных от C/C++. Цель тома: вы знаете, как использовать libmdbx из своего языка, понимаете специфику и ограничения каждого биндинга. Честность: факты по конкретным обёрткам (репозиторий, версия, баги) требуют анализа их репозиториев и issues. Здесь приведены архитектурные следствия (NoStickyThreads, Send/Sync, GC/pinning, GIL — Global Interpreter Lock, глобальная блокировка интерпретатора в Python); позиции, требующие сверки с репозиторием, помечены явно.


Глава 34. Обзор экосистемы биндингов

Официально отслеживаемые биндинги: Rust, Go, Node.js, Zig, Python, .NET (C#), C++, Dart, Nim, Java, Haskell, Ruby, Scala. Плюс неофициальные (openresty/lua и др.).

Биндинг Язык Статус Подробнее
mdbx.h++ C++ Зрелый (официальный C++ API) Гл. 40
mdbx-rs / mdbx-sys Rust Зрелый/активный Гл. 35
mdbx-go Go Активный Гл. 36
python-lmdbx / mdbx-py Python Активный Гл. 37
node-mdbx Node.js Активный Гл. 38
mdbx-zig (lmdbx-zig) Zig Официально отслеживаемый §34.2
libmdbx-dotnet .NET / C# Активный Гл. 39
mdbx-dart / Isar Dart (Flutter) Активный (Isar использует libmdbx) Гл. 41
Nim Nim — Гл. 42
Java Java — Гл. 43
Haskell Haskell — Гл. 44
Ruby Ruby — Гл. 45
Scala Scala — Гл. 46

Нюанс: карточки Гл. 42–46 (Nim, Java, Haskell, Ruby, Scala) — краткие; детали репозиториев требуют анализа (см. пометки в каждой карточке).

34.1. Что покрывает каждый биндинг

Типичная картина: биндинг оборачивает C API (mdbx_env_*, mdbx_txn_*, mdbx_dbi_*, mdbx_cursor_*, MDBX_val) в идиоматику языка. Полнота покрытия различается: ядро (env/txn/ get/put/del/cursor) покрыто везде; продвинутые API (cache_get, defrag, clone, embark_read, estimate_*) — не всегда. Сверяйте с документацией конкретного биндинга.

34.2. Zig (краткая справка)

mdbx-zig (репозиторий lmdbx-zig) — официально отслеживаемый биндинг для Zig. Известные особенности: Zig активно используется для кросскомпиляции C/C++; для обхода багов тулчейна может понадобиться MDBX_HAVE_BUILTIN_CPU_SUPPORTS=0. Отдельная карточка не выделена — детали репозитория и версии требуют анализа.

34.3. Резюме главы 34

  • Официально отслеживаемые биндинги: Rust, Go, Node.js, Zig, Python, .NET (C#), C++, Dart, Nim, Java, Haskell, Ruby, Scala; плюс неофициальные (openresty/lua и др.).
  • Каждый биндинг оборачивает C API (mdbx_env_*, mdbx_txn_*, mdbx_dbi_*, mdbx_cursor_*) в идиоматику языка.
  • Ядро (env/txn/get/put/del/cursor) покрыто везде; продвинутые API (cache_get, defrag, clone, embark_read, estimate_*) — не всегда.
  • Зрелые/активные: mdbx.h++ (C++), mdbx-rs/mdbx-sys (Rust), mdbx-go (Go), python-lmdbx/mdbx-py, node-mdbx, libmdbx-dotnet, mdbx-dart/Isar.
  • mdbx-zig (lmdbx-zig) — официально отслеживаемый; для обхода багов тулчейна может понадобиться MDBX_HAVE_BUILTIN_CPU_SUPPORTS=0.
  • Карточки Nim/Java/Haskell/Ruby/Scala — краткие; детали репозиториев требуют анализа.

34.4. Чек-лист главы 34

  • [ ] Выбрать биндинг по таблице главы и сверить его статус с репозиторием.
  • [ ] Проверить, какие продвинутые API (cache_get, defrag, clone, embark_read, estimate_*) поддерживает биндинг.
  • [ ] Для Zig при проблемах тулчейна учесть MDBX_HAVE_BUILTIN_CPU_SUPPORTS=0.
  • [ ] Для кратких карточек (Nim/Java/Haskell/Ruby/Scala) проанализировать репозиторий биндинга до production.

Глава 35. Rust (mdbx-sys / mdbx-rs)

Обзор. mdbx-sys — низкоуровневые FFI-привязки к C API; mdbx-rs — безопасная обёртка (идиоматичный Rust). Статус — активный/зрелый. (Детали репозитория требуют сверки.)

Установка. Через cargo: mdbx-sys = "...", mdbx-rs = "...".

Базовый пример (упрощённый, общий для типа):

use mdbx::{Env, Environment};

fn main() -> mdbx::Result<()> {
    let env = unsafe { Env::builder().open("db.mdbx")? };
    let txn = env.begin_rw_txn()?;
    txn.put(b"greeting", b"hello, libmdbx")?;
    txn.commit()?;
    Ok(())
}

(Точный синтаксис зависит от версии биндинга — сверить с его README.)

Критические нюансы (из архитектуры libmdbx):

  • Send/Sync. Транзакция и курсор привязаны к потоку (sticky). Безопасная обёртка должна отражать это в типах: транзакция — !Sync (использование из двух потоков = THREAD_MISMATCH). Для async-runtime (tokio и др.) обязателен MDBX_NOSTICKYTHREADS, т.к. future может продолжиться в другом OS-потоке.
  • Use-after-free после commit/abort. MDBX_val указывает в mmap; после завершения транзакции указатели недействительны. Обёртка должна ограничить время жизни значений временем транзакции (lifetime), а не передавать &[u8] наружу.
  • Блокирующие вызовы в async-контексте. Commit и запись блокируют поток; в async-runtime — только через spawn_blocking, иначе блокируется весь воркер.

Производительность. Накладные биндинга минимальны (FFI + ownership-проверки); чтение остаётся близким к C. (Сверка с бенчмарками биндинга.)

Пробелы. Зависит от версии: cache_get, defrag, clone и др. могут отсутствовать.

Резюме карточки. Двухслойная связка: mdbx-sys — низкоуровневые FFI-привязки к C API, mdbx-rs — безопасная идиоматичная обёртка (установка через cargo). Главные архитектурные следствия — sticky-транзакции (!Sync, MDBX_NOSTICKYTHREADS обязателен для async-runtime) и недействительность MDBX_val-указателей после commit/abort; блокирующие вызовы в async — только через spawn_blocking. Накладные расходы минимальны (FFI + ownership-проверки), продвинутые API (cache_get, defrag, clone) зависят от версии.

Чек-лист карточки.

  • [ ] Для async-runtime (tokio и др.) выставить MDBX_NOSTICKYTHREADS.
  • [ ] Убедиться, что обёртка ограничивает время жизни значений транзакцией (не выдаёт &[u8] после commit/abort).
  • [ ] Блокирующие commit/записи выполнять через spawn_blocking.
  • [ ] Сверить наличие cache_get/defrag/clone с версией биндинга.

Глава 36. Go (mdbx-go)

Обзор. mdbx-go — Go-биндинг через CGO. Статус — активный.

Установка. go get github.com/.../mdbx-go.

Базовый пример:

import "github.com/.../mdbx-go"

env, _ := mdbx.EnvCreate()
env.Open("db.mdbx", mdbx.CREATE|mdbx.NOSUBDIR, 0664)
txn, _ := env.BeginTxn(nil, 0)
dbi, _ := txn.DBIOpen(nil, 0)
txn.Put(dbi, []byte("k"), []byte("v"), 0)
txn.Commit()

(Сигнатуры сверить с репозиторием.)

Критические нюансы (из архитектуры):

  • NoStickyThreads всегда выставляется. Go-горутина может мигрировать между OS-потоками, поэтому биндинг открывает окружение с MDBX_NOSTICKYTHREADS.
  • Один объект транзакции нельзя использовать из двух горутин одновременно — это гонка.
  • Deadlock-риск: функции, требующие write-блокировку среды (Env.SetOption, Env.Sync, Env.Stat, Env.Defrag, Env.Close), при активной write-транзакции в другой горутине могут взаимоблокироваться. Паттерн: одна транзакция — одна горутина, либо явная сериализация через channel/mutex.
  • runtime.LockOSThread нужен, если хотите зафиксировать горутину на OS-потоке (снижает риски при работе с TLS-слотом читателя).
  • CGO overhead — на каждую операцию границы CGO; для хот-путей группируйте операции в батч.

Пробелы. Полнота продвинутого API — сверять с репозиторием.

Резюме карточки. mdbx-go — активный CGO-биндинг (установка — go get). Горутины мигрируют между OS-потоками, поэтому биндинг всегда открывает среду с MDBX_NOSTICKYTHREADS; один объект транзакции нельзя использовать из двух горутин одновременно. Главные риски — deadlock на write-функциях среды и CGO-overhead на каждой операции, который лечится батчингом.

Чек-лист карточки.

  • [ ] Одна транзакция — одна горутина (либо явная сериализация через channel/mutex).
  • [ ] Не вызывать Env.SetOption/Env.Sync/Env.Stat/Env.Defrag/Env.Close из другой горутины при активной write-транзакции.
  • [ ] При необходимости зафиксировать горутину через runtime.LockOSThread.
  • [ ] Для хот-путей группировать операции в батчи (граница CGO).

Глава 37. Python (python-lmdbx / mdbx-py)

Обзор. Python-биндинг поверх C API. Статус — активный (сверка).

Критические нюансы (из архитектуры):

  • GIL и блокирующие вызовы. Долгие read-транзакции и массовые записи держат GIL → другие потоки Python блокируются. Освобождайте GIL в биндинге при длительных операциях.
  • Указатели в mmap действительны, пока жива транзакция. Python GC не управляет этим — нужен явный context manager (with env.begin(...)), чтобы гарантировать commit/abort вовремя.
  • fork() после import. При multiprocessing вызывайте resurrect_after_fork в дочернем процессе.
  • ctypes vs CFFI vs pybind. Выбор влияет на накладные; для батчей — минимизируйте число вызовов границы.

Пробелы. Сверка с репозиторием (Python 3.x версии, wheels, продвинутое API).

Резюме карточки. Python-биндинг поверх C API, статус активный (сверка). Ключевое — GIL: долгие read-транзакции и массовые записи блокируют другие потоки Python; время жизни mmap-указателей нужно гарантировать явно — context manager'ом (with env.begin(...)). При multiprocessing в дочернем процессе обязателен resurrect_after_fork; выбор ctypes/CFFI/pybind влияет на накладные расходы.

Чек-лист карточки.

  • [ ] Гарантировать commit/abort context manager'ом (with env.begin(...)).
  • [ ] Освобождать GIL в биндинге при длительных операциях.
  • [ ] В дочернем процессе после fork() вызывать resurrect_after_fork.
  • [ ] Для батчей минимизировать число вызовов границы (ctypes/CFFI/pybind).

Глава 38. Node.js (node-mdbx)

Обзор. Node.js-биндинг (N-API). Статус — активный (сверка).

Критические нюансы (из архитектуры):

  • Event loop и блокирующие вызовы. Синхронный C API блокирует цикл событий. Обёртка должна предлагать async-варианты (worker threads / libuv) для больших операций.
  • Buffer ↔ MDBX_val. Node.js Buffer — владеющая обёртка; при передаче в libmdbx следите за временем жизни (не передавайте буфер, который может быть освобождён).
  • NoStickyThreads — аналогично Go, если используется worker threads.

Пробелы. Сверка с репозиторием.

Резюме карточки. node-mdbx — активный N-API-биндинг (сверка). Синхронный C API блокирует event loop — обёртка должна предлагать async-варианты (worker threads/libuv) для больших операций; при worker threads нужен MDBX_NOSTICKYTHREADS (как в Go). Node.js Buffer — владеющая обёртка: следите за её временем жизни при передаче в libmdbx.

Чек-лист карточки.

  • [ ] Большие операции выполнять через async-варианты (worker threads/libuv), не блокируя event loop.
  • [ ] Не передавать буферы, время жизни которых не гарантировано.
  • [ ] С worker threads выставлять MDBX_NOSTICKYTHREADS.
  • [ ] Сверить возможности биндинга с репозиторием.

Глава 39. .NET / C# (libmdbx-dotnet)

Обзор. .NET-биндинг поверх C API. Альтернатива в экосистеме — LightningDB (не libmdbx! — проверяйте, что именно вы используете). Статус — активный (сверка).

Критические нюансы (из архитектуры):

  • Pinning. MDBX_val указывает в mmap; если значение — управляемый буфер, .NET GC может его переместить. Решение: fixed/pinned, либо marshal-копия, либо Span<T>.
  • SafeHandle для env/txn/cursor. Корректное освобождение через SafeHandle/finalizer.
  • GC finalizer и порядок. Закрывайте в правильном порядке: env → txn → cursor; висячие хендлы дают BAD_DBI/BAD_TXN.
  • Непредсказуемое время жизни из-за GC — не держите длинные транзакции между сборками.

Пробелы. Сверка с репозиторием (версии, платформы, продвинутое API).

Резюме карточки. libmdbx-dotnet — активный .NET-биндинг поверх C API (сверка); не путать с LightningDB (это не libmdbx). Главное — pinning: MDBX_val указывает в mmap, а .NET GC может переместить управляемый буфер (fixed/pinned, marshal-копия или Span<T>). Освобождение — через SafeHandle/finalizer в порядке env → txn → cursor, висячие хендлы дают BAD_DBI/BAD_TXN.

Чек-лист карточки.

  • [ ] Пиннировать управляемые буферы (fixed/marshal-копия/Span<T>), не полагаясь на GC.
  • [ ] Освобождать env/txn/cursor через SafeHandle/finalizer в правильном порядке.
  • [ ] Не держать длинные транзакции между сборками GC.
  • [ ] Проверить, что используется именно libmdbx-биндинг, а не LightningDB.

Глава 40. C++ (mdbx.h++)

Обзор. Официальный C++ API (mdbx.h++), канон v0.15.0-263. Зрелый.

Иерархия классов:

  • env / env_managed — окружение; txn / txn_managed — транзакции; cursor / cursor_managed — курсоры; map_handle — таблица; slice / buffer — ключи/значения.
  • Управляемые варианты (*_managed) — RAII: автоматическое закрытие при выходе из области видимости.

Нюанс: slice — невладеющий, buffer — владеющий. mdbx::slice — лишь представление над MDBX_val (аналог std::string_view): ссылка на чужие байты без копирования. Поэтому mdbx::slice(std::to_string(k)) в аргументе вызова безопасна (временная строка живёт до конца полного выражения, а insert() копирует байты в базу синхронно), но хранить такой срез дольше оператора — висячая ссылка. mdbx::buffer по умолчанию хранит собственную копию; режим «ссылка» включается явно (make_reference=true) — тогда действуют правила времени жизни slice.

Типизированные операции. Map-over-keys/values через трансляцию типов; параметры-структуры с fluent-установщиками (геометрия, режим, долговечность, reclaiming).

Исключения. Иерархия mdbx::error → mdbx::exception (от std::runtime_error) → mdbx::fatal; 35 типизированных исключений (bad_map_id, db_corrupted, db_full, key_exists, not_found, transaction_ousted и др.). Фатальные ошибки приводят к завершению.

Фрагмент из examples/c++/47-cpp-api.c++ — типизированные map-операции (insert/upsert через map_handle) и типизированные исключения (mdbx::key_exists и др.):

    {
      auto txn = env.start_write(); // RAII: сам закроется/откатится при исключении
      auto ordinal = txn.create_map("ordinal", mdbx::key_mode::ordinal, mdbx::value_mode::single);
      auto multi = txn.create_map("multi", mdbx::key_mode::usual, mdbx::value_mode::multi);

      txn.insert(ordinal, buffer::key_from_u64(42), "answer");
      txn.upsert(multi, mdbx::slice("tag"), mdbx::slice("x"));
      txn.upsert(multi, mdbx::slice("tag"), mdbx::slice("y"));

      // Типизированные исключения по коду ошибки.
      try {
        txn.insert(ordinal, buffer::key_from_u64(42), "again");
        std::cerr << "FAIL: duplicate insert did not throw\n";
        return EXIT_FAILURE;
      } catch (const mdbx::key_exists &) {
        std::cout << "duplicate -> mdbx::key_exists as expected\n";
      }

      txn.commit();
    }

Полный код: 47-cpp-api.c++.

C++20. Концепты, аллокаторы (buffer<> с политиками владения), inplace_storage_size_rounding.

Пример:

Фрагмент (C++, иллюстрация): полная компилируемая версия — в examples/c++/47-cpp-api.c++. Операции map-стиля (insert, upsert, get, erase) — методы транзакции, а не таблицы.

#include <mdbx.h++>

int main() {
    mdbx::env_managed env("db.mdbx",
                          mdbx::env::operate_parameters{});
    auto txn = env.start_write();
    auto db = txn.create_map("kv", mdbx::key_mode::usual,
                             mdbx::value_mode::single);
    txn.upsert(db, "greeting", "hello, libmdbx");
    txn.commit();
}

Проверьте точный синтаксис по вашей версии mdbx.h++.

Критические нюансы:

  • Значение upper для новой БД движок выбирает сам (~золотое сечение ОЗУ, ограничено mmap-лимитом ≈140 ТБ на 64-бит); TOO_LARGE/ENOMEM возможны при явно чрезмерном upper (например, под ASAN/Valgrind). Всегда задавайте геометрию явно.
  • Исключения vs коды возврата: выбирайте единый стиль; mdbx::error ловится по типам.

Пробелы. key_mode::msgpack объявлен, но не реализован.

Резюме карточки. mdbx.h++ — официальный зрелый C++ API (канон v0.15.0-263): RAII-классы env_managed/txn_managed/cursor_managed, типизированные операции и 35 типизированных исключений через mdbx::error. Геометрию задавайте явно — upper для новой БД движок выбирает сам (~золотое сечение ОЗУ). Известный пробел: key_mode::msgpack объявлен, но не реализован.

Чек-лист карточки.

  • [ ] Использовать RAII-варианты (*_managed) для автоматического закрытия.
  • [ ] Всегда задавать геометрию явно (умолчательный upper выбирает движок).
  • [ ] Выбрать единый стиль ошибок: исключения mdbx::error (ловятся по типам) или коды возврата.
  • [ ] Не использовать key_mode::msgpack (объявлен, но не реализован).

Примеры к главе: examples/c++/47-cpp-api.c++.

Глава 41. Dart (mdbx-dart / Isar)

Обзор. mdbx-dart — биндинг для Dart/Flutter; Isar — популярная локальная БД Flutter, использующая libmdbx. Статус — активный.

Критические нюансы (из архитектуры):

  • Isolate-специфика. Dart-изоляты — отдельные потоки; транзакции не пересекают изоляты (sticky thread). Каждый изолят — свои транзакции.
  • Мобильные платформы. Встроенная БД на Android/iOS: аккуратно с парковкой длинных чтений и малым WAF (продление жизни flash).

Пробелы. Сверка с репозиторием.

Резюме карточки. mdbx-dart — активный биндинг для Dart/Flutter; на libmdbx построена популярная локальная БД Isar. Транзакции не пересекают изоляты (sticky thread) — каждый изолят работает со своими транзакциями. На мобильных платформах (Android/iOS) важны парковка длинных чтений и малый WAF (продление жизни flash).

Чек-лист карточки.

  • [ ] Не передавать транзакции между изолятами (sticky thread).
  • [ ] На Android/iOS аккуратно парковать длинные чтения.
  • [ ] Следить за малым WAF для продления жизни flash.
  • [ ] Сверить детали с репозиторием.

Глава 42. Nim

Обзор. Nim-биндинг. (Данные о репозитории и версии требуют анализа репозитория биндинга.)

Ожидаемые нюансы (из архитектуры): управление памятью (GC Nim не владеет mmap-указателями — нужны ручные ограничения времени жизни); привязка к потокам (sticky).

Резюме карточки. Nim-биндинг; данные о репозитории и версии требуют анализа репозитория. Ожидаемые нюансы: Nim GC не владеет mmap-указателями — нужны ручные ограничения времени жизни; транзакции привязаны к потокам (sticky).

Чек-лист карточки.

  • [ ] Ручно ограничивать время жизни mmap-указателей (GC Nim ими не владеет).
  • [ ] Держать транзакции в породившем их потоке (sticky).
  • [ ] Сверить репозиторий и версию биндинга.

Глава 43. Java

Обзор. Java-биндинг (JNI). (Данные требуют анализа репозитория.)

Ожидаемые нюансы (из архитектуры): pinning критических буферов (JNI GetByteArrayElements или direct ByteBuffer); освобождение native-хендлов (env/txn/cursor) в правильном порядке; многопоточность (каждый поток — свои транзакции).

Резюме карточки. Java-биндинг через JNI; данные требуют анализа репозитория. Ожидаемые нюансы: pinning критических буферов (JNI GetByteArrayElements или direct ByteBuffer), освобождение native-хендлов (env/txn/cursor) в правильном порядке, у каждого потока — свои транзакции.

Чек-лист карточки.

  • [ ] Пиннировать критические буферы (JNI GetByteArrayElements/direct ByteBuffer).
  • [ ] Освобождать native-хендлы (env/txn/cursor) в правильном порядке.
  • [ ] Не использовать одну транзакцию из нескольких потоков.
  • [ ] Сверить данные с репозиторием биндинга.

Глава 44. Haskell

Обзор. Haskell-биндинг (FFI). (Данные требуют анализа репозитория.)

Ожидаемые нюансы (из архитектуры): чистота и эффекты (STM vs IO); привязка транзакций к OS-потокам; время жизни указателей в mmap.

Резюме карточки. Haskell-биндинг через FFI; данные требуют анализа репозитория. Ожидаемые нюансы: согласование чистоты и эффектов (STM vs IO), привязка транзакций к OS-потокам, время жизни указателей в mmap.

Чек-лист карточки.

  • [ ] Разрешить чистоту/эффекты транзакций (STM vs IO).
  • [ ] Учитывать привязку транзакций к OS-потокам.
  • [ ] Ограничить время жизни указателей в mmap.
  • [ ] Сверить данные с репозиторием биндинга.

Глава 45. Ruby

Обзор. Ruby-биндинг. (Данные требуют анализа репозитория.)

Ожидаемые нюансы (из архитектуры): GVL (глобальная блокировка VM Ruby — аналог Python GIL) и блокирующие вызовы; освобождение native-хендлов через finalizer/ensure.

Резюме карточки. Ruby-биндинг; данные требуют анализа репозитория. Ожидаемые нюансы: GVL (глобальная блокировка VM Ruby — аналог Python GIL) и блокирующие вызовы; освобождение native-хендлов через finalizer/ensure.

Чек-лист карточки.

  • [ ] Учитывать GVL при длительных/блокирующих операциях.
  • [ ] Освобождать native-хендлы через finalizer/ensure.
  • [ ] Сверить данные с репозиторием биндинга.

Глава 46. Scala

Обзор. Scala-биндинг (через JVM). (Данные требуют анализа репозитория.)

Ожидаемые нюансы (из архитектуры): те же, что Java (JNI, pinning, хендлы), плюс особенности Scala-идиом (futures — аккуратно с потоками).

Резюме карточки. Scala-биндинг через JVM; данные требуют анализа репозитория. Ожидаемые нюансы — те же, что у Java (JNI, pinning, освобождение хендлов), плюс особенности Scala-идиом: futures требуют аккуратности с потоками.

Чек-лист карточки.

  • [ ] Учитывать JNI-нюансы Java (pinning, порядок освобождения хендлов).
  • [ ] В futures не рассчитывать на фиксированный поток.
  • [ ] Сверить данные с репозиторием биндинга.

Итог тома

  • Ядро биндингов (env/txn/CRUD/cursor) покрыто везде; продвинутое API — не всегда.
  • Критические архитектурные следствия повторяются: NoStickyThreads (Go/async), Send/Sync и use-after-free (Rust), pinning/SafeHandle (Java/.NET), GIL (Python/Ruby), время жизни указателей в mmap (все).
  • Карточки Nim/Java/Haskell/Ruby/Scala требуют анализа репозиториев — помечены явно.

Заключение учебника (тома I–VI)

Вы прошли путь от «что такое key-value база» до «почему GC-цикл деградирует при долгих читателях» и «как использовать libmdbx из любого языка». Освежите понятия по мере необходимости, проверьте свои конфигурации по чек-листам Тома V, главы 30, и измеряйте — числа без контекста не имеют смысла.