- разведка storage-without-pocketbase: шесть ролей библиотеки в этом коде, отпавший довод перевода, шесть модулей достижимы только через неё - ADR-2026-08-22-storage-without-pocketbase заменяет три записи: перевод в PocketBase, очередь коллекцией, файл за защищённым полем - задача storage-without-pocketbase встала первой строкой плана стройки
68 lines
5.2 KiB
Markdown
68 lines
5.2 KiB
Markdown
# Очередь остаётся своей таблицей, но коллекцией 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, отбирая фильтром.
|