Files
pipu-music/docs/architecture.md
T
mrleo1nid 1e3b350dfc
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
feat(upload): enhance audio upload handling and user feedback
- 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.
2026-10-21 10:07:43 +03:00

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 Воспроизведение

  1. Клиент получает трек (DTO содержит stream_url — подписанную ссылку со сроком жизни).
  2. GET /api/v1/stream/{trackId}?q=high&exp=…&sig=… → nginx → api.
  3. api проверяет подпись / срок / пользователя, при необходимости готовит транскод в кэше, отвечает X-Accel-Redirect: /_internal/library/<source>/<path> (или /_internal/cache/...).
  4. nginx сам отдаёт файл с поддержкой Range (перемотка), sendfile, без участия Go.

Подробно — playback.md.

3.2 События и обучение

  1. Клиент шлёт события (play_start, progress, play_end, skip, like, …) пачками в POST /api/v1/events.
  2. api валидирует, пишет в play_events (источник истины) и публикует в Redis Stream events.
  3. recommender читает стрим: обновляет сессионный вектор волны и вектор вкуса пользователя онлайн.
  4. Ночью recommender переобучает ALS и LightGBM по play_events и пересчитывает агрегаты.

Подробно — recommendations.md.

3.3 Сканирование библиотеки

  1. Админ добавляет хранилище → api → storage-agent монтирует NFS.
  2. api ставит задачу scan_source в River → обход файлов, чтение тегов (Go, dhowden/tag + ffprobe), применение шаблонов путей, upsert в БД, индексирование в Meilisearch.
  3. Для новых / изменённых треков создаётся задача анализа (таблица analysis_jobs) → analyzer.

Подробно — library-and-storage.md.

3.4 Предложение треков

  1. Модератор перетаскивает файлы → клиент считает SHA-256 и читает теги → POST /uploads/precheck: известные дубли отбрасываются без загрузки.
  2. POST /uploads резервирует квоту очереди → загрузка по tus в staging (/srv/pipu/uploads).
  3. Задача process_upload_file: ffprobe, хэш, Chromaprint → дубль (удаление, квота освобождена) или pending_review.
  4. Админ правит метаданные и одобряет → 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).