Alpha-Online
踩坑复盘

无限拉取死循环

朴素增量逻辑 if latest > last 遇上「永远没有新数据」的标的,会把每日任务拖成一场无声的无限重试。#19 事故档案。

层级 ODS · 数据接入  严重度 高  关联 #19 · commit 8c493ff · data_loader.py database.py

朴素的增量同步逻辑 if latest > last,在遇到无数据可拉取的标的时,会导致每日增量任务陷入无限重试。

现象

MMC、CTRA 等标的在 IEX 免费源无数据覆盖。每次任务从旧时间戳出发、尝试补齐至当日,返回空数据后下次重复同一操作——CPU 资源与 Alpaca API 配额被持续消耗,任务耗时逐日递增,但无任何异常抛出。

定位

顺着「为什么 pipeline 越来越慢」往回查,发现少数几只标的每天都在重复拉取同一段空区间。问题出在增量判定那一行:

# 旧逻辑:只要落后于今天,就无脑补
if latest_date > last_synced:
    fetch(from=last_synced)   # 空源标的永远满足 → ∞

latest_date(交易日历的今天)永远大于 last_synced(这只标的最后一次有数据的日子),条件恒为真

根因

增量同步的可靠性取决于异常路径的处理。「数据源不覆盖该标的」与「数据尚未到达」是两种本质不同的状态,朴素逻辑未加区分——将「标的已退市 / 数据源不覆盖」误判为「仍有待补齐的增量」。

修复

新增 sync_status 水位线表(src/utils/database.py),为每个 (symbol, market, source, interval) 记录:

  • last_checked_date —— 上次「检查」到哪天(不是上次「有数据」到哪天)
  • consecutive_empty_days —— 连续多少天拉回空数据
  • status —— active / 停用

data_loader.py 拉取前先读水位线:已停用的直接跳过;连续空数据达到 CONSECUTIVE_EMPTY_WARN_THRESHOLD 告警、达到 CONSECUTIVE_EMPTY_SUSPEND_THRESHOLD 则标记停用。一旦真的有新数据,consecutive_empty_days 归零。

教训:静默失败比显式异常代价更高。增量同步的难点不在于正常的数据拉取,而在于对「无数据可拉取」状态的正确识别与处理。

关联

On this page