Жизненный цикл инстанса¶
В контексте операционных систем каждый инстанс соответствует одному процессу. Данный раздел высокоуровнево описывает основные этапы инициализации инстанса. Они изображены на следующей схеме.
Вся логика, начиная с присоединения (joining) к кластеру, и заканчивая
обслуживанием клиентских запросов, происходит в едином процессе в
функции fn main()
. Голубым
цветом обозначен жизненный цикл процесса без перезапуска, сиреневым — с
учетом перезапуска (rebootstrap).
Ниже описаны детали выполнения каждого этапа и соответствующей программной функции.
rebootstrap¶
В процессе инициализации инстансу Picodata может потребоваться совершить так называемый rebootstrap, при котором данные базы данных стираются с диска и процесс перезапускается. Это используется для того чтобы корректно учесть две особенности Tarantool:
- Принадлежность инстанса тому или иному репликасету определяется в
момент первого вызова
box.cfg()
, когда создается первый снапшот. Впоследствии изменить принадлежность репликасету невозможно. - Инициализация сервера Iproto, реализующего бинарный сетевой протокол
Tarantool, выполняется той же функцией
box.cfg()
.
В совокупности эти две особенности создают проблему "курицы и яйца":
- инстанс не может общаться по сети, пока не узнает принадлежность репликасету;
- принадлежность репликасету невозможно узнать без общения по сети.
Чтобы эту проблему решить, инициализация на этапе start_discover()
происходит со случайно сгенерированными идентификаторами инстанса,
после чего процесс операционной системы перезапускается при помощи
вызова execvp
. При перезапуске рабочая директория очищается и
исполняется функция start_boot()
или start_join()
.
При этом, информация о том, какую из функций нужно выполнять, передается
через pipe
. Для передачи файлового дескриптора, ассоциированного с ним,
используется скрытый аргумент командной строки --entrypoint-fd
.
fn start_discover()¶
Процесс начинает свое существование с функции
init_common()
, в рамках которой в т.ч.
инициализируется движок базы данных box.cfg()
.
Возможно, что при этом из БД станет ясно, что данный инстанса уже был
добавлен в кластер — в таком случае этап discovery пропускается, инстанс
регистрирует событие безопасности recover_local_db, после чего переходит к
этапу postjoin()
.
В противном случае, если место инстанса в кластере еще не известно,
алгоритм discovery позволяет найти адрес
лидера или же определить, что им должен стать данный инстанс
(i_am_bootstrap_leader
).
В контексте инициализации кластера важно лишь то, что этот алгоритм позволяет выполнить инициализацию не более чем одному инстансу. Если таких инстансов было бы несколько, то и кластеров Picodata получилось бы несколько.
После discovery при отсутствии ошибок инстанс выполняет процедуру
"rebootstrap" — перезапускается и сбрасывает свое состояние, чтобы
повторно провести инициализацию box.cfg()
, теперь уже с известными
параметрами. Bootstrap-лидер выполняет start_boot()
.
Остальные инстансы переходят к start_join()
.
fn start_boot()¶
В функции start_boot()
происходит инициализация системных глобальных
таблиц Picodata — лидер генерирует и сохраняет в БД первые записи в
raft-журнале. Эти записи описывают добавление первого инстанса в пустую
raft-группу и создание начальной конфигурации кластера.
Сам raft-узел на данном этапе еще не создается. Это произойдет позже, на
стадии postjoin()
.
fn start_join()¶
Вызову функции start_join()
всегда предшествует rebootstrap (удаление
всех данных и перезапуск процесса), поэтому на данном этапе в БД нет ни
модуля box
, ни пространства хранения. Функция start_join()
имеет
простое устройство:
Инстанс отправляет join-запрос лидеру raft-группы (он известен после discovery), который в ответе присылает всю необходимую для инициализации информацию:
- идентификатор
raft_id
; - данные системной таблицы
_pico_peer_address
; - идентификаторы
instance_uuid
,replicaset_uuid
; box.cfg.replication
— список адресов для репликации.
Получив все настройки, инстанс использует их в box.cfg()
(см.
init_common()
), и затем заполняет таблицу
_pico_peer_address
актуальными адресами других инстансов. Без этого
инстанс не сможет отвечать на сообщения от других членов raft-группы.
По завершении этих манипуляций инстанс регистрирует событие безопасности
connect_local_db и также переходит к этапу postjoin()
.
fn postjoin()¶
Логика функции postjoin()
одинакова для всех инстансов. К этому
моменту для инстанса уже инициализированы корректные пространства
хранения в БД и могут быть накоплены записи в журнале Raft.
Функция postjoin()
выполняет следующие действия:
- инициализирует HTTP-сервер в соответствии с параметром
--http-listen
; - запускает Lua-скрипт, указанный в аргументе
--script
; - инициализирует узел Raft, который начинает взаимодействовать с другими инстансами кластера;
- в случае, если инстанс в кластере всего один, он тут же избирает себя лидером группы;
- устанавливает триггер
on_shutdown
, который обеспечит корректное завершение работы инстанса.
Последним шагом инстанс оповещает кластер о том, что он готов проходить
настройку необходимых подсистем (репликации, шардинга, и т.д.). Для
этого лидеру отправляется запрос на обновление target_state
текущего
инстанса до уровня Online
, после чего за дальнейшие действия будет
отвечать специальный поток управления topology
governor.
Как только запись с обновленным target_state
будет применена, функция
postjoin
завершается. Сам процесс при этом остается запущен и
продолжает исполнять файберы и обслуживать сетевые запросы.
fn init_common()¶
Функция init_common
обобщает действия, необходимые для инициализации
инстанса во всех трех вышеописанных сценариях —
start_discover()
,
start_boot()
, start_join()
.
Инициализация инстанса подразумевает следующие шаги:
- создание директории с данными инстанса (из аргумента
picodata run --data-dir
); - первичный вызов
box.cfg
- инициализация журнала событий безопасности
- инициализация Lua-модулей
vshard
иhttp
- инициализация распределенного SQL
- инициализация хранимых процедур (
box.schema.func.create
) - создание локальных таблиц
_raft_log
и_raft_state
- создание системных таблиц
Параметры первичного вызова box.cfg
зависят от конкретного сценария:
param | start_discover |
start_boot |
start_join |
---|---|---|---|
listen | None | None | from args |
read_only | false | false | from join response |
uuids | random | given | from join response |
replication | None | None | from join response |
data_dir | from args | from args | from args |
log_level | from args | from args | from args |