video-shuoshan/docs/celery-polling-fix-20260404.md
2026-04-04 10:15:23 +08:00

4.6 KiB
Raw Permalink Blame History

Celery 轮询机制修复报告

日期2026-04-04 版本v0.16.0 影响范围backend/apps/generation/tasks.py, backend/config/settings.py


一、问题现象

2026/4/1 下午,大量用户反馈视频生成任务长时间卡在"生成中",前端显示耗时 60~65 分钟。 火山引擎侧确认视频实际生成仅需约 10 分钟,结果已就绪但未被平台及时同步。

截图数据4/1 下午完成的任务):

提交时间 显示耗时
2026/4/1 16:57:28 63 分 33 秒
2026/4/1 16:58:41 62 分 37 秒
2026/4/1 16:59:16 62 分 7 秒
2026/4/1 17:00:36 64 分 24 秒
2026/4/1 17:04:53 64 分 2 秒

二、根因分析

2.1 状态同步链路

用户提交任务
    → 后端调 create_task火山 API
    → 获得 ark_task_id
    → 派发 Celery 任务 poll_video_task
    → Celery worker 每 5 秒查一次火山 API
    → 火山返回完成 → 写 DB + 上传 TOS + 结算
    → 前端轮询 DB → 展示结果

前端只读 DB 状态,不直接调火山 API。整个链路完全依赖 Celery worker 轮询。

2.2 旧实现缺陷

poll_video_task 使用 while True + time.sleep(5) 长驻循环:

# 旧代码
while True:
    time.sleep(POLL_INTERVAL)   # 5 秒
    ark_resp = query_task(...)  # 查一次
    if terminal:
        break

三个致命问题:

问题 影响
每个任务占死一个 worker 进程 concurrency=4 最多同时轮询 4 个任务,第 5 个排队
worker 重启后循环直接丢失 内存中的 while True 不可持久化OOM/重启 = 任务丢失
time.sleep 浪费进程资源 worker 99% 时间在 sleep实际有用工作不到 1%

2.3 OOM 重启链

4 个任务同时轮询
    → 某些任务完成,触发 TOS 上传(下载视频 + 上传对象存储)
    → 内存飙升超过 512Mi 限制
    → K8s OOM Kill → worker 重启(共重启 15 次)
    → 4 个进程中的 while True 循环全部丢失
    → 等 recover_stuck_tasks每 10 分钟)重新派发
    → 重新派发后 worker 又被占满 → 又 OOM → 循环
    → 实际恢复耗时 ≈ 50~60 分钟

三、修复方案

3.1 核心改动self.retry 替代 while True

# 新代码
@shared_task(bind=True, max_retries=None, ignore_result=True)
def poll_video_task(self, record_id):
    record = GenerationRecord.objects.get(pk=record_id)

    ark_resp = query_task(record.ark_task_id)
    new_status = map_status(ark_resp.get('status', ''))

    if new_status in ('queued', 'processing'):
        record.save(update_fields=['status', 'updated_at'])
        raise self.retry(countdown=5)  # 5 秒后重新入队

    # 到达终态 → 处理结果
    ...

原理对比:

旧方式while True 新方式self.retry
任务生命周期 在 worker 进程内存中 在 Redis 队列中
worker 占用 持续占用直到完成(分钟级) 每次查询仅占用毫秒级
worker 重启 任务丢失 Redis 中的任务自动恢复
并发能力 最多 4 个(= concurrency 数百个(受 API RPM 限制)

3.2 recover_stuck_tasks 间隔缩短

旧值 新值
Beat 调度间隔 600 秒10 分钟) 180 秒3 分钟)
stuck 判定门槛 10 分钟 3 分钟
最坏恢复时间 ~20 分钟 ~6 分钟

3.3 变更文件

文件 改动
backend/apps/generation/tasks.py poll_video_task: while True → self.retryrecover_stuck_tasks: 门槛 10 → 3 分钟
backend/config/settings.py Beat schedule: 600 → 180 秒

四、效果预估

指标 修复前 修复后
同时轮询任务数上限 4 数百
worker 重启后任务恢复 丢失,等 10 分钟兜底 自动恢复,无需兜底
最坏同步延迟 60+ 分钟 ~15 秒(= 查询间隔 + 网络延迟)
内存占用 持续占满sleep 期间不释放) 脉冲式占用(查完释放)
OOM 风险 4 进程常驻 + TOS 上传峰值) 低(进程闲置时内存极小)

五、部署注意

  1. 无需数据库迁移 — 仅修改 Python 代码
  2. 部署后旧的 while True 任务会自然消亡 — 不需要手动干预
  3. Redis 中可能有旧格式的任务 — 兼容无问题,新旧 poll_video_task 签名一致(record_id 参数不变)
  4. 建议同步部署:先部署代码,再重启 Celery workerkubectl rollout restart deployment celery-worker