Жизненный цикл инстанса¶
В контексте операционных систем каждый инстанс соответствует одному процессу. Данный раздел высокоуровнево описывает основные этапы инициализации инстанса. Они изображены на следующей схеме.
Вся логика, начиная с присоединения (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 --instance-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 |
| instance_dir | from args | from args | from args |
| log_level | from args | from args | from args |