# Управление топологией

## Общие сведения {: #intro }

Топологией кластера в Picodata называется совокупность конфигураций
инстансов и репликасетов, связей между ними, их состояний относительно
друг друга. Picodata хранит эту информацию в глобальных таблицах,
содержимое которых реплицируется на все узлы посредством алгоритма Raft.

Важной составляющей топологии является информация о **стейтах** всех
узлов. Данный термин является специфичным для Picodata. Он отражает не
состояние самого инстанса, а конфигурацию остальных участников кластера
по отношению к нему. Существуют две разновидности стейтов: текущий
(`current_state`) и целевой (`target_state`).

Управление топологией в Picodata происходит за счет последовательного
изменения стейтов: сначала меняется значения `target_state`,
затем это значение задается в `current_state`.

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

- запуск инстанса (`target_state = Online`)
- штатное выключение инстанса (`target_state = Offline`)
- аварийное переключение (`target_state = Offline`)
- удаление инстанса из кластера (`target_state = Expelled`)

Конфигурацией инстансов и обновлением `current_state` централизованно
управляет [raft-лидер](../overview/glossary.md#raft_leader). По мере
появления изменений `target_state`, он применяет изменения к остальным
инстансам кластера (к каждому в отдельности) и обновляет
`current_state`. Реализует эту логику компонент `governor`.

Изменить `current_state` может только лидер при поддержке кворума, что
гарантирует консистентность принятого решения (и поддерживает доверие к
системе в плане отказоустойчивости).

Все изменения стейтов в кластере регистрируются в [журнале
аудита](../reference/audit_events.md).

## Сценарии управления топологией {: #scenarios }

### Присоединение (joining) инстанса к кластеру {: #joining }

Добавление нового инстанса в работающий кластер происходит по следующему
сценарию:

- Администратор лично или средствами автоматизации запускает новый
  инстанс командой [`picodata run`]
- Инстанс определяет, что рабочие файлы инстанса в начале [жизненного
  цикла][instance_lifecycle] отсутствуют
- Инстанс выполняет алгоритм [discovery] — связывается с другими узлами
  из параметра [`picodata run --peer`] и определяет адрес текущего
  raft-лидера
- Инстанс отправляет raft-лидеру запрос [`.proc_raft_join`], содержащий
    - его сетевой адрес `iproto_advertise`
    - домен отказа `failure_domain`
    - [тир][tier] `tier`
    - версию инстанса `picodata_version`
    - также, опционально можно явно передать `instance_name`, `replicaset_name`
- raft-лидер, получив этот запрос, выполняет ряд проверок и транзакций в
  глобальные таблицы:
    - сравнивает глобальную версию кластера (из `_pico_property`) и
      версию инстанса, который отправил запрос на присоединение. Если
      версия инстанса Picodata несовместима, запрос отклоняется
      (допускается различие на 1 минорную версию, то есть, если кластер
      с версиями 25.1, то добавиться могут только инстансы версий 25.1 и
      25.2)
    - если переданного тира нет в таблице `_pico_tier`, запрос отклоняется
    - если требуется, выполняется запрос `INSERT ... INTO _pico_replicaset`
    - если `instance_name` передан явно и уже добавлен в кластер, проверяет
      возможность автоматически его удалить
    - генерирует `raft_id` для инстанса, никому другому ранее не
      принадлежавший
    - инициализирует `current/target_state = Offline`
    - `INSERT ... INTO _pico_peer_address`
    - `INSERT ... INTO _pico_instance`
- инстанс инициализирует локальную БД и актуализирует свое состояние
  относительно других реплик

!!! note "Примечание"
    Если кластер состоит из инстансов разных версий
    (например, различающихся в минорной части версии), то
    [DDL](../reference/sql/ddl.md) будет автоматически ограничен только
    DROP-операциями.

[`picodata run`]: ../reference/cli.md#run
[discovery]: ../architecture/discovery.md
[instance_lifecycle]: ../architecture/instance_lifecycle.md
[`picodata run --peer`]: ../reference/cli.md#run_peer
[`.proc_raft_join`]: ../architecture/rpc_api.md#proc_raft_join
[tier]: ../overview/glossary.md#tier

### Штатное выключение инстанса {: #graceful_shutdown }

Чтобы выключение инстанса прошло штатно и не имело негативных
последствий, Picodata обеспечивает соблюдение следующих условий:

- инстанс не должен оставаться голосующим, если можно его заменить
  другим
- инстанс не должен оставаться raft-лидером
<!-- - инстанс не должен оставаться лидером репликасета. -->

При срабатывании триггера `on_shutdown` инстанс сначала отправляет
лидеру запрос на изменение `target_state = Offline`. Завершение работы
происходит после того, как будет применено соответствующее изменение
`current_state`.

Логика изменения собственного `target_state` до `Offline` реализована в
алгоритме [`sentinel`](../overview/glossary.md#sentinel).

Максимальное время ожидания составляет 3 с и не настраивается. По
истечении времени инстанс в любом случае завершает свою работу.

### Аварийное переключение {: #failover }

За обслуживание отказов инстансов также отвечает raft-лидер и алгоритм
[`sentinel`](../overview/glossary.md#sentinel), который при обнаружении
отказа инициирует изменение `target_state = Offline`.

Критерием отказа является невозможность доставки raft-сообщений в течение
времени, заданного системным параметром [`governor_auto_offline_timeout`]
(по умолчанию, 30 секунд).

[`governor_auto_offline_timeout`]: ../reference/db_config.md#governor_auto_offline_timeout

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

## Governor — централизованное управление кластером {: #governor }

Большинство механизмов, связанных с отказоустойчивостью в Picodata, так
или иначе связаны с компонентом governor. С технической точки зрения
governor — это поток управления (fiber), всегда выполняющийся на
[raft-лидере](../overview/glossary.md#raft_leader). Губернатор действует
в два этапа. По данным из системных таблиц, которые отражают состояние
кластера, генерируется событие. Например, лидер некоторого репликасета
имеет `target_state` = `Offline`. Второй этап — реакция на событие, при
этом все необходимые действия предпринимает также губернатор. Например,
в случае недоступности лидера репликасета, необходимые вызовы RPC и
записи в глобальные таблицы будет делать непосредственно губернатор.

### Доступность инстансов {: #instance_reachability }

Инстанс называется _доступным_ или логически _доступным_, если его
целевой стейт (`target_state`) не равен `Offline` или `Expelled`.
Физическая доступность хоть и связана с этим понятием, но это не одно и
то же. Например, инстанс, адрес которого стал аргументом команды
[`picodata expel`](../reference/cli.md#expel), некоторое время будет
считаться логически недоступным, несмотря на его физическую доступность.

Доступный инстанс может участвовать в raft-голосовании и быть
назначенным в лидеры как репликасета, так и raft-группы. Соответственно,
_недоступный_ инстанс такую роль играть не может.

Доступность инстансов влияет на возможность изменять содержимое таблиц в
кластере. При плановом выключении одного из серверов кластера, или при
аварии в одном из датацентров инстансы могут уходить в стейт `Offline`.
В этом случае [DDL-операции](../reference/sql/ddl.md) в кластере
перестают работать, даже если реплики/мастера этих инстансов в соответствующих
репликасетах остались доступны. Возможность проводить кластерные
DDL-операции возобновится после того, как неактивные инстансы перейдут в
стейт `Online` или `Expel`. До этого момента DDL-операции будут
доступны только в локальном режиме (с параметром `WAIT APPLIED
LOCALLY`).

### Автоматическое назначение мастеров репликасетов {: #replicaset_master_switchover }

Picodata следит за тем, чтобы в каждом
[репликасете](../overview/glossary.md#replicaset) была ровно одна
мастер-реплика. Для этого для губернатора реализован алгоритм
автоматического переключения мастера, срабатывающий в том случае, если
мастер-реплика потеряла связь с кластером. Этот алгоритм, разбит на 2 шага, что увеличивает его надежность,
а также позволяет администратору БД использовать его для ручного
переключения мастера в репликасете.

Первым шагом губернатор ищет репликасет, мастер которого [недоступен](#instance_reachability).
Найдя такой репликасет, губернатор выбирает в нем нового кандидата на
роль мастера. На момент написания этих строк, единственный критерий, по
которому делается выбор, такой: инстанс должен быть [доступен](#instance_reachability).
Однако, в будущем могут быть добавлены дополнительные критерии.

В конце первого шага губернатор изменяет запись в глобальной таблице
[`_pico_replicaset`](./system_tables.md#_pico_replicaset) для
выбранного репликасета, где в поле `target_master_name` указывается
идентификатор инстанса, ставшего новым мастером.

Вторым шагом губернатор находит в таблице [`_pico_replicaset`](./system_tables.md#_pico_replicaset)
запись с несовпадающими полями `target_master_name` и `current_master_name`. Такая ситуация возможна либо после
выполнения первого шага алгоритма, либо после ручного назначения мастера администратором.

В случае, если текущий мастер репликасета [доступен](#instance_reachability) для связи, губернатор
отправляет ему [запрос](../architecture/rpc_api.md#proc_replication_demote) на перевод в режим "только для чтения", и получает
от него в ответ его текущее значение [vclock](../overview/glossary.md#vclock).
Дальше отправляется запрос новому мастеру на перевод его в активный режим.
Здесь, при наличии значения vclock от предыдущего мастера, оно используется для
синхронизации перед тем как инстанс станет доступен для записи, что
предотвращает потенциальную потерю данных.

Стоит заметить, что в случае потери связи с текущим мастером, синхронизация не
происходит, и потеря данных из реплицируемых асинхронно таблиц все таки может
произойти.

В конце этого шага губернатор обновляет запись в таблице [`_pico_replicaset`](./system_tables.md#_pico_replicaset),
актуализируя значения колонок `current_master_name` и `promotion_vclock`.

Стоит заметить, что второй шаг, если имеется возможность, выполняется в первую очередь.
Губернатор не начинает поиск нового кандидата в мастеры репликасета, пока есть хотя бы один
репликасет с целевым мастером, который отличается от текущего.

<!--
Стоит заметить, что хотя логически первый шаг выполняется перед вторым, в
программном коде они описаны в обратном порядке, так как губернатор не должен
начинать поиск нового кандидата в мастеры репликасета пока есть хотя бы один
репликасет с уже выбранным целевым мастером.
-->


### Автоматическое назначение весов шардирования {: #replicaset_weight_change }

Picodata следит за тем, чтобы [репликасеты](../overview/glossary.md#replicaset)
содержали количество реплик не ниже чем [фактор репликации](../overview/glossary.md#replication_factor)
соответствующего [тира][tier]. До тех пор пока это
условие не будет выполнено, данные на такой репликасет подаваться не будут.
Технически это реализовано при помощи так называемых _весов шардирования_, за
актуальностью которых следит губернатор.

Вес [шардирования](../overview/glossary.md#sharding) определяет то, какая пропорция
[бакетов](../overview/glossary.md#bucket) со всего кластера должна быть распределена
на данный репликасет по сравнению с остальными. Изначально все добавляемые
репликасеты в Picodata имеют нулевой вес, что означает, что данные на них
поступать не будут. И только после того, как в репликасете окажется достаточно
реплик, губернатор автоматически установит ему ненулевой вес, тем самым запустив
в работу подсистему [vshard](../overview/glossary.md#vshard), отвечающую за
перераспределение ьакетов.


### Первичное распределение бакетов {: #governor_vshard_bootstrap }

<!-- А вообще нужно ли это событие? Как будто само по себе оно не интересное,
а идея выше написана. -->
[Репликасеты](../overview/glossary.md#replicaset) в Picodata начинают свое
существование с нулевым весом и только в какой-то определенный момент жизненного
цикла кластера появляется первый репликасет с ненулевым весом, на
который можно распределять [бакеты](../overview/glossary.md#bucket). Для этого
у губернатора есть специальный шаг, который выполняется не больше одного раза за
жизнь кластера.


### Автоматическое изменение конфигурации vshard {: #governor_vshard_cfg }

Во время нормальной работы кластера Picodata возможно динамическое изменение
топологии, как штатное, так и не очень: инстансы и репликасеты могут добавляться,
связь между ними может пропадать. При этом Picodata поддерживает безотказный
доступ к данным через [распределенный SQL](./distributed_sql.md). Для этого
все инстансы должны знать информацию о конфигурации кластера, а также
своевременно обновлять состояние подсистемы [vshard](../overview/glossary.md#vshard).

Триггерами для изменения конфигурации vshard служат почти все изменения
топологии — например, добавление нового инстанса, или возвращение в строй инстанса,
потерявшего связь с кластером на какое-то время; а также изменения весов
шардирования ([см. параграф выше](#replicaset_weight_change)).
Как только губернатор обнаруживает такое изменение, он обновляет запись о
целевой конфигурации vshard в глобальной системной таблице, что обеспечивает
отказоустойчивость такого изменения: если raft-лидер выйдет из строя, на его
замену будет избран новый, который продолжит выполнять алгоритм губернатора
с того места, где остановился предыдущий.

Следующим шагом губернатор обнаруживает несовпадение текущей и целевой
конфигураций vshard и приступает к ее актуализации, отправляя [запрос](../architecture/rpc_api.md#proc_sharding) на все
инстансы кластера одновременно. Если хотя бы от одного инстанса не будет получен
положительный результат, этот шаг повторяется.

### Автоматическое переключение узлов, голосующих в raft {: #governor_raft_failover }

Governor следит за выполнение ограничений, описанных [здесь](raft_failover.md#raft_voter_failover).

### Применение изменений кластерной схемы данных {: #governor_schema_change }

Governor гарантирует отказоустойчивость изменений кластерной схемы данных.
Подробнее алгоритм описан [здесь](clusterwide_schema.md#two_phase_algorithm).

