题干与适用场景
你负责广告点击预测的特征平台。训练样本包含 entityid、labelts 和标签,特征值会随着事件不断更新;线上请求还要求低延迟读取最新特征。请说明离线训练集如何做 point-in-time join,以及回填、迟到事件和线上服务如何处理。假设同一实体可能有多个特征版本,特征记录带有计算时间,训练标签时间是事件发生时间。
面试官考察点
面试官关注你是否先定义“可用时间”,再选择存储和连接方式。普通回答只说离线仓库加 Redis;高质量回答会明确 featurets <= labelts 的不变量、同一实体取不晚于标签时间的最新版本、没有历史值时返回 null,并区分训练时的历史视图与线上最新值。还要说明迟到数据会触发哪些样本重算,以及如何检测训练与服务的特征偏移。
回答前需要澄清的问题
- 标签时间代表业务事件发生时间还是标签写入时间?两者不同会改变连接边界。
- 预测允许使用事件发生后多久到达的特征?若只能使用严格实时可见数据,必须以计算完成时间或可用时间建模。
- 训练集需要重现历史版本,还是只需要最近窗口?前者要求保留完整历史,后者可以增加 lookback 限制。
- 线上读取要求的是截至请求时刻的最新值,还是截至某个事件时间的值?后者需要时间戳查询接口,不能只读单值缓存。
30 秒回答框架
“我会先把每条特征记录保存实体键、特征版本和可用时间。生成训练集时,对每个样本按实体键做 as-of join,只取 featurets <= labelts 的最新记录;没有满足条件的记录就保留 null。离线层保存历史,在线层提供低延迟最新值,二者由同一份特征定义和物化流程连接。迟到事件进入重算队列,重算受影响时间窗并版本化训练集。最后监控 join 后 null 率、特征新鲜度、线上线下分布和模型效果。”
分步骤深入解答
1. 先写出时间不变量
对样本 s=(e, tlabel) 和特征历史 He,选择集合 C={h∈He | h.featurets ≤ tlabel},结果是 arg max featurets(C)。这条规则直接阻止把未来特征带入训练。若业务语义要求“数据到达并可用”而非“事件发生”,应把 available_ts 纳入条件,而不是只比较事件时间。
2. 设计历史表与连接
历史表至少保存实体键、特征名或版本、特征值、featurets、availablets、来源批次和质量状态。训练样本带 label_ts。执行按实体键分区、按时间排序的 as-of join;同一时间有多条记录时使用来源序号或写入批次作为确定性 tie-breaker。精确时间等值连接会漏掉大多数样本,直接取最新值则会产生标签泄漏。
3. 分离离线与在线路径
离线存储适合保留完整历史并批量生成训练集;在线键值存储适合按实体快速读取当前值。物化任务从统一的特征定义生成两条路径,记录定义版本、输入快照和物化水位。线上读取允许短暂陈旧时返回最近值并附带 feature_ts,由调用方决定是否降级;不能假装缓存值与训练时刻相同。
4. 处理迟到事件、回填和版本
迟到事件先写入不可变原始层,再按受影响实体和时间窗排入重算。重算只替换对应训练快照版本,不覆盖已发布模型使用的快照。回填任务要幂等:输入批次和定义版本组成作业键,重复执行不会产生重复历史。若特征定义改变,生成新版本并同时记录旧版本,避免用新逻辑重写旧实验结果。
5. 验证与监控
离线检查抽样样本是否满足 featurets <= labelts,并统计无历史值的 null 率。线上监控读取延迟、特征年龄、物化延迟和错误率;对齐训练与服务的特征分布,发现缺失值处理或窗口长度不同造成的 skew。把样本快照、定义版本和输入水位写入模型元数据,才能复现一次训练。
6. 选择替代方案的边界
规模小、只有批量训练时,直接在仓库中使用窗口排序和 as-of join,避免引入完整 feature store。需要毫秒级线上读取、多人复用特征和持续回填时,再采用离线历史层、在线键值层、注册表和物化管道。把所有特征都实时计算会增加状态、成本和一致性风险;只保留最新值又无法重建历史训练集。
高质量示范回答
我会把“当时可见”定义成硬约束。每个实体的特征历史都带 featurets,如果数据的到达时间可能晚于事件时间,我还会保存 availablets。生成样本 (entityid, labelts) 时做 as-of join,取同一实体中时间不晚于 labelts 的最新版本;若产品语义要求数据真正可用,就同时要求 availablets <= label_ts。这比直接 join 最新值安全,因为后者会把未来信息泄漏进训练集。
架构上,离线层保存完整历史用于训练,在线层保存当前值用于低延迟推理,两个层都由同一份定义和版本驱动。迟到事件先落原始层,再触发受影响时间窗的幂等重算;已发布模型使用的训练快照不被覆盖,重算结果生成新版本。验证包括时间不变量抽样、null 率、新鲜度、线上线下分布和模型效果。只有在没有线上低延迟需求时,我才会省略在线层,保持批处理方案简单。
常见错误
- 错误表现:训练集直接 join 特征表的最新一行 → 未来更新值进入历史样本,离线指标虚高 → 使用按实体和标签时间的 as-of join。
- 错误表现:只保存事件时间,不保存可用时间 → 事件虽早发生但当时尚未到达,训练仍然使用了不可见数据 → 在有迟到或批处理延迟时记录
available_ts。 - 错误表现:回填直接覆盖现有特征历史 → 已训练模型无法复现,审计失去依据 → 以输入批次和定义版本生成不可变快照。
- 错误表现:线上和离线各写一套转换代码 → null 处理或窗口边界漂移,产生 training-serving skew → 共享定义或用同一组黄金样本做一致性测试。
追问及应对
如果特征事件在标签之后到达,但事件时间早于标签时间,能否使用?
不能只看事件时间。若线上在标签时刻无法看到该事件,应要求 availablets <= labelts;否则训练集会模拟出线上不可能拥有的信息。可以把事件时间和可用时间都保存下来,并在不同业务承诺下选择严格或宽松规则。
线上需要查询过去某一时刻的特征,当前值缓存还够吗?
不够。当前值缓存只能回答“现在最新值”,无法回答历史时刻。应提供带时间戳的历史查询或从离线层生成时间点快照;若延迟预算不允许在线历史查询,应在请求链路提前物化所需版本。
迟到事件持续到达,如何避免重算成本失控?
按实体、时间窗和特征版本合并重算请求,设置最大回看窗口和优先级。超过窗口的事件进入人工或离线批次,并记录哪些模型版本未重算。用受影响样本数、重算耗时和队列年龄监控成本,而不是无限追赶所有历史。