Pregel Model

Вычислительная модель, на которой стоит LangGraph: выполнение разбито на дискретные супершаги, внутри шага узлы работают параллельно и изолированно, а общее состояние переходит в новую версию одним коммитом в конце шага. Заимствована из системы Google Pregel (2010) для обработки больших графов и из модели акторов.

Суть

LangGraph выглядит как «граф с узлами и рёбрами», но под этим лежит конкретная модель вычислений, а не просто удобная нотация. Она отвечает на вопрос, который в цепочках вообще не возникает: что происходит с общим состоянием, когда несколько узлов работают одновременно.

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

Зачем это нужно

Цепочка (chain) — это домино: узел передаёт результат следующему, состояние живёт в передаче. Пока путь линейный, всё в порядке. Как только появляется параллелизм, начинается гонка: два узла меняют одно поле, побеждает тот, кто записал последним, и результат зависит от того, кто первым вернул ответ из сети.

Модель супершагов убирает этот класс ошибок конструктивно. Внутри шага узлы физически не видят промежуточных результатов друг друга, поэтому влиять друг на друга не могут. Слияние происходит в одной точке и по явным правилам — это и есть LangGraph Reducers.

Второе следствие — восстановление. Раз состояние согласовано на границе супершага, эта граница становится естественной точкой сохранения: упавший процесс продолжает не с начала, а с шага падения (см. LangGraph Checkpointers).

Как работает

Супершаг. Дискретная итерация из трёх фаз, как их называет документация рантайма: Plan — определить, какие узлы активны на этом шаге (на первом шаге те, что подписаны на входные каналы; дальше — те, чьи каналы обновились на предыдущем шаге); Execution — выполнить их параллельно, пока все не завершатся, либо один не упадёт, либо не выйдет таймаут; Update — записать в каналы значения, которые узлы отдали. Обновления каналов не видны узлам до следующего шага. Следующий шаг видит уже согласованную картину.

Actor model. Каждый узел — актор: своё изолированное состояние, общение только через сообщения, никакой общей изменяемой памяти. Разработчику с опытом C# или Java эта модель знакома напрямую — LangGraph не изобрёл её, а перенёс.

Где проходит граница атомарности. Атомарен коммит снапшота состояния, а не работа шага — и это не одно и то же.

Полный снапшот состояния пишется, когда супершаг завершился целиком: наполовину слитой версии состояния не существует, и следующий шаг никогда не увидит частичную картину. Для финансового или комплаенс-контура это ключевое свойство.

Но работа успевших узлов при этом не пропадает. LangGraph сохраняет результаты на уровне отдельного узла, а не только шага: как только узел внутри супершага завершился, его вывод пишется в таблицу checkpoint_writes записями, привязанными к ещё не закрытому чекпоинту. Это и есть механизм pending writes. Если другой узел того же шага падает, выводы успешных соседей уже сохранены — и при возобновлении эти узлы не выполняются заново, переигрывается только упавший.

Формулировка «all-or-nothing: упал один — откатывается вся итерация» описывает модель точнее, чем происходит на деле, и ведёт к неверному выводу об эксплуатации. Она встречается в изложениях модели — но документация рантайма прямо говорит обратное: «if another node in the same super-step fails, the successful nodes' writes are already durable and don't need to be re-run on resume».

Практическое следствие: цена падения узла — это переигрывание одного узла, а не всего шага. Дорогие соседи по шагу — длинный вызов модели, тяжёлый поиск — второй раз не оплачиваются.

Когда именно всё это пишется. Момент сохранения задаётся режимом устойчивости на вызове (durability="sync" | "async" | "exit"). В режиме "exit" промежуточное состояние не сохраняется вовсе — записи происходят только на выходе из прогона, и тогда вся конструкция с pending writes для этого прогона не работает. См. Durable Execution.

Узел возвращает дельту, а не состояние. Он принимает словарь и отдаёт только то, что изменил. Полная изоляция вычислений: узел не может «случайно» затереть чужое поле, потому что он его не отдаёт.

Пример

Три супершага с ветвлением: на первом работают два узла, на втором три, на третьем один. Все читают и пишут общее состояние, но только на границах шагов.

Superstep t1        Superstep t2            Superstep t3
[Node A] [Node B]   [Node C][Node D][Node E]   [Node F]
    ↓   ↓               ↓     ↓     ↓             ↓
─────── Shared Global Memory State ────────────────────

Если на шаге t2 падает Node D — общее состояние остаётся таким, каким оно было после t1: снапшот шага t2 не закрывается, и следующий шаг частичную картину не увидит. Но выводы C и E, которые успели завершиться, уже лежат в checkpoint_writes. При возобновлении переигрывается только D, а C и E берутся готовыми.

Откат состояния не откатывает внешний мир. Супершаг атомарен относительно состояния графа: если шаг не завершился, изменения состояния не применяются. Но вызов, который узел успел сделать наружу, уже произошёл — письмо отправлено, платёж проведён, запись создана. Никакой механизм графа этого не вернёт.

Отсюда требование, которое лежит на инструменте, а не на оркестраторе: любое действие с побочным эффектом должно быть идемпотентным — принимать ключ операции и при повторе с тем же ключом не выполнять действие заново, а возвращать прежний результат. Тогда повтор супершага после сбоя или отмотки безопасен (LangGraph Reliability, идемпотентность узлов).

Там, где идемпотентность недостижима, действие выносят за пределы автоматического повтора: либо за гейт подтверждения (Human in the Loop), либо в отдельный шаг, который никогда не переигрывается автоматически.

Ограничение применимости

Из той же модели следует, где LangGraph не нужен. Он силён там, где логика выразима явно: аудит, кредитный скоринг, комплаенс, любой регламент. На задачах без жёсткой логики — диалоговый ассистент «поговорить», где важна не траектория, а реплика, — граф превращается в бесконечность сценариев, и проект тонет в ветках. Это не недостаток реализации, а следствие того, что модель заточена под детерминизм.

Связано с

  • LangGraph — фреймворк, построенный на этой модели
  • LangGraph State — что именно живёт в общем состоянии между супершагами
  • LangGraph Reducers — правила слияния дельт на границе шага
  • LangGraph Nodes and Edges — как задаётся, какие узлы активны на следующем шаге
  • LangGraph Checkpointers — почему граница супершага годится как точка сохранения, и что такое pending writes
  • Durable Execution — режимы устойчивости: когда именно состояние попадает в хранилище