4. PSI监控体系架构:从数据采集到告警链路的完整设计
一个生产级的因子PSI监控系统,不能仅停留在单次计算脚本的层面,而需要构建一条从数据采集、计算、存储到告警的完整链路。本章将拆解这一架构的每一层,并给出可直接落地的组件选型与交互流程。
4.1 架构总览:五层模型
我们将PSI监控体系划分为五个核心层,每一层承担明确的职责,并通过标准接口进行数据传递。
| 层级 | 核心职责 | 典型组件 |
|---|---|---|
| 数据采集层 | 从离线数仓或实时流中拉取因子快照与基准分布 | SparkSQL / Flink CDC / Airflow DAG |
| 计算引擎层 | 执行PSI、特征缺失率、分箱稳定性等统计计算 | PySpark / Pandas UDF / ClickHouse |
| 存储层 | 持久化因子分布快照、PSI时序、告警记录 | HDFS / MySQL / Redis / InfluxDB |
| 告警决策层 | 基于阈值规则或动态基线判断是否触发告警 | Prometheus AlertManager / 自研规则引擎 |
| 通知与展示层 | 将告警推送到飞书/钉钉/邮件,并提供可视化看板 | Grafana / Feishu Bot / 自研Dashboard |
架构原则:每一层之间通过消息队列(Kafka)或API解耦,避免单点故障扩散。计算层与存储层之间建议使用列式存储(Parquet + ClickHouse)以提升批量查询性能。
4.2 数据采集层:基准分布与监控分布的获取
PSI计算需要两个分布:基准分布(Reference Distribution)和当前分布(Current Distribution)。采集层需要解决三个关键问题:
- 基准版本管理:通常以模型上线日、月度重训日或季度回顾日为基准快照时间点,将因子分箱后的计数存入基准表。
- 监控频率:离线场景按天/周调度,实时场景按小时或分钟级滑动窗口采集。
- 数据质量校验:采集时需检查空值率、异常值、分箱边界漂移,避免脏数据污染PSI结果。
# 伪代码:基准分布采集与存储
def collect_reference_distribution(factor_name, reference_date, bins):
df = spark.sql(f"""
SELECT
CASE
WHEN {factor_name} < {bins[0]} THEN 'bin_0'
WHEN {factor_name} < {bins[1]} THEN 'bin_1'
...
ELSE 'bin_n'
END AS bin_label,
COUNT(*) AS cnt
FROM model_features
WHERE dt = '{reference_date}'
GROUP BY bin_label
""")
df.write.mode("overwrite").parquet(f"/data/psi_ref/{factor_name}/{reference_date}")
return df
4.3 计算引擎层:PSI与辅助指标的计算
计算层不仅计算PSI,还需同步计算以下辅助指标,用于告警降噪:
- 分箱覆盖率:每个分箱的样本占比,防止某个分箱样本过少导致PSI虚高。
- 缺失率变化:当前分布与基准分布的缺失率差值,缺失率突变往往先于PSI恶化。
- 分箱漂移方向:记录每个分箱是正向偏移还是负向偏移,辅助归因。
| 指标名称 | 计算公式 | 告警阈值参考 |
|---|---|---|
| PSI | Σ (P_i - Q_i) * ln(P_i / Q_i) | > 0.1 轻度漂移;> 0.25 严重漂移 |
| 缺失率差值 | |missing_rate_current - missing_rate_ref| | > 0.05 |
| 最大分箱偏移 | max(|P_i - Q_i|) | > 0.15 |
避坑提示:当某个分箱在基准分布中占比为0时,ln(P_i / Q_i) 会除零。建议对零频分箱做拉普拉斯平滑(加1平滑),或直接跳过该分箱并在日志中记录。
4.4 存储层:时序化与多版本管理
存储层需要支持两种查询模式:
- 按因子查询PSI时序:用于Grafana绘制趋势图,推荐使用InfluxDB或ClickHouse的时序表引擎。
- 按时间点回溯分布快照:用于事后归因分析,推荐将分箱明细以Parquet格式存储在HDFS,元数据写入MySQL。
-- ClickHouse 建表示例:因子PSI时序表
CREATE TABLE psi_monitor.psi_series (
factor_name String,
monitor_date Date,
psi_value Float64,
missing_rate_diff Float64,
max_bin_shift Float64,
total_sample_cnt UInt64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(monitor_date)
ORDER BY (factor_name, monitor_date);
4.5 告警决策层:阈值规则与动态基线
告警决策层是避免告警风暴的关键。我们采用分级告警策略:
- P0(严重):PSI > 0.25 且 缺失率差值 > 0.1,立即通知模型负责人与风控值班。
- P1(警告):PSI > 0.1 且 连续3天上升趋势,发送飞书群消息。
- P2(提示):PSI > 0.05 但未达P1标准,仅记录日志,不主动推送。
动态基线优化:对于周期性明显的因子(如周末与工作日分布不同),建议使用过去7天或28天的滑动窗口计算动态阈值,而非固定0.1。例如:threshold = mean(psi_last_7d) + 2 * std(psi_last_7d)。
4.6 通知与展示层:多通道触达与可视化
通知层需要支持多渠道、可配置的告警路由:
- 飞书/钉钉机器人:发送卡片消息,包含因子名称、当前PSI值、趋势图链接。
- 邮件:发送日报/周报汇总,包含Top-N漂移因子列表。
- Grafana看板:展示所有因子的PSI热力图,支持按业务线、模型版本筛选。
4.7 完整链路SVG架构图
下图展示了从数据采集到告警通知的完整数据流与组件交互关系:
4.8 架构落地检查清单
在将上述架构部署到生产环境前,建议逐项确认以下要点:
- 数据采集层:是否已处理基准分布版本过期问题?是否对实时流做了背压控制?
- 计算引擎层:PSI计算是否支持增量更新(仅计算新增分箱)?是否记录了计算耗时用于性能监控?
- 存储层:时序表是否设置了合理的TTL(数据保留周期)?分箱快照是否做了压缩?
- 告警决策层:是否配置了告警静默期(避免重复告警)?动态基线是否在节假日自动调整?
- 通知层:是否支持告警升级(P0未确认则自动电话通知)?是否提供了告警自助屏蔽入口?
最佳实践:建议在架构上线初期,先对所有因子开启“静默模式”(只记录告警日志不推送),运行一周后根据实际告警频率调整阈值,再逐步开放推送。这能有效避免初期阈值不合理导致的告警疲劳。