diff --git a/docs/clustering.md b/docs/clustering.md index ed55d48481fe9e32ea36cb40707c243c6ee1dbad..eabe14cab772db5e501cd597741d34deb53ca125 100644 --- a/docs/clustering.md +++ b/docs/clustering.md @@ -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) и пытаетÑÑ Ð¸Ñ… организовать. Пока не организует, никаких других изменений в текущих грейдах не произойдет (но могут изменитьÑÑ Ñ†ÐµÐ»ÐµÐ²Ñ‹Ðµ). ЕÑли активноÑти ÑфейлÑÑ‚ÑÑ, то на Ñледующей итерации они будут перевычиÑлены Ñ ÑƒÑ‡ÐµÑ‚Ð¾Ð¼ новых целей. + + + +Какие бывают активноÑти? Давайте перечиÑлим. + +### 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` на вÑем клаÑтере. diff --git a/docs/fsm.svg b/docs/fsm.svg new file mode 100644 index 0000000000000000000000000000000000000000..128dfd5e1ab9ba541e3c86ed974855e1ba60256e Binary files /dev/null and b/docs/fsm.svg differ