最近在评估一个 分布式查询引擎(Distributed Query Engine) 相关的 Rust 项目需求。
需求本身并不模糊:多节点执行、低延迟、高吞吐、数据 Shuffle、长期可维护。
真正开始写代码前,我没有直接进入“系统搭建”阶段,而是先做了一件很基础的事情:
先写一个最小可运行的查询执行 POC,用来验证执行模型是否合理。
这一步看起来很慢,但在系统类项目里,反而是最省时间的做法。
一、为什么这个阶段选择 Rust
在分布式查询引擎这类系统中,语言并不是个人偏好,而是工程约束的一部分。
选择 Rust,主要基于以下几个现实原因:
1. 并发安全是长期成本问题
查询执行层天然是并发系统,多 worker 并行执行算子,如果内存与并发模型不清晰,问题会在后期不断放大。
Rust 至少把一部分风险提前暴露在编译期。
2. async 非常贴合执行层模型
查询执行过程本质是 IO、计算和中间结果流转的组合,async 能让这些行为在代码结构上保持清晰。
3. 系统是长期演进的
查询引擎不是一次性交付项目,而是会不断增加算子、优化规则、执行策略的系统,维护成本必须可控。
二、这个 POC 有意保持“不完整”
这个 POC 刻意不实现很多常见组件:
- 没有 SQL Parser
- 没有 Cost-Based Optimizer
- 没有网络通信层
- 没有完整的容错与恢复机制
原因很简单:
在验证执行模型之前,这些内容只会增加干扰。
在这个阶段,我只关心三件事:
- 查询是否被明确建模为 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 跑通,再考虑功能扩展。