Прямой ответ
Celery в проде ломается предсказуемо, и против каждого отказа есть настройка. Потерянные задачи: дефолтный ack при получении — падение воркера теряет взятую задачу; task_acks_late=True + task_reject_on_worker_lost=True — ack после исполнения, потерянный воркер возвращает задачу в очередь (цена — повторное исполнение: идемпотентность обязательна — гарантии at-least-once). Зависшие воркеры: задача дольше task_time_limit убивается (soft limit — исключение в задаче, hard — SIGKILL) — без лимитов одна зависшая задача съедает слот воркера навсегда; worker_max_tasks_per_child — ротация процессов (страховка от утечек памяти). Лавина ретраев: autoretry_for=(ConnectionError,) + retry_backoff=True + retry_backoff_max=600 + max_retries=5 — сетевые сбои ретраятся экспоненциально, не мгновенным штормом; после исчерпания — retry_jitter и dead-letter-логика (задача-обработчик провала с алертом). Слепота: Flower (веб-мониторинг: очереди, воркеры, задачи в реальном времени) для людей + метрики в Prometheus (экспортер) для алертов: глубина очередей, возраст старейшей задачи, доля failed, rate выполнений — «очередь растёт» видно до жалоб пользователей. И базовая гигиена: отдельные очереди под классы задач (тяжёлые отчёты не блокируют письма — маршрутизация task_routes), приоритеты только там, где брокер их честно поддерживает (RabbitMQ — да, Redis — с оговорками).
Надёжность: acks, ретраи, лимиты
Конфигурация надёжного воркера (собирается из названного): task_acks_late=True, task_reject_on_worker_lost=True, worker_prefetch_multiplier=1 (воркер не гребёт пачку «про запас» — при acks_late большой prefetch удерживает сообщения у воркера, который может умереть — единица выравнивает распределение), task_time_limit=600 / task_soft_time_limit=540 (soft — задача получает SoftTimeLimitExceeded и успевает убраться, hard — гарантия), worker_max_tasks_per_child=1000 (ротация), task_serializer='json' (pickle-сериализация задач — выполнение произвольного кода при компрометации брокера — безопасность в контексте очередей). Ретраи — явные и осмысленные: autoretry_for на транспортные исключения + экспоненциальный backoff с джиттером; бизнес-отказы (неверные данные) не ретраятся вовсе (повтор не исправит — сразу в failed/DLQ-ветку); max_retries конечен всегда (бесконечный ретрай ядовитой задачи — вечный цикл нагрузки). Идемпотентность задачи — контракт, не опция: дубли при redelivery нормальны — уникальность эффекта по task-аргументам/внешнему id (паттерны).
Маршрутизация и изоляция задач
Одна очередь на всё — антипаттерн прода: тяжёлый отчёт на 10 минут занимает слоты — письма и пуши стоят за ним; решение — очереди по классам: task_routes = {'tasks.email.*': {'queue': 'email'}, 'tasks.reports.*': {'queue': 'reports'}, ...} + воркеры на очередь (celery -A app worker -Q email -c 4 — почтовые лёгкие с большей конкурентностью, отчётные тяжёлые с меньшей и бо́льшими лимитами памяти); масштабирование независимо (почта в пике — добавить email-воркеров, отчёты не затронуты). Приоритеты — RabbitMQ (x-max-priority — честно); Redis-брокер — эмуляция отдельными очередями (high/default/low — порядок обхода воркером) — выбор брокера учитывает это. Периодические задачи — beat-процесс (планировщики в Celery-мире): один beat на кластер (два beat'а — двойные запуски — блокировка или один инстанс systemd), расписание — код (beat_schedule), задачи периодики — идемпотентны вдвойне (наложение прогонов при зависании предыдущего — обычное дело). Изоляция отказов: критичные задачи (платежи) — отдельная очередь и воркеры от некритичных (флуд фоновых не топит важное) — SLO-мышление в применении к очередям.
Мониторинг: Flower, метрики, алерты
Flower — оперативная панель (очереди и их глубина, воркеры и их занятость, задачи: активные/завершённые/проваленные с трейсбеками, запуск/отмена/ретрай вручную) — инструмент разбора «что происходит сейчас»; в проде — за аутентификацией и прокси (nginx), не наружу голым. Метрики (celery-exporter / статс-хуки в Prometheus): queue_length по очередям (главная — рост = воркеры не успевают или встали), task_age_oldest (задача ждёт N минут — пользовательская задержка), success/failure counts по именам задач (всплеск failed конкретной задачи — баг/отказ зависимости), длительности (p95 задачи — деградация внешнего API видна здесь раньше его алертов), число активных воркеров (упал — рестарт/OOM). Алерты (дисциплина): очередь растёт 15+ минут (ticket → page по порогу), воркеров меньше ожидаемого, доля failed задачи > X%, «периодическая задача не выполнялась N периодов» (тихий отказ beat — паттерн last-success-metric); каждый алерт с runbook-ссылкой. Логи задач — структурные с task_id/request_id (структура) — трейсинг «веб-запрос → задача» одним id (OpenTelemetry propagates контекст в задачу — сквозная видимость).
Практический сценарий: неделя стабилизации Celery-прода
Исходник: «задачи иногда теряются, воркеры иногда висят, что в очередях — не видим». Прогон по чек-листу: (1) надёжность — acks_late+reject_on_worker_lost+prefetch=1; тест: kill -9 воркера посреди задачи — задача переисполнилась другим (идемпотентность по order_id уже была — дублей эффекта нет); (2) лимиты — soft/hard time limit на отчёты (найдена задача, зависавшая на внешнем API без таймаута — два слота воркера были мертвы неделю — лимит + таймаут httpx закрыли); (3) маршрутизация — очереди email/reports/default, воркеры разнесены (письма в пике отчётов идут ровно — до этого стояли по 20 минут); (4) Flower + exporter + три алерта (рост очереди, failed-доля, пропуск периодики); (5) ротация max_tasks_per_child=500 для отчётных (утечка в библиотеке графиков — до починки библиотеки ротация держит RSS в норме). Первая же неделя с мониторингом: алерт «reports-очередь растёт» в 11:20 — разбор во Flower: внешний API отчётов деградировал (p95 задачи ×10) — временное снижение частоты генерации, тикет провайдеру; до мониторинга это обнаружилось бы вечером по жалобам. Итог: потери задач — ноль за квартал, зависания лечатся лимитами автоматически, видимость полная.
FAQ
Celery против RQ/Dramatiq/arq? Celery — максимум возможностей (маршрутизация, canvas-цепочки, beat, экосистема) ценой сложности конфига; RQ — минимализм на Redis (проще, меньше фич); arq — asyncio-native (для асинхронных приложений — async-стек); Dramatiq — «Celery с лучшими дефолтами»; выбор по потребностям: 90% задач — любой; маршрутизация/периодика/canvas — Celery; чисто async — arq.
Почему задача «висит в очереди» и не берётся?
Чек: воркеры на нужную очередь живы (-Q совпадает с маршрутом задачи — половина случаев), брокер доступен (connection в логах воркеров), prefetch/слоты заняты (Flower: active tasks), задача с countdown/eta в будущем (в Redis-брокере eta-задачи держатся в памяти воркера — рестарт теряет их при acks-дефолтах — знать семантику).
Как отменять и повторять задачи вручную?
revoke(task_id, terminate=True) — сигнал воркеру (terminate — SIGTERM выполняющему; «гарантированно убить» не всегда — задача может быть между ack-точками); повтор — Flower-кнопка или task.apply_async(args) с теми же аргументами (идемпотентность делает повтор безопасным); массовые операции — через API Flower/свои админ-ручки, не «перезапустить всё».
Нужен ли отдельный beat-процесс в контейнере/K8s? Да: beat — singleton (репликация запрещена — двойные запуски периодики); в K8s — Deployment с одной репликой (не DaemonSet/HPA) или внешние планировщики (K8s CronJob дёргает send_task — beat-процесс не нужен вовсе — K8s-основы); воркеры — наоборот, масштабируются свободно.
Источники: документация Celery (tasks, monitoring), Flower docs — ссылки в шапке.