Ограничения и подводные камни
Смежные: Обзор · Первые шаги · Режимы долговечности · Учебник: Том V «Экспертные темы»
Ограничения количественных параметров — в Обзоре; здесь — поведенческие ограничения, операционные риски и способы их обхода.
Многоверсионность и «долгоживущие» читатели
Самый важный операционный нюанс libmdbx. Механика: каждая write-транзакция
строит новую версию изменяемых страниц (MVCC на базе copy-on-write); читатели
работают со снапшотами и не блокируются. Страницы, освободившиеся в новых
write-транзакциях, не могут быть переиспользованы, пока на них смотрит хоть
один активный старый снапшот. Поэтому активная модификация данных при
параллельном долгом чтении увеличивает рабочий набор и может быстро
исчерпать свободное место (MDBX_MAP_FULL).
Типичные источники долгих читателей: горячий backup на медленное хранилище, отладка клиентского приложения с активной read-транзакцией, приостановленные (SUSPEND/ptrace) процессы с открытым чтением.
Инструменты смягчения — уже доступны:
- Парковка транзакций —
mdbx_txn_park()/mdbx_txn_unpark(): припаркованный читатель помечается псевдо-TID, перестаёт влиять на водораздел переиспользования и может быть вытеснен ( ousted ) с автоматическим перезапуском при обращении. - Handle-Slow-Readers —
mdbx_env_set_hsr(): колбэк вызывается, когда БД заполнилась из-за отставших читателей; может подождать, асинхронно прервать транзакцию читателя (возврат 1) или зафиксировать гибель процесса (возврат 2+). MDBX_LIFORECLAIM— LIFO-переиспользование укорачивает цикл оборота страниц и смягчает последствия на write-back носителях.- Выселение припаркованных читателей автоматически перед исчерпанием места (вместе с установкой нового steady-снапшота).
- Читайте второстепенные данные на второстепенной копии БД.
Roadmap: радикальное преодоление архитектурного ограничения (переиспользование страниц независимо от старых MVCC-снапшотов — nonsequential GC recycling) — в следующей версии движка (MithrilDB), где проблема долгоживущих читателей решена полностью; частичные улучшения планируются и в ближайших релизах libmdbx.
Избегайте: остановки/блокировки потоков с активным чтением (включая отладку), abort'ов процессов с чтением (libmdbx чистит слоты читателей автоматически, но проверка не может выполняться слишком часто без ущерба производительности).
Крупные значения
- Значения до
0x7FF00000байт хранятся в цепочках последовательных страниц — чтение происходит напрямую, без копирования и накладных расходов. - Обратная сторона: запись крупного значения требует найти достаточную последовательность свободных страниц, что при фрагментации B+tree может быть дорогим или невозможным (движок может обработать весь GC или увеличить файл). Максимум скорости чтения — ценой скорости записи.
- Что уже доступно: поиск свободных последовательностей в GC существенно ускорен (SIMD: NEON/SSE2/AVX2/AVX512); спилл и refund уменьшают фрагментацию; геометрия с автоматическим ростом помогает при нехватке последовательностей.
- Roadmap: переработка хранения крупных значений и обхода фрагментации — в MithrilDB (где проблема снята архитектурно) и в уточнениях ближайших релизов libmdbx.
Огромные транзакции
Аналогичная ситуация с транзакциями, освобождающими много страниц: список освобождённых страниц сам по себе — длинное значение, требующее последовательности свободных страниц.
Что уже доступно: функция «Big Foot» (включена по умолчанию) — крупные
PNL-списки дробятся на цепочки записей последовательных txnid, исключая поиск,
выделение и хранение длинных последовательностей; см. MDBX_ENABLE_BIGFOOT.
Дополнительно — LIFO-политика и refund уменьшают объём бухгалтерии GC.
Roadmap: сохраняется в MithrilDB — где списки освобождённых страниц вписаны в архитектуру без поиска свободных последовательностей.
Резервирование адресного пространства
Конфигурация БД часто резервирует заметный объём виртуального адресного
пространства и (потенциально) файла под рост — это не потребляет реальную
память и диск. Но на 64-битных системах с малым объёмом RAM задание неадекватно
большого size_upper через mdbx_env_set_geometry() может исчерпать системные
ресурсы (ENOMEM). Не задавайте верхнюю границу «про запас».
Сетевые файловые системы
- Не размещайте БД на удалённых файловых системах — даже между процессами одного хоста: это ломает файловые блокировки на некоторых платформах и синхронизацию memory mapping.
- Исключения: эксклюзивный режим работы по сети и кооперативное read-only чтение с read-only сетевых шар.
Дочерние процессы и fork()
- Не используйте открытое окружение в потомке после
fork(). Лучший способ — не иметь открытых экземпляров libmdbx на моментfork(); build-опцияMDBX_ENV_CHECKPID(по умолчанию ON на не-Windows) позволяет выявить такие ошибки раньше, но не даёт гарантий. - С v0.13.1 доступна
mdbx_env_resurrect_after_fork()— уже доступное переиспользование уже открытого окружения в потомке, строго без наследования транзакций родителя. - Не вызывайте
fork()одновременно из нескольких потоков.
Read-only режим
«Чистого» read-only режима нет: читателям нужна запись в LCK-файл, чтобы быть
видимыми писателю. libmdbx всегда пытается открыть/создать LCK в read-write;
при ошибках (EROFS, EACCES, EPERM) и запрошенном MDBX_RDONLY
переключается в режим без LCK.
Один поток — одна транзакция
Поток может использовать только одну транзакцию одновременно (плюс вложенные
write-транзакции вне WRITEMAP). Каждая транзакция принадлежит одному потоку;
write-транзакция завершается в породившем её потоке. Нарушения возвращают
MDBX_TXN_OVERLAPPING, MDBX_BAD_RSLOT, MDBX_BUSY. Опция
MDBX_NOSTICKYTHREADS разрешает передачу транзакций между потоками — только
для тех, кто точно понимает, что делает (дедлоки, чтение чужих данных).
Не открывайте БД дважды
Повторное открытие одной БД в одном процессе недопустимо: при отсутствии
OFD-блокировок используются POSIX-блокировки, которые ломаются при
многократном открытии файла одним процессом (закрытие одного экземпляра
снимает все блокировки). libmdbx отслеживает и предотвращает это
(MDBX_BUSY); совместимость с LMDB — mdbx_setup_debug(MDBX_DBG_LEGACY_MULTIOPEN, ...)
с восстановлением блокировок (возможны паузы).
Диагностика LCK-файла
LCK-файл очищается при старте первого процесса с БД: при любых повреждениях LCK достаточно закрыть БД во всех процессах (или остановить их) и перезапустить.
- Повреждение LCK возможно при некорректном приложении, пишущем через указатели в shared-память; переносимого способа защиты нет (LCK обновляется конкуренто и lock-free — блокировки дали бы большие накладные расходы).
- Застрявшие читатели (после аварийного завершения программы) приводят к быстрому росту БД; libmdbx проверяет их при открытии окружения и перед ростом.
- Застрявшие писатели чистятся автоматически (платформенно-зависимо, через robust-мьютексы); известных проблем на поддерживаемых платформах нет.