Пересмотр очереди, выводы из текста, наблюдаемость
Разведка job-queue-choice — очередь написана вручную: захват не транзакционен, повторов и счётчика попыток нет, очереди мёртвых задач нет. Стоит перед сменой хранилища, чтобы не переписывать захват дважды. Цель text-insights: заголовок, пересказ и темы внешним сервисом с OpenAI-совместимым интерфейсом. Граница паспорта сдвинута — «понимание сказанного» было записано как то, чем проект не является; за границей остались ответы на вопросы по записи и поиск по смыслу. Цель service-observability в сопровождении и разведка opentelemetry-fit: /metrics остаётся и развивается, способ решает замер.
This commit is contained in:
@@ -138,3 +138,16 @@
|
|||||||
сервис определяет содержимое сам, то ли часть записей теряется на этом.
|
сервис определяет содержимое сам, то ли часть записей теряется на этом.
|
||||||
- **Видео.** Дорожку из видеофайла бот принимает по MIME-типу `video/`, но
|
- **Видео.** Дорожку из видеофайла бот принимает по MIME-типу `video/`, но
|
||||||
конвертер этот случай не проверялся.
|
конвертер этот случай не проверялся.
|
||||||
|
- **Очередь.** Принцип «очередь таблицей» и захват `FindAndAcquire` написаны
|
||||||
|
вручную: захват не транзакционен, повторов с нарастающей паузой нет, число
|
||||||
|
попыток не считается, очереди мёртвых задач нет. Пересматривается разведкой
|
||||||
|
`job-queue-choice` — раньше смены хранилища, чтобы не переписывать захват
|
||||||
|
дважды.
|
||||||
|
- **Наблюдаемость.** `/metrics` остаётся и развивается. Чем — дописывать
|
||||||
|
счётчики через `client_golang` или перейти на OpenTelemetry с трассировкой —
|
||||||
|
решает разведка `opentelemetry-fit`. Коллектор был бы процессом, которого в
|
||||||
|
выкладке сегодня нет.
|
||||||
|
- **Выводы из текста.** Заголовок, пересказ и темы решено считать внешним
|
||||||
|
сервисом с OpenAI-совместимым интерфейсом. Появляется пятая внешняя
|
||||||
|
зависимость, платная, и текст расшифровки начинает уходить ещё на одну
|
||||||
|
сторону — сдвиг периметра [security.md](security.md).
|
||||||
|
|||||||
+6
-4
@@ -38,10 +38,12 @@
|
|||||||
и поиском по прошлым записям сервис не становится. Сколько запись лежит на
|
и поиском по прошлым записям сервис не становится. Сколько запись лежит на
|
||||||
диске после обработки — вопрос срока хранения, а его нет вовсе:
|
диске после обработки — вопрос срока хранения, а его нет вовсе:
|
||||||
[database.md](database.md), «Представление данных».
|
[database.md](database.md), «Представление данных».
|
||||||
- **Понимание сказанного.** Пересказ, выжимка, ответы на вопросы по записи, поиск
|
- **Разговор о записи.** Ответы на вопросы по содержанию и поиск по смыслу — за
|
||||||
по смыслу — за границей: мы отдаём текст, а не выводы из него.
|
границей. Заголовок, пересказ и темы **внутри** границы: она сдвинута
|
||||||
- **Собственное распознавание.** Модель не обучаем и не держим у себя, речь
|
2026-08-10, и до того запись читалась «мы отдаём текст, а не выводы из него».
|
||||||
распознаёт внешний сервис.
|
Направление — цель [text-insights](../tasks/items/text-insights.md).
|
||||||
|
- **Собственные модели.** Не обучаем и не держим у себя ни модель распознавания,
|
||||||
|
ни языковую модель: и речь, и выводы из текста считает внешний сервис.
|
||||||
- **Управление учётными записями.** Пользователей заводит и проверяет внешний
|
- **Управление учётными записями.** Пользователей заводит и проверяет внешний
|
||||||
провайдер, свою регистрацию и свои пароли не делаем.
|
провайдер, свою регистрацию и свои пароли не делаем.
|
||||||
- **Живая расшифровка.** Работаем с готовой записью, поток в реальном времени не
|
- **Живая расшифровка.** Работаем с готовой записью, поток в реальном времени не
|
||||||
|
|||||||
@@ -113,6 +113,7 @@
|
|||||||
- изменение, трогающее конвейер задач целиком: состояние, воркер, шаг сервиса и
|
- изменение, трогающее конвейер задач целиком: состояние, воркер, шаг сервиса и
|
||||||
колонку разом;
|
колонку разом;
|
||||||
- замена хранилища или переход на PocketBase — любой её кусок;
|
- замена хранилища или переход на PocketBase — любой её кусок;
|
||||||
|
- смена модели очереди: захват, повторы и воркеры разом;
|
||||||
- каркас приложения: сборка фронтенда, раздача статики и шаг гейта разом;
|
- каркас приложения: сборка фронтенда, раздача статики и шаг гейта разом;
|
||||||
- изменение, трогающее оба входа сразу — Telegram и HTTP.
|
- изменение, трогающее оба входа сразу — Telegram и HTTP.
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
- [🐞 Починить тесты http-обработчика, ни разу не бывшие зелёными](items/http-handler-tests-never-green.md) — go test ./... падает на master: тесты требуют файла, которого нет в репозитории, и ждут 201 от ffprobe, которому скормили строку.
|
- [🐞 Починить тесты http-обработчика, ни разу не бывшие зелёными](items/http-handler-tests-never-green.md) — go test ./... падает на master: тесты требуют файла, которого нет в репозитории, и ждут 201 от ffprobe, которому скормили строку.
|
||||||
- [🧹 Сравнивать доменные ошибки через errors.As](items/errors-as-instead-of-typecast.md) — NoopJobError и JobNotFoundError проверяются приведением типа: первая же обёртка %w между слоями сломает проверку молча.
|
- [🧹 Сравнивать доменные ошибки через errors.As](items/errors-as-instead-of-typecast.md) — NoopJobError и JobNotFoundError проверяются приведением типа: первая же обёртка %w между слоями сломает проверку молча.
|
||||||
- [🧹 Задать таймауты обращениям к внешним сервисам](items/external-call-timeouts.md) — Ни у Telegram, ни у Object Storage, ни у SpeechKit нет таймаута: молчащий собеседник держит шаг конвейера до истечения часового захвата.
|
- [🧹 Задать таймауты обращениям к внешним сервисам](items/external-call-timeouts.md) — Ни у Telegram, ни у Object Storage, ни у SpeechKit нет таймаута: молчащий собеседник держит шаг конвейера до истечения часового захвата.
|
||||||
|
- [🔬 Очередь задач: своя таблица или готовая библиотека](items/job-queue-choice.md) — Очередь написана вручную: захват двумя запросами без транзакции, протухание временем, опрос раз в секунду вхолостую тремя воркерами.
|
||||||
- [🧹 Перевести хранилище на встроенный PocketBase](items/pocketbase-storage.md) — Хранилище, учётные записи и веб-панель нужны все три, и SQLite с goqu не даёт ни второго, ни третьего.
|
- [🧹 Перевести хранилище на встроенный PocketBase](items/pocketbase-storage.md) — Хранилище, учётные записи и веб-панель нужны все три, и SQLite с goqu не даёт ни второго, ни третьего.
|
||||||
- [✨ Пускать в приложение только после входа через OIDC](items/oidc-login.md) — HTTP API открыт наружу без аутентификации: любой из интернета заводит задачи за наши деньги и читает чужие расшифровки по идентификатору.
|
- [✨ Пускать в приложение только после входа через OIDC](items/oidc-login.md) — HTTP API открыт наружу без аутентификации: любой из интернета заводит задачи за наши деньги и читает чужие расшифровки по идентификатору.
|
||||||
- [✨ Привязать запись к владельцу и отдавать только свои](items/record-ownership.md) — У задачи и файла нет владельца, поэтому знание UUID задачи и есть право её читать.
|
- [✨ Привязать запись к владельцу и отдавать только свои](items/record-ownership.md) — У задачи и файла нет владельца, поэтому знание UUID задачи и есть право её читать.
|
||||||
@@ -34,4 +35,5 @@
|
|||||||
- [✨ Сделать экран списка своих записей и чтения текста](items/records-list-screen.md) — Расшифровка сегодня доходит одним сообщением и теряется в переписке; вернуться к ней через неделю нечем.
|
- [✨ Сделать экран списка своих записей и чтения текста](items/records-list-screen.md) — Расшифровка сегодня доходит одним сообщением и теряется в переписке; вернуться к ней через неделю нечем.
|
||||||
- [✨ Сделать приложение устанавливаемым на телефон](items/installable-pwa.md) — Приложение, живущее вкладкой браузера, теряется среди прочих: ярлыка на экране у него нет.
|
- [✨ Сделать приложение устанавливаемым на телефон](items/installable-pwa.md) — Приложение, живущее вкладкой браузера, теряется среди прочих: ярлыка на экране у него нет.
|
||||||
- [✨ Отправлять готовый текст через apprise и ntfy](items/ntfy-delivery.md) — Пользователь веба узнаёт о готовности только опросом с открытого экрана.
|
- [✨ Отправлять готовый текст через apprise и ntfy](items/ntfy-delivery.md) — Пользователь веба узнаёт о готовности только опросом с открытого экрана.
|
||||||
|
- [🔬 Стоит ли брать OpenTelemetry вместо голого Prometheus](items/opentelemetry-fit.md) — Метрик одиннадцать штук на пять счётчиков, трассировки нет вовсе: путь одной записи по конвейеру собирается только чтением логов глазами.
|
||||||
- [🔬 Потолки SpeechKit по длине записи и по формату](items/speechkit-limits.md) — Потолок длины записи и перечень принимаемых форматов неизвестны, а цель про долгие записи без них не начинается.
|
- [🔬 Потолки SpeechKit по длине записи и по формату](items/speechkit-limits.md) — Потолок длины записи и перечень принимаемых форматов неизвестны, а цель про долгие записи без них не начинается.
|
||||||
|
|||||||
@@ -30,9 +30,12 @@
|
|||||||
|
|
||||||
- [🎯 Принимается запись любого формата, включая дорожку из видео](items/any-audio-source.md) — Конвертер вызывается одной командой ffmpeg, проверенной на голосовых Telegram; что он берёт помимо них, никто не мерил.
|
- [🎯 Принимается запись любого формата, включая дорожку из видео](items/any-audio-source.md) — Конвертер вызывается одной командой ffmpeg, проверенной на голосовых Telegram; что он берёт помимо них, никто не мерил.
|
||||||
- [🎯 Запись длиной в несколько часов доходит до текста](items/long-recordings.md) — Потолок не замерен ни на одном звене: Telegram не отдаёт больше 20 МиБ, а границы модели deferred-general неизвестны.
|
- [🎯 Запись длиной в несколько часов доходит до текста](items/long-recordings.md) — Потолок не замерен ни на одном звене: Telegram не отдаёт больше 20 МиБ, а границы модели deferred-general неизвестны.
|
||||||
|
- [🎯 Приложение показывает, о чём запись, не читая её целиком](items/text-insights.md) — Расшифровка часового разговора — это стена текста: найти в списке нужную запись и вспомнить, о чём она, сегодня нечем.
|
||||||
|
|
||||||
## Сопровождение
|
## Сопровождение
|
||||||
|
|
||||||
|
- [🎯 Состояние сервиса видно без чтения логов](items/service-observability.md) — Отказ замечает пользователь, а не владелец: оповещения нет, а путь записи по конвейеру собирается глазами по логам контейнера.
|
||||||
|
|
||||||
## Готово
|
## Готово
|
||||||
|
|
||||||
- 2025-08-14 `telegram-transcription` — Голосовое сообщение из Telegram возвращается текстом. Основной вход сервиса: бот принимает голосовое, аудиофайл и документ с аудио и отвечает расшифровкой.
|
- 2025-08-14 `telegram-transcription` — Голосовое сообщение из Telegram возвращается текстом. Основной вход сервиса: бот принимает голосовое, аудиофайл и документ с аудио и отвечает расшифровкой.
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# 🔬 Очередь задач: своя таблица или готовая библиотека
|
||||||
|
|
||||||
|
- **Тип:** research
|
||||||
|
- **Категория:** Очередь
|
||||||
|
- **Зачем:** Очередь написана вручную: захват двумя запросами без транзакции, протухание временем, опрос раз в секунду вхолостую тремя воркерами.
|
||||||
|
|
||||||
|
Сегодняшняя очередь — принцип «очередь таблицей» из
|
||||||
|
[architecture.md](../../docs/architecture.md) плюс `FindAndAcquire` в
|
||||||
|
репозитории. Работает она на единицах записей в день, но написана целиком
|
||||||
|
своими руками, и вот чего в ней нет:
|
||||||
|
|
||||||
|
- **захват не транзакционен** — `UPDATE` проставляет `acquisition_id`, отдельный
|
||||||
|
`SELECT` читает строку по нему; сегодня это безопасно только потому, что три
|
||||||
|
воркера читают три разных состояния и одну строку не делят;
|
||||||
|
- **повторов с нарастающей паузой нет** — задача либо повторяется каждую
|
||||||
|
секунду, либо ждёт истечения захвата целый час;
|
||||||
|
- **число попыток не считается** — задача, падающая всегда, падает вечно;
|
||||||
|
- **опрос вхолостую** — три воркера дёргают базу раз в секунду независимо от
|
||||||
|
того, есть ли работа;
|
||||||
|
- **очереди мёртвых задач нет** — `is_error = 1` исключает запись из выборки
|
||||||
|
навсегда и молча.
|
||||||
|
|
||||||
|
Разведка идёт **перед** `pocketbase-storage`: смена хранилища перепишет захват
|
||||||
|
задачи в любом случае, и переписывать его дважды незачем.
|
||||||
|
|
||||||
|
## Вопрос
|
||||||
|
|
||||||
|
Берём ли готовую очередь на Go поверх той же базы, и если да — какую, или
|
||||||
|
оставляем свою таблицу, дописав к ней повторы, счётчик попыток и очередь мёртвых
|
||||||
|
задач.
|
||||||
|
|
||||||
|
## Куда ляжет ответ
|
||||||
|
|
||||||
|
`docs/research/job-queue.md` — сравнение кандидатов с числами: зависимости,
|
||||||
|
какая база нужна, переживает ли перезапуск, есть ли повторы и очередь мёртвых
|
||||||
|
задач. Решение и причина отказа от остальных — в `docs/adr/`: принцип «очередь
|
||||||
|
таблицей» записан в архитектуре, и его пересмотр обратной правкой не
|
||||||
|
откатывается. Следом правится раздел «Принципы» в
|
||||||
|
[architecture.md](../../docs/architecture.md).
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Внешнего брокера (Redis, RabbitMQ, NATS) не рассматриваем: он добавляет к
|
||||||
|
выкладке процесс, которого там нет, ради нагрузки в единицы записей в день.
|
||||||
|
Кандидаты — библиотеки, работающие поверх той же встроенной базы. Ответ обязан
|
||||||
|
учесть, что хранилище скоро сменится на PocketBase.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# 🔬 Стоит ли брать OpenTelemetry вместо голого Prometheus
|
||||||
|
|
||||||
|
- **Тип:** research
|
||||||
|
- **Категория:** Очередь
|
||||||
|
- **Зачем:** Метрик одиннадцать штук на пять счётчиков, трассировки нет вовсе: путь одной записи по конвейеру собирается только чтением логов глазами.
|
||||||
|
- **Теги:** goal:service-observability
|
||||||
|
|
||||||
|
Эндпоинт `/metrics` остаётся и развивается — это решено. Вопрос в том, чем его
|
||||||
|
развивать: дописывать счётчики в `internal/metrics` напрямую через
|
||||||
|
`client_golang` или перевести на OpenTelemetry и получить заодно трассировку.
|
||||||
|
|
||||||
|
Сегодня у записи есть путь длиной в минуты через четыре внешних сервиса и три
|
||||||
|
воркера, и связать его звенья можно только по `job_id` в логах. Трассировка
|
||||||
|
отвечает на это прямо, но приносит коллектор — процесс, которого в выкладке нет.
|
||||||
|
|
||||||
|
## Вопрос
|
||||||
|
|
||||||
|
Даёт ли OpenTelemetry на этом проекте больше, чем стоит: коллектор в выкладке,
|
||||||
|
переписанные метрики и словарь имён, — или дешевле остаться на `client_golang` и
|
||||||
|
дописать недостающие счётчики.
|
||||||
|
|
||||||
|
## Куда ляжет ответ
|
||||||
|
|
||||||
|
`docs/research/opentelemetry.md` — с числами: сколько зависимостей приносит,
|
||||||
|
что появляется в выкладке, сколько метрик переписывается. Решение и причина
|
||||||
|
отказа — в `docs/adr/`, если берём: переход на другой словарь метрик ломает
|
||||||
|
собранные ряды и обратной правкой не откатывается.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Метрики Prometheus и путь `/metrics` из ответа не исчезают ни при каком исходе:
|
||||||
|
OpenTelemetry рассматривается как источник тех же метрик, а не как замена
|
||||||
|
эндпоинта. Своего хранилища метрик и своих панелей не поднимаем — это отдельная
|
||||||
|
работа.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# 🎯 Состояние сервиса видно без чтения логов
|
||||||
|
|
||||||
|
- **Тип:** goal
|
||||||
|
- **Секция:** Сопровождение
|
||||||
|
- **Зачем:** Отказ замечает пользователь, а не владелец: оповещения нет, а путь записи по конвейеру собирается глазами по логам контейнера.
|
||||||
|
|
||||||
|
Наблюдаем **мы**, а не пользователь сервиса, — поэтому цель стоит в
|
||||||
|
сопровождении, а не среди возможностей приложения. Пользовательская половина
|
||||||
|
той же темы живёт отдельно: о готовности своей записи человек узнаёт по цели
|
||||||
|
[ready-notification](ready-notification.md).
|
||||||
|
|
||||||
|
Эндпоинт `/metrics` остаётся и развивается. Чем именно развивать — голым
|
||||||
|
`client_golang` или OpenTelemetry с трассировкой — решает разведка
|
||||||
|
`opentelemetry-fit`.
|
||||||
|
|
||||||
|
## Завершение
|
||||||
|
|
||||||
|
1. По метрикам видно, что конвейер встал: задача висит в состоянии дольше
|
||||||
|
обычного, и это отличимо от «работы нет».
|
||||||
|
2. Каждый внешний сервис имеет метрику вызовов, отказов и длительности —
|
||||||
|
сегодня их нет ни у одного.
|
||||||
|
3. Путь одной записи по конвейеру собирается запросом, а не чтением логов
|
||||||
|
глазами.
|
||||||
|
4. Владелец узнаёт об отказе сам, а не от пользователя.
|
||||||
|
5. Стоимость обращений к платным сервисам видна числом.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# 🎯 Приложение показывает, о чём запись, не читая её целиком
|
||||||
|
|
||||||
|
- **Тип:** goal
|
||||||
|
- **Секция:** Направления
|
||||||
|
- **Зачем:** Расшифровка часового разговора — это стена текста: найти в списке нужную запись и вспомнить, о чём она, сегодня нечем.
|
||||||
|
|
||||||
|
К расшифровке добавляется короткий заголовок, пересказ и темы. Считает их
|
||||||
|
внешний сервис с OpenAI-совместимым интерфейсом — своей модели не держим, как не
|
||||||
|
держим и модели распознавания.
|
||||||
|
|
||||||
|
**Это сдвиг границы паспорта.** До 2026-08-10 «понимание сказанного» было
|
||||||
|
записано как то, чем проект не является: «мы отдаём текст, а не выводы из него».
|
||||||
|
Граница сдвинута сознательно, и в паспорте от неё остался более узкий запрет —
|
||||||
|
ответов на вопросы по записи и поиска по смыслу не делаем.
|
||||||
|
|
||||||
|
## Завершение
|
||||||
|
|
||||||
|
1. У готовой записи есть заголовок в одну строку, и он виден в списке вместо
|
||||||
|
первых слов расшифровки.
|
||||||
|
2. У записи есть пересказ, который читается быстрее самой расшифровки.
|
||||||
|
3. У записи есть темы, и по ним список отбирается.
|
||||||
|
4. Отказ или молчание внешнего сервиса не роняют задачу: расшифровка доходит до
|
||||||
|
человека без заголовка и пересказа.
|
||||||
|
5. Стоимость обращения к сервису видна метрикой: сколько записей обработано и
|
||||||
|
сколько это стоило по числу токенов.
|
||||||
Reference in New Issue
Block a user