fix(prod): 调度脚本改用 bundle venv 直调,修复「uv run 运行期联网」导致日更整体中止
故障(2026-09-19 15:40 实测)
------------------------------
`tushare_sync.sh daily` **一张表都没抓**,日志只有一行:
── T0 每日同步 (日线 + 资金面 + 事件公告) ──
table_group: daily (6 tables)
error: Request failed after 3 retries in 4.5s
cause: Failed to fetch: `https://pypi.tuna.tsinghua.edu.cn/simple/structlog/`
cause: dns error
cause: failed to lookup address information: nodename nor servname provided
根因
----
两个调度脚本都用 `uv run <entrypoint>` 调用。**`uv run` 会在运行时同步/重建依赖
环境**,需要联网下载缺失包 —— 当天该请求 DNS 解析失败;又因脚本带
`set -eo pipefail`,这条 error 直接**中止整条任务**。
手动复现同一条命令还看到额外危害:
Building tushare-fetcher @ file:///...
Downloading mypy (13.0MiB)
Installed 14 packages in 31ms
即**生产运行期会联网改写自身 venv 的依赖**(把 mypy 装进生产环境)。
改动
----
`tushare_sync.sh`(17 处)与 `akshare_fetcher/scripts/sync_all.sh`(14 处)
全部改为直调 bundle 内的可执行文件:
TUSHARE_BIN="$PROJECT_DIR/.venv/bin/tushare-fetcher" # 原 uv run tushare-fetcher
AKSHARE_BIN="$PROJECT_DIR/.venv/bin/akshare-fetcher" # 原 uv run akshare-fetcher
VENV_PY="$PROJECT_DIR/.venv/bin/python" # 原 uv run python3 / --no-sync python
并在使用前校验可执行文件存在,缺失即明确报错退出(而不是让 uv 去"修好它")。
→ 不联网、不重建、也不依赖 uv 是否在 PATH 上(launchd 环境 PATH 极简,这本身也是风险)。
验证(端到端实跑)
------------------
- 当日日志现为:15:40 那次(第 1–8 行,含 dns error)之后,17:46:50 起新运行
至 17:48:45 **完成,exit 0,464 行,0 error**
- 6 个 table_group 全部执行到(含本批新增的「事件公告」与「补排期-日频」两组)
- 银层迁移同批完成:11,246 行写入 fin_data_clean.stock_monthly,blocked=False
- akshare 路径(18:00 任务)冒烟通过,无 uv 报错
说明:今天为周六(非交易日),故本次故障的**数据**影响有限(无新行情可采),
但该缺陷在每个工作日都会造成**整日零采集**,属最高优先级修复。
附带提交(同日稍早完成、一并入库)
----------------------------------
- `tables_registry.json`:`us_basic` 唯一键由 ["ts_code"] 修正为
["ts_code","list_date","name"]。同一 ticker 会被不同公司复用(AEM/ABX/AGI 等),
旧键导致静态表 replace 写入报 (1062, Duplicate entry),整批失败。
已按 MySQL NULL 语义实测验证为 0 冲突,DB 侧唯一索引同步重建。
- `fut_wsr` 追加警告:接口返回行在 (trade_date,symbol,warehouse) 下可重复且
**无自然唯一键**(同仓库同日多条仓单,vol 不同),按唯一键 upsert 会静默覆盖丢行。
- 新增 `Scripts/bin/verify_unique_keys.py`(校验 registry 声明的唯一键在真实数据上
是否唯一,含 MySQL NULL 语义与组合搜索建议)与 `Scripts/bin/inspect_dup_rows.py`
(打印重复行以确定真实唯一键)。