Для координации многошаговой работы между сервисами BloodGPT мы используем инструмент уровня workflow engine, конкретно inngest — не message broker (сырой транспорт) и не job queue (fire-and-forget «выполни метод»). Это и каркас pipeline’ов (recognize → … → interpret → enrichments → PDF), и целевой substrate для межсервисной координации / очередей / retry / concurrency (вместо ad-hoc Python-сервисов с самодельными Redis-очередями — см. no-self-rolled-queues, loinc-unification-direction). Legacy .NET-бэкенд работал на уровне job queue (Hangfire-style background jobs); новый стек поднялся на уровень workflow engine. Внутри этого уровня: Temporal — избыточен для нашего профиля; Vercel WDK — beta, не адоптирован; job queues (Hangfire / BullMQ / Celery) — это уровень ниже, не наш.

Контекст

Вопрос — на каком уровне инструмента координировать многошаговую работу между сервисами: pipeline’ы recognize → normalize → LOINC → ranges → save-FHIR → interpret → enrichments → PDF, плюс cross-service вызовы / очереди / retry, которые сейчас в Python-сервисах сделаны руками (Async API + Redis BZPOPMIN + worker pod + job polling). Для этого есть три уровня инструментов — их легко спутать, а разница принципиальная.

Три уровня инструментов для межсервисной координации

УровеньПримеры«Что делает»
Message brokerRabbitMQ, Kafka, SQS, NATS«доставь сообщение». Сырой транспорт. Не знает про jobs / retry / scheduling.
Job queueHangfire (.NET), Sidekiq (Ruby), Celery (Python), BullMQ (Node)«выполни метод надёжно». Сериализует вызов (тип+метод+аргументы) → хранилище → worker. Fire-and-forget.
Workflow engineTemporal, Inngest, Vercel WDK, Step Functions, Restate«смоделируй и управляй бизнес-процессом как кодом» — оркестрация, long-running, реакция на события, queryable state.

Мы выбираем верхний уровень — workflow engine (broker и job queue для нашей задачи недостаточны: первый — только транспорт, второй — только надёжное выполнение метода без оркестрации, ожидания, состояния), а внутри него — Inngest.

Workflow engine — принципиально другая модель, чем «job queue, которая видит шаги»:

  • Оркестрация — if/else, циклы, параллельные ветки, sub-workflows записываются как обычный код.
  • Long-running state — workflow живёт дни/недели; ждёт approval, таймер, событие. Job выполнился и исчез.
  • Реакция на событияwaitForEvent("user.approved"); workflow спит и просыпается от сигнала. Job ждать не умеет.
  • Запрашиваемое состояние — «где сейчас этот заказ / анализ?» в любой момент. С job queue это строишь сам.
  • Компенсация (saga) — шаг 5 упал → откатить 1–4. Job queue не знает про связи между jobs.

Суть workflow engine — «виртуализация исполнения»: пишешь код как обычную программу (последовательные вызовы, if/else, циклы), а движок прячет за этим распределённую систему — шаги на разных машинах, переживают crash’и, переживают деплой, ждут неделями. Temporal так это и называет — «Durable Execution virtualizes execution…». Сам термин «durable execution» — маркетинговый (2019, Temporal); технику строили те же люди (Fateev + Abbas) с 2002: Amazon internal → SWF (2009) → Microsoft Durable Task Framework (2014) → Uber Cadence (2015) → Temporal (2019); Inngest/Restate/Vercel подхватили термин позже. Ключевое: workflow engine ≠ улучшенная очередь — job queue работает на уровне сообщений, workflow engine на уровне процессов.

Наш профиль: data pipeline, не side-effect process

Два типа процессов:

  • Side-effect process — шаги меняют внешний мир (списывают деньги, бронируют, создают записи в чужих системах). Поздний шаг упал → ранние надо откатить → нужна saga. Пример: booking (reserve hotel → book flight → charge card → FAIL → cancel flight → cancel hotel).
  • Data pipeline — шаги трансформируют данные, не меняя внешний мир; результат шага = input следующего. Шаг упал → просто retry, предыдущие результаты валидны, откатывать нечего. Пример: AI-анализ (upload → recognize → validate → interpret → FAIL → just retry interpret).

