TimescaleDB hook

post_analyze_hook的作用

post_analyze_hook 是 TimescaleDB loader 模块中的一个静态函数(定义在 loader.c:356),其核心作用是作为 延迟加载(lazy loading)机制 的入口点。

整体架构背景

TimescaleDB 分为两层:

  1. loader — 通过 shared_preload_libraries 在 PostgreSQL 启动时预加载(loader.c
  2. versioned 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 被触发:

  1. 检查当前数据库是否安装了 TimescaleDB 扩展(通过 extension_check()
  2. 如果是,调用 do_load() 加载对应版本的 .so 文件
  3. 加载,捕获版本化扩展自己设置的 post_parse_analyze_hook(保存在 extension_post_parse_analyze_hook
  4. 将 post_parse_analyze_hook 恢复回 loader 自己的 post_analyze_hook,这样后续的 SQL 语句仍然会先经过 loader
  5. 通过 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 注册到 PostgreSQL planner_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 list):TimescaleDB 自定义的 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 注册到 PostgreSQL set_rel_pathlist_hook 的钩子函数(注册在 planner.c:1186),其作用是在 PostgreSQL 规划器为每个关系(表)生成访问路径时进行干预,为超表相关的关系添加自定义的优化路径。

背景

在 PostgreSQL 的查询规划过程中,规划器会为每个基表(relation)生成一组可能的访问路径(paths),如顺序扫描、索引扫描等,然后从中选择代价最低的。set_rel_pathlist_hook 允许扩展在这个阶段插入自定义逻辑。

函数流程(planner.c:742-787

1. 快速退出(第 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 注册到 PostgreSQL get_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. 处理 Chunk(TS_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 注册到 PostgreSQL create_upper_paths_hook 的钩子函数(注册在 planner.c:1192)。它在规划器的上层路径生成阶段被调用,用于处理和优化超表上的 INSERT 和聚合查询。

函数流程(planner.c:991-1045

1. 调用前一个钩子(第 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 路由到正确的 chunk
4. 处理部分聚合(第 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_hookset_rel_pathlist_hookcreate_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 进入规划器时(最外层入口)

做了什么

  1. 推入规划器缓存栈(planner_hcache_push
  2. 调用 preprocess_query() — 遍历查询树:
    • 识别涉及超表的范围表条目(RTE)
    • 如果启用了约束排除且不是 UPDATE/DELETE,标记超表 RTE 用于展开rte_mark_for_expansion
    • 对时间分区等做预处理
  3. 调用 standard_planner()(或前一个规划器钩子)— 真正进入 PostgreSQL 标准规划流程
  4. 规划完成后,修复 HypertableInsert 的目标列表
③ get_relation_info_hook → timescaledb_get_relation_info_hookplanner.c:793

时机:规划器为每个表收集基本信息时(在 set_rel_pathlist 之前)

发生了什么 — 对于超表:

  1. PG12+:再次尝试标记 RTE 展开(处理内联函数中未被 preprocess_query 捕获的情况)
  2. 创建私有 RelOptInfo — 挂载 TimescaleDB 自己的元数据结构
  3. PG12+ts_plan_expand_timebucket_annotate() — 注解时间桶表达式
  4. PG11 及以下:直接调用 ts_plan_expand_hypertable_chunks() — 展开超表为 chunk 集合

对于 chunk 且已压缩

  1. 标记 compressed = true
  2. 清空索引列表 — 因为压缩 chunk 上的索引无用,避免规划器浪费时间生成索引扫描路径
④ set_rel_pathlist_hook → timescaledb_set_rel_pathlistplanner.c:742

时机:规划器为每个表生成所有可能的访问路径时

发生了什么

根据关系类型做不同处理:

  1. 超表(TS_REL_HYPERTABLE — 调用 apply_optimizations()

    • 约束排除:根据 WHERE 条件(如 time > '2020-01-01')排除不需要扫描的 chunk,只保留相关 chunk
    • 排序优化、空间分区剪枝等
  2. 已压缩的 chunk(TS_REL_CHUNK — 如果是 UPDATE/DELETE 涉及压缩 chunk:

    • 调用 set_rel_pathlist_dml 处理压缩数据的 DML 路径
    • 否则也通过 apply_optimizations 优化

结果:规划器只收到需要扫描的 chunk 的访问路径,不需要的 chunk 被剪枝掉了。这就是 TimescaleDB chunk 剪枝(约束排除) 的实现。

⑤ create_upper_paths_hook → timescale_create_upper_paths_hookplanner.c:991

时机:规划器生成上层路径(聚合、排序、分组等)时

发生了什么(SELECT 查询):

  • 如果查询有聚合(GROUP BY time_bucket(...)):
    1. 调用 ts_plan_process_partialize_agg() — 处理部分聚合,在每个 chunk 上先做部分聚合,再合并结果
    2. 调用 ts_plan_add_hashagg() — 添加 HashAgg 路径作为候选
    3. 预处理 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() 遍历所有路径,遇到 ModifyTablePath(INSERT 用)且目标为超表时:

标准 PostgreSQL INSERT 计划:

[ ModifyTable ] ──→ resultRelation = conditions (超表)
      ↑
 [ ValuesScan ]

改造后的计划:

[ HypertableInsert ]
      ↑
[ ModifyTable ] ──→ resultRelation = conditions (超表)
      ↑           ↗
[ ChunkDispatch ] ─ 根据元组时间戳,动态将 resultRelation 设置对正确的 chunk
      ↑
 [ ValuesScan ]

这就是 TimescaleDB INSERT 路由的机制 — 用户 INSERT 到超表,系统自动将数据分发到正确的 chunk。


完整调用顺序总结表

顺序钩子函数阶段SELECT 作用INSERT 作用
1post_parse_analyze_hookpost_analyze_hook分析后懒加载扩展,确保就绪同上
2planner_hooktimescaledb_planner规划入口preprocess_query → 标记展开preprocess_query
3get_relation_info_hooktimescaledb_get_relation_info_hook收集信息创建私有元数据、标记压缩、展开超表同上(但不标记展开)
4set_rel_pathlist_hooktimescaledb_set_rel_pathlist生成路径约束排除、剪枝 chunkapply_optimizations
5create_upper_paths_hooktimescale_create_upper_paths_hook上层路径部分聚合、HashAgg、first/lastINSERT 路径改造(插入 ChunkDispatch)

一句话概括

扩展懒加载 → 查询预处理 → 关系元数据准备 → 约束排除/路径生成 → 上层路径优化(聚合改造或 INSERT 路由)

每个钩子在前一个的基础上深化处理,层层递进,最终由规划器选择代价最低的执行计划。

内容概要:本文系统研究了构网型变流器的正负序阻抗解耦特性及其在弱电网环境下的稳定性表现,重点依托Matlab/Simulink仿真平台,构建了详细的阻抗数学模型,设计了解耦控制策略,并采用小信号扫频法进行频域辨识与稳定性验证。研究深入探讨了构网型变流器与传统跟网型逆变器在正负序阻抗特性上的本质差异,结合虚拟同步发电机(VSG)等先进控制技术,分析其在抑制宽频带振荡、削弱锁相环动态耦合等方面的优越性。文中不仅提供了完整的仿真模型与MATLAB代码实现,还整合了光伏、风电、储能、微电网等多类新能源系统的阻抗建模与稳定性分析资源,形成了一套面向新型电力系统稳定性的综合性技术资料体系,具有较强的科研复现与工程参考价值。; 适合人群:面向具备电力电子、电力系统自动化、新能源并网等专业背景的研究生、高校教师及工程技术人员,特别适用于从事阻抗建模、小干扰稳定性分析、宽频振荡机理研究以及撰写高水平学术论文的科研工作者。; 使用场景及目标:①掌握构网型变流器正负序阻抗建模与扫频辨识的仿真方法;②深入理解VSG等构网型控制在弱电网中提升稳定性的内在机理;③复现顶刊论文中的阻抗分析流程与稳定性判据应用;④利用提供的成熟模型与代码加速科研进程,支撑课题研究与学术成果产出。; 阅读建议:建议结合文中提供的Simulink模型与MATLAB代码,按照“理论建模—仿真搭建—扫频激励—频响提取—Nyquist判据分析”的完整流程进行实践操作,重点关注扫频信号的注入方式、频率范围设置及阻抗曲线的物理意义解读,并参考博士论文复现案例深化对复杂动态耦合问题的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值