
做过一次数据平台后,我不再把它当报表入口
我一开始对数据平台的理解很直接:把数据接进来,存到库里,再提供页面、报表或者接口给别人用。
做过以后,发现这个理解太简单了。
报表和页面在很靠后的位置。麻烦的事情在前面:数据从哪里来,字段是谁定义的,谁负责解释口径,多久刷新一次,谁可以消费,接口申请有没有审批,质量规则失败以后谁处理,整个数据量越来越大以后如何管理。
这些问题如果只靠文档和人工沟通,平台很快就会变成一个更大的“查数入口”。页面很多,表很多,数据很多,但每一次字段变化、规则失败、接口异常,还是靠人去问、靠日志去翻。
这篇文章不是讲完整的数据治理理论。我只记录自己在内部数据平台项目里碰到过的几个具体问题,以及后来为什么会觉得:数据平台要管理的不是页面,而是数据关系。
这个数据平台里有哪些东西
这个平台面向不同角色提供数据管理和消费能力。可以抽象成下面这些部分:
Use Case Management
Data Object & Metadata Management
Manual Data Onboarding
Data Dictionary
Data Consumption Interface
Data Refresh
Data Store
Data Quality Check
Performance Report模块之间大致是这样的关系:
flowchart TD
A["源系统数据"] --> B["ETL Task"]
C["手工上传文件"] --> D["Manual Data Onboarding"]
B --> E["Data Store"]
D --> E
E --> F["Data Object"]
F --> G["Metadata"]
G --> H["Data Dictionary"]
G --> I["Quality Rules"]
G --> J["API Apply"]
J --> L["下游应用消费"]
I --> M["质量结果和监控"]
E --> N["Performance Report"]这张图不难看懂,麻烦的是引用关系。
一个字段可能被质量规则引用,也可能被 API 返回给下游应用。一个手工表字段改了,上传模板、历史数据、字段校验和 API 返回结构都要跟着检查。一个表刷新慢了,问题也未必在接口层,可能是底层表块、索引、统计信息或者 ETL 任务出了问题。
所以我现在不会把数据平台简单理解成一个“查数系统”。它的每一层都可能影响最终结果。
Data Object 不是数据库表名
我最开始也会把 Data Object 理解成数据库表。
后来发现不够。平台要管理的是“这份数据能被谁理解、谁维护、谁消费”。表名只是其中一个属性。
为了说明关系,我把元数据表脱敏并简化成下面这样:
CREATE TABLE META_DATA (
ID VARCHAR2(64) PRIMARY KEY,
DATA_DOMAIN VARCHAR2(100),
DATA_OWNER VARCHAR2(100),
DATA_CATEGORY VARCHAR2(100),
DATA_SOURCE_TYPE VARCHAR2(50),
SOURCE_TABLE_NAME VARCHAR2(200),
DATA_UPDATE_CYCLE VARCHAR2(50),
DATA_LOGIC_PROVIDER VARCHAR2(100),
STATUS VARCHAR2(30),
CREATED_AT TIMESTAMP
);DATA_OWNER 不是展示字段。后面质量规则失败、字段口径变更、接口消费审批,都需要知道谁能解释这份数据。
DATA_SOURCE_TYPE 会影响后续治理方式。来自 ETL 的数据,要看同步任务、源表结构和刷新周期。来自手工上传的数据,要看模板、字段校验、上传权限和历史覆盖规则。
DATA_UPDATE_CYCLE 也不是只给页面显示“每天”或“每小时”。它会影响质量规则什么时候跑,用户看到的数据是否过期,下游接口能不能接受延迟。
这些信息如果只写在文档里,后面一定会丢。把它们结构化存下来,才有机会做影响分析。
手工表字段变更比普通 CRUD 危险
手工数据上传看起来简单:用户上传 Excel,后台解析,写入数据库。
问题出现在字段变化时。
我翻历史记录时看到过一类处理逻辑:如果元数据类型是手工表,就读取真实数据库表字段,再和新的字段配置做对比,判断要新增哪些列、删除哪些列、更新哪些列。
脱敏后的伪代码大概是这样:
if (MetaDataType.MANUAL.equals(metaData.getType())) {
List<ColumnInfo> currentColumns = businessDataMapper.selectColumns(metaData);
List<FieldConfig> oldConfig = currentColumns.stream()
.map(column -> new FieldConfig(column.getColumnName()))
.toList();
List<FieldConfig> newConfig = request.getFieldConfigList();
List<FieldConfig> needAdd = diffAdd(oldConfig, newConfig);
List<FieldConfig> needDrop = diffDrop(oldConfig, newConfig);
List<FieldConfig> needUpdate = diffUpdate(oldConfig, newConfig);
alterManualTable(metaData, needAdd, needDrop, needUpdate);
}这段逻辑本身不难,风险在业务动作后面。
比如用户把所有字段都删了,Oracle 会直接报错:
ORA-12983: cannot drop all columns in a table这个报错表面上是 DDL 失败,实际说明系统没有提前拦住一个不合法的业务动作。更麻烦的是,Oracle 的 DDL 不适合当成普通业务更新来处理。你不能简单假设“元数据保存”和“物理表变更”一定能像普通事务一样一起回滚。
所以手工表字段变更不能只靠数据库兜底。保存前至少要检查这些事:
不能删除全部字段。
不能删除被质量规则引用的字段。
不能删除已被 API 暴露的字段。
字段类型变更要检查历史数据是否还能转换。
字段删除前要记录变更历史。
物理表变更失败时,元数据不能表现成已经生效。这里我后来会更倾向把“变更申请”和“变更执行”拆开:先保存一个待执行的变更单,完成引用检查和 SQL 预览,再执行 DDL。执行成功后再把 Data Object 切到新版本。这样比页面点保存以后直接改物理表稳得多。
这就是数据平台和普通 CRUD 的区别。你改的不是一个页面字段,而是一整条数据链路上的关系。
质量规则不能只留下日志
我在历史记录里还看到过数据质量规则执行日志。脱敏后大概是这种结构:
[executeRulesAsyncExecutor-7] Executing rule config:
{
"ruleId": "RULE_001",
"objectId": "DATA_OBJECT_001",
"fieldName": "SERIAL_NO",
"ruleType": "LENGTH_CHECK",
"expectedLength": 10,
"dataOwner": null,
"dataSteward": null
}当时还有一个具体问题:想检查 Oracle 某个字段所有数据长度是不是 10。
这类规则的 SQL 很简单:
SELECT
COUNT(*) AS invalid_count
FROM ods_example_object
WHERE LENGTH(serial_no) <> 10
OR serial_no IS NULL;如果业务检查的是字节长度,而不是字符长度,这里就不能直接用 LENGTH,要改成 LENGTHB 或按具体编码规则处理。这个例子里的 SERIAL_NO 更接近普通编号字段,所以用字符长度表达思路。
如果要看异常值分布,可以继续查:
SELECT
LENGTH(serial_no) AS value_length,
COUNT(*) AS row_count
FROM ods_example_object
GROUP BY LENGTH(serial_no)
ORDER BY value_length;难点不在这条 SQL,而在规则失败后怎么办。
如果 invalid_count > 0,平台至少要能回答:
这条规则属于哪个 Data Object?
检查的是哪个字段?
字段负责人是谁?
规则是手工配置还是模板生成?
失败数据要不要落表?
失败后通知谁?
下游 API 是否还允许消费?否则质量规则只是往日志里多写了一条记录。业务上没有人接手处理。
我还遇到过本地执行和服务器执行 CPU 占用差异的问题。本地跑规则时 CPU 看起来接近满载,服务器上异步执行却只占很低比例。这个现象不能直接归因成“服务器更强”或“异步更慢”。要看线程池配置、任务拆分方式、数据库等待、连接池、批量大小,以及规则是否真的并行执行。
所以质量规则模块不能只设计“规则配置”和“执行按钮”。它还要有执行模型、失败结果、责任人和观测方式。
API 消费要能追到字段级别
API 消费这一块也很具体。平台可以生成 Restful API,并做权限管控。
为了说明后面的字段引用检查,我把结构简化成三张表:
CREATE TABLE API (
ID VARCHAR2(64) PRIMARY KEY,
META_DATA_ID VARCHAR2(64),
API_NAME VARCHAR2(200),
API_ACCOUNT_ID VARCHAR2(64),
API_ID VARCHAR2(100),
STATUS VARCHAR2(30),
CREATED_AT TIMESTAMP
);
CREATE TABLE API_FIELD (
ID VARCHAR2(64) PRIMARY KEY,
API_ID VARCHAR2(100),
META_DATA_ID VARCHAR2(64),
FIELD_NAME VARCHAR2(100),
FIELD_ALIAS VARCHAR2(100),
ENABLED NUMBER(1)
);
CREATE TABLE API_CALL_HISTORY (
ID VARCHAR2(64) PRIMARY KEY,
API_ID VARCHAR2(100),
CALLER VARCHAR2(100),
STATUS_CODE NUMBER,
COST_MS NUMBER,
CALLED_AT TIMESTAMP
);这里的重点不是表怎么建,而是状态和历史。
用户申请 API 后,状态可能是 Awaiting approval。撤回后,状态可能是 Revoked。接口字段也要单独存,因为字段删除、改名、隐藏、别名变化都会影响下游。
这说明 API 消费不是开发随手给一个接口地址。它要经过申请、审批、账号绑定、字段暴露和调用审计。
API 一旦给出去,下游应用就会依赖它。字段改名、字段删除、口径变化、权限变化,都可能影响别人。平台如果只记录“某个 API 被申请过”,却没有记录这个 API 暴露了哪些字段,后面做影响分析时还是会退回人工排查。
所以我后来理解的 API 消费,不是“把查询接口开放出去”,而是把 Data Object 变成一个有权限、有审计、有字段版本意识的服务。
变更前检查应该成为平台能力
重看这些素材以后,我觉得数据平台很应该补一类能力:变更前检查。
比如有人要修改 Data Object 的字段配置,系统不应该直接保存。至少要先做字段引用检查。
SELECT 'QUALITY_RULE' AS ref_type, rule_id AS ref_id
FROM quality_rule
WHERE meta_data_id = :metaDataId
AND field_name = :fieldName
UNION ALL
SELECT 'API_FIELD' AS ref_type, api_id AS ref_id
FROM api_field
WHERE meta_data_id = :metaDataId
AND field_name = :fieldName
UNION ALL
SELECT 'DICTIONARY' AS ref_type, dict_id AS ref_id
FROM data_dictionary
WHERE meta_data_id = :metaDataId
AND field_name = :fieldName;如果查出来有引用,就不能让字段静默删除。提示要具体到用户能处理:
字段 SERIAL_NO 正在被 2 条质量规则、1 个 API、1 条数据字典记录引用。
请先停用相关规则或调整接口字段,再执行删除。如果这类引用关系每次都现查不同业务表,后面会越来越难维护。我更倾向单独沉淀一张字段引用表,或者至少沉淀一个统一视图:
CREATE TABLE FIELD_REFERENCE (
ID VARCHAR2(64) PRIMARY KEY,
META_DATA_ID VARCHAR2(64),
FIELD_NAME VARCHAR2(100),
REF_TYPE VARCHAR2(50),
REF_ID VARCHAR2(100),
REF_STATUS VARCHAR2(30),
CREATED_AT TIMESTAMP
);质量规则、API 字段、数据字典、上传模板发生变化时,同步维护这张引用表。这样做会多一点开发成本,但换来的是稳定的影响分析能力。
这比等数据库报错、等下游应用失败、等用户投诉要稳得多。
数据平台的难点是把关系显式化
如果只看功能清单,这个平台的模块并不难理解:Use Case、Metadata、手工上传、数据字典、API、质量规则、性能监控。
做起来以后,难点在模块之间的关系:
Data Object 和字段的关系。
字段和质量规则的关系。
字段和 API 返回结构的关系。
字段和数据字典的关系。
手工上传模板和物理表的关系。
ETL 任务和目标表的关系。
表空间状态和平台性能的关系。这些关系如果不建模,所有影响分析都会退回人工搜索。
数据能不能查到只是第一步。接下来要问的是,当数据变化时,系统能不能告诉你会影响谁;当规则失败时,系统能不能告诉你谁负责;当接口被消费时,系统能不能告诉你这次消费是否被允许。
如果这些关系都能被系统记录下来,数据平台才开始变得可维护。
