// Каждый инстанс обладает на старте следующей информацией:// --advertise-uri// --replicaset-id// --instance-id// --peers
Если инстанса уже есть снапшоты, goto rejoin
Пробуем подключиться к пирам и спросить "чо как"
Сразу поднимаем листен порт
Идём с наскока собирать транзитивное замыкание
На всякий случай передаём свой адвертайз, все пирс, айдишники.
все пирс передавать нельзя, иначе изза неправильного адвертайза это будет не правдой.
айдишники тоже, а в одном адвертайзе (да еще и ненадежном), смысла ноль.
Делаем ко всем пирам запрос raft_discover (без аргументов)
Возможно нам скажут "нечего тут собирать, у нас уже есть лидер такой-то", тогда можно пойти сразу на него и goto join
А возможно транзитивное замыкание пока не собрано. Тогда в ответе должны быть все пиры и адвертайз и айдишники этого инстанса. Так замыкание и собирается.
Ладно, замыкание собрали, че дальше делать?
Транзитивное замыкание гарантирует нам одинаковость собранной информации на всех инстансах, в этом сборе участвовавших.
Теперь выбираем кто будет главным. Для этого сравниваются лексикографически все instance_id.
Этот инстанс присваивает себе raft_id = 1, генерит снапшот рафт ноды с одним собой любимым, персистит его, и начинает тикать нодой. Бутстрап первыша закончен.
Остальные инстансы goto join
как работает хранимка raft_discover
Если транзитивное замыкание еще не собрано, сервер на запрос raft_join присылает свой адвертайз, айдишники, и все пиры
Если нода давно забутстраплена, сервер отвечает "у нас уже есть лидер", достает его raft_id из статуса рафта, а по raft_id - его адвертайз из спейса с рафт топологией. О том, как и когда инстансы обновляют свои адвертайзы, поговорим позже.
На заапрос raft_join есть и третий вариант ответа, когда транзитивное замыкание уже собрано, но сама рафт нода еще не проинициализирована. В этом случае серверу правильнее отдавать значение advertise_uri первыша, вычисленное из транзитивного замыкания. Именно оно прилетит к нам из рафт снапшота первыша как только нода поднимется.
join
Если нас послали делать джойн, значит адрес лидера уже известен. Либо нам его сказали в ответе на raft_discover, либо мы его сами вычислили из транзитивного замыкания.
Идём на этого лидера и говорим - "приджойнь меня в кластер, пожалуйста"
Конечно надо сообщить свои айдишники и адвертайз урл.
И он приджойнивает. И любезно сообщает в ответ raft_id нашей ноды. А ещё правильный instance_id и replicaset_id, и урлы братьев по репликации, потому что до сих пор у нас были какие-то временные спейсы.
Имея на руках все юиды, надо сделать ребутстрап тарантула.
Потом стартануть box.cfg с параметрами репликации.
Потом создать сервисные рафтовые спейсы.
Заперсистить адрес лидера, чтобы рафт смог стартануть (блин, еще и его передавать)
Стратануть рафт ноду.
И вот теперь то бутстрап окончен
как работает хранимка raft_join
Ну во-первых, если это на самом деле не лидер, надо вернуть ошибку
От клиента-кандидата мы знаем его айдишники и адвертайз
В ответ надо дать raft_id, но перед этим предстоит много работы.
В ответе обязательно ответе должен быть replicaset_uuid (правильно сгенеренный / выбранный среди существующих), instance_uuid (ну так, за компанию), урлы других инстансов из его репликасета, чтобы он сразу мог забутстрапить репликацию. Всё это раздаётся из таблички с топологией, но сначала её надо туда положить, причем класть надо средствами рафта.
Будущий raft_id выбирается как box.space.raft_topology:max() + 1
Алгоритм выбора replciaset_uuid и instance_uuid мы пока не обсуждаем, пусть пока это будет чистый рандом.
Коммитить в рафт изменение топологии надо пачкой - в propose_conf_change надо упихать всю инфу об инстансе, иначе инфа может разъехаться.
Есть и ещё одна проблема. Тк комит в рафт - штука не быстрая, другие конкурентные запросы (от других инстансов) всё это время будут получать отлуп типа "conf change already in progress". Это может сильно замедлить сборку больших кластеров.
Чтобы ускорить процесс джойна, запросы на джойн должны вставать в очередь, и обрабатываться траншами.
Ни один клиент не уйдёт обиженным, получит свой raft_id, и сможет запуститься
By Yaroslav Dynnikov on 2022-03-15T07:13:38 (imported from GitLab)
Единственная причина, по которой нужен ребутстрап - это необходимость поднять iproto listen до того, как инстанс узнает свой репликасет. Но вместо того, чтоы запускать бокс.цфг в основном процессе, лучше сделать fork() и запустить его в дочернем. Так будет проще освободить ресурсы. Делать exec при этом совершенно не обязательно.
Единственная задача дочернего процесса - отвечать на iproto запросы raft_discover. Сам он никакую логику не крутит. Транзитивное замыкание собирает основной (родительский) процесс - делает нетбокс запросы, вот это всё. Когда родитель решает что всё, транзитивное замыкание собрано, отправляет дочернему sigint.
By Yaroslav Dynnikov on 2022-03-15T15:16:09 (imported from GitLab)
Приджойнившийся инстанс инициализирует рафт ноду с пустым списком воутеров. Но чтобы запросить хотя бы первый снапшот, ему нужен список урлов. Причем, желательно, полный (черт его знает кому отвечать надо будет). Но урлы лежат в рафт логе (и возможно могут меняться со временем), а это значит урлы надо добыть каким-то другим путём.
Выход есть. Вызов raft_join помимо прочего должен вернуть полную таблицу {raft_id -> uri}. А чтобы эта таблица не затерлась рафт логом, каждую запись снабдим индексом коммита в котором она появилась.
Когда будут применяться записи из рафт лога, они не ожны перетирать более свежие урлы таблички.
By Yaroslav Dynnikov on 2022-03-15T13:23:37 (imported from GitLab)
должен ли запрос join() ждать, пока confchange закоммитится в рафт?
Здесь нас ждут очередные курица и яйцо.
Чтобы confchange прошёл успешно, добавляющаяся нода должна подтвердить что она вообще жива. Добавляющейся ноде нужны спейсы, а спейсам нужны юиды. А юиды приходят в ответе на джойн. Следовательно лидер отвечает "ок" не дожидаясь коммита.
С другой стороны, если не дождаться коммита, то лидер может сдохнуть, и этот же raft_id получит какая-то другая нода.
Решение проблемы следующее: обработка запроса join происходит в два этапа. Сначала в рафт комитится энтря с айдишниками и урлом. Потом идёт propose_conf_change. И не дожидаясь его коммита можно отвечать join ok.
By Yaroslav Dynnikov on 2022-03-15T15:05:52 (imported from GitLab)