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
+1 -1
View File
@@ -24,7 +24,7 @@
- [🐞 Убирать записанный файл, когда приём отказал на середине](items/orphan-file-on-failed-intake.md) — Отказ чтения метаданных и отказ записи на диск оставляют файл в каталоге хранения без задачи и без учёта: сопоставить его не с чем, удалять приходится руками.
- [🧹 Задать таймауты обращениям к внешним сервисам](items/external-call-timeouts.md) — Ни у Telegram, ни у Object Storage, ни у SpeechKit нет таймаута: молчащий собеседник держит шаг конвейера до истечения часового захвата.
- [🔬 Очередь задач: своя таблица или готовая библиотека](items/job-queue-choice.md) — Очередь написана вручную: захват двумя запросами без транзакции, протухание временем, опрос раз в секунду вхолостую тремя воркерами.
- [🧹 Перевести хранилище и файлы записей на встроенный PocketBase](items/pocketbase-storage.md) — Записи, метаданные и файлы лежат порознь, и владелец не видит их ничем, кроме консоли на сервере: панель PocketBase покажет и то и другое, только если они переедут к ней.
- [🧹 Перевести хранилище и файлы записей на встроенный PocketBase](items/pocketbase-storage.md) — Записи, метаданные и файлы лежат порознь, и владелец не видит их ничем, кроме консоли на сервере: панель PocketBase покажет их, только если они переедут к ней. Заодно переписывается захват задачи: сегодня это два запроса без транзакции, а падающая всегда задача падает вечно.
- [✨ Пускать в приложение только после входа через OIDC](items/oidc-login.md) — HTTP API открыт наружу без аутентификации: любой из интернета заводит задачи за наши деньги и читает чужие расшифровки по идентификатору.
- [✨ Привязать запись к владельцу и отдавать только свои](items/record-ownership.md) — У задачи и файла нет владельца, поэтому знание UUID задачи и есть право её читать.
- [✨ Сопоставить пользователя Telegram с учётной записью](items/telegram-account-link.md) — Белый список сверяется с именем пользователя Telegram, которое владелец меняет в любой момент, а записи из бота ни с кем не связаны.
+24 -9
View File
@@ -2,7 +2,7 @@
- **Тип:** chore
- **Категория:** Очередь
- **Зачем:** Записи, метаданные и файлы лежат порознь, и владелец не видит их ничем, кроме консоли на сервере: панель PocketBase покажет и то и другое, только если они переедут к ней.
- **Зачем:** Записи, метаданные и файлы лежат порознь, и владелец не видит их ничем, кроме консоли на сервере: панель PocketBase покажет их, только если они переедут к ней. Заодно переписывается захват задачи: сегодня это два запроса без транзакции, а падающая всегда задача падает вечно.
PocketBase встраивается библиотекой в тот же бинарник и приносит хранилище,
учётные записи и панель администратора разом. Конвейер и оба входа работают
@@ -20,12 +20,22 @@ PocketBase встраивается библиотекой в тот же бин
Данные не переносим — база заводится с чистого листа, и это решение принято
сознательно.
**Очередь переписывается тем же заходом.** Разведка `job-queue-choice` 2026-08-11
[отвергла готовые библиотеки](../../docs/adr/ADR-2026-08-11-queue-as-pocketbase-collection.md):
очередь остаётся своей таблицей, но становится коллекцией PocketBase. Захват
сворачивается в один запрос с `RETURNING`, число попыток ложится колонкой,
нарастающая пауза выражается существующим `delay_time`, а исчерпавшая попытки
задача переходит в состояние «мертва» вместо `is_error = 1`. Сравнение
кандидатов — [docs/research/job-queue.md](../../docs/research/job-queue.md).
## Затрагивает
- таблицы `files` и `transcribe_jobs`, каталог `migrations/` и весь механизм
goose;
- `internal/adapter/repo/sqlite` целиком, включая захват задачи через
`FindAndAcquire`;
- состав колонок очереди: прибавляется число попыток, а `is_error` уступает
место состоянию «мертва» в перечне состояний задачи;
- `internal/contract`, интерфейсы `FileRepository` и `TranscriptJobRepository`;
- ключ конфигурации `[database] path`, ключ `[storage] path` и раскладка
каталога `data/`;
@@ -47,14 +57,19 @@ PocketBase встраивается библиотекой в тот же бин
- Сервис поднимается на чистом каталоге данных, накатывает свою схему сам и
принимает запись обоими входами. Оракул — запуск на пустом `data/` и прогон
записи из Telegram и через `POST /api/audio` до состояния `done`.
- Захват задачи воркером не выдаёт одну запись двум вызывающим. Оракул — тест
на трёх параллельных вызовах захвата по одному состоянию: ровно один
получает запись.
- Задача, брошенная на середине, достаётся снова по истечении срока захвата.
Оракул — тест с проставленным задним числом `acquire_time`.
- Принятая запись видна в панели строкой и скачивается из неё файлом. Оракул —
прогон записи через `POST /api/audio` на пустом каталоге, затем запрос
`/api/files/...` за тем же файлом: длина совпадает с загруженной.
- Захват задачи воркером идёт одним запросом и не выдаёт одну запись двум
вызывающим. Оракул — тест на трёх параллельных вызовах захвата по одному
состоянию: ровно один получает запись.
- Задача, брошенная на середине, достаётся снова по истечении срока захвата, а
падающая всегда — уходит в «мертва» и из выборки исчезает. Оракулы — тест с
проставленным задним числом `acquire_time` и тест с шагом, падающим на каждой
попытке: после заданного их числа задача не выдаётся, а её состояние видно
отбором.
- Принятая запись видна в панели строкой и скачивается по ссылке
`/api/files/...` тем же файлом. Оракулы — прогон записи через
`POST /api/audio` на пустом каталоге, затем поиск её строки в коллекции задач
на `/_/` по идентификатору и запрос `/api/files/...` за тем же файлом: длина
совпадает с загруженной.
- `docs/database.md` описывает новую схему, а старые упоминания goose и goqu из
документов канона убраны. Оракул — `task gate`, шаг `docs.py check`.