Failover

Supervision

Для достижения отказоустойчивости Runtime применяет подход схожий с supervision (actors). Каждый узел являтся чьим-то "супервизором", в нашей терминологии - "родителем" (parent). Родительская нода поручает выполнение некой задачи (исполнение фрагмента) "исполнителю"/child-ноде. Любая нода одновременно может быть родителем и исполнителем для разных задач. Родитель следит за здоровьем узла-исполнителя, если тот умирает - перезапускает исполнение фрагмента на другом узле (поручает задачу другому узлу). Исполнитель не следит за здоровьем родителя. Если родтель оказывается недоступен в момент отправки ему результата, он просто отбрасывается.

Принцип работы покажем на простом примере:

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

В подобной ситуации корректность не теряется, т.к. есть возможность перезапускать фрагменты неограниченное количество раз. Такой подход открывает путь к спекулятивному retry, когда исполнитель может оптравить исполнение одной и той-же задачи на разные узлы, не дожидаясь отказа какого-то из них (жертвуем КПД кластера ради скорости). Каждая задача имеет уникальный в пределах всех кластеров идентификатор (transaction ID + стартовый DAM). Это позволяет принимать результаты выполнения задач от любых исполнителей, даже тех, которые не находятся в родственной связи с неким родителем (родителю просто плевать кто ее выполнил). В отдельных случаях это позволяет посылать результаты выполнения по любым доступным маршрутам:

  • Если был потерян прямой канал связи с родителем, есть вероятность, что существует "непрямой" канал связи.
  • Если родтель упал, родитель родителя перезапустил его задачу на другом узле, и этот другой узел уже отправил задачу на исполнение, то результат придет ему почти сразу.

При балансировке нагрузки, узлы, которых назначили исполнителями, могут быть слишком загружены на момент назначения, поэтому отдают задачу кому-то еще. При этом они уведомляют родителя, что более не являются исполнителем. Это уведомление может не дойти до родителя, и тогда у него будет устаревшая информация о том кто исполнитель. Родитель увидит недоступность этого "ненастоящего" исполнителя и попытается перезапустить задачу в другом месте - тут нас вновь выручает семантика системы, позволяющая перезапускать много и без ограничений и принимать результаты от кого угодно.

Transaction failover

У всех узлов есть родитель?

В vm4 нет - там для слежения за здоровьем родителей форимруется родительский комитет из мнимум 3 узлов. Они реализуют выбор лидера (raft-like) и лидер становится "корневым" родителем. В процессе работы лидер может упасть, произойдут перевыборы и транзакция перезапустится.

В vm5 немного другая схема - там родители есть у всех. AppServer перед отправкой клиенту подтверждения о старте задачи формирует искусственное кольцо из родителей неограниченного размера (минимум 2).

cpvm_parent_ring