实时特征偏差,可能藏在 Flink 水位线里
Kafka 与 Flink 的迟到事件策略决定模型预测时能看到哪些数据,批量回放训练则可能看到更完整的数据,形成系统性的训练与线上偏差。文章建议对迟到策略做版本管理,记录实际服务时可见的数据,并按来源衡量延迟。
AI 深度解读
Kafka 和 Flink 管道跑得正常,模型线上效果却比离线评估差,问题可能藏在一个很少进入模型实验配置的参数里:水位线(watermark)。它影响模型读取特征时,有多少已经发生的事件真正被算了进去。
作者的核心主张是:流处理的迟到策略应该像模型超参数一样,被记录、版本化和评估。文章没有给出实测收益,但解释了一种容易被当成模型漂移的数据偏差,也提供了排查路径。
模型读到的计数,究竟数了什么
先区分两个时间:事件时间(event time)是事情实际发生的时间,处理时间(processing time)是系统处理它的时间。手机离线、请求重试、合作方批量推送、跨地域复制,以及部署后的消费积压,都可能拉开两者的距离。
watermark 是流处理器对事件进度的估计:系统认为,事件时间早于 T 的数据大致已经到齐。常见依据是有界乱序假设,例如允许事件最多乱序 N 秒。
它不是完整性证明。
当 watermark 越过窗口结束时间,窗口通常会触发计算。之后到达的数据如何处理,还要看允许迟到时间(allowed lateness)等配置:可能被丢弃、进入侧输出,也可能更新之前的结果。因此,「窗口已经触发」与「后续数据绝不会再计入」需要分开理解。
以「最近十分钟的银行卡交易次数」为例,业务想要的是实际发生的交易总数,模型读到的却是当前计算结果里已经纳入的交易数。两者之间隔着数据到达速度、窗口触发机制和迟到处理规则。
迟到策略参与了特征的定义,而不只是影响计算速度。
这也是作者把它称为隐藏超参数的理由。团队可能反复调整 learning rate、context window 和 retrieval top-k,却把决定特征完整程度的配置留在流处理作业里,没有纳入实验记录。
为什么离线更完整,反而会让评估失真
典型链路是:线上读取实时流作业生成的特征,训练则从完整事件日志或事后整理的数仓表重算特征。
等批处理开始时,迟到事件往往已经落地。于是,同一段业务历史在线上和离线被表示成了不同的数据。
作者用计数说明这种差异:某个实际场景有 7 次交易,离线重算得到 7;线上预测发生时,还有 2 条事件在路上,模型只能读到 5。训练时学到的「7 对应什么风险」,未必适用于线上看到的「5」。
这里的偏差具有方向性。对于交易次数这样的累计计数,遗漏事件会让数值偏低,而不是围绕真实值随机上下波动。不过,不能把这一方向直接推广到所有特征,均值、比率等指标如何变化,还取决于缺失的具体数据。
更棘手的是,迟到数据可能与预测目标相关。网络较差的用户可能具有不同的行为;事故期间的重试风暴可能恰好伴随异常;批量交付数据的合作方可能覆盖不同人群;欺诈流量也可能借助延迟伪装行为。
这些是作者提出的风险机制,并非文中已经验证的因果关系。但它们足以提示:只看全局特征均值、吞吐量和作业状态,可能发现不了问题。管道可以一直正常运行,模型却持续在某些人群上接收不完整输入,看起来就像发生了漂移。
Kafka 分区为什么会把新鲜度和完整性拉向两端
问题还会出现在多个输入的协调上。作者指出,流处理器通常受最慢输入的 watermark 约束。
假设一个 Kafka 分区因为流量很少或上游故障,长时间没有新事件。如果它持续拖住整体 watermark,窗口就可能迟迟不触发。服务仍能读取已有特征,但读到的是越来越旧的值。
配置空闲超时(idle timeout)可以让系统暂时不再等待这个安静的分区,其他输入便能继续推动 watermark。
代价在分区恢复后出现:部分事件相对于已经前进的 watermark,可能已经属于迟到数据。若处理策略直接丢弃,这些分区所承载的 key 就会集中遭受数据损失。
所以,idle timeout 并不能单独解决完整性问题。它缓解了等待,同时把后续处理压力交给迟到策略。是否真的丢数据,还取决于 allowed lateness、侧输出和修正机制。
作者据此提出了一组治理办法:
- 记录预测时实际提供给模型的特征向量,等标签到达后再关联,训练数据便能保留当时真实的信息状态,包括事故和分区停滞期间的状态。
- 必须回放时,使用相同的流处理逻辑和迟到配置。没有迟到截断的 batch SQL,即使产出同名字段,也可能算出了另一种特征。
- 将 allowed lateness、乱序容忍边界和 idle timeout 与模型产物一起固定版本。为了降低延迟而缩短等待时间,也应经过模型评估。
- 按上游来源统计事件时间到到达时间的间隔分布,不能只看一个全局 p99;同时统计迟到丢弃量,并按模型关心的人群或特征切分。
- 向模型或服务策略提供数据年龄、完整程度等信号。数据过旧时,可以降低对当前特征的依赖,或切换到更稳妥的默认策略。
这些做法最终指向一个业务选择:到底愿意等待多久。推荐、排序可能更重视低延迟;欺诈、风控可能更在意迟到尾部携带的信号。作者还提出两阶段设计:先给临时评分,窗口关闭后再给修正评分。
但修正评分是否还有用,要看业务动作能否撤销或补救。交易已经完成之后,更准确的分数未必还能改变这笔交易的结果。
我的判断
我认同作者把迟到配置纳入模型治理。只要配置能改变线上特征的取值,它就值得进入实验记录和发布评估。平台团队追求延迟,算法团队追求离线指标,若双方没有共同描述特征在预测时究竟包含什么,偏差很容易藏在交界处。
不过,作者提出了记录线上特征、按相同迟到配置回放等建议,没有给出数据集、配置对照、准确率变化或延迟成本曲线。「记录线上特征最可靠」应理解为对齐实际输入的一条直接路径,不能据此宣称它已经被证明优于所有回放方案。
回放建议也有一个没有展开的前提:仅有相同配置和事件时间,并不足以保证复现当时的信息状态。 如果回放把所有事件按事件时间排好,再送入计算,原来的乱序、停顿和空闲分区行为可能已经消失。文章没有说明如何保留到达顺序、到达时间以及推进 watermark 所需的条件。
线上特征日志同样有边界。它记录了旧策略实际产生的输入,却不能单独回答「当时如果多等一会儿,效果会怎样」。比较不同迟到策略,仍需要原始事件和能够还原过程的记录。
和只做离线重算相比,记录线上特征更适合检查训练与服务是否一致;离线完整历史则适合分析漏掉了什么。两种数据回答的问题不同,可以配合使用。
能拿来做什么
- 如果你负责 Node 服务中的模型调用,先检查预测日志能否关联实际特征向量、特征版本和后续标签。只记录模型返回分数,无法追查模型当时看到了什么。
- 如果项目使用 Kafka 和 Flink,把 allowed lateness、乱序容忍边界和 idle timeout 加入模型发布记录。修改这些配置时,比较受影响来源和人群的特征变化,再决定是否上线。
- 给实时特征补上按来源统计的迟到分布、丢弃量和新鲜度监控。排查线上效果下降时,将这些指标与消费积压、上游故障及部署时间对齐,检查是否先出现了数据变化。
- 如果准备采用临时评分加修正评分,先明确业务如何消费第二次结果,包括能否更新决策、触发补救或仅用于后续分析,再设计流处理输出。