[元数据/数据治理] OpenMetadata 使用指南

1 概述:OpenMetadata 使用指南

[元数据/数据资产/数据治理] OpenMetadata:面向数据与 AI 的开源【统一元数据】、上下文层平台、【数据治理平台】 - 博客园/千千寰宇

2 安装部署

前置要求

  • 最小规格资源: 4C / 6GB
  • Docker 20.10+(或 Docker Compose v2)与足够的 RAM(官方建议 ≥ 8GB);生产环境需规划 MySQL/Postgres 与 Elasticsearch 资源。
  • Python 3.8+(用于本地运行采集/metadata CLI,仅安装 Docker 时非必需)。

方式1:Docker Compose 快速开始(推荐 / 亲测)

部署

  1. 拉取仓库或直接下载 docker-compose 文件:
git clone https://github.com/open-metadata/OpenMetadata.git
cd OpenMetadata

# 切换到指定版本Tag
git checkout 2.0.2
git pull

# 或直接下载最新版 compose 文件(以 2.0.1 为例):
# curl -L -o docker-compose-openmetadata.yml \
#   https://github.com/open-metadata/OpenMetadata/releases/download/2.0.1-release/docker-compose-openmetadata.yml
  1. 启动全部服务(Server + Ingestion/Airflow + 数据库 + 搜索索引):
cd F:\Codes-Minor\Github\OpenMetadata\

# 使用 MySQL 作为元数据仓库 
docker compose -f docker/docker-compose-quickstart/docker-compose.yml up -d
# 或使用 自己调整后的策略: (笔者的最终选择,基于mysql、且调小了部分组件的资源配置)
cp docker/docker-compose-quickstart/docker-compose.yml docker/docker-compose-quickstart/my-docker-compose-mysql.yml
docker compose -f docker/docker-compose-quickstart/my-docker-compose-mysql.yml up -d

# 或使用 PostgreSQL 的 compose 文件: docker compose -f docker/docker-compose-postgres.yml up -d

如果资源吃紧,可以尝试调小关键组件的资源:

elasticsearch(堆降到 512m,容器上限 1G):

elasticsearch:
 container_name: openmetadata_elasticsearch
 image: docker.elastic.co/elasticsearch/elasticsearch:9.3.0
 environment:
      - discovery.type=single-node
      # - ES_JAVA_OPTS=-Xms1024m -Xmx1024m
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
      - xpack.security.enabled=false
    deploy:
      resources:
        limits:
          memory: 1g
        reservations:
          memory: 512m
    # …其余(ports/healthcheck/volumes/networks)保持不变

mysql(容器上限 1G,顺手把 InnoDB 缓冲池调小)

mysql:
 container_name: openmetadata_mysql
 image: ${OPENMETADATA_DB_IMAGE:-docker.getcollate.io/openmetadata/db:2.0.0}
 # command: "--sort_buffer_size=10M"
 command: "--sort_buffer_size=10M --innodb_buffer_pool_size=64M --innodb_log_buffer_size=8M --max_connections=50"
 deploy:
   resources:
     limits:
       memory: 1g
     reservations:
       memory: 512m
 # …其余保持不变

deploy.resources.limits 在 Compose v2(docker compose)下生效;如果你用的是旧的 docker-compose v1,就改用 mem_limit: 1g 这个兼容写法。

  • 三条必须知道的边界:

    1. ES 堆别压到 256m 以下。512m 对 quickstart / 测试够用,但元数据量大时搜索会变慢、甚至集群转 red。生产仍建议 1G+。

    2. limit 必须 ≥ 堆 + off-heap 开销:ES 512m 堆配 1g limit 安全;若堆设 1g 而 limit 只给 768m,容器会被内核 OOM-Kill,症状和你现在很像(ES 反复重启)。

    3. 调小是「分蛋糕」不是「变出蛋糕」。它只是把有限内存合理分配给 server/ingestion,让整体峰值降下来。如果宿主机(或 Docker Desktop 配额)本身真的低于 6~8GB,单靠压容器救不回来 ——首选还是把 Docker 内存调到 ≥8GB,压参只是锦上添花。


访问 Web UI(OpenMetadata-Server)

  1. 访问 Web UI:

默认账号 admin@open-metadata.org / admin。
一个 docker compose up 会拉起 4 类容器:openmetadata-server、openmetadata-ingestion(Airflow)、MySQL/Postgres、Elasticsearch。

  • 登录页

image

  • 主页

http://localhost:8585/my-data
image

访问 Web UI(OpenMetadata-Ingestion := Airflow)

本质上是 Airflow 的二次封装 | 默认账密: admin / admin

image
image

http://localhost:8080/providers
image


常用运维命令

  1. 常用运维命令
  • 查看运行状态
docker compose -f docker/docker-compose-quickstart/my-docker-compose-mysql.yml ps

image

  • 查看指定容器的日志
docker compose -f docker/docker-compose-quickstart/my-docker-compose-mysql.yml logs --tail 10 openmetadata-server

image

  • 进入容器 / 容器执行shell
//进入指定的容器
docker exec -it openmetadata_server bash
或 docker exec -it openmetadata_server sh

//执行shell命令
docker exec openmetadata_server ls /opt/openmetadata
  • 停止运行
