В какой момент узел консенсусного кластера вправе применить запись журнала к состоянию?
Узел вправе применить запись к состоянию только после того, как консensusный протокол признал её зафиксированной, то есть гарантировал сохранность и одинаковый порядок этой записи на необходимом наборе узлов. Самой локальной записи в журнал или даже подтверждения клиенту недостаточно.
В протоколах реплицируемого журнала, таких как Raft, узел применяет записи последовательно до значения commit index. Это не позволяет состояниям узлов разойтись из-за применения записи, которая позже окажется отменённой.
Репликация журнала возникла как способ переживать отказ узлов, сохраняя единое логическое состояние сервиса. Однако простое копирование записей не решает проблему: разные узлы могут получить разные префиксы журнала или временно выбрать несовместимые записи.
Консенсусные протоколы отделяют добавление записи в локальный журнал от её фиксации. Такой подход позволяет сначала согласовать порядок операций, а затем безопасно применить только тот общий префикс, который уже нельзя заменить другой записью.
Запись может быть сохранена на одном узле, но потеряна при его отказе. Она также может находиться на нескольких узлах, однако ещё не быть частью согласованного общего журнала: например, лидер может потерять полномочия до того, как запись станет зафиксированной.
Если применить такую запись сразу, состояние узла изменится необратимо. После выбора нового лидера часть узлов может применить другую запись в той же позиции, что приведёт к расхождению данных и различиям в результатах запросов.
В типичном протоколе узел сначала добавляет запись в свой журнал и сохраняет её устойчиво. Лидер передаёт запись последователям; когда протокол получает подтверждение от требуемого большинства, соответствующая позиция может стать зафиксированной с учётом правил конкретного протокола.
После фиксации лидер сообщает узлам новый commit index. Каждый узел применяет к конечному автомату все записи от последней применённой позиции до commit index строго по порядку, не пропуская промежуточные записи.
В Raft есть важная деталь: лидер безопасно продвигает commit index по большинству прежде всего для записи текущего терма. После фиксации такой записи все предыдущие записи в журнале также становятся частью зафиксированного префикса, поскольку применяются последовательно.
Запись в журнале и применение к состоянию — разные этапы. Запись может быть уже устойчиво сохранена, но ещё не видна прикладному состоянию; это увеличивает задержку видимости, зато предотвращает применение потенциально отменённой операции.
Применение не означает автоматического завершения внешнего побочного эффекта. Отправка письма, списание во внешней платёжной системе или публикация события должны быть защищены отдельными механизмами: например, идемпотентностью, таблицей выполненных операций или транзакционным исходящим журналом.
Цена подхода — задержка до фиксации и зависимость записи от доступности кворума. При сетевом разделении меньшая изолированная группа не может безопасно продвигать commit index, хотя отдельные узлы могут продолжать принимать записи в локальный журнал.
В кластере обработки заказов лидер записал операцию подтверждения заказа локально и немедленно применил её: заказ стал подтверждённым, а клиент получил успешный ответ. Затем лидер отказал до репликации записи; новый лидер не содержит этой операции и считает заказ неподтверждённым.
Рассматривались три варианта. Немедленное применение минимизировало задержку, но нарушало согласованность; ожидание записи на всех узлах делало систему слишком хрупкой к отказу одной реплики; ожидание кворума сохраняло доступность при допустимом числе отказов, но увеличивало задержку.
Был выбран третий вариант: операция применялась только после фиксации большинством, а ответ клиенту выдавался после этого. Для внешней отправки уведомления использовался отдельный надёжный механизм, чтобы повторное применение после сбоя не создавало дубликат.
В результате новый лидер мог безопасно восстановить состояние из зафиксированного журнала. Незафиксированные записи удалялись или заменялись согласно протоколу и никогда не попадали в прикладное состояние.
Нет. Локальная запись означает лишь, что данный узел её сохранил; после его отказа она может исчезнуть или быть вытеснена другим согласованным журналом. Применение допускается только после фиксации протоколом, а устойчивое сохранение обычно требуется ещё до подтверждения записи клиенту.
Для протоколов вроде Raft важны не только число реплик, но и терм, лидерство и правила фиксации записей предыдущих термов. Запись старого терма, оказавшаяся на большинстве узлов, не всегда должна немедленно считаться зафиксированной в общем случае; фиксация новой записи текущего терма позволяет безопасно зафиксировать и предшествующий префикс.
Кроме того, понятие большинства зависит от конфигурации кластера. При изменении состава узлов используются специальные правила перехода, чтобы не возникли два пересекающихся кворума, считающих разные журналы допустимыми.
Нет. Оно гарантирует безопасность журнала и одинаковое применение зафиксированного порядка, но само по себе не доказывает, что чтение увидит самую свежую завершённую запись.
Для линейризуемого чтения узел должен дополнительно убедиться, что он обладает актуальным лидерством и знает текущий commit index, либо выполнить протокольный обмен, подтверждающий это. Чтение с отставшей реплики может вернуть старое состояние, даже если реплика никогда не применяет незафиксированные записи.