Перейти к содержанию

Жизненный цикл инстанса

В контексте операционных систем каждый инстанс соответствует одному процессу. Данный раздел высокоуровнево описывает основные этапы инициализации инстанса. Они изображены на следующей схеме.

main.rs

Вся логика, начиная с присоединения (joining) к кластеру, и заканчивая обслуживанием клиентских запросов, происходит в едином процессе в функции fn main(). Голубым цветом обозначен жизненный цикл процесса без перезапуска, сиреневым — с учетом перезапуска (rebootstrap).

Ниже описаны детали выполнения каждого этапа и соответствующей программной функции.

rebootstrap

В процессе инициализации инстансу Picodata может потребоваться совершить так называемый rebootstrap, при котором данные базы данных стираются с диска и процесс перезапускается. Это используется для того чтобы корректно учесть две особенности Tarantool:

  1. Принадлежность инстанса тому или иному репликасету определяется в момент первого вызова box.cfg(), когда создается первый снапшот. Впоследствии изменить принадлежность репликасету невозможно.
  2. Инициализация сервера 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