最近在评估一个 分布式查询引擎(Distributed Query Engine) 的 Rust 项目需求,核心目标很明确:
-
多节点执行
-
低延迟、高吞吐
-
数据 Shuffle / Partition
-
长期可维护
在真正动手写系统之前,我没有急着堆功能,而是先做了一件很基础、但很关键的事:
写一个最小可运行的查询执行 POC,验证执行模型本身是否合理。
一、为什么选择 Rust 做这个 POC
在分布式查询引擎这种系统里,语言选择并不是偏好问题,而是工程约束。
选择 Rust,主要基于三个现实考虑:
1. 并发安全是硬需求
查询执行层天然是并发系统,多 worker 同时运行算子,如果内存模型不清晰,稳定性会长期被消耗。
Rust 把很多问题提前到了编译期。
2. async 对执行层非常重要
查询执行本质上是 IO + 计算 + 中间结果流转的组合,async 让这些行为在代码结构上是显式的。
3. 维护成本可控
查询引擎不是一次性代码,而是会不断增加算子、优化规则的长期系统。
二、这个 POC 刻意不"完整"
这个 POC 有意不实现很多常见模块:
-
没有 SQL Parser
-
没有成本优化器
-
没有网络 RPC
-
没有完整容错机制
原因很简单:
在 POC 阶段,这些都不是决定性问题。
我更关心的是:
-
查询是否以 执行计划(Execution Plan) 的形式存在
-
执行是否能自然拆分到多个 worker
-
中间数据是否必须经过 Shuffle
三、最小查询执行模型
在 POC 中,查询不是字符串,而是直接建模成执行节点:
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 中,Shuffle 也不能省略。
实现方式非常简单:
hash(key) % worker_count
但这一过程已经意味着:
-
数据需要重新分布
-
执行流程出现同步点
-
性能与资源问题开始显性化
这也是分布式查询引擎复杂度真正开始的地方。
六、这个 POC 想验证的结论
这个 POC 并不是为了证明"能写查询引擎",而是验证几个基础判断:
-
查询引擎的复杂性主要集中在 执行层
-
Shuffle 是系统级问题,而不是后期优化细节
-
Rust 在这种系统中是工程选择,而不是噱头
七、POC 刻意不解决的问题
为了避免误解,这个 POC 明确不解决:
-
高可用
-
完整容错
-
复杂优化策略
-
云原生部署
这些问题都应建立在执行模型稳定之后。
八、为什么要先做这个 POC
在分布式查询引擎这类系统中:
如果执行模型一开始就有问题,
后续所有功能都会变成补救。
最小 POC 的意义,只是尽早暴露这些风险。
如果你也在评估或实现类似的分布式查询 / 执行系统,
建议先把 Execution 跑通,再考虑功能扩展。