Первоначальная версия 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

94
workers/celery_app.py Normal file
View File

@@ -0,0 +1,94 @@
"""Конфигурация Celery-приложения: broker Redis и расписание периодических задач.
Пакет `workers` запускается в окружении backend (тот же venv, `models`/`core`
доступны как top-level модули — см. `deploy/docker-compose.yml`, сервис
`worker`, и зависимости Celery в `backend/pyproject.toml`).
"""
from celery import Celery
from core.config import get_settings
settings = get_settings()
app = Celery("vidconf", broker=settings.redis_url, backend=None)
# Периодические задачи (beat). cleanup_conferences (ADR-001):
# закрытие зависших сеансов (>10 мин без участников) и завершение
# просроченных незакреплённых плановых конференций без единого сеанса.
# recover_stuck_summaries — уровень 2 защиты от потери
# постановки `summarize_session` при сбое брокера в `run_pipeline`: раз в
# 5 минут переставляет задачу для сеансов, зависших в `summarizing` без
# `summary_data` дольше `STUCK_SUMMARIZING_THRESHOLD` (30 мин) — интервал
# планировщика намного короче порога, чтобы «зависание» было устранено
# в течение нескольких минут после порога, а не одним запросом на грани.
# recover_stuck_notifications — тот же уровень 2 защиты для
# следующего шага пайплайна: переставляет `notify_session` для сеансов с уже
# готовым `summary_data`, для которых её постановка из `summarize_session`
# не удалась (тот же интервал/порог 5мин/30мин, симметрично recover-stuck-summaries).
app.conf.beat_schedule = {
"cleanup-conferences": {
"task": "workers.tasks.maintenance.cleanup_conferences",
"schedule": 60.0,
},
"recover-stuck-summaries": {
"task": "workers.tasks.maintenance.recover_stuck_summaries",
"schedule": 300.0,
},
"recover-stuck-notifications": {
"task": "workers.tasks.maintenance.recover_stuck_notifications",
"schedule": 300.0,
},
}
# Маршрутизация задач по очередям.
#
# `run_pipeline` — отдельная очередь `transcription`: слушает только
# `worker-transcriber` (`--pool=solo`, faster-whisper/ctranslate2 несовместимы
# с prefork-пулом Celery), базовый `worker` эту очередь не слушает.
# `pipeline_producer.enqueue_pipeline` уже передаёт `queue=` явно при
# отправке — этот маршрут страхует остальные пути постановки задачи
# (beat/ретраи/ручной вызов по имени без явной очереди).
#
# `summarize_session` — очередь `summarize`, `notify_session`/`send_invitations`
# (рассылка .ics-приглашений) — очередь `notify`: обе ставятся через
# `app.send_task` (общий клиент `app` из этого модуля, `workers.tasks.dispatch`)
# либо через отдельный клиент `services/invitations_producer.py` (backend), у
# которого нет собственного `task_routes` — там очередь передаётся явным
# `queue="notify"` при отправке (см. этот файл), а маршрут ниже страхует
# остальные пути постановки (ретраи `send_invitations` через `task.retry`
# используют исходную очередь сообщения, а не эту конфигурацию).
#
# Задачи обслуживания (`workers.tasks.maintenance.*` — `cleanup_conferences`,
# `recover_stuck_summaries`, `recover_stuck_notifications`) явного маршрута не
# получают и остаются на дефолтной очереди Celery `celery` — базовый `worker`
# слушает `celery,summarize,notify` (`docs/deploy/scaling.md`), поэтому они
# обрабатываются тем же контейнером, что и summarize/notify на малых пресетах.
#
# Приоритет между очередями реализуем ИЗОЛЯЦИЕЙ (отдельные очереди слушаются
# отдельными воркерами/репликами при масштабировании), а НЕ Redis-priorities
# Celery (`Kombu`/`redis` транспорт эмулирует приоритеты через несколько
# внутренних списков и не даёт строгих гарантий порядка — по сути ненадёжны
# на брокере Redis, в отличие от RabbitMQ; см. `docs/deploy/scaling.md`).
app.conf.task_routes = {
"workers.tasks.pipeline.run_pipeline": {"queue": "transcription"},
"workers.tasks.summarize.*": {"queue": "summarize"},
"workers.tasks.notify.*": {"queue": "notify"},
"workers.tasks.invitations.*": {"queue": "notify"},
}
# Явный импорт модулей с задачами: наши задачи лежат в `workers/tasks/*.py`,
# а не в `workers/tasks.py`, поэтому `autodiscover_tasks` со стандартным
# суффиксом `.tasks` их бы не нашёл. `workers.tasks.pipeline` и
# `workers.tasks.summarize` регистрируются в обоих процессах (базовый
# `worker` и `worker-transcriber` запускают один и тот же `-A
# workers.celery_app`) — очередь, а не импорт, разграничивает, где задача
# реально выполняется (`task_routes` ниже); `pipeline.py` ссылается на
# `summarize_session`, а `summarize.py` — на `notify_session`
# по имени задачи (`app.send_task`/`workers.tasks.dispatch.send_task_with_retry`),
# не импортируя модули друг друга напрямую.
import workers.tasks.invitations # noqa: E402,F401
import workers.tasks.maintenance # noqa: E402,F401
import workers.tasks.notify # noqa: E402,F401
import workers.tasks.pipeline # noqa: E402,F401
import workers.tasks.summarize # noqa: E402,F401