Первоначальная версия VidConf

This commit is contained in:
2026-07-23 01:04:01 +03:00
commit 896455381a
335 changed files with 61527 additions and 0 deletions

146
docs/deploy/scaling.md Normal file
View File

@@ -0,0 +1,146 @@
# Горизонтальное масштабирование очередей Celery
Описывает, как развести обработку задач по отдельным
репликам/сервисам под нагрузкой, когда одного контейнера `worker`
недостаточно. Здесь — инструкция для оператора, применимая к текущей и
будущей конфигурации `deploy/docker-compose.yml`.
## 1. Карта очередей
Маршрутизация задач по очередям задана в `workers/celery_app.py`
(`app.conf.task_routes`) — приоритет между семьями задач реализован
**изоляцией очередей**, а не Redis-priorities Celery (транспорт `redis`
эмулирует приоритеты ненадёжно, без строгих гарантий порядка — в отличие от
RabbitMQ).
| Очередь | Задачи | Кто слушает по умолчанию |
|----------------|-------------------------------------------------------------------------|---------------------------|
| `transcription`| `run_pipeline` (faster-whisper) | `worker-transcriber` (профиль `transcribe`), `--pool=solo` — ctranslate2/faster-whisper несовместимы с prefork |
| `summarize` | `summarize_session` (Qwen map-reduce) | базовый `worker` |
| `notify` | `notify_session`, `send_invitations` (.ics-приглашения) | базовый `worker` |
| `celery` (default) | `cleanup_conferences`, `recover_stuck_summaries`, `recover_stuck_notifications` (маршрута не имеют, обслуживание) | базовый `worker` |
Базовый сервис `worker` слушает `celery,summarize,notify` — для малых
пресетов поставки (13) это один контейнер, обрабатывающий и суммаризацию, и
уведомления, и обслуживание. `worker-transcriber` — всегда отдельный процесс
(независимо от пресета), т.к. пул `solo` несовместим с остальными задачами
в том же процессе.
Глубину каждой очереди в реальном времени видно в `GET /metrics`
(`vidconf_celery_queue_depth{queue=...}`, `backend/api/metrics.py`) и в
Grafana-дашборде «Пайплайны пост-обработки» (алерт `QueueGrowing`,
`deploy/monitoring/alerts.yml`) — по этим показателям и принимается решение
о вынесении очереди в отдельную реплику.
## 2. Общее правило: `celery beat` — только один экземпляр
Периодические задачи (`app.conf.beat_schedule`) планирует `celery beat`.
Базовый `worker` запускается с флагом `-B` (worker + встроенный beat в одном
процессе, `deploy/docker-compose.yml`). При масштабировании **нельзя** просто
поднять несколько реплик сервиса с `-B` — каждая реплика завела бы
собственный планировщик, и periodic-задачи (`cleanup_conferences`,
`recover_stuck_*`) ставились бы в очередь многократно на каждом тике.
Правило: ровно один процесс во всей инсталляции запускается с `-B`
(встроенным или отдельным `celery -A workers.celery_app beat`); все
дополнительные реплики — только `worker` без `-B`, с явным `-Q` на нужные
очереди.
## 3. Вынос очереди в отдельную реплику
Пример: суммаризация (`summarize`) стала узким местом — очередь растёт,
базовый `worker` не успевает. Решение — отдельный сервис-потребитель только
этой очереди, без встроенного beat (он остаётся на базовом `worker`).
Добавить в `deploy/docker-compose.override.yml` (или новый профиль по
аналогии с `worker-transcriber`):
```yaml
services:
worker-summarize:
build:
context: ../backend
dockerfile: Dockerfile
restart: unless-stopped
# Без -B: beat уже запущен на базовом worker (см. правило выше).
command: ["uv", "run", "celery", "-A", "workers.celery_app", "worker",
"-Q", "summarize", "--hostname=worker-summarize-%h@%h", "--loglevel=info"]
env_file:
- ../.env
environment:
DATABASE_URL: ${DATABASE_URL:-postgresql+asyncpg://vidconf:vidconf@postgres:5432/vidconf}
REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
PLUGINS_CONFIG_PATH: ${PLUGINS_CONFIG_PATH:-config/plugins.yaml}
PYTHONPATH: /app
volumes:
- ../workers:/app/workers:ro
- ../config:/app/config:ro
- llm-models:/models/qwen:ro
depends_on:
redis:
condition: service_healthy
```
и одновременно убрать `summarize` из списка очередей базового `worker`
(его команда сужается до `-Q celery,notify`), чтобы задачи не выполнялись
дважды разными процессами (Celery доставляет задачу ровно одному
consumer'у одной и той же очереди — дублирования не будет, но держать
лишний неиспользуемый consumer незачем).
Аналогично можно выделить `notify` в `worker-notify` (та же схема,
`-Q notify`) — например, если рассылка приглашений/уведомлений на большую
аудиторию (SMTP-латентность) начинает задерживать саммаризацию соседних
сеансов при их совместном обслуживании базовым `worker`.
## 4. Масштабирование реплик через `docker compose up --scale`
Если одной выделенной очереди тоже мало (несколько ядер CPU для
суммаризации/уведомлений), реплицируем сервис командой `--scale`:
```bash
docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.override.yml \
up -d --scale worker-summarize=3
```
Требования для корректного масштабирования сервиса:
1. **Без `-B`** на масштабируемом сервисе (правило §2).
2. **Без фиксированного `--hostname`** на весь сервис — при нескольких
репликах одинаковое имя узла Celery приведёт к конфликту регистрации в
кластере (соединения будут путаться, `celery inspect` начнёт видеть
произвольного из реплик). Использовать `%h` (имя контейнера, уникальное
у каждой реплики Compose) — см. `--hostname=worker-summarize-%h@%h` в
примере выше. Это же ограничение действует и для `worker-transcriber`:
его текущий healthcheck (`--destination worker-transcriber@localhost`)
жёстко завязан на единственную реплику; при `--scale worker-transcriber=N`
healthcheck и `--hostname` в `deploy/docker-compose.yml` потребуется
поменять на шаблон `%h` (и убрать `--destination`, либо адресовать
каждую реплику отдельно) — вне рамок этого документа, т.к. правит
основной `deploy/docker-compose.yml` (devops-часть).
3. **Без `container_name`** и без фиксированных host-портов на
масштабируемом сервисе (у Celery-воркеров портов нет — ограничение не
актуально для `worker*`, но актуально, если аналогичный приём
применяется к `backend` за `nginx upstream`).
4. Ресурсные лимиты (`cpus`/`mem_limit`) в Compose-файле применяются к
КАЖДОЙ реплике, а не разделяются между ними — планировать суммарное
потребление хоста (`N × cpus`).
Для `worker-transcriber` тот же приём уже применим на уровне профиля:
```bash
docker compose -f deploy/docker-compose.yml --profile transcribe \
up -d --scale worker-transcriber=2
```
(после снятия ограничения фиксированного `--hostname`, см. пункт 2 выше).
## 5. Когда масштабировать
Ориентир — глубина очереди (`vidconf_celery_queue_depth`) растущая дольше
15 минут (алерт `QueueGrowing`, `deploy/monitoring/alerts.yml`) либо
устойчиво положительная под обычной нагрузкой инстанса. Для `summarize` и
`transcription` также ориентир — доля CPU/GPU (транскрибация и
суммаризация — тяжёлые по вычислениям шаги, `docs/deploy/hardware-profiles.md`);
`notify` почти всегда I/O-bound (SMTP) — реплики полезны в первую очередь
при большой аудитории рассылок (закреплённые конференции с длинным списком
участников/приглашённых).