//样例命令
docker compose -f docker/docker-compose-quickstart/docker-compose.yml down -v

样例输出:

$ docker compose -f docker/docker-compose-quickstart/docker-compose.yml down -v
time="2026-09-23T15:29:09+08:00" level=warning msg="F:\\Codes-Minor\\Github\\OpenMetadata\\docker\\docker-compose-quickstart\\docker-compose.yml:
the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion"
[+] down 10/10
 ✔ Container openmetadata_ingestion                              Removed                                                                      0.6s
 ✔ Container openmetadata_server                                 Removed                                                                      0.4s
 ✔ Container execute_migrate_all                                 Removed                                                                      0.5s
 ✔ Container openmetadata_mysql                                  Removed                                                                      4.9s
 ✔ Container openmetadata_elasticsearch                          Removed                                                                      5.9s
 ✔ Volume docker-compose-quickstart_ingestion-volume-tmp         Removed                                                                      1.2s
 ✔ Volume docker-compose-quickstart_ingestion-volume-dags        Removed                                                                      0.5s
 ✔ Volume docker-compose-quickstart_ingestion-volume-dag-airflow Removed                                                                      0.8s
 ✔ Volume docker-compose-quickstart_es-data                      Removed                                                                      1.5s
 ✔ Network docker-compose-quickstart_app_net                     Removed                                                                      1.8s

方式2:Linux(Ubuntu/CentOS)生产部署

# 1) 安装 Docker 与 Docker Compose(略)
# 2) 下载指定版本 compose 文件
curl -sL -o docker-compose.yml \
  https://github.com/open-metadata/OpenMetadata/releases/download/1.10.3-release/docker-compose.yml
curl -sL -o docker-compose-postgres.yml \
  https://github.com/open-metadata/OpenMetadata/releases/download/1.10.3-release/docker-compose-postgres.yml
# 3) 后台启动
docker compose up -d
# 4) 查看日志 / 状态
docker compose logs -f openmetadata-server

国内网络拉取镜像可配置 Docker 镜像加速,或使用镜像托管(docker.getcollate.io / 阿里云加速)。

方式3:源码开发 / 手动构建(Windows / Linux)

  • 需 Java(JDK 17+)、Maven、Node.js/Yarn、Python 3.8+。
  • 遵循仓库 DEVELOPER.md:make generate 生成模型 → 构建后端(Maven reactor)与前端(Yarn)→ 本地启动 openmetadata-service。

3 使用指南 for WEB-UI(OpenMetadata-Server)

假设通过 docker compose 安装部署完成后:

主页

image

添加服务(postgresql 数据源) + 采集元数据后:

image

设置

服务:配置【连接器】 + 从不同【数据源】提取【元数据】

  • 服务: 设置连接器并从不同数据来源提取元数据

http://localhost:8585/settings/services

image

  • 支持的数据源:

APIs / 数据库 / 消息队列 / 仪表盘 / 工作流 / 机器学习模型 / 存储 / 搜索引擎 / 元数据 / 驱动器 / 数据观测

APIs

image

image

数据库(必读)

  • 数据库服务

image

  • 添加新服务:

image

下面以添加 Postgresql 数据库为例

  • 配置数据源的连接信息

image

由于笔者的第三方数据源 postgresql 在本地宿主机的另一 docker 容器内,所以我将 Host and Port 配置项 改为了 host.docker.internal:5432

  • 测试连接

image

  • 配置元数据的采集策略
    image

image

配置完毕后(笔者选择:默认,除了系统库以外的全部库、全部schema、全部表、全部存储过程),选择:创建并部署

http://localhost:8585/service/databaseServices/postgresql.mydb/insights

image

图:人工点击/触发【Tab:代理】-【运行】按钮后

  • 连接信息
    image
  • 洞察
    image
  • 数据库
    image
  • 代理

http://localhost:8585/service/databaseServices/postgresql.mydb/agents/metadata
image

人工点击/触发【运行】按钮:
image

查看【日志】 (支持复制、下载等操作)
image

image

运行完毕后,元数据即采集完成
image

再去看数据资产,即有数据了。
image

搜索引擎

image

消息队列

image

机器学习模型

image

探索

image

image

image

血缘关系

观测-数据质控

概要

测试用例

治理-术语库/glossary

image

治理-本体浏览器/ontology

image

治理-分类/Tags

image

治理-列批量操作

治理-指标/Metrics

治理-工作流/Workflow

数据市场-概览

image

数据市场-域

image

数据市场-数据产品

image

上下文中心-仪表盘

上下文中心-文章

上下文中心-文件

上下文中心-记忆

上下文中心-归档

Z FAQ for OpenMetadata 部署 & 使用

Q: 数据源适配与自定义连接器开发? (必读)

Q: 基于OpenMetadata SDK开发企业内部门户连接器的自定义数据源采集? (必读)

  • pip install openmetadata-ingestion-sdk
  • 编写核心的数据采集代码
# 导入OpenMetadata SDK的核心客户端,负责和OpenMetadata服务交互
from metadata.ingestion.ometa.ometa_api import OpenMetadata
from metadata.ingestion.api.models import Database, Table, Column, ColumnType, DatabaseSchema

