一张表已经很少有人查询,任务还在每天跑。一个专题结束了,相关数据还在持续更新。准备调整一个字段,往下查才发现几个报表和接口都直接依赖它。
这些表、任务和接口刚出现时,大多有明确用途。需求一点点加上去,原来简单的关系也会跟着变复杂。同一份数据为了离线加工、实时查询、专题分析留下不同结果,单看每一条链路都说得通。碰到补数、口径调整或者表结构变化时,需要同时处理的东西就多了。
《道德经》里讲“知止不殆,可以长久”。放到数据平台里,可以落到一些很具体的事情上:一份数据经过某个加工阶段,要不要把中间结果长期留下;已有数据能够满足使用,要不要再复制一份;一个专题需要数据,是重新建一套表,还是先从已有数据中组织出来。
湖仓一体让数据留存、计算和访问之间多了一些选择,这些原本容易按习惯处理的问题,也有了重新考虑的空间。
Spark、Flink、Kafka、Iceberg、Catalog、调度和查询引擎放到一起,基本关系并不难理解。批量、实时、消息、湖表、查询各自承担不同角色,运行一段时间以后,麻烦往往出在数据跟着计算方式各留一套上。
离线链路保存一份结果,实时链路为了时效再写一份,生产查询为了响应时间又进入另一套存储。平时各走各的,等到历史数据需要补算、公共口径发生变化,或者源数据需要修正,几份结果就都要重新核对。
把数据文件放在对象存储或分布式存储上,由 Iceberg 管理文件之上的表,Spark、Flink 和查询引擎根据负载访问这些数据,可以少一些数据跟着计算引擎重复落地的情况。实时和离线仍然有不同的计算过程,但不必从一开始就形成几套彼此独立的数据。

Iceberg 使用时间长了以后,Snapshot、旧元数据和小文件会逐渐积累,失败任务还可能留下孤立文件。Apache Iceberg 官方把 Snapshot 过期、孤立文件清理和数据文件重写都列入了表维护内容。湖表减少了一部分数据副本和目录问题,日常维护也随之转到文件组织、元数据规模和 Snapshot 生命周期上。
查询入口同样需要结合负载来看。以 Spark 为主要计算体系时,可以在 JDBC/ODBC 和后端计算之间增加多租户 SQL Gateway;跨源查询比较多时,也可能需要能够通过 Catalog、Connector 访问多源的查询引擎。入口多了,权限、资源隔离、客户端连接和故障排查也会多一套,主要查询落在哪里、是否需要跨源访问,通常比技术栈本身更值得先看。
先从 ODS 说起。
源系统字段比较稳定、数据本身也比较规范的情况并不少见。进入平台以后可能只做格式处理、补充接入时间或者原样留存,业务偶尔需要查历史数据时,这批 ODS 已经够用。
按照 ODS、DWD、DWS、ADS 的层次继续往下建并没有技术障碍,但如果新建的 DWD 和 ODS 几乎一样,也意味着从此多了一张表、一条任务以及对应的血缘、质量和权限关系。
这种时候,ODS 的“原始”更适合理解成数据所处的位置和来源,不必直接等同于“脏”。Databricks 文档对 Medallion Architecture 中 Bronze 层的描述,同样强调保留接近源端的数据状态,并把 Bronze、Silver、Gold 作为一种逻辑数据设计模式。
ODS 已经可以使用以后,接下来通常就是访问问题。
一次性的历史分析或者低频查询,通过受控的 SQL 入口读取已有数据,并不一定值得为此长期复制一份结果。多个主题开始稳定复用、源系统语义需要重新整理,或者下游不适合直接感知源端变化时,再沉淀公共明细会更自然。

过去一些需求习惯用“再落一张表”处理。把数据位置和访问方式拆开以后,就不一定只剩这一种办法。
实时链路又会遇到另一类情况。

