В каждом достаточно сложном backend рано или поздно появляется код, который должен надёжно выполнить последовательность шагов: вызвать внешний API, дождаться ответа, обработать результат, при ошибке — повторить или компенсировать. Обычно это реализуется через очереди, cron-задачи, таблицы состояний и ручную логику повторов.
Temporal — это платформа для durable workflows. Она берёт на себя надёжность выполнения: если процесс упал на середине, Temporal восстановит его состояние через replay истории событий и продолжит с логически нужной точки — не с начала и не с последнего checkpoint. На уровне кода это выглядит так, будто выполнение продолжилось с той же строки.
Звучит как магия. На практике — это серьёзный инфраструктурный компонент со своей кривой обучения и операционной стоимостью. Эта статья разберёт, когда Temporal действительно оправдан, а когда проще обойтись без него.
В статье
- Проблема: почему самодельные workflow ломаются
- Что такое Temporal и как он работает
- Workflow и Activity: разделение логики и побочных эффектов
- Durability: как Temporal восстанавливает состояние
- Что ещё важно: task queues, signals, child workflows, тестирование
- Лунная база: координация аварийного протокола через workflow
- Temporal vs очереди, Temporal vs saga
- Ландшафт durable execution: аналоги и когда что
- Когда Temporal стоит внедрять
- Где Temporal избыточен
- Checklist: готовы ли вы к Temporal
Проблема: почему самодельные workflow ломаются
Типичная эволюция «просто задачи в очереди»:
- Простой воркер: взял задачу, обработал, удалил.
- Появились retry — при ошибке задача возвращается в очередь.
- Появились зависимые шаги — результат одной задачи нужен для следующей. Добавляется таблица состояний.
- Появились таймауты и дедлайны — задача должна выполниться за N минут, иначе эскалация.
- Появились компенсации — если шаг 3 упал, нужно откатить шаги 1 и 2.
- Появились длинные процессы — workflow может длиться дни или недели (согласование, ожидание внешнего события).
К пункту 4–5 команда обычно уже поддерживает самодельный state machine на таблице workflow_state с полями status, retry_count, last_error, step, data. Этот код хрупкий, плохо тестируется и тяжело меняется.
Что такое Temporal и как он работает
Temporal — это open-source платформа (исходники на GitHub, документация; официальные SDK для Go, TypeScript, Java, Python, .NET, PHP, Ruby). Архитектура:
- Temporal Server — хранит состояние workflow, управляет таймерами, ретраями и историей выполнения. Требует persistence-store для состояния (PostgreSQL, MySQL или Cassandra) и отдельный visibility-store для поиска и фильтрации workflow; в типовом self-hosted-деплое (в т.ч. в официальном docker-compose) это PostgreSQL + Elasticsearch.
- Worker — ваш код, подключается к серверу. Выполняет workflow-функции и activity-функции.
- Workflow — детерминированная функция, описывающая логику процесса. Не делает I/O напрямую.
- Activity — функция с побочными эффектами (HTTP-запрос, запись в БД, отправка уведомления). Вызывается из workflow.
Ключевой принцип: workflow-код выглядит как обычная последовательная программа, но Temporal гарантирует его durable execution.
Workflow и Activity: разделение логики и побочных эффектов
Workflow:
func ProvisionResource(ctx, request):
result = CheckAvailability(ctx, request.resource)
if not result.available:
return Error("resource unavailable")
reservation = ReserveResource(ctx, request)
Sleep(ctx, 24h) // ждём подтверждения сутки
confirmed = WaitForSignal(ctx, "confirmation")
if not confirmed:
CancelReservation(ctx, reservation)
return Error("not confirmed")
AllocateResource(ctx, reservation)
return Success()Код выше — схематичный псевдокод (синтаксис условный, 24h и вызовы упрощены). Но идея за ним реальна: в Temporal Go SDK workflow пишется как обычная функция с последовательным потоком управления — if/else, циклы, вызовы activity через workflow.ExecuteActivity, таймеры через workflow.Sleep, ожидание сигналов, — а не как самодельный state machine. Если worker перезапустится после workflow.Sleep(24h), Temporal восстановит выполнение с той же точки.
Activity — обычные функции с side effects. Temporal автоматически управляет retry, timeout и heartbeat для каждой activity.
Durability: как Temporal восстанавливает состояние
Temporal записывает в базу каждое событие выполнения workflow: старт, вызов activity, результат activity, таймер, сигнал. При перезапуске worker’а Temporal replays историю событий, и workflow-функция «проигрывается» до текущей точки без повторного вызова activity (результаты берутся из истории).
Ограничение: workflow-код должен быть детерминированным. Нельзя использовать time.Now(), rand() или прямой I/O в workflow. Для этого есть activity.
Из детерминизма вытекают две неочевидные эксплуатационные вещи, о которых стоит знать заранее:
- Версионирование workflow. Нельзя просто поменять код workflow, у которого есть запущенные экземпляры: при replay новой логики на старой истории Temporal обнаружит несоответствие (non-determinism error). Изменения вводятся через patching/versioning API — это отдельная дисциплина, которую легко недооценить.
- Continue-As-New. История событий не бесконечна (есть лимит на размер). Долгие и циклические workflow периодически «перезапускают» себя с чистой историей через
Continue-As-New, перенося только нужное состояние. Без этого очень длинный процесс упрётся в лимит истории.
Что ещё важно: task queues, signals, child workflows, тестирование
За «линейным кодом с await» стоит несколько понятий, без которых Temporal в проде не собрать:
- Task queues и маршрутизация воркеров. Workflow и activity привязаны к именованной task queue; воркеры слушают конкретные очереди. Это даёт роутинг (например, GPU-activity — на отдельный пул воркеров), изоляцию и горизонтальное масштабирование — воркеров можно добавлять независимо.
- Signals и Queries.
Signal— асинхронное внешнее событие, доставленное в бегущий workflow (в примере ниже — «подтверждение оператора»);Query— синхронное чтение текущего состояния workflow без изменения истории. Это и есть механизм «человек в цикле» и внешнего наблюдения. - Child workflows. Workflow может запускать дочерние — для декомпозиции и независимого версионирования крупных процессов.
- Activity: таймауты, ретраи, heartbeat. У каждой activity —
StartToClose/ScheduleToCloseтаймауты, политика ретраев (backoff, максимум попыток) и heartbeat для долгих задач (чтобы отличить «зависла» от «ещё работает»). Идемпотентность activity — на вашей стороне (ретрай может вызвать её повторно) — мостик к идемпотентности и outbox. - Тестирование. SDK дают test-framework с «промоткой времени»:
Sleep(24h)в тесте не ждёт сутки, а перематывается мгновенно; activity мокаются. Это выгодно отличает Temporal от самодельных state machine, которые тестировать больно (мостик к тестированию распределённых системготовится, с 10 сентября).
Workflow-код на реальном SDK (Go) — не псевдокод, а рабочая функция: workflow.ExecuteActivity(ctx, ...).Get(ctx, &res), workflow.Sleep(ctx, 24*time.Hour), workflow.GetSignalChannel(ctx, "confirmation"). Полный пример — в demo-стенде.
Лунная база: координация аварийного протокола через workflow
Аварийный протокол лунной базы — хороший пример для Temporal:
- Получить сигнал о разгерметизации.
- Оценить критичность (activity: запрос к подсистеме мониторинга).
- Если критично — запустить параллельно:
- зарезервировать аварийный кислород;
- переключить энергоснабжение;
- отправить робота.
- Дождаться подтверждения всех трёх шагов (с таймаутом 5 минут).
- Если таймаут — эскалация на оператора (activity: уведомление + ожидание сигнала).
- Если оператор подтвердил — продолжить. Если нет — компенсация.
На самодельной очереди это 200+ строк state machine. В Temporal — линейный код с await и parallel.
Temporal vs очереди, Temporal vs saga
| Очереди (RabbitMQ, Kafka) | Saga (самодельная) | Temporal | |
|---|---|---|---|
| Гарантия выполнения | retry, DLQ | ручной state machine | автоматическая durability |
| Длинные процессы | таймеры в коде | поле status в БД |
Sleep(), WaitForSignal() |
| Компенсации | ручная логика | ручная логика | try/catch в workflow |
| Параллелизм | отдельные задачи | сложная координация | параллельный fan-out/fan-in |
| Наблюдаемость | логи + метрики | query по таблице | Temporal UI, history API |
| Сложность | низкая | растёт экспоненциально | средняя начальная, стабильная |
Temporal не заменяет очереди задачготовится, с 17 сентября для простой обработки сообщений — для fire-and-forget джоб с ретраями и DLQ они проще. Он заменяет самодельные workflow-движки: то, что иначе пишется как saga-оркестратор на таблице состояний. Где саму механику «события без распределённой транзакции» проще держать на outbox — Temporal не нужен; где нужна долгая координация с компенсациями и человеком в цикле — нужен. Как это соотносится с остальными событийными паттернами — в матрице решенийготовится, с 8 августа.
Temporal не одинок в своей нише. Он вырос из Uber Cadence (тот по-прежнему развивается), а схожие идеи durable execution предлагают managed-решения — AWS Step Functions, Azure Durable Functions — и open-source Netflix Conductor и Restate. Выбор между ними — это в первую очередь выбор между «свой stateful-компонент с полным контролем» (Temporal/Cadence) и «managed-сервис без своей инфраструктуры, но с привязкой к облаку» (Step Functions, Durable Functions). Есть и managed-Temporal — Temporal Cloud, если свой сервер держать не хочется.
Ландшафт durable execution: аналоги и когда что
Temporal — не единственный способ получить durable execution, и выбор между аналогами — отдельное решение. Сравнить их проще по трём осям: как описывается workflow (обычный код vs конфиг/DSL), хостинг (свой сервер vs managed) и привязка к облаку.
| Продукт | Модель workflow | Хостинг | Lock-in | Языки | Ниша |
|---|---|---|---|---|---|
| Temporal | code-first (обычный код + await) |
self-hosted / Cloud | нет (OSS) | Go, Java, TS, Python, .NET, PHP, Ruby | дефолт «своего durable-движка» |
| Cadence | code-first | self-hosted | нет | Go, Java | предок Temporal (Uber); экосистема меньше |
| AWS Step Functions | конфиг (ASL — JSON-DSL) + визуал | managed (AWS) | сильный (AWS) | язык-агностично (через Lambda) | процессы внутри экосистемы AWS |
| Azure Durable Functions | code-first (orchestrator functions) | managed (Azure) | сильный (Azure) | C#, JS, Python, … | durable поверх Azure Functions |
| Netflix Conductor | JSON-DSL workflow | self-hosted (OSS) | нет | язык-агностично (task workers) | микросервисная оркестрация по DSL |
| Restate | code-first + durable RPC | self-hosted / Cloud | нет | TS, Java/Kotlin, Go, Python | новый, лёгкий рантайм |
Как выбирать:
- Свой durable-движок, много языков, полный контроль, зрелость → Temporal. Cadence — если уже на нём; догонять на новых проектах смысла мало (Temporal активнее).
- Уже глубоко в AWS, процессы простые/визуальные, не хочется своей инфраструктуры → Step Functions (но ASL — это конфиг, не код, и lock-in). Аналогично Azure → Durable Functions (там как раз code-first, но привязка к Azure Functions).
- Оркестрация микросервисов по декларативному DSL, а не «код как workflow» → Conductor.
- Хочется durable execution без тяжёлого сервера, с durable-RPC и лёгким рантаймом → присмотреться к Restate (моложе, но растёт).
- Общее правило: managed снимает операционную нагрузку ценой lock-in; self-hosted (Temporal/Cadence/Conductor/Restate) даёт контроль и переносимость ценой своего stateful-компонента (см. чеклист ниже).
Когда Temporal стоит внедрять
- Многошаговые процессы с ретраями, таймаутами и компенсациями;
- длинные workflow — часы, дни, недели (согласования, ожидание внешних событий);
- координация между сервисами — вместо хрупких цепочек событий;
- человек в цикле — workflow ждёт решения оператора;
- критичная надёжность — процесс не должен потеряться при перезапуске;
- самодельный state machine уже стал проблемой — слишком сложный, хрупкий, нетестируемый.
Где Temporal избыточен
- Простая обработка очереди — fire-and-forget задачи, одношаговые воркеры;
- процесс в одном сервисе и одной транзакции — не нужен внешний координатор;
- нет инфраструктурной готовности — Temporal Server требует базу данных, мониторинг и операционной поддержки;
- маленькая команда — кривая обучения и debugging Temporal-workflow не бесплатны;
- latency-критичные процессы — overhead Temporal (запись истории) не подходит для задач с требованием <10ms.
Checklist: готовы ли вы к Temporal
- Есть ли в системе многошаговые процессы, которые сейчас реализованы через таблицы состояний или цепочки очередей?
- Сколько строк кода занимает текущий state machine? Если >300 — Temporal, вероятно, упростит.
- Есть ли процессы длительностью больше минуты (ожидание ответа, согласование)?
- Готова ли инфраструктура к ещё одному stateful-компоненту (Temporal Server + persistence/visibility store + мониторинг)?
- Есть ли ресурс на обучение команды (детерминизм workflow, тестирование, debugging)?
- Готовы ли вы к версионированию workflow (patching) при изменении логики уже запущенных процессов?
- Можно ли начать с одного workflow и оценить ROI, прежде чем мигрировать всё?
Начинайте с одного конкретного болезненного процесса. Не переписывайте всё сразу.
Демо и версии
- Demo:
digital-cookbook/architecture/temporal— Temporal dev-сервер (server start-dev+ Web UI) и суб-стенд00-paradigmна Go: workflow сSleep,Signalи компенсацией; убить воркер на середине → workflow продолжается с той же точки, activity не переисполняются (durable execution наглядно). Остальные суб-стенды — полная топология с PostgreSQL+Elasticsearch, детерминизм, activities, версионирование через patching, тестирование с промоткой времени, воркеры на Go и Java — в разработке под серию «Temporal: durable execution вглубь». - Версии Temporal Server и SDK пиновать (поведение determinism/patching чувствительно к версии).
Документация и первоисточники
- Temporal: сайт, документация, исходники, Temporal Cloud.
- Ключевые концепции: workflow determinism, versioning/patching, Continue-As-New.
- Альтернативы: Uber Cadence, AWS Step Functions, Azure Durable Functions, Netflix Conductor, Restate.
- Смежное на сайте: событийная архитектура — карта, saga, outbox/inbox, очереди задач и планировщикиготовится, с 17 сентября.
Комментарии