Skip to content
Snippets Groups Projects
Commit 0cba86d7 authored by Yaroslav Dynnikov's avatar Yaroslav Dynnikov
Browse files

doc: desribe instance grades in clustering.md

parent 3c440a8f
No related branches found
No related tags found
1 merge request!226doc: extend clustering.md
Pipeline #11482 passed
......@@ -56,7 +56,7 @@ picodata run --instance-id iN --listen iN --peer i1
Логика функции `postjoin()` одинакова для всех инстансов. К этому моменту для инстанса уже инициализированы корректные пространства хранения в БД и могут быть накоплены записи в журнале Raft. Инстанс инициализирует узел Raft и проверяет, что данные синхронизированы (`read barrier` получен) и журнал Raft актуален. Из журнала становятся известны параметры репликации, и инстанс начинает синхронизацию данных уровне репликационных групп Tarantool.
Следующим шагом инстансу необходимо актуализировать свой статус (`UpdatePeerRequest{ grade: Online, failure_domain }`), и дождаться коммита этой записи в Raft.
Следующим шагом инстансу необходимо актуализировать свой статус (`UpdatePeerRequest{ target_grade: 50_Online, failure_domain }`), и дождаться коммита этой записи в Raft.
Теперь узел Raft готов к использованию.
......@@ -100,8 +100,14 @@ struct Peer {
peer_address: String,
failure_domain: FailureDomain,
/// Loading / Online / Offline
grade: Grade,
/// 0_Offline (current / target)
/// 10_RaftSynced (current)
/// 20_BoxSynced (current)
/// 30_VshardInitialized (current)
/// 50_Online (current / target)
/// 60_Expelled (current / target)
target_grade: Grade,
current_grade: Grade,
/// Индекс записи в Raft-журнале. Препятствует затиранию
/// более старыми записями, по мере применения Raft-журнала.
......@@ -113,7 +119,7 @@ struct Peer {
- `JoinRequest` отправляет всегда неинициализированный инстанс.
- В зависимости от того, содержится ли в запросе `instance_id`, проводится анализ его корректности (уникальности).
- В процессе обработки запроса в Raft-журнал добавляется запись `op::PersistPeer{ peer }`, который помимо всевозможных айдишников содержит поле `grade: Loading`, которое играет важную роль в обеспечении надежности кластера. [TODO](## "Сейчас в коде Online вместо Loading, но это надо исправить.")
- В процессе обработки запроса в Raft-журнал добавляется запись `op::PersistPeer{ peer }`, который помимо всевозможных айдишников содержит поля `current_grade: 0_Offline`, `target_grade: 50_Online`, которые играет важную роль в обеспечении надежности кластера.
- Обработка запроса также включает в себя добавление инстанса в Raft-группу в роли `Learner` (процедура также известная как `raft::ConfChangeV2`). Raft не позволяет изменять топологию, пока предыдущее изменение не было применено. Поэтому в целях оптимизации обработка идет в отдельном потоке (т.н. `raft_conf_change_loop`) и выполняется группами.
- Прежде чем отвечать на запрос, инстанс дожидается применения изменений конфигурации. Это произойдет после того как он выйдет из состояния `joint state` ([подробнее](https://web.stanford.edu/~ouster/cgi-bin/papers/OngaroPhD.pdf), §4.3). [TODO](## "этот тезис в коде пока не реализован")
- В ответ выдаётся всегда новый `raft_id`, никому другому ранее не принадлежавший.
......@@ -127,7 +133,7 @@ struct Peer {
Поток представляет собой бесконечный цикл. На каждой итерации выполняется проверка, что состав `voters` / `learners` соответствует состоянию инстансов, и при необходимости эти изменения пачкой записываются в Raft-журнал:
- Инстансы в статусе `grade: Loading` довавляются как неголосующие.
- Инстансы с `target_grade: 50_Online` довавляются как неголосующие.
- Если есть возможность, `Offline` инстансы передают право голоса другим `Online`. [TODO](## "Сейчас offline инстанс демоутится безусловно, даже если других онлайн кандидатов нет. Не надо так делать.")
- Если общее количество голосующих инстансов оказывается меньше целевого, `Online` инстансы получают право голоса.
......@@ -154,10 +160,6 @@ struct Peer {
Была у нас однажды такая история - шла разработка graceful shutdown. Тест (`test_joining.py::test_deactivation`) останавливал один из двух инстансов и проверял, что тот (назовем его i2) перстал быть голосующим. Иногда тест проходил нормально, но иногда падал - `i2` завершал работу раньше, чем `i1` получал от него подтверждение. При этом критерий остановки включал в себя ожидание коммита, но только локально на `i2`, а не на `i1`. Из-за этого `i1` терял кворум.
# Кто и когда выполняет box.cfg и vshard.cfg?
Отличный вопрос. Ответ на него нам предстоит найти.
# Graceful shutdown
Чтобы выключение прошло штатно и не имело негативных последствий необходимо следующее:
......@@ -165,4 +167,48 @@ struct Peer {
- Инстанс не должен оставаться воутером, пока есть другие онлайн кандидаты.
- Инстанс не должен оставаться лидером.
Чтобы этого добиться, каждый инстанс на `on_shutdown` триггер отправляет лидеру запрос `UpdatePeerRequest{ grade: Offline }`. Непосредственно изменением роли `voter` -> `learner` занимается отдеьный поток на лидере (тот самый `raft_conf_change_loop`), инстанс только дожидается его применения.
Чтобы этого добиться, каждый инстанс на `on_shutdown` триггер отправляет лидеру запрос `UpdatePeerRequest{ target_grade: 0_Offline, graceful: true }`. Непосредственно изменением роли `voter` -> `learner` занимается отдеьный поток на лидере (тот самый `raft_conf_change_loop`), инстанс только дожидается его применения.
# Описание состояний кластера
В отличие от других кластерных решений (например, того же Tarantool Cartridge) Picodata не использует понятие "состояния" для отдельных инстансов. Вместо этого мы говорим об их "грейдах". Грейд инстанса — это лишь синоним слова "состояние", но измениться спонтанно он не может. Мы вводим два конкретных термина: `current_grade` и `target_grade`.
Инициировать изменение `current_grade` может только лидер при поддержке кворума, что гарантирует консистентность принятого решения (и внушает доверие по части отказоустойчивости всей системы).
Инициировать изменение `target_grade` может кто угодно — это может быть сам инстанс (при добавлении), или админ командой `picodata expel`, или нажав Crtl+C на клавиатуре. `target_grade` - это желаемое состояние инстанса, в которое тот должен прийти.
Приведением действительного к желаемому занимается специальный файбер на лидере - `topology_governor` <!-- или лучше grade_manager? -->. Он управляет всеми инстансами сразу.
На основе совокупности грейдов `topology_governor` на каждой итерации бесконечного цикла придумывает ~~дурацкие менеджерские~~ активности (activity) и пытается их организовать. Пока не организует, никаких других изменений в текущих грейдах не произойдет (но могут измениться целевые). Если активности сфейлятся, то на следующей итерации они будут перевычислены с учетом новых целей.
![Instance states](fsm.svg "Возможные переходы состояний инстанса")
Какие бывают активности? Давайте перечислим.
### 1. Обновить состав воутеров / лернеров
Надо сгенерировать `ConfChangeV2`. Если он пустой, переходим к следующиему шагу. Нет — отправляем его в рафт. Если не получилось - начинаем новую итерацию и перевычисляем активности. Получилось - ждем события `JointStateLeave`.
### 2. Обработать target_grade 0_Offline и 60_Expelled.
По большей части активности сводятся к удалению инстанса из воутеров. Этот шаг уже пройден, поэтому всех желающий стать `0_Offline` можно сразу обновлять. Для `target_grade: 60_Expelled` требуется также подчистить `box.space.cluster` у его оставшихся реплик. Также, если это последний сторадж в репликасете, ему надо выставить вес в 0. Дожидаться ребалансировки на этом шаге не требуется (да и не получится — слишком долгая блокировка), для этого есть отдельный пункт.
### 3. Запромоутить 0_Offline в 10_RaftSynced
Операция выполняется для одного инстанса за раз (потом ее можно будет распараллелить). Чтобы это произошло, достаточно взять текущий commit_index на лидере и дождаться, пока выбранный инстанс его к себе применит. Как только это произошло, инстансу можно присваивать 10 грейд.
### 4. Запромоутить 10_RaftSynced в 20_BoxSynced
Данная активность включает в всебя выполнение `box.cfg({replication})` на всех инстансах выбранного репликасета. Перед выполнением все инстансы дожидаются применения `commit_index`, который сообщит им лидер. При успешном результате сразу несколько инстансов могут быть запромоучены.
### 5. Запромоутить 20_BoxSynced в 30_VshardInitialized
Это уже веселее. Эта активность выполняется на всем кластере. В первую очередь надо проверить, всем ли репликасетам назначено хоть какое-то значение vshard_weight. Если нет — простоавить 0 и дождаться коммита. По итогу надо убедиться, что все инстаны выполнили `vshard.router.cfg()` и `vshard.storage.cfg()`. Как и в предыдущем случае, грейд дается нескольким инстансам сразу.
### 6. Запромоутить 30_VshardInitialized в 50_Online
Данная активность аналогична предыдущей, только vshard_weight должен быть 1. Потом необходимо еще раз убедиться, что все инстансы в кластере выполнили `vshard.router.cfg()` и `vshard.storage.cfg()`.
### 7. Возвращаясь к 60_Expelled
Наименьший приоритет имеет активность, связанная с ожиданием окончания ребалансироки. В конце концов инстанс должен быть удален из `box.space.cluster` на всех оставшихся репликах и из `vshard.router.cfg` и `vshard.storage.cfg` на всем кластере.
docs/fsm.svg 0 → 100644
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment