如何在 YSQL 中对时态连接使用并行索引扫描
摘要: 本文探讨了 YSQL 现在如何为时态连接(temporal join)模式提供原生 PostgreSQL 并行查询(Parallel Query,PQ)支持,从而无需再使用分桶索引和查询变通方案。通过对时态表使用并行索引扫描(Parallel Index Scan)和 YB 批量嵌套循环连接(YB Batched Nested Loop Join),查询可以实现 50-55% 的延迟改善,同时保持原始 schema 和 SQL 不变。
在本博客中,我们探讨了分析领域中一种常见的时态连接模式过去在分布式 Postgres 兼容系统中如何需要额外调整,以及现在通过全面支持原生 PostgreSQL 并行查询(PQ)后,这已不再必要。
团队过去常常需要为并行追加(Parallel Append)实现分桶索引和 UNION ALL 视图。借助当前的 YSQL 优化器功能,同样的模式现在可以通过时态表上的并行索引扫描和进入主键(PK)查找的 YB 批量嵌套循环连接(使用 PostgreSQL 索引和原生 SQL)来运行。
并行索引扫描相比串行执行显示出性能提升,但 YugabyteDB 已经提供了 tablet 级别的并行性,这为非 PostgreSQL PQ 执行增加了并行能力。这意味着它实际上提供了双重并行性。
变通方案
在这一改进路径出现之前,常见的变通方案是混合使用分桶索引和查询重写,以匹配这些结构并启用并行追加。这种方法效果很好,但增加了 schema 和查询更改的复杂性。
有什么变化?
YSQL 优化器的改进(在 2025.2.2 版本中)现在允许此查询模式进行并行执行,最高可达工作进程上限。
当索引的前导列与范围谓词对齐时(例如,用于时间窗口访问的 tt_to ASC),规划器可以选择:
- Gather/Gather Merge
- 启动工作进程
- 时态索引上的并行索引扫描
如何设置 GUC 以使用此功能
这些功能可以通过 GUC(Grand Unified Configuration,统一配置参数)启用,此处我们通过将以下 GUC 设置为 ON 来执行数据库级别的功能启用:
请注意,我们计划将来将这些参数默认设置为 'on',因此未来无需配置更改即可启用此功能。
ALTER DATABASE yugabyte SET yb_enable_cbo = on;
ALTER DATABASE yugabyte SET yb_enable_parallel_scan_colocated = on;
ALTER DATABASE yugabyte SET yb_enable_parallel_scan_range_sharded = on;
ALTER DATABASE yugabyte SET yb_enable_parallel_scan_hash_sharded = on;
ALTER DATABASE yugabyte SET yb_enable_parallel_append = on;
测试路径中使用的会话级别 DOP(并行度):
SET max_parallel_workers_per_gather = 6;
其他所有设置保持默认值,包括 parallel_tuple_cost 和 parallel_setup_cost,因此规划器仍然假设相对较高的并行开销,除非工作负载足够大以证明其合理性,否则不会选择并行计划。
DDL 和查询形态
以下是我们用于演示 PQ 激活的匿名化 schema:
CREATE SCHEMA pq_anon_parallel_demo;
CREATE TABLE entity_payload (
version_ref BIGINT NOT NULL,
entity_type_id INT NOT NULL,
payload JSONB NOT NULL,
PRIMARY KEY ((version_ref) HASH)
);
CREATE TABLE entity_validity (
version_ref BIGINT NOT NULL,
entity_ref TEXT NOT NULL,
tt_from TIMESTAMPTZ NOT NULL,
tt_to TIMESTAMPTZ NOT NULL,
vt_from TIMESTAMPTZ NOT NULL,
vt_to TIMESTAMPTZ NOT NULL,
aux_metric INT NOT NULL,
PRIMARY KEY ((version_ref) HASH)
);
CREATE INDEX idx_entity_validity_tt_to_asc_vkey
ON entity_validity (tt_to ASC, version_ref ASC);
使用的查询:
SELECT count(*)
FROM entity_validity v
JOIN entity_payload p ON p.version_ref = v.version_ref
WHERE v.tt_to > timestamptz '2025-11-17 06:06:09.391+00'
AND v.tt_to <= timestamptz '2025-11-17 06:06:09.391+00' + interval '180 day'
AND v.vt_to > timestamptz '2025-11-17 06:06:09.391+00'
AND v.tt_from <= timestamptz '2025-11-17 06:06:09.391+00'
AND v.vt_from <= timestamptz '2025-11-17 06:06:09.391+00'
AND ((p.payload->>'hasNonFlatPosition')::boolean = true);
如何验证 PQ 是否正常工作
使用:
EXPLAIN (ANALYZE, DIST, COSTS OFF)
...query...
需要查找的执行计划形态:
Finalize Aggregate
-> Gather
Workers Planned: 6
Workers Launched: 6
-> Partial Aggregate
-> YB Batched Nested Loop Join
-> Parallel Index Scan using idx_entity_validity_tt_to_asc_vkey on entity_validity v
-> Index Scan using entity_payload_pkey on entity_payload p
如果你看到该节点链,则说明此查询模式中的时态侧正在作为并行索引扫描运行,而不是并行顺序扫描(Parallel Seq Scan)。
并行索引扫描比非 PQ 计划快约 50-55%。随着符合条件的工作量增加,这些收益应该会进一步提高。
最大受益场景
最适合:
- 对范围索引列进行大时间窗口分析
- HTAP(混合事务和分析处理)风格的读取,需要实时数据且不能仅依赖预聚合
帮助较小:
- 微小范围
- 点查找
- CPU 饱和的系统,额外的工作进程会增加争用
结论
此时态连接模式允许你保留原始 schema 和查询,并让 YSQL 直接执行并行索引扫描。这实现了延迟收益,且随着符合条件的工作量增加,扩展性更好。
有关并行查询的更多信息,请查看此博客: 在 YugabyteDB 中交付 Postgres 并行查询