BloodGPT-пайплайн — data pipeline. recognize → normalize → LOINC → ranges → save-FHIR → interpret → enrichments → PDF — это трансформация данных. FHIR-запись (Healthcare API) — наш собственный store, не «чужая система, которую надо откатывать»; единственный реальный внешний side-effect — webhook клиенту, и он в самом конце. Что нам поэтому нужно:

нужно нам?
Crash recoveryДа — не перезапускать дорогие LLM-вызовы при крэше/деплое
HITL / long-runningДа — doctor review (approve/edit/reject до доставки пациенту)
Queryable stateПолезно — «где сейчас анализ X?» (UI / monitoring / DQD)
Compensation / sagaНет — data pipeline, откатывать нечего

Это сразу обесценивает главное, чем Temporal отделяется от остальных (нативные saga + queryable-state-из-кода): saga нам не нужна, а queryable-state можно собрать из FHIR-ресурсов (что записано) + Inngest UI (где выполнение).

Рассматривали

Inngest — HTTP re-invocation + memoization (выбран)

inngest: workflow engine, который создаёт «иллюзию единого процесса». Crash recovery — через re-invoke + memoization: при крэше функция вызывается заново, step.run("id", fn) уже выполненных шагов возвращают результат из state store (не выполняются повторно). Две модели подключения: serve() (push-HTTP, serverless) и connect() (pull-WebSocket — для long-running K8s/ECS-сервисов, без публичного URL, latency как у Temporal). Деплой: single binary (SQLite по умолчанию / PostgreSQL+Redis для prod) или Cloud (managed). Flow control сильнее, чем у Temporal — built-in throttle / concurrency (per-function, keyed) / priority. HITL — waitForEvent (до 7 дней, matching по полям). Минусы: saga нет (руками через try/catch — нам не нужно); queryable-state — только Dashboard + SQL Insights, нет программного API; self-hosted в beta.

Temporal — event sourcing + replay (оверкилл для нас)

Workflow engine эталонного уровня: crash recovery / HITL / queryable-state / saga — все четыре production-proven. Worker (ваш long-running процесс) poll’ит Temporal Server (Temporal не вызывает ваш код); при крэше — replay из Event History (activities не пере-вызываются, SDK подставляет результаты). Multi-language (Go/Java/Python/TS/.NET). Цена — инфраструктура: 4 независимо масштабируемых сервиса (Frontend / History / Matching / Worker) + Cassandra/PostgreSQL/MySQL, History Shards. Нативный saga.addCompensation() с авто-rollback в обратном порядке и QueryWorkflow() из кода — это то, что реально отделяет Temporal; для data pipeline без side-effects обе вещи не нужны. Flow control (throttle/priority) — слабее, чем у Inngest. Вывод: переплата за надёжность side-effect-процессов, которых у нас нет.

Vercel WDK — compiler + sandboxed VM + Worlds (beta, не адоптирован)

SWC-plugin трансформирует один файл в три output’а (Client / Step / Workflow mode); workflow-код бежит в sandboxed VM (нет прямого Node.js, нет Date()/Math.random(), sleep() вместо setTimeout, всё I/O через steps); replay — event sourcing, как у Temporal, но без отдельного сервера (всё в вашем приложении + «World» для persistence — Local/Vercel/self-host/custom). DX лучший (time-travel debugging, директивы "use step", портируемость через Worlds). Но beta: crash recovery работает, HITL/long-running сырое (есть баги), queryable-state — только Workbench для dev, нет concurrency. TypeScript-only (наш стек — плюс), но незрелость перевешивает.

Job queues (Hangfire / BullMQ / Celery / Sidekiq) — другой класс, не наш

