Недавно вышла статья-продолжение к предыдущему «просто используйте Postgres» для надёжных рабочих процессов. Авторы поясняют: речь шла не о том, чтобы взять любой workflow-движок, который хранит состояние в Postgres. Идея в том, что сам движок должен жить внутри той же базы данных Postgres, что и ваше приложение. Звучит сомнительно: обычно же разделяют данные и оркестрацию? Но в распределённых системах совместное размещение (co-location) — суперсила.
Когда метаданные воркфлоу и данные приложения лежат в одном Postgres, их можно обновить в одной транзакции. Это убивает частичные сбои. Строить воркфлоу, корректно обрабатывающие все граничные случаи, становится гораздо проще.
Главная проблема — идемпотентность. Обычные durable workflows чекпоинтят шаги после их завершения. Если сбой случился после выполнения шага, но до записи чекпоинта, то при восстановлении шаг выполнится снова. Например, зачисление $100 на счёт — неидемпотентная операция: повторишь — добавишь лишние деньги. Типовое решение — добавлять таблицу applied_payments и вручную проверять дубликаты.
Но если workflow-движок co-located, он может записывать чекпоинт и делать обновление базы в одной транзакции. Движок даёт шагу транзакцию; шаг обновляет данные, движок пишет чекпоинт — и всё коммитится атомарно. Если коммит прошёл — оба действия зафиксированы, шаг больше не запустится. Если сбой до коммита — откат всего, и при восстановлении шаг выполняется заново. Так достигается exactly-once для транзакционных шагов без дополнительной логики и таблиц.
Вторая типичная проблема — атомарность при записи в несколько систем (например, обновить запись в БД и отправить уведомление на склад). Классическое решение — transactional outbox: в одной транзакции обновляем данные и пишем сообщение в таблицу-outbox, а отдельный фоновый процесс забирает и рассылает. Но это добавляет сложности: нужен poll, retry, reconciliation jobs.
Co-location упрощает и это. Вместо outbox и polling можно использовать Postgres UDF (user-defined function), которая в той же транзакции, что и обновление данных, создаёт запись воркфлоу (название, очередь, входные данные). Гарантия атомарности: или обновление + enqueue, или ничего. Асинхронный worker потом забирает и выполняет воркфлоу.
Авторы из DBOS утверждают, что их подход делаетPostgres-backed durable execution простым и производительным.