timescaledb RegisterCustomScanMethods
2026/7/8 3:20:28 网站建设 项目流程

chunk_append_plan_methods的注册与调用链条

注册:在set_rel_pathlist_hook之前完成

_PG_init() [init.c:80] └─ _planner_init() [init.c:80] ← 注册所有 planner 钩子 ├─ planner_hook = timescaledb_planner ├─ set_rel_pathlist_hook = timescaledb_set_rel_pathlist ├─ get_relation_info_hook = timescaledb_get_relation_info_hook └─ create_upper_paths_hook = timescale_create_upper_paths_hook └─ _chunk_append_init() [init.c:82] ← 独立于钩子,在扩展初始化时 └─ RegisterCustomScanMethods(&chunk_append_plan_methods) [planner.c:50]

这一步不是任何钩子触发的,而是 TimescaleDB 扩展加载时(_PG_init)直接注册的。RegisterCustomScanMethods告诉 PostgreSQL 系统:有一个名为"ChunkAppend"的自定义扫描类型存在,PostgreSQL 在序列化/反序列化计划时可以识别它。


调用:两个阶段,通过两层 methods 串联

阶段 1 — 计划生成阶段(Creating Plan)
用户执行 SELECT 查询超表 │ 1. post_parse_analyze_hook ─── 确保扩展已加载 2. planner_hook ─── preprocess_query 预处理 3. get_relation_info_hook ─── 附加元数据,展开超表 4. set_rel_pathlist_hook ─── ★ 关键入口 │ └─ timescaledb_set_rel_pathlist() [planner.c:742] └─ apply_optimizations() [planner.c:638] │ └─ 对超表,遍历 rel->pathlist: 遇到 AppendPath / MergeAppendPath └─ ts_chunk_append_path_create() [chunk_append.c:44] │ ├─ 创建 ChunkAppendPath(CustomPath) └─ 设置 cpath.methods = &chunk_append_path_methods [chunk_append.c:72-73]
// chunk_append_path_methods 定义在 chunk_append.c:32 static CustomPathMethods chunk_append_path_methods = { .CustomName = "ChunkAppend", .PlanCustomPath = ts_chunk_append_plan_create, // ← 回调 };

PostgreSQL 的标准规划流程会调用PlanCustomPath回调来将CustomPath转换为Plan节点:

│ 5. PostgreSQL planner → PlanCustomPath │ └─ ts_chunk_append_plan_create() [planner.c:89] │ ├─ 创建 CustomScan 节点 └─ 设置 cscan->methods = &chunk_append_plan_methods [planner.c:103]
// chunk_append_plan_methods 定义在 planner.c:42 static CustomScanMethods chunk_append_plan_methods = { .CustomName = "ChunkAppend", .CreateCustomScanState = ts_chunk_append_state_create, // ← 执行时回调 };

同时create_upper_paths_hook还会做后处理(修复HypertableInsert的目标列表等)。

阶段 2 — 执行阶段(Executing Plan)
PostgreSQL Executor │ └─ CreateCustomScanState │ └─ ts_chunk_append_state_create(chunk_append/exec.c) │ └─ 创建 ChunkAppendState 执行状态 负责在运行时:按顺序或并行方式扫描多个 chunk

完整调用链一览

注册阶段(扩展加载时): _PG_init └─ _chunk_append_init() → RegisterCustomScanMethods(&chunk_append_plan_methods) 计划阶段(set_rel_pathlist_hook 内): timescaledb_set_rel_pathlist ← set_rel_pathlist_hook └─ apply_optimizations() └─ ts_chunk_append_path_create() └─ 创建 CustomPath + chunk_append_path_methods PostgreSQL 标准规划器: └─ PlanCustomPath(回调) └─ ts_chunk_append_plan_create() └─ 创建 CustomScan + chunk_append_plan_methods 执行阶段(executor 启动): ExecInitCustomScan └─ CreateCustomScanState(回调) └─ ts_chunk_append_state_create()

关键技术点

层级数据结构methods 字段回调函数
PathChunkAppendPathcpath.methodsts_chunk_append_plan_create(Path→Plan 转换)
PlanCustomScanmethodsts_chunk_append_state_create(Plan→State 执行)

双层 methods 设计是 PostgreSQLCustomScan接口的标准模式:第一层把自定义路径转为计划节点,第二层把计划节点转为执行状态。chunk_append_plan_methods就是第二层的那个。它本身不通过任何 hook 调用,而是通过set_rel_pathlist_hook生成的ChunkAppendPath一路携带,最终由 PostgreSQL 执行器框架回调触发的。

chunkdispatch为什么不调用RegisterCustomScanMethods函数呢?

关键区别在于ChunkAppend 用于 SELECT 查询(可能会参与并行查询),而ChunkDispatch 用于 INSERT(在 ModifyTable 内部,永远不会被序列化)

RegisterCustomScanMethods的作用

RegisterCustomScanMethodsCustomScanMethods注册到 PostgreSQL 的全局哈希表中,这样当 plan 被序列化再反序列化时(比如传给并行 worker,或者缓存 prepared statement 的 plan),PostgreSQL 可以根据CustomName字符串重建methods指针。

直接赋值cscan->methods = &xxx只在同一进程内、不跨越序列化边界时有效。

为什么 ChunkAppend 需要,ChunkDispatch 不需要

ChunkAppend(chunk_append/planner.c:50):

  • 用于SELECT查询
  • 在 planner.c:706-717 可以看到,ChunkAppend也会出现在partial_pathlist(并行查询路径)中:
    foreach (lc, rel->partial_pathlist) { ... *pathptr = ts_chunk_append_path_create(root, rel, ht, *pathptr, true, false, NIL);
    这里的第4个参数true表示is_partial,说明 ChunkAppend 可以用于并行查询
  • 并行查询需要把 plan序列化后发给 worker 进程,worker 端需要反序列化重建 CustomScan
  • 如果不注册,worker 收到 "ChunkAppend" 的 CustomScan 时找不到对应的methods,会报错

ChunkDispatch(chunk_dispatch_plan.c):

  • 用于INSERT查询,嵌入在ModifyTable内部
  • PostgreSQL 的ModifyTable执行是单进程的,不参与并行查询
  • INSERT 的 plan 不会被序列化传给其他进程
  • plan 在同一个 backend 里完成 planning → execution,methods指针一直有效
  • 所以光靠cscan->methods = &chunk_dispatch_plan_methods;就够了

同理

HypertableInsert(同属 INSERT 路径)也没有调用RegisterCustomScanMethods,原因完全一样。

总结

CustomScan用于并行查询?需要序列化?RegisterCustomScanMethods?
ChunkAppendSELECT✅ 可能(partial pathlist)✅ 需要传递给 worker调用
ChunkDispatchINSERT (ModifyTable)❌ 不支持❌ 在同一进程内不调用
HypertableInsertINSERT (ModifyTable)❌ 不支持❌ 在同一进程内不调用

本质上就是:只在需要跨进程传递 plan(并行查询 / prepared statement plan 缓存)时,才需要注册。INSERT 路径上的 CustomScan 一直留在同一个进程里,直接赋值指针就够了。

注册的核心是CreateCustomScanState(plan → state,用于反序列化之后的 executor 重建),不是PlanCustomPath(path → plan)。

CustomPathMethodsvsCustomScanMethods

这两个是不同阶段、不同生命周期的东西:

阶段结构体函数指针注册?
planning: path → planCustomPathMethodsPlanCustomPath❌ 不用注册,随CustomPath指针传递,规划在同一进程内完成
execution: plan → stateCustomScanMethodsCreateCustomScanState✅ 需要RegisterCustomScanMethods,因为 plan 可能被序列化再反序列化

ChunkDispatch 在 chunk_dispatch_plan.c:82-85 只定义了CustomPathMethods

static CustomPathMethods chunk_dispatch_path_methods = { .CustomName = "ChunkDispatchPath", .PlanCustomPath = chunk_dispatch_plan_create, // ← path → plan };

而在 chunk_dispatch_plan.c:33-36 定义了CustomScanMethods

static CustomScanMethods chunk_dispatch_plan_methods = { .CustomName = "ChunkDispatch", .CreateCustomScanState = create_chunk_dispatch_state, // ← plan → state };

但这两个都不应该被等价对待—— 关键问题:为什么 ChunkDispatch 不需要RegisterCustomScanMethods

前面说"因为不支持并行"——这个答案依然成立,但更精确的表述是:

RegisterCustomScanMethods解决的场景

PostgreSQL 的_readCustomScannodeRead反序列化)在读到custom_privatecustom_name之后,会调用FindCustomScanMethods(name)来查找注册过的CustomScanMethods。也就是说,只要这个 plan 节点有可能被序列化→反序列化,就必须注册CreateCustomScanState,否则重建 executor state 时会找不到方法。

哪些场景会序列化 plan?

  1. 并行查询— 主进程_writeCustomScan→ 文本 → worker_readCustomScan→ 需要找到CreateCustomScanState
  2. EXPLAIN (FORMAT JSON)等输出格式 — 这些只_write_read,不需要注册
  3. plan 日志 /debug_print_plan— 只写不读,也不需要
  4. prepared statement 跨 session— 实际上 PG 的 CachedPlan 是纯内存的,不跨进程序列化

总结

注册的是CreateCustomScanState(反序列化后重建执行状态),不是PlanCustomPath(路径转计划阶段)。

ChunkAppend 注册是因为它可能出现在partial_pathlist中,plan 会被发给并行 worker 反序列化执行。ChunkDispatch/HypertableInsert 的 plan 只在主进程的 ModifyTable 内部使用,永远不会被序列化传给其他进程,所以cscan->methods = &chunk_dispatch_plan_methods直接赋值指针就够了,不需要全局注册。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询