Files
transcriber/tasks/items/pocketbase-storage.md
T
av df65eb5e32 docs: решено оставить очередь своей таблицей коллекцией PocketBase
- заведены записка разведки job-queue-choice и ADR: готовые библиотеки River и
  goqite отвергнуты, захват сворачивается в один запрос с RETURNING
- в architecture.md уточнён принцип «очередь таблицей» и закрыт открытый вопрос
  «Очередь», кроме холостого опроса
- задача pocketbase-storage забрала очередь себе: границы, счётчик попыток,
  состояние «мертва» и оракулы
2026-08-11 14:13:27 +03:00

7.4 KiB

🧹 Перевести хранилище и файлы записей на встроенный PocketBase

  • Тип: chore
  • Категория: Очередь
  • Зачем: Записи, метаданные и файлы лежат порознь, и владелец не видит их ничем, кроме консоли на сервере: панель PocketBase покажет их, только если они переедут к ней. Заодно переписывается захват задачи: сегодня это два запроса без транзакции, а падающая всегда задача падает вечно.

PocketBase встраивается библиотекой в тот же бинарник и приносит хранилище, учётные записи и панель администратора разом. Конвейер и оба входа работают по-прежнему, а снаружи прибавляется панель по адресу /_/.

Разведка pocketbase-admin-fit довод проверила, и 2026-08-11 принято решение перевести хранилище вместе с файлами: панель показывает файлы и пользователей только тех, что лежат у неё, а половина перевода довода не окупает. Замер — docs/research/pocketbase.md.

Файлы переезжают в раскладку PocketBase, а плоского каталога data/files с именами-UUID не остаётся. Это необратимо, и момент перехода назначает человек. Вход в этой задаче не трогаем: его переводит oidc-login.

Данные не переносим — база заводится с чистого листа, и это решение принято сознательно.

Очередь переписывается тем же заходом. Разведка job-queue-choice 2026-08-11 отвергла готовые библиотеки: очередь остаётся своей таблицей, но становится коллекцией PocketBase. Захват сворачивается в один запрос с RETURNING, число попыток ложится колонкой, нарастающая пауза выражается существующим delay_time, а исчерпавшая попытки задача переходит в состояние «мертва» вместо is_error = 1. Сравнение кандидатов — 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/;
  • формат файла на диске: запись переезжает в поле коллекции, путь становится pb_data/storage/<коллекция>/<запись>/<имя>_<10 случайных символов> рядом с файлом атрибутов. Имя, данное отправителем, впервые попадает в путь целиком;
  • отдача файла: вместо чтения с диска — ссылка /api/files/..., и решение, помечать ли поле защищённым;
  • сборка образа: PocketBase тянет свой набор зависимостей, а mattn/go-sqlite3 с его требованием CGO уходит — библиотека ходит в SQLite через modernc.org/sqlite;
  • пароль суперпользователя панели: где заводится и как попадает на сервер;
  • docs/database.md — схема, представление данных и таблица настроек;
  • CLAUDE.md — строка стека про CGO и раздел про запреты с путями;
  • docs/security.md — раздел «Из чего строятся пути и ключи».

Критерии приёмки

  • Сервис поднимается на чистом каталоге данных, накатывает свою схему сам и принимает запись обоими входами. Оракул — запуск на пустом data/ и прогон записи из Telegram и через POST /api/audio до состояния done.
  • Захват задачи воркером идёт одним запросом и не выдаёт одну запись двум вызывающим. Оракул — тест на трёх параллельных вызовах захвата по одному состоянию: ровно один получает запись.
  • Задача, брошенная на середине, достаётся снова по истечении срока захвата, а падающая всегда — уходит в «мертва» и из выборки исчезает. Оракулы — тест с проставленным задним числом acquire_time и тест с шагом, падающим на каждой попытке: после заданного их числа задача не выдаётся, а её состояние видно отбором.
  • Принятая запись видна в панели строкой и скачивается по ссылке /api/files/... тем же файлом. Оракулы — прогон записи через POST /api/audio на пустом каталоге, затем поиск её строки в коллекции задач на /_/ по идентификатору и запрос /api/files/... за тем же файлом: длина совпадает с загруженной.
  • docs/database.md описывает новую схему, а старые упоминания goose и goqu из документов канона убраны. Оракул — task gate, шаг docs.py check.

Рамки

Данные прежней базы не переносим и не пытаемся сохранить; выкладку не запускаем; смена формата хранения на сервере необратима, и момент перехода назначает человек. Вход не трогаем — он на oidc-login. Панель наружу закрывает Authelia на обратном прокси: это работа выкладки, а не приложения.