Vladislav Shpilevoy PRO
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.
План доклада
Репликация
История репликации в Tarantool
Алгоритм Raft
Схема асинхронной репликации
Реализация синхронной репликации
Интерфейс
Отличия от Raft
Планы
Репликационная группа - "репликасет"
Мастер
Реплика
Реплика
Реплика
Мастер
Мастер
Мастер
Задачи
Типы
Запись в журнал = успешный коммит. Не ждет репликацию
Транзакция
Журнал (мастер)
Коммит и ответ
Репликация
Журнал (реплика)
Транзакция
1
Гибель мастера между (3) и (5) = потеря транзакции.
3
Коммит и ответ
Журнал
2
4
Репликация
Журнал
5
{money: 100}
{money: 150}
У меня 100 денег
Кладу еще 50
У меня 150 денег
{money: 100}
{money: 100}
Где мои 150 денег?!
При асинхронной репликации гарантии - почти как без репликации
{money: +50}
{success}
get({money})
{money: 100}
Журнал
Начало репликации...
_ = box.schema.create_space(‘test’) _ = _:create_index(‘pk’)
box.space.test:replace{1, 100}
Инициализация схемы данных
Выполнение транзакции
wait_replication()
Ручное ожидание репликации
Здесь изменения уже видимы!
Транзакция
Журнал
(мастер)
Репликация
Журнал
(реплика)
Коммит и ответ
Репликация гарантируется до коммита
1
Транзакция
2
Журнал
Коммит и ответ
5
3
Репликация
Журнал
4
Типичные конфигурации
1 + 1
Тройка
50% + 1
Синхронная репликация
Асинхронная репликация
Высокая скорость
Хорошая доступность
Легко конфигурировать
Легко потерять данные
Хрупкая доступность
Сложность конфигурации
Высокая надежность
Низкая скорость
Raft - алгоритм синхронной репликации
Гарантия сохранности данных на > 50% узлов;
Чрезвычайная простота
Проверен временем
Выборы лидера
Год 2015
Появился спрос на синхронность
Выбрали Raft, составили великий план
Модуль SWIM - для сборки кластера
Авто-прокси
Ручные выборы - box.ctl.promote()
Оптимизации репликаци
Raft
Создание плана
box.ctl.promote()
SWIM
Смена руководства
Raft
2015
2018
2019
2020
Авто-прокси
Оптимизации репликации
SQL
Vinyl
Это Синхронная репликация и Выборы лидера
Роли:
- лидер
- реплика
- кандидат
Каждый узел изначально реплика
Узлы хранят персистентный терм - логические часы кластера
Терм: 1
Терм: 1
Терм: 1
Терм: 1
Терм: 1
Ждут лидера. Долго нет лидера - начинаются выборы
Запрос голосов
Терм: 2
Голоса: 1
Терм: 2
Голоса: 1
После набора большинства один становится лидером
Терм: 2
Терм: 2
Голоса: 3
Терм: 2
Голоса: 2
Терм: 2
Терм: 2
Лидер принимает все транзакции, реплицирует, и завершает коммит сразу по достижении кворума
Репликация
Кворум
Коммит
Терм: 2
Терм: 2
Подтверждения
Догонка реплик
Транзакция
Ответ
Четыре этапа коммита транзакции
Данные в журналы реплик
Коммит в журнал лидера и ответ пользователю
Данные в журнал лидера
Коммит в журналы реплик
Отказ на любом этапе любого узла не приводит к потере данных после коммита, пока живо 50% + 1 узлов
Журнал: транзакции, термы, голоса, коммиты
Терм 1
Терм 2
Терм 3
Лидер
Реплика
Реплика
Реплика
1
2
1
2
3
4
5
6
4
3
2
1
1
2
3
4
5
6
7
8
9
Новые транзакции
Последний коммит
5
6
7
TX-поток
IProto-поток
WAL-поток
Relay-потоки
Сеть
Клиентские соединения
Данные от реплик
Управление соединениями;
Ввод/вывод
Журнал;
Запись на диск
Отправка транзакций на реплики
База данных;
Транзакции;
Файберы
Данные на реплики
ID транзакции: {Replica ID, LSN}
VClock - пары {Replica ID, LSN} со всех узлов, снимок состояния кластера
{0, 0, 0}
{0, 0, 0}
{0, 0, 0}
{1, 0, 0}
{1, 0, 0}
{1, 0, 0}
{1, 2, 0}
{1, 2, 0}
{1, 2, 0}
Replica ID = 1
Replica ID = 2
Replica ID = 3
Набор железных требований
Полная совместимость со старыми версиями, пока синхронность не используется
Не меняется формат журнала, только добавляются новые типы записей
Архитектура Тарантула существенно не меняется
$> sync = box.schema.create_space( ‘stest’, {is_sync = true} ):create_index(‘pk’) $> sync:replace{1} $> box.begin() $> sync:replace{2} $> sync:replace{3} $> box.commit()
Синхронность - свойство спейса
Это синхронные транзакции
Один синхронный спейс - вся транзакция тоже
$> async = box.schema.create_space( ‘atest’, {is_sync = false} ):create_index(‘pk’) $> box.begin() $> sync:replace{5} $> async:replace{6} $> box.commit()
Коммит начинается с записи в журнал мастера
После журнала транзакция попадает в лимб
Лимб - это очередь синхронных транзакций в TX потоке
Лимб - это связь между TX, WAL, и Relay потоками
5
7
10
LSN
...
Новые транзакции
Закоммиченные транзакции
TX-поток
Кворум
Реплика пишет в журнал, в лимб, и отвечает последним примененным vclock. Лимб мастера собирает по ним кворум
Транзакции
{0, 0}
{0, 0}
vclock
vclock
Запись в журнал
{3, 0}
Репликация
Запись в журнал
{3, 0}
Ожидание в лимбе
Подтверждение
{3, 0}
Ожидание в лимбе
3
1
2
3
1
2
3
1
2
3
1
2
3
1
2
Сборка кворума
Лимб использует особый vclock
{0, 0, 0}
{0, 0, 0}
vclock журнала
vclock лимба
{0, 0, 0}
{0, 0, 0}
Транзакции
{0, 4, 0}
{0, 4, 0}
Репликация
{0, 4, 0}
{0, 4, 0}
Подтверждения
{4, 4, 4}
Лимб видит, что все транзакции с LSN <= 4 собрали кворум 3
Транзакции
{0, 8, 0}
Репликация
{0, 8, 0}
{0, 8, 0}
Подтверждения
{4, 8, 4}
{8, 8, 4}
Лимб видит, что LSN 4 собрал кворум 3, а LSN 8 - кворум 2
Реплика отказала
Replica ID = 1
Replica ID = 3
Replica ID = 2
{0, 0, 1, 0, 0}
Лимб
При сборке подтверждений лимб проверяет кворум
{1, 0, 1, 0, 0}
Получено подтверждение от реплики ID = 1. Всего 2, а кворум - 3. Ждать еще
{1, 0, 1, 2, 2}
Получено подтверждение от реплик ID = 4 и 5.
1
2
3
4
5
6
7
8
...
LSN
Коммит
Новые транзакции
Коммит
Commit({LSN = 1})
Коммит пишется в журнал
{5, 5, 4, 6, 7}
Получены новые подтверждения от всех реплик:
Коммит LSN 5
Коммит
Commit({LSN = 5})
Коммит пишется в журнал
Против бесконечного роста очереди есть таймаут
По истечении удаляются все транзакции, в журнал пишется ROLLBACK, пользователи получают ошибку
Но ROLLBACK не значит, что репликация не произошла. То есть при смерти мастера транзакция может получить коммит на новом мастере
Удаляются все, так как могут быть зависимы по данным
Автоматически
box.cfg{ election_mode = <mode>, election_timeout = <seconds>, replication_synchro_quorum = <count>, }
Автоматика по второй части протокола Raft
Режим - off/candidate/voter
Таймаут выборов в секундах
Кворум на выборы и репликацию
Вручную
box.ctl.clear_synchro_queue()
Новый лидер должен иметь самый большой LSN старого лидера среди остальных узлов
Смотреть по
box.info.replication
На новом лидере надо позвать
Чтобы очистить лимб
box.cfg{ election_mode = <mode>, election_timeout = <seconds>, replication_synchro_quorum = <count>, replication_synchro_timeout = <seconds>, replication_timeout = <seconds>, }
box.info.replication
box.ctl.clear_synchro_queue()
box.info.election
Опции выборов
Опции синхронной репликации
box.space.<name>:alter{is_sync = true}
box.schema.create_space(name { {is_sync = true} })
Включить на существующем спейсе
Создать новый спейс
Формат журнала
В Raft журнал линейный - один LSN для всех
В Tarantool журнал векторный - у всех свой LSN
Тип журнала
В Raft журнал UNDO - можно откатывать с конца
В Tarantool журнал REDO - нельзя откатить, если уже записано
Открывает дорогу к синхронной мастер-мастер репликации
Реплика не может удалить из журнала незакоммиченные транзакции - может требовать инициализации с лидера с нуля
Мастер-мастер синхронная репликация
Авто-сборка кластера со SWIM
Интеграция с VShard
Пример выборов лидера
Пример синхронной репликации
Официальный сайт
By Vladislav Shpilevoy
Использование асинхронной репликации может приводить к потере транзакции, если мастер-узел выходит из строя сразу после ее коммита. Синхронная репликация позволяет добиться репликации транзакции на заданное число реплик до ее коммита, когда изменения становятся видны пользователю. В данном докладе рассматривается один из таких алгоритмов - Raft, и его применение в СУБД Tarantool.
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.