# --------------------------
# 1. 配置OpenMetadata连接信息(替换成你自己的实际地址和账号)
# --------------------------
# OpenMetadata服务的访问地址,本地测试一般是http://localhost:8585
OPENMETADATA_HOST = "http://your-openmetadata-address:8585"
# OpenMetadata的管理员账号,建议使用专门的服务创建账号
OPENMETADATA_USER = "admin"
OPENMETADATA_PASSWORD = "your-admin-password"

# --------------------------
# 2. 模拟内部考勤系统的元数据(实际项目中这里替换为真实数据源的获取逻辑)
# --------------------------
internal_db_name = "internal_attendance_system"
# 3张核心表,每张表都加了业务相关的描述,方便后续使用时理解
sample_tables = [
    {
        "table_name": "employee",
        "table_description": "存储公司所有员工的基本身份信息,包含工号、姓名、所属部门等",
        "columns": [
            {"name": "emp_id", "type": ColumnType.STRING, "description": "全局唯一的员工工号"},
            {"name": "emp_name", "type": ColumnType.STRING, "description": "员工的姓名"},
            {"name": "dept", "type": ColumnType.STRING, "description": "员工所属的部门名称"}
        ]
    },
    {
        "table_name": "attendance_record",
        "table_description": "存储员工每日的考勤打卡数据,包含上下班时间、考勤状态等",
        "columns": [
            {"name": "attendance_id", "type": ColumnType.STRING, "description": "考勤记录的唯一ID"},
            {"name": "emp_id", "type": ColumnType.STRING, "description": "关联员工表的工号,外键字段"},
            {"name": "checkin_time", "type": ColumnType.DATETIME, "description": "上班打卡的时间"},
            {"name": "checkout_time", "type": ColumnType.DATETIME, "description": "下班打卡的时间"},
            {"name": "status", "type": ColumnType.STRING, "description": "考勤状态,取值为正常、迟到、早退、旷工"}
        ]
    },
    {
        "table_name": "overtime_apply",
        "table_description": "存储员工的加班申请记录,包含申请时长、审批状态等",
        "columns": [
            {"name": "apply_id", "type": ColumnType.STRING, "description": "加班申请的唯一ID"},
            {"name": "emp_id", "type": ColumnType.STRING, "description": "关联员工表的工号,外键字段"},
            {"name": "apply_hours", "type": ColumnType.FLOAT, "description": "申请的加班时长,单位为小时"},
            {"name": "approval_status", "type": ColumnType.STRING, "description": "审批状态,取值为待审批、已通过、已拒绝"}
        ]
    }
]

# --------------------------
# 3. 初始化OpenMetadata客户端,建立连接
# --------------------------
try:
    # 使用基础认证方式初始化客户端,负责后续的元数据提交
    ometa_client = OpenMetadata(
        host=OPENMETADATA_HOST,
        auth_provider="basic",
        username=OPENMETADATA_USER,
        password=OPENMETADATA_PASSWORD
    )
    print("成功连接到OpenMetadata服务")
except Exception as e:
    print(f"连接OpenMetadata失败,错误信息:{str(e)}")
    exit(1)

# --------------------------
# 4. 上传元数据到OpenMetadata
# --------------------------
try:
    # 创建数据源服务,作为内部考勤系统的容器,名称建议用业务相关的标识
    attendance_service = ometa_client.create_or_update_database_service(
        name="internal_attendance_system",
        description="公司内部考勤管理系统的数据源服务"
    )
    print(f"成功创建/更新数据源服务:{attendance_service.name}")

    # 在该服务下创建对应的数据库,存储考勤相关的所有表
    attendance_db = ometa_client.create_or_update_database(
        service=attendance_service.id,
        name=internal_db_name,
        description="存储考勤、加班相关的所有业务元数据"
    )
    print(f"成功创建/更新数据库:{attendance_db.name}")

    # 遍历模拟的表数据,逐个创建表和字段
    for table_info in sample_tables:
        # 构建表对象,关联到刚才创建的数据库
        table = Table(
            name=table_info["table_name"],
            description=table_info["table_description"],
            columns=[
                Column(
                    name=col["name"],
                    column_type=col["type"],
                    description=col["description"]
                ) for col in table_info["columns"]
            ],
            database_id=attendance_db.id
        )
        # 提交表对象到OpenMetadata
        created_table = ometa_client.create_or_update_table(table)
        print(f"成功创建表:{created_table.name},包含{len(table_info['columns'])}个业务字段")

except Exception as e:
    print(f"上传元数据失败,错误信息:{str(e)}")
    exit(1)

print("恭喜!自定义连接器的元数据采集已完成,你可以登录OpenMetadata平台查看刚才创建的考勤系统元数据")

Q: openmetadata 如何维护、采集 表级、列级元数据的血缘关系的?有哪几种方式?

血缘在 OpenMetadata 中如何存储(数据模型)

