关于 doris-profile-reader
doris-profile-reader 是用于分析 Apache Doris 查询运行时 Profile 的技能,帮助数据库工程师区分实际计算与等待,并检查连接和运行时过滤器行为,避免把 Profile 证据误当作深层根因。
主要功能
- 01区分计算、等待与背压
- 02解读算子和计数器信号
- 03诊断连接顺序与运行时过滤器
- 04检查倾斜、扫描、内存和溢写
- 05区分 Profile 证据与根因判断
适用场景
- 01查找 Doris 慢查询瓶颈
- 02评估连接顺序和运行时过滤器方向
- 03排查倾斜、溢写或内存压力
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ apache/ doris-skills --skill doris-profile-reader安装后在 Claude Code 中输入/doris-profile-reader确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlreferences/counter-semantics.mdfailure-patterns.mdjoin-order-diagnosis.mdknown-issues-solutions.mdoperator-guide.mdreading-workflow.mdruntime-filters.mdsolution-playbook.mdsource-profile-inventory.mdscripts/extract_source_profile_inventory.py详细介绍
Doris Profile Reader
目的
使用此技能识别 Apache Doris 查询运行时 Profile 中的实际瓶颈。核心原则是,在将某个算子判定为开销高之前,先区分实际工作与依赖等待、排队等待和背压等待。如果计划包含联接,还要区分直接的运行时瓶颈与计划形态成因,尤其是糟糕的联接顺序和运行时过滤器方向。
必须遵循的阅读顺序
- 阅读
references/reading-workflow.md,了解分析工作流和输出约定。 - 阅读
references/counter-semantics.md,了解计数器含义和优先级,尤其是等待计数器。 - 阅读
references/operator-guide.md,了解相关算子系列。 - 当 Profile 显示数据倾斜、不合理的联接/分布选择、剪枝不足、排序/溢出/资源压力、外部扫描/目录、生命周期等待,或用户希望仅凭 Profile 证据定位问题级根因时,阅读
references/failure-patterns.md。 - 当 Profile 或计划包含多个联接、大型哈希/嵌套循环构建、可能被联接剪枝的大型扫描、成对的快速/慢速计划、提示/重排后的 SQL,或用户询问联接形态/重排时,阅读
references/join-order-diagnosis.md。 - 当 Profile 或计划包含
RuntimeFilterInfo、RF... <-、RF... ->、JRFs、WaitForRuntimeFilter或AcquireRuntimeFilter时,阅读references/runtime-filters.md。 - 使用
references/source-profile-inventory.md作为有源代码依据的算子/计数器清单。如果叙述性文档中没有提到某个计数器或算子,不要忽略它;请在此清单中查找,并按照counter-semantics.md中的规则分类。 - 当用户要求修复、变通方案、缓解措施、调优、SQL 重写,或所请求的输出结构包含解决方案字段时,阅读
references/solution-playbook.md。 - 确立根因层级并收到解决方案请求后,阅读
references/known-issues-solutions.md。将其作为历史证据,而不是 Profile 诊断的替代品。
必须遵守的解读规则
- 不要将
WaitForDependencyTime、WaitForDependency[...]Time、WaitForData0、WaitForDataN、WaitForRpcBufferQueue、WaitForBroadcastBuffer、PendingFinishDependency或 pipeline 阻塞/等待计数器直接视为算子计算成本。它们是依赖、数据到达、队列、内存或背压信号。 - 不要把
DATA_STREAM_SINK_OPERATOR上较高的RpcMaxTime/RpcAvgTime当作普通等待计数器而忽略。如果BytesSent和RpcCount均非零,这就是交换/RPC 路径的证据;如果该值接近墙钟时间,或高于本地序列化/压缩/发送计时器,就应将其纳入瓶颈结论,并检查通道、接收端或 BE 离群偏斜。 - 如果某个会话变量、缺失的优化或较大的行数可以解释数据为何到达交换节点,除非本地扫描/排序/窗口/连接活动计时器占主导,否则不要将其作为主要慢因。如果
DATA_STREAM_SINK_OPERATOR的 RPC 时间占用的时间超过已用时间的一半,应将计划/数据量视为放大因素,并将 RPC/BE 到 BE 传输保留为首要瓶颈候选。 - 提供多个 profile 时,它们之间的差异优先于单个 profile 的等待现象。如果慢/快 profile 在连接顺序、裁剪、
instance_num、交换分布或 bucket/sink 布局方面不同,应先诊断这些计划/数据布局差异,而不是归咎于普通 RPC、扫描器或交换背压。 - 如果连接/投影热点主要由来自某个表的宽 JSON/VARIANT/字符串列造成,且工作由单个实例处理或存在严重偏斜,不要止步于表达式 CPU。应将可能的根因层表述为连接顺序/分布选择,或成本模型低估了宽列处理开销,并说明表达式 CPU 是运行时表现。
- 对于 INSERT/SELECT profile,可见的巨量行
SPLIT_BY_STRING/cardinality(split_by_string(...))表达式是首要根因候选。除非有独立证据表明 sink/写入活动计时器占主导,否则不要将它们排在普通扫描/Shuffle/table-sink 数据量之后。 - 对于 INSERT 偏斜,必须将接收端或 UNION/本地交换偏斜与目标 bucket 数、bucket 键、写入 bucket 并行度及热点值联系起来。如果没有准确的 DDL,应得出可能是目标 bucket/键设计不匹配或写入 bucket 数不足的结论,而不是止步于交换偏斜。
- 在得出全表扫描或 INSERT 慢只是因为行数多之前,应先在 SQL/计划文本中搜索字符串密集型表达式,例如
SPLIT_BY_STRING、regexp、trim/replace 链、INSTR、COALESCE和NULLIF。处理海量行时,即使没有可见的ProjectionTime计数器,这些表达式仍是根因候选。 - 对于点查或窄查询,如果输出很少但涉及许多 tablet/扫描器,应明确说明 bucket/tablet 裁剪情况。较高的
ScannerWorkerWaitTime是扇出导致的现象,除非有工作负载压力证据;如果谓词和索引有效,但打开了许多 tablet,应优先考虑缺少 bucket 裁剪/表布局问题,而不是扫描器池争用。 - 不要仅凭最大的类等待计数器给算子排序。应先根据
ExecTime/活动计时器、直接自定义计时器、行数/字节数、内存/溢写和偏斜进行排序。 - 对查询瓶颈排序之前,应先确认 profile 类型。Async-profiler CPU 火焰图、FE load-image/load-auth profile、日志或缓存摘要可以作为其自身生命周期热点的有效证据,但它们不是 BE 查询运行时 profile。如果文件类型与所请求的慢查询问题不匹配,应指出这种不匹配,并要求提供相应的运行时 profile,而不是强行得出无关的 Doris 查询结论。
- 对于 OOM、溢写后超时或内存限制 profile,应先找到内存占用方,再选择根因:检查会话/全局内存设置、溢写状态、各算子峰值内存、哈希连接构建/探测内存、聚合哈希/状态内存、排序缓冲区、table-sink 缓冲区、交换缓冲区和表达式物化。未确认聚合状态和表达式物化成本是否为原因之前,不要将大型连接定为根因。
- 会话/配置证据的优先级可能高于对算子形态的推测。应先评估明确的非默认设置,例如禁用并行扫描、
parallel_pipeline_task_num设置过小、内存百分比/限制过低、禁用两阶段读取或禁用本地 Shuffle,然后再得出连接顺序不佳、扫描器争用或网络离群的结论。 - 先独立分析多个 profile,再进行总结。对于每个 profile,先指出其主要活动/等待/资源信号,然后再进行比较。不要把无关的 profile 归并为一种笼统的扇出、扫描、连接或 RPC 解释。
- 将 profile 可见的瓶颈与问题层面的根因分开。如果更深层的原因需要当前未提供的 schema、统计信息、版本、配置、日志、注释或快慢对比,应说明 profile 能证明什么,并列出确切的待查项,不要过度断言。
- 对于解决方案字段,不要把每个合理的验证项都转成修复方案。先确定解决方案的来源:profile 已证实、确切的历史模式、代码支持但尚未证实的后续检查,或没有历史解决方案。如果已知模式证据中包含精确的历史参数名、值、PR 编号、版本号和 SQL 改写形式,应予以保留。
- 如果诊断不确定或仅限于 profile 层面,优先留空历史解决方案,并提供有针对性的
next_checks,而不是给出具体缓解措施。错误的具体修复方案比不提供修复方案更糟。 - 如果所请求的 schema 包含
historical_solution,只有对于profile_proven或确切的historical_pattern操作才填写该字段。如果该操作仍需验证,或是根据相邻模式族推断得出,应将historical_solution留空,并将该操作移至next_checks。 - 将合并 profile 中的
sum计时器视为多个并行实例的累计值。计时器可能超过查询已用时间,且在许多扫描器、driver 或 fragment 并行运行时仍属正常。 - 对于扫描,应优先关注
RowsRead、ScanRows、ScanBytes、ScannerCpuTime、ScannerGetBlockTime、ScannerWorkerWaitTime、I/O/解压计时器、谓词/延迟读取计时器和行过滤计数器。ScannerWorkerWaitTime很重要,但它表示扫描器调度/线程池等待,而非扫描 CPU。 - 对于运行时过滤器,应区分来源/构建端与目标/探测端扫描。在计划文本中,
RFxxx <- expr是由构建端生成的;RFxxx -> expr则应用于目标扫描。在 profile 中,RuntimeFilterInfo和扫描端等待/过滤计数器决定 RF 是发挥了作用,还是仅仅在等待。 - 对于连接,在判断顺序之前,始终先识别构建端和探测/目标端。如果选择性连接/RF 来源的调度过晚,未能裁剪该扫描,那么即使扫描是当前直接的活动瓶颈,根因仍可能是连接顺序不佳。
- 如果连接/分布选择看起来不佳,应检查估算行数与实际行数、统计信息/NULL/热点值证据、连接分布,以及存在时的 RF 方向。如果 profile 暴露了规划器/统计信息方面的问题,不要止步于“大扫描”或“大构建端”。
- 不要要求必须提供配对的快速 profile 才能指出可能的连接顺序不佳。如果单个慢 profile 表明:在探测端/结果为空之前进行了大量无用的构建/扫描工作;RF 来源端在发出空/极小过滤器之前必须进行大量扫描;或者巨大的中间结果随后被高度选择性或矛盾的连接谓词消除,那么单个慢 profile 就足以作为证据。
- 不要把“当前 RF 让其他扫描等待后跳过”视为连接顺序良好的证据。如果生成这个空/极小 RF 必须进行唯一一次昂贵扫描,那么 RF 来源/目标选择本身就是连接顺序问题。
- 如果在空/极小探测端、保留端、半连接键集合或矛盾连接消除结果之前,已经付出了大型构建/来源端的成本,那么应指出构建/探测顺序可能不佳,除非 profile 证明该顺序在语义上不可避免,且不可能更早裁剪或短路。
- 如果扫描、连接、聚合、交换、排序或 sink 算子的
max远高于avg,应进行偏斜检查:比较各实例的行数/时间/字节数、tablet 或 bucket 分配、本地 Shuffle、分布键以及全局/会话级并行度,然后再将原因归结为普通数据量。 - 如果强烈匹配单 profile 连接顺序模式,不要用“可疑”“很可能不佳”或“尚未证实”来含糊其辞。如果更优的合法顺序仍需验证,应使用“可能不佳”;“尚未证实”仅用于确切的备选形态,而不是用于连接顺序诊断本身。
- 当连接查询存在计划形态证据时,答案必须明确判断连接顺序/构建端-探测端/RF 方向是良好、可疑还是不佳。不要用含糊的“谓词问题”或“计划形态问题”取代这一判断。
- 较长的
InitTime、OpenTime、CloseTime或 profile 总时长可能很重要,但只有在确认它不是许多实例的累计值,且没有被已知的等待/依赖分支主导之后,才能将其作为依据。
标准回答格式
解释 profile 时,按以下顺序回答:
Conclusion:用一到两句话指出可能的瓶颈;如果涉及 join,还要说明计划形态/join 顺序是否可能是原因。Evidence:列出 profile 计数器、算子名称、数值,并说明每项属于活跃工作量、数据量、等待/背压、内存/溢写,还是运行时过滤器证据。Reasoning:说明证据如何对应执行计划、哪一侧是构建端/探测端或 RF 源/目标、为何不采信容易误导的计数器,以及 join 顺序是否合理。Next checks:如果结论仍不确定,列出所需的最少额外 profile/日志/代码检查。Solution / mitigation(需要时):只提及此 profile 直接支持的解决方案,或references/known-issues-solutions.md中与之匹配的历史模式。如果没有适用的历史解决方案或经 profile 证实的解决方案,请如实说明,并将其列为后续检查项,而不是凭空给出建议。
始终保留不确定性。当 profile 信息不足时,明确使用“已证实”“可能”和“尚未证实”。
脚本
scripts/extract_source_profile_inventory.py:扫描 Doris 源码,查找由工厂创建的算子以及计数器/信息注册项。
脚本是用于生成证据的辅助工具,不能替代阅读流程。
评论
支持 Markdown