HashJoin DPE 实现状态

当前状态:ORCA 优化阶段崩溃

实现基于 docs/hashjoin-dpe-design.md,已完成代码编写,但运行时 ORCA 优化器在处理包含分区表的 HashJoin 查询时崩溃(postgres backend SIGFAULT)。

复现命令: sql SET pg_orca.enable_orca = on; EXPLAIN SELECT * FROM prt1 JOIN t2 ON prt1.a = t2.a;


已完成的代码改动

文件 改动内容
libgpopt/src/xforms/CXformDynamicGet2DynamicTableScan.cpp Exfp()ExfpNone 改为 ExfpHigh,启用 xform
gpopt/config/CConfigParamMapping.cpp enable_dynamic_tablescanfalse 改为 true
libgpopt/src/operators/CPhysicalHashJoin.cpp 实现 PppsRequiredForJoins() DPE 逻辑:对 outer 的 partition consumer + join 谓词 → 在 inner child 上 require EpptPropagator
gpopt/translate/CTranslatorDXLToPlStmt.cpp TranslateDXLPartSelector non-Append 分支 → 生成 PartitionSelectorCS CustomScan;TranslateDXLDynTblScan → 生成 DynamicTableScanCS CustomScan
compat/executor/dyn_scan.c / .h 两个 CustomScan 节点的完整执行逻辑(新增文件)
pg_orca.cpp _PG_init 中调用 RegisterDynScanCustomScanMethods()
CMakeLists.txt 添加 compat/executor/dyn_scan.c

临时 debug 代码(待清理)

以下文件含有 fopen("/tmp/dpe_debug.log", ...) 的 fprintf 调试代码,修复后需删除: - libgpopt/src/engine/CEngine.cpp — Optimize() / RecursiveOptimize() 入口 - libgpopt/src/search/CJobGroupImplementation.cpp — FScheduleGroupExpressions() - libgpopt/src/search/CJobGroupExpressionImplementation.cpp — ScheduleApplicableTransformations() - pg_orca.cpp — _PG_init()


已排除的问题

1. Xform 未触发 ✅ 已排除

最初怀疑 CXformDynamicGet2DynamicTableScan 被 search stage 过滤掉。通过 debug trace 证明 xform 正常触发