OpenMetadata 把血缘建模为一个有向图,节点是各类资产实体(表、管道、仪表盘、ML 模型等),边是血缘关系,基于 W3C PROV-O 标准:

  • 表级血缘:一条 Edge,fromEntity → toEntity(实体之间的上下游边)。
  • 列级血缘:挂在表级边上的 lineageDetails.columnsLineage,结构为 fromColumns[] → toColumn + function,即「一个或多个源列,经某个变换函数,生成目标列」;同时可附带该变换的 sqlQuery、所属 pipeline、description。
  • 来源可追溯:每条血缘边都带一个 source 标记(标识这条血缘是怎么来的,见下文 11 种枚举)以及 createdBy / createdAt / updatedAt。
  • 物理存储:落在元数据库(MySQL/PostgreSQL)的 entity_lineage 表中,通过 REST API GET/PUT /api/v1/lineage 读写。

有哪几种血缘采集方式?——官方定义了 11 种血缘来源(source 枚举)

# source 枚举 含义 典型产生途径
1 Manual 手动血缘 UI 无代码血缘编辑器 / 直接调 API 添加
2 ViewLineage 视图血缘 采集时解析数据库视图定义
3 QueryLineage SQL 查询血缘 解析查询日志/查询历史里的 SQL
4 PipelineLineage 管道血缘 Airflow / Dagster / Prefect 等任务输入输出
5 DashboardLineage 仪表盘血缘 Tableau / Looker / Metabase 等数据源关系
6 DbtLineage dbt 血缘 解析 dbt manifest.json 的模型依赖与列映射
7 SparkLineage Spark 血缘 分析 Spark 执行计划 / Databricks notebook lineage
8 OpenLineage OpenLineage 标准事件 接收符合 OpenLineage 规范的事件
9 ExternalTableLineage 外部表血缘 湖仓外部表(如 Unity Catalog、Iceberg、Glue)
10 CrossDatabaseLineage 跨库血缘 跨数据库/跨服务的数据流
11 ChildAssets 子资产聚合血缘 容器/父资产聚合其子资产(如存储桶内对象)的边

按采集通道看它们怎么工作

① SQL 解析与查询日志(QueryLineage / ViewLineage)—— 最主流的自动方式

  • 数据库连接器(Snowflake、BigQuery、Redshift、PostgreSQL 等)采集查询日志/查询历史(Snowflake query history、BigQuery audit logs、Redshift 系统表、PG pg_stat_statements)。
  • 内置 SQL Parser 解析 SELECT / INSERT / CREATE VIEW / MERGE 等语句,自动推导出表级边(读哪些表、写哪张表)和列级映射(SELECT 列表中的列表达式 → 目标列,含 JOIN key、聚合、表达式变换)。
  • 采集时可配置查询日志窗口时长、解析超时、结果行数上限、是否启用血缘解析等。

② 管道与调度(PipelineLineage / OpenLineage)

  • Airflow:启用官方 lineage backend(airflow_provider_openmetadata.lineage.backend.OpenMetadataLineageBackend),DAG 任务运行时自动上报「输入表 → 任务 → 输出表」及列级映射。
  • Dagster、Prefect、Fivetran、Airbyte 等管道连接器从任务元数据提取血缘。
  • 通用 OpenLineage 事件接入,Flink/Spark 等发射的事件可直接进入血缘图。

③ 转换工具(DbtLineage / SparkLineage)

  • dbt:读取 manifest.json,得到模型依赖(ref/source)与列级转换映射,这是列级血缘质量最高的来源之一。
  • Spark / Databricks:分析执行计划或 notebook lineage,产出任务级与列级血缘。

④ BI(DashboardLineage)

  • Tableau / Looker / Metabase / Power BI 等连接器提取「仪表盘 → 图表 → 底层表/字段」关系,打通「源表 → 管道 → 数仓 → 报表」的端到端血缘。

⑤ 手动维护(Manual)

  • UI 上「Lineage 标签页 → Edit」可视化拖拽连线(表级);列级可编辑列映射。
  • 或编程式 PUT /api/v1/lineage 直接写入边 + columnsLineage。
  • 适合补充自动解析不到的(存储过程、动态 SQL、遗留系统)。

⑥ 其他(ExternalTable / CrossDatabase / ChildAssets)

  • 湖仓外部表、跨库数据流、容器/子资产聚合场景下的自动血缘。

表级与列级血缘的维护机制

  • 自动采集是增量 upsert:每次采集管道运行,解析出的边与列映射会被写入/合并(表级边按实体 ID 去重,列级 columnsLineage 覆盖该边的最新映射),血缘图随调度周期持续更新。

  • 人工修正与治理:可删除错误边、补充缺失边、为边加描述;createdBy/updatedAt 保留审计痕迹。

  • 血缘深度控制:UI 可设置上游/下游查询深度(如 3 层),用于影响分析。

  • 最佳实践:

  • dbt + Airflow + 查询日志三路并用,配合手动补充;
  • 对 PII/核心指标列强制开启列级血缘;
  • 定期抽验血缘与实际数据流是否一致(自动解析在存储过程、复杂动态 SQL、临时表跳转等场景可能漏判,必要时可外接第三方解析器补全)。

下面这张图汇总了整个「采集 → 解析 → 存储 → 消费」链路与两种粒度:

image

  • 总结:自动采集靠「SQL 解析 + 查询日志 + 管道/转换工具集成」三路主力,人工用 UI/API 兜底;表级存边、列级存 columnsLineage 映射,每条边都带 source 标记以便追溯血缘来源。

