Vladislav Shpilevoy PRO
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.
План доклада
Репликация
История репликации в Tarantool
Алгоритм Raft
Транзакции в Tarantool
Синхронная репликация
Интерфейс
Отличия от 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}
Журнал
Начало репликации
{money: 100}
{money: 150}
У меня 100 денег
{money: +50}
{failure}
Репликация работает
Репликация сломалась
Ожидание репликации
Журнал
{money: -150}
Журнал
{money: 0}
{money: 100}
{success}
Ждать репликацию вручную – видеть незакоммиченные данные
Кладу еще 50, чтобы купить что-то
Жду репликации, но изменения уже видимы
Покупаю на "грязные" деньги
Жду репликации
Таймаут на пополнение – откат
А покупка реплицировалась
Из-за грязного чтения покупка прошла бесплатно (это плохо)!
Транзакция
Журнал
(мастер)
Репликация
Журнал
(реплика)
Коммит и ответ
Репликация гарантируется до коммита
1
Транзакция
2
Журнал
Коммит и ответ
5
3
Репликация
Журнал
4
Типичные конфигурации
1 + 1
Тройка
50% + 1
{money: 100}
У меня 100 денег
Кладу еще 50
У меня 150 денег
{money: 150}
{money: 100}
Синхронная репликация гарантирует сохранность данных, пока достаточное количество узлов живо
{money: +50}
{success}
get({money})
{money: 150}
Журнал
Репликация
Журнал
{money: 150}
{money: 150}
Репликация
Ответ
Коммит
{money: 150}
Коммит
{money: 150}
Репликация происходит до коммита
Коммит происходит после подтверждений от кворума реплик
Сам факт коммита реплицируется асинхронно, но его потеря уже не критична
Синхронная репликация
Асинхронная репликация
Быстрая
Медленная
Высокая доступность
Хрупкая доступность
Легко конфигурировать
Трудно конфигурировать
Есть мастер-мастер
Только мастер-реплика
Легко потерять данные
Повышенная надежность
Алгоритм синхронной репликации
Гарантия сохранности данных на > 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
Терм: 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
Log Index
2
3
2
3
1
1
1
2
?
4
Новые транзакции
Коммит первой транзакции
Вторая в журнале, и лидер сразу офлайн
Лидером выбрался другой инстанс
Он закоммитил две других транзакции – в новом терме
Первый лидер вернулся как реплика – конфликт!
Откат незакоммиченного хвоста
Можно лить транзакции дальше
2
3
Терм 1
Терм 2
ID транзакции: {Replica ID, LSN}
VClock – пары {Replica ID, LSN}, снимок состояния журнала
Replica ID = 1
Replica ID = 2
Replica ID = 3
{
0
0
0
}
{
0
0
0
}
{
0
0
0
}
1
1
1
2
2
2
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()
Коммит начинается с записи в журнал мастера
После журнала транзакция попадает в лимб
Лимб – это очередь синхронных транзакций
Новые транзакции
Кворум
5
7
10
...
LSN
Закоммиченные транзакции
Синхронные транзакции надо доставить на реплики и собрать подтверждения
Транзакции
vclock
vclock
Запись в журнал
Репликация
Запись в журнал
Ожидание в лимбе
Подтверждение
{3, 0}
Ожидание в лимбе
3
1
2
3
1
2
3
1
2
3
1
2
3
1
2
Сборка кворума
{
0
0
}
3
{
0
0
}
3
Мастер делает транзакции
Пишет в свой журнал
Они попадают в лимб
Происходит репликация на другие инстансы
Реплика пишет эти же транзакции в свой журнал
И они попадают снова в лимб, но на реплике
Реплика посылает лидеру подтверждение
Лимб использует особый vclock
Лидер делает 4 транзакции
{
0
0
0
}
{
0
0
0
}
Replica ID = 2
{
0
0
0
}
{
0
0
0
}
Replica ID = 1
Replica ID = 3
4
4
Сам их подтверждает в лимбе
Репликация и запись в журналы реплик
4
4
Реплики отправили подтверждения
4
4
{0, 4, 0}
{0, 4, 0}
Внутри лидер доставляет их в лимб – есть кворум на LSN 4
Еще 5 транзакций и послал реплики
9
9
Одна реплика отказала, другая ответила частично
8
{0, 8, 0}
8
Лимб
8
...
100
Новые транзакции
{
0
100
}
0
0
0
Все транзакции уже в журнале лидера
LSN лидера в лимбе
1
Пришло одно подтверждение – кворума нет
3
2
Еще два подтверждения от других реплик
Есть кворум на LSN 2: от трех инстансов
1
2
CONFIRM LSN 2
8
6
4
7
Коммит сразу пяти транзакций
Еще несколько подтверждений
Максимальный LSN с кворумом – 7, его можно коммитить
CONFIRM LSN 7
3
4
5
6
7
Против бесконечного роста очереди есть тайм-аут
1
2
3
4
5
6
7
ROLLBACK LSN 1
По истечении удаляются все транзакции, в журнал пишется ROLLBACK, пользователи получают ошибку
Удаляются все, так как могут быть зависимы по данным
Но ROLLBACK не гарантирует ничего! В случае перевыборов может произойти коммит
ROLLBACK не дает гарантий!
1
1
1
CONFIRM
LSN 1
CONFIRM
LSN 1
CONFIRM
LSN 1
Пусть есть транзакция на лидере
Кворум собран, коммит записан и разослан
Еще транзакция и репликация
2
2
2
Сеть сломалась! Подтверждения не дошли
Нет сети
Мастер делает откат, пользователь получил ошибку
ROLLBACK
LSN 2
Выбран новый лидер, и сделал коммит
CONFIRM
LSN 2
CONFIRM
LSN 2
Этот инстанс в кластер не вернется!
box.cfg{ replication_synchro_timeout = <seconds>, replication_synchro_quorum = <count> }
box.schema.create_space(name, { is_sync = true })
box.info.synchro --- - queue: len: 0 quorum: 1 ...
Создать синхронный спейс
Мониторинг
Таймаут на сбор подтверждений
replication_synchro_quorum = "N/2 + 1" replication_synchro_quorum = "math.max(2*N/3, 1)" replication_synchro_quorum = 5
Кворум на коммит
box.cfg{ election_mode = <mode>, election_timeout = <seconds>, replication_synchro_quorum = <formula>, }
Работает по Raft
candidate / voter / off
Таймаут выборов в секундах
Кворум на выборы и репликацию
box.cfg{ election_mode = <mode>, replication_synchro_quorum = <formula>, }
box.ctl.promote()
Назначение лидера
manual / voter / off
Кворум на выборы и репликацию
box.cfg{ listen = 3313, replication = { '127.0.0.1:3313', '127.0.0.1:3314', '127.0.0.1:3315' }, memtx_use_mvcc_engine = true, replication_synchro_quorum = 3, replication_synchro_timeout = 1000, } box.schema.user.grant('guest', 'super')
Две реплики
Не париться про доступы
Выключить грязные чтения
Кворум – 100%, для наглядности
box.cfg{ listen = 3314, replication = { '127.0.0.1:3313', '127.0.0.1:3314', '127.0.0.1:3315' }, read_only = true, memtx_use_mvcc_engine = true }
box.cfg{ listen = 3315, replication = { '127.0.0.1:3313', '127.0.0.1:3314', '127.0.0.1:3315' }, read_only = true, memtx_use_mvcc_engine = true }
Реплика 1
Реплика 2
Без опций replication_synchro – нужны только на мастере
$> s = box.schema.create_space( 'test', {is_sync = true}) $> _ = s:create_index('pk')
Реплика 2
Реплика 1
Мастер
Создание схемы
$> s:replace{1}
Проверка репликации
$> box.space.test:get({1})
---
- [1]
...
$> box.space.test:get({1})
---
- [1]
...
$> os.exit(1)
Реплика убита
$> fiber = require('fiber') $> f = fiber.create(function() s:replace{2} end) $> f:status() --- - suspended ...
Запуск синхронной транзакции в корутине
Реплика 2
Реплика 1
Мастер
Нет коммита – изменения не видны. На реплике тоже. Потому что нет кворума
$> s:get{2}
---
...
$> box.space.test:get{2}
---
...
$> box.cfg{...}
Перезапуск с тем же конфигом
$> f:status() --- - dead ...
Транзакция завершена на мастере
$> s:get{2} --- - [2] ...
Изменения видны везде
$> box.space.test:get{2}
---
- [2]
...
$> box.space.test:get{2}
---
- [2]
...
Векторный формат журнала
REDO-журнал
Чтение реплик
Откаты транзакций по времени
Дорога к мастер-мастер-синхронности
Нельзя удалить лишние транзакции
Ограничение очереди
Выше доступность
Опции для транзакций
box.commit({is_lazy, is_sync})
Триггеры на выборы
box.ctl.on_election(function() ... end)
Повышение стабильности
И API может поменяться
Мои доклады
Мемы
By Vladislav Shpilevoy
Использование асинхронной репликации может приводить к потере транзакции, если мастер-узел выходит из строя сразу после ее коммита. Синхронная репликация позволяет добиться репликации транзакции на заданное число реплик до ее коммита, когда изменения становятся видны пользователю. В данном докладе рассматривается один из таких алгоритмов - Raft, и его применение в СУБД Tarantool.
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.