Snowflake App Runtime 数据血缘集成:OpenLineage 实战

用正则加 sqlglot 加 OpenLineage 三步管道,把 Snowflake App Runtime 应用补进血缘图。
Snowflake App Runtime 让开发者能直接在 Snowflake 上部署 Next.js 应用,但这些应用默认不会出现在 Snowsight 血缘图中,导致无法回答"改某一列会破坏哪个应用或 API"这类问题。文章给出一套三步管道:先用正则从 TypeScript API 路由中提取 SQL 模板字面量,再用 sqlglot 解析 SQL 并借助 `lineage()` API 追踪列级血缘,最后把结果构造成 OpenLineage COMPLETE 事件推送到 Snowflake 的 External Lineage 端点。集成后,读取型 API 出现在表的下游,写入型 API 出现在上游,并标记为 External。最实用的收益是变更影响分析——一条 `GET_LINEAGE` 查询即可查出修改 `CUSTOMERS.CUSTOMER_NAME` 会影响哪些端点。作者同时提醒,Snowflake 目前只识别 datasetType 和 columnLineage 两种 facet。
Snowflake App Runtime 数据血缘集成:OpenLineage 实战
为什么 App Runtime 应用会从元数据中消失
Snowflake App Runtime 允许开发者直接在 Snowflake 上构建和部署 Next.js 应用,这为快速交付内部工具提供了极大便利。但应用一旦上线,它就从元数据中消失了——Snowsight 的血缘图只追踪到表和视图级别,无法回答"哪个应用或 API 依赖某个特定列"这类问题。
据 B站UP主和 YouTube 技术频道的分析,Snowflake 的 External Lineage API 已经正式可用一段时间了,它接受 OpenLineage 格式的血缘事件并直接合并到血缘图中。而 Horizon Context 作为 Snowflake 近期推出的预览功能,将 Horizon Catalog 扩展为更广泛、可插拔的元数据层。如果 App Runtime 的 API 和页面能作为常规节点出现在血缘图中,位于它们所读取表的下游,那么"改这个列会破坏什么"的猜测工作将大幅减少。
注意:Horizon Context 本身在 2026 年 10 月仍处于预览阶段,相关框架可能变化。但本文依赖的 External Lineage 机制已经 GA。
OpenLineage 是 LF AI & Data 基金会下的开源数据血缘标准,最初由 Datakin(现 Astronomer)发起,目标是提供与调度器、计算引擎无关的统一血缘事件格式。它的核心模型由三类对象组成:Job(产生血缘的工作单元,如一个 Airflow DAG、一次 Spark 作业或一个 API 端点)、Run(Job 的一次具体执行实例,带唯一 runId)和 Dataset(被读写的数据集,可带 namespace)。三者之间通过 inputs / outputs 边连接。事件本身以 JSON 载荷发送,通常包含 eventType(START、RUNNING、COMPLETE、ABORT、FAIL)、eventTime、producer 等字段,以及可扩展的 facets——这是 OpenLineage 最灵活的设计:所有非核心信息(schema、列级血缘、数据质量、所有权)都通过具名 facet 挂载,接收方可以按需解析、忽略未知 facet。正因为这种"核心稳定、扩展自由"的结构,Snowflake 才能只挑选 datasetType 和 columnLineage 两个 facet 落地,而对 documentation、ownership 静默丢弃而不破坏整体协议兼容性。
示例应用的结构
作者使用 dbt-labs 的 jaffle_shop 示例数据构建了一个小型 Next.js 应用,包含分析仪表盘和主数据编辑两个页面。应用遵循标准 Next.js App Router 约定:API 路由位于 app/api/ 下,页面位于 app/ 和 components/ 下。
需要从目录结构中自动提取五类信息:API 端点名称、HTTP 方法、引用的表、页面节点名称、以及页面到 API 的映射。例如 app/api/customers/route.ts 映射为 /app/api/customers,customers-view.tsx 中的 fetch("/api/customers") 调用建立页面与 API 的关联。
整个管道分为三步:提取——用正则从 TypeScript API 路由文件中提取 SQL 模板字面量;分析——用 sqlglot 解析 SQL,解析表全限定名并追踪列级血缘;发送——构建 OpenLineage COMPLETE 事件推送到 Snowflake REST API。

