题干与适用场景
请为实时欺诈评分系统设计一条 ML 特征流水线。训练集覆盖过去 12 个月,线上每秒约有 5,000 次预测;每次预测需要读取约 50 个特征,留给特征读取的延迟预算是 5 ms p99。这些数值是本题用于讨论取舍的容量假设。
特征既有交易流上的一小时金额聚合,也有每天更新的账户年龄。部分事件会延迟到达,历史数据还可能被回填;模型更新时也可能改变特征定义。请说明如何生成点时正确的训练数据、低延迟提供线上值、处理缺失和陈旧、发布新版本,并用可执行的验证证明离线与在线语义一致。
这类题适用于数据工程、机器学习工程和 ML 平台岗位。它考察的重点不是背诵“加一个特征库”,而是把一项特征从定义、计算、存储、服务到验证连成一条可追溯链路。
面试官考察点
第一,候选人能否先定义特征语义,再画存储架构。高质量答案会把实体键、数据类型、来源、变换版本、事件时间、可用时间、聚合窗口、时效要求、默认策略和负责人写进特征契约。离线和在线系统都实现这份契约,不能各自凭名称猜测含义。
第二,能否正确处理时间。eventat 表示业务事件何时发生,availableat 表示系统何时真正知道它。一条记录即使发生在预测之前,若预测之后才到达,当时的服务也不可能使用它。训练集若只判断 eventat <= predictionat,仍可能把回填或晚到信息带回历史。
第三,能否为不同访问模式选择计算和存储方式。离线训练需要时间历史和 point-in-time join;在线服务通常需要实体的最新值。窗口聚合适合预计算并物化,廉价且只依赖当前请求的特征可以按请求计算。离线与在线可以使用不同执行引擎,但必须保持相同语义并通过数据证明等价。
第四,能否设计安全的版本发布和故障行为。模型应固定依赖的特征集合版本,新旧版本要能在灰度和回滚窗口内共存。缺失不等于零;陈旧、存储不可用和版本不兼容都要有明确策略。
最后,面试官会看验证是否可执行:契约检查、黄金样本、历史点时测试、线上特征向量采样、离线重放、晚到与乱序注入、回填和回滚演练,以及延迟、时效、缺失率和一致性指标。
回答前需要澄清的问题
- 预测时刻与标签窗口是什么? 本题假设交易到达时立即评分,拒付标签在之后 30 天内成熟;标签不能回流为特征。
- 线上延迟预算覆盖什么? 5 ms p99 是特征读取本身,还是包含网络、序列化和模型推理?本题只把它用于特征读取。
- 哪些特征需要多新? 一小时交易额可能要求分钟级时效,账户年龄通常允许日级更新;不能给所有特征套同一个 TTL。
- 事件会多晚、是否乱序或重复? 这决定水位线、去重、重算和在线覆盖规则。
- 历史回填要复现“当时所知”还是“最新修正事实”? 训练过去线上行为时需要前者;做事后分析时可能需要后者,两者不能混成一个数据集。
- 线上缺失或陈旧时业务怎样降级? 风控场景可以切换基线模型、进入人工审核或拒绝高风险请求,不能默认把所有缺失填成零。
- 模型与特征如何独立发布? 需要知道是否允许并行物化多个版本、旧版本保留多久,以及回滚目标。
- 现有基础设施是什么? 单模型、纯批处理、低流量时,共享变换代码和不可变快照可能已经够用,不必先引入完整特征平台。
30 秒回答框架
“我先建立带实体键、双时间、窗口、版本和时效要求的特征契约。原始事件不可变保存;同一份定义生成离线历史和在线最新值。训练样本按 predictionat 做 point-in-time join,同时约束 eventat 与 availableat,避免把后来才到达的数据带回过去。模型固定 featureset_version,新版本与旧版本并行物化、影子比较后再切流。线上采样记录实际特征向量,我用原始事件和同版本定义离线重放,逐项比较值、缺失和时效,再注入晚到、回填、存储故障与回滚场景。”
分步骤深入解答
第一步:把特征定义变成可执行契约。
每项特征至少登记以下字段:
| 契约字段 | 作用 | |---|---| | 实体与连接键 | 明确特征属于账户、设备还是交易,避免错连 | | 类型与模式 | 在写入前发现类型漂移或非法空值 | | 来源与变换版本 | 让训练和服务能够重建同一计算 | | eventat 与 availableat | 区分事件发生时间和系统可见时间 | | 窗口与 TTL/时效 | 定义聚合边界和何时算陈旧 | | 默认值与降级策略 | 让缺失、陈旧和故障有业务含义 | | 离线/在线可用性 | 说明是否需要历史训练和低延迟服务 | | 所有者 | 让质量告警与变更审核有负责人 |
特征名一旦发布,就不能静默改变定义。若 txnamountsum_1h 从“已授权交易”改成“所有尝试交易”,应发布新版本或新特征名,而不是让旧模型在不知情时读到新语义。
第二步:让离线和在线从相同事实出发。
交易、账户变更等原始事件先进入不可变日志或可重放的历史层。流处理计算高时效窗口特征,批处理计算慢变维度和历史回填。两条路径把完整时间历史写入离线存储,同时把每个实体的最新有效值物化到在线存储。
“相同定义”不要求批处理和流处理必须使用同一种语言。团队可以共享声明式变换或公共代码;确实需要两套实现时,就把黄金样本和重放一致性测试设为发布门槛。特征库只是组织这些约束的工具,不能自动消除两套逻辑的差异。
在线写入要幂等。事件携带稳定 ID,重复事件不应重复累计;写入最新值时比较特征时间和版本,晚到的旧结果不能覆盖已经更新的值。对于窗口聚合,必须明确迟到容忍区间、水位线和修正方式。
第三步:生成真正点时正确的训练集。
每个训练样本都有 predictionat。对同一实体,普通 as-of join 会选择 eventat <= predictionat 的最近特征版本;存在晚到或回填时,还要满足 availableat <= prediction_at:
eligible_feature = same_entity
AND event_at <= prediction_at
AND available_at <= prediction_at
selected_feature = latest eligible_feature by event_at, then available_at第二个条件很关键。某笔交易在周一发生、周三才到达,而历史预测发生在周二;它在业务时间上属于过去,在系统知识上仍属于未来。若数据平台无法保存 available_at,就要使用当时的不可变快照或线上特征日志复现,而不能用今天修正后的表冒充历史服务状态。
“当时所知”与“最新修正”应成为显式的数据集模式。前者用于复现模型在历史时刻能看到什么,后者适合财务核对或事后分析。标签也单独处理:只有观察窗口成熟的样本可进入训练,标签生成数据永远不参与特征连接。
第四步:在物化与按需计算之间取舍。
一小时交易额、近七天设备数等窗口聚合计算昂贵且有严格时效,适合在流中增量计算并物化。账户年龄这类低频特征可以批量更新。只依赖本次请求、计算便宜且无需历史的特征可以按请求计算,减少存储和同步面。
在线存储按实体键和 featuresetversion 读取最新值,避免临时扫描历史。离线存储保留时间序列,用于训练、回填和审计。读取 API 返回值之外,还应返回或记录特征时间、计算版本和缺失状态,服务端才能判断是否陈旧或不兼容。
第五步:明确缺失、陈旧与故障策略。
每项特征或特征组都有独立的时效预算。读取时计算 predictionat - featuretimestamp,超出预算就标记陈旧。缺失、陈旧、合法的数值零是三个不同状态,训练数据也要用与线上相同的缺失表示。
降级由风险决定。非关键特征可以使用经过训练验证的默认值;允许短暂陈旧的特征可以读取上一个值;关键风控特征不可用时,可以切到不依赖它的基线模型、转人工审核,或采取更保守的决策。所有降级都要记录原因,防止系统悄悄长期运行在默认值上。
第六步:版本化发布并保留回滚路径。
模型制品固定 featuresetversion,其中包含特征名称、模式和变换版本。发布 v2 时,先同时物化 v1 和 v2;用相同实体做影子读取和离线重放;确认覆盖率、时效、值分布和逐项差异符合预期后,再部署读取 v2 的模型。v1 至少保留到回滚窗口结束。
模式兼容规则也要显式:新增可选特征可以向后兼容,删除、改类型或改变语义通常需要新版本。模型部署前检查在线存储已具备所需版本和覆盖率,避免模型先上线、特征尚未填充。
第七步:用线上事实验证一致性。
从线上请求采样记录实体、predictionat、featureset_version、每项特征的值、特征时间、缺失/陈旧状态和最终模型版本。不要只记录模型分数,否则无法判断差异发生在哪个特征。
验证任务从不可变原始事件开始,使用相同版本定义,在相同预测时刻重建离线向量,再逐项比较:
- 数值是否一致,浮点特征是否在声明的容差内;
- 缺失、默认和陈旧状态是否一致;
- 事件窗口边界和时区是否一致;
- 线上读取的版本是否与模型声明一致;
- 晚到、重复、乱序和回填后能否稳定重放。
发布前还要注入在线存储不可用、部分键缺失、数据超时、v2 回滚到 v1 等故障。运行时监控 p50/p95/p99 读取延迟、物化滞后、缺失率、默认率、陈旧率、版本不匹配率和重放差异率。模型效果监控可以发现后果,但不能替代这组特征层证据。
高质量示范回答
“我会先定义特征契约,而不是从数据库选型开始。每项特征登记实体键、类型、来源、变换版本、事件时间、系统可用时间、窗口、时效预算和降级规则。原始事件进入不可变历史层;流处理负责一小时金额等高时效聚合,批处理负责账户维度和回填。两者写入离线时间历史,并把每个实体的最新有效值物化到在线存储。
训练样本以 predictionat 为锚做 point-in-time join。候选特征必须同时满足 eventat <= predictionat 和 availableat <= prediction_at。这能排除虽然早已发生、但当时尚未到达的记录。若要复现历史线上行为,我使用 as-known 数据集;最新修正数据单独用于事后分析。标签等 30 天窗口成熟后才加入,且标签来源不进入特征流水线。
在线读取按实体和 featuresetversion 一次获取约 50 个值,并携带特征时间与状态。窗口聚合预计算,只依赖请求的廉价特征按需计算。重复事件用稳定 ID 去重,晚到的旧版本不能覆盖较新的在线值。每个特征有独立的时效预算;缺失、陈旧和零值分开。关键风控特征不可用时切换经过验证的基线模型或人工审核,而不是静默填零。
模型制品固定特征集合版本。发布 v2 时并行物化 v1/v2,做影子读取和重放比较,覆盖率满足门槛后再切模型,并保留 v1 供回滚。为了证明一致性,我会采样记录线上实际特征向量、时间和版本,从不可变事件离线重建同一时刻的向量,逐特征比较值与状态。发布门槛还包括晚到、乱序、回填、存储故障和版本回滚测试。运行时重点看读取 p99、物化滞后、缺失/陈旧率、版本不匹配和重放差异率。
如果当前只有单个低流量批模型,我会先用一份版本化变换和不可变训练快照解决问题;只有多个模型真实共享特征、同时需要历史检索和低延迟服务时,才引入完整特征平台。”
常见错误
- 先选在线数据库再定义语义 → 存储低延迟也无法阻止同名特征含义不同 → 先建立可执行的特征契约。
- 只用
eventat做历史连接 → 后来才到达的记录会被带回过去 → 同时约束availableat或复现当时快照。 - 认为共享特征库就自动一致 → 批处理和流处理仍可能有两套窗口、默认值和时区规则 → 用共享定义、黄金样本与重放证明等价。
- 让晚到结果直接覆盖在线值 → 较旧窗口可能倒退实体状态 → 比较特征时间和版本,并让写入幂等。
- 把缺失统一填成零 → 零可能是合法业务值,故障也会被模型当成信号 → 区分缺失、默认、陈旧与真实零。
- 原地修改已发布特征 → 旧模型会在无版本变化时读取新语义 → 发布新版本并让模型固定依赖。
- 模型先上线、特征再慢慢回填 → 初始流量会大量读取缺失或混合版本 → 先物化、验覆盖率,再切模型。
- 只比较离线和线上分布 → 相似分布可能掩盖单实体错连或窗口偏差 → 对同一请求逐特征重放比较。
- 只记录预测分数 → 出错后无法定位值、时间还是版本 → 采样记录实际向量及其元数据。
- 所有场景都建设完整特征平台 → 单模型批处理可能承担不必要复杂度 → 按共享规模、历史检索和延迟需求逐步引入。
追问及应对
追问一:事件时间和可用时间为什么不能合并?
事件时间回答“业务上何时发生”,可用时间回答“系统何时知道”。晚到事件、人工补录和历史回填会让两者不同。复现历史预测时必须满足两条时间边界,否则模型会使用当时不可见的信息。若业务能保证同步到达且这项保证可审计,两者可以相同;不能把假设默认为事实。
追问二:批处理和流处理必须共用同一份代码吗?
共用代码能减少差异,值得优先考虑,但不是唯一办法。执行引擎、状态管理或性能要求可能迫使团队实现两条路径。此时应共享契约和测试数据,用黄金样本、边界条件和历史事件重放验证结果等价,并把差异测试作为发布门槛。
追问三:晚到事件怎样修正在线窗口特征?
先定义允许迟到范围和窗口关闭规则。范围内的事件可以去重后更新受影响窗口,并只用较新的特征版本覆盖在线值;范围外事件进入修正或回填任务。是否改写过去的训练集取决于数据集模式:复现当时服务使用 as-known,分析最新事实使用 corrected,两者分别保存。
追问四:怎样判断一个特征应该物化还是按需计算?
比较计算成本、复用程度、时效要求、读取延迟和一致性风险。跨大量历史事件的窗口聚合、高复用且延迟敏感的特征适合物化;只依赖当前请求、计算便宜且没有历史训练需求的特征适合按需计算。还要把回填成本和故障面算进去,而不是只看单次 CPU 时间。
追问五:如何在不中断服务的情况下修改特征定义?
发布新的特征集合版本,并行物化旧版和新版。先验证模式与覆盖率,再对线上实体做影子读取和模型影响比较;随后小流量切换读取新版本的模型。旧模型继续固定旧版,旧数据保留到回滚窗口结束。禁止用同一名称在原地替换语义。
追问六:离线与在线浮点结果不完全相同怎么办?
先区分允许的数值误差和语义差异。为每项特征声明绝对或相对容差,并使用相同的空值、时区、舍入和窗口边界规则。若差异随实体或边界系统性出现,应按错误处理;不能用很宽的全局容差掩盖错连、精度截断或不同聚合顺序。
追问七:在线特征存储不可用时应该失败还是降级?
由决策风险和可恢复性决定。低风险推荐可以短时使用缓存或基线排序;高风险欺诈决策可能转人工、采用更保守规则,或拒绝无法安全评估的请求。策略必须在训练和演练中出现,记录降级原因,并设置持续时间和流量上限,避免故障模式变成常态。
追问八:怎样证明新流水线真的减少了训练—服务偏差?
选择一组带正常、缺失、边界窗口、晚到和回填情况的历史请求,保存线上实际向量及版本;用不可变事件在相同 prediction_at 离线重建,逐特征比较值和状态。上线后持续采样同样的对账,并观察差异率是否低于预设门槛。分布图和模型指标只能补充,不能替代同请求重放。