- база своя: два пула, захват одним UPDATE ... RETURNING, шаги схемы на goose под файловым замком, одна миграция начальной схемы вместо семи прежних - транспорт переписан на net/http: свои слои, свой ограничитель частоты, отдача файла с проверкой владельца; панель /_/ и пространство /api/ исчезли - по находкам ревью: журнал не пишет путь под корнем приложения, ключ бюджета читается справа налево, узнавание известного идёт читающим пулом
99 lines
8.1 KiB
Markdown
99 lines
8.1 KiB
Markdown
# Хранилищем становится SQLite с каталогом файлов, а PocketBase уходит целиком
|
||
|
||
- **Дата:** 2026-08-22
|
||
- **Источник:** [../research/storage-without-pocketbase.md](../research/storage-without-pocketbase.md) —
|
||
записка разведки о выборе хранилища
|
||
|
||
## Решение
|
||
|
||
PocketBase уходит из проекта целиком: состояние записей и метаданные переезжают
|
||
в SQLite, с которым сервис работает напрямую через `modernc.org/sqlite`, файлы
|
||
записей — в свой каталог со своей раскладкой, маршруты и слои — на `net/http`,
|
||
шаги схемы — на свой раннер. Панель администратора теряется и **не заменяется
|
||
ничем**: пока идёт стройка, остановленную запись возвращает в работу запрос к
|
||
базе.
|
||
|
||
**Два решения абзаца выше сменились при разметке изменения**, и заменившее
|
||
названо здесь.
|
||
|
||
Шаги схемы двигает библиотека `github.com/pressly/goose/v3`, а не свой раннер.
|
||
Инструмент выбрал владелец 2026-08-22: библиотека уже была в этом проекте и ушла
|
||
вместе с PocketBase, а из трёх норм, которые накат обязан выполнять, две
|
||
выполняет сама.
|
||
|
||
Остановленную запись возвращает в работу подкоманда `cmd/devtools resume`, а не
|
||
запрос к базе руками. Возврат сбрасывает не одно поле записи и пишет событие
|
||
журнала с происхождением `entity.EventOriginHuman`; рука за клавиатурой не делает
|
||
ни того, ни другого. Последствие ниже — «возврат остановленной в работу […]
|
||
делает запрос к базе руками» — читается этой сменой.
|
||
|
||
Доводы обоих решений записаны в
|
||
[design.md](../../openspec/changes/archive/2026-08-23-storage-without-pocketbase/design.md),
|
||
разделы «Шаги схемы двигает `goose`, а не свой раннер» и «Панель не заменяется
|
||
ничем, а возврат в работу делает подкоманда оснастки».
|
||
|
||
Запись заменяет три:
|
||
[ADR-2026-08-11-pocketbase-storage-with-admin-panel](ADR-2026-08-11-pocketbase-storage-with-admin-panel.md),
|
||
[ADR-2026-08-11-queue-as-pocketbase-collection](ADR-2026-08-11-queue-as-pocketbase-collection.md) и
|
||
[ADR-2026-08-12-protected-file-behind-session](ADR-2026-08-12-protected-file-behind-session.md).
|
||
|
||
**Что из заменённых решений подтверждается, а не отменяется.** Очередь остаётся
|
||
своей таблицей, захват — одним запросом с `RETURNING`, готовую библиотеку
|
||
очереди по-прежнему не берём: замер, которым это решено, снят на
|
||
`modernc.org/sqlite` — том самом драйвере, который остаётся и после ухода.
|
||
Отменяется у той записи одно слово: таблица перестаёт быть коллекцией.
|
||
Приложение остаётся в своём корне `/app/`
|
||
([ADR-2026-08-15-app-namespace](ADR-2026-08-15-app-namespace.md)). Файл записи
|
||
остаётся закрытым — но проверкой владельца в своём обработчике, а не защищённым
|
||
полем коллекции и коротким токеном.
|
||
|
||
## Почему
|
||
|
||
Разведка мерила не «хранилище против хранилища», а то, что библиотека держит в
|
||
этом коде. Цитата из источника:
|
||
|
||
> Разрез «хранилище против хранилища» вопроса не покрывает: библиотека держит
|
||
> шесть ролей сразу, и только две из них про хранение.
|
||
|
||
Довод, на котором стоял перевод, отпал сам. Источник, раздел о трёх доводах:
|
||
|
||
> Вход делает само приложение с 2026-08-22 […]: пришедшего называет заголовок
|
||
> прокси, а учётную запись заводит наш `EnsureUser`. Пользователи в коллекции
|
||
> есть **потому, что их пишет наш код**, а не провайдер библиотеки. Довод,
|
||
> которым отвергнут отвергнутый вариант, перестал быть верным.
|
||
|
||
Отвергнут вариант «уйти в два шага», оставив панель жить в промежутке, и
|
||
отвергнут решением владельца: панель на стройке заменяется запросом к базе, а
|
||
вторая порция работы стоит дороже, чем то, что она сберегает.
|
||
|
||
Обстоятельство, которое назначило момент:
|
||
|
||
> на сервере данных нет и сервис остановлен, поэтому смена стоит только кода.
|
||
> Дешевле она не станет никогда — каталог `internal/controller/http` прирастает
|
||
> кодом на чужих типах с каждой задачей.
|
||
|
||
## Последствия
|
||
|
||
- `+` периметр сервиса становится только нашим. Панель `/_/` исчезает вместе с
|
||
дефектом `/%5f/` из [../security.md](../security.md), а пространство
|
||
хранилища `/api/` — вместе с необходимостью держать его открытым ради файлов.
|
||
- `+` пропадает секрет, которого не было до перевода, — пароль суперпользователя
|
||
панели.
|
||
- `+` файлы ложатся своей раскладкой, и загрузка частями, узнавание по хеш-сумме,
|
||
удаление записи и вторая копия рядом становятся обычной работой с файлами.
|
||
- `+` из сборки уходят шесть модулей, достижимых только через библиотеку:
|
||
`imaging`, `mailyak`, `jwt`, `fexpr`, `cobra`, драйвер MySQL.
|
||
`modernc.org/sqlite` остаётся, и сборка по-прежнему обходится без CGO.
|
||
- `−` владелец сервиса остаётся без панели. Правку записи, возврат остановленной
|
||
в работу и просмотр очереди до появления экранов делает запрос к базе руками.
|
||
Задачи `audiorecord-actions` и `play-recording-in-app` этим становятся не
|
||
улучшением, а заменой утраченного инструмента.
|
||
- `−` шаги схемы, отдачу файла, ограничитель частоты и настройку базы пишем и
|
||
сопровождаем сами. Единственный писатель у `modernc.org/sqlite` — наша забота
|
||
с этого дня.
|
||
- `−` раскладка каталога данных меняется необратимо. Цена сегодня нулевая:
|
||
стройка, на сервере пусто; после первой боевой записи она перестаёт быть
|
||
нулевой.
|
||
- `−` 1497 строк контроллера и 3075 строк его проверок написаны на
|
||
`*core.RequestEvent` и переписываются целиком.
|