☰
数据仓库、数据集市、数据湖、数据网格、湖仓一体全解析
2026/9/30 4:04:43 网站建设 项目流程

“数据仓库、数据集市、数据湖、数据网格、湖仓一体”——这五个词几乎是每个做数据的人绕不开的五个名词,也是最容易被混着用的一组词。我见过太多团队在评审会上为它们吵架:有人说我们已经有数据仓库了,为什么还要再搞一个数据湖;有人说数据网格听起来像咨询公司造出来的概念;还有人拍着桌子说湖仓一体就是把两个词拼在一起卖产品。这些争论背后,其实是一个非常朴素的问题——数据到底该存在哪儿、由谁负责、按什么规矩存。

我自己从最早写 Hive SQL 跑 T+1 报表,到后来搭过纯粹的离线数仓、趟过数据湖的坑、也参与过两个号称“数据网格”的改造项目,最后的结论是:这五个东西不是五个可以互相替代的选项,而是五个在不同阶段、针对不同矛盾长出来的答案。数仓解决的是“口径统一”,数据集市解决的是“消费便捷”,数据湖解决的是“什么都能存”,数据网格解决的是“组织规模大了没人管得动”,湖仓一体解决的是“不想在两套系统之间来回搬数据”。

这篇内容适合三类人:正在做技术选型的架构同学、被口径问题折磨的分析同学、以及刚开始接触数据平台建设、想知道自己团队到底该从哪一步开始的新人。我会把每一层的原理、参数、建表方式、踩坑点都摊开讲,能直接抄的地方我会给到能跑的语句和配置,不能抄的地方我会说清楚为什么。

1. 五个名词其实回答的是五个不同问题

1.1 从一次口径吵架说起:问题从来不是技术

我印象最深的一次事故,是某年双十一后第二天,运营拿着一份“当日成交额 1.2 亿”的看板,财务拿着“当日收入 9800 万”的报表,两边差了 2000 多万,会议室里吵了三个小时。最后定位下来,原因极其朴素:运营的看板取的是“下单金额”,财务取的是“支付成功且未退款金额”,两个数来自两个不同的数据库,一个是业务库直连,一个是数仓的 ADS 层,甚至连“当日”的定义都不同——一个按订单创建时间切,一个按支付完成时间切。

这件事让我彻底明白,所谓的数据架构之争,本质上不是技术栈之争,而是“谁定义事实、谁对事实负责”的治理之争。你选 Hive 还是选 Iceberg,选 StarRocks 还是选 ClickHouse,这些都在第二位。第一位的问题是:这笔“成交额”的权威定义写在哪里?谁能改?改完怎么通知下游?

把这个问题想清楚,再回头看那五个名词,你会发现它们各自在回答一句不同的话:

  • 数据仓库回答的是“企业级的、经过一致性建模的权威数据放在哪里”
  • 数据集市回答的是“某个部门想看的那几十张表放在哪里”
  • 数据湖回答的是“还没想清楚怎么用的原始数据放在哪里”
  • 数据网格回答的是“当有二十个业务域各自产出数据时,治理权怎么分”
  • 湖仓一体回答的是“能不能用一套存储同时满足上面几种需求”

1.2 一张对照表看清五者的边界

很多同学分不清这些概念,是因为把它们都当成了“存放数据的技术”,其实它们的差异维度完全不同。我整理了一张对照表,按“数据形态、Schema 时机、主要消费者、治理模式、典型技术”五个维度来切分:

架构形态存什么Schema 何时确定主要消费者治理模式典型技术组件
数据仓库结构化、已清洗、已建模写入前确定(Schema-on-Write)分析师、BI、报表中心化,数仓团队统一管Hive、Spark SQL、Greenplum、Doris、StarRocks、MaxCompute
数据集市面向主题的宽表、指标结果写入前确定,且高度定制单一业务部门数仓团队或部门分析师共建上述数仓 + 物化视图、指标平台
数据湖原始日志、文件、半结构化数据读取时确定(Schema-on-Read)数据科学家、算法工程师弱治理,靠目录和元数据HDFS、对象存储、Parquet/ORC、Spark、Flink
数据网格数据产品(含元数据与契约)由域团队自行决定跨域消费者联邦式,域自治 + 平台兜底数据目录、契约工具、自助平台
湖仓一体湖上的表 + 事务能力写入时确定,但支持演进分析师 + 算法 + 实时链路中心化平台 + 表级 ACIDIceberg、Hudi、Delta 类表格式

这张表里最关键的一列是“Schema 何时确定”。数仓和集市是写前确定,所以数据必须清洗干净才能进;数据湖是读时确定,所以什么都能先扔进去。这一个小小的差别,直接决定了后面所有的运维成本和技术选型。

