Персистентность: WAL, снапшоты, recovery

Рейтинг: 40.9% · 8 голосов
Исчерпывающий курс по Tarantool 3.x: модель данных, движки memtx и vinyl, Lua и файберы, транзакции и MVCC, SQL, конфигурация (box.cfg и декларативная 3.x), репликация и Raft, шардирование vshard, эксплуатация, безопасность. 47 уроков со схемами.
Ответить
Аватара пользователя
denis_tnt
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Персистентность: WAL, снапшоты, recovery

Сообщение denis_tnt »

Оглавление курса (47)
  1. Что такое Tarantool: in-memory СУБД и сервер приложений
  2. Архитектура изнутри: процесс, потоки, event-loop
  3. Установка и первый запуск: tt CLI, пакеты, Docker
  4. Интерактив: консоль, admin-консоль, первые команды
  5. Спейсы и кортежи: форматы, типы данных
  6. Типы индексов и их применимость
  7. Движки хранения: memtx vs vinyl
  8. DDL: схема, создание спейсов и индексов, миграции
  9. DML и выборки: insert/update/upsert, итераторы
  10. Персистентность: WAL, снапшоты, recovery (вы здесь)
  11. Внутренности memtx: аллокаторы slab/arena, память
  12. Внутренности vinyl: LSM, компакция, тюнинг
  13. Lua и LuaJIT в Tarantool: box, модули, rocks
  14. Файберы: кооперативная многозадачность, каналы
  15. Транзакции: ACID, изоляция, MVCC
  16. Хранимые процедуры, модули, организация приложения
  17. net.box: удалённые вызовы, async
  18. Пулы соединений, балансировка, реконнект
  19. Ошибки и диагностика: box.error, pcall
  20. Типы и сериализация: MsgPack, decimal, datetime, uuid
  21. SQL в Tarantool: возможности и связь с box
  22. SQL: таблицы, JOIN, подзапросы, представления
  23. SQL: подготовленные выражения, транзакции, Lua-интероп
  24. Классическая конфигурация box.cfg (legacy 1.x/2.x)
  25. Декларативная конфигурация 3.x: config.yaml, иерархия
  26. Роли и приложения в 3.x
  27. Централизованная конфигурация: etcd / config storage
  28. tt CLI глубоко: разработка, сборка, запуск
  29. Cartridge (официальный legacy) и миграция на 3.x
  30. Репликация: replicaset, топологии
  31. Механика репликации: WAL-стриминг, vclock
  32. Синхронная репликация и выборы лидера (Raft)
  33. Жизненный цикл узла: bootstrap, join, rejoin
  34. vshard: router/storage, виртуальные бакеты
  35. Решардинг и rebalancing бакетов
  36. Запросы поверх шардов: map-reduce, crud
  37. Мониторинг: метрики, Prometheus, Grafana
  38. Логирование и аудит
  39. Бэкапы и восстановление
  40. Безопасность: аутентификация, RBAC, TLS
  41. Производительность: профилирование, тюнинг
  42. Обновления: схема, rolling upgrade
  43. Деплой в продакшен: Docker, топология (официальные паттерны)
  44. Администрирование через официальный TCM (Tarantool Cluster Manager)
  45. Коннекторы: Python, Go, Java
  46. Ключевые модули (rocks): crud, metrics, queue, expirationd
  47. Capstone: шардированный отказоустойчивый кластер
Зачем это нужно

Tarantool держит данные memtx целиком в оперативной памяти. Память исчезает при перезапуске или сбое, поэтому одной её мало - нужен способ переживать рестарты. Эту задачу решают два артефакта на диске: write-ahead log (WAL, файлы .xlog) и снапшоты (файлы .snap). Правило, которое надо унести из урока:
Снапшот - это полный слепок состояния на момент времени T. WAL - это журнал всех изменений после T. Снапшот плюс хвост WAL дают точное состояние базы на момент последней успешной записи. Recovery = загрузить снапшот, потом доиграть WAL.
WAL пишется ДО подтверждения клиенту (отсюда write-ahead). Снапшот - это оптимизация: без него восстановление пришлось бы проигрывать весь журнал с самого первого дня, что долго.

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