三步实现血缘集成
第一步:从 TypeScript 中提取 SQL
每个 App Runtime API 路由都位于 app/api/<name>/route.ts,遵循 Next.js App Router 约定,其中的 SQL 以模板字面量形式书写。作者使用一个正则表达式从 TypeScript 代码中提取这些 SQL 模板,支持两种形式:直接的 const NAME = \...`和箭头函数形式const NAME = (db, schema) => `...``。HTTP 方法则从导出的函数名和变量名推断。
第二步:用 sqlglot 解析 SQL 并追踪列级血缘
提取的查询需要预处理:将 ${db} 模板占位符替换为实际值,将 Snowflake 绑定变量(:name、?)转换为占位符。sqlglot 将 SQL 解析为抽象语法树,从中提取所有表引用。
其 lineage() API 更进一步,为 SELECT 的每个输出列追踪确切的来源表和列。传入 dialect="snowflake" 即可正确处理 JOIN、CASE 表达式和聚合函数的列级归属。
一个关键前提:lineage() 需要 schema 信息才能工作,作者通过连接 Snowflake 并执行 DESCRIBE TABLE 提前获取了表结构。
列级血缘(column-level lineage) 与表级血缘的区别在于追踪粒度:表级只回答"这个查询读了哪几张表",列级则要回答"输出结果中的每一列,分别来自输入表的哪一列、经过了什么变换"。这在实践中意义重大——当你准备重命名或修改 CUSTOMERS.CUSTOMER_NAME 时,表级血缘只能告诉你有三个下游任务依赖 CUSTOMERS 表,而列级血缘能精确指出哪些任务的哪些字段真正用到了这一列,从而避免误伤。sqlglot 的 lineage() 实现方式是把 SQL 解析为 AST,然后自底向上对每个 SELECT 输出表达式做来源归因:直接列引用归到源表,CASE 表达式会拆分至各分支,聚合函数归到其参数列。它对 JOIN、CTE、子查询和 UNION 都有较完整的处理。局限在于它做的是语法层面的静态推断,遇到 SELECT *、动态 SQL 拼接或 UDF 内部逻辑时无法完全还原真实依赖,因此 schema 信息(列有哪些、类型如何)是它展开 *、校验列归属的必要输入。
第三步:构建并发送 OpenLineage 事件
两类节点被注册为血缘:Layer 1 建立表与 API 端点的关系,包含列级血缘;Layer 2 建立 API 端点与页面节点的关系。页面节点命名包含应用名,如 jaffle-app/dashboard。
OpenLineage 是一种开放的数据血缘格式,Snowflake 在 /api/v2/lineage/external-lineage 端点接受该格式并合并到血缘图中。Layer 1 的读取事件携带 columnLineage facet,但 Snowflake 目前只识别两种 facet:datasetType 和 columnLineage,其他 facet 如 documentation 和 ownership 会被静默忽略。
Horizon Catalog 是 Snowflake 在 2024 年前后正式确立的元数据品牌,把原先分散的权限、对象、血缘、标签等治理能力统一到一个目录层之下。它既是 Snowsight 里浏览表、列、标签的入口,也是 GET_LINEAGE、策略标签、对象标签等能力的底层支撑。Horizon Context 则是在此之上的一次扩展尝试:把 Catalog 从"只管理 Snowflake 自身对象"变成"可插拔的通用元数据层",通过连接器接入外部系统(如 dbt、Looker、Airflow)的元数据,使血缘图能横跨多个技术栈。它之所以被本文特别提及,是因为如果 App Runtime 的应用与 API 未来能作为一等公民注册进 Horizon Context,就无需再依赖手动推送 OpenLineage 事件。但截至文中所注时间点,它仍处于预览阶段,接口与模型可能变动,因此作者选择了已经 GA 的 External Lineage 作为稳定实现路径。
实际效果与查询
集成之前,Snowflake 原生查询血缘能追踪 CUSTOMERS 表回溯到暂存和原始表,但到此为止——没有应用、没有 API、下游一片空白。事件注册后,应用的 API 出现在其触碰的表和视图的血缘中。通过 OpenLineage 注册的节点标记为 External。
写入型 API(如 PUT 风格的主数据编辑器)显示为表的上游,因为它们是写入而非读取。
最实用的场景是变更影响分析。假设要修改 CUSTOMERS.CUSTOMER_NAME 的定义,Snowflake 内置的 GET_LINEAGE 函数可以精确告出受影响的对象。一条查询就能看出,修改 CUSTOMER_NAME 会影响 /api/customers 和 /api/orders 两个端点。从 Snowflake 的 CoCo 界面也能走通同样的路径。
GET_LINEAGE 是 Snowflake 提供的表函数(table function),用于以程序化方式查询血缘图,内部走的就是 Snowsight 血缘图所用的同一套 Horizon 元数据。它的典型调用形式是 SELECT * FROM TABLE(SNOWFLAKE.CORE.GET_LINEAGE('<object_fqn>', '<direction>', '<object_type>')),其中方向可取 DOWNSTREAM 或 UPSTREAM。与图形界面相比,它的价值在于可组合——返回结果可以继续 JOIN、过滤、聚合,也能嵌入脚本或 CI 检查。例如在列级场景下,把目标列名作为过滤条件传入,就能把"改这一列会破坏哪些下游对象"从人工排查变成一条可复用的 SQL,甚至做成 schema 变更前的自动阻断门禁。需要注意该函数返回的是对象级依赖,要下钻到具体列仍需结合列级血缘信息。
思考与展望
Horizon Context 的元数据连接器处理简单场景——接入已有连接器的系统。OpenLineage 则适用于没有现成连接器的情况:它让一个原本对 Snowflake 血缘图不可见的应用逐 API、逐页面地注册进来,且带完整的列级细节。
虽然目前 Snowflake 对 OpenLineage facet 的支持还比较有限,但 columnLineage 已经能覆盖核心的列级追踪需求。如果曾为弄清一次 schema 变更的影响而不得不翻阅源码,这套方法提供了一个务实的起点。
对于在 Snowflake 上构建内部工具且关注数据治理的团队,将应用层纳入血缘图是一个值得投入的方向。
相关推荐

AI数字分身实战:用DOT自动追踪Bug与用户反馈
DOTS 团队工程师演示如何用 AI 数字分身 DOT 跨平台收集用户反馈、自动归类问题主题、开线程调查 Bug 根因并直接提交 PR,展示 AI 代理参与缺陷分诊的完整工作流与可审计设计。

晶体管为何不再扁平?从平面到环绕栅极的演进
晶体管从平面到FinFET再到环绕栅极的演进:为何栅极需要从三面包裹到四面环绕,以抑制短沟道效应和泄漏电流,延续摩尔定律。

蜂群思维=单个RL智能体?多臂老虎机等价性证明
一篇论文证明蜂群通过局部模仿进行的集体决策,在数学上等价于单智能体强化学习。N个盲目模仿者相当于N个并行环境采样,有效学习率为1/N。本文解读Maynard-Cross Learning算法的推导与影响。