- заведены записка разведки job-queue-choice и ADR: готовые библиотеки River и goqite отвергнуты, захват сворачивается в один запрос с RETURNING - в architecture.md уточнён принцип «очередь таблицей» и закрыт открытый вопрос «Очередь», кроме холостого опроса - задача pocketbase-storage забрала очередь себе: границы, счётчик попыток, состояние «мертва» и оракулы
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
на обратном прокси: это работа выкладки, а не приложения.