Обзор системы · дипломный уровень

PrimeTime — онлайн-кинотеатр с нейросетевыми рекомендациями

Обзорный документ дипломного уровня. Описывает систему целиком: назначение, архитектуру, технологический стек, инфраструктуру и принятые инженерные решения. Детальные разборы по слоям — в сопутствующих файлах: frontend.md, backend.md, ml.md.

01 Что это за проект

PrimeTime — полноценный онлайн-кинотеатр (фильмы, сериалы, аниме) с каталогом на ~290 000 единиц контента, персональными рекомендациями на базе собственной двухбашенной нейросети, гибридным (текст + смысл) поиском, фасетным каталогом, системой аналитики просмотров и Telegram-рассылками.

Это не учебный CRUD: проект работает в продакшене, имеет реальных пользователей, непрерывную доставку (CI/CD через GitLab → Docker registry → VPS) и решает нетривиальные инженерные задачи — векторный поиск на 290k записей с укладкой в десятки миллисекунд, балансировку внешних API-провайдеров с circuit breaker, explainable-рекомендации в виде «полок».

Проект сознательно построен как полигон: автор обкатывает на нём современные веб- и ML-технологии (об этом прямо сказано в README фронтенда — «a super-duper pet project with which I play with different technologies»), но делает это на производственном уровне качества.

Продакшен: https://primetime.su/ · Автор: Фёдор Гаранин (yesfedor)

Масштаб кодовой базы (метрики)

РепозиторийНазначениеLOCКлючевые числа
core-webFrontend (Nuxt 4 / Vue 3)~37 400119 компонентов, 37 страниц, 34 composable, 57 API-обёрток
core-api-grpcBackend (NestJS / gRPC)~18 00026 сервисов, 18 gRPC-контроллеров, 8 миграций
ai-recommendationML-пайплайн (TensorFlow.js)7-шаговый пайплайн обучения Two-Tower
content.protoКонтракт1 497 строк22 сервиса, 78 RPC-методов

Только один backend-репозиторий — 206 коммитов, единственный автор, последняя активность в день анализа. Это длительная, последовательная работа одного инженера, а не разовый учебный проект.

02 Архитектура в целом

┌──────────────────────────┐ Браузер ◄──────► │ core-web (Nuxt 4 SSR) │ :4503 → :3000 │ Vue 3 + Pinia + SCSS │ └─────────────┬────────────┘ gRPC-Web │ (бинарный протокол) ▼ ┌──────────────────────────┐ │ Envoy proxy v1.28 │ :4775 (gRPC-Web → gRPC) └─────────────┬────────────┘ gRPC │ :50051 ▼ ┌───────────────────────────────────────────────────────────┐ │ core-api-grpc (NestJS microservice) │ │ Auth · Movie · Catalog · Search · Shelves · Recommendation │ │ · Balancer · Analytics · Telegram · Studio · Staff · ... │ └───┬──────────┬──────────┬───────────┬──────────┬──────────┘ ▼ ▼ ▼ ▼ ▼ ┌──────────┐ ┌────────┐ ┌──────────┐ ┌────────┐ ┌──────────────┐ │PostgreSQL│ │ Redis │ │Elastic │ │Infinity│ │External APIs │ │ pg17 + │ │ :3435 │ │Search │ │embed │ │ Kinopoisk │ │ pgvector │ │ │ │ 8.12 :4801│ │ :1919 │ │ TMDB · TW LLM│ └──────────┘ └────────┘ └──────────┘ └────────┘ └──────────────┘ ▲ ┌──────┴──────────┐ ┌─────────────┐ ┌──────────┐ │ ai-recommendation│ │ imgproxy │ │ wordpress│ │ (офлайн обучение)│ │ :4506 │ │ blog:4025│ └──────────────────┘ └─────────────┘ └──────────┘

Ключевая архитектурная идея

