docs: решено оставить очередь своей таблицей коллекцией PocketBase

- заведены записка разведки job-queue-choice и ADR: готовые библиотеки River и
  goqite отвергнуты, захват сворачивается в один запрос с RETURNING
- в architecture.md уточнён принцип «очередь таблицей» и закрыт открытый вопрос
  «Очередь», кроме холостого опроса
- задача pocketbase-storage забрала очередь себе: границы, счётчик попыток,
  состояние «мертва» и оракулы
This commit is contained in:
av
2026-08-11 14:13:27 +03:00
parent 6c996209b7
commit df65eb5e32
7 changed files with 248 additions and 20 deletions
@@ -0,0 +1,66 @@
# Очередь остаётся своей таблицей, но коллекцией PocketBase
- **Дата:** 2026-08-11
- **Источник:** [../research/job-queue.md](../research/job-queue.md) — записка
разведки `job-queue-choice`
## Решение
Очередь задач остаётся своей таблицей и становится коллекцией 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, отбирая фильтром.