op_eopid=38 candidates=3 after_impl=2 stage_size=150 after_stage=2
  • eopid=38 = CLogicalDynamicGet
  • candidates=3 表示 PxfsCandidates() 返回 3 个候选 xform
  • after_impl=2 表示经过 ∩ PxfsImplementation() 后剩 2 个(ExfDynamicGet2DynamicTableScanExfDynamicGet2AppendTableScan
  • after_stage=2 表示 ∩ PxfsCurrentStage() 没有额外过滤

2. Search stage 过滤实现 xform ✅ 已排除

PdrgpssDefault() 创建的默认 search stage 使用 PxfsExploration(),其中包含 ~150 个 xform(包括 implementation xform),stage_size=150 证实了这一点。

3. shared_preload_libraries 导致代码未更新 ✅ 已解决

postgresql.conf 中配置了 shared_preload_libraries = 'pg_orca',.so 在 server 启动时加载。后续 LOAD 'pg_orca' 是 no-op。必须 pg_ctl restart 才能加载新代码


当前问题:优化阶段崩溃

现象

debug log 输出以下内容后 backend 崩溃(server closed the connection unexpectedly):

[CEngine::Optimize] called
[FScheduleGroupExpressions] pgexpr=0x931793aa8
[FScheduleGroupExpressions] pgexpr=0x9317916b0
[FScheduleGroupExpressions] pgexpr=0x931792c38
op_eopid=38 candidates=3 after_impl=2 stage_size=150 after_stage=2   ← DynamicGet
[FScheduleGroupExpressions] pgexpr=0x9317aced0
[FScheduleGroupExpressions] pgexpr=0x931790678
op_eopid=0  candidates=1 after_impl=1 stage_size=150 after_stage=1   ← LogicalGet(t2)
[FScheduleGroupExpressions] pgexpr=0x931dae738
op_eopid=11 candidates=11 after_impl=1 stage_size=150 after_stage=1  ← InnerJoin
op_eopid=11 candidates=11 after_impl=1 stage_size=150 after_stage=1  ← InnerJoin(另一个)
op_eopid=12 candidates=6 after_impl=0 stage_size=150 after_stage=0   ← NAryJoin(正常,无实现xform)
[FScheduleGroupExpressions] pgexpr=0x931db29b0

注意:[CEngine::RecursiveOptimize] 的 trace 未出现,说明崩溃发生在 implementation 阶段完成之后、RecursiveOptimize 开始之前,即 CEngine::Implement() 的某个后续步骤中。

可能的崩溃原因

根据 ORCA 优化流程,implementation 之后的步骤是 property derivation / enforcement。崩溃最可能发生在以下位置:

假设 A:CPhysicalDynamicTableScan 的 property 方法

CPhysicalDynamicTableScan 是一个存在于 vendored ORCA 代码中的物理算子。它的 PppsDerive() 会产生 EpptConsumer,依赖上层 join 提供匹配的 EpptPropagator。可能的问题: - PppsDerive() 中引用了不存在的 partition info - 某些 property 方法(如 PcrsRequired, PosDerive)对 DynamicTableScan 有特殊处理,在单节点模式下可能触发未预期的代码路径

假设 B:CPhysicalHashJoin::PppsRequiredForJoins() 新代码

新增的 DPE propagation 逻辑可能有问题: - exprhdl.DerivePartitionInfo(0) 可能在某些 group expression 上返回 null - PexprExtractPredicatesOnPartKeys() 可能在某些谓词结构上崩溃 - pps_result->Insert() 的参数可能不正确(如 rel_mdid 的引用计数问题) - InsertAllowedConsumers() 的使用方式可能不正确

假设 C:AppendEnforcers / PartitionSelector 插入

ORCA 的 enforcer 机制在看到 inner child 需要 EpptPropagator 时,会尝试插入 CPhysicalPartitionSelector。这个过程中可能: - PartitionSelector 的构造依赖了某些在单节点模式下不存在的信息 - enforcer 和 property resolution 之间的交互导致无限递归或空指针

调试困难

  • 无 core dump:macOS 默认不生成 core dump(/cores/ 为空)
  • LLDB 不可用:因为 OBJECT library 编译方式,LLDB 报大量 “debug map object file changed” 错误,无法有效设置断点
  • 无 PG log:logging_collector 关闭,stderr 输出未被捕获

下一步排查计划

1. 获取崩溃堆栈(最优先)

方案 A — 启用 core dump: ```bash ulimit -c unlimited sudo sysctl kern.corefile=/tmp/core.%P

重启 postgres,复现崩溃,用 lldb /path/to/postgres /tmp/core.XXX 分析


方案 B — 启用 PG logging 捕获 backtrace:

postgresql.conf

logging_collector = on log_directory = ‘/tmp/pg_log’ ```

方案 C — 在关键路径添加更多 fprintf trace 缩小范围: - CEngine::Implement() 结束后 - CJobGroupOptimization 中 - CPhysicalDynamicTableScan::PppsDerive() - CPhysicalHashJoin::PppsRequiredForJoins() 各分支

2. 修复崩溃后的验证

  1. EXPLAIN 输出应显示: HashJoin -> Custom Scan (DynamicTableScanCS) on prt1 -> Hash -> Custom Scan (PartitionSelectorCS) -> Seq Scan on t2

  2. EXPLAIN ANALYZE 应显示实际只扫描了匹配的分区

  3. 不含 join 的查询(SELECT * FROM prt1 WHERE a > 10)应 fallback 到 AppendTableScan(DTS 被 property enforcement 淘汰)

3. 清理

  • 删除所有 /tmp/dpe_debug.log 相关的 fprintf 调试代码
  • 确认 NLJ DPE 路径仍然工作正常

环境信息

  • PostgreSQL data dir: /tmp/pgd3
  • Port: 15434
  • shared_preload_libraries = 'pg_orca'(需 restart 加载新代码)
  • Build: cd build && ninja -j$(nproc) && ninja install && pg_ctl -D /tmp/pgd3 restart
  • psql: /Users/jianghua/code/postgresql/src/bin/psql/psql -p 15434 -d postgres