1. Doris并行查询机制概述
Apache Doris作为一款开源的MPP(大规模并行处理)分析型数据库,其并行查询机制是其高性能的核心保障。在Doris中,每条查询都会在多个BE(Backend)节点上并行执行,同时在单个BE内部也会采用多线程并行方式来加速查询执行。这种双层并行架构使得Doris能够充分利用集群的计算资源,实现查询性能的线性提升。
Doris目前支持所有SQL语句(包括Query、DML、DDL)的并行执行。这种全面的并行支持使得Doris能够应对各种复杂的数据分析场景,从简单的点查询到复杂的多表关联分析,都能获得良好的性能表现。
2. 并行度配置原理与参数解析
2.1 核心参数parallel_pipeline_task_num
Doris中控制单个BE内部并行度的核心参数是parallel_pipeline_task_num,它决定了单个Fragment在执行时使用的工作任务数。这个参数的合理设置对查询性能有着直接影响:
- 默认值为0,表示使用BE CPU核数的一半
- 可以设置为正整数,表示具体的工作任务数
- 支持SQL级别、会话级别和全局级别的设置
这个参数的默认值设计考虑了单查询和并发查询的资源利用情况。使用CPU核数的一半作为默认值,既能够保证单个查询有足够的并行度,又为并发查询留出了资源空间。
2.2 并行度调优的基本原则
在实际生产环境中配置并行度时,需要遵循以下原则:
- 资源利用率平衡:更高的并行度可以更快完成单个查询,但会增加资源消耗,可能影响并发性能
- 查询类型适配:不同类型的查询需要不同的并行度策略
- 数据分布考虑:并行度应该与数据分布情况相匹配
- 系统负载感知:需要根据系统当前负载动态调整并行度
提示:在调整并行度前,建议先通过EXPLAIN和PROFILE命令分析查询计划和执行情况,找到性能瓶颈所在。
3. 不同场景下的并行度配置策略
3.1 单表简单操作场景
对于单表的简单操作(如点查询、少量数据扫描、命中物化视图的查询等),建议将并行度设置为1。这是因为:
- 这类查询通常只涉及一个Fragment
- 数据扫描线程和查询执行线程是分开的,扫描线程会自动做并行扫描
- 设置过高的并行度反而会增加线程调度开销
示例配置:
-- 会话级别设置 SET parallel_pipeline_task_num = 1; -- SQL级别设置 SELECT /*+SET_VAR(parallel_pipeline_task_num=1)*/ * FROM table WHERE id = 100;3.2 大表JOIN复杂查询场景
对于涉及大表JOIN的复杂查询,特别是CPU密集型的计算场景,可以适当提高并行度。假设BE节点有16个CPU核:
- 初始可以设置为16(与CPU核数相同)
- 如果CPU利用率未打满,可以尝试增大到24或32
- 但不宜设置过大(如超过核数2倍),否则会引入过多线程调度开销
示例配置:
-- 对大表JOIN查询设置较高并行度 SELECT /*+SET_VAR(parallel_pipeline_task_num=16)*/ a.*, b.* FROM large_table_a a JOIN large_table_b b ON a.key = b.key;3.3 压力测试场景
在进行压力测试时,为了模拟高并发场景,建议将并行度设置为1:
- 压力测试时会有大量并发查询
- 每个查询设置低并行度可以更好地模拟真实高并发场景
- 避免因单个查询占用过多资源而影响测试结果准确性
配置示例:
-- 压力测试前设置会话级并行度 SET parallel_pipeline_task_num = 1;4. 多级并行度配置方法
4.1 SQL级别配置
通过SQL HINT可以灵活控制单个SQL的并行度,这是最精细化的控制方式:
-- 使用HINT设置并行度为8 SELECT /*+SET_VAR(parallel_pipeline_task_num=8)*/ * FROM table1, table2 WHERE table1.id = table2.id; -- 可以同时设置多个参数 SELECT /*+SET_VAR(parallel_pipeline_task_num=8, runtime_filter_mode=global)*/ * FROM table1, table2 WHERE table1.id = table2.id;4.2 会话级别配置
通过会话变量设置对当前会话生效的并行度:
-- 设置当前会话的并行度 SET parallel_pipeline_task_num = 8; -- 注意这会影响到会话中的所有SQL,包括简单查询 SELECT * FROM small_table; -- 也会使用并行度84.3 全局级别配置
通过全局变量设置对所有新会话生效的并行度:
-- 设置全局并行度 SET GLOBAL parallel_pipeline_task_num = 8; -- 需要重新连接才会生效,或者重启FE5. 数据分片与并行度的关系
从Doris 2.1版本开始,支持并行度与数据分片(Tablet)数量的解耦:
- 之前版本:并行度不能大于查询涉及的数据分片数量
- 2.1+版本:支持分片内部的并行读取,突破了这一限制
这个特性对于以下场景特别有用:
- 大分片表的并行扫描
- 数据分布不均匀时的资源利用
- 减少小文件问题对查询性能的影响
注意:
- 该功能仅支持Duplicate和Unique Key Merge-On-Write表模型
- 对于Aggregate和Unique Key Merge-On-Read模型,并行度仍受限于分片数量
6. 性能调优实战案例
6.1 案例一:降低并行度缓解CPU压力
问题现象:
- 线上系统CPU使用率长期处于高位
- 部分低延迟查询受到影响
分析过程:
- 通过监控发现CPU使用率峰值达到90%+
- 使用SHOW PROCESSLIST查看正在执行的查询
- 通过PROFILE分析发现多数查询使用默认并行度(8核机器使用4个并行任务)
解决方案:
-- 将全局并行度从4降到2 SET GLOBAL parallel_pipeline_task_num = 2;效果:
- CPU使用率降至60%左右
- 低延迟查询的响应时间改善明显
- 复杂查询的耗时略有增加但在可接受范围
6.2 案例二:提高并行度加速大表JOIN
问题现象:
- 一个20亿条记录表与500万条记录表的JOIN查询需要28秒
- CPU使用率只有60%,资源未充分利用
分析过程:
- 通过PROFILE分析发现主要耗时在JOIN算子
- 确认数据扫描不是瓶颈
- 确认内存充足,无spill发生
解决方案:
-- 对该查询设置更高并行度 SELECT /*+SET_VAR(parallel_pipeline_task_num=16)*/ sum(if(t2.value is null, 0, 1)) exist_value, sum(if(t2.value is null, 1, 0)) no_exist_value FROM t1 LEFT JOIN t2 ON t1.key = t2.key;效果:
- 查询耗时从28秒降至19秒
- CPU使用率提升至90%
- 资源利用率更充分
7. 最佳实践与注意事项
7.1 并行度配置的最佳实践
- 从默认值开始:大多数情况下使用默认值即可获得良好性能
- 渐进式调整:按照4-2-1的阶梯方式调整,观察性能变化
- 关注CPU利用率:理想的CPU利用率在70%-90%之间
- 区分查询类型:简单查询低并行,复杂查询高并行
- 监控系统负载:高并发时段适当降低并行度
7.2 常见问题与解决方案
问题1:设置高并行度后查询反而变慢
- 可能原因:线程调度开销过大
- 解决方案:降低并行度,特别是对于简单查询
问题2:CPU使用率始终上不去
- 可能原因:
- 并行度设置过低
- 存在其他瓶颈(如IO、网络)
- 解决方案:
- 检查PROFILE确认瓶颈点
- 适当提高并行度
- 检查数据分布是否均匀
问题3:高并发时系统响应变慢
- 可能原因:单个查询占用资源过多
- 解决方案:
- 降低全局并行度
- 对简单查询设置单独的并行度
7.3 监控与维护建议
- 建立基线:记录不同查询在不同并行度下的性能表现
- 定期审查:随着数据量和查询模式变化,调整并行度策略
- 异常报警:对CPU使用率、查询延迟等设置监控告警
- 文档记录:记录各种查询类型的推荐并行度配置
8. 深度优化技巧
8.1 基于查询计划的并行度调整
通过EXPLAIN分析查询计划,针对特定算子调整并行度:
-- 分析查询计划 EXPLAIN SELECT * FROM table1 JOIN table2 ON table1.id = table2.id; -- 根据计划中的瓶颈点针对性调整 SELECT /*+SET_VAR(parallel_pipeline_task_num=8)*/ * FROM table1 JOIN table2 ON table1.id = table2.id;8.2 并行度与Runtime Filter的结合使用
并行度与Runtime Filter配合可以进一步提升JOIN性能:
SELECT /*+SET_VAR(parallel_pipeline_task_num=8, runtime_filter_mode=global)*/ * FROM large_table l JOIN small_table s ON l.id = s.id;8.3 动态并行度调整策略
对于周期性负载变化的系统,可以编写脚本动态调整并行度:
-- 业务高峰时段设置较低并行度 SET GLOBAL parallel_pipeline_task_num = 4; -- 业务低谷时段设置较高并行度 SET GLOBAL parallel_pipeline_task_num = 8;9. 性能测试方法论
9.1 测试环境搭建建议
- 环境隔离:测试环境应与生产环境隔离
- 数据模拟:使用真实数据或具有相似特征的数据
- 基准测试:先测试单查询性能,再测试并发性能
9.2 测试指标
- 单查询延迟:不同并行度下的查询响应时间
- 系统吞吐量:固定时间内完成的查询数量
- 资源利用率:CPU、内存、IO等资源使用情况
- 可扩展性:增加资源后的性能提升比例
9.3 测试用例设计
- 简单查询:点查询、小范围扫描
- 复杂查询:多表JOIN、聚合计算
- 混合负载:模拟真实生产中的查询混合
10. 未来发展方向
Doris社区正在持续优化并行查询机制,未来可能增强的方向包括:
- 自适应并行度:根据系统负载和查询特征自动调整并行度
- 更细粒度的并行控制:针对不同算子设置不同并行度
- 资源隔离:确保关键查询获得足够资源
- 与查询优化器深度集成:基于代价模型选择最优并行度
在实际使用中,我发现并行度的配置需要结合业务特点反复试验和调整。一个好的做法是为不同类型的查询建立性能基线,记录不同配置下的表现,这样才能找到最适合自己业务场景的配置方案。