«Выполни метод надёжно», fire-and-forget. Нет long-running state, нет оркестрации (только цепочки ContinueWith), нет реакции на события, нет компенсации, нет shared state между jobs. Legacy .NET-бэкенд BloodGPT был на этом уровне (background jobs + ручной retry/timeout — отсюда же и таймаут+перезапуск для recognition-зацикливаний, см. gemini-doom-loop); Python normalization/LOINC-сервисы свернули свой Redis-job-queue (Async API + BZPOPMIN + worker pod + job polling) — это всё job-queue-tier механизмы, миграционные цели на Inngest (см. no-self-rolled-queues, loinc-unification-direction).

Сводка

TemporalInngestVercel WDKHangfire (job queue)
Категорияworkflow engineworkflow engineworkflow enginejob queue
Deployment4 сервиса + DB (Cassandra/PG)single binary (SQLite/PG+Redis) или Cloudbuild plugin + WorldNuGet + SQL Server
Процесс / вызовlong-running worker, poll’ит серверHTTP endpoint (push) или WS (pull, connect)sandboxed VM, встроен в appbackground thread, poll’ит БД
При crashreplay из Event Historyre-invoke + memoizereplay из eventsretry job с начала
Crash recovery+++ proven+++ proven+ работает (beta)— (нет checkpoint’ов)
HITL / long-running+++ Signal+wait, без лимита++ waitForEvent, до 7 дней+ sleep()+webhooks, beta+баги
Queryable state+++ из кода + REST + UI+ Dashboard + SQL Insights, нет API− только dev Workbench
Saga / compensation+++ нативно, авто-rollback− руками (нам не нужно)− руками
Flow control (throttle/concurrency/priority)base (task-queue limits)built-in, сильнее всехнетqueue-level
ЯзыкиGo/Java/Python/TS/.NETTS/JS/Python/GoTS/JSC#

Выбрали

Уровень — workflow engine (а не message broker / job queue); движок — Inngest. Каркас pipeline’ов BloodGPT и целевой substrate для межсервисной координации / очередей / retry / concurrency (вместо ad-hoc Python-сервисов с самодельными Redis-очередями). Self-hosted локально (через Tilt), Cloud (платный план) в проде; модель подключения воркеров — вероятно connect() (K8s, long-running), TBD verify.

Почему

  • Нужен именно workflow-engine уровень. Нам нужны оркестрация (последовательность шагов как код, if/else, параллельные ветки), long-running (doctor review — ждать approval дни), реакция на события, queryable state («где анализ X?»), crash-recovery. Message broker даёт только транспорт; job queue — только «выполни метод надёжно», без оркестрации / ожидания / состояния / связей между jobs. Дальше — почему Inngest внутри этого уровня.
  • Мы data pipeline → saga не нужна, а это главное преимущество Temporal; значит его тяжёлая инфра (4 сервиса + Cassandra/PG, History Shards) — переплата.
  • TS-native — наш стек на TypeScript; Temporal-овский multi-language — фича, которой мы не пользуемся; WDK тоже TS, но beta с багами.
  • Минимальная инфра — single binary, SQLite локально / PostgreSQL+Redis в проде (или вообще Cloud) vs Temporal-кластер.
  • Лучший flow control — built-in throttle / concurrency (keyed) / priority; критично для per-tenant rate limiting и noisy-neighbor protection (concurrency: { key: "event.data.organizationId", limit: N } — см. inngest § Multi-tenant паттерн).
  • connect() — pull через persistent WebSocket для long-running сервисов в K8s (наши воркеры): не нужен публичный URL, latency как у Temporal, при этом простой API Inngest.
  • «Иллюзия единого процесса» + crash recovery (re-invoke + memoize) — закрывает главную потребность data pipeline: не пере-запускать дорогие LLM-вызовы при крэше/деплое.
  • HITL через waitForEvent (до 7 дней, matching по полям) — хватает на цикл doctor review.

