Files
transcriber/docs/research/pocketbase-defaults.md
T
av 01cc31d45f хранилище, файлы записей и очередь переведены на встроенную PocketBase
- записи, метаданные и файлы съехались под один каталог данных; появилась
  панель владельца, а gin, goqu, goose и требование CGO ушли
- захват задачи стал одним запросом с RETURNING; заведены число попыток,
  состояние dead и нарастающая пауза вместо признака is_error
- имя файла в хранилище задаёт сервис и в журнал не идёт: вместе с
  идентификатором записи оно собирало бы ссылку на скачивание
2026-08-12 08:31:59 +03:00

6.6 KiB

PocketBase: умолчания, которые ломают штатный сценарий

Наблюдения, снятые по ходу задачи pocketbase-storage уже на своём коде. От записки разведки отличаются предметом: та мерила, что даёт панель, эта — что библиотека делает молча, если её не переубедить.

Все четыре наблюдения нашлись ревью, а не чтением документации: три из них выглядят как «значение по умолчанию — нет ограничения», а значат обратное.

Как снималось

Версия 0.39.10, та же, что у первой записки. Прогоны — на пустом каталоге данных во временном каталоге и на поднятом сервере 127.0.0.1:18099; боевые данные и ключи не участвовали. Числа ниже сняты 2026-08-11 и 2026-08-12.

Нулевой потолок у поля файла значит 5 МиБ, а не «без предела»

&core.FileField{MaxSize: 0} читается библиотекой как её собственное умолчание:

core/field_file.go:28   const DefaultFileFieldMaxSize int64 = 5 << 20
core/field_file.go:310  if f.MaxSize <= 0 { return DefaultFileFieldMaxSize }

Проверено укладкой: файл в 6 МиБ отвергается на сохранении записи — the maximum allowed file size is 5242880 bytes. Прогон через боевой роутер дал границу дословно:

тело запроса ответ
4 194 304 байта 201
5 238 784 байта 201
5 246 976 байт 500
34 603 008 байт 413

Цена для сервиса: 5 МиБ — это примерно 5,5 минут mp3 при 128 кбит/с. Отвергалась бы не только длинная запись на приёме: результат конвертации в ogg переваливает тот же порог примерно на пятой минуте, и уже принятая задача исчерпывала бы попытки на шаге конвертации.

Тело запроса режется на 32 МиБ раньше обработчика

apis/base.go:36 вешает BodyLimit(DefaultMaxBodySize) на корневой роутер, то есть и на чужие маршруты; apis/middlewares_body_limit.go:14const DefaultMaxBodySize int64 = 32 << 20. Ответ 413 уходит мимо обработчика, без строки в журнале приёма (последняя строка таблицы выше).

Снимается на маршруте: .Bind(apis.BodyLimit(<своё число>)).

Таймаут чтения запроса — пять минут

apis/serve.go:151 ставит ReadTimeout: 5 * time.Minute. Заливка шестичасовой записи по медленному каналу его переживает: соединение рвётся на середине. Снимается в хуке OnServese.Server.ReadTimeout = 0.

Суффикс к имени файла дописывает конструктор, а не укладка

Первая записка наблюдала sample.ogg → sample_uztrv6wvz3.ogg и читала это как свойство хранилища. Наблюдение верно только когда имя строит сама библиотека: десять случайных знаков добавляет normalizeName, вызываемый из filesystem.NewFileFrom*. Имя, положенное в поле File.Name после конструктора, ложится на диск дословно:

задано  11111111-2222-3333-4444-555555555555.mp3
на диске 11111111-2222-3333-4444-555555555555.mp3

Цена: тот, кто задаёт имя сам, не получает от суффикса никакой неугадываемости — и защищать ссылку на файл ему приходится другим.

Хук правки записи не различает, кто пишет

app.OnRecordUpdate(<коллекция>) — событие модели: оно срабатывает на каждом app.Save, включая сохранение из собственного кода. Хук, написанный «для панели», правил записи конвейера: проверено прогоном — задержка, поставленная шагом вместе со сменой состояния, обнулялась тем же сохранением.

Различает источник app.OnRecordUpdateRequest(<коллекция>): оно поднимается только на правку запросом, а код, пишущий мимо HTTP-слоя, под него не попадает.

Приглашение завести владельца панели живёт полчаса

apis/installer.go:31systemSuperuser.NewStaticAuthToken(30 * time.Minute); печатается только пока владельца нет (needInstallerSuperuser). Проверено прогоном: при первом запуске строка со ссылкой в журнале есть, после заведения владельца при следующем запуске её нет.

Чего эта записка не узнала

  • Во что обходится потолок в 8 ГиБ на диске. Число выбрано расчётом из шестичасовой записи с запасом на видео, а не замером: настоящего распределения длин у сервиса нет.
  • Как ведёт себя укладка файла в несколько гигабайт. Самая длинная проверенная запись — 9,6 МБ (десять минут mp3). Потоковую укладку это подтверждает, предел — нет.
  • Поведение под одновременной правкой панели и конвейера в бою. Проверено тестом на одной машине, не живой нагрузкой.