From 3bcfae22ae56e921ff83ab2eb0c0910e30b245d6 Mon Sep 17 00:00:00 2001
From: Yaroslav Dynnikov <yaroslav.dynnikov@gmail.com>
Date: Mon, 23 Jan 2023 18:03:03 +0300
Subject: [PATCH] doc: enhance clustering.md

---
 docs/clustering.md | 65 ++++++++++++++++++++++++++++++++--------------
 1 file changed, 46 insertions(+), 19 deletions(-)

diff --git a/docs/clustering.md b/docs/clustering.md
index 526cdb9dd9..81cd32bccf 100644
--- a/docs/clustering.md
+++ b/docs/clustering.md
@@ -1,7 +1,7 @@
 # Общая схема инициализации кластера
-Данный раздел содержит описание архитектуры Picodata, в том числе
+Данный раздел содержит описание архитектуры Picodata, а конкретно —
 высокоуровневый процесс инициализации кластера на основе нескольких
-отдельно запущенных экземпляров Picodata (инстансов).
+отдельно запущенных экземпляров (инстансов) Picodata.
 
 Администратор запускает несколько инстансов, передавая в качестве
 аргументов необходимые параметры:
@@ -37,13 +37,16 @@ picodata run --instance-id iN --listen iN --peer i1
 
 ![main.rs](clustering_curves.svg "main.rs control flow")
 
+В контексте операционных систем каждый инстанс соответствует группе из
+двух процессов — родительсого (supervisor) и дочернего (именно он
+выполняет tarantool runtime).
+
 Красным показан родительский процесс, который запущен на всем протяжении
 жизненного цикла инстанса. Вся логика, начиная с присоединения к
 кластеру, и заканчивая обслуживанием клиентских запросов, происходит в
 дочернем процессе (голубой цвет). Единственное предназначение
-родительского процесса — иметь возможность сбросить состояние дочернего
-(выполнить rebootstrap) и инициализировать его повторно (сиреневый
-цвет).
+родительского процесса — выполнять [ребутстрап](#Ребутстрап) и
+инициализировать дочерний процес повторно (сиреневый цвет).
 
 Данная схема наиболее полно отражает логику кода в файле `main.rs`. Ниже
 описаны детали выполнения каждого этапа и соответствующей программной
@@ -54,11 +57,35 @@ picodata run --instance-id iN --listen iN --peer i1
 На этом этапе происходит ветвление (форк) процесса `picodata`.
 Родительский процесс (supervisor) ожидает от дочернего процесса
 сообщения по механизму IPC и при необходимости перезапускает дочерний
-процесс. Также, при необходимости дочерний процесс может попросить
-родителя удалить все файлы БД, т.е. вызвать функцию `drop_db()`. Это
-может понадобиться для повторной инициализации кластера когда, например,
-у инстанса изначально имеется временный, случайно сгенерированный
-`replicaset_id`.
+процесс.
+
+Выполнение дочернего процесса начинается с вызова функции
+[`start_discover()`](#fn-start_discover) и далее следует алгоритму. При
+необходимости дочерний процесс может попросить родителя удалить все
+файлы БД (см. раздел ["Ребутстрап"](#Ребутстрап)). Это используется для
+повторной инициализации инстанса с нормальным `replicaset_uuid` вместо
+рандомного.
+
+### Ребутстрап
+
+У тарантула есть две особенности, из-за которых процесс инициализации
+выглядит так как выглядит:
+
+1. Принадлежность инстанса тому или иному репликасету определяется в
+   момент первого вызова `box.cfg()` когда создается первый снапшот.
+   Впоследстии изменить принадлежность репликасету невозможно.
+2. Инициализация iproto сервера, реализующего бинарный сетевой протокол
+   тарантула, выполняется той же функцией `box.cfg()`.
+
+В совокупности эти две особенности создают проблему курицы и яйца:
+
+- Инстанс не может общаться по сети, пока не узнает принадлежность
+  репликасету.
+- Принадлежность репликасету невозможно узнать без общения по сети.
+
+Чтобы эту проблему решить, Picodata инициализируется со случайно
+сгенерированными идентификаторами, а позже перезапускает процесс,
+попутно очищая рабочую директорию.
 
 ### fn start_discover()
 
@@ -70,11 +97,11 @@ picodata run --instance-id iN --listen iN --peer i1
 discovery не будет, инстанс сразу перейдет к этапу `postjoin()`. В
 противном случае, если место инстанса в кластере еще не известно,
 алгоритм discovery определяет значение флага `i_am_bootstrap_leader` и
-адрес лидера Raft-группы. Далее инстанс сбрасывает свое состояние (этап
-rebootstrap), чтобы повторно провести инициализацию `box.cfg()`, теперь
-уже с известными параметрами. Сам лидер (единственный с
-`i_am_bootstrap_leader == true`) выполняет функцию `start_boot()`.
-Остальные инстансы переходят к функции `start_join()`.
+адрес лидера Raft-группы. Далее инстанс сбрасывает свое состояние (см.
+["Ребутстрап"](#Ребутстрап)), чтобы повторно провести инициализацию
+`box.cfg()`, теперь уже с известными параметрами. Сам лидер
+(единственный с `i_am_bootstrap_leader == true`) выполняет функцию
+`start_boot()`. Остальные инстансы переходят к функции `start_join()`.
 
 ### fn start_boot()
 
@@ -89,10 +116,10 @@ rebootstrap), чтобы повторно провести инициализа
 
 ### fn start_join()
 
-Вызову функции `start_join()` всегда предшествует rebootstrap (удаление
-БД и перезапуск процесса), поэтому на данном этапе в БД нет ни модуля
-`box`, ни пространства хранения. Функция `start_join()` имеет простое
-устройство:
+Вызову функции `start_join()` всегда предшествует
+[ребутстрап](#Ребутстрап) (удаление всех данных и перезапуск процесса),
+поэтому на данном этапе в БД нет ни модуля `box`, ни пространства
+хранения. Функция `start_join()` имеет простое устройство:
 
 Инстанс-клиент отправляет запрос `raft_join` лидеру Raft-группы (он
 известен после discovery). После достижения консенсуса в Raft-группе
-- 
GitLab