Q: 如何通过请求API的方式手动配置血缘关系?(必读)

需求描述

  • 假设 postgresql 数仓中有这么一条 ETL SQL,如何通过请求API的方式手动注册血缘?
INSERT INTO dwd_elective (
  id, student_id, course_id, score, semester, create_time
  , student_no, student_name, student_major_no
  , course_no , course_name, course_credit
)
select 
  e.id , student_id , course_id, score, semester, e.create_time
  
  , student_no, stu.name as student_name, stu.major_no as student_major_no
  , course_no , course_name, cr.credit as course_credit
from ods_elective e
left join dim_student stu on e.student_id = stu.id 
left join dim_course cr on e.course_id = cr.id ;
  • 官方标准的精确接口格式:

手动注册血缘用 PUT /api/v1/lineage,请求体是 { "edge": { fromEntity, toEntity, description, lineageDetails: { source, sqlQuery, columnsLineage[] } } }
其中 fromEntity/toEntity 用实体 UUID,columnsLineage 的 fromColumns/toColumn 用列的 全限定名(FQN)。

下面针对上面的这条 ETL SQL 给出完整可执行方案:

这条 ETL 形成的血缘(表级 + 列级)

表级:3 条上游 → 1 条下游

ods_elective ─┐
dim_student  ─┼─► dwd_elective
dim_course   ─┘

列级映射(fromColumns[] → toColumn, function):

源列 目标列(dwd_elective) 变换
ods_elective.id id DIRECT
ods_elective.student_id student_id DIRECT
ods_elective.course_id course_id DIRECT
ods_elective.score score DIRECT
ods_elective.semester semester DIRECT
ods_elective.create_time create_time DIRECT
dim_student.student_no student_no JOIN
dim_course.course_no course_no JOIN
dim_course.course_name course_name JOIN
dim_student.name student_name JOIN
dim_student.major_no student_major_no JOIN
dim_course.credit course_credit JOIN

⚠️ 你的 SQL 里 student_no、course_no、course_name 是未限定前缀的列。若这些列在 dim_student/dim_course 中不存在,则按 SQL 语义归属 ods_elective(我按此处理);若实际上取自维表,请把对应 fromColumns 改成维表列。

API 请求体结构(PUT /api/v1/lineage)

  • 一条边一个请求(上游表 → dwd_elective 各一次),columnsLineage 只放该上游表真正贡献的列。
  • FQN : 形如 服务名.库名.schema名.表名.列名。

步骤与命令

1. 登录拿 Token

ENCODED_PASSWORD=$(echo -n 'admin' | base64)
  例如: YWRtaW4=

TOKEN=$(curl -X POST http://localhost:8585/api/v1/users/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@open-metadata.org","password":"YWRtaW4="}' \
   | sed -n 's/.*"accessToken":"\([^"]*\)".*/\1/p' )

注: curl 的样例输出:
{
  "accessToken":"eyJraWQiOiJHYjM4OWEtOWY3Ni1nZGpzLWE5MmotMDI0MmJrOTQzNTYiLCJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJvcGVuLW1ldGFkYXRhLm9yZyIsInN1YiI6ImFkbWluIiwicm9sZXMiOlsiQWRtaW4iXSwiZW1haWwiOiJhZG1pbkBvcGVuLW1ldGFkYXRhLm9yZyIsImlzQm90IjpmYWxzZSwidG9rZW5UeXBlIjoiT01fVVNFUiIsInVzZXJuYW1lIjoiYWRtaW4iLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc5MDE3NjQ0MCwiZXhwIjoxNzkwMTgwMDQwfQ.LUz8R8tZcjkNHAnKnkQY3O4t5MsBnJqBMsTaitILThYfnrsffHy_-51_MOgZzkq8vn8ixjuGgjb9swQnLGXTXN_JnlgPaE_jKy_VWNa1BdxjDDt8e76fqbj5rv3z_kGNgi4qAB4Y8dBfXa-98rgUcxysg5Ndq-261eU_gWovDxKNkMr9nKY9UlheWetHZQX8KJIR_bBbWxYiWsyVlZuWuvjbaTsJ339Jy-coqGo_4t2TCY3Vufu6_Kezx_9F-A-v4snZ7Q7Q4V4tepvg1dkLTPH5DSbjFcZGXMjWy4ta_rWG7Xt79eZuU0Ng4_siDChdBBuB_I75s_sTakaLGpeLQA"
  ,"refreshToken":"bff2e2e6-2ffc-4a97-b721-5f702908540d"
  ,"tokenType":"Bearer"
  ,"expiryDuration":1790180040093
}

echo $TOKEN

2. 查4张表的 UUID(API 要求 fromEntity/toEntity 用 UUID)

  • 获取表的UUID

即 curl api 响应中的 第1个"id"字段的值

for t in ods_elective dim_student dim_course dwd_elective; do
  curl -s "http://localhost:8585/api/v1/tables/name/%22postgresql.mydb%22.mydb.public.$t" \
    -H "Authorization: Bearer $TOKEN" | jq -r '"\(.fullyQualifiedName): \(.id)"'
done

