Изобретая синхронную репликацию

Владислав Шпилевой

План доклада

План доклада

Репликация

История репликации в Tarantool

Алгоритм Raft

Транзакции в Tarantool

Синхронная репликация

Интерфейс

Отличия от Raft

Планы

Репликация [1]

Репликационная группа – "репликасет"

Мастер

Реплика

Реплика

Реплика

Мастер

Мастер

Мастер

Задачи

  • Резервная копия
  • Балансировка запросов

Типы

  • Асинхронная
  • Синхронная

Репликация [2]: асинхронная

Запись в журнал – успешный коммит. Не ждет репликацию

Транзакция

Журнал (мастер)

Коммит и ответ

Репликация

Журнал (реплика)

Транзакция

1

Гибель мастера между (3) и (5) – потеря транзакции.

3

Коммит и ответ

Журнал

2

4

Репликация

Журнал

5

Репликация [3]: потеря данных

{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}

Репликация работает

Репликация [4]: грязные чтения

Репликация сломалась

Ожидание репликации

Журнал

{money: -150}

Журнал

{money: 0}

{money: 100}

{success}

Ждать репликацию вручную – видеть незакоммиченные данные

Кладу еще 50, чтобы купить что-то

Жду репликации, но изменения уже видимы

Покупаю на "грязные" деньги

Жду репликации

Таймаут на пополнение – откат

А покупка реплицировалась

Из-за грязного чтения покупка прошла бесплатно (это плохо)!

Синхронная репликация [1]

Транзакция

Журнал

(мастер)

Репликация

Журнал

(реплика)

Коммит и ответ

Репликация гарантируется до коммита

1

Транзакция

2

Журнал

Коммит и ответ

5

3

Репликация

Журнал

4

Синхронная репликация [2]: кворум

Типичные конфигурации

1 + 1

Тройка

50% + 1

Синхронная репликация [3]

{money: 100}

У меня 100 денег

Кладу еще 50

У меня 150 денег

{money: 150}

{money: 100}

Синхронная репликация гарантирует сохранность данных, пока достаточное количество узлов живо

{money: +50}

{success}

get({money})

{money: 150}

Журнал

Репликация

Журнал

{money: 150}

{money: 150}

Репликация

Ответ

Коммит

{money: 150}

Коммит

{money: 150}

Репликация происходит до коммита

Коммит происходит после подтверждений от кворума реплик

Сам факт коммита реплицируется асинхронно, но его потеря уже не критична

Сравнение

Синхронная репликация

Асинхронная репликация

Быстрая

Медленная

Высокая доступность

Хрупкая доступность

Легко конфигурировать

Трудно конфигурировать

Есть мастер-мастер

Только мастер-реплика

Легко потерять данные

Повышенная надежность

Raft

Алгоритм синхронной репликации

Гарантия сохранности данных на > 50% узлов;

Чрезвычайная простота

Проверен временем

Выборы лидера

История разработки  [1]

Год 2015

Появился спрос на синхронность

Выбрали Raft, составили великий план

Модуль SWIM – для сборки кластера

Авто-прокси

Ручные выборы – box.ctl.promote()

Оптимизации репликаци

Raft

История разработки [2]

Создание плана

box.ctl.promote()

SWIM

Смена руководства

Raft

2015

2018

2019

2020

Авто-прокси

Оптимизации репликации

SQL

Vinyl

Рафт [1]: общая схема

Это Синхронная репликация и Выборы лидера

Роли:

– лидер

– реплика

– кандидат

Каждый узел изначально реплика

Узлы хранят персистентный терм – логические часы кластера

Терм: 1

Терм: 1

Терм: 1

Терм: 1

Терм: 1

Ждут лидера. Долго нет лидера – начинаются выборы

Запрос голосов

Терм: 2

Терм: 2

После набора большинства один становится лидером

Терм: 2

Терм: 2

Терм: 2

Лидер принимает все транзакции, реплицирует, и завершает коммит сразу по достижении кворума

Репликация

Кворум

Коммит

Подтверждения

Догонка реплик

Транзакция

Ответ

Рафт [2]: синхронная транзакция

Четыре этапа коммита транзакции

Данные в журналы реплик

Коммит в журнал лидера и ответ пользователю

Данные в журнал лидера

Коммит в журналы реплик

Отказ на любом этапе любого инстанса не приводит к потере данных после коммита, пока живо 50% + 1 инстанс

Рафт [3]: синхронный журнал

Транзакции, термы, голоса, коммиты

Терм 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

Рафт [4]: откат транзакций

2

3

2

3

1

1

1

2

?

4

Новые транзакции

Коммит первой транзакции

Вторая в журнале, и лидер сразу офлайн

Лидером выбрался другой инстанс

Он закоммитил две других транзакции – в новом терме

Первый лидер вернулся как реплика – конфликт!

Откат незакоммиченного хвоста

Можно лить транзакции дальше

2

3

Терм 1

Терм 2

Транзакции в Tarantool

ID транзакции: {Replica ID, LSN}

  • Replica ID – 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()

Коммит начинается с записи в журнал мастера

Ожидание кворума [1]: лимб

После журнала транзакция попадает в лимб

Лимб – это очередь синхронных транзакций

Новые транзакции

Кворум

5

7

10

...

LSN

Закоммиченные транзакции

Ожидание кворума [2]: репликация

Синхронные транзакции надо доставить на реплики и собрать подтверждения

Транзакции

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

Мастер делает транзакции

Пишет в свой журнал

Они попадают в лимб

Происходит репликация на другие инстансы

Реплика пишет эти же транзакции в свой журнал

И они попадают снова в лимб, но на реплике

Реплика посылает лидеру подтверждение

Ожидание кворума [3]: сборка

Лимб использует особый vclock

  • Replica ID – ID реплики
  • LSN – последний LSN лидера, подтвержденный этой репликой

Лидер делает 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}

  • LSN 8 – два инстанса = кворум
  • LSN 9 – один инстанс = нет кворума

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]

Против бесконечного роста очереди есть тайм-аут

1

2

3

4

5

6

7

ROLLBACK LSN 1

По истечении удаляются все транзакции, в журнал пишется ROLLBACK, пользователи получают ошибку

Удаляются все, так как могут быть зависимы по данным

Но ROLLBACK не гарантирует ничего! В случае перевыборов может произойти коммит

Откат синхронной транзакции [2]

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

Этот инстанс в кластер не вернется!

API

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

Кворум на выборы и репликацию

Пример [1]

Конфигурация мастера

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%, для наглядности

Пример [2]

Конфигурация реплик

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 – нужны только на мастере

Пример 3

$> 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
...

Запуск синхронной транзакции в корутине

Пример [4]

Реплика 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]
...

Отличия от Raft

Векторный формат журнала

REDO-журнал

Чтение реплик

Откаты транзакций по времени

Дорога к мастер-мастер-синхронности

Нельзя удалить лишние транзакции

Ограничение очереди

Выше доступность

Планы

Опции для транзакций

box.commit({is_lazy, is_sync})

Триггеры на выборы

box.ctl.on_election(function()
    ...
end)

Повышение стабильности

И API может поменяться

Мои доклады

Мемы

Highload 2021: Изобретая синхронную репликацию

By Vladislav Shpilevoy

Highload 2021: Изобретая синхронную репликацию

Использование асинхронной репликации может приводить к потере транзакции, если мастер-узел выходит из строя сразу после ее коммита. Синхронная репликация позволяет добиться репликации транзакции на заданное число реплик до ее коммита, когда изменения становятся видны пользователю. В данном докладе рассматривается один из таких алгоритмов - Raft, и его применение в СУБД Tarantool.

  • 986