Следствия

  • Оркестрация / очереди / retry / fan-out — через Inngest, не самодельные in-app механизмы (worker-pool, ручной retry-loop, рекурсивная concurrency): см. no-self-rolled-queues. Legacy .NET background-jobs и Python Redis-job-queues — это job-queue-tier, миграционные цели.
  • Data pipeline ⇒ нет compensation-кода: если шаг упал — просто retry, предыдущие результаты валидны. Не пытаться «откатывать» FHIR-записи как side-effects (FHIR — наш store, не чужая система).
  • Как именно компоновать наш пайплайн на Inngest (один большой оркестратор vs event-choreography vs гибрид) — отдельный открытый вопрос: inngest-pipeline-orchestration-vs-choreography.
  • Большие данные между шагами — через FHIR-store, не через step output (лимиты Inngest: step output 4 MB, run state 32 MB; и медданные не должны оседать в логах/трейсах оркестратора) — см. inngest § Передача данных между шагами, phi-in-fhir-not-sql.
  • Queryable state «где анализ X?» собирается из FHIR-ресурсов (что записано) + Inngest UI (где выполнение) — программного API статуса у Inngest нет, своего «pipeline-run»-объекта с прогрессом мы пока не делаем (см. open в inngest-pipeline-orchestration-vs-choreography).
  • Inngest Cloud — платный план (тариф TBD verify); локально — self-hosted (single binary через Tilt). Переход на self-hosted в проде — не зафиксирован (self-hosted beta).
  • Checkpointing (Inngest dev preview, Dec 2025) — снизит latency длинных step.run-цепочек (~−50% inter-step), делает большой оркестратор дешевле; следить.

Открытые вопросы

  • Inngest Cloud vs self-hosted в проде — сейчас Cloud; можно ли / стоит ли перейти на self-hosted (single binary beta, нужен HA-setup) — не решено.
  • connect() vs serve() — какую модель реально используют наши воркеры (analysis-worker, fhir-services) — вероятно connect(), TBD verify.
  • Если появятся side-effect-процессы (например, что-то с внешними платёжными / insurance API, где нужен откат) — Inngest saga не даёт; тогда либо отдельный движок (Temporal-уровень) для тех процессов, либо saga руками. Пока не актуально — фиксируем как «если понадобится».

Связано

  • inngest — vendor-страница: как устроен Inngest и как мы его используем (function/step, step.run/invoke/sendEvent, limits, deployment, pricing, roadmap)
  • no-self-rolled-queues — оркестрация / очереди / concurrency / retry — через Inngest, не самодельные in-app механизмы (job-queue-tier механизмы внутри/вместо workflow engine — анти-паттерн)
  • inngest-pipeline-orchestration-vs-choreography — как именно компоновать наш пайплайн на Inngest (status: draft)
  • recognition-enrichment-hourglass — наш пайплайн как data pipeline (песочные часы: unstructured → recognition → FHIR R4 → enrichment → unstructured)
  • loinc-unification-direction — Python normalization/LOINC-сервисы → Inngest functions (job-queue-tier → workflow engine)
  • llm-proxy-choice — аналог: model-routing/fallback вынесен на инфраструктурный слой (Bifrost), как оркестрация — на Inngest
  • gemini-doom-loop — legacy .NET уже делал timeout+restart для recognition-зацикливаний (job-queue-tier механизм)
  • health-report-vocabulary — словарь стадий пайплайна несогласован (смежная боль)

Источники

Источники: 1 2 3 4 5.

Сноски

  1. [Temporal: The definitive guide to Durable Execution](, accessed 2026-05-17, https://temporal.io/blog/what-is-durable-execution — · SE Radio 596: Maxim Fateev on Durable Execution · Temporal Server Architecture.

  2. [How Inngest Functions Are Executed](, accessed 2026-05-17, https://www.inngest.com/docs/learn/how-functions-are-executed — · Inngest Steps · Inngest Connect (WebSocket) · Inngest Self-Hosting · Principles of Durable Execution (Inngest).

  3. [Vercel Workflow DevKit Blog](, accessed 2026-05-17, https://vercel.com/blog/introducing-workflow — · Vercel WDK Docs · Vercel WDK GitHub.

  4. [Hangfire Documentation](, accessed 2026-05-17, https://docs.hangfire.io/ — · Hangfire Batches.

  5. [The Emerging Landscape of Durable Computing (Golem)](, accessed 2026-05-17, https://www.golem.cloud/post/the-emerging-landscape-of-durable-computing — · The Rise of the Durable Execution Engine.