Skip to content
Snippets Groups Projects
Commit ff62d393 authored by Yaroslav Dynnikov's avatar Yaroslav Dynnikov
Browse files

instance_lifecycle.md: major edits

parent c45c7502
No related branches found
No related tags found
1 merge request!199Replace clustering.md with instance_lifecycle.md
Pipeline #28088 passed
# Жизненный цикл инстанса
В контексте операционных систем каждый инстанс соответствует группе из
двух процессов — родительского (supervisor) и дочернего (именно он
выполняет tarantool runtime).
двух процессов. Данный раздел высокоуровнево описывает основные этапы
инициализации инстанса. Они изображены на следующей схеме.
![main.rs](../images/clustering.svg "main.rs control flow")
Красным показан родительский процесс, который запущен на всем протяжении
жизненного цикла инстанса. Вся логика, начиная с присоединения к
кластеру, и заканчивая обслуживанием клиентских запросов, происходит в
жизненного цикла инстанса. Вся логика, начиная с присоединения (joining)
к кластеру, и заканчивая обслуживанием клиентских запросов, происходит в
дочернем процессе (голубой цвет). Единственное предназначение
родительского процесса — иметь возможность сбросить состояние дочернего
(выполнить rebootstrap) и инициализировать его повторно (сиреневый
цвет).
и инициализировать его повторно (сиреневый цвет).
Данная схема наиболее полно отражает логику кода в файле `main.rs`. Ниже
описаны детали выполнения каждого этапа и соответствующей программной
функции.
Ниже описаны детали выполнения каждого этапа и соответствующей
программной функции.
### fn main()
### fn main() {: #fn_main }
На этом этапе происходит ветвление (форк) процесса `picodata`.
Родительский процесс (supervisor) ожидает от дочернего процесса
сообщения по механизму IPC и при необходимости перезапускает дочерний
процесс.
На этом этапе происходит ветвление (форк) процесса. Родительский
процесс, именуемый в дальнейшем "supervisor", ожидает от дочернего
процесса сообщения по механизму IPC и при необходимости перезапускает
его.
Выполнение дочернего процесса начинается с вызова функции
[`start_discover()`](#fn-start_discover) и далее следует алгоритму. При
[`start_discover()`](#fn_start_discover) и далее следует алгоритму. При
необходимости дочерний процесс может попросить родителя удалить все
файлы БД (см. раздел [Ребутстрап](#rebootstrap)). Это используется для
повторной инициализации инстанса с нормальным `replicaset_uuid` вместо
рандомного.
### Ребутстрап {: #rebootstrap }
В СУБД Tarantool имеются две особенности, из-за которых процесс инициализации
выглядит следующим образом:
файлы БД. Это используется для того чтобы корректно учесть две
особенности Tarantool:
1. Принадлежность инстанса тому или иному репликасету определяется в
момент первого вызова `box.cfg()`, когда создается первый снапшот.
......@@ -47,67 +41,73 @@
репликасету;
- принадлежность репликасету невозможно узнать без общения по сети.
Чтобы эту проблему решить, Picodata инициализируется со случайно
сгенерированными идентификаторами, а позже перезапускает процесс,
попутно очищая рабочую директорию.
Чтобы эту проблему решить, инициализация на этапе `start_discover()`
происходит со случайно сгенерированными идентификаторами, после чего
supervisor очищает рабочую директорию и перезапускает дочерний процесс с
функций `start_boot()` или `start_join()`.
### fn start_discover()
### fn start_discover() {: #fn_start_discover }
Дочерний процесс начинает свое существование с функции
[`init_common()`](#fn-init_common), в рамках которой в т.ч.
инициализируется модуль `box`. Возможно, что при этом из БД будет ясно,
что bootstrap данного инстанса уже был произведен ранее и что raft уже
знает о вхождении этого инстанса в кластер — в таком случае никакого
discovery не будет, инстанс сразу перейдет к этапу `postjoin()`. В
противном случае, если место инстанса в кластере еще не известно,
алгоритм discovery определяет значение флага `i_am_bootstrap_leader` и
адрес лидера raft-группы. Далее инстанс сбрасывает свое состояние (см.
[Ребутстрап](#rebootstrap)), чтобы повторно провести инициализацию
`box.cfg()`, теперь уже с известными параметрами. Сам лидер
(единственный с `i_am_bootstrap_leader == true`) выполняет функцию
`start_boot()`. Остальные инстансы переходят к функции `start_join()`.
### fn start_boot()
В функции `start_boot` происходит инициализация raft-группы — лидер
генерирует и сохраняет в БД первые записи в журнале. Эти записи
описывают добавление первого инстанса в пустую raft-группу и создание
начальной clusterwide-конфигурации. Таким образом достигается
однообразие кода, обрабатывающего эти записи.
[`init_common()`](#fn_init_common), в рамках которой в т.ч.
инициализируется движок базы данных `box.cfg()`.
Сам raft-узел на данном этапе еще не создается. Это произойдет позже, на
стадии `postjoin()`.
Возможно, что при этом из БД станет ясно, что данный инстанса уже был
добавлен в кластер — в таком случае этап discovery пропускается и
инстанс сразу переходит к этапу [`postjoin()`](#fn_postjoin).
### fn start_join()
В противном случае, если место инстанса в кластере еще не известно,
[алгоритм discovery](../architecture/discovery.md) позволяет найти адрес
лидера или же определить, что им должен стать данный инстанс
(`i_am_bootstrap_leader`).
Вызову функции `start_join()` всегда предшествует
[ребутстрап](#rebootstrap) (удаление всех данных и перезапуск процесса),
поэтому на данном этапе в БД нет ни модуля `box`, ни пространства
хранения. Функция `start_join()` имеет простое устройство:
В контексте инициализации кластера важно лишь то, что этот алгоритм
позволяет выполнить инициализацию не более чем одному инстансу. Если
таких инстансов было бы несколько, то и кластеров Picodata получилось бы
несколько.
Инстанс отправляет запрос `rpc::join` лидеру raft-группы (он
известен после discovery), который в ответе присылает всю необходимую
для инициализации информацию:
После discovery при отсутствии ошибок инстанс выполняет процедуру
"rebootstrap" — сбрасывает свое состояние, чтобы повторно провести
инициализацию `box.cfg()`, теперь уже с известными параметрами.
Bootstrap-лидер выполняет [`start_boot()`](#fn_start_boot).
Остальные инстансы переходят к [`start_join()`](#fn_start_join).
Для инициализации raft-узла:
### fn start_boot() {: #fn_start_boot }
- идентификатор `raft_id`;
- данные таблицы `_picodata_peer_address`.
В функции `start_boot()` происходит инициализация системных глобальных
таблиц Picodata — лидер генерирует и сохраняет в БД первые записи в
raft-журнале. Эти записи описывают добавление первого инстанса в пустую
raft-группу и создание начальной конфигурации кластера.
Сам raft-узел на данном этапе еще не создается. Это произойдет позже, на
стадии [`postjoin()`](#fn_postjoin).
Для первичного вызова `box.cfg()`:
- идентификаторы `instance_uuid`, `replicaset_uuid`,
### 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)), и затем создает в БД группу
`_picodata_peer_address` с актуальными адресами других инстансов. Без
этого инстанс не сможет отвечать на сообщения от других членов
raft-группы.
[`init_common()`](#fn_init_common)), и затем заполняет таблицу
`_pico_peer_address` актуальными адресами других инстансов. Без этого
инстанс не сможет отвечать на сообщения от других членов raft-группы.
По завершении этих манипуляций инстанс также переходит к этапу
`postjoin()`.
### fn postjoin()
### fn postjoin() {: #fn_postjoin }
Логика функции `postjoin()` одинакова для всех инстансов. К этому
моменту для инстанса уже инициализированы корректные пространства
......@@ -115,47 +115,52 @@ raft-группы.
Функция `postjoin()` выполняет следующие действия:
- инициализирует HTTP-сервер в соответствии с параметром `--http-listen`.
- инициализирует HTTP-сервер в соответствии с параметром `--http-listen`;
- запускает Lua-скрипт, указанный в аргументе `--script`;
- инициализирует узел Raft, который начинает взаимодействовать с
raft-группой;
- в случае, если других кандидатов нет, инстанс тут же
избирает себя лидером группы;
- устанавливает триггер `on_shutdown`, который обеспечит
[корректное завершение работы инстанса](#graceful-shutdown).
другими инстансами кластера;
- в случае, если инстанс в кластере всего один, он тут же избирает себя
лидером группы;
- устанавливает триггер `on_shutdown`, который обеспечит корректное
завершение работы инстанса.
Последним шагом инстанс оповещает кластер о том, что он готов проходить
настройку необходимых подсистем (репликации, шардинга, и т.д.). Для
этого лидеру отправляется запрос на обновление `target_grade` текущего
инстанса до уровня `Online`, после чего за дальнейшие действия будет
отвечать специальный поток управления [topology governor](#topology-governor).
отвечать специальный поток управления [topology
governor](../overview/glossary.md#governor).
Как только запись с обновленным грейдом будет зафиксирована в Raft, узел
готов к использованию.
Как только запись с обновленным `target_grade` будет применена, функция
`postjoin` завершается. Сам процесс при этом остается запущен и
продолжает исполнять файберы и обслуживать сетевые запросы.
### fn init_common()
### fn init_common() {: #fn_init_common }
Функция `init_common` обобщает действия, необходимые для инициализации
инстанса во всех трех вышеописанных сценариях — `start_discover`,
`start_boot`, `start_join`.
инстанса во всех трех вышеописанных сценариях —
[`start_discover()`](#fn_start_discover),
[`start_boot()`](#fn_start_boot), [`start_join()`](#fn_start_join).
Инициализация инстанса подразумевает следующие шаги:
- создание `data_dir`;
- создание директории с данными инстанса (из аргумента `picodata run
--data-dir`);
- первичный вызов `box.cfg`;
- инициализация `package.preload.vshard`;
- инициализация журнала событий безопасности;
- инициализация Lua модулей `vshard` и `http`;
- инициализация распределенного SQL;
- инициализация хранимых процедур (`box.schema.func.create`);
- создание системных таблиц (`_picodata_raft_log` и т.д).
- создание локальных спейсов `_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 `rpc::join` response |
| uuids | _random_ | _given_ | from `rpc::join` response |
| replication | None | None | from `rpc::join` response |
| data_dir | _from args_ | ... | ... |
| log_level | _from args_ | ... | ... |
| 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_ |
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment