timescaledb hook

发布时间:2026/7/8 4:24:57
timescaledb hook post_analyze_hook的作用post_analyze_hook是 TimescaleDBloader模块中的一个静态函数定义在 loader.c:356其核心作用是作为延迟加载lazy loading机制的入口点。整体架构背景TimescaleDB 分为两层loader— 通过shared_preload_libraries在 PostgreSQL 启动时预加载loader.cversioned extension— 实际的 TimescaleDB 功能安装在每个数据库中的扩展因为 loader 在服务器启动时、分配数据库之前就被加载了此时无法查询扩展是否存在所以不能直接加载完整的扩展。于是采取了懒加载策略。具体工作流程loader.c:37-71阶段 1 — 注册钩子_PG_init第 535 行post_parse_analyze_hook post_analyze_hook;将post_analyze_hook注册为 PostgreSQL 的post_parse_analyze_hook。这是 PostgreSQL 定义的一个钩子每条 SQL 语句被解析后都会调用它。阶段 2 — 懒加载hook 被调用时当用户第一次执行 SQL 语句时post_analyze_hook被触发检查当前数据库是否安装了 TimescaleDB 扩展通过extension_check()如果是调用do_load()加载对应版本的.so文件加载后捕获版本化扩展自己设置的post_parse_analyze_hook保存在extension_post_parse_analyze_hook将post_parse_analyze_hook恢复回loader 自己的post_analyze_hook这样后续的 SQL 语句仍然会先经过 loader通过call_extension_post_parse_analyze_hook()委托给版本化扩展的实际钩子做真正的处理阶段 3 — 钩子链loader 还保存了之前的钩子prev_post_parse_analyze_hook确保与其他也使用该钩子的扩展兼容。函数本身的功能post_analyze_hook自身从第 356 行开始主要处理实用命令Utility Command例如ALTER DATABASE SET TABLESPACE— 在执行前停止该数据库的后台工作进程使表空间移动成功其他需要拦截的 DDL 操作总结简而言之post_analyze_hook是 TimescaleDB loader 的钩子入口函数充当代理/转发器的角色——既负责在首次使用时延迟加载真正扩展也负责在每次 SQL 解析后将控制权转交给已加载的版本化扩展同时还要维护多个扩展之间的钩子链完整性。timescaledb_planner的作用timescaledb_planner是 TimescaleDB 注册到 PostgreSQLplanner_hook的钩子函数注册在 planner.c:1184用于拦截和修改查询计划过程使 TimescaleDB 能够对超表hypertable上的查询进行优化。工作流程static PlannedStmt * timescaledb_planner(Query *parse, int cursor_opts, ParamListInfo bound_params)它围绕 PostgreSQL 的标准规划器做三件事情1. 预处理查询第 288-289 行if (ts_extension_is_loaded()) preprocess_query((Node *) parse, parse);在规划之前对查询树进行预处理。preprocess_query函数会遍历查询树对涉及超表的查询进行转换例如将针对父表的查询重写为针对子表的查询插入必要的约束条件等。2. 调用原始规划链第 291-296 行if (prev_planner_hook ! NULL) stmt (prev_planner_hook)(parse, cursor_opts, bound_params); else stmt standard_planner(parse, cursor_opts, bound_params);维护规划器钩子链如果其他扩展也注册了规划器钩子则先调用它们的钩子否则调用 PostgreSQL 标准的standard_planner。这确保了与其他扩展的兼容性。3. 后处理修复第 298-316 行if (ts_extension_is_loaded()) { ts_hypertable_insert_fixup_tlist(stmt-planTree); foreach (lc, stmt-subplans) { Plan *subplan (Plan *) lfirst(lc); ts_hypertable_insert_fixup_tlist(subplan); } }在标准规划完成后对生成的计划树进行后处理修复HypertableInsert的目标列表target listTimescaleDB 自定义的HypertableInsert计划节点包装了 PostgreSQL 的ModifyTable节点。ModifyTable的最终目标列表只有在set_plan_references()运行之后才能确定所以需要在这里做一次修复确保HypertableInsert节点的目标列表与内部的ModifyTable节点一致。4. 缓存管理第 284, 327 行planner_hcache_push(); // 规划前推送新的缓存栈帧 // ... planner_hcache_pop(true); // 规划后弹出并释放缓存使用PG_TRY/PG_CATCH确保异常情况下也能正确释放缓存。总结timescaledb_planner是 TimescaleDB查询优化扩展的核心入口。它在 PostgreSQL 的查询规划过程中插入了自己的逻辑规划前对超表查询进行改写规划后对包含超表插入的计划进行目标列表修复。通过这种方式TimescaleDB 能够在不修改 PostgreSQL 内核代码的前提下实现对分布式超表查询的透明优化timescaledb_set_rel_pathlist的作用timescaledb_set_rel_pathlist是 TimescaleDB 注册到 PostgreSQLset_rel_pathlist_hook的钩子函数注册在 planner.c:1186其作用是在 PostgreSQL 规划器为每个关系表生成访问路径时进行干预为超表相关的关系添加自定义的优化路径。背景在 PostgreSQL 的查询规划过程中规划器会为每个基表relation生成一组可能的访问路径paths如顺序扫描、索引扫描等然后从中选择代价最低的。set_rel_pathlist_hook允许扩展在这个阶段插入自定义逻辑。函数流程planner.c:742-7871. 快速退出第 749-754 行if (!valid_hook_call() || !OidIsValid(rte-relid) || IS_DUMMY_REL(rel))如果不是有效的钩子调用、关系 OID 无效、或者是虚拟关系dummy rel则直接调用前一个钩子并返回。2. 分类关系第 756 行reltype classify_relation(root, rel, ht);判断当前关系是哪种类型超表TS_REL_HYPERTABLE、普通表、超表子表、chunk、chunk 子表等。3. 调用其他扩展的钩子第 765-766 行if (prev_set_rel_pathlist_hook ! NULL) (*prev_set_rel_pathlist_hook)(root, rel, rti, rte);维护钩子链确保与其他扩展兼容。4. 按关系类型处理第 768-786 行TS_REL_HYPERTABLE_CHILD— 直接跳过不需要特殊处理。TS_REL_CHUNK/TS_REL_CHUNK_CHILD— 对于 chunk 关系如果是UPDATE/DELETE 操作且涉及压缩 chunk调用ts_cm_functions-set_rel_pathlist_dml由压缩模块compression module为 DML 操作设置访问路径确保能正确修改压缩数据。其他情况包括非 DML 或未压缩则向下执行apply_optimizations。apply_optimizations第 784 行— 默认处理分支对超表、chunk 等应用 TimescaleDB 的各种优化例如启用约束排除constraint exclusion根据查询条件排除不需要扫描的 chunk应用排序优化处理空间分区相关的优化其他针对分布式超表的时间/空间维度剪枝总结timescaledb_set_rel_pathlist是 TimescaleDB 查询优化的关键细粒度钩子。与timescaledb_planner在整体规划层面干预不同它深入到每个关系生成访问路径的环节让 TimescaleDB 能针对不同的关系类型超表、chunk、压缩表等注入专门的路径生成逻辑是实现 chunk 剪枝、压缩透明访问等核心功能的基石之一。timescaledb_get_relation_info_hook的作用timescaledb_get_relation_info_hook是 TimescaleDB 注册到 PostgreSQLget_relation_info_hook的钩子函数注册在 planner.c:1189。它的注释已经清楚地说明了意图planner.c:789-792这个钩子用于编辑editorialize规划器获取的关于关系的信息。我们用它来附加自己的元数据到超表和 chunk 关系的 RelOptInfo 上这些元数据在规划阶段是必需的。我们也在这里展开超表。具体工作流程get_relation_info_hook是 PostgreSQL 在规划器收集关系信息时调用的钩子timescaledb_get_relation_info_hook按关系类型做不同处理1. 调用前一个钩子第 799-800 行if (prev_get_relation_info_hook ! NULL) prev_get_relation_info_hook(root, relation_objectid, inhparent, rel);维护钩子链。2. 处理超表TS_REL_HYPERTABLE第 807-848 行做了两件事标记超表 RTE 用于自主展开PG12第 828-833 行if (!ts_guc_disable_optimizations ts_guc_enable_constraint_exclusion inhparent rte-ctename NULL !IS_UPDL_CMD(query) query-resultRelation 0 query-rowMarks NIL (rte-requiredPerms (ACL_UPDATE | ACL_DELETE)) 0) { rte_mark_for_expansion(rte); }在特定条件下优化未禁用、约束排除启用、非 UPDATE/DELETE 等将超表的 RTE 标记为需要展开。这样 TimescaleDB 后续可以自主控制超表展开为 chunk 集合而不是依赖 PostgreSQL 默认的继承展开逻辑。对于 UPDATE/DELETE 会特别避开因为 PG 对它们有特殊的二次规划流程。创建私有 RelOptInfo第 835 行ts_create_private_reloptinfo(rel);在RelOptInfo上挂载 TimescaleDB 私有的元数据用于后续规划阶段使用。时间桶注解PG12第 838 行ts_plan_expand_timebucket_annotate(root, rel);对时间桶表达式如time_bucket()进行预处理注解。PG11 及以下直接展开第 840-846 行ts_plan_expand_hypertable_chunks(ht, root, rel);PG11 及以下版本由于继承展开时机不同直接在钩子里展开超表的 chunk。3. 处理 ChunkTS_REL_CHUNK/TS_REL_CHUNK_CHILD第 850-874 行创建私有 RelOptInfo第 853 行。透明解压缩支持第 855-872 行if (ts_guc_enable_transparent_decompression TS_HYPERTABLE_HAS_COMPRESSION(ht)) { Chunk *chunk ts_chunk_get_by_relid(chunk_rte-relid, true); if (chunk-fd.compressed_chunk_id 0) { ts_get_private_reloptinfo(rel)-compressed true; rel-indexlist NIL; // 清空索引列表 } }对于已压缩的 chunk标记该关系为已压缩清空索引列表因为压缩 chunk 中的所有数据实际上存储在压缩文件中未压缩版本上的索引毫无意义清除索引列表可以避免规划器浪费大量时间来生成无用的 IndexPath显著提升规划性能总结timescaledb_get_relation_info_hook在规划器收集关系元数据的阶段介入为超表和 chunk 附加私有元数据控制超表展开策略并为压缩 chunk 优化访问路径。它是后续timescaledb_set_rel_pathlist能够做出正确优化决策的数据准备基础。timescale_create_upper_paths_hook的作用timescale_create_upper_paths_hook是 TimescaleDB 注册到 PostgreSQLcreate_upper_paths_hook的钩子函数注册在 planner.c:1192。它在规划器的上层路径生成阶段被调用用于处理和优化超表上的 INSERT 和聚合查询。函数流程planner.c:991-10451. 调用前一个钩子第 999-1000 / 1008-1009 行维护钩子链。2. 委托给压缩模块第 1015-1016 行if (ts_cm_functions-create_upper_paths_hook ! NULL) ts_cm_functions-create_upper_paths_hook(root, stage, input_rel, output_rel);将控制权转交给压缩模块的自定义钩子如果存在用于处理压缩相关的上层路径生成。3. 替换超表上的 INSERT 路径第 1020-1022 行if (output_rel-pathlist ! NIL) output_rel-pathlist replace_hypertable_insert_paths(root, output_rel-pathlist);这是最核心的功能之一。调用replace_hypertable_insert_paths对 INSERT 路径进行改造其工作原理在注释中详细说明planner.c:908-964原始计划[ ModifyTable ] → resultRelation ↑ Tuple [ subplan ]改造后的计划[ HypertableInsert ] ↑ [ ModifyTable ] → resultRelation ↑ Tuple ↑ [ ChunkDispatch ] ── 将 resultRelation 设置为匹配的 chunk 表 ↑ Tuple [ subplan ]关键点ModifyTablePath是 PostgreSQL 用于 INSERT/UPDATE/DELETE 的标准路径replace_hypertable_insert_paths检查每个ModifyTablePath的目标是否是超表如果是超表则调用ts_hypertable_insert_path_create将其包装为HypertableInsert路径节点在HypertableInsert内部插入一个ChunkDispatch节点它在执行时根据元组数据动态查找目标 chunk并设置正确的resultRelation实现将 INSERT 路由到正确的 chunk4. 处理部分聚合第 1023-1029 行if (parse-hasAggs stage UPPERREL_GROUP_AGG) { partials_found ts_plan_process_partialize_agg(root, input_rel, output_rel); }如果查询包含聚合函数且当前阶段是分组聚合阶段UPPERREL_GROUP_AGG则处理部分聚合partial aggregation支持在 chunk 级别先做部分聚合然后在上层合并结果。5. 添加 HashAgg 优化第 1037-1043 行if (stage UPPERREL_GROUP_AGG output_rel ! NULL) { if (!partials_found) ts_plan_add_hashagg(root, input_rel, output_rel); if (parse-hasAggs) ts_preprocess_first_last_aggregates(root, root-processed_tlist); }如果没有部分聚合被处理添加HashAgg路径作为额外的聚合选项为规划器提供更多选择预处理first()/last()聚合函数这些是 TimescaleDB 自定义的聚合需要特殊处理总结timescale_create_upper_paths_hook在 PostgreSQL 规划器的上层路径生成阶段介入主要做三件事功能说明INSERT 路由将超表上的 INSERT 计划改造为HypertableInsert → ChunkDispatch结构实现元组到正确 chunk 的透明路由部分聚合在分组聚合阶段处理 chunk 级别的部分聚合支持跨 chunk 的高效聚合聚合优化添加 HashAgg 路径选项处理自定义聚合函数如 first/last这是 TimescaleDB透明分布式写入和查询的关键基础设施之一让用户无需关心数据具体落在哪个 chunk 上系统自动处理路由。PostgreSQL 查询处理全流程SQL 语句 │ 1. Parser解析器──── 生成解析树parse tree │ 2. Analyzer分析器── 生成查询树Query tree │ └─ post_parse_analyze_hook ← ★ 第一个钩子 │ 3. Rewriter重写器── 应用规则重写 │ 4. Planner规划器── 生成执行计划PlannedStmt │ ├─ planner_hook ← ★ 第二个钩子整体规划入口 │ │ │ ├─ get_relation_info()每个关系 │ │ └─ get_relation_info_hook ← ★ 第三个钩子收集关系信息 │ │ │ ├─ set_rel_pathlist()每个关系 │ │ └─ set_rel_pathlist_hook ← ★ 第四个钩子生成访问路径 │ │ │ └─ create_upper_paths()上层路径 │ └─ create_upper_paths_hook ← ★ 第五个钩子聚合/排序/INSERT 改造 │ 5. Executor执行器── 执行计划关键理解第 2 步是为每个 SQL 语句调用一次第 4 步中get_relation_info_hook→set_rel_pathlist_hook→create_upper_paths_hook是为**每个关系(表)**调用的如果查询涉及多个表会重复执行多次。场景一SELECT 查询超表以SELECT * FROM conditions WHERE time 2020-01-01为例conditions是超表钩子调用链①post_parse_analyze_hook→post_analyze_hookloader.c:356时机SQL 语句语义分析完成后做了什么首次调用时extension_check()→do_load()→延迟加载真正的 TimescaleDB 扩展.so加载后捕获版本化扩展的钩子保存到extension_post_parse_analyze_hook然后调用版本化扩展的钩子即实际的post_analyze_hook在 init.c 中后续调用时直接call_extension_post_parse_analyze_hook()转交控制权本质这是 TimescaleDB懒加载机制的入口确保实际扩展只在需要时才加载。②planner_hook→timescaledb_plannerplanner.c:279时机SQL 进入规划器时最外层入口做了什么推入规划器缓存栈planner_hcache_push调用preprocess_query()— 遍历查询树识别涉及超表的范围表条目RTE如果启用了约束排除且不是 UPDATE/DELETE标记超表 RTE 用于展开rte_mark_for_expansion对时间分区等做预处理调用standard_planner()或前一个规划器钩子—真正进入 PostgreSQL 标准规划流程规划完成后修复HypertableInsert的目标列表③get_relation_info_hook→timescaledb_get_relation_info_hookplanner.c:793时机规划器为每个表收集基本信息时在set_rel_pathlist之前发生了什么— 对于超表PG12再次尝试标记 RTE 展开处理内联函数中未被 preprocess_query 捕获的情况创建私有 RelOptInfo— 挂载 TimescaleDB 自己的元数据结构PG12ts_plan_expand_timebucket_annotate()— 注解时间桶表达式PG11 及以下直接调用ts_plan_expand_hypertable_chunks()—展开超表为 chunk 集合对于chunk 且已压缩标记compressed true清空索引列表— 因为压缩 chunk 上的索引无用避免规划器浪费时间生成索引扫描路径④set_rel_pathlist_hook→timescaledb_set_rel_pathlistplanner.c:742时机规划器为每个表生成所有可能的访问路径时发生了什么根据关系类型做不同处理超表TS_REL_HYPERTABLE— 调用apply_optimizations()约束排除根据 WHERE 条件如time 2020-01-01排除不需要扫描的 chunk只保留相关 chunk排序优化、空间分区剪枝等已压缩的 chunkTS_REL_CHUNK— 如果是 UPDATE/DELETE 涉及压缩 chunk调用set_rel_pathlist_dml处理压缩数据的 DML 路径否则也通过apply_optimizations优化结果规划器只收到需要扫描的 chunk 的访问路径不需要的 chunk 被剪枝掉了。这就是 TimescaleDBchunk 剪枝约束排除的实现。⑤create_upper_paths_hook→timescale_create_upper_paths_hookplanner.c:991时机规划器生成上层路径聚合、排序、分组等时发生了什么SELECT 查询如果查询有聚合GROUP BY time_bucket(...)调用ts_plan_process_partialize_agg()— 处理部分聚合在每个 chunk 上先做部分聚合再合并结果调用ts_plan_add_hashagg()— 添加HashAgg路径作为候选预处理first()/last()自定义聚合场景二INSERT 插入超表以INSERT INTO conditions VALUES (2020-01-01, 25.0, ...)为例①-③ 与 SELECT 完全一样post_parse_analyze_hook— 确保扩展已加载timescaledb_planner—preprocess_query预处理get_relation_info_hook— 附加元数据④set_rel_pathlist_hook有所不同对于 INSERT超表不会被标记展开因为有IS_UPDL_CMD检查chunk 路径统一由apply_optimizations处理。⑤create_upper_paths_hook—重点在此这是 INSERT 跟 SELECT最不同的地方if (output_rel-pathlist ! NIL) output_rel-pathlist replace_hypertable_insert_paths(root, output_rel-pathlist);replace_hypertable_insert_paths()遍历所有路径遇到ModifyTablePathINSERT 用且目标为超表时标准 PostgreSQL INSERT 计划[ ModifyTable ] ──→ resultRelation conditions (超表) ↑ [ ValuesScan ]改造后的计划[ HypertableInsert ] ↑ [ ModifyTable ] ──→ resultRelation conditions (超表) ↑ ↗ [ ChunkDispatch ] ─ 根据元组时间戳动态将 resultRelation 设置对正确的 chunk ↑ [ ValuesScan ]这就是 TimescaleDBINSERT 路由的机制 — 用户 INSERT 到超表系统自动将数据分发到正确的 chunk。完整调用顺序总结表顺序钩子函数阶段SELECT 作用INSERT 作用1post_parse_analyze_hookpost_analyze_hook分析后懒加载扩展确保就绪同上2planner_hooktimescaledb_planner规划入口preprocess_query → 标记展开preprocess_query3get_relation_info_hooktimescaledb_get_relation_info_hook收集信息创建私有元数据、标记压缩、展开超表同上但不标记展开4set_rel_pathlist_hooktimescaledb_set_rel_pathlist生成路径约束排除、剪枝 chunkapply_optimizations5create_upper_paths_hooktimescale_create_upper_paths_hook上层路径部分聚合、HashAgg、first/lastINSERT 路径改造插入 ChunkDispatch一句话概括扩展懒加载 → 查询预处理 → 关系元数据准备 → 约束排除/路径生成 → 上层路径优化聚合改造或 INSERT 路由每个钩子在前一个的基础上深化处理层层递进最终由规划器选择代价最低的执行计划。