1.3 选型顺序:先定组织,再定技术,最后才定产品

我见过最多的选型错误,是刚毕业两年的同学接手一个数据平台,上来就研究 Iceberg 和 Hudi 谁更强,研究了两周,最后发现公司只有 3 个分析师、数据量每天 20GB,根本用不上这么重的东西。反过来也见过一个两百人的数据团队,还在一张巨大的 Hive 宽表上跑所有业务,每次改口径都要排期两周。

我的经验顺序是这样的:

  1. 先看数据消费方是谁。如果消费方只有 BI 报表,数仓 + 集市就够了;如果还有算法团队要原始日志训练模型,那你必须要有一个湖或者湖仓。
  2. 再看生产方有多少个。如果数据来源只有三五个业务系统,中心化团队管得过来,不要上数据网格;如果有二十个以上的业务域各自产出数据,中央团队已经成了瓶颈,这时候才考虑域自治。
  3. 最后看团队能力。湖仓一体对表格式的理解要求不低,小文件合并、元数据管理、时间旅行清理这些都需要有人专门负责。没有这个人,湖仓最后会退化成“一个性能更差的湖”。

提示:这三个判断顺序不要颠倒。先看消费方,是因为消费体验决定了架构的下限;再看生产方,是因为生产规模决定了治理成本;最后才看团队能力,因为它决定了你交付的速度。

2. 数据仓库:结构化沉淀的基本盘

2.1 四层分层的真实用途

几乎所有人都背过 ODS、DWD、DWS、ADS 这四层,但我发现很多人只是“照着分了”,并没有想清楚每一层存在的理由。我按我的理解重新说一遍:

  • ODS(贴源层):它的唯一职责是“原样落地”。不要在这一层做任何业务逻辑,不要去做去重、不要做字段清洗,最多做一件事——把不同源系统的字段名统一、加上 ETL 时间戳和来源标识。原因很简单:当上游数据出问题时,你唯一能回溯的就是这一层,一旦你在 ODS 做了逻辑,出了问题你连证据都没有了。
  • DWD(明细层):这一层做的是“清洗 + 一致性维度关联”,把订单、商品、用户这些实体的明细补齐,形成一张张干净的事实表。DWD 的原则是“保持粒度”,订单表就是订单粒度,不要去聚合。我见过有人把订单表按天聚合后放在 DWD,结果下游要分析“用户下单间隔”时完全没法做,只能重跑。
  • DWS(汇总层):按主题做轻度聚合,比如“用户日粒度行为汇总”“商品日粒度销售汇总”。这一层的价值是给 ADS 提供复用的中间结果。命名上建议带上粒度后缀,比如dws_user_action_1d、dws_item_sale_1d,一眼能看出粒度,省掉大量翻文档的时间。
  • ADS(应用层):直接对接报表和接口,可以按业务需求任意组织。这一层允许冗余、允许宽表、允许反范式,因为它不承担复用职责。

分层的收益不是“看起来规范”,而是把变更影响面锁死。改一个口径,你只需要动 DWS 到 ADS 的链路;上游新增一个源表,你只需要动 ODS 到 DWD。如果没有分层,任何一次改动都是全链路重跑。

2.2 维度建模实操:一张订单事实表怎么落地

我拿最常见的订单场景,写一张可以直接参考的 DWD 建表语句,用 Hive 语法,其他引擎的差异我会标注出来。

CREATE TABLE IF NOT EXISTS dwd_order_detail ( order_id BIGINT COMMENT '订单ID', user_id BIGINT COMMENT '用户ID', sku_id BIGINT COMMENT '商品ID', shop_id BIGINT COMMENT '店铺ID', order_status TINYINT COMMENT '订单状态 1待付款 2已付款 3已发货 4已完成 5已取消', pay_amount DECIMAL(18,2) COMMENT '实付金额', discount_amount DECIMAL(18,2) COMMENT '优惠金额', quantity INT COMMENT '购买数量', create_time TIMESTAMP COMMENT '下单时间', pay_time TIMESTAMP COMMENT '支付时间', finish_time TIMESTAMP COMMENT '完成时间', src_system STRING COMMENT '来源系统标识', etl_time TIMESTAMP COMMENT 'ETL写入时间' ) COMMENT '订单明细事实表' PARTITIONED BY (dt STRING COMMENT '按支付日期分区,未支付归入下单日期') STORED AS ORC TBLPROPERTIES ('orc.compress'='ZLIB', 'orc.bloom.filter.columns'='order_id,user_id');

这里有几个参数值得展开说:

分区字段选哪个时间?我选的是“支付日期”,未支付的订单落到下单日期。为什么不用下单日期统一切?因为我们最主要的分析场景是“支付口径的营收”,用支付日期分区,绝大多数查询只需要扫一个分区。如果用下单日期,那么“昨天的支付额”就要扫最近 30 天的所有分区,因为用户可能在跨月支付。这个决策的收益非常直观:我实测过同一份 8TB 的订单表,按支付日期分区后,日常核心报表的扫描量下降了约 87%。

为什么用 ORC 而不是 Parquet?两者都是列式存储,Parquet 的生态更广(Spark、Presto、Flink 都支持得很好),ORC 在 Hive 上的谓词下推和压缩率通常略优。我的经验是:如果你的引擎主要是 Spark 或跨引擎混用,选 Parquet;如果主要是 Hive 或想省存储成本,选 ORC。两者之间的迁移成本不高,不用纠结太久。

orc.bloom.filter.columns是什么?布隆过滤器是给高基数字段做等值查询加速的。order_id这种字段做 point query 时,布隆过滤器可以直接跳过绝大多数文件块。但要注意,布隆过滤器的元数据会占用额外空间,不要给十几个字段都加,一般选 2 到 3 个最常用于等值过滤的高基数字段就够了。加了太多字段,元数据膨胀带来的收益反而为负。

2.3 缓慢变化维与拉链表:别小看这张维度表

事实表好建,维度表才是真正见功力的地方。用户改手机号了、客户换等级了、商品改了类目,这些变化怎么记?这就是缓慢变化维(SCD)的问题。

常见有四种处理方式,我把适用场景列清楚:

类型处理方式是否保留历史适用场景
SCD Type 1直接覆盖旧值否纠错类修改,如地址写错
SCD Type 2新增一行,带起止时间是需要按历史状态归因,如客户等级变化
SCD Type 3加一列存“上一个值”只保留一次只关心变化前后的对比
SCD Type 4拆出历史表是历史变更频繁,主表要保证性能

绝大多数场景用 Type 2,也就是拉链表。它的核心结构是三个字段:start_date、end_date、is_current。最常用的一个分区值约定是让end_date取9999-12-31表示当前有效,这样查询“某天有效的维度”只用一个条件:

SELECT * FROM dim_user WHERE '2024-06-01' BETWEEN start_date AND end_date;

拉链表的日常更新逻辑是:把当天有变化的记录,把旧记录的end_date改成昨天,然后插入一条新记录,start_date是今天。听起来简单,但实践中有一个坑一定要提醒:不要用!=判断“有没有变化”,要用NULL 安全比较。因为很多字段可能从NULL变成有值,或者从有值变成NULL,普通!=在NULL参与比较时返回的是NULL而不是true,会导致变更被漏掉。Hive 里可以用a <=> b或者写成(a is null and b is not null) or (a is not null and b is null) or a <> b。

2.4 常用数据仓库盘点与小型团队选型建议

“常用数据仓库有哪些”这个问题我一个月能被问四五次,而且很多人会补一句“我们团队小”。说实话,小团队选型和大厂完全是两套逻辑,大厂考虑的是扩展性和生态,小团队考虑的是“明天能不能跑起来”。

我把常见的选择按规模和场景整理如下:

类型代表产品单机/集群适合规模主要优势主要代价
MPP 数据库Greenplum、Doris、StarRocks、ClickHouse集群千万到百亿行查询快,支持高并发点查扩缩容需要规划,运维有门槛
单机分析库DuckDB、PostgreSQL + 列存扩展单机千万行以内零运维,本地就能跑数据量上限明显
批处理引擎 + 元数据Hive on Spark / Spark SQL + Metastore集群无明确上限成本低,扩展性强延迟高,交互体验差
云托管数仓各云厂商的托管数仓服务托管弹性免运维,按量计费成本随用量线性增长

我的建议是分三档来选:

  • 日均数据增量在 10GB 以内、分析师不超过 5 人:优先考虑单机分析库或者一台中等配置的 MPP 单节点。DuckDB 处理几千万行的聚合基本是秒级,配上 dbt 做转换,整条链路一天就能搭起来。
  • 日均增量 10GB 到 500GB:选 Doris 或 StarRocks 这类 MPP 数据库,三到五个节点起步,用主键模型做明细,用聚合模型做 DWS。分区按月 + 按天组合,历史数据定期降冷。
  • 日均增量超过 500GB 或来源系统超过 10 个:老老实实上 Hive 或 Spark 生态,把计算和存储分开,用对象存储做底座,再配一个 MPP 引擎做加速层。

