递归运行时调用指标 – #24565
在 #24031 合并之后,我认为对于下一轮递归 CTE 优化,可能仍然需要有更好的依据。端到端的基准测试时间能告诉我一个查询是否有所改进,但无法告诉我一个轮次(epoch)是主要消耗在调度、去重、键控状态维护、循环状态访问,还是不变构建(invariant build)上。现有的递归 PhysicalOperator 日志包含有用的聚合计数,但对于嵌套调用或区分主要执行阶段,它无法可靠地回答这些问题。
简而言之,此 PR:
- 在现有的 PhysicalOperator 日志中,为每个递归 CTE 状态提供一个稳定的调用身份(invocation identity);
- 报告有界的每轮次分布和前沿容量指标;
- 归因于直接探测、键控状态维护、回退扫描(fallback scan)、最终排空(final draining)、去重、活跃递归管道、保留构建(retained build)和保留 CTE 物化;
- 在选择工作进程(worker)时,区分递归扫描工作和直接键探测工作;以及
- 保持无日志记录(no-logging)的热路径不受指标时钟和原子更新的影响。
调用和轮次证据
嵌套递归 CTE 可以为同一个物理运算符创建和销毁多个运行时状态。一些缓存状态的生命周期也超过了最初描述它们的运算符数据。因此,当创建一个启用日志记录的状态时,我会快照物理运算符类型和参数,并为每个物理递归运算符分配一个单调递增的调用 ID。RuntimeMetrics、EpochSummary 和 DistinctPromoted 记录可以通过运算符标识(例如,表索引)加上调用 ID 进行关联,而无需保留对生命周期更短的运算符元数据的引用。
EpochSummary 对前沿行数、工作进程数、任务数和耗时使用固定的 2 的幂次直方图。它报告 p50 桶上界和确切的最大值,而无需为每个轮次保留一个值。前沿部分还报告存储和分配字节-轮次(byte-epochs)以及峰值容量,这使得窄但过度分配的递归变得可见。终端直方图桶直接覆盖完整的 idx_t 范围;这捕获并修复了早期一个十进制位数与二进制位数混淆的错误。
阶段指标特意明确说明了它们衡量什么:
| 直接 USING KEY 探测 | 查找、键收集和负载最终化的工作进程时间;探测行数、匹配数和部分索引链访问数 |
| 键控状态 | 哈希表提交、部分索引维护、回退循环扫描和最终状态排空的工作进程时间 |
| UNION DISTINCT | 分组工作进程时间以及候选数、插入数和重复拒绝数;提升迁移(promotion migration)不计入候选总数 |
| 递归管道 | 活跃递归、保留连接/构建和保留物化 CTE 的 PipelineExecutor::Execute 工作进程时间 |
以 _work_ns 结尾的字段是累积的工作进程时间,而非关键路径上的墙上时钟时间,因此并行值可能超过轮次的已用时间。直接探测和去重分组等运算符阶段发生在管道执行内部;这些字段是嵌套的归因透镜(attribution lens),而不是应加到管道总数中的值。管道指标涵盖执行器工作,并有意识地排除了准备/最终化阶段。现有的 RuntimeMetrics 总数对于轮次计数、调度器输入、接收器锁等待/工作、状态行数、保留构建计数和保留 CTE 重用仍然有用。
我还纠正了 PhysicalRecursiveCTEKeyJoin 的递归工作发现。直接探测不再伪装成全循环状态扫描。递归探测输入已经由它们可见的递归扫描表示;独立的探测输入为工作进程选择贡献估计的数据块(chunk)数。这既避免了对冻结状态的重复计数,也避免了将一个本应宽泛的直接探测强制推向串行路径。


