Absurd: пять месяцев durable execution на Postgres — отчёт Армина Ронахера
Один SQL-файл, SDK на 1400–1900 строк и Postgres вместо отдельного сервиса. Отчёт автора Flask о пяти месяцах эксплуатации Absurd в реальной нагрузке.
Когда воркер падает на шаге 6 из 10, обычно проще всё начать заново или городить свою retry-логику поверх очереди. Durable execution даёт третий путь: каждый шаг — отдельный чекпоинт, при рестарте задача продолжается ровно с того места, где упала. Если эта идея вам близка, но Temporal с его 170 тысячами строк SDK на Python кажется перебором — посмотрите на Absurd. Армин Ронахер, автор Flask, пять месяцев крутит в продакшене систему, которая помещается в один SQL-файл и SDK на 1400–1900 строк (TypeScript и Python соответственно). Ниже — его свежий разбор того, что сработало, а что нет.
Absurd — это система durable execution, целиком живущая внутри Postgres. Ядро — один SQL-файл absurd.sql с хранимыми процедурами для управления задачами, чекпоинтами, событиями и claim-based-планированием (claim — это резервирование задачи воркером, чтобы её не подхватил соседний). Поверх — тонкие SDK на TypeScript, Python и экспериментальный на Go. Никаких отдельных сервисов, плагинов компилятора и собственных рантаймов — только база и тонкая обёртка над ней.
Армин впервые написал про Absurd пять месяцев назад. С тех пор библиотека прошла десяток релизов и реальную нагрузку. Свежий пост — отчёт о том, какие куски архитектуры дожили без изменений, какие пришлось доделать и где остались дыры.
Главное
Что нужно знать про Absurd сегодня
Краткая выжимка из отчёта Армина Ронахера о пяти месяцах эксплуатации
Архитектура из ноября 2025 устояла без переделок: задачи, шаги, чекпоинты, события, suspend — те же абстракции, что и на старте.
Главное обновление — beginStep / completeStep: теперь шаг можно «начать», проверить состояние и только потом завершить. Закрывает кейсы before/after-call хуков.
Появился absurdctl — CLI для миграций, очередей, ретраев. Делает дебаг прод-инцидентов человечным: видишь, на каком шаге задача застряла.
TypeScript SDK — 1400 строк, Python — 1900. Для сравнения, Temporal-SDK на Python — около 170 000.
Главная нерешённая проблема — партицирование таблиц. Detach Partition Concurrently не запускается из pg_cron из-за транзакций.
Перевод выполнен по условиям лицензии оригинала CC BY-NC 4.0. Сама библиотека Absurd распространяется отдельно — под Apache 2.0.
Что такое Absurd, если коротко
Absurd — система durable execution, которая живёт целиком в Postgres. Ядро — один SQL-файл absurd.sql с хранимыми процедурами для управления задачами, чекпоинтами, событиями и claim-based-планированием. Сверху — тонкие SDK, чтобы было удобно вызывать всё это из TypeScript, Python и Go.
Модель простая: вы регистрируете задачу, разбиваете её на шаги, и каждый шаг становится чекпоинтом. Если что-то падает, задача рестартует с последнего успешного шага. Задачи могут спать, ждать внешних событий и приостанавливаться на дни и недели. Всё состояние хранится в Postgres.
Если хотите полное введение — оригинальный пост разбирает фундаменты. Здесь — то, что мы узнали с тех пор.
Что изменилось
За пять месяцев вышло несколько релизов. Большинство правок — то, что и ожидаешь от системы, на которую начали реально рассчитывать: ужесточили обработку claim-ов (резервирование задач воркерами), добавили watchdog-и, которые убивают сломанных воркеров и возвращают их задачи в очередь, защиту от deadlock-ов на уровне Postgres, нормальный lease management (продление «аренды» задачи воркером, который её взял), race-условия на событиях и кучу edge-cases, которые вылезают только под реальной нагрузкой.
Декомпозированные шаги
Изначально было только ctx.step(): передаёшь функцию — получаешь её закешированный результат. Это покрывает большинство кейсов, но не все. Иногда нужно знать, выполнялся ли шаг раньше, прежде чем решить, что делать дальше. Поэтому добавили beginStep() / completeStep(): возвращают handle, который можно проверить до коммита результата. Это очень полезно для моделирования намеренных падений и условной логики.
Особенно нужно при работе с before-call / after-call хук-API.
Результаты задач
Теперь можно стартовать задачу, заняться другими делами и потом вернуться, чтобы получить или дождаться её результата. Звучит очевидно задним числом, но изначально система была чисто fire-and-forget. С нормальным доступом к результатам стало возможно использовать Absurd, например, для спавна child-задач из родительского workflow и ожидания их завершения. Особенно полезно при дебаге с агентами.
absurdctl
Сделали из этого нормальный CLI. Через него инициализируются схемы, гоняются миграции, создаются очереди, спавнятся задачи, эмитятся события, ретраятся падения. Ставится через uvx или как standalone-бинарь.
Для дебага продакшен-инцидентов это незаменимо. Когда что-то застряло, возможность просто написать absurdctl dump-task --task-id=<id> и сразу увидеть, где именно остановилось — это совсем другой опыт, чем копаться в логах.
Habitat
Маленькое приложение на Go, которое поднимает веб-дашборд для мониторинга задач, прогонов, чекпоинтов и событий. Подключается прямо к Postgres и даёт живое представление о происходящем. Простое, но именно такие вещи делают систему приятной для людей.
Интеграция с агентами
Раз Absurd изначально строился под workload-ы агентов, добавили bundled-скилл, который кодинг-агенты могут обнаружить и использовать для дебага состояния workflow через absurdctl. Также описан паттерн, как сделать turn-ы агента pi (это один из coding-агентов) durable: каждое сообщение логируется как чекпоинт.
Что устояло без переделок
Что меня радует больше всего — фундамент проекта почти не поменялся. Базовая модель из задач, шагов, чекпоинтов, событий и suspend-ов — ровно та же, что была в начале. Мы добавили вокруг фичи, но ничего не заставило пересмотреть сами абстракции.
Ставка на «вся сложность в SQL, SDK тонкие» оказалась реально хорошей. TypeScript-SDK — около 1400 строк. Python-SDK — около 1900, и большая часть объёма из-за поддержки «цветных» функций (sync/async). Сравните с Temporal Python SDK на ~170 000 строк. Это значит, что SDK легко понять, легко дебажить и легко портировать. Если что-то идёт не так, можно прочитать весь SDK за полдня и понять, что он делает.
Модель replay по чекпоинтам тоже себя оправдала. В отличие от систем, которые требуют детерминированного replay всей workflow-функции, Absurd просто загружает закешированные результаты шагов и пропускает выполненную работу. Это значит, что код не обязан быть детерминированным вне шагов. Между шагами можно вызывать Math.random() или datetime.now() — всё работает, потому что важны только границы шагов. На практике это сильно упрощает рассуждения о том, что безопасно, а что нет.
Pull-based планирование тоже оказалось правильным выбором. Воркеры сами забирают задачи из Postgres по мере появления свободных ресурсов. Никакого координатора, никакого push-механизма, никаких HTTP-callback-ов. Это делает систему тривиально self-hostable и снимает необходимость думать о load management на уровне инфраструктуры.
Что осталось нерешённым архитектурно
Открытый вопрос — стоило ли строить всё вокруг durable promise (долгоживущего обещания результата, который выживет рестарт процесса). В теории это более мощная абстракция: вместо явных шагов вы получаете объект-обещание, на котором можно ждать или вешать колбэки, а система сама обеспечивает его доставку. На практике — заметно сложнее в реализации. Я делал попытки прикинуть, как выглядел бы Absurd на durable promises, но ни к чему не пришёл. Эксперимент, который было бы интересно довести до конца.
Для чего мы это используем
Основной use-case по-прежнему — workflow-ы агентов. Агент — это, по сути, цикл, который зовёт LLM, обрабатывает результаты тулзов и повторяет, пока не решит, что закончил. Каждая итерация становится шагом, результат каждого шага чекпоинтится. Если процесс умирает на 7-й итерации, он стартует, replay-ит итерации 1–6 из стора и продолжает с 7-й.
Но мы нашли применение и для другого. Все наши cron-задачи теперь просто диспатчат Absurd-таски с заранее сгенерированным ключом дедупликации (формула: ключ = имя крона + текущий слот времени). Поэтому даже два cron-процесса, запущенные параллельно, триггернут только один Absurd-таск — это всё ещё pull-модель, просто триггер живёт снаружи. Также используем для фоновой обработки, которая должна переживать деплои. По сути, везде, где иначе пришлось бы строить свою retry-and-resume-логику поверх очереди.
Чего не хватает
Absurd намеренно минималистичен, но кое-что хотелось бы видеть.
Нет встроенного шедулера. Если хочется cron-подобного поведения — поднимаешь свой scheduler-цикл и используешь idempotency-ключи для дедупликации. Это работает, и у нас есть описанный паттерн, но было бы приятно иметь что-то более интегрированное.
Нет push-модели. Всё на pull. Если нужен HTTP-эндпоинт, чтобы принимать вебхуки и будить задачи — это пишется руками. Я считаю, что это правильный дефолт: push-системы сложнее эксплуатировать, и их легче перегрузить. Но иногда было бы удобно. В частности, для агентных систем было бы здорово иметь нативно встроенные вебхуки (wake on incoming POST). В ядро это совсем не хочется тащить — но звучит как задача для смежной библиотеки поверх Absurd.
Главная дыра — поддержки партицирования пока нет. Это обидно, потому что чистка данных получается дороже, чем могла бы. В теории партицирование добавляется довольно просто: weekly-партиции, которые detach-ишь и удаляешь по мере истечения. Единственная загвоздка — у Postgres нет удобного способа это делать.
Сложная часть — не само партицирование, а управление жизненным циклом партиций под реальной нагрузкой. Если воркер вставляет строку, у которой expires_at попадает в месяц без партиции — insert падает, и workflow рушится. Поэтому нужен отдельный maintenance-loop, который всегда создаёт будущие партиции достаточно далеко вперёд для sleep-ов и retry-ев, и делает это для каждой очереди.
На стороне удаления безопасный путь — DETACH PARTITION CONCURRENTLY, но запустить его из pg_cron не получается, потому что эту команду нельзя выполнять внутри транзакции, а pg_cron всё гоняет в одной.
Не думаю, что это нерешаемая проблема, но решения у меня пока нет, и я был бы рад обратной связи в issues.
А open source ещё имеет смысл?
Это подводит к мета-вопросу: какой смысл в open-source библиотеках в эпоху агентной разработки. Durable execution сейчас продают десятки стартапов. С другой стороны, это то, что вам мог бы написать агент, и люди могут даже не искать готовых решений. Странно немного, да?
Я не верю, что библиотека для durable execution может прокормить компанию, реально не верю. С другой стороны, эта задача ровно настолько сложная, что может стать хорошим open-source проектом без коммерческих интересов. Нужна экосистема вокруг — особенно UI и нормальный DX для дебага — а это сложно собрать одноразовой реализацией.
Не уверен, что мы ответили на этот вопрос окончательно. Но Absurd сейчас уже сильно лучше, чем был несколько месяцев назад.
Часто задаваемые вопросы
Чем Absurd отличается от Temporal?
Главное отличие — масштаб. Temporal — большая система с собственным сервером, требующим координатор и отдельную инфраструктуру; Python-SDK Temporal весит около 170 000 строк. Absurd — это один SQL-файл и тонкий SDK на 1400–1900 строк, всё работает поверх обычного Postgres без отдельных сервисов. Absurd проще понять и self-host-ить, но у Temporal больше готовых интеграций и зрелая экосистема.
Можно ли использовать Absurd с уже работающим Postgres-приложением?
Да. Absurd подгружается в существующую базу через миграции от absurdctl и не требует отдельного инстанса. Но имейте в виду нагрузку: workflow-операции и чекпоинты создают регулярный поток writes, поэтому для high-traffic-приложений лучше иметь отдельный инстанс или схему. Партицирования пока нет, поэтому большие таблицы со временем потребуют ручной чистки.
На каких языках есть SDK?
Стабильные SDK — TypeScript и Python. Есть экспериментальный SDK на Go. Ядро — SQL, поэтому теоретически SDK можно написать под любой язык, у которого есть Postgres-драйвер. Из-за тонкости SDK портировать его — задача на дни, а не на месяцы.
Зачем разбивать workflow на шаги, если можно просто обернуть всё в транзакцию?
Шаги — это не транзакции, а долгоживущие чекпоинты, переживающие падение процесса, рестарт сервера и деплои. Транзакция длиной в часы или дни заблокирует ресурсы Postgres и упадёт при первом разрыве соединения. Absurd пишет результат каждого шага отдельно и при рестарте просто пропускает выполненные.
Где посмотреть код и попробовать?
Репозиторий — github.com/earendil-works/absurd, лицензия Apache 2.0. Документация и примеры там же. Стартовать проще всего через absurdctl: устанавливаете через uvx, делаете absurdctl init и получаете готовую схему в своей Postgres-базе.
Выводы
Если у вас уже есть Postgres и нужно что-то из перечисленного — workflow-ы для агентов с retry, cron с дедупликацией, фоновая обработка, переживающая деплои, или просто долгоживущая задача с шагами — Absurd подойдёт. Полтора SDK на 1400–1900 строк читаются за вечер, и вы сразу поймёте, как это работает и где ваши данные лежат. Это редкое ощущение для durable-execution систем.
Сам Армин в финале оригинала пишет: «Если вы используете Absurd, думаете о нём или строите что-то рядом — буду рад вашей обратной связи. Баг-репорты, неудобные углы, критика дизайна, контрибьюшены — всё приветствуется. Этот проект становится лучше каждый раз, когда кто-то ковыряется в нём с другой стороны».
Источник: lucumr.pocoo.org — Absurd In Production, 4 апреля 2026.