图上只有两根箭头,运行时容易花时间的地方却是失败场景。
Flink 的 Checkpoint 可以提供状态一致性和失败恢复能力,但端到端的数据交付语义还要结合 Source 和 Sink 来看。Apache Flink 文档也说明,Source 需要具备相应的重放能力,Sink 则要结合具体 Connector 的提交语义、事务机制或幂等处理方式来判断。
一条实时链路除了看正常数据流,还要看任务失败后从哪里恢复,重复数据怎样处理,一个目标已经写成功、另一个目标失败以后怎么补。
湖侧和在线查询体系是两个不同存储时,严格跨存储事务往往会让链路变得很重。有些场景会保留可重放的数据源,再结合 Checkpoint、幂等、失败重试和必要的数据核对处理结果一致性。能做到什么程度,还取决于 Sink 的写入能力以及业务对时效和一致性的要求。
数据进入生产以后,会碰到公共语义的问题。
同一个业务状态,几个系统可能分别使用 1/2/3、A/B/C 和中文描述;日期格式、业务键、组织编码也可能各自不同。如果这些差异一直带到下游,每个主题都会重新解释一次,时间长了很容易留下几个略有差异的版本。
这些反复使用的时间、状态码、业务键放到 DWD 里整理,后面的主题、指标和接口可以直接复用。
DWS、ADS 更容易随着具体需求增加。报表查询慢了提前计算一份,接口字段不同再留一份,新专题又准备一套结果。每一次新增都有理由,问题通常出现在几年以后,相似表很多,谁还在用却越来越难说清楚。
做到这一层,逻辑分层和物理物化可以分开看。

一份数据经过了 DWS 的主题加工,只能说明这个加工过程存在,并不意味着中间结果一定要长期保存。
判断时可以先看这份结果有没有稳定的复用关系。只是一次性的分析,中间结果长期留下来的意义通常不大;使用关系已经稳定,再看现算能不能满足响应要求;响应时间还能接受,物化未必急着做。再往下才是重算成本,如果每次都要扫描大量明细、做复杂关联,长期重复计算本身也会变成一笔固定开销。
但这个顺序在物化当时能看准多少?后面会不会多出新的使用方,当时往往并不知道。它能帮助判断眼前要不要留下这份结果,却不能保证这个判断几年以后还成立。物化以后,隔一段时间还得重新看一遍这些条件,尤其是使用关系有没有变化。
这里也很难给出统一阈值。一天查询多少次算高频、多少秒值得提前计算,与数据量、SQL 复杂度和资源条件都有关系。项目内部可以根据负载逐步形成规则,脱离具体环境写成一个固定数字,参考意义反而有限。
还有一种情况是在结果物化以后出现的。报表已经下线,原来的 DWS、ADS 任务还在定时计算;准备停掉时,又发现某个接口仍然在读取这张表。最初为了一个页面留下的结果,时间久了以后已经变成了其他链路的上游。
已经物化的结果,后面还会遇到谁在使用、什么时候能够停止更新的问题。准备退出时,可以先把对象标记出来,确认新的依赖不再继续增加,再结合查询、任务和服务调用决定停更、归档还是清理。能够从上游重新生成的数据,处理起来也会比无法恢复的结果更从容。
数据集市也有类似情况。
专题不多时,从公共数据中抽一份形成专题库很直观。专题多起来以后,同一份明细可能因为不同专题被复制几次。
第一次建表没什么感觉。源字段、标准或者权限发生变化时,多个专题分支就需要一起处理。
集市有时是一套物理数据,有时只是对已有数据重新组织。资源目录回答有什么数据、由谁维护、更新到哪里;主题和专题提供使用视角。已有 DWD、DWS 可以满足使用时先复用,访问逐渐稳定、查询量或者隔离要求发生变化以后,再增加物化结果。
走到数据服务这一端,问题又从数据本身变成了依赖关系。
一张内部表并不难调整,麻烦常常出在几个业务系统已经直接依赖它。某张 DWS、ADS 一旦被外部应用长期查询,事实上已经承担了接口角色,只是没有明确的版本和契约。
分析查询可以继续走 SQL,实时变化可以通过消息订阅,高频生产调用再通过相对稳定的服务接口承接。底层表发生拆分、合并或者存储调整时,外部系统不用跟着一起变化,后续改动会轻一些。
服务层也有自己的维护成本。接口一旦被多个应用长期调用,字段、口径和返回结构本身又会形成新的依赖。底层表难改,很多时候是因为外部依赖没有被看见;到了服务层,依赖看见了,契约反而更不能随意变化。依赖没有消失,只是从表变成了需要明确维护的服务关系。
元数据、标准、质量、血缘、权限这些功能逐渐补齐以后,会出现一个挺现实的现象:治理功能在增加,需要管理的对象也一直在增加。
一张表创建以后,会跟着产生任务、元数据、质量规则、血缘、权限和上下游关系;一个服务开放以后,又多了授权、调用日志和审计信息。前面的数据生产长期留下多少对象,后面的治理工作基本都会跟着变化。
架构图里治理通常画在右侧,运行时却从数据接入就已经开始了。