Фронтенд и бэкенд связаны строго типизированным контрактом — единым proto-файлом (content.proto). Из него buf/protoc генерирует TypeScript-клиент для фронта и серверные интерфейсы для NestJS. Это даёт сквозную типобезопасность: изменил поле в proto → перегенерировал → TypeScript ломается там, где контракт нарушен. Никакого ручного REST-DTO-дрейфа.

Транспорт — gRPC-Web с бинарной сериализацией (не JSON). Браузер не умеет HTTP/2-gRPC напрямую, поэтому в цепочку добавлен Envoy как трансляционный прокси (gRPC-Web → gRPC). Это взрослое, «нетипичное для пет-проекта» решение.

03 Технологический стек

СлойТехнологии
FrontendNuxt 4, Vue 3 (Composition API), TypeScript 5, Pinia, SCSS (7-1 pattern), VeeValidate + Yup, @connectrpc/connect-web, Bun
BackendNestJS 10, TypeScript, gRPC (@grpc, ConnectRPC), Prisma 6, Passport JWT, Bull (очереди), @nestjs/schedule (cron)
БДPostgreSQL 17 + расширения pgvector, pg_trgm, btree_gin, uuid-ossp; multi-schema (content, content-balancer)
ПоискElasticsearch 8.12 (BM25 + фасеты) + pgvector (kNN-семантика)
MLTensorFlow.js (Node-биндинги), Two-Tower, in-batch sampled-softmax; эмбеддинги — Alibaba-NLP/gte-multilingual-base через Infinity Inference Server
LLMTimeweb AI (deepseek-v4-flash) — оптимизация поисковых запросов
ИнфраDocker, Envoy, Redis, imgproxy, GitLab CI/CD, nginx-proxy, VPS
Внешние данныеKinopoisk Unofficial / Dev API (контент), TMDB (студии/сети)

04 Продакшен-инфраструктура (анализ запущенных контейнеров)

Список реально работающих в продакшене контейнеров и оценка качества их применения:

КонтейнерРольКак используется — оценка
core-api-grpc :50051gRPC backendЯдро системы. Чистая модульная архитектура NestJS. Качественно.
core-web :4503→3000Nuxt SSR frontendSSR + гидрация, проксирование gRPC-Web. Качественно.
pgvector/pgvector:pg17 :3338PostgreSQL + векторыХранит и контент, и 768/64-мерные векторы; HNSW-индексы. Используется по делу — векторный поиск прямо в SQL. Очень качественно.
elasticsearch:8.12 :4801Полнотекст + фасетыBM25-кандидаты для гибридного поиска, агрегации для фасетного каталога. Не «ES ради ES» — есть чёткое разделение ответственности с pgvector. Качественно.
envoyproxy/envoy:v1.28 :4775gRPC-Web → gRPCОбязательное звено для браузерного gRPC. Грамотный выбор вместо самописного прокси. Качественно.
darthsim/imgproxy :4506On-the-fly ресайз постеровВсе постеры идут через imgproxy.primetime.su/insecure/rs:fill:.... Снимает нагрузку и нормализует размеры. Качественно.
michaelf34/infinity :1919Embedding Inference ServerГенерация 768-мерных эмбеддингов (gte-multilingual-base) для семантики. Поднят отдельным сервисом (13 дней аптайма) — правильное разделение CPU-тяжёлой задачи. Качественно.
redis:alpine :3435Кэш + rate-limit + локиКэш каталога/трендов (stale-while-revalidate), single-flight NX-локи, rate-limit токенов балансировщика. Используется на нескольких нетривиальных паттернах. Очень качественно.
dpage/pgadmin4 :3340Админка БДВспомогательный инструмент. Нормально.
wordpress :4025БлогКонтент-маркетинг отдельным сервисом. Прагматично.

Вывод по инфраструктуре: каждый контейнер имеет ясную причину существования, нет «карго-культа». Связка Postgres+pgvector / Elasticsearch / Infinity / Redis — это осознанно спроектированная data-платформа, а не набор случайных зависимостей.

