botruntime
Фоновые процессы

Гарантии runtime

Возобновление, повторная доставка, таймеры и идемпотентность воркфлоу.

Платформа хранит состояние воркфлоу и доставляет продолжение боту. Логика процесса остаётся в вашем TypeScript-коде.

Возобновление

При каждом продолжении handler запускается с начала. Выполненные шаги возвращают сохранённый результат. Поэтому побочные эффекты должны находиться внутри step().

const result = await step('reserve-stock', async () => {
  return inventory.reserve(input.orderNumber)
})

Код между шагами может выполниться несколько раз. Держите его чистым и детерминированным.

Длительность воркфлоу и одного запуска

timeout ограничивает полную жизнь экземпляра воркфлоу: она может составлять минуты, часы или дни. Один запуск handler — это короткий runtime invocation с бюджетом до двух минут. Перед исчерпанием бюджета runtime сохраняет dirty state, уступает выполнение платформе и получает новое продолжение.

Поэтому долгую работу делите на устойчивые checkpoint-ы:

for (const batch of batches) {
  await step(`index-${batch.id}`, () => indexBatch(batch))
}

После возобновления завершённые шаги реплеятся из сохранённых результатов, а первый незавершённый шаг выполняется. Не увеличивайте HTTP timeout, чтобы «уместить» весь процесс в один invocation: это ломает возобновление и не увеличивает timeout самого воркфлоу.

Платформа выдаёт invocation execution lease, runtime продлевает её heartbeat-ом, а eventId служит fencing-token. Пока lease активна, reconciler не запускает второй обработчик. Если HTTP-ответ потерян после acknowledgement, доставка не повторяется конкурентно; после штатного yield продолжение ставится сразу.

Доставка at-least-once

Runtime может повторить доставку после сетевой ошибки или перезапуска. Это означает, что обработчик должен быть идемпотентным.

At-least-once относится к доставке, которая не была подтверждена. Принятый runtime-ом eventId отделяет потерянный HTTP-ответ от действительно не доставленного события.

  • Используйте стабильный workflow ID для одной бизнес-операции.
  • Давайте каждому шагу стабильное имя.
  • Передавайте idempotency key во внешнюю систему, если она его поддерживает.
  • Не считайте HTTP timeout доказательством, что операция не выполнилась.

Повтор шага

step() повторяет ошибку с ограниченным backoff. maxAttempts задаёт верхнюю границу попыток.

await step('sync-crm', syncCrm, { maxAttempts: 3 })

После исчерпания попыток воркфлоу завершается как failed. Возвращать значение по умолчанию вместо ошибки следует только тогда, когда это разрешено контрактом процесса.

Таймеры

step.sleep() и step.sleepUntil() создают durable timer. Он хранится на платформе и переживает остановку runtime.

await step.sleep('wait-before-retry', '15m')
await step.sleepUntil('send-reminder', reminderAt)

timeout ограничивает жизнь всего экземпляра. После дедлайна процесс получает терминальный статус timed_out.

Запросы и callback

step.request() переводит процесс в ожидание данных. Workflow.provide() возобновляет его после проверки payload по схеме.

После завершения связанный диалог получает workflow_callback со статусом и результатом. Обрабатывайте все терминальные статусы:

  • completed;
  • failed;
  • canceled;
  • timed_out.

Наблюдаемость

Workflow ID и имена шагов должны помогать диагностике без раскрытия содержимого сообщений. Не помещайте персональные данные и токены в имена шагов, логи и метки.

Используйте мониторинг для связи callback, событий и логов. Повторную доставку не следует считать отдельной бизнес-операцией, если workflow ID совпадает.

Дальше

On this page