ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

银行数据仓库架构设计实战:分层、血缘与踩坑经验

银行数据仓库架构设计实战:分层、血缘与踩坑经验 这几年扎在银行数据仓库项目上被问得最多的问题不是“用什么工具”而是“我的数据架构到底该怎么搭”。很多团队启动的时候架构图画得漂漂亮亮ETL工具选型报告写了一厚本可真正跑上小半年报表对不上数、口径说不清楚、临时补丁越来越多——症结基本都出在数据架构这一层。银行的数据仓库和互联网数仓有个明显差异下游不只是报表还有监管报送、审计追数、运营分析、风控模型。数据从贴源、明细、汇总到集市每一道加工都必须可追溯银行数据仓库体系里的所谓“数据架构”核心矛盾就是既要快又要说得清。这篇是“银行数据仓库体系实践”系列的第三篇。前两篇分别聊了项目整体路径和需求侧的主题域划分这篇就不再重复选型对比直接聚焦数据架构的落地细节分层怎么设计、数据怎么流、表怎么存、字段怎么命名、账务数据有哪些绕不开的规则、元数据和血缘要怎么管以及我自己踩过的几个坑。适合正在建或者正在维护银行数仓、又不想让架构烂尾的同行参考也适合从互联网转金融数仓方向的朋友快速对齐银行思维。1. 银行数据仓库的分层设计从贴源到集市到底分几层才合适1.1 四层半是主流先看清每一层的职责边界我在银行项目里见过各种分法有上来就画九层的也有偷懒就分两层的。九层的最后多半会合并两层的最后基本会变成“大泥坑”。综合来看最稳定的是四层半结构。这里说的“半层”指的是ODS之外的异常数据区或接口暂存区一般不算完整的数据层但它真实存在对银行场景很重要。先看分层职责层次常用前缀主要职责典型使用方ODS贴源层ods_原样落地源系统数据只做类型转换、编码统一、分区落地审计追溯、增量抽取比数BDL基础明细层bdl_按主题域做清洗、标准化、整合后的明细数据统一代码和业务口径分析模型、明细查询、下游加工的主数据源GPL通用汇总层gpl_按常用维度预聚合比如机构维度、产品维度、科目日结报表系统、指标层、集市加工IDL集市/应用层idl_面向特定应用或部门的成品数据集市报表前端、数据服务接口这四层里最不该省的是BDL。有些项目为了省事直接从ODS拉到GPL结果就是每个集市各算各的同一个“存款余额”零售部和财务部能算出两个数根源就是少了BDL这道统一标准化的关口。ODS存在的意义也常被低估。很多源系统报文格式混乱字符集不统一甚至上游表结构隔几个月就变一次。ODS作为“数据落地的缓冲区”把源系统的物理结构和数仓的逻辑模型隔离开。源系统改了字段我们只动ODS到BDL的映射不至于全线崩溃。1.2 分层不是越多越好关键是层与层之间的约束四层半结构真正起作用的不是这四张表而是层间的三组强约束第一ODS不直接出报表。任何报表数据必须至少经过BDL或者GPL。这条约束强制所有下游应用使用统一加工过的数据而不是各取所需地读取原始报文。第二GPL不直接面向明细查询。GPL是预聚合产物粒度已经做了汇总如果明细查询打到GPL数据对不上不说还会出现“汇总之后再拆分”这种自欺欺人的操作。第三集市层只允许消费GPL和BDL不允许直接吃ODS。集市是面向应用做“最后十米”的加工比如补一个报表特定的过滤条件、做一次行列转换。集市层做太多业务加工时间长了会形成“集市私有化”这是很多银行数仓后期混乱的主因。1.3 指标层的争议分五层还是做注册中心业内关于“要不要单独建指标层”一直有争议。争来争去我认为重点不是数据层数而是指标口径的注册机制。分五层只是把指标计算逻辑物理化地摆在GPL和IDL之间如果没有配套的“指标字典”和“口径审批流程”这层很快会变成第二个明细层的拷贝反而增加调度复杂度和数据冗余。我参与过的项目采用的是四层加一套“指标注册中心”。指标注册中心不落数据只维护指标的业务定义、计算公式、来源表、过滤条件、责任人。实际加工时GPL从明细聚合成“原子指标”集市层通过组合原子指标得到“派生指标”。这套做法相当于在数据架构之外加了一条逻辑规则链物理分层保持简单逻辑口径却极其严格。对银行这种高合规要求的场景比较实用。2. 数据流向与存储策略批量、准实时与存储引擎怎么搭配2.1 日终批量为主的加工节奏银行数据仓库的调度节奏本质上是被“日切”和“跑批窗口”这两个时间点框住的。核心系统的日切一般在夜间日切以后T-1的数据才是完整的、可供下游加工的数据。所以典型的一天大致是凌晨0点到3点各源系统T-1日数据抽取到ODS凌晨3点到6点ODS清洗整合到BDL上午6点到10点BDL按主题汇总到GPL做总分核对上午10点到下午集市层刷新报表落地数据服务接口开放。这个链条里BDL加工是最容易出问题的环节因为它的数据覆盖面最大、校验逻辑最复杂。我踩过的教训是要把“主数据校验”前置到ODS到BDL这一步而不是放到GPL汇总时再发现。比如机构代码在源系统里是“1001”在数仓标准代码表里是“01001”如果ODS阶段不做代码映射到GPL按机构汇总时才发现大量“未识别机构”整个批处理会卡死。2.2 准实时链路不能全量准实时但也不能完全没有银行早年只有T1现在业务侧越来越要求T0的准实时数据典型场景是大额资金异动监控、贷后风险预警、客户行为实时分析。数据架构上处理准实时有两条路线一是源头增量捕获。源系统数据库通过日志解析等方式把增量变更同步到ODS的增量区然后触发BDL、GPL的局部增量任务只重算受影响的分区和相关汇总不跑全链路。这种方式可控性高是银行场景的首选。二是接口轮询或消息队列。增量数据通过MQ或API落到ODS的实时表ODS实时表和ODS离线表的表结构保持一致只是分区策略不同。实时表只保留最近3到7天的增量离线表按日全量分区。准实时任务挂在调度平台上调度频率可以做到5分钟或15分钟一次。准实时最忌“全链路准实时”。之前见过一个项目要求从源系统到集市每一层都做成流式处理结果数据链路长了以后每一层都延迟几分钟端到端延迟反而比T1还难评估而且数据对不上账都不知道是哪个环节丢的。准实时链路在银行数据架构里应该是“局部加速”不是“整体重构”。2.3 存储引擎选择与分区策略银行数仓的存储引擎选择这些年经历了清晰的演进路径。早几年主流是Hadoop/Hive做离线底座优点是便宜、扩展性好缺点是SQL延迟高、小文件问题多而且大批量并发查询时性能不太稳定。后来很多银行在汇总层和集市层引入了MPP架构的数据库明细层仍保留Hive或文件表。最近几年国产化趋势明显GaussDB(DWS)、TDSQL这类分布式数据库逐步进入核心数仓选型视野。架构上的建议是“混部不混层”不同数据层可以使用不同引擎但每层尽量保持单一引擎。BDL用批量引擎做重加工GPL和IDL用交互式查询引擎做高并发服务。避免同一层里既有Hive表又有MPP表还有手工导出的文件这种“混层”会让链路管理很快失控。分区设计上日期分区是底线分区字段统一命名为dt。除日期分区外大表建议增加机构或业务类型分区时慎重评估数据倾斜。对公客户表按机构分区问题不大但交易流水表如果按机构分区大机构的日流水可能有几千万笔小机构只有几十万笔倾斜非常严重。交易流水类大表我建议只做日期分区把机构维度的聚合放在GPL里处理。小文件治理是存储策略里最容易被忽略的一环。ODS抽取时每个源系统的任务会各自向Hive表写文件如果并发度过高一天可能产生上万个几十KB的小文件后面跑SQL光打开文件就卡半天。我们当时的做法是统一设置写入并发并对ODS大表每天做一次文件合并目标是把单个文件大小控制在64MB到128MB之间。这看似是性能优化实际上属于存储架构的一部分必须在设计阶段定好规则。2.4 表类型选型全量快照、增量追加与拉链表银行数仓的表类型大体分三类选型错了后面会非常痛苦。表类型适用场景操作注意点全量快照各种参数表、维表、客户数在可控范围的主数据每天保留历史快照分区会膨胀建议只保留近30天快照更早的做归档增量追加交易流水、日志类数据重跑时要用主键去重否则重复数据会污染汇总拉链表账户状态、客户信息、合同信息等需要保留历史变化轨迹的数据维护成本高需定期对账新开链和闭链要保证时间连续拉链表在银行场景里用得非常多。举一个典型例子客户表每天都会变化但如果只保留全量快照查“客户上个月是什么地址”就很麻烦如果保留全量日志查当前状态又要做去重。拉链表则记录每一段有效期的状态变化。拉链表加工的核心逻辑以Hive SQL为例大致是这样-- 假设已有客户维表 dim_cust_zip 是拉链表每日增量 cust_delta 是当天变化数据 -- 第一步对当天已有闭链记录做闭链把end_dt更新为昨天 UPDATE dim_cust_zip SET end_dt ${yesterday} WHERE cust_id IN (SELECT cust_id FROM cust_delta) AND end_dt 9999-12-31; -- 第二步插入新开链记录 INSERT INTO dim_cust_zip SELECT cust_id, cust_name, cust_addr, ${today} AS start_dt, 9999-12-31 AS end_dt FROM cust_delta;这里面有个关键细节闭链和开链不是同一组分区闭链更新的是历史分区上的记录开链插入的是今天的新分区。很多团队第一次做拉链表时把这两步写成了同一个事务结果历史分区被重算整张表状态错乱。拉链表维护完必须做链路对账比如对每个cust_id确保任何时候只有一条end_dt9999-12-31的记录并且start_dt和end_dt之间没有断层。3. 命名规范与代码体系银行数仓最容易忽略但回报最高的设计3.1 表命名规范要能“一眼读懂”命名规范看起来是小事但在银行数仓里它是数据架构能否长期维护的基础。一张表叫ods_cif_custinfo_d_i和叫ods_t03_cust_info_day_inc完全不是一个信息量。前者只有建表的人自己懂后者能让人从名字上就猜到这是ODS层、主题域是客户信息、日增量。我这边在项目中落地的命名规则是“层级_主题域_业务子类_粒度_周期_版本”六段式bdl_dep_loan_bal_d_i 解释BDL层、存款主题域、贷款余额、日粒度、增量i gpl_cif_cust_cnt_m_s 解释GPL层、客户主题域、客户数、月粒度、快照s主题域代码需要单独建一张表维护常见的有cif客户、dep存款、loan贷款、acct账务、card银行卡、finc财务、risk风险、chg渠道等。每个主题域下再按业务子类细分。这套规则的目的是让一个没参与建模的人看到表名就能知道这张表是做什么的、在哪个层、粒度多粗、数据怎么刷新。字段命名也一样要有规则。主键字段统一叫business_key日期分区字段统一叫dt业务日期字段统一叫business_dt会计日期字段统一叫account_dt。这样做的价值在大规模调度和血缘分析时会体现出来——解析SQL时系统可以自动识别每个字段的角色血缘关系才能做得准。3.2 金额单位、时间格式和代码类的三大约束银行数仓有三大类标准必须在架构层面定死否则后患无穷。第一是金额单位的约束。金额字段一律以“分”为最小存储单位字段命名后缀统一为amt或者bal。为什么不是元因为源的多个系统里既有以元为单位的报送报文又有以分为单位的核心流水混在一起后报表上的金额经常差一百倍。以分为统一单位ETL里做一次换算再往上层传就再也不用关心单位问题。如果怕精度问题可以存为decimal(18,2)表示分即精确到分。第二是时间格式的约束。业务时间、会计时间、抽取时间在表里必须分开存格式统一为char(8)即yyyyMMdd或者用date类型。禁止一部分表用yyyy-MM-dd、另一部分用yyyyMMdd。银行报表经常要按日、按周、按月做时间维度对比时间格式不统一下游所有SQL都在做to_char、date_format转换性能和可维护性都会变差。第三是代码类标准。机构代码、币种代码、借贷方向标志、客户类型代码这些“字典值”必须引用统一的代码表禁止在业务表中直接存自由文本。比如借贷方向核心系统里常用D和C表示借贷会计报送里用1和2表示借贷如果不做代码映射汇总层的科目余额计算会全错。银行里的代码类标准还要考虑“一个代码多套外部映射”的场景比如行内机构编码、监管报送编码、反洗钱编码各是一套但数仓内部必须有一个“标准机构代码”作为唯一基准。3.3 数据权限与密级标签是银行数据架构的硬约束银行数据安全合规要求高数据架构层面就必须包含权限模型和敏感数据识别机制而不是等出了问题再手工授权。我们的做法是在建表规范里强制包含两类标签一是密级标签。每一张表、每一个字段都要有密级定义比如公开、内部、敏感、机密。客户姓名、身份证号、手机号、账户余额这些字段默认是敏感级在元数据里打标。下游使用这些字段时需要经过脱敏规则默认生效。二是权限模型。数仓统一规划三类角色只读角色可以查询BDL和GPL的非敏感字段加工角色可以读写自己负责主题域的表但无权跨主题域读取管理员角色才有DDL权限。集市层的表通过发布机制授权应用系统只能拿到集市层的一张事实表加关联维表不允许直接扫全库。权限和数据血缘是绑在一起的。没有血缘做依据的权限申请审批人根本无法判断“这个申请是不是合理”。有了字段级血缘审批人可以看到申请者要查的字段来自哪些上游表涉及哪些敏感数据才能做出有效判断。4. 账务数据的三条特殊规则冲正、方向口径与日切架构上怎么埋点4.1 冲正数据不能简单理解成“删掉一条错误记录”银行账务数据最特殊的地方是冲正机制。业务系统里冲正不是删除而是新增一条反向记录。因为监管和审计要求保留完整的交易流水任何一笔账务操作都不能从历史流水中物理消失。冲正分两种形态当日抹账和隔日冲正。当日抹账是指交易日当天发现错误做同额反方向的“红字冲正”原交易记录和冲正记录在当天账务流水里同时存在。隔日冲正是指交易日已经日切不能再动当天的账只能做“蓝字冲正”即生成一笔同方向的负数记录或者生成一笔反方向的正数记录记入发现日的账务流水。数据架构上怎么处理这堆冲正记录ODS层必须原样保留原始方向不做任何合并。BDL层在加工时同时保留“原始流水”和“还原流水”两个视角。原始流水就是源系统的所有记录一条不少还原流水是在原始流水基础上增加了冲正标识字段比如is_reversal、reversal_trans_id。GPL层汇总时分别统计“总额”和“净额”。比如当日存款发生额包括原交易、冲正交易最终余额按净额计算但交易笔数、涉及客户数则要按总额口径来统计。这里有个实际例子客户日上午在柜台存入1000元下午发现金额录错做了当日抹账。ODS里这一天有两条记录存入1000和抹账-1000。如果直接求和存款发生额是0但这个客户确实发生了一笔交易监管报表里交易笔数要算1笔。所以在GPL做日累计时交易金额用净额交易笔数用总笔数这两个口径必须同时在汇总表里保留。很多银行数仓的账务汇总表会同时设计sum_amt和cnt_txn两个字段就是为了同时支撑这两种口径。4.2 借贷方向银行余额不是“加减法”是“方向聚合”银行数据仓库的汇总层有个很容易踩的雷科目余额计算。在银行核心系统里资产类科目的余额借方发生额-贷方发生额负债类科目正好相反是贷方发生额减借方发生额。如果你在数仓里只会做加法汇总那科目余额表一定是错的。处理思路是在ODS阶段就把借贷方向转成标准标志把“D”“C”统一为“1”“2”或“借”“贷”。在GPL汇总时为每个科目维护一个“余额方向”属性资产类科目余额方向为借负债类科目为贷然后按这个方向做归一化计算。实际SQL可以参考这样设计-- 科目余额汇总表加工示例资产类科目 SELECT account_dt, subject_cd, SUM(CASE WHEN dr_cr_flag D THEN amt ELSE 0 END) - SUM(CASE WHEN dr_cr_flag C THEN amt ELSE 0 END) AS subject_bal FROM bdl_acct_trans WHERE account_dt ${bizdate} GROUP BY account_dt, subject_cd;负债类科目则反过来。很多团队一开始把所有科目都按“借方减贷方”算结果贷款余额算对了、存款余额算成负数排查半天找不到原因。这个“方向属性”建议在科目维度表里维护而不是写死在ETL代码里。科目方向变了只改维表不需要改调度任务。4.3 日切和会计日期归属哪一天不能靠“猜”银行系统有“日切”概念。日切不是自然日的零点而是核心系统确定“当日账务结束、下一日开始”的时点可能是晚上11点也可能是凌晨1点。日切前发生的交易记入当前会计日日切后发生的交易记入下一个会计日。数据仓库里最容易出现的错误是用“数据到达时间”或者“抽取时间”替代“会计日期”。比如源系统在23:50发生了一笔交易核心系统日切还没执行这笔交易从会计归属上仍然是今天但如果数仓按“数据落地到ODS的时间”来分区这笔交易就可能被划到第二天。账务汇总在日切前后各跑一次就会出现“今天少了明天多了”的差数。架构上的解法是在ODS层的每一张账务类表里都单独维护两个日期字段——business_dt业务发生日期和account_dt会计归属日期。所有汇总统计一律用account_dt过滤业务分析则用business_dt。日切点之后的交易记录源系统自己会在后续批次补充或修正account_dt数仓不做推测只用源系统给的值。另外银行还有工作日历的概念自然日、工作日、会计日。比如节假日调休某天虽然是周六但是工作日某天虽然是周一但是休息日。数仓的日期维表需要提前维护至少一年的“工作日历”把自然日、工作日、会计日三个口径全部打标这样才能支撑监管报送、利息计提等强日期依赖的加工逻辑。5. 元数据、血缘与数据地图架构落地后的管理能力5.1 元数据不能只采技术信息业务口径必须一起管很多银行数仓的元数据仓库只存了表结构、分区信息、调度依赖这些技术元数据业务口径散落在Excel和各个开发人员的脑子里这等于没管。元数据至少要覆盖三类技术元数据表、字段、分区、存储路径、格式、更新频率、调度依赖。这部分通常由调度平台和元数据采集工具自动抓取。业务元数据指标名称、指标定义、计算公式、适用的过滤条件、数据的业务含义和负责人。这部分必须由业务分析师手工录入靠的是制度约束。操作元数据每天每个任务跑了多久、处理了多少行、成功还是失败、上次重跑时间、数据质量校验结果。这部分由调度平台自动生成但需要在架构设计时预留存储位置。5.2 字段级血缘是“算得清”的关键血缘分析是银行数仓数据架构里回报最高的基础设施之一。简单说血缘就是要能回答“GPL表的这个‘存款余额’字段是哪些上游BDL表、哪些字段经过哪几步加工得到的如果上游某张表修改了字段含义会影响下游哪些报表”血缘分表级和字段级。表级血缘好做调度平台基本都能画出来。字段级血缘难度大很多尤其当ETL里出现大量select *、中间用临时表、或者同一字段经过多次改名时。做字段级血缘需要在ETL开发规范里就强制要求不得使用select *所有字段必须显式列出同一字段在加工过程中尽量保持原命名确实需要改名的在元数据里登记映射关系。血缘的采集方式我建议自建解析器加调度日志双重保障。自建解析器负责从SQL中解析“目标字段依赖哪些来源字段”调度日志负责确认“这个依赖真实发生过、且成功运行过”。两边的数据交叉验证血缘关系才是可信的。血缘采集还有一个很实际的用途是“影响分析”。监管报送口径调整时业务问“这个调整会影响哪些报表”血缘能直接给出影响范围清单而不是靠开发人员回忆。这个能力在银行侧的价值比在互联网侧高很多因为银行的每次口径变动都涉及合规审计。5.3 数据地图让使用者“找得到、看得懂、用得上”元数据和血缘最终是要给业务用、给开发用、给审计用的。一个数据地图产品至少要包含三个入口一是找表。输入业务关键词比如“存款余额”能把与之相关的BDL表、GPL表、指标定义、所属负责人全部列出来。二是理解。点开一张表能看到字段说明、枚举值、敏感标签、更新频率、所属主题域和责任人最好还能看到最近的数据质量校验结果。三是影响。针对某一张表发起“影响分析”列出所有依赖它的下游表和报表以及最近一次运行的状态。数据地图很难一步到位建议先做“找表”和“影响分析”这是价值最直接的功能。“理解”这个入口依赖业务元数据的完善程度可以放在第二个迭代做。数据地图做完以后新入职的数据开发基本上两周就能接手一张陌生表的维护任务这个效率提升会非常明显。6. 落地过程中踩过的六个坑与对应解法坑1跨层直连层次被绕过项目中期最容易出现的就是“集市直连ODS”。原因也很好理解BDL加工任务延时了集市开发为了不阻塞下游报表直接在集市层写一个从ODS取数的小任务。一次两次还好时间长了BDL的加工反而越来越没人维护因为“绕过它也能出数”最后整张BDL表变成了摆设。解法是两条腿走路。技术上权限模型限定集市层角色只能访问GPL和BDL的已发布表从权限上阻断直连ODS。管理上设一个“层次例外评审”机制确实需要直连的场景要走评审并明确直连表的生命期到期必须迁移到标准分层。坑2主键不统一重复数据漏进BDL银行源系统的数据多次推送很常见。同一个账户信息上午推一次下午由于发生变更又推一次而源系统没有把“变更”和“新增”区分开。结果BDL里出现两条相同business_key的记录到GPL按日汇总时数据翻倍。解法是在BDL入口设置统一主键校验。每张BDL表定义business_key加工时先查“前一天已存在的business_key集合”和“当天到达的business_key集合”做差集和交集判断。重复推送的数据进入“异常数据区”同时触发告警。这个校验有一定开销但值得做否则数据错误往往要等到月底报数字对不上时才暴露。坑3汇总层没有强制分区裁剪一次查询全表扫GPL汇总表如果数据量达到千万级而下游查询没有带分区条件数据库会做全表扫描。银行报表团队如果习惯了“with as直接拼接多张表”每个查询都全表扫MPP数据库也会顶不住。解法是在GPL建表阶段就明确分区裁剪要求所有GPL表必须有日期分区下游查询必须显式指定分区条件对个别必须支持跨分区扫描的大表单独建索引并限制扫描并行度。过程中可以通过调度平台做查询审计发现全表扫描的SQL直接反馈给报表团队整改。坑4拉链表维护出问题历史状态对不上拉链表第一次上线时通常很顺利三个月后就可能出现闭链和开链不同步的情况。比如某天新增了接口推送增量数据里包含大量历史客户闭链步骤把已经被闭链的客户又闭了一次历史记录出现重叠。解法是拉链表必须做每日对账。对账逻辑很简单对每个business_key检查是否有且仅有一条end_dt9999-12-31的当前记录对闭链记录检查start_dt必须等于上一次的end_dt加一天。这类对账SQL建议在拉链表对应的调度任务里固化发现异常直接阻断下游汇总。坑5口径“私货化”各有各的“存款余额”数仓建设到中后期各部门都会提各种口径的“存款余额”。有的是核心系统科目余额有的是监管报送口径有的是运营考核口径它们名称相似但算法不同。如果指标注册中心没发挥作用各集市自己定义字段最终就会出现“同名不同数、同数不同名”。解法是指标口径变更必须走审批流程。每次新指标注册要明确它对应的原子指标、派生公式、过滤条件以及区别于其他指标的关键差异。这个流程有点重但对银行数仓是必要的。数据不一致在银行不是技术问题是操作风险问题。坑6权限模型缺“集市岗”发布管理流于形式数据权限设计时大多会考虑到“开发角色”和“只读角色”。但集市层上线后业务部门经常会有“我要在这个集市表上加一个字段”的临时需求。如果权限模型里没有“集市发布岗”临时需求就只能由管理员直接改表结构时间一长集市层表和BDL表的边界又开始模糊。解法是把集市表当成产品来管理。建集市表走发布流程表结构变更走版本控制下游应用只能消费已发布的表版本。集市发布岗负责审核每个新版本对下游的影响并且保留历史版本的回滚能力。这层管理机制建好以后架构才不会在半年之后悄悄腐化。我个人在实际操作中的体会是银行数据仓库的数据架构最难的不是设计那几天而是接下来一年每一天的“守规矩”。分层、命名、元数据、血缘这些机制在一开始都显得繁琐但等到数据量上来、口径变多、人员流动之后你会感激当初把这些规则定死的决定。
返回列表