3. 数据契约核心要素:Schema定义、数据质量约束、数据血缘与SLA

数据契约不是一张空头支票。它得写清楚几件事:数据长什么样、数据干不干净、数据从哪来到哪去、出了问题谁负责。这四块,就是契约的四大核心要素。

我见过不少团队,上来就搞契约,结果只写了个字段名和类型。上线第一天,下游就炸了——字段为空没人管,数据延迟没人赔。说白了,契约要是只定义结构,那跟一张Excel表没区别。

下面我们一个一个拆开讲。

3.1 Schema定义:契约的骨架

Schema定义是契约最基础的部分。它规定了数据的结构、类型、格式。没有Schema,下游根本不知道该怎么消费数据。

我个人习惯,在契约里至少写清楚以下几项:

  • 字段名:大小写敏感,统一风格(比如snake_case)
  • 数据类型:INT、STRING、TIMESTAMP等,要精确到长度和精度
  • 字段描述:每个字段是干什么的,别让下游猜
  • 默认值:字段为空时用什么兜底
  • 版本号:Schema版本,兼容性策略(向前/向后)

举个例子,一个用户订单表的契约Schema定义:

{
  "schema_version": "1.2.0",
  "compatibility": "BACKWARD",
  "fields": [
    {
      "name": "order_id",
      "type": "STRING",
      "length": 32,
      "description": "订单唯一标识,UUID格式",
      "nullable": false,
      "default": null
    },
    {
      "name": "user_id",
      "type": "INT64",
      "description": "用户ID,关联用户表主键",
      "nullable": false,
      "default": null
    },
    {
      "name": "order_amount",
      "type": "DECIMAL(10,2)",
      "description": "订单金额,单位元",
      "nullable": true,
      "default": 0.00
    },
    {
      "name": "created_at",
      "type": "TIMESTAMP",
      "description": "订单创建时间,UTC时区",
      "nullable": false,
      "default": "CURRENT_TIMESTAMP"
    }
  ]
}
我的经验:Schema定义里一定要写兼容性策略。我遇到过下游用旧版Schema解析新版数据,结果字段类型变了,直接报错。后来我们在契约里强制要求BACKWARD兼容,新增字段必须可空或有默认值,才算过了第一关。

3.2 数据质量约束:契约的肌肉

光有Schema还不够。数据进了湖仓,质量不过关,下游照样没法用。数据质量约束,就是契约里对数据“干净程度”的硬性要求。

常见的约束有三类:

约束类型 说明 示例
非空约束 关键字段不能为NULL user_id NOT NULL
唯一约束 字段值在数据集内唯一 order_id UNIQUE
取值范围约束 字段值必须在指定范围内 order_amount >= 0 AND order_amount <= 999999.99

你想想看,如果订单金额出现了负数,下游财务系统直接算错账,谁来赔?所以这些约束必须在数据写入湖仓之前就校验。

我曾经在一个项目中,上游把用户年龄字段传成了负数。下游做用户画像分析,结果平均年龄变成了-5岁。排查了半天,才发现是上游代码bug。从那以后,我坚持在契约里加上取值范围约束,并且用自动化工具在写入时做校验。

质量约束的落地方式:

  • 在数据写入API层做校验(实时)
  • 在湖仓的ETL管道里做校验(准实时)
  • 在数据湖的元数据层做定期扫描(离线)

三种方式组合使用,才能覆盖所有场景。

3.3 数据血缘:契约的脉络

数据血缘,就是数据的“族谱”。它记录了数据从哪来、经过哪些转换、最终去了哪。没有血缘,出了问题你根本不知道影响面有多大。

在数据契约里,血缘信息至少要包含:

  • 上游来源:数据来自哪个表、哪个字段、哪个系统
  • 转换逻辑:经过了哪些ETL步骤、SQL逻辑、函数处理
  • 下游消费方:哪些报表、模型、应用依赖这份数据
  • 依赖关系:数据链路中的上下游依赖,包括调度依赖

下面这张图,展示了一个典型的数据血缘链路:

数据血缘链路示例 订单系统(MySQL) order_id, user_id, amount 用户系统(MySQL) user_id, name, age ETL管道 数据清洗 + 关联 去重 + 格式转换 湖仓宽表 ods_order_user 用户画像模型 dws_user_profile BI报表 日活/营收报表 💡 血缘的价值: 当湖仓宽表数据异常时,可快速定位是上游订单系统还是用户系统的问题。 同时能评估影响范围——哪些报表和模型会受影响。
注意:血缘信息不是一次性的。数据链路会变,ETL逻辑会改,下游消费方会增减。我建议在契约里约定血缘信息的更新频率,比如每次Schema变更时同步更新血缘图。否则,血缘图就成了摆设。

3.4 SLA:契约的底线

SLA(Service Level Agreement)是契约里最“硬”的部分。它规定了数据提供方必须达到的服务水平,以及违约后的处理方式。

常见的SLA指标包括:

  • 数据时效性:数据多久更新一次?T+1?实时?
  • 数据可用性:数据表/接口的可用时间百分比(99.9%?99.99%?)
  • 数据完整性:数据量是否达标?行数偏差在多少以内?
  • 数据准确性:关键字段的准确率要求(比如金额字段误差不超过0.01%)
  • 响应时间:查询接口的P99延迟是多少?

举个例子,一个实时订单流的SLA定义:

SLA指标 目标值 测量方式 违约处理
数据时效性 延迟 < 5分钟 Kafka消费延迟监控 超时30分钟,赔偿下游1小时数据重跑资源
数据可用性 99.9% Prometheus探活 月度可用性低于99.9%,减免下月服务费
数据完整性 行数偏差 < 0.1% 每日对账任务 偏差超0.1%,触发告警并补推数据
数据准确性 金额字段准确率 100% 抽样校验 + 规则引擎 发现一笔错误,立即回滚并修复

说实话,SLA这块最容易扯皮。我见过一个项目,上游说“数据延迟不超过1小时”,下游理解成“数据最晚1小时到”。结果有一次数据延迟了59分钟,下游说没超,上游说快超了。后来我们在契约里明确写了“延迟从数据产生时间算起,到湖仓可查询为止”,才把标准统一了。

我的建议:SLA的测量方式一定要可量化、可自动化。不要写“尽量保证数据准确”这种模糊话。每条SLA都要有对应的监控指标和告警规则。我曾经帮一个团队梳理SLA,发现他们写了“数据质量良好”这种条款——这根本没法执行。后来我们改成了“关键字段非空率≥99.9%”,才算落地。

3.5 四要素的关系

这四个要素不是孤立的。它们共同构成了数据契约的完整体系:

  • Schema定义是基础,没有它,其他都无从谈起
  • 数据质量约束是保障,确保数据可用
  • 数据血缘是追溯能力,出了问题能快速定位
  • SLA是承诺,让上下游有据可依

我习惯把它们比作一栋房子:Schema是地基,质量约束是墙体,血缘是水电管线,SLA是物业合同。缺了任何一个,这房子都住不踏实。