fix(bms): #335 低频采集两类失败 —— bms_order OOM 循环 + 导出异常请求诊断
Some checks failed
gitleaks / gitleaks (push) Failing after 10s
backend-test / commit-message-lint (push) Successful in 3s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 14m3s
backend-test / frontend-lint-test (push) Failing after 36m8s
Some checks failed
gitleaks / gitleaks (push) Failing after 10s
backend-test / commit-message-lint (push) Successful in 3s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 14m3s
backend-test / frontend-lint-test (push) Failing after 36m8s
- _fetch.py 两处全量导出读取(openpyxl→calamine): bms_order 导出被 BMS 硬截断 10 万行, openpyxl DOM 全量加载峰值 RSS>4GiB 触发 memcg OOM (dmesg 实证 anon-rss 4161640kB), gunicorn 被杀→重启→僵尸清理标 failed, 2026-10-13 起每小时循环; calamine 内存降一个量级(data_parser 同款先例) - docker-compose.yml backend 内存 4G→8G 双保险(主机 23G 余量充足) - _browser.py 导出 JSON 异常时记录请求 url/method/post_data(诊断): 定位 return_tracking 页面把日期序列化为 ISO-UTC(BMS 10-11 收紧只认 yyyy-MM-dd HH:mm:ss)的实际端点与 payload, 为下一步 route 改写供事实 - ruff 全绿; bms/fetch/browser 相关测试 EXIT=0 Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
This commit is contained in:
@@ -522,6 +522,20 @@ async def _download_with_retry(
|
||||
try:
|
||||
body = await resp.text()
|
||||
export_rate_limited["msg"] = body[:200]
|
||||
# #335: 导出异常时记录请求端点与 payload ——
|
||||
# 定位 BMS 前端实际发送的参数形态(如 return_tracking
|
||||
# 日期被页面序列化为 ISO-UTC 触发 400), 为 route
|
||||
# 改写提供事实依据; 正常下载不经过此路径。
|
||||
try:
|
||||
req = resp.request
|
||||
logger_obj.warning(
|
||||
"[%s] 导出异常请求诊断: url=%s method=%s post_data=%r",
|
||||
data_type, resp.url,
|
||||
req.method if req else "?",
|
||||
(req.post_data or "")[:500] if req else "",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
export_rate_limited["msg"] = "(body 读取失败)"
|
||||
# 用 create_task 挂后台,避免同步回调里直接 await 协程;
|
||||
|
||||
@@ -1976,7 +1976,11 @@ class BmsFetcher:
|
||||
import pandas as pd
|
||||
suffix_l = str(dest_path).lower()
|
||||
if suffix_l.endswith(".xlsx"):
|
||||
df = pd.read_excel(dest_path, engine="openpyxl", dtype=str)
|
||||
# #335: 大文件读取换 calamine(Rust 流式) —— openpyxl 全量 DOM 加载,
|
||||
# bms_order 导出被 BMS 硬截断 10 万行时峰值 RSS > 4GiB 触发 memcg OOM
|
||||
# (gunicorn 被杀 → 服务重启 → 僵尸清理标 failed, 2026-10-13 起每小时循环)。
|
||||
# calamine 内存降一个量级; data_parser 同款引擎(其 514 行先例)。
|
||||
df = pd.read_excel(dest_path, engine="calamine", dtype=str)
|
||||
elif suffix_l.endswith(".xls"):
|
||||
try:
|
||||
df = pd.read_excel(dest_path, dtype=str)
|
||||
@@ -2096,7 +2100,8 @@ class BmsFetcher:
|
||||
import pandas as pd
|
||||
suffix_l = str(dest_path).lower()
|
||||
if suffix_l.endswith(".xlsx"):
|
||||
df = pd.read_excel(dest_path, engine="openpyxl", dtype=str)
|
||||
# #335: 同上 —— 全量导出(10万行硬截断)读取换 calamine, 防 memcg OOM
|
||||
df = pd.read_excel(dest_path, engine="calamine", dtype=str)
|
||||
elif suffix_l.endswith(".xls"):
|
||||
try:
|
||||
df = pd.read_excel(dest_path, dtype=str)
|
||||
|
||||
@@ -147,11 +147,14 @@ services:
|
||||
start_period: 30s # 预留迁移/种子/调度器启动时间
|
||||
# 上限(#279): 4G 给 Playwright chromium + ddddocr onnx 留余量(采集期单容器峰值
|
||||
# 观测约 1.5-2.5G); 2 CPU 防止采集/导出任务饿死健康检查响应。
|
||||
# #335: bms_order 全量导出(10万行硬截断)采集期峰值破 4G → memcg OOM 杀 gunicorn
|
||||
# (2026-10-13 起每小时循环, 详见 #335); _fetch 读取已换 calamine 降一个量级,
|
||||
# 内存再放宽到 8G 双保险(主机 23G, 余量充足)。
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2.0'
|
||||
memory: 4G
|
||||
memory: 8G
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
Reference in New Issue
Block a user