或:
for t in ods_elective dim_student dim_course dwd_elective; do
  id=$(curl -s "http://localhost:8585/api/v1/tables/name/%22postgresql.mydb%22.mydb.public.$t" \
    -H "Authorization: Bearer $TOKEN" \
    | grep -oP '"id":\s*"\K[^"]+' | head -1)
  echo "$t : $id"
done
  • %22 即 " 英文双引号
  • 服务名 = postgresql.mydb
  • 库名 = mydb
  • schema名 = public

(把 pg.dw.public 换成你 OpenMetadata 里实际的 FQN,可从 WEB UI 面包屑可看到,如: http://localhost:8585/table/"postgresql.mydb".mydb.public.dim_course 。)

样例输出

curl的样例输出: ods_elective 表
{"id":"b172ad01-4e80-499d-8923-7dc1692448b5","name":"ods_elective","fullyQualifiedName":"\"postgresql.mydb\".mydb.public.ods_elective","description":"选修事实表","version":0.1,"updatedAt":1790172961000,"updatedBy":"ingestion-bot","href":
"http://localhost:8585/api/v1/tables/b172ad01-4e80-499d-8923-7dc1692448b5","tableType":"Regular","columns":[{"name":"id","dataType":"BIGINT","dataLength":1,"dataTypeDisplay":"bigint","description":"代理主键ID","fullyQualifiedName":"\"pos
tgresql.mydb\".mydb.public.ods_elective.id","tags":[],"constraint":"PRIMARY_KEY","children":[]},{"name":"student_id","dataType":"BIGINT","dataLength":1,"dataTypeDisplay":"bigint","description":"关联学生表的代理主键ID","fullyQualifiedName
":"\"postgresql.mydb\".mydb.public.ods_elective.student_id","tags":[],"constraint":"NOT_NULL","children":[]},{"name":"course_id","dataType":"BIGINT","dataLength":1,"dataTypeDisplay":"bigint","description":"关联课程表的代理主键ID","fullyQ
ualifiedName":"\"postgresql.mydb\".mydb.public.ods_elective.course_id","tags":[],"constraint":"NOT_NULL","children":[]},{"name":"score","dataType":"NUMERIC","dataLength":1,"precision":5,"scale":2,"dataTypeDisplay":"numeric(5,2)","descrip
tion":"成绩","fullyQualifiedName":"\"postgresql.mydb\".mydb.public.ods_elective.score","tags":[],"constraint":"NULL","children":[]},{"name":"semester","dataType":"VARCHAR","dataLength":20,"dataTypeDisplay":"character varying(20)","descri
ption":"学期 (示例维度扩展)","fullyQualifiedName":"\"postgresql.mydb\".mydb.public.ods_elective.semester","tags":[],"constraint":"NULL","children":[]},{"name":"create_time","dataType":"TIMESTAMP","dataLength":1,"dataTypeDisplay":"timesta
mp without time zone","description":"选课时间","fullyQualifiedName":"\"postgresql.mydb\".mydb.public.ods_elective.create_time","tags":[],"constraint":"NULL","children":[]}],"databaseSchema":{"id":"d515164e-93b5-46ed-a36b-3e6bf3515676","t
ype":"databaseSchema","name":"public","fullyQualifiedName":"\"postgresql.mydb\".mydb.public","description":"standard public schema","displayName":"public","deleted":false,"href":"http://localhost:8585/api/v1/databaseSchemas/d515164e-93b5
-46ed-a36b-3e6bf3515676"},"database":{"id":"c2a36e8a-2fa8-43c2-8c9a-e92f9c4736ba","type":"database","name":"mydb","fullyQualifiedName":"\"postgresql.mydb\".mydb","displayName":"mydb","deleted":false,"href":"http://localhost:8585/api/v1/d
atabases/c2a36e8a-2fa8-43c2-8c9a-e92f9c4736ba"},"service":{"id":"0cd13192-ff8c-4aed-b4c0-fa19384dc0fa","type":"databaseService","name":"postgresql.mydb","fullyQualifiedName":"\"postgresql.mydb\"","description":"<p>demo service</p>","disp
layName":"postgresql.mydb","deleted":false,"href":"http://localhost:8585/api/v1/services/databaseServices/0cd13192-ff8c-4aed-b4c0-fa19384dc0fa"},"serviceType":"Postgres","deleted":false,"sourceHash":"35ffccbe530592bc8c88c425d1a447c5","pr
ocessedLineage":false,"entityStatus":"Unprocessed"}

最终的样例输出:
ods_elective : b172ad01-4e80-499d-8923-7dc1692448b5
dim_student : d4812bea-5697-4fd6-9a20-1928b1f06e5f
dim_course : 9630d23b-6106-487f-acd2-ddba3c1f7bf2
dwd_elective : 8df87fa3-147c-40b9-abef-f9c939db5853

3. 注册血缘(表级血缘 or 列级血缘) —— 每个上游表一个 PUT

边①:ods_elective → dwd_elective
  • 创建表级血缘 (示例)
curl -v -X PUT http://localhost:8585/api/v1/lineage \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "edge": {
      "fromEntity": { "id": "<ods_elective uuid>", "type": "table" },
      "toEntity":   { "id": "<dwd_elective uuid>", "type": "table" }
    }
  }'

UI上查看血缘关系: http://localhost:8585/table/"postgresql.mydb".mydb.public.ods_elective/lineage
image

  • 创建列级血缘 (示例)
curl -v -X PUT http://localhost:8585/api/v1/lineage \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "edge": {
      "fromEntity": "<ods_elective uuid>",
      "toEntity":   "<dwd_elective uuid>",
      "description": "ETL: dwd_elective : based on ods_elective table generated(LEFT JOIN dim tables)",
      "lineageDetails": {
        "source": "Manual",
        "sqlQuery": "INSERT INTO dwd_elective (id, student_id, course_id, score, semester, create_time, student_no, student_name, student_major_no, course_no, course_name, course_credit) select e.id, student_id, course_id, score, semester, e.create_time, student_no, stu.name as student_name, stu.major_no as student_major_no, course_no, course_name, cr.credit as course_credit from ods_elective e left join dim_student stu on e.student_id = stu.id left join dim_course cr on e.course_id = cr.id",
        "columnsLineage": [
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.id"],          "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.id"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.student_id"],  "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_id"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.course_id"],   "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_id"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.score"],       "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.score"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.semester"],    "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.semester"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.create_time"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.create_time"}
        ]
      }
    }
  }'


