1. 我原来以为,语义层就是一份 YAML
接触问数系统时,我对语义层的理解很简单:把指标、维度、口径写进一份模型文件,查询时拿出来拼 SQL。
Metric YAML → 拼 SQL → 数据库执行后来读 DOSI 的文档,这个理解被纠正了。YAML 只是语义资产的载体,真正决定结果对不对的,是 YAML 背后的执行器:校验、Join 规划、粒度检查、Fanout 保护、方言 SQL 生成。DOSI 给这类东西起了一个准确的名字:
Semantic Compiler / Runtime。
2. DOSI 一句话是什么
DOSI 是以 Apache Ossie 为输入契约的 Headless 语义编译器与运行时。它不做 BI 展示,也不做指标目录,而是把标准语义模型编译成各家数仓的原生 SQL。
Apache Ossie
Dataset / Field / Relation / Metric
↓
DOSI
校验 → 规划 Join → 检查粒度与 Fanout → 生成逻辑计划 → 渲染方言 SQL
↓
Snowflake / StarRocks / ClickHouse / PG / Databricks ...
↓
REST / Arrow / Python / CLI / MCP
↓
BI / App / Agent三者的分工拆得很清楚:
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,错误的数字
假设:
Customer
│ 1:N
▼
Order
│ 1:N
▼
OrderItem指标 Revenue = SUM(Order.amount),查询是 Revenue BY Product。裸 SQL 很容易写成三表 Join 后直接求和,订单金额会被每个 OrderItem 重复计算。SQL 完全合法,结果完全错误。
DOSI 的处理链是:
语义请求 → 指标归属 → 数据集粒度 → 关系图 → Fanout 分析
→ 安全 Join / 预聚合 / 直接拒绝如果查询需要从 customers 反向走到 one-to-many 的 orders 明细,它会直接拒绝并返回 detail_fanout,而不是悄悄复制行再返回一个看起来合理的数字。
宁可显式失败,也不要静默返回错误数字。
这是 Data Agent 最需要的行为,也是我认为这篇文章最值得带走的一句话。
5. 它已经长得像数据库编译器了
DOSI 有一个 explain 命令,不直接吐 SQL,而是先停在逻辑计划:根数据集、Join 路径、Join 类型、Fanout 分支、粒度处理,然后才渲染成各家方言。
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 的权限被收紧了:
User → LLM 理解意图 → 提交指标、维度、过滤、时间范围
→ DOSI 校验、规划 Join、检查 Fanout、展开指标、编译 SQL
→ 数仓执行Agent 拿不到自由 SQL 权,结构化错误还会告诉它候选名和重试建议,让它修正语义请求,而不是反复 hallucinate SQL。这和我项目里的分工一致:Java 校验权限、状态与版本,执行器只干活。
语义模型只是源码,编译器才是质量。这句话 DOSI 算是实例化了。
相关阅读
- SQL 能运行,为什么问数结果还是错的?
- 仰望星空:从 Databricks、Snowflake 与 Palantir 看数据平台走向 Agent
- Agent 平台为什么要保存 Trace 和 Evidence:从一次检索到受控 Action