ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m1s
ci / db / db (push) Successful in 1m16s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m12s
ci / api / api (push) Successful in 45s
ci / recommender / recommender (push) Successful in 1m50s
ci / ts / ts (packages + web) (push) Failing after 6m53s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m13s
- Introduced a new warning for users uploading non-FLAC or WAV formats, indicating that these formats should only be used as a last resort. - Added functionality to highlight files needing format attention in the upload queue. - Updated the UI to provide clearer guidance on acceptable audio formats during uploads. - Improved documentation to reflect changes in upload requirements and user interface updates.
11 KiB
11 KiB
Архитектура
1. Контекст
- До ~100 пользователей, библиотека от 11k треков (рассчитываем на рост до 100k без смены архитектуры).
- Всё разворачивается на одном сервере через Docker Compose.
- Музыка лежит на NFS-хранилищах, которые подключает администратор из админки. Для всех сервисов доступ к музыке —
только чтение; единственный писатель —
storage-agent, который раскладывает одобренные треки из очереди на добавление. - Клиенты: веб (SPA) и Android-приложение.
2. Компоненты
┌──────────────┐ ┌────────────────┐
Web SPA ─────┐ │ │ │ │
├──► │ nginx │────►│ api │──► PostgreSQL (+pgvector)
Android ─────┘ │ (TLS, SPA, │ │ (Go) │──► Redis
│ X-Accel) │ │ │──► Meilisearch
└──────┬───────┘ └───┬────────┬───┘
│ read-only │ HTTP │ HTTP (internal)
▼ ▼ ▼
/library/<source> recommender storage-agent
/cache/transcode (Python) (Go, CAP_SYS_ADMIN)
▲ │ │ mount -t nfs
│ ▼ ▼
│ analyzer /library/<source>
└────────── (Python, (mount propagation
Essentia) rshared → остальным)
| Сервис | Язык | Ответственность |
|---|---|---|
nginx |
— | TLS, раздача SPA, reverse proxy на api, отдача аудиофайлов и обложек через X-Accel-Redirect (internal locations), gzip/brotli, кэш статики. |
api |
Go | Весь публичный REST API: auth, пользователи, каталог, поиск (прокси к Meilisearch), коллекция, состояние плеера, события прослушиваний, админка, фоновые задачи (сканер, транскодинг) через очередь River (на Postgres). |
storage-agent |
Go | Единственный привилегированный контейнер: монтирует / размонтирует NFS-шары в /library/<source_id>, проверяет доступность, записывает одобренные загрузки в принимающие хранилища (только создание новых файлов). Принимает команды только от api по внутренней сети. |
recommender |
Python | Онлайн-сервис рекомендаций: генерация кандидатов, ранжирование, пачки для «Моей волны», обработка обратной связи в реальном времени; ночное обучение моделей (ALS, LightGBM). |
analyzer |
Python | Воркер аудиоанализа: Essentia — BPM, энергия, танцевальность, настроение, эмбеддинги. Тот же образ, что recommender, другая команда запуска. |
postgres |
— | Основное хранилище, pgvector для эмбеддингов, очередь задач River. |
redis |
— | Rate limiting, кэш, Redis Streams для событий в recommender, сессионные векторы волны. |
meilisearch |
— | Полнотекстовый поиск с опечатками и автокомплитом. |
3. Ключевые потоки
3.1 Воспроизведение
- Клиент получает трек (DTO содержит
stream_url— подписанную ссылку со сроком жизни). GET /api/v1/stream/{trackId}?q=high&exp=…&sig=…→ nginx →api.apiпроверяет подпись / срок / пользователя, при необходимости готовит транскод в кэше, отвечаетX-Accel-Redirect: /_internal/library/<source>/<path>(или/_internal/cache/...).- nginx сам отдаёт файл с поддержкой
Range(перемотка),sendfile, без участия Go.
Подробно — playback.md.
3.2 События и обучение
- Клиент шлёт события (
play_start,progress,play_end,skip,like, …) пачками вPOST /api/v1/events. apiвалидирует, пишет вplay_events(источник истины) и публикует в Redis Streamevents.recommenderчитает стрим: обновляет сессионный вектор волны и вектор вкуса пользователя онлайн.- Ночью
recommenderпереобучает ALS и LightGBM поplay_eventsи пересчитывает агрегаты.
Подробно — recommendations.md.
3.3 Сканирование библиотеки
- Админ добавляет хранилище →
api→storage-agentмонтирует NFS. apiставит задачуscan_sourceв River → обход файлов, чтение тегов (Go,dhowden/tag+ffprobe), применение шаблонов путей, upsert в БД, индексирование в Meilisearch.- Для новых / изменённых треков создаётся задача анализа (таблица
analysis_jobs) →analyzer.
Подробно — library-and-storage.md.
3.4 Предложение треков
- Модератор перетаскивает файлы → клиент считает SHA-256 и читает теги →
POST /uploads/precheck: известные дубли отбрасываются без загрузки. POST /uploadsрезервирует квоту очереди → загрузка по tus в staging (/srv/pipu/uploads).- Задача
process_upload_file: ffprobe, хэш, Chromaprint → дубль (удаление, квота освобождена) илиpending_review. - Админ правит метаданные и одобряет →
import_upload_file: целевой путь поwrite_templateхранилища →storage-agentзаписывает файл →scan_path→ трек в каталоге, поиске и очереди анализа.
Подробно — uploads.md.
4. Структура репозитория
pipu-music/
├── apps/
│ ├── web/ # React + Vite SPA
│ └── mobile/ # React Native (Expo) Android
├── packages/ # общий TypeScript-код (pnpm workspaces)
│ ├── api-client/ # сгенерированный из OpenAPI клиент + типы
│ ├── core/ # бизнес-логика клиентов: очередь плеера, события, сторы
│ ├── i18n/ # словари ru/en и настройка i18next
│ └── design-tokens/ # цвета, типографика, отступы → CSS vars и TS
├── services/
│ ├── api/ # Go
│ │ ├── cmd/api/ # main сервиса
│ │ ├── cmd/storage-agent/ # main агента хранилищ
│ │ ├── internal/
│ │ │ ├── auth/ users/ library/ catalog/ search/
│ │ │ ├── playback/ collection/ events/ wave/ admin/ uploads/
│ │ │ └── platform/ # db, redis, config, logging, http, jobs
│ │ ├── migrations/ # goose SQL-миграции
│ │ └── queries/ # sqlc SQL
│ └── recommender/ # Python (uv)
│ ├── pipu_rec/
│ │ ├── api/ # FastAPI
│ │ ├── analysis/ # признаки и эмбеддинги (numpy)
│ │ ├── candidates/ ranking/ wave/ training/
│ │ └── storage/ # доступ к Postgres / Redis
│ └── tests/
├── api/openapi.yaml # единый контракт публичного API
├── deploy/
│ ├── compose.yaml
│ ├── compose.dev.yaml
│ ├── nginx/
│ └── .env.example
├── docs/
├── turbo.json pnpm-workspace.yaml package.json
└── README.md
5. Архитектурный стиль
- Модульный монолит в
api: каждый модуль вinternal/<module>—handler(HTTP) →service(бизнес-логика, интерфейсы) →repository(sqlc). Модули общаются через интерфейсы сервисов, не лезут в чужие таблицы напрямую. - Contract-first:
api/openapi.yaml— источник истины. Go-сервер генерирует типы и интерфейс хендлеров (oapi-codegen, strict server), TS-клиент генерируется вpackages/api-client. Сгенерированный код совпадает с контрактом. - Внутренний API
api ↔ recommenderиapi ↔ storage-agent— HTTP/JSON во внутренней docker-сети, описан отдельнымиinternal-*.yamlспеками. - Идемпотентность: события и мутирующие запросы клиентов принимают
Idempotency-Key/event_id(UUIDv7), что важно для офлайн-синхронизации мобильного. - Деградация: если
recommenderнедоступен,apiотдаёт волну по упрощённому fallback-алгоритму (похожие по жанрам + лайки + случайное), приложение продолжает работать. - Наблюдаемость: структурированные JSON-логи (
slog,structlog),/healthzи/readyzу каждого сервиса, метрики Prometheus/metrics(опционально подключаются Grafana / Prometheus).