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, и сможет запуститься