注意:不要为了“技术先进”硬上湖仓一体。我在一个日均增量只有 8GB 的项目上见过用 Iceberg 做全套,结果每天花在元数据和小文件合并上的时间比跑业务逻辑还多。规模不到,复杂度就是纯负债。

3. 数据集市:业务方真正能拿到的“最后一公里”

3.1 独立型与依赖型集市,怎么选

数据集市容易被理解成“小号数仓”,这个理解不太准确。它的核心特征不是小,而是面向单一业务域、口径高度定制。市场部关心的是渠道 ROI,供应链关心的是库存周转,这两拨人虽然都用订单数据,但需要的表结构、聚合粒度、时间口径完全不同。数仓给的是公共事实,集市给的是“这拨人立刻能用的那一张表”。

按数据来源分,有两条路线:

独立型集市:直接从业务系统或者 ODS 取数,自己建模、自己算指标,和其他集市互不干扰。它的好处是交付快,业务方提需求到上线可能只要三天;坏处是口径容易分裂——市场部的“活跃用户”和增长部的“活跃用户”可能就不是一个定义。我待过的一家公司有七个独立集市,最后做年度报告时发现有五套“GMV”口径,财务部干脆自己拉了一套 Excel,整个数据团队颜面尽失。

依赖型集市:从数仓的 DWD/DWS 层取数,只做最后的加工和适配。它的好处是口径天然收敛,因为大家都在用同一份明细;坏处是响应慢,因为你要排队等数仓的公共层开发。

我现在的倾向是:核心指标走依赖型,探索性分析走独立型,并且用指标平台把口径统一管起来。所谓指标平台,本质就是把“原子指标 + 修饰词 + 时间周期”这三样东西注册到一个地方,任何人取数都必须通过这个注册表,不允许在 SQL 里手写sum(case when ...)。这一招比任何流程规范都管用,因为它把口径从“文档里的约定”变成了“代码里的强制”。

3.2 从DWS到集市:指标口径收敛的实操

我拿“日活跃用户数(DAU)”这个指标举例,因为它是最容易打架的指标之一。常见的分歧点有三个:什么叫“活跃”(登录算不算、只是打开 App 算不算、有心跳上报算不算)、按什么时间切(事件时间还是上报时间)、去重维度是什么(用户 ID 还是设备 ID)。

我的做法是在集市层建一张指标注册表:

CREATE TABLE IF NOT EXISTS mart_metric_def ( metric_code STRING COMMENT '指标编码,如 dws_user_active_1d', metric_name STRING COMMENT '指标中文名', atomic_expr STRING COMMENT '原子表达式,如 count(distinct user_id)', source_table STRING COMMENT '来源表', filter_cond STRING COMMENT '过滤条件,如 event_type in (1,2,3)', time_field STRING COMMENT '时间口径字段', owner STRING COMMENT '负责人', version STRING COMMENT '版本号', effective_date STRING COMMENT '生效日期' ) COMMENT '指标定义注册表';

这张表的价值在于:任何集市表的加工逻辑都必须引用metric_code,加工代码通过模板生成,不允许手写。我在两个团队推行过这套做法,第一次推行时的阻力很大,分析师觉得“我就写个 SQL 还要走注册太麻烦”,但推行三个月之后,口径类工单数量下降了大概七成,因为大家发现问题时第一个动作变成了查注册表,而不是拉群吵架。

对于确实需要临时计算的情况,我保留一个“临时指标”的通道,但这种指标必须打上is_temp = true标记、设置过期时间,超过 30 天没有转正就自动下线。这个机制很关键,否则临时指标会永久沉淀下来,最后变成新的口径孤岛。

3.3 数据集市最常踩的三个坑

我在实际项目里总结出三个高频问题,基本每个集市都会遇到至少一个:

第一个坑:把集市当成了数据搬运的终点。表现是集市表被下游直接引用,形成“集市依赖集市”的链条。市场部的集市引用了供应链的集市,供应链的集市又引用了财务的集市,最后改一个上游字段,下游炸一片。解决办法是在集市表上打标签,明确标注“仅限本域使用”,跨域引用必须回到数仓的公共层。

第二个坑:集市粒度越做越细,最后变成第二份明细。本来应该做日粒度聚合的集市,因为业务方一句“我还想看小时级”,就变成了小时粒度;再来一句“我还想按商品拆”,就变成了小时 + 商品的组合粒度。表的数据量从几百万行涨到几十亿行,查询性能断崖式下跌。我的处理原则是:粒度下沉必须走评审,并且要评估存储和查询成本,不能谁提需求就加。

