Absurd: пять месяцев durable execution на Postgres - разбор боевого опыта Армина Ронахера
Durable execution в классическом виде обычно ассоциируется с тяжёлыми отдельными сервисами, сложной инфраструктурой и громоздкими SDK. Absurd идёт другим путём: вся система умещается в один SQL‑файл, работает поверх обычного Postgres, а клиентские библиотеки на TypeScript и Python занимают всего по полторы-две тысячи строк кода. Армин Ронахер, автор Flask, уже пять месяцев крутит Absurd в продакшене и делится тем, что за это время устояло, что пришлось переписать и какие архитектурные дыры всё ещё остаются.
Зачем вообще нужен durable execution
Типичная ситуация: у вас есть задача, разбитая на последовательность шагов - скажем, 10 стадий сложного бизнес‑процесса. Воркер успешно выполняет пять из них, на шестом падает - из‑за сети, бага в коде или перезапуска контейнера. В большинстве очередей и job‑раннеров выбор небогат:
- либо перезапускать всю задачу заново,
- либо навешивать поверх очереди собственную ретрай‑логику, хранить состояние где‑то ещё, аккуратно мержить результаты и следить за идемпотентностью.
Durable execution предлагает третий вариант. Каждый шаг задачи становится чекпоинтом: состояние зафиксировано в базе, и при рестарте воркер продолжает выполнение ровно с того места, где произошёл сбой. Не нужно переизобретать паттерны саг, вручную писать оркестраторы и городить вокруг очереди свои велосипеды.
Если сама идея пошаговых чекпоинтов кажется правильной, но громоздкие решения уровня Temporal с его десятками (а то и сотнями) тысяч строк SDK вам не подходят, стоит посмотреть в сторону Absurd.
Что такое Absurd в текущем виде
Absurd - это минималистичная система durable execution, целиком живущая внутри Postgres. Её ядро - один SQL‑файл `absurd.sql`. В нём описаны:
- хранимые процедуры для постановки и управления задачами;
- модель шагов и чекпоинтов;
- хранение и обработка событий;
- claim‑based планирование - механизм, при котором воркер "забивает" задачу за собой, чтобы её не подобрал другой процесс.
Сверху над этим ядром - тонкие SDK:
- на TypeScript (около 1400 строк),
- на Python (примерно 1900 строк),
- плюс экспериментальная библиотека на Go.
Никаких выделенных сервисов, собственных рантаймов, плагинов к компилятору и тяжёлой инфраструктуры - только Postgres и лёгкая обвязка вокруг него.
Впервые Армин публично рассказал об Absurd около пяти месяцев назад. С тех пор проект пережил десяток релизов, оброс критически важными деталями и прошёл проверку реальной нагрузкой. Сейчас можно уже не просто рассуждать о красивой архитектуре, а честно посмотреть, что из исходной идеи выдержало продакшен, а что приходится дорабатывать на ходу.
Базовая модель: задачи, шаги и чекпоинты
Ключевая концепция Absurd - разбиение любой сложной операции на дискретные шаги, каждый из которых превращается в чекпоинт.
Вы регистрируете задачу, описываете её в виде последовательности шагов:
- при успешном выполнении шага система фиксирует результат в Postgres;
- если воркер падает, новая попытка продолжает выполнение с последнего успешно завершённого шага;
- задача может "засыпать", ожидая события или таймера, и "просыпаться" через часы, дни или даже недели;
- всё состояние - от прогресса до вложенных результатов - хранится в базе.
Изначально взаимодействие со шагами в SDK было построено вокруг единственного примитива `ctx.step()`: вы передаёте функцию и получаете закешированный результат её выполнения. Для большинства сценариев этого хватает: шаг выполнен - результат сохранён, при повторном вызове можно не пересчитывать. Но продакшен быстро подсказал, что реальный мир сложнее.
Новые примитивы: beginStep / completeStep
В процессе эксплуатации выяснилось, что иногда перед выполнением шага нужно сначала понять, вызывался ли он уже раньше, и только после этого решать, что делать дальше. Возникли сценарии с условной логикой, преднамеренными падениями для тестирования устойчивости и сложными хуками до и после вызовов.
Чтобы закрыть эти кейсы, в Absurd добавили пару примитивов:
- `beginStep()` - стартует шаг и возвращает handle, по которому можно проверить его текущее состояние;
- `completeStep()` - завершает шаг, фиксируя итог.
Такой разнос на "начало" и "окончание" шага позволяет:
- узнать, запускался ли шаг ранее;
- принять решение о его повторе или пропуске;
- моделировать преднамеренные ошибки (например, для нагрузочного тестирования или проверки ретраев);
- реализовывать гибкий before/after‑call API, где логика до и после основного вызова может зависеть от того, что происходило с шагом раньше.
По сути, это переход от простой функции "сделал и закешировал" к полноценной модели управления жизненным циклом шага.
Работа с результатами задач: от fire‑and‑forget к контролю
Первые версии Absurd были, по сути, системой fire‑and‑forget:
- вы создаёте задачу,
- отправляете её в очередь,
- дальше она живёт своей жизнью, а вы лишь доверяете тому, что в какой‑то момент она завершится.
Такой подход оказался удобен для простых фоновых задач, но быстро стал ограничивать сценарии, где важно явно дождаться результата. В итоге в системе появилась полноценная работа с итогами выполнения:
- теперь можно стартовать задачу и вернуться к ней позже, чтобы забрать результат или дождаться его;
- стало возможным порождать дочерние задачи из родительского workflow и отслеживать их завершение;
- при интеграции с агентами и внешними системами это сильно упростило отладку: можно анализировать, что именно вернула каждая подзадача и на каком шаге всё пошло не так.
Этот сдвиг превратил Absurd из чистого "механизма фоновой обработки" в полноценный оркестратор, которым можно управлять более осознанно.
absurdctl: человеческий интерфейс к системе
Чтобы не работать с системой вслепую, появился отдельный CLI‑инструмент - `absurdctl`. Через него можно:
- инициализировать схемы в Postgres;
- прогонять миграции;
- управлять очередями - создавать, смотреть состояние, приостанавливать;
- запускать задачи вручную и отслеживать их жизненный цикл;
- инициировать ретраи, диагностировать подвисшие или застрявшие задания.
Для продакшена это критично:
- можно быстро увидеть, на каком конкретно шаге зависла задача;
- понять, какой воркер её заклеймил;
- оценить, не нарушены ли lease‑гарантии;
- безопасно вернуть задачу в очередь, если воркер "умер" или застрял.
CLI превращает систему в инструмент, с которым приятно работать на инцидентах, а не в "чёрный ящик" из одних лишь SQL‑функций.
Habitat и интеграция с агентами
Поверх базовых примитивов в Absurd появился дополнительный слой - Habitat. Это набор удобств и абстракций для построения более сложных workflow и интеграции с внешними агентами.
Habitat позволяет:
- структурировать жизненный цикл задач и воркеров,
- унифицировать работу с внешними сервисами,
- легче подключать "умных" агентов, которые принимают решения на основе промежуточного состояния задач.
Интеграция с агентами особенно важна для сценариев, где решения принимаются не жёстко по коду, а на основе анализа результатов, логов, статистики или внешних сигналов. Durable execution при этом обеспечивает надёжное хранение всего контекста, а Habitat - удобную связку с логикой на стороне агента.
Что устояло без серьёзных переделок
Несмотря на множество доработок, фундаментальная архитектура, описанная ещё в ноябре 2025 года, пережила пять месяцев продакшена практически без изменений. Устояли:
- модель задач, шагов и чекпоинтов;
- событийная система;
- механика suspend/резюма (приостановка задач на произвольный срок);
- подход к claim‑based планированию.
То есть сама идея "всё состояние в Postgres, тонкое SDK сверху и никаких отдельных сервисов" показала жизнеспособность. Пострадали в основном детали реализации и "боевые" мелочи - именно те, которые обычно проявляются только под нагрузкой.
Эволюция под нагрузкой: что пришлось ужесточить
За пять месяцев и несколько релизов в системе было сделано множество доработок, типичных для продуктов, которые вдруг начинают играть критическую роль в инфраструктуре:
- Усилена обработка claim‑ов: теперь воркеры надёжнее резервируют задачи, а риск того, что одно и то же задание параллельно возьмут несколько процессов, существенно снижен.
- Добавлены watchdog‑процессы: они отслеживают некорректно работающих или "умерших" воркеров, забирают у них невыполненные задачи и возвращают в очередь.
- Введены защиты от deadlock‑ов на уровне Postgres: при активном конкурентном доступе и большом количестве задач блокировки - вопрос времени, и без дополнительной логики система начинает вести себя нестабильно.
- Нормализовано управление lease‑ами: воркер, взявший задачу, периодически продлевает "аренду". Если он перестаёт это делать, задача может быть безопасно передана другому исполнителю.
- Обработаны разнообразные гонки на событиях и "краевые" кейсы, которые встречаются только в реальной жизни - с нестабильными сетями, внезапными рестартами и неидеальным кодом.
Именно эти доработки отличают прототип от реально используемой системы: код не стал в разы объёмнее, но стал заметно взрослее.
Главная нерешённая проблема: партицирование
На фоне всех улучшений осталась архитектурная проблема, которую пока так и не удалось решить элегантно: партицирование таблиц в Postgres.
С ростом нагрузки и объёма исторических данных стало очевидно, что без партиций не обойтись. Но при попытке автоматизировать управление ими всплыли ограничения:
- операция `DETACH PARTITION CONCURRENTLY` не запускается из `pg_cron` из‑за особенностей работы с транзакциями;
- обслуживать партиции вручную на нагруженной системе неудобно и рискованно;
- хочется иметь прозрачный, автоматизированный механизм ротации данных, не требующий постоянного внимания.
Пока это остаётся открытым архитектурным вопросом: использовать внешние инструменты, усложнять схемы миграций или дописывать специальные "обслуживающие" процессы.
Для чего уже используют Absurd
Несмотря на компактность и относительную молодость системы, за пять месяцев она нашла себе несколько устойчивых применений:
- сложные фоновые задачи с большим числом шагов и внешними вызовами;
- интеграционные workflow, где нужно надёжно хранить состояние и результаты каждого этапа;
- долгоживущие процессы, которые могут прерываться и возобновляться через большие промежутки времени;
- оркестрация задач, порождаемых агентами или внешними системами, с контролем результатов.
Во всех этих сценариях ключевым преимуществом остаётся сочетание: один Postgres + тонкое SDK + никакой лишней инфраструктуры.
Чего пока не хватает
Даже после десятка релизов в Absurd остаются очевидные зоны роста:
- стандартные паттерны поверх базовых примитивов (саги, транзакционные аутбоксы, типичные retry‑стратегии) ещё только формируются;
- tooling вокруг системы - от UI до более продвинутых средств мониторинга - находится в стадии активного развития;
- нет "из коробки" идеального решения для масштабирования с учётом партиций и очень больших объёмов истории;
- разработчикам по‑прежнему нужно достаточно хорошо понимать Postgres, чтобы использовать Absurd осознанно.
С другой стороны, именно компактность кода и прозрачность архитектуры делают эти ограничения честными: не приходится разбираться в сотнях тысяч строк SDK, чтобы понять, как всё устроено.
Имеет ли открытый код ещё смысл
Отдельная тема, которой Армин касается в своём отчёте, - практическая ценность open source в таком формате.
Absurd доступен под лицензией Apache 2.0 (перевод отчёта - под CC BY‑NC 4.0), и это даёт несколько важных эффектов:
- прозрачность архитектуры: любой разработчик может открыть SQL‑файл и SDK, посмотреть, как именно система работает, и понять её ограничения;
- возможность адаптации под свои нужды: при желании можно форкнуть проект, добавить собственные примитивы, интеграции или изменённую модель задач;
- снижение порога доверия: лёгче внедрять новый инструмент в критичную инфраструктуру, когда его устройство не скрыто за проприетарными бинарями.
На фоне тяжёлых, закрытых или полузакрытых систем компактный и понятный open source‑проект выглядит разумной альтернативой.
Сравнение с тяжёлыми решениями
Если посмотреть на цифры, контраст становится особенно заметным:
- TypeScript SDK для Absurd - порядка 1400 строк;
- Python SDK - около 1900;
- для сравнения, один только Python‑SDK популярного Temporal - примерно 170 000 строк.
Разница - не просто в размере репозитория. Компактный SDK легче читать, понимать и сопровождать, а значит, и адаптировать под свои сценарии. Вкупе с тем, что само ядро системы - это один SQL‑файл, получается достаточно редкое сочетание мощности и простоты.
Практические сценарии применения
Если вы уже используете Postgres как основную базу и задумываетесь о durable execution, Absurd может быть интересен для:
- миграции с самописных job‑систем, где состояние шагов хранится в случайных таблицах и логах;
- замены сложных оркестраторов, если вам не нужны все их возможности, но критично важно надёжное выполнение с чекпоинтами;
- построения архитектуры, где вся логика долговременного состояния - от бизнес‑процессов до интеграций - централизована в одной БД;
- экспериментов с агентами и автоматизацией: durable execution даёт надёжную основу, на которой можно строить адаптивное поведение.
Важный плюс - низкий порог входа: достаточно привычного стека (Postgres + TypeScript/Python) и готовности продумать модель шагов.
Итоги пяти месяцев продакшена
За пять месяцев эксплуатации Absurd прошёл путь от интересного эксперимента до реально работающего компонента инфраструктуры:
- фундаментальная модель задач, шагов, чекпоинтов и событий выдержала нагрузку без радикальных изменений;
- поверх неё были наращены важные примитивы (`beginStep`/`completeStep`, управление результатами, Habitat, CLI);
- система обросла базовой "боевой" обвязкой - от watchdog‑ов до защиты от deadlock‑ов и нормального lease‑менеджмента;
- остаются архитектурные вопросы, в первую очередь - партицирование и масштабирование больших объёмов истории;
- при этом всё по‑прежнему укладывается в один SQL‑файл и очень компактные SDK на популярных языках.
Absurd показывает, что durable execution не обязан быть синонимом тяжёлой распределённой платформы. Вполне возможно построить надёжную, чекпоинт‑ориентированную систему выполнения задач, опираясь только на Postgres и тонкий слой кода вокруг него - и пять месяцев продакшена у Армина Ронахера это подтверждают.



