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

В контексте операционных систем каждый инстанс соответствует одному
[процессу](https://ru.wikipedia.org/wiki/Процесс_(информатика)).
Данный раздел высокоуровнево описывает основные этапы
инициализации инстанса. Они изображены на следующей схеме.

![main.rs](../images/clustering.svg "main.rs control flow")

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

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

### 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() {: #fn_start_discover }

Процесс начинает свое существование с функции
[`init_common()`](#fn_init_common), в рамках которой в т.ч.
инициализируется движок базы данных `box.cfg()`.

Возможно, что при этом из БД станет ясно, что данный инстанс уже был
добавлен в кластер — в таком случае этап discovery пропускается, инстанс
регистрирует событие безопасности [`recover_local_db`], после чего переходит к
этапу [`postjoin()`](#fn_postjoin).

В противном случае, если место инстанса в кластере еще не известно,
[алгоритм discovery](../architecture/discovery.md) позволяет найти адрес
лидера или же определить, что им должен стать данный инстанс
(`i_am_bootstrap_leader`).

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

После discovery при отсутствии ошибок инстанс выполняет процедуру
"rebootstrap" — перезапускается и сбрасывает свое состояние, чтобы
повторно провести инициализацию `box.cfg()`, теперь уже с известными
параметрами. Bootstrap-лидер выполняет [`start_boot()`](#fn_start_boot).
Остальные инстансы переходят к [`start_join()`](#fn_start_join).

[`recover_local_db`]: ../reference/audit_events.md#recover_local_db

### fn start_boot() {: #fn_start_boot }

В функции `start_boot()` происходит инициализация системных глобальных
таблиц Picodata — лидер генерирует и сохраняет в БД первые записи в
raft-журнале. Эти записи описывают добавление первого инстанса в пустую
raft-группу и создание начальной конфигурации кластера. Raft-журнал
хранится в ОЗУ узла, на котором запускается инстанс.

Сам raft-узел на данном этапе еще не создается. Это произойдет позже, на
стадии [`postjoin()`](#fn_postjoin).

### fn start_join() {: #fn_start_join }

Вызову функции `start_join()` всегда предшествует rebootstrap (удаление
всех данных и перезапуск процесса), поэтому на данном этапе в БД нет ни
модуля `box`, ни пространства хранения. Функция `start_join()` имеет
простое устройство:

Инстанс отправляет join-запрос лидеру raft-группы (он известен после
discovery), который в ответе присылает всю необходимую для инициализации
информацию:

- идентификатор `raft_id`;
- данные системной таблицы
  [`_pico_peer_address`](../architecture/system_tables.md#_pico_peer_address);
- идентификаторы `instance_uuid`, `replicaset_uuid`;
- `box.cfg.replication` — список [адресов](../overview/glossary.md#address) для репликации.

Получив все настройки, инстанс использует их в `box.cfg()` (см.
[`init_common()`](#fn_init_common)), и затем заполняет таблицу
`_pico_peer_address` актуальными адресами других инстансов. Без этого
инстанс не сможет отвечать на сообщения от других членов raft-группы.

По завершении этих манипуляций инстанс регистрирует событие безопасности
[`connect_local_db`] и также переходит к этапу `postjoin()`.

[`connect_local_db`]: ../reference/audit_events.md#connect_local_db

### fn postjoin() {: #fn_postjoin }

Логика функции `postjoin()` одинакова для всех инстансов. К этому
моменту для инстанса уже инициализированы корректные пространства
хранения в БД и могут быть накоплены записи в журнале Raft.

Функция `postjoin()` выполняет следующие действия:

- инициализирует HTTP-сервер в соответствии с параметром `--http-listen`;
- запускает Lua-скрипт, указанный в аргументе `--script`;
- инициализирует узел Raft, который начинает взаимодействовать с
  другими инстансами кластера;
- в случае, если инстанс в кластере всего один, он тут же избирает себя
  лидером группы;
- устанавливает триггер `on_shutdown`, который обеспечит корректное
  завершение работы инстанса.

Последним шагом инстанс оповещает кластер о том, что он готов проходить
настройку необходимых подсистем (репликации, шардинга, и т.д.). Для
этого лидеру отправляется запрос на обновление `target_state` текущего
инстанса до уровня `Online`, после чего за дальнейшие действия будет
отвечать специальный поток управления [topology
governor](../overview/glossary.md#governor).

Как только запись с обновленным `target_state` будет применена, функция
`postjoin` завершается. Сам процесс при этом остается запущен и
продолжает исполнять файберы и обслуживать сетевые запросы.

### fn init_common() {: #fn_init_common }

Функция `init_common` обобщает действия, необходимые для инициализации
инстанса во всех трех вышеописанных сценариях —
[`start_discover()`](#fn_start_discover),
[`start_boot()`](#fn_start_boot), [`start_join()`](#fn_start_join).

Инициализация инстанса подразумевает следующие шаги:

- создание директории с данными инстанса (из аргумента `picodata run
  --instance-dir`);
- первичный вызов `box.cfg`
- инициализация журнала событий безопасности
- инициализация Lua-модулей `vshard` и `http`
- инициализация распределенного SQL
- инициализация хранимых процедур (`box.schema.func.create`)
- создание локальных таблиц `_raft_log` и `_raft_state`
- создание [системных таблиц](../architecture/system_tables.md)

Параметры первичного вызова `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_          |