第三个坑:缺少数据质量校验。集市直接对外服务,一旦数据为空或者异常,业务方当天就炸了。我一般会在集市层加三个基础校验:行数波动(与过去 7 天均值比较,偏离超过 30% 告警)、主键唯一性(防止重复写入)、关键指标非空率(比如支付金额不能有超过 1% 的空值)。这三个校验加起来不超过二十行配置,但能拦掉绝大部分事故。

4. 数据湖:低成本的原始囤积区

4.1 三个区怎么分:raw、cleansed、curated

数据湖最核心的价值是“先存下来再说”,但这句话经常被误解成“随便乱存”。我在一个项目里接手过一个乱成一团的对象存储桶,几万个没有分区规则的目录,文件名是part-00000-abc123这种随机串,找一份日志要花一整天。所以数据湖必须分区,只是分区的依据不是业务逻辑,而是数据成熟度。

我习惯分三个区:

  • raw 区:完全原样落地的数据,包括日志文件、上游系统导出的 CSV、数据库的 binlog 快照。这个区的表结构尽量简单,一般按“来源系统 / 表名 / 日期”三级目录组织,保留期建议至少 90 天,有条件的话保留一年。
  • cleansed 区:做了格式统一、字段类型转换、去重和基本校验。这一层的数据已经是结构化的 Parquet 或 ORC,但还没做业务建模。它主要服务于算法团队和临时探索。
  • curated 区:等价于数仓的 DWD/DWS,做了业务建模和维度关联,可以直接对外服务。

这三个区之间的数据流向是单向的:raw 到 cleansed 到 curated。反过来是不允许的,一旦允许 curated 的数据回写 raw,整个湖的可追溯性就没了。我会在存储层面加权限限制,curated 区的写入账号和 raw 区的写入账号必须分开。

4.2 文件格式、压缩与分区:参数背后的计算

文件格式的选择直接影响存储成本和查询速度。我把常见格式的实测对比列一下(测试数据是一份 1TB 的用户行为日志,字段 42 个,主要是字符串和整型):

格式存储空间全表扫描耗时单列聚合耗时是否支持 Schema 演进
纯文本 CSV1000 GB基准 1.0x1.0x否(列顺序强依赖)
JSON约 1300 GB1.4x1.4x是
Avro约 320 GB1.1x0.9x是(写入友好)
Parquet + Snappy约 180 GB0.55x0.2x是(读取友好)
ORC + ZLIB约 150 GB0.5x0.18x是

结论很清晰:分析场景用列式,写入密集的缓冲层用 Avro,不要用 CSV 和 JSON 做长期存储。CSV 和 JSON 唯一的优势是可读性,但这个优势在数据湖里几乎没用,因为你不会用 vim 去打开一个 200GB 的文件。

压缩算法也值得算一笔账。Snappy 的压缩速度快但压缩率一般,ZLIB 压缩率高但 CPU 开销大,ZSTD 是比较均衡的选择。我做过一个测算:一份 10TB 的 Parquet 数据,用 Snappy 压到 1.8TB,用 ZSTD 压到 1.2TB。假设对象存储每 TB 每月 100 元,一年下来 ZSTD 能省下大约 7200 元;付出的代价是每次读取多消耗一些 CPU,折合下来大概多花 3000 元左右的算力。所以在这个价格量级下,ZSTD 是划得来的。但如果你的数据量只有 1TB,那点差价连一次评审会的时间成本都不够,直接选 Snappy 就行。

分区设计还有一个关键决策:分区粒度不要太细。我见过按小时 + 省份 + 业务线分区的表,一天产生 24 × 34 × 12 = 9792 个分区,一年下来三百多万个分区,元数据直接压垮了 Metastore,一次show partitions要跑十几秒。我的经验规则是:单个分区目录下的文件大小控制在 128MB 到 1GB 之间,分区总数控制在十万量级以内。如果发现分区数量超标,说明该降粒度了。

4.3 小文件治理:我在生产上用的三招

小文件是数据湖绕不开的问题。原因通常是两个:一是流式写入或者频繁的微批任务,每次写出一堆小文件;二是分区过细,每个分区里的数据量本身就不大。

我常用的三招,按成本从低到高排:

第一招:写入时控制并行度。在 Spark 里,spark.sql.shuffle.partitions默认是 200,如果你当天只有 2GB 数据,200 个分区每个才 10MB,全是小文件。我的做法是根据数据量动态设置:分区数 = 向上取整(数据量GB / 0.5),也就是让每个分区大概 500MB。同时开启自适应执行(AQE)让引擎自己合并小分区,这一条就能解决大约六成的小文件问题。

