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 LOCKED eliminates 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