ADR-0001: Use Postgres As Task Queue
Status
Accepted (2026-04-14)
Context
Мультиагентная система требует очередь задач. Требования:
- Не плодить инфраструктуру (2 сервера, 1 человек)
- Transactional guarantees: задача + результат в одной транзакции
- Легко отлаживается (
SELECT * FROM tasks WHERE status = 'failed') - Совместимо с масштабированием (горизонтальное добавление воркеров)
Важный контекст: опыт с SQLITE_BUSY_SNAPSHOT в проекте Комбинаторика — SQLite не справляется с concurrent writes.
Сервер synth-nova-prod: 4 vCPU / 8 GB — ресурсов достаточно для PG.
Decision
Postgres через FOR UPDATE SKIP LOCKED как очередь задач. Отдельный queue broker не разворачиваем на MVP.
Alternatives Considered
Option A: RabbitMQ / Redis Streams
- Pros: правильный инструмент для очередей, богатые features (dead letter queue, priority, TTL), battle-tested at scale
- Cons: +1 сервис на сервере, +1 точка отказа, +1 набор секретов, overkill для MVP (<1000 tasks/day), усложняет transactional consistency (task в PG + queue state отдельно = two-phase commit проблема)
Option B: SQLite
- Pros: zero infra, файл, знакомый
- Cons:
SQLITE_BUSY_SNAPSHOTуже наступил в Комбинаторике, плохо масштабируется на concurrent writes, нетSKIP LOCKED, нет RLS для security
Option C: Postgres SKIP LOCKED ← chosen
- Pros: одна БД для данных + очередь, полная transactional consistency (task create + artifact save = one TX), capacity 1000s tasks/sec (более чем достаточно), встроен в существующую экосистему, RLS для security,
SELECTдля debugging - Cons: меньше features чем dedicated broker (priority через
ORDER BY, нет native dead letter queue), нагрузка на одну БД - Why chosen: PG уже используется для данных, нагрузка MVP не требует broker, проще поддерживать одному человеку. При необходимости — миграция на broker в Phase 2.
Consequences
Positive
- Единая БД simplifies deployment, backup, monitoring
- Transactional целостность: tasks ↔ artifacts ↔ runs в одной транзакции
- PG уже установлен на synth-nova-prod
- Debug через SQL:
SELECT * FROM tasks WHERE status = 'failed' ORDER BY created_at DESC
Negative / Trade-offs
- Нагрузка на одну БД растёт (data + queue + vector)
- При >100 req/sec возможна необходимость миграции на broker
- Нет native features: dead letter queue, message TTL (реализуем в коде)
Mitigations
- Индексы:
(status, to_agent, priority)для быстрого pickup SKIP LOCKEDeliminates contention between workers- Monitoring: queue depth alert при backpressure
- Revisit в Phase 2 если metrics покажут degradation
Follow-ups
- Миграция SQLite очереди из Комбинаторики на Postgres (починит SQLITE_BUSY)
- Benchmark: tasks/sec на текущем железе (target: >100 sustained)
- Мониторинг: queue depth + alert при > 100 pending
- Watchdog: timeout detection для stuck tasks
References
- Orchestration-Model — техническая реализация
- System-Overview — место в архитектуре
- https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE