Механика репликации: WAL-стриминг, vclock

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

Механика репликации: WAL-стриминг, vclock

Сообщение 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 - это потоковая передача и применение записей WAL (write-ahead log) между инстансами одного репликасета. Каждый запрос на изменение данных (INSERT, UPDATE, DELETE) записывается в WAL как отдельная строка (row) и получает монотонно растущий номер LSN (log sequence number). Реплика просто непрерывно вытягивает эти строки из WAL мастера и применяет их у себя. Это row-based репликация: в журнал пишутся не результаты, а сами запросы, но запросы детерминированные. Для UPDATE хранится только первичный ключ и список операций - экономия места.
Ключевая мысль: вызовы хранимок (Lua) в WAL не пишутся - пишутся только реальные изменения данных, которые этот Lua-код произвёл. Поэтому возможный недетерминизм Lua (os.time(), random) не ломает синхронность реплик.
По умолчанию репликация асинхронная: транзакция считается зафиксированной локально сразу после записи в WAL мастера, ещё до того как доедет до реплик. Это быстро, но означает, что при падении мастера часть подтверждённых клиенту транзакций может потеряться.

Архитектура: relay, applier, WAL и две стороны соединения

Между двумя инстансами всегда есть два рабочих субъекта, по одному с каждой стороны:
  • relay - поток (thread) на стороне-источнике. Читает строки из WAL и отправляет их по сети. Для каждого подключённого пира мастер запускает отдельный relay. На старте relay вызывает восстановление недостающих WAL-ов: сканирует файлы в диапазоне {start_vclock; stop_vclock} и шлёт всё, чего у реплики нет, затем переходит в режим follow и стримит новые строки по мере их появления.
  • applier - файбер (fiber) на стороне-приёмнике. Принимает строки из сокета, применяет их в транзакционный поток (TX) и фиксирует. После успешного коммита applier отправляет обратно ACK - сообщение со своим текущим vclock.
Соединение в Tarantool двунаправленное. В full mesh каждая пара инстансов держит по relay и applier с обеих сторон: A->B стримит изменения A, B->A стримит изменения B. На стороне мастера ACK-и от реплики читает отдельный relay_reader; он декодирует vclock из пришедшего xrow и так узнаёт, до какого LSN реплика догнала. Это и есть основа подсчёта downstream.lag и работы синхронной репликации.

vclock - вектор часов

Поскольку источников изменений может быть несколько (мульти-мастер), одного LSN мало. Каждая строка WAL несёт не только свой LSN, но и instance ID - короткий целочисленный идентификатор инстанса, который её создал (полный UUID занимал бы слишком много места). Соответствие ID и UUID хранится в системном спейсе box.space._cluster.

vclock (vector clock) - это отображение instance ID -> максимальный применённый LSN от этого инстанса. Например:

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

box.info.vclock
--- {1: 827, 2: 584}
Это значит: от инстанса 1 применены строки до LSN 827, от инстанса 2 - до 584. Нулевой компонент (0) - это локальные неприсваиваемые записи. vclock даёт каждому инстансу полную картину "кто что уже видел". Сравнивая свой vclock с vclock пира, инстанс точно вычисляет, какие строки нужно дослать, и гарантирует, что каждая строка применяется ровно один раз - даже если она пришла окольным путём через третий узел в mesh.

Изображение
Поток WAL: relay-applier, vclock и разрешение конфликтов

Ключевые команды и мониторинг

Декларативная конфигурация 3.x (YAML) - топология задаётся в конфиге, роль определяется через database.mode:

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

groups:
  group-001:
    replicasets:
      replicaset-001:
        instances:
          instance-001: { database: { mode: rw } }
          instance-002: { database: { mode: ro } }
          instance-003: { database: { mode: ro } }
        replication:
          failover: off
Классический box.cfg (1.x/2.x-стиль) - то же самое императивно:

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

box.cfg{
  replication = {'replicator:pass@host1:3301',
                 'replicator:pass@host2:3301'},
  read_only   = false,   -- мастер; на репликах true
}
Главный инструмент диагностики - box.info.replication:

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