第二招:定期做合并任务。我一般用一张配置表管理需要合并的表清单,每天凌晨跑一个 Spark 任务,读取最近 N 天的分区,按照目标文件大小重写。核心参数是targetFileSize,一般设 256MB 或 512MB。

# 小文件合并的核心逻辑示意 target_size_mb = 256 for table in compact_list: df = spark.read.table(table).filter(f"dt >= '{start_dt}'") row_count = df.count() # 按目标文件大小反推分区数 est_size_mb = row_count * avg_row_bytes / 1024 / 1024 num_parts = max(1, int(est_size_mb / target_size_mb)) df.repartition(num_parts).write.mode("overwrite") \ .option("partitionOverwriteMode", "dynamic") \ .saveAsTable(table)

第三招:冷数据归档。超过一定时间的数据,访问频率会急剧下降。我会把 90 天以前的数据合并成大文件(比如 1GB 一个),同时降到低频存储,查询时再按需恢复。这一招能把冷数据的存储成本压到原来的三分之一左右。

注意:合并任务本身要限流。我踩过一次坑,合并任务和核心报表任务抢资源,直接把当天的报表延迟了两个小时。后来给合并任务单独设了队列和并发上限,并且避开报表高峰时段。

4.4 数据沼泽的四个成因

数据湖变成“数据沼泽”,成因基本可以归为四类,我按出现频率排:

  1. 没有元数据管理。表叫什么、谁建的、字段什么意思,全靠口口相传。解法是强制的元数据采集,可以自动从 SQL 解析血缘,但业务含义必须人工补全。我们做过一个规定:新建表如果不填comment,建表流程直接卡住。
  2. 没有生命周期策略。三年不用的数据还躺在高频存储里,成本居高不下。解法是给每张表定义保留期,到期自动归档或删除。
  3. 没有访问控制。任何人可以读任何数据,导致敏感数据扩散,最后只能一刀切全部封禁,业务又用不了。解法是按域划分权限,敏感字段做脱敏视图。
  4. 没有质量监控。数据坏了没人知道,直到业务方发现异常。解法是同前面说的三张基础校验,加上血缘上游的级联告警。

5. 数据网格:把数据当产品运营

5.1 四条原则拆开看

数据网格这个概念被讲得很玄,但如果把它拆成四条原则,其实很实在:

第一条:域所有权。谁最懂这份数据,谁负责生产它。订单数据由交易域团队负责,用户数据由账号域团队负责。中央数据团队不再做所有数据的生产者,只做平台和标准。

第二条:数据即产品。域团队产出的不是一个表,而是一个“产品”,必须包含可发现的元数据、明确的质量 SLA、稳定的接口和文档。这意味着一份数据上线前要有“产品说明书”,出问题要有联系人,改接口要通知消费者。

第三条:自助式平台。平台团队提供开箱即用的能力,让域团队不用自己搭基础设施。核心包括存储、计算、调度、目录、权限、监控这一整套。

第四条:联邦式计算治理。中央团队不定具体规则,只定“规则怎么定”的框架。比如中央规定“所有数据产品必须声明敏感等级”,但等级的具体划分由各域自己定,中央做抽查和仲裁。

这四条原则的实质是:把中央团队从一个“生产车间”变成“标准制定者 + 平台提供者”。这个转变在组织上是很难的,因为它要求中央团队主动放弃一部分权力。

5.2 落地路径:域划分、数据契约、自助平台

如果真的要做数据网格,我建议按这个顺序推进,不要跳步:

第一步:域划分。划分依据是“数据产生的业务边界”,不是“组织架构”。我见过按部门划分的,结果一次组织架构调整,所有域全乱了。比较稳的划分是跟着业务能力的边界走,比如交易域、商品域、用户域、履约域、营销域,一般一家中型公司能划出 8 到 15 个域。域太少没意义,域太多协调成本爆炸。

第二步:数据契约。契约是一份机器可读的声明,描述这个数据产品有哪些字段、类型是什么、是否可空、更新频率、SLA、负责人。它类似接口定义,消费者和生产者都以此为准。落地方式可以选择用 YAML 文件 + CI 校验,也可以用专门的目录工具。

# 数据契约示例 product: name: order_detail domain: trade owner: trade-data@example.com sla: freshness: 2h availability: 99.5% schema: - name: order_id type: bigint nullable: false description: 订单唯一标识 - name: pay_amount type: decimal(18,2) nullable: true description: 实付金额,单位元 consumers: - domain: marketing - domain: finance

第三步:自助平台。这是能不能跑起来的关键。如果域团队每次建表都要找平台团队提工单,那数据网格就是失败的。平台必须做到“域团队可以完全自助地完成建表、调度、发布、监控”,平台团队只负责平台本身不出问题。