05 Функциональные подсистемы

  • Аутентификация — JWT (access/refresh), вход по email и по Telegram-виджету, device-id через cookie, бесшовный refresh с дедупликацией (single-flight) на SSR.
  • Каталог и контент — фасетный каталог через Elasticsearch (жанры, страны, студии, годы, рейтинги, длительность, возраст), карточки фильмов, персоны, студии/сети (из TMDB), связи (сиквелы/приквелы/спин-оффы).
  • Поиск — гибридный: BM25 (Elasticsearch) + семантический kNN (pgvector) с Reciprocal Rank Fusion, байесовским взвешиванием популярности и LLM-нормализацией запроса.
  • Рекомендации — двухбашенная нейросеть (Two-Tower); главная страница как лента объяснимых «полок» (shelves) с runtime k-means-кластеризацией вкусов.
  • Аналитика просмотров — точный трекинг watch-сессий (хиты ≥30с, лимит 900с), агрегированная статистика, реалтайм-дашборды в админке.
  • Балансировщик провайдеров — отказоустойчивый роутинг между источниками данных (Kinopoisk) с ротацией токенов, rate-limit и circuit breaker.
  • Telegram-рассылки — кампании, тест-сообщения, авто-отписка при блокировке бота.
  • Админ-панель — пользователи, рассылки, история просмотров, реалтайм-аналитика.

06 Сквозная оценка качества

Сильные стороны

  • Сквозная типобезопасность через единый proto-контракт (frontend ↔ backend).
  • Производительность как явная инженерная дисциплина — в коде есть комментарии с разбором EXPLAIN, переход на двухстадийный HNSW-поиск, SET LOCAL hnsw.ef_search, sparse-маскирование в обучении. Это редкость даже в коммерческих проектах.
  • Чистая модульность на обоих концах (Nuxt feature-folders / NestJS-модули).
  • Зрелые паттерны кэширования — stale-while-revalidate + single-flight на Redis.
  • Грамотное разделение ответственности хранилищ: ES — текст и фасеты, pgvector — смысл, Redis — кэш/локи, PG — источник истины.
  • Документация — каждая подсистема описана в docs/*.md с обоснованием решений.

Зоны риска / что отметила бы аттестационная комиссия

  • Секреты в .env лежат в репозитории (пароли БД, токены Telegram/TMDB, API-ключи). Для продакшена это уязвимость — нужен secret-manager / vault. (Замечание зафиксировано; в рамках задачи правки не вносятся.)
  • Местами «god-объекты» сервисов по 700+ строк (recommendation, elastic-search, shelves) — читаемо, но просятся на декомпозицию.
  • Тестов немного (есть точечные на ML-кодек и загрузку башни); для дипломной работы это нормально, для дальнейшего роста — узкое место.
Секреты в .env в репозитории — для продакшена это уязвимость безопасности: требуется перенос в secret-manager / vault.

Итоговая оценка уровня автора — см. раздел 8 ниже и в каждом профильном файле.

07 Как читать остальные документы

  • frontend.md — устройство Nuxt-приложения, фирменная liquid-вёрстка 1rem = 1px, плагины, gRPC-клиент с интерсепторами, auto-import API-слой, темизация, icon-модуль, примеры кода и оценка чистоты архитектуры.
  • backend.md — модули NestJS, gRPC-контракт, работа с Prisma и multi-schema PostgreSQL, Elasticsearch, pgvector, балансировщик, кэш-стратегии, «крутые решения» с разбором.
  • ml.md — двухбашенная архитектура, признаки, 7-шаговый пайплайн, in-batch sampled-softmax с logQ-коррекцией, инференс и интеграция с бэкендом.

08 Краткий вердикт

Уровень исполнения — уверенный Senior (обоснование в профильных файлах).

Проект демонстрирует не только умение «собрать фичу», но и системное мышление: выбор транспорта, разделение хранилищ, профилирование запросов, отказоустойчивость, объяснимость рекомендаций и собственную ML-инфраструктуру end-to-end.