一、数据契约概念与价值

1.1 什么是数据契约

数据契约,说白了就是数据生产者和消费者之间的一份书面约定。

我习惯把它理解成「API 文档的数据版」。你写接口时,会定义入参、出参、异常码。数据契约做的是同样的事——它明确告诉你:这张表里有什么字段、字段类型是什么、数据什么时候到、延迟多久算异常。

举个例子。实时数仓里,订单流从 Kafka 进来,经过 ETL 写入 ClickHouse。如果没有契约,下游分析师查报表时发现金额字段是 null,他得一层层问:「这个字段谁产的?什么格式?为什么有时候有值有时候没有?」

有了数据契约,这些问题在设计阶段就解决了。

数据契约的核心要素

  • Schema 定义:字段名、类型、精度、是否可为空
  • SLA 承诺:数据延迟上限、可用性百分比
  • 语义说明:字段的业务含义、枚举值范围
  • 变更流程:字段新增/废弃/修改的通知机制
  • 质量基线:空值率、重复率、异常值阈值

1.2 为什么实时数仓需要数据契约

传统离线数仓,一天跑一次,出问题了第二天修。实时数仓不一样——数据是秒级流动的,一个字段类型对不上,整条链路可能直接崩掉。

我在项目中遇到过这样的事:业务方突然在订单 JSON 里加了个嵌套结构,没通知任何人。结果 Flink 任务反序列化失败,整个实时链路挂了 40 分钟。那 40 分钟里,大屏上的 GMV 一动不动,老板直接打电话问怎么回事。

为什么会这样?因为没有契约约束。

实时数仓对数据契约的依赖,比离线数仓重得多。原因有三:

  1. 链路长,排查难:一条数据从业务库到实时大屏,经过 Canal、Kafka、Flink、OLAP 引擎。任何一个环节出问题,定位都要花大量时间。
  2. 时效性要求高:离线任务失败了可以重跑,实时任务失败了,这段时间的数据就丢了。没有契约,你连「数据丢了」都发现不了。
  3. 多方协作频繁:实时数仓通常涉及业务方、数仓团队、数据产品团队。没有契约,沟通成本会指数级上升。

我曾经踩过的坑

有一次,业务方把订单状态字段从 int 改成了 string,没走任何变更流程。Flink 任务直接报错,下游的实时报表停了 2 个小时。从那以后,我强制要求所有数据接入必须签数据契约,否则不给上线。

1.3 数据契约 vs 传统数据建模

很多人问我:「数据契约不就是数据建模吗?搞个新名词唬人?」

还真不是。我做个对比你就明白了。

维度 传统数据建模 数据契约
关注点 数据怎么存、怎么组织 数据怎么交互、怎么保证质量
产出物 ER 图、维度模型、星型模型 契约文件、Schema 注册表、SLA 文档
变更管理 被动响应,出问题了再改 主动约定,变更前通知所有消费者
适用场景 离线数仓、批量处理 实时数仓、流式处理、微服务
核心工具 PowerDesigner、ERWin Schema Registry、Protobuf、Avro
验证方式 建表 SQL 执行 运行时校验 + 自动化测试

传统数据建模解决的是「数据长什么样」的问题。数据契约解决的是「数据怎么保证一直长这样」的问题。

你想想看,一个实时数仓里,数据在多个系统间流转。Kafka 里的消息格式变了,Flink 任务不知道,ClickHouse 表结构没变——这就出事了。数据契约就是要在每个数据交换的边界上,加一道校验和约定。

我的建议

不要把数据契约当成一个独立的东西。它应该嵌入到你的开发流程里:设计阶段定义契约,开发阶段校验契约,上线阶段监控契约。这样才真正落地。

1.4 数据契约在实时数仓中的位置

下面这张图,是我在多个项目中沉淀下来的数据契约落地架构。你可以看到,契约贯穿了实时数仓的每一个数据交换节点。

实时数仓数据契约落地架构 业务数据库 日志采集 消息队列 数据源层 数据契约校验层 Schema 校验 | SLA 监控 | 质量检测 | 变更通知 实时计算层(Flink) ETL 清洗 | 维表关联 | 窗口聚合 | 指标计算 实时存储层 ClickHouse | HBase | Redis | Kafka(结果主题) 数据应用层(大屏 / 报表 / 接口) 契约管理 & 版本控制 每个数据交换边界都部署契约校验,确保数据格式和质量符合约定

从这张图你能看到,数据契约不是挂在某个环节的「装饰品」。它在数据进入实时链路的第一道关口就开始工作,一直贯穿到数据被消费的最后一公里。

我个人习惯在每个数据交换节点都部署契约校验。比如 Kafka 到 Flink 的入口,我会用 Schema Registry 做序列化格式校验;Flink 到 ClickHouse 的出口,我会做字段映射校验。这样任何一个环节出问题,都能第一时间发现并告警。

1.5 数据契约的落地形态

说了这么多概念,你可能想问:「这东西到底长什么样?」

我给你看一个实际的数据契约文件示例。这是我在一个电商实时数仓项目中用过的:

{
  "contract_id": "ods_order_v1",
  "version": "1.2.0",
  "producer": "订单中心",
  "consumers": ["实时数仓", "数据分析", "推荐系统"],
  "schema": {
    "fields": [
      {"name": "order_id", "type": "string", "nullable": false, "description": "订单唯一标识"},
      {"name": "user_id", "type": "string", "nullable": false, "description": "用户ID"},
      {"name": "amount", "type": "decimal(10,2)", "nullable": false, "description": "订单金额"},
      {"name": "status", "type": "int", "nullable": false, "description": "订单状态:1-待支付 2-已支付 3-已取消"},
      {"name": "create_time", "type": "timestamp", "nullable": false, "description": "创建时间"}
    ]
  },
  "sla": {
    "max_latency_seconds": 30,
    "availability": 0.999,
    "data_completeness": 0.99
  },
  "change_notification": {
    "channel": "企业",
    "lead_time_days": 3,
    "approval_required": true
  }
}

这个文件会注册到 Schema Registry 里。每次数据生产方写入 Kafka 时,都会自动校验格式。格式不对?直接拒绝写入,并触发告警。

一个小技巧

契约文件最好用版本号管理。我习惯用语义化版本(SemVer):主版本号变更表示不兼容改动,次版本号表示新增字段,补丁号表示描述性修改。这样下游可以快速判断是否需要调整代码。

嗯,到这里你应该对数据契约有了一个整体的认识。它不是什么高深的理论,就是让数据生产者和消费者之间有个明确的约定。在实时数仓这种「数据秒级流动、链路长、协作方多」的场景下,这个约定能帮你省下大量排查问题的时间。