数据与 BI

语义层的壁垒不在 YAML,而在编译器:从 DOSI 看起

从 DOSI 出发,理解语义层为什么正在变成一个受约束的查询编译器:校验、Join 规划、Fanout 保护与多方言 SQL 生成。

1. 我原来以为,语义层就是一份 YAML

接触问数系统时,我对语义层的理解很简单:把指标、维度、口径写进一份模型文件,查询时拿出来拼 SQL。

text1 行
Metric YAML → 拼 SQL → 数据库执行

后来读 DOSI 的文档,这个理解被纠正了。YAML 只是语义资产的载体,真正决定结果对不对的,是 YAML 背后的执行器:校验、Join 规划、粒度检查、Fanout 保护、方言 SQL 生成。DOSI 给这类东西起了一个准确的名字:

Semantic Compiler / Runtime。

2. DOSI 一句话是什么

DOSI 是以 Apache Ossie 为输入契约的 Headless 语义编译器与运行时。它不做 BI 展示,也不做指标目录,而是把标准语义模型编译成各家数仓的原生 SQL。

text11 行
Apache Ossie
Dataset / Field / Relation / Metric
        ↓
DOSI
校验 → 规划 Join → 检查粒度与 Fanout → 生成逻辑计划 → 渲染方言 SQL
        ↓
Snowflake / StarRocks / ClickHouse / PG / Databricks ...
        ↓
REST / Arrow / Python / CLI / MCP
        ↓
BI / App / Agent

三者的分工拆得很清楚:

text3 行
Apache Ossie = 语义交换契约
DOSI         = 语义编译与执行
Datus Agent  = 从 Schema 与 SQL 历史生成模型,再调用 DOSI 执行

官方称支持 16 种数仓方言。Agent 侧暴露的是查指标、查维度、解释计划这类能力,而不是一个裸的 execute_sql。

3. 值得关注的不是 Rust,而是执行语义被定义了出来

DOSI 是 Rust 写的,官方基准也很快。但只记住“Python 变 Rust,所以快”,就把最重要的东西丢了。

Apache Ossie 当前的规范并没有完整规定:粒度怎么传播、多指标怎么组合、Fanout 怎么处理、Join 到底选哪条、派生指标怎么展开。这些还在标准工作组的路线图里。

DOSI 做的是把“Revenue 这个指标是什么”往下推一步,回答“Revenue 应当如何被严格地执行”。它维护了一份带编号的 Semantics Contract,比如关系默认只允许按 many→one 方向遍历;找不到路径报 no_join_path,有多条路径报 ambiguous_join_path,而不是随便挑一条。

这正是我之前那篇问数文章里想表达的:模型说清楚想查哪个业务指标,后端决定怎么算。DOSI 把这句话做成了产品。

4. 一个例子:合法的 SQL,错误的数字

假设:

text7 行
Customer
   │ 1:N
   ▼
Order
   │ 1:N
   ▼
OrderItem

指标 Revenue = SUM(Order.amount),查询是 Revenue BY Product。裸 SQL 很容易写成三表 Join 后直接求和,订单金额会被每个 OrderItem 重复计算。SQL 完全合法,结果完全错误。

DOSI 的处理链是:

text2 行
语义请求 → 指标归属 → 数据集粒度 → 关系图 → Fanout 分析
→ 安全 Join / 预聚合 / 直接拒绝

如果查询需要从 customers 反向走到 one-to-many 的 orders 明细,它会直接拒绝并返回 detail_fanout,而不是悄悄复制行再返回一个看起来合理的数字。

宁可显式失败,也不要静默返回错误数字。

这是 Data Agent 最需要的行为,也是我认为这篇文章最值得带走的一句话。

5. 它已经长得像数据库编译器了

DOSI 有一个 explain 命令,不直接吐 SQL,而是先停在逻辑计划:根数据集、Join 路径、Join 类型、Fanout 分支、粒度处理,然后才渲染成各家方言。

text1 行
Apache Ossie → 语义 IR → 逻辑计划 → 方言 SQL

所以我不再写“语义层把指标转换成 SQL”,而是写:

现代语义层正在变成一个受约束的领域查询编译器:语义模型是 Schema,语义请求是 Query,逻辑计划是 Logical Plan,数仓 SQL 是 Physical Plan。

6. 关于“快 10–100×”,必须拆开说

严谨的表述只有一种,分段说:

  • SQL 编译热态:约快 10–22×。这是 DOSI 完整进程约 10ms,对 MetricFlow 热态约 100–220ms。
  • 冷启动编译:约快 220×。MetricFlow 光 Python import、模型解析和客户端初始化就要约 1.9 秒,DOSI 还是约 10ms。
  • 端到端(含 DuckDB 执行)冷启动对冷启动:约快 66–73×;但 DOSI 冷启动对 MetricFlow 热态,只剩约 3–7×。一旦进入数仓执行,编译器的几百毫秒差距会被查询执行时间稀释。

这些数字来自厂商在约 25 个 dataset/metric 的小模型、单机环境上的自测。看相对趋势可以,不要直接外推成生产性能。

另外,目前没有看到可信的 DOSI vs Cube 同口径基准,官方也把两者定位为互补:Cube 偏 Serving、缓存、预聚合与嵌入式分析,DOSI 偏标准原生编译与轻量 Agent 运行时。拿它俩比“谁快几十倍”,口径就是错的。

7. 它也暴露了开放标准没成熟的问题

DOSI 一边强调原生支持 Apache Ossie,一边自己加了走到 1.8 版本的 DATUS custom extensions:窗口函数、派生指标、参数化指标、基数声明等。因为 Ossie Core 还是 draft,成熟 Runtime 需要的执行语义,标准暂时还表达不出来。

这里有个值得跟踪的问题:如果每个 Runtime 最后都靠自家扩展才能真正跑起来,互操作会不会退化成“最低公分母互操作”?未来语义层的竞争,可能不只是“谁支持 Ossie”,而是“标准没覆盖的执行语义由谁定义,以及这些扩展能不能回到标准里”。

8. 落回我的后端视角

对我做 Java 后端最直接的启发,是 Agent 的权限被收紧了:

text3 行
User → LLM 理解意图 → 提交指标、维度、过滤、时间范围
→ DOSI 校验、规划 Join、检查 Fanout、展开指标、编译 SQL
→ 数仓执行

Agent 拿不到自由 SQL 权,结构化错误还会告诉它候选名和重试建议,让它修正语义请求,而不是反复 hallucinate SQL。这和我项目里的分工一致:Java 校验权限、状态与版本,执行器只干活。

语义模型只是源码,编译器才是质量。这句话 DOSI 算是实例化了。

相关阅读

参考资料

← 返回文章