最近在看一个 分布式查询引擎(Distributed Query Engine) 的 Rust 项目需求,目标其实很直接:
- 多节点执行
- 低延迟 / 高吞吐
- 数据 Shuffle / Partition
- 可长期维护
在真正开始堆功能之前,我先做了一件工程上很“慢”、但很值的事:
先写一个最小可运行的查询执行 POC,验证执行模型本身是不是对的。
一、为什么这个阶段我直接选 Rust
在分布式查询引擎这种系统里,语言不是偏好问题,而是工程约束的一部分。
我选择 Rust,主要是基于三个现实考虑:
1. 并发安全直接决定系统下限
执行层是天然的并发系统,多 worker 同时跑算子,如果内存和并发模型不清晰,后期问题一定会被放大。
Rust 至少把一部分风险前移到了编译期。
2. async 非常贴合执行层模型
查询执行本质是 IO、计算和中间结果流转的组合,async 能让这些行为在代码结构上更清晰,而不是靠约定。
3. 这是一个长期演进系统
查询引擎不是一次性交付,而是会不断加算子、加规则、加优化策略,维护成本必须可控。
二、POC 阶段,我刻意没做“看起来很重要”的东西
这个 POC 有意不实现很多常见模块:
- SQL Parser
- 成本优化器(CBO)
- 网络通信层
- 完整的容错 / 恢复机制
原因很简单:
在执行模型没跑通之前,这些模块只会放大复杂度。
在这个阶段,我只关心三件事:
- 查询是否被明确建模成 Execution Plan
- 执行是否能自然拆分到多个 worker
- 中间数据是否必须经过 Shuffle 才能继续算
三、最小查询执行模型
在 POC 里,查询不是 SQL 字符串,而是直接建模成执行节点:
enum Query {
Scan { source: String },
Filter { predicate: String },
Aggregate { key: String, agg: String },
}
这样做的目的,是把注意力集中在 执行语义 上,而不是语法本身。
执行流程被明确限制为:
Scan -> Filter -> Shuffle -> Aggregate
四、最小“分布式”执行方式
为了避免一开始就引入网络复杂度,这个 POC 采用了非常直接的模型:
- 一个 Coordinator
- 多个 Worker(async task)
- 使用 Channel 传递中间结果
结构大致是这样:
Coordinator
|
|-- Worker 1: Scan + Filter
|-- Worker 2: Scan + Filter
|
+--> Aggregate
每个 Worker 独立执行算子,中间结果必须返回并参与后续阶段。
五、为什么 Shuffle 在 POC 阶段就必须出现
即便是最小 POC,Shuffle 也不能省。
实现方式非常简单:
hash(key) % worker_count
但这一行代码已经意味着:
- 数据需要重新分布
- 执行流程出现同步点
- 性能与资源问题开始显性化
从这里开始,查询引擎的复杂度就不再是“写不写得出来”的问题了。
六、这个 POC 想验证的工程判断
这个 POC 并不是为了证明“我能写查询引擎”,而是验证几个基础判断:
- 查询引擎的复杂度主要集中在 执行层(Execution Layer)
- Shuffle 是系统级问题,而不是后期优化细节
- Rust 在这种系统里是工程选择,而不是噱头
七、POC 阶段刻意不解决的问题
为了避免误解,这个 POC 明确不解决:
- 高可用
- 完整容错
- 复杂优化策略
- 云原生部署
这些问题都应该在执行模型稳定之后再引入。
八、为什么我会先做这个最小 POC
在分布式查询引擎这类系统里,一个很现实的经验是:
如果执行模型一开始就不清晰,
后续功能基本都会变成补救。
最小 POC 的价值,只是尽早把这些问题暴露出来。
如果你也在做或评估类似系统,
建议先把 Execution 跑通,再谈功能完整性。