Posts 给数据做每日巡检:8 项内检、3 个外锚和一次上游断供
Post
Cancel

给数据做每日巡检:8 项内检、3 个外锚和一次上游断供

  6 月 8 号,我把采集数据的手工核对步骤写成每日任务 scripts/audit-daily.mjs,挂到 pm2 的 cron 上。它审计刚归档的那一天,结果发到 Telegram:全绿就一行 ✅,有问题就列详情。

  这篇记一下这套巡检这一周(06-08 到 06-14)的样子,以及它自己出的几次问题。

8 项内检 + 3 个外锚

  内检只看我采到的数据,检查它内部是否自洽:

  • A:采集进程 /health 上的计数,断线、价格流静默超过 60 秒、盘口连接数不足、溢出丢弃、flush 失败,任何一项非零都算失败;
  • B:Chainlink 全精度列和 float 列是否一致,以及每个币的最大 tick 间隔;
  • C:我存下来的币安盘口顶部有没有买价大于等于卖价的交叉报价;
  • D:每个 token 当天最后一个盘口快照是不是交叉簿;
  • E:成交价是否落在合法价格域内;
  • F:用我采到的数据独立重算每个市场的结算结果,和官方 outcome 对比;
  • G:Chainlink 和币安两个来源的价格是否互相印证;
  • 第八项是 ghost ask-sum 检查。

  外锚拿官方数据来对:Polymarket 官方结算(第一次跑是 101/101 一致);从 CLOB REST 接口实时拉官方盘口,和我重建的盘口对比;再把币安官方 REST 的 1 分钟 K 线和存下来的报价逐分钟对比(高低区间差在 1 美分级别)。

  F 是我主要看的检查项。Up/Down 市场的规则是比较结束那一秒的 Chainlink 价格和开盘那一秒的 strike。这两个价格我都直接采了,可以独立重算每个市场的结果。比较必须用 BigInt,全精度值是 1e18 的整数,float 装不下。

  盘口数据如果有争议,裁决以成交流水 last_trade_price 为准。它是原样存下来的,也不经过盘口重建那条代码路径,和被质疑的东西是独立的。

阈值本身也会错

  E 最早写的是成交价必须在 [0.01, 0.99] 之间,这是错的。有些市场的最小价格单位是 0.001,临近结算时合法地在 0.999 和 0.001 成交。协议层面的硬边界是 [0.001, 0.999],按旧阈值会把这些正常成交报成违规。

  F 会跳过缺 strike、缺结束那一秒的 tick,或者还没结算的市场。要是哪天回填没跑,或者查询写错,所有市场都可能被跳过。F 会报 0/0 一致,照样是绿的。我补了两条:当天有市场结束但 0 个已结算,判失败;已结算市场里实际重算覆盖不到一半,也判失败。

巡检和导出撞车

  6 月 8 号是第一个完整数据日,810 万行、524MB。6 月 9 号早上巡检报红,H 说读不到 manifest,I 说累计日 K 线表里没有这一天。H 和 I 是两项归档完整性检查,读的是导出产出的 manifest 和 K 线表。

  数据没问题,两个任务撞到了一起。导出 00:50 UTC 开始,这一天是完整数据,manifest 到 01:15:30 左右才写完,巡检却定在 01:15:00。它运行时归档还没发布,当然读不到。

  把巡检挪到 02:30,比导出开始晚了 100 分钟,按当前体量大概还有 4 倍的余量。顺带发现文档里导出和另一个 00:20 的构建任务的 cron 时间写反了,一起改正。这几个任务的顺序不能对调,也不能两个重任务并发跑。

保存巡检失败记录

  同一天还改了失败记录。巡检失败时只有两个出口:Telegram 消息,以及 stdout。但发 Telegram 的那个 fetch 后面挂着 .catch(() => {}),发送失败会被静默吞掉;stdout 的日志又会被 pm2-logrotate 截断。两个一起失效的话,巡检失败就没有记录了。

  失败时,我会额外把一份机器可读的 JSON 写到 logs/audit-failures/<date>.json。这个目录在 gitignore 里,logrotate 也不会碰。当天重跑变绿后就删掉文件,所以「文件存在」就代表「这一天当前是红的」,不用再翻日志。写文件包在 try/catch 里,磁盘满了写不进去,也不能挡住 Telegram 主告警。Telegram 发送失败则往 stderr 明确打一行。退出码本来就是非 0,没有改。

