死锁根因:sds-refresh-pending 与 label-ocr 整点并发写同一分区 Orders_2026_08, 互等对方未提交事务(ShareLock)。24h 内同类死锁 7 起。 - sync_orders_and_goods 所有写事务先取同一把 PG advisory 锁(事务级),单锁无环,死锁结构性消除 - 同步按批(1000单)独立提交,持锁窗口从整个 pending 集缩到单批 - 连接预设 lock_timeout=15s,DeadlockDetected/LockNotAvailable 整事务重试 3 次(退避1/3/7s) - label-ocr 的 update_ocr_batch/update_ocr_fields 走同一把锁+重试 - sds/riin refresh_pending 的 upsert_records 加任务级重试(retries=3)
6.9 KiB
6.9 KiB
AGENTS.md
ETL 工作流项目,使用 Prefect 编排。 任务:REST API → PostgreSQL(Extract / Transform / Load)。
设计理念(简述)
- 高内聚、低耦合、关注点分离。
api/是纯客户端层(httpx + pydantic,禁止 import prefect),封装 RIIN/SDS 两个外部系统的 REST API,可独立复用/测试。flows/是编排层(依赖api/,使用 prefect),负责重试/缓存/并发/调度/密钥。- 共享行为(token 注入、401 重登、分页、批量、缓存)放
api/_common/,用组合式 helper,不过度抽象。 - 新增系统是加法式:加一个
api/<系统>/+ 一个 flow,不碰旧代码。 - 详细架构说明见
docs/dev/systematic-design.md。
依赖管理:使用 uv(不要用 requirements.txt)
本项目用 uv 管理依赖,锁定文件为 uv.lock。
没有 requirements.txt,也不要新建。
uv sync # 按 uv.lock 安装依赖到 .venv(首次/拉取代码后执行)
uv add <package> # 添加依赖(自动写入 pyproject.toml + 更新 uv.lock)
uv add --dev <package> # 添加开发依赖
uv remove <package> # 移除依赖
uv lock --upgrade # 升级所有依赖的锁定版本
uv run python <file> # 在项目环境中运行(无需手动激活 venv)
uv run prefect <cmd> # 运行 prefect CLI
- Python 版本固定为
>=3.12(见pyproject.toml)。 - 所有命令都用
uv run ...前缀,不要直接用系统python/pip。 - 提交代码时一并提交
pyproject.toml和uv.lock;不要提交.venv/。
运行环境
- Prefect server + worker:自托管,通过 Docker Compose 运行(server + worker 容器),数据持久化。
docker compose up -d # 启动 server(UI: http://192.168.124.92:4200)+ worker docker compose ps # 查看状态 docker compose logs -f prefect-worker # 看 worker 日志 docker compose down # 停止 - API 地址:
http://localhost:4200/api(本机)/http://192.168.124.92:4200(局域网)。 - 客户端已配置:
uv run prefect config set PREFECT_API_URL="http://localhost:4200/api"。
部署模型:Work Pool + Git 拉取(推送即生效)
- 代码托管在 Gitee:
git@gitee.com:inkreachZLL/inkreach-prefect.git(分支master)。 - worker 是 Docker 容器(
prefect-worker服务,基于Dockerfile),镜像只装依赖(git + uv 按锁文件装),不烤业务代码。 - 每个 flow run 执行前,pull 步骤从 Gitee clone 最新代码(token 存
gitee-tokenSecret Block,运行时解析)。 - 改 flow 代码 →
git push→ 下次 run 自动用新代码(worker 不重启、不 redeploy)。 - 改 deployment 配置(cron/参数)才需重跑:
uv run prefect deploy --all(读取prefect.yaml)。 - 日常开发流程:本地开发机 clone Gitee 仓库 → 改代码 →
git push→ 自动生效。
密钥管理(标准做法)
- 不要把密钥写进代码或 pyproject.toml。
- 数据库凭据用 Prefect Block 加密存储,Block 名:
pg-inkreach-dmp(SqlAlchemyConnector类型)。from prefect_sqlalchemy import SqlAlchemyConnector connector = SqlAlchemyConnector.load("pg-inkreach-dmp") engine = connector.get_engine() - RIIN / SDS 登录凭据用
SecretBlock 加密存储,值为 JSON:{"username":"...","password":"..."}。 Block 名:riin-credentials、sds-credentials。flow 在 extract task 内部读取,不外泄。 - Gitee 拉取令牌:
SecretBlockgitee-token(裸 token,pull 步骤用oauth2:{{ secret }}@gitee.com内嵌)。 - 本地开发备用:
.env(已 gitignore)含DB_DSN,加载用python-dotenv或os.environ。
编写/运行工作流
- 同步任务(对齐 inkreach-bi-auth-mng 的 sync tasks)放在
flows/riin/和flows/sds/,每系统各 6 个:order_sync(按时间窗口)、incremental_sync(按 DB 最近下单时间增量,无参)、recent_sync(近 N 天)、refresh_pending(刷新未完结)、by_orders(RIIN 指定单号)/refresh_100_prefix(SDS 100 前缀)、verify(逐天核验修复)。
- 共享层在
flows/shared/:mappers.py(RIIN/SDS→统一 Order/Goods)、db.py(PG upsertSyncDB,写并发控制:advisory 锁串行 + 分批提交 + lock_timeout + 死锁重试)、clients.py(客户端工厂+顺序分页+重试+渠道字典)。 - 本地直接跑(调试):
uv run python flows/riin/<file>.py - 部署到 work pool:
uv run prefect deploy --all(按prefect.yaml,全部 12 个 deployment 挂inkreach-pool)。 - 手动触发一次:
uv run prefect deployment run '<name>/<name>' - 写入目标:PG 的
Orders/Goods/Merchant表(主键Orders=("orderNo","createdAt")、Goods=("orderNo","productionNo")),createdAt写入前以库中已有值覆盖以保证幂等。
注:
serve_all.py为旧进程式部署,已被 work pool 模型取代,保留仅作参考。
企微机器人告警/上报(monitor)
- 发送层
flows/shared/wecom.py:send_markdown/send_text,wecom-aibot-sdk 按需连接(连接→认证→发送→断开),全容错。 凭据在SecretBlockwecom-aibot-credentials(JSON:bot_id/secret/chat_id);chat_id 用scripts/wecom_capture_chatid.py捕获。 flows/monitor/alert_failure.py(wecom-alert):prefect.yaml 里声明的事件触发器,任意 flow runFailed/Crashed自动运行并发群报警,零代码侵入,新 flow 自动覆盖。flows/monitor/daily_report.py(daily-report,18:30):近 7 天 RIIN/SDS 核验(subflow 复用 verify,先报订单数量/商品行数是否一致,再报状态不符单数"已自动刷新修复",verify 有差异时修复后重数即verifyReport.post)+OCR 当日处理量(按Orders.ocrProcessedAt盖章,db.count_ocr_today)+近 24h 失败任务明细(workflow/run/次数/时间)。flows/monitor/device_check.py(device-check,09:00):按 24h 采样报内存/swap/GPU 的平均·峰值·最低 + 磁盘 总体/根目录/home 余量与"增长最快目录"(du 快照对比)+ GPU 温度;另查 Docker 容器异常(docker.sock)、Prefect 失败、OCR/PG 探活。阈值 80% 报警。metrics-agent侧车容器(docker-compose 服务,非 Prefect flow):scripts/metrics_agent.py每 5 分钟采样内存/swap/GPU 写data/metrics/metrics-YYYY-MM-DD.jsonl,每日 7 点后 du 快照data/metrics/disk-*.json(实际占块;容错个别文件消失的 rc=1);保留采样 14 天/快照 7 份。device-check 经/data/metrics:ro读取,采样缺失自动降级为瞬时值。- worker 容器已挂载
/:/host:ro、/var/run/docker.sock、GPU、./data/metrics:ro(见 docker-compose.yml),勿移除,否则对应检查降级;镜像含 tzdata(ENV TZ=Asia/Shanghai)。