Долгие serverless-задачи: цепочки шагов, fan-out и ретраи на шаг
Долгая работа — не одна безграничная функция, а цепочка шагов, каждый с собственным бюджетом таймаута (до 15 минут), политикой ретраев и чекпоинтом выхода. Когда падает шаг 7 из 10, повторяется только шаг 7 — результаты выполненных шагов сохранены в чекпоинтах. Разветвляйтесь в параллельные ветки, собирайте результаты, делайте паузу на подтверждение человеком и возобновляйте с места остановки.
Обновлено: 2026-06-28
- Ретраи на шаг: заново запускается только упавший шаг, выполненная работа сохранена в чекпоинтах
- Fan-out + merge: параллельные ветки сходятся в одном шаге агрегации
- Human-gate паузы: пайплайн ждёт подтверждения перед продолжением
- Каждый шаг: до 15 мин таймаута в изолированном контейнере; цепочки — для более долгой работы
Кратко
Суть ответа
Долгие serverless-задачи: цепочки шагов, fan-out и ретраи на шаг. Пайплайн — это граф шагов. Каждый шаг — вызов serverless-функции с собственным таймаутом, политикой ретраев (maxAttempts, backoffMs, стратегия fixed или exponential) и записью выхода. Завершённые шаги не перезапускаются при падении следующего — пайплайн возобновляется с упавшего шага, имея доступ к выходам предыдущих.
Когда подходит и когда нет
- Работа декомпозируется на независимые шаги, где ретраи на шаг стоят добавленной структуры
- Fan-out: N параллельных подзадач, которые сходятся в одном агрегированном результате
- Паузы для подтверждения человеком или внешних событий в середине воркфлоу
На что обратить внимание
- Одна фоновая функция без декомпозиции шагов означает ретраи по принципу «всё или ничего». Временная сетевая ошибка при записи в БД в конце 45-минутной трансформации перезапускает всю 45-минутную трансформацию.
- Fan-out паттерны — обработка N записей параллельно — в одной функции требуют N горутин или promise-цепочек внутри одного контейнера без видимости, какие подзадачи успели до таймаута.
Ситуация: нагрузка и где обычно ломается
Почему долгая работа не помещается в одну функцию
Одна serverless-функция, даже с таймаутом 15 минут, не может надёжно выполнять многочасовой ETL, параллельное обогащение данных или воркфлоу, требующие подтверждения человека в середине. Одна функция — один failure domain: если шаг 7 из 10 падает на 50-й минуте, вы перезапускаете с нулевой минуты.
Самостоятельно нарезать работу — рекурсивно вызывать Lambda, делить на SQS-батчи — значит строить workflow-движок в коде приложения: отслеживать, какие чанки завершились, повторять нужные, агрегировать результаты. Именно эту проблему решают пайплайны.
Компромиссы
Почему одношаговые async-задачи ломаются под сложностью
Одна фоновая функция без декомпозиции шагов означает ретраи по принципу «всё или ничего». Временная сетевая ошибка при записи в БД в конце 45-минутной трансформации перезапускает всю 45-минутную трансформацию.
Fan-out паттерны — обработка N записей параллельно — в одной функции требуют N горутин или promise-цепочек внутри одного контейнера без видимости, какие подзадачи успели до таймаута.
Как Inquir помогает в этом сценарии
Шаги пайплайна как независимые единицы выполнения
Пайплайн — это граф шагов. Каждый шаг — вызов serverless-функции с собственным таймаутом, политикой ретраев (maxAttempts, backoffMs, стратегия fixed или exponential) и записью выхода. Завершённые шаги не перезапускаются при падении следующего — пайплайн возобновляется с упавшего шага, имея доступ к выходам предыдущих.
Fan-out — первоклассный тип узла: параллельный узел порождает N ветвей конкурентно; узел merge ждёт все ветви перед продолжением. Узлы humanGate приостанавливают пайплайн до прихода внешнего события подтверждения. Всё это — конфигурация пайплайна, а не код приложения.
Что вы получаете на платформе
Паттерны многошаговой архитектуры пайплайна
Цепочка шагов с ретраями на шаг
Цепляйте шаги через dependsOn. У каждого шага свои число попыток и backoff (фиксированный или экспоненциальный). При падении шага 7 повторяется только шаг 7 — результаты шагов 1–6 остаются в чекпоинтах.
Fan-out и merge
Параллельный узел порождает N ветвей конкурентно. Узел merge агрегирует их выходы. Для N-стороннего API-обогащения, параллельного ресайза изображений или массовой рассылки — без управления параллелизмом в коде.
Пауза human-gate и возобновление
Узел humanGate приостанавливает пайплайн. Внешнее событие (вебхук, ручной триггер) возобновляет его. Используйте перед чувствительными действиями: рассылка 100k пользователям, списание, изменение продакшн-данных.
Долгая работа через композицию шагов
Каждый шаг выполняется до 15 минут. Для многочасового ETL декомпозируйте: извлечение (шаг 1), трансформация батча A (шаг 2), трансформация батча B (шаг 3), загрузка (шаг 4). Цепляйте их; платформа управляет последовательностью.
Что сделать дальше, по шагам
Как спроектировать многошаговый serverless-пайплайн
Сначала спроектируйте граф шагов, затем реализуйте каждый шаг как независимую функцию.
Декомпозировать работу на шаги
Нанесите задачу на граф: какие шаги последовательны (dependsOn), какие параллельны (parallel + merge), какие требуют human gate. Каждый шаг должен укладываться в 15 минут.
Реализовать и подключить каждый шаг
Каждый шаг — serverless-функция, читающая event.previousOutput или event.stepResults для контекста upstream. Настройте ретраи и backoff на шаг по характеристикам его сбоев.
Триггер из HTTP и наблюдение
Примите HTTP-запрос, вызовите global.durable.startNew(), верните 202. Наблюдайте прогон в истории выполнения: какой шаг работает, какой повторился, что вернул каждый.
Пример кода
HTTP → извлечение → fan-out → merge пайплайн
HTTP-хендлер принимает задачу и возвращает 202. Оркестратор разветвляется в параллельные шаги обогащения, затем шаг merge агрегирует результаты. У каждого шага — свой таймаут и политика ретраев.
export async function handler(event) { const { batchId, itemIds } = JSON.parse(event.body || '{}'); if (!batchId || !itemIds?.length) return { statusCode: 400, body: JSON.stringify({ error: 'batchId and itemIds required' }) }; const { instanceId: jobId } = await global.durable.startNew( 'enrich-batch', undefined, { batchId, itemIds } ); return { statusCode: 202, body: JSON.stringify({ jobId, status: 'started' }) }; }
export async function handler(event) { // step 1 — runs first, output feeds into fan-out branches const { batchId, itemIds } = event.payload ?? {}; const items = await db.fetchItems(itemIds); // may take several minutes await db.markBatchStarted(batchId); return { batchId, items }; // passed to parallel branches via {{steps.extract.output}} }
export async function handler(event) { // Each parallel branch receives the upstream node's output as its event. // Node retry: maxAttempts=3, backoffMs=2000, strategy=exponential const enriched = await externalApi.enrich(event.item); // retry isolates this network call return { itemId: event.item.id, enriched }; }
export async function handler(event) { // Runs after ALL parallel branches complete. A merge node hands the next step // { inputs: { <branchNodeId>: <branchOutput>, ... } } — one entry per branch. const branchOutputs = Object.values(event.inputs ?? {}); const merged = branchOutputs.map(b => b.enriched).filter(Boolean); await db.saveBatchResults(merged); return { saved: merged.length, total: branchOutputs.length }; }
Когда подходит и когда нет
Используйте многошаговые пайплайны, когда…
Когда это уместно
- Работа декомпозируется на независимые шаги, где ретраи на шаг стоят добавленной структуры
- Fan-out: N параллельных подзадач, которые сходятся в одном агрегированном результате
- Паузы для подтверждения человеком или внешних событий в середине воркфлоу
Когда лучше выбрать другое
- Простая последовательная работа, укладывающаяся в один шаг менее 15 минут — один шаг пайплайна без декомпозиции вполне подходит
Вопросы и ответы
Вопросы и ответы
Как долго может выполняться один шаг пайплайна?
Каждый шаг — вызов функции с настраиваемым таймаутом до 15 минут (900 000 мс). Для работы дольше 15 минут декомпозируйте её на несколько шагов, связанных в пайплайне — каждый шаг может работать до 15 минут независимо.
Как именно работают ретраи на шаг?
У каждого шага пайплайна своя конфигурация ретраев: maxAttempts, backoffMs и strategy (fixed или exponential). Когда шаг падает и исчерпывает попытки, пайплайн помечает его failed и прогон останавливается — ранее завершённые шаги не перезапускаются. Затем пайплайн можно перезапустить с упавшего шага.
Может ли human gate ждать бесконечно?
Узел humanGate приостанавливает пайплайн до прихода внешнего события через event API платформы. Жёсткого лимита ожидания нет, но прогоны со статусом WAITING учитываются в лимитах workspace. Используйте таймауты для воркфлоу с SLA-требованиями.
Как ветви fan-out получают свои входные данные?
Параллельный узел передаёт выход родительского шага каждой ветви как payload триггера ветви. Хендлеры ветвей читают event.payload для своей части работы. Узел merge получает все выходы ветвей как event.previousStepResults.