box.info.replication[2]
--- upstream:   -- мы СЛЕДУЕМ за инстансом 2 (его applier у нас)
      status: follow
      lag:    0.012   -- сек: разница между временем записи на мастере
                      --      и временем применения у нас
      idle:   0.3     -- сек с последнего полученного сообщения
    downstream: -- инстанс 2 следует за НАМИ (наш relay -> него)
      status: follow
      vclock: {1: 827, 2: 584}
      lag:    0.008   -- сек до получения ACK от реплики
  • upstream.lag - насколько мы отстаём от мастера по времени.
  • upstream.idle - сколько секунд не было сообщений (растёт -> связь подвисла).
  • downstream.lag - сколько мастер ждёт ACK от этой реплики.
Частые заблуждения и грабли
  • "lag - это всегда тормоза репликации." Нет. lag считается по часам ДВУХ разных машин. Рассинхрон NTP легко даёт отрицательный или фантомный lag. Всегда синхронизируйте время (chrony/ntpd) перед тем как верить цифрам.
  • "После реконнекта вижу гигантский lag - всё пропало." Известное поведение: сразу после переподключения lag показывает абсурдное значение и сам сбрасывается, как только пойдут свежие строки. Не паникуйте.
  • "Мульти-мастер просто работает." Tarantool гарантирует только что каждое изменение доедет и применится один раз, а изменения ОДНОГО инстанса применятся в исходном порядке. Изменения РАЗНЫХ инстансов могут перемешаться. Безопасен мульти-мастер только когда операции коммутативны (результат не зависит от порядка). UPDATE с присваиванием/инкрементом не коммутативен - реплики разойдутся.
  • "Конфликт ключей просто проигнорируется." Нет: при попытке применить дубль applier получает ER_TUPLE_FOUND и рвёт соединение. Это защита целостности, а не баг.
  • "Каскад надёжнее меша." Каскад (реплика реплики) не рекомендуется: узлы на концах цепочки не знают UUID друг друга через _cluster, и при смене топологии мастер отвергнет подключение. Рекомендация - full mesh (до 32 инстансов).
  • Split-brain. Если приходящий поток противоречит уже зафиксированной истории (например, старый лидер с асинхронными транзакциями, которых нет у нового лидера), включается защита и пишется ER_SPLIT_BRAIN; такой узел нужно перебутстрапить.
Разрешение конфликтов

Два штатных механизма:
  • replication_skip_conflict - если включено, строки, вызвавшие ER_TUPLE_FOUND / ER_TUPLE_NOT_FOUND, пропускаются, и соединение не рвётся. Грубо: данные разойдутся, но репликация выживет.
  • before_replace-триггер - тонкий инструмент. На спейс вешается триггер, который для каждой реплицируемой строки получает old и new и решает: оставить старую, взять новую, смержить или пропустить. Так реализуют политики "выигрывает большая версия", LWW по timestamp и т.п.

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

box.space.profiles:before_replace(function(old, new)
  if old ~= nil and new ~= nil and old.version > new.version then
    return old        -- наша запись новее - отвергаем входящую
  end
  return new
end)
Мини-лаба
Задание. Поднимите два инстанса в одном репликасете (мастер + реплика). На мастере выполните несколько box.space.X:insert{...}. На реплике посмотрите box.info.vclock и box.info.replication[<id>].upstream.lag. Затем на мастере сделайте паузу записи и проследите, как растёт upstream.idle на реплике. Цель - увидеть глазами: vclock реплики догоняет vclock мастера, а lag отражает задержку применения.
Контрольные вопросы
  • Чем relay отличается от applier и на какой стороне соединения каждый из них работает?
  • Что хранит vclock и почему одного LSN недостаточно в мульти-мастере?
  • Почему вызовы Lua-хранимок не попадают в WAL, а их эффекты - попадают? Как это связано с детерминизмом репликации?
  • Что происходит при ER_TUPLE_FOUND по умолчанию и какие два способа разрешить конфликт вы знаете?
</body>
</invoke>
👍 ❤️2 🔥5 😄 🤔3
Ответить
← Предыдущая глава
Репликация: replicaset, топологии
Следующая глава →
Синхронная репликация и выборы лидера (Raft)

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

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

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

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

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