# Очередь остаётся своей таблицей, но коллекцией PocketBase

- **Дата:** 2026-08-11
- **Источник:** [../research/job-queue.md](../research/job-queue.md) — записка
  разведки `job-queue-choice`
- **Статус:** заменено на [ADR-2026-08-22-storage-without-pocketbase](ADR-2026-08-22-storage-without-pocketbase.md)

## Решение

Очередь задач остаётся своей таблицей и становится коллекцией PocketBase: захват
идёт одним запросом с `RETURNING`, число попыток лежит колонкой, нарастающая
пауза выражается существующим `delay_time`, а исчерпавшая попытки задача
переходит в состояние «мертва» вместо сегодняшнего `is_error = 1`. Готовую
библиотеку очереди не берём.

## Почему

Разведка искала готовую очередь и нашла, что для PocketBase её нет:

> Единственная очередь в списке — `pocketbase-queue`, написана на TypeScript и
> работает из JS-хуков; из Go её не подключить.

Отсюда разрез, который и решил дело:

> выбор идёт не между готовым и своим, а между **своим в коллекции PocketBase** и
> **чужой очередью, живущей рядом с PocketBase и мимо её панели**. River и goqite
> про PocketBase не знают.

Главный довод в пользу чужой библиотеки — транзакционный захват — снялся
замером:

> движок за `modernc.org/sqlite` v1.55.0 — версии 3.53.3, `RETURNING` в нём
> есть, и на трёх горутинах разом запись получила **ровно одна**. Это снимает
> главный довод в пользу чужой библиотеки: транзакционность захвата покупается
> одной строкой запроса, а не новой зависимостью.

Отвергнуты два кандидата, и оба с названной ценой:

> **River с драйвером SQLite** — покупает повторы, счётчик и мёртвых готовыми, но
> выносит очередь из панели PocketBase, ради которой хранилище и переезжает, и
> переписывает конвейер в цепочку задач.
>
> **goqite** — не отвечает ни на один из трёх вопросов задачи целиком, а его
> предел выдач молча теряет запись.

Запись попадает в журнал как **намеренный отказ от очевидного подхода**: взять
готовую библиотеку вместо своего кода — первое, что предлагают на такой вопрос, и
без записанной причины его предложат снова. Принцип «очередь таблицей» из
[../architecture.md](../architecture.md) этим решением подтверждён, а не
пересмотрен, поэтому парного статуса «заменено на» никакая запись не получает.

## Последствия

- `+` очередь видна и правится в панели администратора: мёртвая задача повторяется
  снятием состояния, а не запросом в консоли сервера. Ровно за это и куплен
  перевод хранилища на PocketBase
  ([ADR-2026-08-11-pocketbase-storage-with-admin-panel](ADR-2026-08-11-pocketbase-storage-with-admin-panel.md)).
- `+` ни одной новой зависимости: River добавил бы 46 пакетов в сборку, goqite — 3.
- `+` захват перестаёт быть двумя запросами без транзакции, и это перестаёт быть
  местом, которое держится на том, что три воркера читают три разных состояния.
- `−` повторы, счётчик попыток и очередь мёртвых пишем сами, и корректность
  захвата наша. Проверяется это тестами задачи `pocketbase-storage`, а не
  чужим набором проверок.
- `−` захват идёт сырым запросом мимо записей PocketBase: хуки коллекции на нём
  не срабатывают, и поле времени изменения проставляет наш код.
- `−` приборной панели очереди — числа ждущих, упавших, среднего времени — не
  появляется. Смотрим таблицу коллекции в панели PocketBase, отбирая фильтром.