06-11:上游断供

  6 月 12 号的巡检是真红:F 报 1394 个市场缺 strike,还有一个 DOGE 市场重算结果和官方不一致。

  我先确认到底是自己断了,还是上游断了。把三条链路按 recv_ms 分桶对比:relay 价格流、CLOB 盘口事件、直连币安的报价。关键证据就在 relay 的 WebSocket 上:它同时订阅 Chainlink 和币安镜像两个 topic。币安镜像全天正常推送,只有 Chainlink 那个 topic 停了。同一条连接上,一个 topic 在流,另一个静默,说明不是我这边断线,而是上游停发。CLOB 和币安直连也全程正常。

  时间线是 UTC 03:00 左右开始劣化,04:27 到 13:18、13:47 到 16:02 两段完全中断。当天 tick 丢了大约一半。看门狗对这种情况无能为力,它看的是所有 topic 加起来有没有 tick,币安镜像一直在推,连接看上去就一直是活的。

  1394 个市场的 strike 永久缺失。strike 是开盘那一秒的 Chainlink 价格,gamma 元数据里没有这个字段。那一秒我没收到,也没有别的来源可以补。那个 DOGE 的分歧也是同一个原因:价格贴着 strike,偏偏丢了关键的一帧。

  从这次以后,巡检报红时我会先判断是不是上游问题。确认上游断供后,只报告根因,不做完整性补救,也不把这段时间加进审计排除清单,更不改对外口径。覆盖度报告本来就会列出当天断档。红色巡检的失败文件也继续留着,因为断供确实发生过。如果是我自己的进程、代码或部署出了问题,就照常修。

06-13:巡检自己的结算口径错了

  6 月 14 号又红了,这次是 06-13 两个市场(XRP 和 SOL 的 15 分钟市场)F 报分歧。查下来数据是对的,错的是巡检。

  原来的写法是取结束时刻之前(≤ end_sec)最近的一个 tick,用 > 和 strike 比。有两个问题。一是官方规则其实是「大于等于」:我把所有 20608 个市场的描述都查了一遍,全部写的是 “greater than or equal to”,平局归 Up,原来内部文档里写的 > 是错的。二是结束价应该取 feed_ts_ms = end_sec 那一秒的边界 tick,和 strike 取开盘那一秒是对称的。

  这两个市场的情况是结束那一秒刚好缺了 tick,前一秒和后一秒的价格分别落在 strike 两边。旧写法拿了前一秒,得出和官方相反的结果;后一秒那个 tick 其实和官方的 Up 一致。

  新口径:Chainlink 大约 1Hz、整秒对齐,正常情况下结束那一秒的 tick 就在,直接用它。缺这一秒时取前后各 10 秒内最近的两条,如果它们在 strike 同一侧,就判为确定;如果分跨两侧,就记成「边界不可判」,不裁决。不可判的数量写进报告,超过 5 个才告警。改完之后全天复跑,分歧 0,一致 2609。

归档延后一天

  这一周几次报红时,那一天都已经归档发出去了。数据真有问题,只能再导一遍。6 月 14 号,我把流程拆成两段:

  • 导出加了 ARCHIVE_LAG_DAYS,采集机上设成 2,每天归档的是前天,留出一天的修复窗口;
  • 巡检拆成两段。--phase=pre 在 02:30 审昨天的数据,也就是明天才归档的那天,跑 A 到 G 加外锚,F 也在里面,但跳过依赖 manifest 的 H 和 I,这一段是提前预警;--phase=post 在 02:45 审今天凌晨刚归档的那天,完整跑一遍含 H、I,并在实际发出去的快照上再验一次 F。两段错开 15 分钟,避免两个重的读循环并发。

  改的时候还处理了两处。一是 post 那段沿用了原来的 pm2 进程名,只改参数和 cron。如果起个新名字,重新部署后旧的那个不带参数的进程会留下来继续在 02:30 跑全量,每天在 manifest 还没写的时候去查 H、I,造成误报。二是失败文件按 phase 分开命名(<date>.pre.json / <date>.post.json),不然 post 变绿时会把 pre 留下的红色记录删掉。

  还要照顾数据库清理。清理本来就是 fail-closed 的,只删 manifest 存在的已归档日,没归档的缓冲日不会被删。但归档延后一天,「归档到删除」之间就从 3 天缩到了 2 天,所以约束是保留天数必须不小于 lag + 1。

  从 lag 1 切到 lag 2 的这几天有一个一次性的过渡:06-13 在 06-14 按旧规则归档过,06-15 按新规则又指向了 06-13,于是被重导一次。导出是完全幂等的,重导无害。06-14 则顺延到今天 06-16 归档,晚一天,没有丢数据。

  这一周的数据问题是 06-11 的上游断供,其余几次报红都是巡检的阈值、时间或口径错误。


outcometick 工程笔记(2/21)

上一篇:采集第一周:磁盘、僵尸连接和跨午夜的市场

下一篇:每日巡检的假红:三周里修掉的几处误判

This post is licensed under CC BY 4.0 by the author.

采集第一周:磁盘、僵尸连接和跨午夜的市场

每日巡检的假红:三周里修掉的几处误判