Files
transcriber/docs/database.md
T
av 4d1c2bf44c Канон документов, каталог задач и OpenSpec
docs/ по канону 12: паспорт с целью проекта, архитектура сегодняшнего
устройства, схема хранилища, модель угроз, конвенции кода, журнал ревью.
Конвенции перенесены из jellybit; места, где код им не следует, помечены
строкой «Расхождение» как объявленный долг.

tasks/ с роадмапом: две достигнутые цели, две запланированные (веб и
многопользовательский режим), два направления (все форматы, долгие
записи) и пять задач в беклоге.

openspec/config.yaml — маршрутизатор с адресами документов, спек пока нет.

CLAUDE.md переписан по форме канона: инварианты с severity, семантика
гейта, запреты с путями. Taskfile получил task gate.
2026-08-10 21:19:07 +03:00

102 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Схема хранилища
СУБД, миграции, правило времени и идентификаторов.
СУБД — SQLite, драйвер `mattn/go-sqlite3` (нужен CGO). Запросы строит
`doug-martin/goqu` с диалектом `sqlite3`. Миграции — `pressly/goose`, каталог
`migrations/`, вшит в бинарник через `//go:embed migrations/*.sql` в `main.go` и
накатывается при старте. Новый файл достаточно положить в каталог.
**Идентификаторы** — UUID v4 строкой.
**Время** — локальная зона процесса, UTC не навязан. Колонки `created_at` и
`updated_at` проставляет приложение, а не СУБД; умолчание `CURRENT_TIMESTAMP`
стоит только у `files.created_at`.
Того, что единой точки генерации идентификатора и времени нет, здесь не
повторяем: перечень единых точек и их отсутствий держит
[architecture.md](architecture.md), «Единые точки проекта».
Переход на PocketBase запланирован, но не начат — здесь описано сегодняшнее
состояние. Открытые вопросы перехода — в
[architecture.md](architecture.md), раздел «Открытые вопросы».
## Таблицы
### `files`
Один файл на одну физическую копию: исходник, результат конвертации и копия в
Object Storage — три разные записи.
| Колонка | Тип | Что |
| --- | --- | --- |
| `id` | TEXT PK | UUID файла |
| `storage` | TEXT | `local` или `s3` |
| `file_name` | TEXT | Имя в хранилище: UUID с расширением |
| `size` | INTEGER | Размер в байтах |
| `created_at` | DATETIME | Умолчание `CURRENT_TIMESTAMP` |
### `transcribe_jobs`
Задача расшифровки и она же очередь.
| Колонка | Тип | Что |
| --- | --- | --- |
| `id` | TEXT PK | UUID задачи |
| `state` | TEXT | `created`, `converted`, `transcribe`, `done`, `failed` |
| `source` | TEXT | `api`, `telegram`, `unknown`; умолчание `unknown` |
| `file_id` | TEXT FK → `files.id` | **Текущий** файл задачи: шаг конвейера переставляет ссылку на свой результат |
| `delay_time` | DATETIME | Не брать задачу раньше этого времени |
| `acquisition_id` | TEXT | Кто захватил задачу |
| `acquire_time` | DATETIME | Когда захватил; по нему считается протухание |
| `recognition_op_id` | TEXT | Идентификатор операции в Yandex Cloud |
| `transcription_text` | TEXT | Результат распознавания |
| `is_error` | BOOLEAN | Задача с `1` из выборки исключена навсегда |
| `error_text` | TEXT | Текст ошибки, машинный |
| `tg_chat_id` | INTEGER | Куда отправить результат |
| `tg_reply_message_id` | INTEGER | С каким сообщением связать |
| `created_at`, `updated_at` | DATETIME | Проставляет приложение |
Индексов, кроме первичных ключей, нет. Выборка воркера идёт полным перебором по
`state`, `is_error`, `delay_time` и `acquire_time`.
## Представление данных
Чем физически лежит запись и что происходит при чтении и записи.
- **Расшифровка лежит целиком в колонке `transcription_text`** одной строкой.
Запись длиной в час даёт десятки килобайт в одной ячейке; читается она
целиком при каждом `GetByID` и при каждом захвате задачи воркером.
- **Аудио в базе не лежит.** На диске — каталог `data/files`, плоский, имя файла
равно UUID с расширением. Ни файлы, ни объекты в Object Storage не удаляются
после завершения задачи: каталог и бакет растут неограниченно.
- **Захват задачи — два запроса подряд, не транзакция.** Сперва `UPDATE …
WHERE id = (SELECT … LIMIT 1)` проставляет `acquisition_id`, затем отдельный
`SELECT … WHERE acquisition_id = ?` читает строку. Репозиторий сверяет число
затронутых строк с ожидаемым, но между запросами задачу может перехватить
другой воркер с тем же значением — на одном процессе это не наблюдалось.
- **Список колонок задан не одним местом** — четырьмя запросами файла
`internal/adapter/repo/sqlite/transcript_job_repo.go`. Правило правки всех
четырёх и его severity — инвариант в [CLAUDE.md](../CLAUDE.md), «Инварианты».
## Настройки с числовым значением
| Настройка | Значение | Где |
| --- | --- | --- |
| Срок захвата, конвертация и распознавание | 1 час | `service/transcribe.go`, вызовы `findJob` |
| Срок захвата, проверка операции | 24 часа | там же |
| Пауза воркера между попытками | 1 секунда | `controller/worker/worker.go` |
| Задержка перед первой проверкой операции | 10 секунд | `service/transcribe.go` |
| Задержка между проверками операции | 5 секунд | там же |
| Предел длины сообщения Telegram | 4000 символов | `adapter/telegram/sender.go` |
| Память под multipart-загрузку | 32 МиБ | `main.go`, `router.MaxMultipartMemory` |
| Таймаут мягкой остановки | 5 секунд | конфиг, `[server] shutdown_timeout` |
| Таймаут жёсткой остановки | 20 секунд | конфиг, `[server] force_shutdown_timeout` |
| Таймаут обновлений Telegram | 10 секунд | конфиг, `[telegram] update_timeout` |
| Качество кодирования vorbis | `-q:a 4` | `adapter/converter/ffmpeg/ffmpeg.go` |
Чего среди настроек **нет**: режим журналирования SQLite не задан (значение по
умолчанию, не WAL), таймаут занятости не задан, размер пула соединений не задан,
срока хранения файлов и объектов нет вовсе. Таймаутов у обращений к Telegram, S3 и
SpeechKit тоже нет — ни одного.