2026/4/18 9:27:38
网站建设
项目流程
网站关键词多少个字数 站长网,陕西省建设厅申报网站,网站动态图怎么做,网站开发技术孵化在日常的数据分析和业务报表中#xff0c;TopN 查询几乎无处不在#xff1a;无论是寻找销量最高的前十件商品#xff0c;还是筛选访问量最多的前几条日志#xff0c;开发者和数据分析师都在频繁处理 前 N 条数据。然而#xff0c;当表的列数达到百余或更多时TopN 查询几乎无处不在无论是寻找销量最高的前十件商品还是筛选访问量最多的前几条日志开发者和数据分析师都在频繁处理 前 N 条数据。然而当表的列数达到百余或更多时一个看似简单的SELECT \* ... ORDER BY ... LIMIT N查询背后可能隐藏着巨大的性能瓶颈。尽管我们只关心某一列的前 N 条结果数据库依然可能扫描整张表的所有列从而导致 IO 读放大Read Amplification拖慢查询速度。在大数据场景下这种低效不仅浪费存储带宽还直接影响业务决策的实时性。为了帮助用户快速获取目标数据Apache Doris 针对 TopN 类型查询进行了全局优化可将此类查询的性能提升约 5 倍同时优化范围也从单表进一步拓展至数据湖场景与多表关联查询显著扩大了适用范围。TopN 查询优化思路为直观说明 TopN 查询的性能瓶颈我们不妨将其简化为列式存储文件的读取场景比如访问 Apache Doris 内部 Segment 文件或访问数据湖中常见的 Parquet / ORC 文件。假设需要找 第二列 中数值最大的那条记录SELECT * FROM table ORDER BY col2 LIMIT 1。由于查询需要返回整行传统做法通常是先扫描表的所有列排序后再定位到对应记录。而 Apache Doris 原生列式存储的物理布局能够提供更优解由于各列独立存放因此可先仅读取第二列的数据快速计算出最大值所在的行号再利用文件元数据直接按行号提取该行的完整记录无需扫描无关列。相比传统方式这种方法显著减少 IO 读放大并降低内存占用。这一优化对于湖仓分析场景尤为关键因其直接关乎成本及性能。 对于 Iceberg、Paimon 等开放湖格式数据通常存放在 S3 等对象存储中其 IO 性能普遍低于本地磁盘且常按访问流量或请求次数计费。数据扫描次数的减少意味着更低的延迟与更少的费用。特别是在数据量庞大、查询频繁的分析业务中TopN 的优化不仅能大幅提升响应速度更能带来切实的成本节约实现性能与经济的双重收益。全局 TopN 优化实现基于上述思路指引Apache Doris 完成了对 TopN 的全局优化。对于单表的 TopN利用单节点内的 Runtime Filter 对内部表查询进行动态过滤有效减少 IO 并提升执行性能。在前不久不发的 4.0 版本中也进一步提升了 TopN 查询性能通过引入 MaterializeNode实现了两阶段数据访问机制并将优化范围从单表进一步拓展至数据湖场景与多表关联查询显著扩大了其适用范围。接下来我们将深入解析 TopN Runtime Filter、单表两阶段 TopN 以及多表关联 TopN 的具体优化实现。01 采用 Runtime FilterRuntime Filter 是一种运行时数据裁剪技术。Doris 在执行 SQL 时动态生成过滤条件并将这些条件下推到后续数据处理环节利用运行时信息进行数据裁剪从而降低 IO 开销并提升性能。在两表 Join 场景中这一技术的典型应用是将 build 侧的 key 集合通过 IN-list、Bloom Filter 等形式下推到 probe 侧尽早过滤掉无关数据减少扫描和传输。TopN Runtime Filter 同样采用这一思路在运行时维护排序列的值范围并生成 Runtime Filter 以裁剪后续扫描从而提升单节点上的 TopN 查询性能。在单机测试中基于 Runtime Filter 优化后的 TopN 查询耗时从3 秒降到 1 秒性能提升约 3 倍SELECT * FROM lineitem ORDER BY l_orderkey LIMIT 1000;02 两阶段数据访问机制基于 Runtime Filter 的方法虽然能够在运行时动态过滤数据但仍需读取所有列无法彻底消除读放大。为此我们引入了两阶段数据访问机制进一步减少列的读取与 IO 开销。其执行流程示意图如下以如下 SQL 为例SELECT * FROM table ORDER BY colA LIMIT 10;第 1 阶段只读取排序列在该阶段的 Scan 任务中系统只读取排序列colA并增加一个辅助列__DORIS_ROWID_COL__。相当于执行SELECT colA, __DORIS_ROWID_COL__ FROM table ORDER BY colA LIMIT 10;该方法跳过了非排序列的读取仅扫描与排序相关的数据并记录其位置信息。DORIS_ROWID_COL用于唯一标识数据所在文件与行号其具体编码设计将在后续章节详细说明。第 2 阶段基于 RowID 的完整数据获取新增的 MaterializeNode 接收第一阶段的结果后会根据__DORIS_ROWID_COL__向对应 Backend 发起基于行号RowID的数据拉取请求。借助文件中记录的位置信息Doris 可以快速定位并读取对应记录由于已完成 TopN 计算第二阶段通常只需读取有限行例如示例中的 10 行。得益于该阶段可通过RPC跨节点执行打破了单节点执行限制两阶段访问机制也自然扩展至多表关联的 TopN 场景例如SELECT * FROM lineitem JOIN orders ON l_orderkey o_orderkey WHERE o_orderdate DATE 1995-03-15 ORDER BY l_partkey LIMIT 100;其执行规划示意如下执行计划中MaterializeNode 在第二阶段可以穿透 Join 节点从扫描节点获取最终数据。优化前后性能表现Apache Doris 对于 TopN 的优化已在多种场景上得到验证。我们在 Doris 内表、Parquet 及 ORC 格式的 Hive 表上基于 TPCH 100G 标准数据集中的lineitem表分别构建了单表与多表TopN 查询场景系统对比了优化前后的性能表现。单表 TopN 查询示例选取不同排序列-- Q1 - Q3: select * from lineitem order by l_orderkey limit 1000; select * from lineitem order by l_partkey limit 1000; select * from lineitem order by l_comment limit 1000;多表 TopN 查询示例不同的表数、JOIN 方式与 SELECT 列数-- Q4: SELECT * FROM lineitem JOIN orders ON l_orderkey o_orderkey WHERE o_orderdate DATE 1995-03-15 ORDER BY l_partkey LIMIT 100; -- Q5: SELECT * FROM customer, orders, lineitem WHERE c_mktsegment BUILDING AND c_custkey o_custkey AND l_orderkey o_orderkey AND o_orderdate DATE 1995-03-15 AND l_shipdate DATE 1995-03-15 ORDER BY o_orderdate LIMIT 10; -- Q6: SELECT lineitem.* FROM customer, orders, lineitem WHERE c_mktsegment BUILDING AND c_custkey o_custkey AND l_orderkey o_orderkey AND o_orderdate DATE 1995-03-15 AND l_shipdate DATE 1995-03-15 ORDER BY o_orderdate LIMIT 10; -- Q7: SELECT l_shipdate, l_orderkey, l_linenumber FROM customer, orders, lineitem WHERE c_mktsegment BUILDING AND c_custkey o_custkey AND l_orderkey o_orderkey AND o_orderdate DATE 1995-03-15 AND l_shipdate DATE 1995-03-15 ORDER BY o_orderdate LIMIT 10; -- Q8: SELECT * FROM supplier, lineitem l1, orders, nation WHERE s_suppkey l1.l_suppkey AND o_orderkey l1.l_orderkey AND o_orderstatus F AND l1.l_receiptdate l1.l_commitdate AND EXISTS (SELECT * FROM lineitem l2 WHERE l2.l_orderkey l1.l_orderkey AND l2.l_suppkey l1.l_suppkey) AND NOT EXISTS (SELECT * FROM lineitem l3 WHERE l3.l_orderkey l1.l_orderkey AND l3.l_suppkey l1.l_suppkey AND l3.l_receiptdate l3.l_commitdate) AND s_nationkey n_nationkey AND n_name SAUDI ARABIA ORDER BY s_name LIMIT 100; -- Q9: SELECT s_name, s_address, s_phone, s_acctbal, l_shipdate, l_quantity, l_extendedprice, l_discount, l_tax, l_returnflag, l_linestatus, l_shipinstruct, o_orderdate, o_totalprice, o_orderpriority, n_name FROM supplier, lineitem l1, orders, nation WHERE s_suppkey l1.l_suppkey AND o_orderkey l1.l_orderkey AND o_orderstatus F AND l1.l_receiptdate l1.l_commitdate AND EXISTS (SELECT * FROM lineitem l2 WHERE l2.l_orderkey l1.l_orderkey AND l2.l_suppkey l1.l_suppkey) AND NOT EXISTS (SELECT * FROM lineitem l3 WHERE l3.l_orderkey l1.l_orderkey AND l3.l_suppkey l1.l_suppkey AND l3.l_receiptdate l3.l_commitdate) AND s_nationkey n_nationkey AND n_name SAUDI ARABIA ORDER BY s_name LIMIT 100; -- Q10: SELECT s_name, s_nationkey, l_orderkey, o_orderstatus, n_name FROM supplier, lineitem l1, orders, nation WHERE s_suppkey l1.l_suppkey AND o_orderkey l1.l_orderkey AND o_orderstatus F AND l1.l_receiptdate l1.l_commitdate AND EXISTS (SELECT * FROM lineitem l2 WHERE l2.l_orderkey l1.l_orderkey AND l2.l_suppkey l1.l_suppkey) AND NOT EXISTS (SELECT * FROM lineitem l3 WHERE l3.l_orderkey l1.l_orderkey AND l3.l_suppkey l1.l_suppkey AND l3.l_receiptdate l3.l_commitdate) AND s_nationkey n_nationkey AND n_name SAUDI ARABIA ORDER BY s_name LIMIT 100;下表汇总了优化带来的平均性能提升查询时间缩短的百分比区间数据表明TopN 优化在多种数据格式与查询模式下均能显著提升性能。平均可降低查询时间 30% 至 40%在部分多表关联场景中性能提升幅度最高可达 80%效果尤为突出。这证明了两阶段访问机制有效减少了不必要的 IO在不同存储格式和复杂查询中均能带来可观的收益。TopN 执行逻辑解析前文简要介绍了 TopN 的两阶段执行逻辑在实际实现中该流程面临几项核心挑战Pipeline 执行线程的阻塞第二阶段数据拉取涉及网络 IO若在 Pipeline 执行线程中同步进行会导致线程被阻塞降低系统整体吞吐。多表查询的支持Join 算子涉及多张表的物化需要准确识别对应需要物化的列。内外表格式的统一Doris 内表与 Parquet、ORC 等开放格式在行号管理上机制不同需设计统一的行标识抽象以确保内外表逻辑一致。资源隔离管控延迟物化阶段的 IO 操作需纳入 Workload Group 进行统一资源管控避免干扰线上其他查询保证系统稳定性。针对上述挑战Doris 通过混合任务调度器、全局行标识编码 与 智能优化器规则 协同工作系统性地解决了这些问题。以下我们将逐一展开其设计实现。01 混合调度器为解决 Pipeline 执行线程在网络 IO 场景下易被阻塞的问题我们重构了 Doris 的 Pipeline 执行框架引入了混合任务调度器HybridTaskScheduler从调度层面分离阻塞与非阻塞任务显著降低了 IO 等待对执行效率的影响。其核心设计如下图所示具体实现上原有统一的 TaskScheduler 被拆分为两类调度器共同构成新的 HybridTaskSchedulerNonBlockingScheduler专门调度非阻塞型任务如纯计算操作。调度器线程数量跟 CPU 核数相等。能够确保充分利用 CPU 资源。BlockingScheduler用于调度可能阻塞的任务如涉及磁盘 IO、网络 IO 等操作。该调度器线程数可动态调整默认为 CPU 核数的两倍以更好地容纳 IO 等待。通过将任务按是否阻塞分类调度系统有效避免了阻塞型任务对计算密集型任务的资源抢占。例如TopN 查询第二阶段中的 Materialization Node 会被自动提交至 BlockingScheduler 执行从而大幅减少 IO 阻塞对全局 Pipeline 执行线程的占用。02 全局 ID 编码与资源管控上文提到的__DORIS_ROWID_COL__用于在第二阶段精确定位数据行其编码设计兼顾了效率、跨格式一致性与资源管控。编码格式如下编码格式: [version:uint8] [backend_id:uint64] [file_id:uint32] [row_id:uint32]version标识编码格式版本用于后续扩展与兼容。backend_idBE 节点 ID。该字段实现了精准的 RPC 定向 ------ 第二阶段请求可直接发送至对应节点避免广播开销。同时接收请求的节点会将数据读取任务提交至该查询所属的 Workload Group从而确保资源隔离与统一管控。file_id系统为查询涉及到的每个文件生成唯一 ID并在内存中维护 ID 到实际文件路径的映射。通过唯一 ID 可以减少第二阶段发送文件信息的请求大小减少网络资源开销。对于内表文件名编码为tabet_id-rowset_id-segment_id对于 Parquet/ORC文件名编码为filename-rowgroup_idrow_id用于标识数据在对应文件中的行号。同时针对 OUTER JOIN 等可能会生成 NULL 值的场景row_id可以编码为 NULL从而在第二阶段直接跳过请求进一步提升效率。03 全局延迟物化算法为系统支持两阶段数据访问Doris 优化器引入了全新的全局延迟物化算法。该算法在编译阶段自动识别可延迟读取的列从而在保证语义正确的前提下最大限度减少第一阶段的数据扫描量。其执行流程可概括如下列集合划分优化器将需要访问的列分为关键列集 K 和 延迟列集 D。K 列是在第一阶段需要读取的列D 列是需要在第二阶段延迟读取的列。自顶向下遍历算法自顶向下遍历执行计划数的每个算子将需要参与计算的列如条件过滤列Join 列等加入到 K 集合中其余列加入到 D 集合中。字段转换如遇到投影节点Projection Operator或集合操作节点Set Operator等产生字段变化的节点则会将 K 中相应的字段转换成下层节点的字段。结果推导最终推导出 Scan 节点需读取的 K 集合以及上游各算子对应的 D 集合。以如下执行计划片段为例FILTER(x 10) -- PROJECT(ab as x) -- SCAN(T)FILTER节点依赖列x因此将x加入 K。PROJECT节点将x映射为底层表达式a b因此从 K 中移除x并加入a和b。最终传递至SCAN的 K 集合为{a, b}即仅需在第一阶段读取列a与b。该算法在语法树层面实现了列读取的智能推迟为高效的两阶段执行奠定了编译基础。结束语TopN 优化极大地强化了从海量数据中高效提取核心信息的能力可广泛应用于实时排行榜、热点分析、销量统计、告警排序等高价值业务场景。在方案设计过程中我们也研究了业界其他系统的实现思路。以 DuckDB 为例其在处理单表 TopN 时会将其转换为一个特殊的 semi Join 操作左节点去扫描整表右节点在扫描排序列后取出其 TopN 行并且会借助 Runtime Filter 减少左表扫描数据量。该方案的优势在于复用了成熟的 Join 框架但在某些场景下 ------ 例如排序列不是主键或面对 Parquet 等格式的 Row Group 时 ------ 过滤效率可能受到影响适用性存在一定边界。未来我们计划进一步进行深度开发包括集合运算UNION/EXCEPT/INTERSECT等复杂算子的 TopN 支持。动态自适应物化阈值调整。我们将持续追踪数据查询领域的前沿技术并不断探索其在真实业务场景中的落地实践致力于为用户提供持续领先的查询性能体验。秀干终成栋精钢不作钩 在 极致性能 的探索路上Apache Doris 永不止步。长按下方二维码回复「1218」加入 Doris 社区交流群并免费领取 Doris x Al 和 100 企业实践案例集获取技术帮助、了解最新动态并与更多开发者和用户互动。