Репликация в Tarantool - это потоковая передача и применение записей WAL (write-ahead log) между инстансами одного репликасета. Каждый запрос на изменение данных (INSERT, UPDATE, DELETE) записывается в WAL как отдельная строка (row) и получает монотонно растущий номер LSN (log sequence number). Реплика просто непрерывно вытягивает эти строки из WAL мастера и применяет их у себя. Это row-based репликация: в журнал пишутся не результаты, а сами запросы, но запросы детерминированные. Для UPDATE хранится только первичный ключ и список операций - экономия места.
По умолчанию репликация асинхронная: транзакция считается зафиксированной локально сразу после записи в WAL мастера, ещё до того как доедет до реплик. Это быстро, но означает, что при падении мастера часть подтверждённых клиенту транзакций может потеряться.Ключевая мысль: вызовы хранимок (Lua) в WAL не пишутся - пишутся только реальные изменения данных, которые этот Lua-код произвёл. Поэтому возможный недетерминизм Lua (os.time(), random) не ломает синхронность реплик.
Архитектура: relay, applier, WAL и две стороны соединения
Между двумя инстансами всегда есть два рабочих субъекта, по одному с каждой стороны:
- relay - поток (thread) на стороне-источнике. Читает строки из WAL и отправляет их по сети. Для каждого подключённого пира мастер запускает отдельный relay. На старте relay вызывает восстановление недостающих WAL-ов: сканирует файлы в диапазоне {start_vclock; stop_vclock} и шлёт всё, чего у реплики нет, затем переходит в режим follow и стримит новые строки по мере их появления.
- applier - файбер (fiber) на стороне-приёмнике. Принимает строки из сокета, применяет их в транзакционный поток (TX) и фиксирует. После успешного коммита applier отправляет обратно ACK - сообщение со своим текущим vclock.
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}

Поток 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{
replication = {'replicator:pass@host1:3301',
'replicator:pass@host2:3301'},
read_only = false, -- мастер; на репликах true
}
Код: Выделить всё
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 по умолчанию и какие два способа разрешить конфликт вы знаете?
</invoke>