From 51e20a36ff62c196d01e1529edb4818ba3f6d224 Mon Sep 17 00:00:00 2001
From: Yaroslav Dynnikov <yaroslav.dynnikov@gmail.com>
Date: Wed, 16 Mar 2022 16:34:10 +0300
Subject: [PATCH] doc: describe bootstrapping process

Close https://gitlab.com/picodata/picodata/picodata/-/issues/27
---
 docs/clustering.md | 51 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 51 insertions(+)
 create mode 100644 docs/clustering.md

diff --git a/docs/clustering.md b/docs/clustering.md
new file mode 100644
index 0000000000..6de6bb6c83
--- /dev/null
+++ b/docs/clustering.md
@@ -0,0 +1,51 @@
+# Создание кластера. Алгоритм и детали реализации.
+
+## fn main()
+
+```
+// Каждый инстанс обладает на старте следующей информацией:
+// --advertise-uri (по-дефолту <hostname>:<listen_port>)
+// --replicaset-id (опционально)
+// --instance-id (опционально)
+// --peers
+```
+
+1. Если у инстанса уже есть снапшоты, можно сразу вызывать box.cfg() и стартовать рафт ноду.
+1. Инстанс идёт собирать транзитивное замыкание в дочернем процессе.
+1. Дочерний процесс поднимает listen порт и высовывает хранимку raft_discover.
+1. Дочерний процесс пробегается по всем --peer, обменивается с ними этими списками, а заодно получает айдишники (instance_id, replicaset_id) и advertise_address.
+1. Возможно ответом будет "нечего тут собирать, у нас уже есть лидер такой-то", тогда можно пойти сразу на него и goto *join*.
+1. Как только ото всех пиров (известных изначально, и обнаруженных в процессе) получен утвердительный ответ, транзитивное замыкание считается собраным.
+1. Транзитивное замыкание гарантирует нам одинаковость собранной информации на всех инстансах, в этом сборе участвовавших.
+1. Теперь выбираем кто будет главным. Для этого сравниваются лексикографически все instance_id. Этот инстанс присваивает себе raft_id = 1, генерит снапшот рафт ноды с одним собой любимым, персистит его, и начинает тикать нодой. Бутстрап первого инстанса закончен.
+1. Остальные инстансы goto *join*
+
+### join
+
+1. Если нас послали делать джойн, значит адрес лидера уже известен. Либо нам его сказали в ответе на raft_discover, либо мы его сами вычислили из транзитивного замыкания.
+1. Инстанс идет на лидера и говорит: "приджойнь меня в кластер, пожалуйста". Попутно сообщает свои айдишники и адвертайз урл.
+1. И лидер приджойнивает. И любезно сообщает в ответ raft_id нашей ноды. А ещё правильный instance_id и replicaset_id, и урлы братьев по репликации.
+1. Обладая этой информацией, инстанс стартует box.cfg с параметрами репликации.
+1. Потом создает сервисные рафтовые спейсы и стратует рафт ноду.
+1. И вот теперь то бутстрап окончен.
+
+## fn raft_discover()
+
+Если транзитивное замыкание еще не собрано, сервер на запрос raft_join присылает свой адвертайз, айдишники, и все пиры.
+
+Если нода давно забутстраплена, сервер отвечает "у нас уже есть лидер", достает его raft_id из статуса рафта, а по raft_id - его адвертайз из спейса с рафт топологией.
+
+На запрос raft_discover есть и третий вариант ответа, когда транзитивное замыкание уже собрано, но сама рафт нода еще не проинициализирована. В этом случае серверу правильнее отдавать значение advertise_uri первого инстанса, вычисленное из транзитивного замыкания. Именно оно прилетит из рафт снапшота как только нода поднимется.
+
+## fn raft_join()
+
+1. Во-первых, если это на самом деле не лидер, надо вернуть ошибку.
+1. Из аргументов известны айдишники и адвертайз адрес клиента.
+1. В ответ надо дать raft_id.
+1. Ещё в ответе должны быть replicaset_uuid (правильно сгенеренный, либо выбранный среди существующих), instance_uuid (ну так, за компанию), урлы других инстансов из его репликасета, чтобы он сразу мог забутстрапить репликацию. Всё это раздаётся из таблички с топологией, но сначала её надо туда положить, причем класть надо средствами рафта.
+1. Будущий raft_id выбирается как box.space.raft_topology:max() + 1
+1. Алгоритм выбора replciaset_uuid и instance_uuid мы пока не обсуждаем, пусть пока это будет чистый рандом.
+1. Коммитить в рафт изменение топологии надо пачкой - в propose_conf_change надо упихать всю инфу об инстансе, иначе инфа может разъехаться.
+1. Есть и ещё одна проблема. Тк комит в рафт - штука не быстрая, другие конкурентные запросы (от других инстансов) всё это время будут получать отлуп типа "conf change already in progress". Это может сильно замедлить сборку больших кластеров.
+1. Чтобы ускорить процесс джойна, запросы на джойн должны вставать в очередь, и обрабатываться траншами.
+1. Ни один клиент не уйдёт обиженным, получит свой raft_id, и сможет запуститься
-- 
GitLab