Aurora PostgreSQL 中并行查询的最佳实践 - Amazon Aurora

Aurora PostgreSQL 中并行查询的最佳实践

并行查询执行是 PostgreSQL 中的一项功能,支持将单个 SQL 查询分解为由多个后台工作进程同时处理的较小任务。PostgreSQL 可以将查询的某些部分(例如扫描、联接、聚合或排序)分配到多个 CPU 内核,而不是完全在单个后端进程中执行查询。领导进程协调此执行过程并从并行工作线程收集结果。

但是,对于大多数生产工作负载,尤其是高并发 OLTP 系统,我们建议禁用自动并行查询执行。虽然并行性可以加快对分析或报告工作负载中大型数据集的查询,但它会带来重大风险,在繁忙的生产环境中,这些风险往往大于收益。

并行执行还会带来大量开销。每个并行工作线程都是一个完整的 PostgreSQL 后端进程,它需要进程分叉(复制内存结构和初始化进程状态)和身份验证(占用 max_connections 限制中的连接槽)。每个工作线程还会消耗其自己的内存,包括用于排序和哈希操作的 work_mem,每个查询有多个工作线程,内存使用量会迅速成倍增加(例如,4 个工作线程 × 64 MB work_mem = 每个查询 256 MB)。因此,与单进程查询相比,并行查询消耗的系统资源可能要大得多。如果调整不当,它们可能会导致 CPU 饱和(多个工作线程使可用的处理能力不堪重负)、上下文切换增加(操作系统频繁在多个工作进程之间切换,增加开销并降低吞吐量)或连接耗尽(由于每个并行工作进程占用一个连接槽,因此包含 4 个工作线程的单个查询总共使用 5 个连接,1 个领导进程 + 4 个工作进程,这可能会在高并发下迅速耗尽连接池,从而防止新的客户端连接并导致应用程序故障)。在高并发工作负载下,多个查询可能会同时尝试并行执行,此时这些问题尤其严重。

PostgreSQL 根据成本估算来决定是否使用并行性。在某些情况下,如果并行计划看起来代价较低,即使在实践中并不理想,计划程序也可能会自动切换到并行计划。如果索引统计数据已过时,或者膨胀使顺序扫描看起来比索引查找更具吸引力,则可能会发生这种情况。由于这种行为,自动并行计划有时可能会导致查询性能或系统稳定性出现倒退。

要从 Aurora PostgreSQL 中的并行查询中获得最大优势,务必根据您的工作负载对这些并行查询进行测试和调优,监控系统影响,并禁用自动并行计划选择以支持查询级控制。

配置参数

PostgreSQL 使用多个参数来控制并行查询的行为和可用性。了解和调优这些参数对于实现可预测的性能至关重要:

参数 说明 默认值
max_parallel_workers 总共可以运行的后台工作进程的最大数量 GREATEST($DBInstanceVCPU/2,8)
max_parallel_workers_per_gather 每个查询计划节点的最大工作线程数(例如,每个 Gather 2
parallel_setup_cost 为启动并行查询基础设施而增加的计划程序成本 1000
parallel_tuple_cost 在并行模式下处理的每个元组的成本(影响计划程序决策) 0.1
force_parallel_mode 强制计划程序测试并行计划(offonregress off

重要注意事项

  • max_parallel_workers 控制并行工作线程的总池。如果设置得过低,则某些查询可能会回退到串行执行。

  • max_parallel_workers_per_gather 影响单个查询可以使用多少个工作线程。值较高会提高并发性,但也会增加资源使用量。

  • parallel_setup_costparallel_tuple_cost 影响计划程序的成本模型。降低这些值可以使并行计划更有可能被选中。

  • force_parallel_mode 对测试很有用,但除非必要,否则不应在生产环境中使用。

注意

max_parallel_workers 参数的默认值是使用公式 GREATEST($DBInstanceVCPU/2, 8) 根据实例大小动态计算得出的。这意味着,当您将 Aurora 实例扩展到更大的计算大小和更多 vCPU 时,可用并行工作线程的最大数量将自动增加。因此,以前以串行方式或有限并行性执行的查询可能会在纵向扩展操作后突然利用更多的并行工作线程,从而可能导致连接使用率、CPU 利用率和内存消耗量意外增加。重要的是要在发生任何计算扩展事件后监控并行查询行为,并在必要时调整 max_parallel_workers_per_gather 以保持可预测的资源使用量。

确定并行查询使用情况

查询可能会根据数据分布或统计数据转变为并行计划。例如:

SELECT count(*) FROM customers WHERE last_login < now() - interval '6 months';

此查询可能使用最近数据的索引,但切换为对历史数据进行并行顺序扫描。

您可以通过加载 auto_explain 模块来记录查询执行计划。要了解更多信息,请参阅 AWS 知识中心中的记录查询执行计划

您可以监控 Aurora PostgreSQL 数据库实例中的查询执行计划,以检测导致当前数据库负载的执行计划,并使用 aurora_compute_plan_id 参数跟踪执行计划随时间推移的性能统计数据。要了解更多信息,请参阅监控 Aurora PostgreSQL 的查询执行计划和峰值内存

您可以监控 CloudWatch 数据库洞察,以了解与并行查询相关的等待事件。要详细了解与并行查询相关的等待事件,请浏览 IPC:并行等待事件

从 PostgreSQL 版本 18 开始,您可以使用 pg_stat_databasepg_stat_statements 中的新列来监控并行工作线程活动:

  • parallel_workers_to_launch:计划启动的并行工作线程数量

  • parallel_workers_launched:实际启动的并行工作线程数量

这些指标有助于识别计划的并行性与实际并行性之间的差异,这可能指示资源约束或配置问题。使用以下查询来监控并行执行:

对于数据库级别的并行工作线程指标:

SELECT datname, parallel_workers_to_launch, parallel_workers_launched FROM pg_stat_database WHERE datname = current_database();

对于查询级别的并行工作线程指标

SELECT query, parallel_workers_to_launch, parallel_workers_launched FROM pg_stat_statements ORDER BY parallel_workers_launched;

如何控制并行性

有多种方法可以控制查询并行性,每种方法均针对不同的场景和需求而设计。

要全局禁用自动并行性,请修改参数组来设置:

max_parallel_workers_per_gather = 0;

对于用户特定的永久设置,ALTER ROLE 命令提供了一种设置参数的方法,这些参数将应用于特定用户的所有将来会话。

例如:

ALTER ROLE username SET max_parallel_workers_per_gather = 4; 可确保每次该用户连接到数据库时,其会话都将在需要时使用此并行工作线程设置。

可以使用 SET 命令来实现会话级控制,该命令可在当前数据库会话的持续时间内修改参数。当您需要在不影响其他用户或将来会话的情况下临时调整设置时,这特别有用。设置后,这些参数将保持有效,直到显式重置或会话结束。这些命令很简单:

SET max_parallel_workers_per_gather = 4; -- Run your queries RESET max_parallel_workers_per_gather;

为了实现更精细的控制,SET LOCAL 支持您修改单个事务的参数。当您需要调整事务中一组特定查询的设置时,这是理想之选,之后设置会自动恢复到以前的值。这种方法有助于防止对同一会话中的其它操作产生意外影响。

利用查询计划管理(QPM)

在 Aurora PostgreSQL 中,查询计划管理(QPM)功能旨在确保计划的适应性和稳定性,而无论数据库环境如何变化(此类变化可能导致查询计划回归)。有关更多信息,请参阅 Aurora PostgreSQL 查询计划管理概览。QPM 会对优化程序进行一定程度的控制。在 QPM 中查看已批准的计划,以确保它们与当前的并行性设置保持一致。更新或移除过时的计划,这些计划可能会强制执行不够理想的并行执行。

也可以使用 pg_hint_plan 修复计划。有关更多信息,请参阅使用 pg_hint_plan 修复计划。可以使用名为 Parallel 的提示来强制并行执行。有关更多信息,请参阅 Hints for parallel plans

诊断并行查询行为

使用 EXPLAIN (ANALYZE, VERBOSE) 来确认查询是否使用了并行执行:

  • 查找诸如 GatherGather MergeParallel Seq Scan 之类的节点。

  • 比较有并行性和无并行性的计划。

要暂时禁用并行性以进行比较:

SET max_parallel_workers_per_gather = 0; EXPLAIN ANALYZE <your_query>; RESET max_parallel_workers_per_gather;