作者:[SelectDB](https://c.d4t.cn/CZuTEn 技术团队
在现代数据库系统中,执行引擎在数据库体系结构中起着承上启下的作用,与查询优化器和存储引擎共同组成了数据库的三大模块。我们以 SQL 语句在数据库系统中的完整执行过程为例,来介绍执行引擎在其中发挥的作用:
- 在接收到一条 SQL 查询语句之后,查询优化器会对 SQL 进行语法/词法分析,基于代价模型和规则生成最优执行计划;
- 执行引擎会将生成的执行计划调度到计算节点,按照最优执行计划对底层存储引擎中的数据进行操作并返回查询结果;
在整个查询过程中,查询执行是至关重要的环节,往往需要通过数据读取、过滤、排序、聚合等操作,才能提交给执行引擎进行下一步查询,这几个步骤的设计是否合理直接影响到查询的性能及资源的利用率。而这些能力均由执行模型来提供,而不同的执行模型在数据处理、查询优化和并发控制等方面存在较大差异,因此,一个合适的执行模型对于提高查询效率和系统性能至关重要。
目前业界常见的执行模型有迭代模型/火山模型(Iterator Model)、物化模型(Materialization Model)、向量化/批处理模型(Vectorized / Batch Model)。其中火山模型(Volcano Model)是数据库查询优化和执行中最为常用的执行模型。每一种操作抽象为一个 Operator,整个 SQL 查询被构建成一个 Operator 树。查询执行时,树自顶向下调用 next() 接口,数据则自底向上被拉取处理,因此这种处理方式也被称为拉取执行模型(Pull Based)。火山模型因其具有很高灵活性高、可扩展性好、易于实现和优化等特性,被广泛应用于数据库查询优化和执行中。
作为典型的 MPP 数据库,过去版本中 Apache Doris 亦采取的也是火山模型。当用户发起 SQL 查询时,Apache Doris 会将查询解析成分布式执行计划并分发到执行节点执行,分发到节点的单个执行任务被称为 Instance,在此我们一条简单的 SQL 查询来了解 Instance 在火山模型下的执行过程:
select age, sex from employees where age > 30

如上图可知,Instance 是一个算子(ExecNode)树,算子之间通过数据重分布(Exchange)算子连接起来,从而实现数据流的传递和处理,每个算子实现 next() 方法。当对算子的 next() 方法进行调用时,该算子将调用其孩子算子的 next() 方法来获取输入的数据,然后对数据进行逻辑加工并输出。而因为算子的 next() 方法是同步方法,在没有数据产生时, next() 方法将会持续阻塞。这时候需要循环调用根节点算子的 next() 方法,直到全部数据处理完毕,即可得到整个 Instance 的计算结果。
从上述执行过程可以看出,火山模型是一种简单易用、灵活性高的执行模型,但在单机多核的场景下,存在一些问题需要进一步解决和优化,具体体现在以下几方面:
- 线程阻塞执行:在线程池大小固定的情况下,当一个 Instance 占用一个线程阻塞执行时,如果存在大量的 Instance 同时请求,执行线程池将被占满,从而导致查询引擎出现假死状态,无法响应后续请求。特别是在存在 Instance 之间相互依赖的情况下,还可能会出现逻辑死锁的情况,比如当前线程中正在执行的 Instance 依赖于其他的 Instance,而这些 Instance 正处于等待队列中,无法得到执行,从而加剧系统的负载和压力。当一个执行节点同时运行的 Instance 线程数远大于 CPU 核数时,Instance 间的调度将依赖于系统调度机制,这就可能产生 Context 切换开销,尤其是在系统混部的场景中,线程切换的开销会更加显著。
- CPU 资源抢占:Instance 线程之间出现争抢 CPU 资源的问题,可能导致不同大小的查询、不同租户之间互相影响。
- 无法充分利用多核计算能力:执行计划的并行度取决于数据分布,当一台执行节点上存在 N 个数据分桶时,该节点上运行的 Instance 数量不能超过 N,因此分桶的设置显得尤为重要。如果分桶设置过少,难以充分利用多核计算能力,反之,则会带来碎片化问题。多数场景下进行性能调优时需要手动设置并行度,而在生产环境中,预估数据分桶数是一项极具挑战性的任务,不合理的分桶使得 Doris 的性能优势无法得到充分发挥,无法充分利用多核计算能力。
Pipeline 执行模型的引入
为了解决过去版本所存在的问题,

最低0.47元/天 解锁文章
3971

被折叠的 条评论
为什么被折叠?