采集时已经有数据源、批次和接入信息;到了公共明细,会出现标准、质量和主数据对应;进入主题和集市以后,需要知道数据由谁负责;服务开放以后,又增加应用身份、权限和访问记录。
Catalog 如果只有表名、字段和注释,遇到变更时能够提供的信息还比较有限。更需要知道的是一张表从哪里来,哪个任务在写,谁负责,下游还有哪些任务、指标和服务。
血缘做多以后也会遇到新的问题。自动采集可以得到很多关系,但临时 SQL 和长期生产任务都可能留下一条线。字段变更时,两类关系的影响不同。把血缘和任务状态、查询记录、接口调用放在一起看,往往比单独看一张关系图更容易判断。
质量也会自然落在不同位置。数据没有正常进入平台,问题在接入一侧;编码、重复、字段合法性更接近公共明细;指标之间的关系,则要等主题数据形成以后再检查。规则放在问题产生的位置,排查时少一些来回。
平台运行时间长了,还会慢慢出现另一类对象:很少有人使用,但也没人敢停。
一张表几乎没有查询,任务仍然每天执行;专题结束以后,数据还在更新;接口已经没有调用,权限、日志和监控仍然保留。删除动作很简单,费时间的是确认有没有遗漏的依赖。
查询记录、任务关系、服务调用和数据能否重新生成,在这个时候都会派上用场。实际处理时,可以先把对象放进待退出范围,暂停继续增加新的依赖,再看关联任务、服务和使用记录;确认关系逐渐收敛以后,再进入停更、归档或者清理。
治理本身也会积累对象。
质量规则会增加,血缘关系会增加,权限申请、审批记录和审计配置也会持续增加。数据表已经下线了,对应的质量规则可能还在跑;任务已经停了,血缘图里仍然留着过去的关系;应用已经退出,原来的权限配置没有同步清理。
治理做得越来越细以后,还有一个反过来的问题:这些对象会不会因为已经被纳入管理,反而更容易一直留下来?规则有负责人,血缘能够追踪,权限也有记录,看上去每一项都有出处。但有记录、有人管,只能说明它还在管理范围里,不能直接说明它还有继续存在的必要。
这些东西不会像业务表那样直接占据很多存储,却会逐渐增加判断成本。质量规则、血缘和权限配置可以跟着关联的数据对象一起复核。筛的时候也不用一开始就做得很复杂:质量规则最近还有没有触发,血缘关系对应的任务和查询还有没有运行,权限对应的应用最近还有没有调用,都可以作为继续保留还是进入待清理范围的依据。
湖仓用起来以后,过去一些需要靠复制数据处理的需求,多了其他选择。
低频历史分析可以利用湖里的已有数据,访问频率起来以后再决定是否物化;实时数据可以进入湖侧留存,同时为高频生产查询准备合适的在线访问方式;Spark、Flink 和 SQL 查询各自承担不同负载,也不一定需要各自维护完整的数据副本。
数据副本分散带来的问题少了一些,小文件、Snapshot、查询资源和元数据维护又会逐渐成为新的负担。
多份数据不一致时,通常还能从表和结果里直接发现。小文件数量、Snapshot 累积和元数据增长如果没有专门关注,日常使用时不一定马上能感觉出来,往往要到查询变慢、维护时间变长或者元数据操作开始吃力时才会暴露。
平时可以顺带看几个变化:文件是不是越来越碎,Snapshot 和元数据是不是持续增长,查询规划、提交或者元数据操作是不是比以前更慢。同样的数据规模和任务负载下,这些变化如果一直往一个方向走,表维护通常就需要跟上了。不用专门监控,日常提交、查询和表维护时顺带看一眼,就能看出趋势。
数据什么时候值得再留一份,什么时候按需计算已经够用;一个专题什么时候需要物理集市,什么时候只是重新组织已有数据;一张表、一个任务、一条服务运行几年以后,还有没有继续存在的理由。
Apache Iceberg, Maintenance https://iceberg.apache.org/docs/latest/maintenance
Databricks, What is the medallion lakehouse architecture? https://docs.databricks.com/gcp/en/lakehouse/medallion
Apache Flink, Fault Tolerance Guarantees of Data Sources and Sinks https://nightlies.apache.org/flink/flink-docs-stable/docs/connectors/datastream/guarantees
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。