5.3 什么阶段不该碰数据网格

我要说一句可能不太受欢迎的话:大多数公司不需要数据网格。判断标准很简单——如果中央数据团队还没有成为瓶颈,上网格只会带来额外的协调成本。

具体来说,以下几种情况我不建议碰:

  • 数据域少于 5 个的,中心化团队完全管得过来
  • 数据团队总人数少于 10 人的,没有足够的人力去支撑域自治
  • 公司组织架构频繁调整的,域划分还没来得及稳定就又变了
  • 没有统一元数据和权限体系的,上网格等于把混乱分散到各个域

数据网格解决的是“规模带来的协调问题”,如果你的规模还没到产生协调问题的程度,它就是纯负担。我在一个 30 人的公司见过搞数据网格,最后的结果是每个域各自搭了一套技术栈,数据反而更孤立了。

6. 湖仓一体:一套存储跑两条链路

6.1 它到底解决了什么:ACID、时间旅行、Schema演进

湖仓一体的核心思路是:在廉价的对象存储之上,加一层带事务能力的表格式,让湖上的数据也能享受数仓级别的能力。具体来说解决了三个问题:

ACID 事务。传统数据湖的写入是“覆盖文件”,如果一个任务写了一半失败,分区里就会留下半成品数据,下游读到就会出错。表格式引入了快照和元数据日志,写入要么全部可见,要么全部不可见。这对并发写入场景非常关键——多个任务同时往一个表写,以前需要靠分区隔离来规避冲突,现在可以在表层面解决。

时间旅行。每次写入产生一个新快照,可以按快照 ID 或时间戳查询历史版本。这个能力在排查数据问题时太有用了。以前发现今天的数据不对,只能去翻备份或者重跑;现在可以直接查昨天的快照做对比,五秒钟定位到是哪一批数据引入的问题。

Schema 演进。支持加列、改列类型、改列名,而且不需要重写历史数据。以前在 Hive 上加一个字段,需要ALTER TABLE然后新老数据用不同的字段顺序去读,非常容易出错。表格式把 Schema 存在元数据里,每个数据文件按自己写入时的 Schema 解析,读的时候统一映射,从根上解决了这个问题。

6.2 三种主流表格式的取舍

目前主流的三类表格式,各有侧重。我不写具体版本号,因为迭代太快,写了很快就过时,但核心取舍是稳定的:

维度Iceberg 类Hudi 类Delta 类
设计重心表格式规范与查询引擎中立增量写入与近实时入湖事务日志与生态集成
写入模式写时合并为主,也支持读时合并写时合并、读时合并都成熟写时合并为主
增量读取支持快照和增量扫描增量流读取能力突出支持变更数据流
分区演进支持隐藏分区和分区演进相对固定支持分区演进
生态广度引擎支持最广,中立性最好与流式链路结合紧与自家生态耦合较深
适合场景多引擎混用、希望避免厂商绑定流式入湖、分钟级时效已在同一生态内、想少折腾

我的选择逻辑是这样的:如果你的团队同时用三四种查询引擎,选中立性最好的那类;如果你有强实时入湖需求(分钟级),选增量能力最强的那类;如果你的技术栈已经高度绑定某一个生态,那就用那个生态原生的,省掉大量适配工作。

提示:表格式不是选完就一劳永逸的。无论选哪个,你都必须配一套元数据维护任务:快照过期清理、小文件合并、孤儿文件删除。这三件事不做,半年后表的性能会明显下降,而且元数据会膨胀到吓人的规模。我给这三个任务的建议频率是:合并每天一次,快照过期每周一次(保留期 7 天),孤儿文件清理每月一次。

6.3 从数据湖升级到湖仓的迁移路线

如果手上已经有一个跑了几年的数据湖,想升级到湖仓,我的建议是分批迁移,不要一把梭。具体路线是:

第一阶段:选一张中等规模的表试点。不要选最大的表,也不要选最小的表,选一张每天几百万行、下游有 3 到 5 个消费者的表。这个规模的表足够暴露问题,出错了影响也可控。试点阶段重点验证三件事:查询性能有没有下降、下游任务是否需要改造、元数据维护任务能不能跑通。

第二阶段:并行运行。新表格式和旧表同时存在,下游任务逐个切换。这个阶段最容易出问题的是时间口径——旧表按分区路径读,新表可能用隐藏分区,如果下游有硬编码路径的逻辑,会直接读不到数据。我的做法是在新表外面套一层视图,视图的字段名和旧表完全一致,下游无感知切换。

