Spaces:
Running
Running
| # Worker Pool 饱和问题修复方案 | |
| ## 问题背景 | |
| 在 HuggingFace Space 部署的 stock_data_api_service 中,以下端点频繁出现 timeout / cooldown / worker_pool_saturated 错误: | |
| - `stock_search`(`/api/v1/search`) | |
| - `boards_flow`(`/api/v1/boards/flow`、`/boards/concepts/flow`、`/boards/industries/flow`) | |
| - `sector_concept_hot`(`/api/v1/boards/concepts/flow`) | |
| ### 根本原因 | |
| 1. **HuggingFace Space 的 Worker 限制** | |
| - Free tier:1-2 个并发 worker | |
| - Pro tier:10+ 个并发 worker | |
| - 每个慢请求(15-60s)会占用一个 worker,导致 worker pool 饱和 | |
| 2. **无全局并发控制** | |
| - `source_runner.py` 中的 `call_with_timeout` 每次都创建新的 `ThreadPoolExecutor` | |
| - 没有全局的并发限制和请求排队机制 | |
| - 多个慢请求同时到达,立即超出 HF 限制 | |
| 3. **网络请求慢** | |
| - eastmoney/AKShare 接口响应时间不稳定(15-60s+) | |
| - 当前 `SOURCE_TIMEOUT_SECONDS = 15` 太短,但增加超时会占用更多 worker | |
| ## 修复方案 | |
| ### 1. 引入全局源调用池(GlobalSourcePool) | |
| **修改文件**: `app/services/source_runner.py` | |
| ```python | |
| class GlobalSourcePool: | |
| """全局源调用池,控制并发数,防止 worker pool 饱和""" | |
| def __init__(self, max_concurrent: int = 2, max_workers: int = 4): | |
| self._max_concurrent = max_concurrent | |
| self._semaphore = threading.Semaphore(max_concurrent) | |
| self._executor = concurrent.futures.ThreadPoolExecutor( | |
| max_workers=max_workers, | |
| thread_name_prefix="source_pool" | |
| ) | |
| self._stats = { | |
| "active": 0, | |
| "queued": 0, | |
| "completed": 0, | |
| "failed": 0, | |
| "timeout": 0, | |
| } | |
| ``` | |
| **关键特性**: | |
| - ✅ 使用 `Semaphore` 控制最大并发数 | |
| - ✅ 线程池复用,不再每次创建新的 ThreadPoolExecutor | |
| - ✅ 统计指标(active, queued, completed, failed, timeout) | |
| - ✅ 懒初始化,全局单例 | |
| ### 2. 添加环境变量配置 | |
| **修改文件**: `app/core/config.py` | |
| ```python | |
| # 源调用池配置 | |
| max_concurrent_sources: int = _int_env("MAX_CONCURRENT_SOURCES", 2) # HF Free: 2, Pro: 10 | |
| source_pool_workers: int = _int_env("SOURCE_POOL_WORKERS", 4) # 线程池大小 | |
| ``` | |
| **配置项说明**: | |
| | 配置项 | 默认值 | HF Free | HF Pro | 说明 | | |
| |---|---|---|---|---| | |
| | `MAX_CONCURRENT_SOURCES` | 2 | 2 | 10 | 最大并发源调用数 | | |
| | `SOURCE_POOL_WORKERS` | 4 | 4 | 10 | 线程池大小 | | |
| | `SOURCE_TIMEOUT_SECONDS` | 15 | 15-30 | 15 | 单个请求超时 | | |
| ### 3. 添加监控端点 | |
| **修改文件**: `app/api/routes.py` | |
| ```python | |
| @router.get("/admin/source-pool-stats") | |
| def source_pool_stats(): | |
| """获取源调用池的统计信息""" | |
| stats = get_source_pool_stats() | |
| return { | |
| "pool_stats": stats, | |
| "recommendations": { | |
| "worker_pool_saturated": stats.get("active", 0) >= stats.get("max_concurrent", 2), | |
| "suggestion": "如果 active >= max_concurrent,说明 worker pool 已饱和", | |
| }, | |
| } | |
| @router.get("/admin/health-detailed") | |
| def health_detailed(): | |
| """详细的健康检查""" | |
| pool_stats = get_source_pool_stats() | |
| return { | |
| "ok": True, | |
| "version": "v24-concurrent-control", | |
| "source_pool": pool_stats, | |
| "config": {...}, | |
| } | |
| ``` | |
| ## 部署指南 | |
| ### 步骤 1:更新环境变量 | |
| 在 HuggingFace Space 的 Settings > Repository secrets 中添加: | |
| ``` | |
| MAX_CONCURRENT_SOURCES=2 # HF Free tier 保持 2 | |
| SOURCE_POOL_WORKERS=4 # 线程池大小 | |
| SOURCE_TIMEOUT_SECONDS=15 # 可以适当增加到 30(如果响应慢) | |
| ``` | |
| ### 步骤 2:提交代码 | |
| ```bash | |
| cd stock_data_api_service | |
| git add -A | |
| git commit -m "fix: 引入全局源调用池,防止 HF worker pool 饱和 | |
| - 添加 GlobalSourcePool 类,使用 Semaphore 控制并发数 | |
| - 配置 MAX_CONCURRENT_SOURCES 和 SOURCE_POOL_WORKERS 环境变量 | |
| - 添加 /admin/source-pool-stats 和 /admin/health-detailed 监控端点 | |
| - 复用线程池,避免每次请求创建新的 ThreadPoolExecutor | |
| - 问题:board_flow/sector_concept_hot 端点因无并发控制导致 worker_pool_saturated" | |
| ``` | |
| ### 步骤 3:验证修复 | |
| 1. **监控端点检查**: | |
| ```bash | |
| curl -H "X-API-Key: YOUR_KEY" https://your-space.hf.space/api/v1/admin/health-detailed | |
| ``` | |
| 2. **并发测试**: | |
| ```bash | |
| # 同时发起 5 个并发请求(HF Free tier 只有 2 个 worker) | |
| for i in {1..5}; do | |
| curl -H "X-API-Key: YOUR_KEY" \ | |
| "https://your-space.hf.space/api/v1/boards/flow?category=industry&limit=10" & | |
| done | |
| wait | |
| ``` | |
| 3. **查看统计**: | |
| ```bash | |
| curl -H "X-API-Key: YOUR_KEY" https://your-space.hf.space/api/v1/admin/source-pool-stats | |
| ``` | |
| ## 调优指南 | |
| ### 场景 1:Free tier(1-2 workers) | |
| **配置**: | |
| ```env | |
| MAX_CONCURRENT_SOURCES=2 | |
| SOURCE_POOL_WORKERS=2 | |
| SOURCE_TIMEOUT_SECONDS=20 | |
| ``` | |
| **特点**: | |
| - 最大 2 个并发请求,其他排队等待 | |
| - 线程池大小 2,避免线程切换开销 | |
| - 超时 20s,给上游足够时间响应 | |
| ### 场景 2:Pro tier(10+ workers) | |
| **配置**: | |
| ```env | |
| MAX_CONCURRENT_SOURCES=10 | |
| SOURCE_POOL_WORKERS=10 | |
| SOURCE_TIMEOUT_SECONDS=15 | |
| ``` | |
| **特点**: | |
| - 最大 10 个并发请求 | |
| - 线程池大小 10 | |
| - 超时 15s | |
| ### 场景 3:上游响应慢(60s+) | |
| **配置**: | |
| ```env | |
| MAX_CONCURRENT_SOURCES=2 | |
| SOURCE_POOL_WORKERS=2 | |
| SOURCE_TIMEOUT_SECONDS=30 | |
| ``` | |
| **特点**: | |
| - 减少并发数,避免 worker 被长时间占用 | |
| - 增加超时时间,给上游更多时间 | |
| ## 性能指标 | |
| ### 修复前 | |
| - ❌ `worker_pool_saturated` 错误:多个并发请求时立即饱和 | |
| - ❌ 请求排队无限制,所有请求竞争 | |
| - ❌ 每次请求创建新的线程池,资源浪费 | |
| ### 修复后 | |
| - ✅ 最多 N 个并发请求(N = MAX_CONCURRENT_SOURCES) | |
| - ✅ 超出的请求排队等待,而不是立即饱和 | |
| - ✅ 线程池复用,资源效率提升 | |
| - ✅ 统计指标可用于监控和调优 | |
| ## 常见问题 | |
| ### Q1:为什么 `boards_flow` 和 `sector_concept_hot` 容易饱和? | |
| **A1**:这些端点的特征: | |
| - 调用 eastmoney/AKShare 的网络接口 | |
| - 响应时间不稳定(15-60s) | |
| - 没有本地缓存(不像 `/stocks/{code}/daily` 有长期缓存) | |
| - 并发调用时立即占用所有 worker | |
| ### Q2:如何判断 worker pool 是否饱和? | |
| **A2**:调用 `/admin/source-pool-stats`,查看: | |
| ```json | |
| { | |
| "pool_stats": { | |
| "active": 2, // 当前活跃请求数 | |
| "queued": 5, // 排队等待的请求数 | |
| "max_concurrent": 2, // 最大并发数 | |
| "completed": 100, // 已完成请求数 | |
| "timeout": 10, // 超时请求数 | |
| } | |
| } | |
| ``` | |
| 如果 `active >= max_concurrent`,说明 worker pool 已饱和。 | |
| ### Q3:应该增加 MAX_CONCURRENT_SOURCES 还是增加 SOURCE_TIMEOUT_SECONDS? | |
| **A3**:取决于你的 HF tier 和上游响应时间: | |
| | 情况 | 建议 | | |
| |---|---| | |
| | HF Free tier + 上游响应快(<15s) | 保持 MAX=2, TIMEOUT=15 | | |
| | HF Free tier + 上游响应慢(>30s) | MAX=2, TIMEOUT=30 | | |
| | HF Pro tier + 上游响应快(<15s) | MAX=10, TIMEOUT=15 | | |
| | HF Pro tier + 上游响应慢(>30s) | MAX=5, TIMEOUT=30 | | |
| **关键原则**: | |
| - `MAX_CONCURRENT_SOURCES` × `SOURCE_TIMEOUT_SECONDS` ≈ HF Worker 数 × 每个 worker 的时间 | |
| - Free tier:2 workers × 30s = 最多 60s 的并发时间 | |
| - Pro tier:10 workers × 15s = 最多 150s 的并发时间 | |
| ## 监控和报警 | |
| ### 指标 | |
| 1. **pool_stats.active** - 当前活跃请求数(>max_concurrent 时饱和) | |
| 2. **pool_stats.queued** - 排队请求数(>0 说明有排队) | |
| 3. **pool_stats.timeout** - 超时请求数(持续增加说明上游响应慢) | |
| 4. **pool_stats.failed** - 失败请求数(>0 需要排查) | |
| ### 报警规则 | |
| - `active >= max_concurrent` 持续 5 分钟 → 考虑增加 `MAX_CONCURRENT_SOURCES` | |
| - `timeout > 10` 持续 5 分钟 → 考虑增加 `SOURCE_TIMEOUT_SECONDS` | |
| - `queued > 0` 持续 10 分钟 → 考虑优化上游或增加 worker | |
| ## 参考资料 | |
| - HuggingFace Spaces Worker Pool: https://huggingface.co/docs/hub/en/spaces-overview | |
| - FastAPI Concurrent Requests: https://fastapi.tiangolo.com/async/ | |
| - Python ThreadPoolExecutor: https://docs.python.org/3/library/concurrent.futures.html | |