Транзакции и сеть обслуживает один поток - TX (transaction processor). Запись на диск выполняет отдельный поток - WAL writer. Они общаются асинхронными, но надёжными сообщениями. Это ключ к производительности: TX не блокируется на диске.

Путь одной операции изменения (insert/replace/update/delete/upsert):
  • TX находит старый кортеж по первичному ключу (если есть), валидирует новый, применяет изменение к индексам в памяти.
  • Каждой записи присваивается LSN - непрерывно растущий 64-битный номер (log sequence number). Набор LSN по инстансам образует vclock.
  • TX формирует journal entry и шлёт сообщение WAL writer-у, после чего сразу берёт следующий запрос (полный пайплайнинг, даже по одному и тому же ключу).
  • WAL writer дописывает строку в текущий .xlog. Каждая строка: магический маркер 0xd5ba0bab, длина, CRC32, заголовок (тип запроса, server id, LSN, timestamp) и тело в формате MessagePack.
  • Только ПОСЛЕ успешной записи на диск клиенту уходит подтверждение коммита.
Если запись в WAL не удалась, начинается откат: TX откатывает все изменения, случившиеся после первого сбойного, от новых к старым, с ошибкой ER_WAL_IO. Это даёт атомарность - изменение либо в журнале, либо его нет вовсе.

В WAL всегда лежит изменение по ПЕРВИЧНОМУ ключу, даже если клиент удалял/обновлял по вторичному. Новый .xlog создаётся, когда текущий дорастает до wal.max_size; имя файла - это LSN первой записи в нём.

Изображение
Поток записи TX-WAL-диск и recovery из снапшота с WAL

Режимы долговечности (wal.mode)

Поведение WAL writer задаёт один параметр. Внутри это всего лишь выбор: ждать ли write(), и звать ли fsync() после каждой записи.

Код: Выделить всё

mode    смысл                                         цена
------  --------------------------------------------  ------------
write   файбер ждёт write() в WAL (default)           быстро
fsync   write() + fsync() после каждой записи         медленно
none    WAL не ведётся вообще, только снапшоты         макс. скорость
Тонкость: в режиме write данные уходят в страничный кеш ОС, но могут остаться в кеше дискового контроллера. При внезапном отключении питания возможна потеря последних записей. fsync проталкивает данные на пластину диска и закрывает эту дыру, но платит латентностью на каждом коммите. none отключает журнал целиком: переживёт корректный рестарт (через снапшот), но НЕ переживёт сбой - всё после последнего снапшота потеряется.

Снапшоты и чекпоинты

Снапшот запускается вручную через box.snapshot() или автоматически checkpoint-демоном. В .snap пишутся только INSERT-записи memtx-спейсов (полное согласованное состояние), отсортированные по id спейса, затем по первичному ключу. Системные спейсы (id 256..511) идут первыми. Для vinyl box.snapshot() инициирует checkpoint движка; если что-то пошло не так, операция откатывается, поэтому checkpoint vinyl всегда не старше .snap.

Код: Выделить всё

-- классический трек (box.cfg, 1.x/2.x/3.x)
box.snapshot()                 -- сделать снапшот прямо сейчас
box.cfg{
  wal_mode = 'write',
  checkpoint_interval = 3600,  -- сек между авто-снапшотами
  checkpoint_count = 2,        -- сколько снапшотов хранить
}
box.info.lsn                   -- текущий LSN инстанса
box.info.vclock                -- vclock (LSN по server id)

Код: Выделить всё

# декларативный трек 3.x (config.yaml)
wal:
  mode: write
  max_size: 268435456     # ротация .xlog при этом размере
snapshot:
  by:
    interval: 3600        # авто-снапшот по времени
    wal_size: 1000000000  # ...или по объёму WAL с прошлого чекпоинта
  count: 2                # хранить 2 снапшота
Сборщик мусора удаляет старые .snap, когда их число превышает count, и заодно подчищает .xlog, которые старше самого старого нужного снапшота. Важно: WAL, который был текущим в момент СТАРТА box.snapshot(), удалять нельзя - в нём есть записи, сделанные уже во время снятия снапшота.

Процесс recovery (по шагам)

Восстановление стартует при первом box.cfg{} после запуска инстанса.
  • Шаг 1. Прочитать пути: wal_dir, memtx_dir, vinyl_dir и флаг force_recovery.
  • Шаг 2. Найти свежайший .snap, восстановить memtx из него, сказать vinyl восстановиться до его checkpoint. По умолчанию (force_recovery=false) кортежи грузятся с выключенными индексами, потом первичный ключ строится bulk-ом - данные уже отсортированы, это быстро.
  • Шаг 3. Найти .xlog, относящийся к моменту снапшота, и пропустить записи с LSN не больше LSN снапшота - это "стартовая позиция".
  • Шаг 4. Доиграть (redo) все записи WAL от стартовой позиции до конца журнала. Движок пропускает запись, если она старше его checkpoint.
  • Шаг 5. Для memtx пересобрать все вторичные индексы.
При force_recovery=true индексы включены сразу, кортежи вставляются по одному, ловятся нарушения уникальности и дубликаты пропускаются - это спасает повреждённую базу ценой скорости.

Частые заблуждения и грабли
  • "wal_mode write = данные гарантированно на диске." Нет, write только доводит до write()/кеша ОС. Гарантию на пластину даёт fsync.
  • "checkpoint_count удалит лишние снапшоты всегда." Нет: если есть реплика, которая ещё не получила данные, файлы не удалятся - GC бережёт их для отстающих реплик. Может неожиданно расти диск.
  • "snapshot - это бэкап." Это слепок памяти для быстрого старта, а не резервная копия (для бэкапа есть отдельные инструменты, копирующие .snap + хвост .xlog согласованно).
  • "wal_mode none быстрее, оставлю в продакшене." Любой сбой между снапшотами = безвозвратная потеря данных. none уместен для тестов и эфемерных кешей.
  • Долгий старт = много WAL. Если снапшоты редкие, recovery проигрывает гигабайты журнала. Лечится более частыми чекпоинтами.
Мини-лаба

Запустите Tarantool интерактивно, инициализируйте box, создайте спейс и сделайте несколько вставок. Снимите снапшот, добавьте ещё кортежи ПОСЛЕ снапшота, затем убейте процесс жёстко (kill -9, имитация сбоя) при wal_mode 'write'. Перезапустите инстанс на том же каталоге и убедитесь, что данные после снапшота тоже на месте - их вернул WAL.

Код: Выделить всё

box.cfg{ wal_mode = 'write' }
local s = box.schema.space.create('t', {if_not_exists=true})
s:create_index('pk', {if_not_exists=true})
for i=1,5 do s:insert{i, 'before snap'} end
box.snapshot()
print('lsn after snap:', box.info.lsn)
for i=6,10 do s:insert{i, 'after snap'} end
print('lsn before crash:', box.info.lsn)
-- теперь kill -9 процесс, перезапусти на том же memtx_dir/wal_dir
-- после старта: box.space.t:count() должно быть 10
Загляните в каталог: hexdump первого .xlog покажет заголовок XLOG и маркеры строк 0xd5ba0bab.

Контрольные вопросы
  • В каком порядке во время recovery применяются снапшот и WAL и почему именно так, а не наоборот?
  • Чем отличаются wal_mode write и fsync на уровне системных вызовов и какие данные рискуют потеряться в режиме write при отключении питания?
  • Почему .xlog, бывший текущим в момент старта box.snapshot(), нельзя удалять сразу после завершения снапшота?
  • Что такое LSN и vclock, и какую роль LSN играет на шаге, где recovery пропускает часть записей WAL?
👍4 ❤️ 🔥4 😄 🤔1
Ответить
← Предыдущая глава
DML и выборки: insert/update/upsert, итераторы
Следующая глава →
Внутренности memtx: аллокаторы slab/arena, память

Все главы курса «Tarantool: in-memory СУБД и сервер приложений с нуля до продакшена»

Поделиться темой: ✈ Telegram VK

Вернуться в «Tarantool: СУБД и сервер приложений»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость