4. 数据模型设计:特征宽表设计、预聚合策略、列式存储与行式存储的选择

在支付风控评分场景中,数据模型设计直接决定了特征计算延迟与系统吞吐量。低延迟评分要求特征读取在毫秒级完成,因此必须围绕“宽表预计算 + 列式存储”的核心思路进行建模。本章从特征宽表设计、预聚合策略、存储选型三个维度展开,并给出可落地的架构方案。

核心原则:将特征计算从“实时计算”前置为“离线/近线预计算”,将多表关联转化为单行读取,将行式扫描转化为列式裁剪。

4.1 特征宽表设计

特征宽表(Feature Wide Table)是将风控评分所需的全部特征字段平铺到一张大表中,每笔交易或每个用户对应一行,避免实时评分时跨表 JOIN。设计要点如下:

  • 行粒度:通常以“交易流水号”或“用户-设备指纹”为行键,确保一次查询即可获取所有特征。
  • 列分组:按特征来源分为基础属性、历史行为、设备指纹、网络环境、关联风险等组,便于后续列裁剪。
  • 版本管理:特征宽表应包含时间戳或版本号字段,支持特征回溯与模型回测。
  • 空值处理:对缺失特征使用默认值(如 -1、0 或均值)填充,避免实时评分时出现 NULL 异常。
特征分组 示例字段 数据类型 更新频率
基础属性 user_id, amount, currency, pay_type string, double, int 实时写入
历史行为 30d_tx_count, 7d_avg_amount, 90d_fail_rate int, double, double 每小时预聚合
设备指纹 device_id, os_version, screen_resolution string, string, string 每日更新
网络环境 ip_country, isp, proxy_score string, string, double 实时查询+缓存
关联风险 device_risk_level, ip_blacklist_flag int, boolean 近线流计算
避坑提示:宽表并非越宽越好。单行字段数建议控制在 200 以内,超过此阈值应拆分特征组为多张宽表,通过行键前缀路由,避免单行 I/O 过大。

4.2 预聚合策略

预聚合(Pre-aggregation)是将高频使用的统计特征(如近 30 天交易次数、近 7 天平均金额)提前计算并写入宽表,评分时直接读取,无需实时聚合。常见策略包括:

  • 时间窗口预聚合:按 1h/6h/24h/7d/30d 窗口,使用滑动窗口或 T+1 批量计算,写入宽表对应字段。
  • 分层聚合:对用户级、设备级、IP 级分别预聚合,评分时按优先级读取最细粒度特征。
  • 增量更新:使用 Lambda 架构,离线批处理计算全量窗口,实时流处理补偿最近窗口的增量数据。
  • 冷热分离:热数据(最近 7 天)使用高频预聚合(每 5 分钟),冷数据使用低频预聚合(每小时),降低计算成本。
-- 示例:预聚合宽表 DDL(ClickHouse 引擎)
CREATE TABLE feature_wide_table (
    tx_id          String,
    user_id        String,
    event_time     DateTime,
    -- 基础属性
    amount         Float64,
    currency       String,
    -- 预聚合特征(每小时更新)
    hour_tx_cnt    UInt32 DEFAULT 0,
    hour_sum_amt   Float64 DEFAULT 0.0,
    day_tx_cnt     UInt32 DEFAULT 0,
    day_sum_amt    Float64 DEFAULT 0.0,
    week_tx_cnt    UInt32 DEFAULT 0,
    week_avg_amt   Float64 DEFAULT 0.0,
    -- 设备/网络特征
    device_id      String,
    ip_geo         String,
    proxy_score    Float64 DEFAULT -1.0
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(event_time)
ORDER BY (user_id, event_time);
注意:预聚合字段的更新延迟必须小于评分 SLA 允许的最大等待时间。对于 50ms 以内的评分请求,建议预聚合延迟控制在 1 分钟以内,否则应使用实时流计算补偿。

4.3 列式存储与行式存储的选择

存储引擎的选择直接影响特征读取效率。行式存储(如 MySQL、PostgreSQL)适合 OLTP 场景,列式存储(如 ClickHouse、Parquet)适合 OLAP 场景。在风控评分中,特征宽表通常采用列式存储,原因如下:

对比维度 行式存储 列式存储
读取模式 按行读取整条记录 按列读取所需字段
I/O 效率 读取 200 个字段时需加载全部列 仅加载评分模型所需的 20~50 个特征列
压缩率 同列数据类型混杂,压缩率低 同列数据类型一致,压缩率可达 5~10 倍
典型延迟 单行 200 字段读取约 2~5ms 单行 50 字段读取约 0.2~0.5ms
适用场景 交易写入、事务更新 批量特征读取、分析查询

选型建议:

  • 特征宽表主存储:使用 ClickHouse 或 Apache Doris 等列式引擎,支持高吞吐写入与低延迟点查。
  • 实时特征缓存:对延迟要求 < 5ms 的特征(如设备风险等级),使用 Redis 或 Memcached 作为行式缓存层。
  • 混合架构:宽表写入列式存储,同时将高频特征同步到行式缓存,评分时优先读缓存,缓存 miss 再读列存。
特征宽表存储架构:列式存储 + 行式缓存混合方案 实时交易流 离线特征批 预聚合引擎 滑动窗口 / Lambda 列式宽表 ClickHouse / Doris 行式缓存 Redis / Memcached 评分服务 50ms SLA 高频特征同步 优先读缓存 缓存miss回源 说明: 列式宽表存储全量特征,行式缓存仅缓存评分模型高频使用的 20~50 个特征,实现低延迟与低成本平衡。

4.4 特征宽表分区与排序键设计

列式存储的分区与排序键直接影响查询裁剪效率。设计原则如下:

  • 分区键:按时间分区(如天或小时),便于 TTL 过期与冷热数据管理。
  • 排序键:将评分查询中最常用的过滤字段(如 user_id、device_id)作为排序键,使列式存储能够快速定位数据块。
  • 跳数索引:对高基数特征(如 ip_country)建立跳数索引,跳过不相关数据块,减少扫描量。
-- ClickHouse 排序键与跳数索引示例
CREATE TABLE feature_wide_table (
    user_id      String,
    event_time   DateTime,
    amount       Float64,
    device_id    String,
    ip_country   String,
    ...
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(event_time)
ORDER BY (user_id, event_time)
-- 跳数索引:对 ip_country 建立布隆过滤器
INDEX idx_ip_country ip_country TYPE bloom_filter(0.01) GRANULARITY 4;
性能数据:在 10 亿行宽表上,使用 user_id 排序键 + 时间分区,单行点查延迟稳定在 0.3~0.8ms;未使用排序键时延迟上升至 3~8ms。合理设计排序键可降低 10 倍 I/O。

4.5 本章小结