形如:
curl -v -X PUT http://localhost:8585/api/v1/lineage \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "edge": {
      "fromEntity": { "id": "b172ad01-4e80-499d-8923-7dc1692448b5", "type": "table" },
      "toEntity":   { "id": "8df87fa3-147c-40b9-abef-f9c939db5853", "type": "table" },
      "description": "ETL: dwd_elective : based on ods_elective table generated(LEFT JOIN dim tables)",
      "lineageDetails": {
        "source": "Manual",
        "sqlQuery": "INSERT INTO dwd_elective (id, student_id, course_id, score, semester, create_time, student_no, student_name, student_major_no, course_no, course_name, course_credit) select e.id, student_id, course_id, score, semester, e.create_time, student_no, stu.name as student_name, stu.major_no as student_major_no, course_no, course_name, cr.credit as course_credit from ods_elective e left join dim_student stu on e.student_id = stu.id left join dim_course cr on e.course_id = cr.id",
        "columnsLineage": [
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.id"],          "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.id"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.student_id"],  "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_id"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.course_id"],   "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_id"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.score"],       "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.score"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.semester"],    "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.semester"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.create_time"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.create_time"}
        ]
      }
    }
  }'
  • 特别注意: description 等字段,尽量别用中文,目前 openmetadata v2.0.0 此 api 有字符集兼容性方面的 bug,会导致报 400 错误: {"code":400,"message":"Invalid request format"}

UI查看列级血缘:

http://localhost:8585/table/"postgresql.mydb".mydb.public.dwd_elective/lineage
image

边②:dim_student → dwd_elective

列级血缘

curl -v -X PUT http://localhost:8585/api/v1/lineage \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "edge": {
      "fromEntity": { "id": "<dim_student uuid>", "type": "table" },
      "toEntity":   { "id": "<dwd_elective uuid>", "type": "table" },
      "description": "ETL: associate dim_student of name , major",
      "lineageDetails": {
        "source": "Manual",
        "sqlQuery": "INSERT INTO dwd_elective (...) select ... stu.name as student_name, stu.major_no as student_major_no ... from ods_elective e left join dim_student stu on e.student_id = stu.id ...",
        "columnsLineage": [
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_student.student_no"],    "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_no",    "function": "JOIN"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_student.name"],    "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_name",    "function": "JOIN"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_student.major_no"],"toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_major_no","function": "JOIN"}
        ]
      }
    }
  }'


形如:
curl -v -X PUT http://localhost:8585/api/v1/lineage \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "edge": {
      "fromEntity": { "id": "d4812bea-5697-4fd6-9a20-1928b1f06e5f", "type": "table" },
      "toEntity":   { "id": "8df87fa3-147c-40b9-abef-f9c939db5853", "type": "table" },
      "description": "ETL: associate dim_student of name , major",
      "lineageDetails": {
        "source": "Manual",
        "sqlQuery": "INSERT INTO dwd_elective (...) select ... stu.name as student_name, stu.major_no as student_major_no ... from ods_elective e left join dim_student stu on e.student_id = stu.id ...",
        "columnsLineage": [
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_student.student_no"],    "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_no",    "function": "JOIN"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_student.name"],    "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_name",    "function": "JOIN"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_student.major_no"],"toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_major_no","function": "JOIN"}
        ]
      }
    }
  }'

UI上查看血缘: http://localhost:8585/table/"postgresql.mydb".mydb.public.dwd_elective/lineage
image

边③:dim_course → dwd_elective
  • 添加列级血缘
curl -X PUT http://localhost:8585/api/v1/lineage \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "edge": {
      "fromEntity": { "id": "<dim_course uuid>", "type": "table" },
      "toEntity":   { "id": "<dwd_elective uuid>", "type": "table" },
      "description": "ETL: associate dim_course of credit field",
      "lineageDetails": {
        "source": "Manual",
        "sqlQuery": "INSERT INTO dwd_elective (...) select ... cr.credit as course_credit ... from ods_elective e left join dim_course cr on e.course_id = cr.id",
        "columnsLineage": [
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_course.course_no"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_no", "function": "JOIN"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_course.course_name"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_name", "function": "JOIN"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_course.credit"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_credit", "function": "JOIN"}
        ]
      }
    }
  }'

