3. 数据聚合层核心概念

在风险数据体系建设中,源系统负责“产生交易”,风险数据集市负责“消费指标”,而两者之间往往存在巨大的语义鸿沟与性能鸿沟。数据聚合层正是填补这道鸿沟的关键中间层。理解聚合层的定义、边界与设计原则,是后续所有建模与开发工作的前提。

一、聚合层的定义与本质

数据聚合层(Data Aggregation Layer,简称聚合层)是位于源系统与风险数据集市之间、以“面向风险主题的整合与轻量汇总”为核心职责的逻辑数据层。它并不追求企业级数据仓库的全域一致性,而是聚焦风险条线所需的客户、协议、担保、额度、交易、评级等核心实体,完成清洗、标准化、关联与预聚合。

聚合层的本质可以概括为三点:

  • 语义转换器:把源系统的技术字段(如 CUST_NO、ACCT_ID)翻译为风险业务可理解的统一实体(客户、协议、敞口)。
  • 性能缓冲带:通过预关联与预汇总,把集市层复杂的多表关联计算前移,降低集市查询压力。
  • 变更隔离墙:源系统结构变化时,只需调整聚合层映射,集市与下游报表保持稳定。
核心判断标准:如果一个数据加工步骤同时被两个以上风险集市复用,或涉及跨源系统整合,它就应该下沉到聚合层,而不是留在集市内部。

二、聚合层与源系统、集市的关系

三者构成“产生—整合—消费”的链路。源系统是事实的发生地,聚合层是事实的整理地,集市是事实的呈现地。可以用下面的结构关系来理解它们在数据流中的位置与职责分工。

源系统 → 聚合层 → 风险数据集市 数据流与职责 源系统 核心银行 / 信贷 信用卡 / 担保 市场 / 同业 评级 / 押品 特点:分散、异构 字段命名不统一 更新频率各异 无风险语义 聚合层 客户 / 协议整合 敞口 / 额度汇总 担保 / 押品关联 评级 / 风险分类 特点:统一、可复用 面向风险主题 预关联 + 预聚合 变更隔离 风险数据集市 信用风险集市 市场风险集市 流动性风险集市 监管报送集市 特点:面向应用 指标口径固定 查询性能敏感 直接服务报表 抽取/清洗 加工/汇总 数据流向:源系统 → 聚合层 → 集市;变更影响:源系统变化被聚合层吸收,集市保持稳定

需要强调的是,聚合层不是“小数据仓库”。数据仓库追求全域一致与历史追溯,聚合层只服务风险条线,允许一定程度的冗余与场景化设计,但必须保证同一实体在聚合层内口径唯一。

三、逻辑架构与分层原则

聚合层内部通常再细分为三个子层,形成清晰的加工梯度:

子层 核心职责 典型对象 更新策略
贴源整合层 字段标准化、编码映射、去重 客户主键、协议主键 日增量
主题关联层 跨源关联、实体拉链、关系构建 客户-协议-担保关系 日全量/拉链
轻量汇总层 敞口、额度、余额预汇总 客户级敞口、产品级余额 日快照

分层设计应遵循四条原则:

  1. 单一实体单一入口:同一客户或协议在聚合层只允许一个权威来源,避免多头加工。
  2. 加工下沉、消费上浮:越通用的加工越靠下,越贴近报表的加工越靠上。
  3. 可追溯:每个汇总值都能回溯到贴源明细,保留血缘关系。
  4. 幂等与可重跑:同一批次重复执行结果一致,便于故障恢复。
实践建议:聚合层的表命名应体现子层与主题,例如 agg_cust_exposure_d、agg_agr_limit_d,让下游一眼识别数据来源与粒度。

四、常见术语与典型误区

在项目沟通中,以下术语出现频率最高,需要统一理解:

  • 实体(Entity):风险关注的对象,如客户、协议、担保、押品。
  • 粒度(Grain):一行数据代表的业务含义,如“一个客户一天一条”。
  • 拉链(Zipper):用起止日期记录实体状态变化的历史技术。
  • 敞口(Exposure):风险暴露金额,是聚合层最核心的汇总指标。
  • 血缘(Lineage):字段从源到集市的加工路径。
常见误区一:把聚合层当成“临时表区”,随意建表、不管理口径,导致下游集市各自为政。
常见误区二:在聚合层做过度汇总,丢失明细,导致集市无法下钻。
常见误区三:忽略拉链与历史,只保留最新状态,监管追溯时无法还原。

一个典型场景:某银行信用风险集市需要“客户级表内外敞口”。若直接在集市内关联核心、信贷、信用卡三套系统,SQL 超过 800 行且每日跑批超时。引入聚合层后,先在贴源整合层统一客户号,再在主题关联层构建客户-协议关系,最后在轻量汇总层按客户汇总敞口。集市只需读取一张宽表,跑批时间下降约 60%,且口径变更只需修改聚合层一处。

-- 聚合层轻量汇总示例:客户级敞口日快照
INSERT OVERWRITE TABLE agg_cust_exposure_d PARTITION (dt='2024-06-30')
SELECT
    c.cust_id,
    SUM(CASE WHEN a.balance_type='ON'  THEN a.balance ELSE 0 END) AS on_bal_exposure,
    SUM(CASE WHEN a.balance_type='OFF' THEN a.balance ELSE 0 END) AS off_bal_exposure,
    SUM(a.balance) AS total_exposure
FROM agg_cust_agr_rel_d r
JOIN dim_cust c ON r.cust_id = c.cust_id
JOIN agg_agr_balance_d a ON r.agr_id = a.agr_id
WHERE r.status = 'A'
GROUP BY c.cust_id;