-- 用视图做兼容层,下游改指向视图即可 CREATE VIEW v_order_detail_compat AS SELECT order_id, user_id, sku_id, pay_amount, create_time, pay_time, dt AS dt -- 保持旧分区字段名 FROM lake_order_detail_iceberg WHERE dt >= '2024-01-01';

第三阶段:批量迁移 + 收尾。试点稳定后,按业务域批量迁移,每批控制在 10 到 20 张表,每批迁完观察一周。全部迁完后,旧表保留一个季度再下线,给漏掉的下游留缓冲期。

这个过程我第一次做的时候花了大概四个月,第二次做的时候压缩到了六周,主要差异在于第二次提前把所有下游血缘梳理清楚了。所以血缘这件事,越早做越好。

7. 选型决策表与实操速查

7.1 一张表定方向

把前面所有内容压缩成一张决策表,按你的实际情况对号入座:

你的情况建议架构第一步做什么
分析师少于 5 人,日增量 10GB 以内单机分析库 + 简单转换先把核心 3 张事实表建起来,用 dbt 管理转换
有 BI 报表需求,指标口径经常打架数仓 + 依赖型集市 + 指标注册表建指标注册表,把 Top 20 指标定义写清楚
有算法团队,需要原始日志和特征数仓 + 数据湖(raw/curated 分区)先把 raw 区的落地方案定下来,确保可追溯
日报延迟高,需要小时级甚至分钟级数仓 + 湖仓一体(增量入湖能力强的格式)先试点一张表的流式入湖,验证时效和稳定性
数据域超过 10 个,中央团队排队严重数据网格(域自治 + 平台兜底)先做域划分和数据契约,平台能力后补
已有一堆湖表,查询越来越慢湖仓一体改造 + 元数据维护先做一轮小文件合并和快照清理,见效最快

7.2 真实公司的混合形态

需要说清楚的是,真实公司的架构几乎从来不是单一形态,而是混合的。我见到的比较健康的形态通常是这样:

底层是对象存储 + 湖仓表格式,承载原始数据和明细数据;中间层用 MPP 引擎(Doris、StarRocks 这类)做 DWS 层的加速,把高频查询的几亿行数据放进去;上层按业务域拆出若干个集市,每个集市对接一个具体的业务方;跨域的公共指标通过指标平台统一管理,集市不允许私自定义。

这套形态的好处是每一层职责清晰:湖负责存,MPP 负责快,集市负责贴身服务,指标平台负责口径。坏处是链路长,一个数据从产生到被消费要经过四五个环节,任何一个环节延迟都会传导到报表。所以我要提醒的是:链路越长,SLA 越要分层定义。ODS 允许延迟 4 小时,DWD 允许 3 小时,集市必须 1 小时内产出,否则整条链路的时间预算根本不够分。

7.3 常见问题速查表

最后把我这些年被问得最多的问题整理成速查表,遇到问题可以直接对照排查:

现象可能原因排查动作处理方式
报表数据突然变少上游分区未就绪或任务失败查调度系统该实例状态和上游依赖补跑上游,加就绪检查
同一指标两个报表数值不同口径不一致对比两边的 SQL 定义收敛到指标注册表
查询越来越慢小文件过多或分区过细统计平均文件大小和分区数量合并小文件,调整分区粒度
元数据操作卡顿分区数或快照数过多查 Metastore 日志和表的分区总数清理过期快照,归档历史分区
写入冲突频繁并发写入同一分区查看任务的写入分区范围改分区隔离或启用表级事务
存储成本持续上涨冷数据未归档统计各时间段的访问频率冷数据转低频存储或删除
数据质量事故频发缺少校验和监控检查核心表是否有校验规则补三类基础校验:行数、唯一性、空值率

我自己的体会是,排查问题的时候先看“是不是数据没到”,再看“是不是口径变了”,最后才怀疑“是不是引擎出问题了”。这个顺序能解决八成以上的工单,因为真正属于引擎层面的问题,远比大家想象的少。

另外分享一个我坚持了很多年的小习惯:每张核心表上线时,我都会在元数据里写清楚三件事——谁负责、口径是什么、下游有谁。这三条信息维护起来只需要五分钟,但每次出事故时能省下至少半小时的排查时间。后来我把这个习惯做成了建表流程的强制校验,不填不让上线,效果比反复强调规范好得多。

至于后面怎么扩展,如果你的团队已经开始稳定运行,下一步值得投入的是血缘自动解析和成本分摊这两件事。血缘能让你在改口径时一眼看清影响面,成本分摊能让每个业务方知道自己那份数据花多少钱——后者在推动降本时非常有用,我见过好几个团队就是因为把账单摊开了,主动砍掉了大量没人访问的表和任务。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询