形如:
curl -v -X PUT http://localhost:8585/api/v1/lineage \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "edge": {
      "fromEntity": { "id": "9630d23b-6106-487f-acd2-ddba3c1f7bf2", "type": "table" },
      "toEntity":   { "id": "8df87fa3-147c-40b9-abef-f9c939db5853", "type": "table" },
      "description": "ETL: associate dim_course of credit field",
      "lineageDetails": {
        "source": "Manual",
        "sqlQuery": "INSERT INTO dwd_elective (...) select ... cr.credit as course_credit ... from ods_elective e left join dim_course cr on e.course_id = cr.id",
        "columnsLineage": [
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_course.course_no"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_no", "function": "JOIN"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_course.course_name"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_name", "function": "JOIN"},
          {"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_course.credit"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_credit", "function": "JOIN"}
        ]
      }
    }
  }'

UI上查看血缘: http://localhost:8585/table/"postgresql.mydb".mydb.public.dwd_elective/lineage

image

4.(可选/未亲测)用 Python requests 一次跑三条

import requests, json

BASE, TOKEN = "http://localhost:8585/api/v1", "<你的accessToken>"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
P = "pg.dw.public"                      # 你的 FQN 前缀

def tid(t):
    r = requests.get(f"{BASE}/tables/name/{P}.{t}", headers=H); return r.json()["id"]

ups = {"ods_elective": tid("ods_elective"),
       "dim_student":  tid("dim_student"),
       "dim_course":   tid("dim_course")}
dwd = tid("dwd_elective")

edges = {
 "ods_elective": [("id","id"),("student_id","student_id"),("course_id","course_id"),
                  ("score","score"),("semester","semester"),("create_time","create_time"),
                  ("student_no","student_no"),("course_no","course_no"),("course_name","course_name")],
 "dim_student":  [("name","student_name"),("major_no","student_major_no")],
 "dim_course":   [("credit","course_credit")],
}
SQL = "INSERT INTO dwd_elective (...) select e.id, ..., cr.credit as course_credit from ods_elective e left join dim_student stu on e.student_id = stu.id left join dim_course cr on e.course_id = cr.id"

for src, cols in edges.items():
    body = {"edge": {
        "fromEntity": ups[src], "toEntity": dwd,
        "description": f"ETL: dwd_elective from {src}",
        "lineageDetails": {"source": "Manual", "sqlQuery": SQL,
            "columnsLineage": [
                {"fromColumns": [f"{P}.{src}.{c}"], "toColumn": f"{P}.dwd_elective.{t}", "function": "DIRECT" if src=="ods_elective" else "JOIN"}
                for c, t in cols]}}}
    r = requests.put(f"{BASE}/lineage", headers=H, data=json.dumps(body))
    print(src, "->", r.status_code)

5. 验证与删除

# 查 dwd_elective 的血缘图
curl -s "http://localhost:8585/api/v1/lineage/table/<表的UUID>?upstreamDepth=3" -H "Authorization: Bearer $TOKEN"
形如: curl -s "http://localhost:8585/api/v1/lineage/table/8df87fa3-147c-40b9-abef-f9c939db5853?upstreamDepth=3" -H "Authorization: Bearer $TOKEN"

# 删除某条边(可选)
curl -X DELETE "http://localhost:8585/api/v1/lineage/table/name/<上游FQN>/table/name/<下游FQN>"  -H "Authorization: Bearer $TOKEN"
形如:curl -X DELETE "http://localhost:8585/api/v1/lineage/table/name/%22postgresql.mydb%22.mydb.public.ods_elective/table/name/%22postgresql.mydb%22.mydb.public.dwd_elective"  -H "Authorization: Bearer $TOKEN"

关键注意点

  1. FQN 必须与库内完全一致:服务名.库名.schema.表名.列名,一个字符都不能差,否则列级血缘对不上。建议先 GET /api/v1/tables/name/... 确认各表及列的确切 FQN。
  2. fromEntity/toEntity 用 UUID:不能直接用 FQN,先查 id(第二步)。
  3. 列必须已存在:列级血缘要求目标列(及源列)在 OpenMetadata 中已采集到;若 dwd_elective 尚未采集,先跑一次它的元数据采集。
  4. 一条边一个请求:多上游时每个上游→目标各 PUT 一次,columnsLineage 只放该上游贡献的列。
  5. function 建议值:直传用 DIRECT,JOIN 取字段用 JOIN,有表达式变换用真实函数名(如 CONCAT、LOWER),便于影响分析时看到变换类型。
  6. 身份认证:默认 basic 认证用登录返回的 accessToken;若开了 OIDC/LDAP 或使用其他 authorizer,按你的配置获取 token。
  7. source 字段:手动注册一律填 Manual;若这条 SQL 实际挂在已登记的 Airflow 管道上,可补充 pipeline 引用并改用 PipelineLineage。

Y 推荐文献


X 参考文献

posted @ 2026-09-23 20:42  千千寰宇  阅读(13)  评论(0)    收藏  举报