Lightning Memory-Mapped Database (LMDB) - представляет собой интересный, во многом уникальный, движок базы данных класса Berkeley DB / Level DB. Будучи относительно малоизвестным, LMDB показывает чемпионскую производительность по чтению и предлагает ряд компромиссов для достижения невероятной производительности по записи.
LMDB был создан для использования внутри известного проекта OpenLDAP с открытым исходным кодом. Как разработчик и поставщик решений для «больших телекомов», компания «Петер-Сервис» решила задействовать связку OpenLDAP+LMDB в одном из своих проектов. Это был вход в кротовую нору, всё было как в сказке – чем дальше, тем страшнее...
В результате мы сделали клон/fork исходного проекта и создали высокопроизводительное стабильное решение промышленного масштаба с открытым исходным кодом. Расскажу о внутреннем устройстве LMDB, о выявленных недостатках и наших доработках для их устранения.
2. О чём мы тут собрались ?
1. Зачем нам OpenLDAP и LMDB?
2. Устройство и плюсы движка LMDB
3. Изъяны оригинальной версии
4. Наши доработки LMDB
5. Варианты развития
3. • решения для крупных операторов связи:
BSS, Telco protocols, BigData, HA & HL
• более 100 миллионов абонентов
обслуживается при участии наших систем
• более 20 лет полного цикла:
разработка, внедрение и сопровождение
• R&D подразделение в Сколково
http://www.billing.ru
4. Спуск в «Кроличью Нору»:
Почему LDAP ?
• Мы делаем решения для «Телекомов»
• LDAP прописан в стандартах и рекомендациях
как интерфейс взаимодействия Telco-подсистем
• Сервер LDAP является одним из «кубиков»
• Lightweight Directory Access Protocol
= понятный концепт с простым интерфейсом
5. Спуск в «Кроличью Нору»:
LDAP для Телекома
• Поиск/Чтение: 10K…100K запросов в секунду
• Модификация/Запись: 5K…50K запросов в секунду
• 10…100 миллионов «записей»,
не просто key-value, а LDAP-кортежи
• Обновления с разной ценностью,
от кеширования … до финансовых данных
• Географически разнесенный кластер 2+2
6. Спуск в «Кроличью Нору»:
Выбирали LDAP-сервер…
• Готовых решений нет
• Не справляются с нагрузкой:
– 386DS, ApacheDS…
– чуть менее чем все
• Более-менее:
– OpenLDAP + LMDB
– OpenDJ, но с большим разбросом latency
• От встраивания «движка» отказались…
– https://github.com/ReOpen/ReOpenLDAP/wiki
7. Спуск в «Кроличью Нору»:
и выбрали… меньшее из зол
OpenLDAP:
– открытый проект с 25-летней историей
– есть мульти-мастер репликация, плюс масса «фишек»
– много стабильных версий, используется миллионами
LMDB:
– рекордсмен по чтению
– по записи «как все»
– есть не-синхронная и пакетная фиксация
8. Обзор LMDB:
характеристики
• Key-value, ACID, без журнала транзакций
• Ключи всегда отсортированы, range lookups
• Memory-mapped, возможен zero-copy
• Встраиваемая, не требует отдельного процесса
• Не требует восстановления, возможно «горячее»
резервное копирование
9. Обзор LMDB:
кратко
• Образец MultiVersion Concurrency Control
• Много неблокируемых читателей
– блокировка только при подключении/отключении
• Один писатель
– операции записи строго последовательно
• B-tree и копирование страниц при изменении
10. Обзор LMDB:
производительность
• Дизайн для производительности по чтению
• Чемпион по чтению:
– двоичный поиск по отображенным в память данным
• По записи:
– при синхронной фиксации «упираемся» в диск
– иначе волшебно быстро
• Write amplification:
– Чуть хуже LSM для коротких значений
– Чуть лучше LSM для больших значений
22. Устройство LMDB: Memory-mapped
• Образ БД целиком отображается в приложение
• При расширении БД страницы добавляются в конец
• Zero Overhead:
– Данные непосредственно доступны (в пределах транзакции)
– Возможен zero-copy, MVCC позволяет lockfree
– Не требуется выделение/освобождение ресурсов
• Read-Write mapping для отлаженных приложений
23. Устройство LMDB: Структура
+-----------+
| META-PAGE |
+-----------+
+--------+ +--------+
| MainDB | | FreeDB |
|--------| |--------|
| | | |
| B-tree | | B-tree |
| | | |
+--------+ +--------+
+---+ +---+ +---+ +---+
| X | | Y | | 2 | | 4 |
+---+ +---+ +---+ +---+
+---+ +---+ +---+ +---+ +---+ +---+
|X-1| |X-2| |Y-0| | 1 | | 3 | | 5 |
+---+ +---+ +---+ +---+ +---+ +---+
/ / /
• Мета-страница
• Два «указателя»
на корни B-деревьев
• Пользовательские данные
в MainDB
• Вложенные таблицы как узлы
специального типа
• FreeDB хранит списки страниц,
чуть позже…
24. Устройство LMDB: Flip-Flop
+-----------+ +-----------+
| A | | B |
|-----------| |-----------|
| META-PAGE | | META-PAGE |
| | | |
| txn #10 | | txn #11 |
+-----------+ +-----------+
+------------+ +------------+
| MainDB | | MainDB |
|------------| |------------|
| FreeDB | | FreeDB |
+------------+ +------------+
• Две МЕТА-страницы
с указанием версии
• Два снимка БД
• Для чтения выбирается
последняя из версий
• При фиксации транзакции:
- создается новый снимок
- обновляется META
26. FreeDB
♺
Устройство LMDB: Pipeline
+-------+ +-------+
| root | | root` |
|-------| ====== |-------|
|page #2| ======/ |page #7|
| | | |
+-------+ +-------+
+-------+ +-------+ +-------+
| node1 | | node2 | | node2`|
|-------| |-------| ======= |-------|
|page #3| |page #4| =======/ |page #8|
| | | | | |
+-------+ +-------+ +-------+
+-------+ +-------+ +-------+
| leaf1 | | leaf2 | | leaf2`|
|-------| |-------| === |-------|
|page #5| |page #6| ===/ |page #9|
| | | | | |
+-------+ +-------+ +-------+
• При изменениях остаются
«старые» копии страниц
• Эти страницы можно
использовать при
ненадобности предыдущих
версий БД
• При фиксации во FreeDB
добавляется запись со
списком «старых» страниц
27. +--------+
| FreeDB |
|--------|
| HEAD |
| |
| # 101 |
| # 100 |
| ... |
| ... |
| ... |
| # 089 |
| # 088 |
| ... |
| ... |
| ... |
| ... |
| # 062 |
| TAIL |
+--------+
Устройство LMDB: Reclaiming
• Находим самую старую
версию, с которой
работают читатели
• Cписки FreeDB старше
этой версии можно
переработать
• Этим утилизируются
ненужные снимки БД
• FreeDB превращается
в подобие FIFO
Очередной
commit Последняя версия
Самая старая
читаемая версия
100…089
заблокированы
читателями
088…062
доступны для
переработки
062 будет
переработана
первой
28. Устройство LMDB: Синхронизация
При чтении:
– поддерживается отдельная таблица читателей
– читатель индицирует версию БД, с которой он работает
– автоматическая регистрация при первом запросе
– для регистрации отдельный межпроцессорный мьютекс
– после регистрации читатели не блокируются
При записи:
– захватывается межпроцессорный мьютекс
– по необходимости просматривается таблица читателей
29. Устройство LMDB: Режимы работы
По-умолчанию = безопасная синхронная запись:
– read-only mapping
– запись через дескриптор открытый с O_DSYNC
Ускоренный = чуть меньше накладных расходов в ОС:
– read-write mapping
– запись на диск msync(MS_SYNC)
Быстрый = асинхронная запись и «ручная» фиксация:
– read-write mapping
– асинхронная запись на диск msync(MS_ASYNC)
– вызов mdb_env_sync() по необходимости
Еще много комбинаций флажков…
30. Внутри «Кроличьей Норы»:
Первые проблемы
• Большие IOPSы по записи (aka write amplification)
• Переполнение базы без увеличения данных
• Были падения при «вытаскивании кабеля»
• Странности репликации когда изменений много
• Вероятность повреждения базы при «не-синхронной»
записи, даже в «контрольных точках»
31. Внутри «Кроличьей Норы»:
#Facepalm
• Отсутствие volatile при lockfree в общей памяти
• Падения под нагрузкой или «проблемах» в сети
• Утечки памяти, ещё падения, много падений…
• Rebus code style: кол-во страниц делится на #txn…
• 5K предупреждений при сборке, падение тестов…
• «у нас всё хорошо» и «coverity смешон» от авторов
32. Внутри «Кроличьей Норы»:
пришли, увидели, победили…
• Починили всё озвученное, включая несколько
многолетних багов
• Не смогли вернуть в mainstream и сделали fork
• Продолжили доработки в 2015
• Далее ближе к теме…
33. Проблемы LMDB: Отстающий читатель-убийца
• Переработка FreeDB не может «перепрыгнуть» хвост
чтения
• Если один из читателей «застрянет», то переработка
остановится
• При большом потоке изменений свободны страницы
будут израсходованы и наступит read-only
• Любой отстающий читатель «кошмарит» БД
34. Проблемы LMDB: Плохие сценарии
Варианты причин для «наступления» read-only:
• резервное копирование (mdb_copy)
• просмотр статистики через less/more (mdb_stat)
• проблемы сети при взаимодействии с клиентом
• плохое поведение клиента, включая его отладку
• долгая обработка запроса
• фаза refresh при LDAP-репликации
35. Проблемы LMDB: Вымывается кэш страниц
• Пишущие транзакции на фоне приостановки
переработки вызывают «разбухание» FreeDB
• Использованные страницы попадают в «голову»
FreeDB, а повторно используются с «хвоста»
• Единожды «распухшая» FreeDB приводит к
перманентному эффекту вымывания кэша страниц
• Эффект сохраняется до увеличения объема полезных
данных, либо до переформатирования БД
36. Проблемы LMDB: Возможно повреждение
• Требуется чтобы META-страницы были записаны не
раньше страниц с данными
• write() для дескриптора без O_SYNC/O_DSYNC
гарантий упорядоченности не дает
• При read-write memory mapped ядро ОС может
сбрасывать страницы в любом порядке
• Для некоторых режимов, сохранность базы не
гарантируется даже после mdb_env_sync()
37. Доработки LMDB: LIFO reclaiming
Идея:
– перерабатывать FreeDB в порядке LIFO,
начиная с самой новой неиспользуемой позиции
Зачем:
– при любом размере FreeDB страницы будут использоваться
повторно по наименьшему кругу
– эффективно используется write-back кэш в системах
хранения «с батарейкой»
– при асинхронной записи работает кэш ОС
Результат:
– повышение производительности по записи в 5-50 раз (*)
38. Доработки LMDB: sync checkpoints
Идея:
– для производительности нужна асинхронная запись
– плюс «контрольные точки» для ограничения потерь
Сделали:
– исходная минутная гранулярность слишком крупная, сделали
посекундной
– добавили учет изменений и триггер по объему
Результат:
– настраиваемый компромисс
между производительностью и потерями при аварии
39. Доработки LMDB: борьба с отстающими
Добавили OOM-handler callback:
– вызывается при исчерпании свободных страниц
– и остановке из-за «отставшего читателя»
Посредством OOM-handler в ReOpenLDAP:
– SIGKILL внешним мешающим процессам
– thread_yield() до завершения собственных запросов
– обе опции включаются в конфигурации
«Dreamcatcher» в ReOpenLDAP:
– безопасно перезапускает «застрявшие» запросы чтения
– активируется при задаваемом заполнении БД
– и задаваемом отставании по транзакциям
40. Доработки LMDB: weak / steady sync
• META-страницы помечаются признаком:
– «steady» если гарантируется, что данные были записаны до
META-страницы
– «weak» когда порядок записи не известен
• «Steady» фиксация из mdb_env_sync()
и при закрытии базы
• При открытии база откатывается к «steady»
• Для соблюдения правил переписан код
синхронизации данных с диском
41. Доработки LMDB: последствия
• Переработка FreeDB не должна обгонять последнюю
steady фиксацию, иначе при аварии возможно
разрушение БД
• Следовательно, при асинхронной фиксации и
отключенных периодических контрольных точках
переработка FreeDB будет останавливаться
• Поэтому, при переполнении в этом случае
производится steady фиксация
42. Доработки LMDB: async!
• Производительность дисков не ограничивает
производительность БД по записи до исчерпания
свободного места
• Скорость фиксации данных стремится к
производительности дисков без использования AIO
• Сбой приложения не приводит к потере данных, так
как асинхронную запись выполняет ядро ОС
• Эффективность LIFO и write-back кэша
«пропорциональна» частоте steady фиксации
43. Доработки LMDB: Оцениваем
• По чтению:
– существенных изменений кода не было
– регрессии не замечено
• По записи:
– «wall clock» зависит от производительности диска
– замерим количество IOPS и потребление CPU
– попробуем различные режимы
– сравним с оригинальной версией
44. Доработки LMDB: Оцениваем
Сценарий:
– Чем проще, тем лучше ?
– Добавляем 10К записей с псевдо-случайным ключом и
размером значения в 777 байт
– Удаляем все добавленные записи
• Add + Delete немного «тяжелее» модификации
• 12 Мб заполняется на 98%,
в не-синхронных режимах будет авто-фиксация
47. Это плохой сценарий оценки
• IOPSы за сеанс не отражают динамику
• Нет замера с write-back кэшем
• Не участвуют контрольные точки
• Фиксация при закрытии увеличивает метрики,
в не-синхронных режимах кардинально!
• Общая длительность зависит от возможностей диска
• Потребление CPU включает большую погрешность
и в целом не показательно
Будем исправляться на Highload-2015
48. ReLMDB: Планы
1) Добавить дайджест (контрольную сумму) данных в
заголовок каждой страницы
2) Дополнить дайджестом все внутренние структуры
3) Для узлов B-дерева применить подход Merkle Tree
Результат: Возможность проверить целостность базы и
не откатывать успешные weak фиксации.
Контраргументы: ломается совместимость с исходной
версией, вероятно проще переписать с «чистого листа».
49. Спасибо за внимание!
ReOpenLDAP и LMDB
https://github.com/ReOpen/ReOpenLDAP
The fork of OpenLDAP with a few new features (mostly for highload and
multi-master clustering), additional bug fixing and code quality
improvement.
Петер-Сервис R&D,
http://www.billing.ru/job