Skip to content

单 gunicorn sync worker 导致页面加载极慢(数分钟) #69

@onepaperbox

Description

@onepaperbox

问题描述

部署后访问 Web 页面非常慢,首次加载需要等待数分钟,健康检查频繁超时(unhealthy)。

根因分析

Dockerfile 中 gunicorn 启动命令硬编码为:

CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:5000", "--timeout", "120", "--access-logfile", "-", "web_outlook_app:app"]

-w 1 + sync worker 意味着所有请求串行处理,一次只能响应一个请求。

并发竞争

以下请求源同时竞争唯一的 worker:

来源 频率/数量
APScheduler 后台任务(通知分发、API 探测、邮箱池维护、Token 刷新) 持续运行,API 探测每 5 秒一次
Docker 健康检查 (/healthz) 每 30 秒
用户浏览器请求(HTML + CSS + JS) 每次页面加载多个请求
iOS Safari 预加载(apple-touch-icon 等) 自动触发
社交媒体爬虫(Facebook/Twitter) 不定时

当后台任务占用 worker 时,用户请求排队等待,导致页面加载需要数分钟。

实际日志证据

健康检查连续超时(5 次 FailingStreak),/healthz 请求在 4 秒超时内无法得到响应:

TimeoutError: timed out

同时静态文件(CSS/JS)返回 200 但 body 为 0 字节:

"GET /static/css/main.css HTTP/1.1" 200 0
"GET /static/js/i18n.js HTTP/1.1" 200 0

建议方案

理解 -w 1 是为了避免 APScheduler 多进程冲突和 Flask 内存 session 不同步问题,但 sync worker 严重影响了生产环境可用性。

方案 A:改用 gevent worker(推荐,改动最小)

CMD ["gunicorn", "-w", "1", "-k", "gevent", "-b", "0.0.0.0:5000", "--timeout", "120", "--access-logfile", "-", "web_outlook_app:app"]

单 worker + gevent 可以并发处理多个请求(协程),不创建多进程,不影响 APScheduler 和 session。只需在 requirements.txt 中添加 gevent

方案 B:拆分调度器到独立进程

将 APScheduler 运行在独立的进程/线程中,Web 部分可以安全地增加 worker 数量。改动较大但架构更清晰。

方案 C:支持环境变量覆盖

允许通过环境变量(如 GUNICORN_WORKERSGUNICORN_WORKER_CLASS)覆盖启动参数,让用户根据部署场景自行调整。

环境信息

  • 镜像:ghcr.io/zeropointsix/outlook-email-plus:latest
  • 容器资源:CPU 0.01%,MEM 65MiB(资源充足,非性能瓶颈)
  • 客户端:macOS Chrome / iOS Safari

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions