存储饱和时,其上方的所有内容都排入队列。 IOPS 指南将从指标图表上看起来几乎一模一样的四种原因区分开来:查询读取的数据量超过应有范围、检查点触发过于频繁、表膨胀使每次扫描的开销增大,以及服务器的需求已超出其预配的 IOPS。
只有最后一个问题可以通过多花钱来解决。
本指南介绍的症状
- 固定在 100% 附近的 IOPS 或磁盘带宽
- 磁盘队列深度持续偏高
- 写入负载下恶化的查询延迟
- 延迟峰值以固定间隔周期性出现
开始之前
启用查询存储运行时、PostgreSQL 服务器日志、会话数据和查询存储等待统计信息;设置为pg_qs.query_capture_modeTOP或pgms_wait_sampling.query_capture_modeALL设置为ALL;启用metrics.collector_database_activity;并设置track_io_timing = ON。
Important
如果没有 track_io_timing,blk_read_time 和 blk_write_time 将保持为零,并且 查询 选项卡无法按 I/O 对任何项目进行排序。 这种情况是导致该指南看起来有问题的最常见原因。
本指南的组织方式
| 选项卡 | 它所回答的问题 |
|---|---|
| 概述 | 存储是否真正饱和,以何种方式? |
| 工作量 | 工作量是否增加? |
| 会话 | 特定会话是否驱动 I/O? |
| 查询 | 哪些语句使用最多的 I/O 时间? |
| 等待 | 这是读取 I/O、写入 I/O,还是 WAL? |
| 检查点 | 服务器是否过于频繁地执行刷新操作? |
| 存储 | 更多预置 IOPS 会有帮助吗? |
演练:延迟每隔几分钟就会飙升
特征化饱和度。 概述选项卡显示,利用率接近 100%,队列深度以固定间隔出现峰值,而不是持续波动。 周期性是一个强有力的提示,因为稳定的工作负荷压力不会按计划到达。
排除工作负荷。工作负荷 显示写入活动提升但稳定,且没有匹配的周期。 某个因素正在将写入操作进行批处理,而不是写入操作本身呈突发式到达。
识别该机制。 “等待”选项卡显示,在峰值区间内占主导的是
IO:WALWrite和IO:DataFileWrite。 是写入侧,而非读取侧,这表明它指向的是刷新而不是查询。确认。 检查点选项卡显示,大多数检查点被归类为 请求触发,而不是 定时触发,每隔几分钟出现一次。
max_wal_size早在checkpoint_timeout到期之前就已被超出,因此服务器会不断执行检查点,并以代价高昂的突发方式将脏页刷盘。操作:在峰值期间测量实际的 WAL 生成量,将
max_wal_size调高到能够容纳该生成量,并将checkpoint_completion_target设置为0.9,这样刷新就会分散在整个间隔内,而不是以尖峰形式出现。 检查点将主要改为按时间触发,周期性延迟也随之消失。
如果等待发生在读侧,那么同样的排查过程就会继续进入 查询,并且最终很可能定位到膨胀或索引缺失问题。
选项卡参考
概述
四个指标,每个指标回答不同的内容:
| Metric | 它告诉你什么 |
|---|---|
| IOPS | 每秒读取和写入操作总数。 |
| 消耗的磁盘带宽 | 使用的吞吐量百分比。 在进行大块顺序读取时,您可能会在达到 IOPS 限制之前先达到这一指标。 |
| 磁盘 IO 消耗 | 正在使用的预配 IOPS 的百分比。 持续接近 100% 的数值意味着已达到饱和状态。 |
| 磁盘队列深度 | 等待中的请求。 持续偏高意味着磁盘跟不上。 此指标最直接解释延迟。 |
工作量
读取与写入元组活动。 随着 IOPS 的上升,工作量也会实实在在地增加。 在保持平稳而 IOPS 却上升时,意味着每次操作的成本都变得更高,最常见的原因是膨胀,这会迫使扫描为了读取相同的行而读取更多的页面。
Sessions
从采样数据 pg_stat_activity 中找出长时间运行的会话,以识别保持 I/O 密集型工作处于打开状态的 PID。
立即缓解。 请先取消当前正在运行的语句。 这是两个选项中干扰较小的一个,因为会话及其事务都得以保留:
SELECT pg_cancel_backend(<pid>);
如果会话处于事务空闲状态,或者取消操作也无法释放资源,请终止整个后端进程:
SELECT pg_terminate_backend(<pid>);
Caution
终止后端会回滚其未完成的事务。 在结束会话之前,请先确认该会话当前正在执行什么操作。 PID 可能属于长时间运行的迁移任务,或属于会从头开始重新启动的批处理作业。
设置防护措施,防止再次发生。 设置会话和事务可以运行多长时间的限制:
| 参数 | 它所限定的范围 | 备注 |
|---|---|---|
statement_timeout |
单个语句 | 最广泛的安全网。 如果某些作业确实会运行较长时间,应按角色或按会话进行设置,而不是在整个服务器范围内统一设置。 |
idle_in_transaction_session_timeout |
处于空闲状态且存在未提交事务的会话 | 默认值为 0 (从不)。 此设置通常可防止清理被阻止。
300000 (5 分钟)是一个常见的起点。 |
idle_session_timeout |
无事务的空闲会话 | PostgreSQL 14 及更高版本。 回收已废弃客户端占用的连接槽位。 |
transaction_timeout |
事务总持续时间 | PostgreSQL 17 及更高版本。 |
Queries
打开track_io_timing时,查询存储记录blk_read_time和blk_write_time。 此选项卡按总量对查询进行排名,并提供按分桶下钻的选项,以显示 I/O 时间、执行时间、读取和写入的数据量、行数和调用次数的平均值、最小值和最大值。
将 I/O 时间与返回行数进行比较。 具有大量 I/O 和几行的查询读取的数据远多于所需数据。 这种情况表明:缺少索引、谓词无法使用现有索引,或者表严重膨胀,以至于有用的行分散在许多页中。
- 运行 索引优化 ,根据实际工作负荷获取索引建议。 此步骤是最有效的长期修复。
- 对该查询运行
EXPLAIN (ANALYZE, BUFFERS),查看时间和 I/O 实际消耗在何处。 在大型表上查找顺序扫描、针对大行计数的嵌套循环,以及对溢出到磁盘的排序。 - 减小表臃肿。 作为一次性步骤,运行
VACUUM (ANALYZE, VERBOSE) <table_name>;。 如果臃肿问题反复出现,根源在上游。 请参阅 监控 autovacuum。 - 添加计划所需的索引,并删除不缩小结果的联接。
- 对持续热的大型表进行分区。
- 重新审视表设计:为子表中的外键创建索引,删除未使用的索引(它们会给每次写入带来开销),并在批量加载期间禁用触发器。
在断定这些查询应负全责之前,请查看 检查点 选项卡。检查点压力会抬高所有并发运行任务的 I/O 成本。
注释
在只读副本上运行 ,并在 VACUUM 上创建索引或分区;这些更改会被复制。 在副本上调优 work_mem,但将 maintenance_work_mem 更改保留在主实例上。 副本上的 WAL 写入 I/O 由主副本上的写入工作负荷驱动,而不是由本地运行的任何内容驱动。
等待
识别出等待磁盘时间最长的查询,依据是归因于查询 ID 的采样等待事件。
- 选择 前 N 个等待事件 以设置要分析的不同事件数。
- 选择 每个等待事件的热门查询。
- 选择查询行以检查其详细信息。
阅读主导事件时,可以了解要走的方向:
| 等待事件 | Meaning | 下一步要访问的位置 |
|---|---|---|
IO:DataFileRead |
从磁盘读取表或索引页 | 查询,用于检查缺失索引或导致页数增加的膨胀问题 |
IO:DataFileWrite |
写入表页或索引页 | 检查点和写入密集型查询 |
IO:WALWrite |
刷新预写日志 |
检查点,用于调整 max_wal_size 和 checkpoint_completion_target |
Checkpoints
检查点会将所有脏页刷写到磁盘,并将一条检查点记录写入 WAL 中。 每隔 checkpoint_timeout 秒开始一次,或在即将超过 max_wal_size 时开始,以先发生者为准。 “以先发生者为准”就是全部关键所在:
-
定时,由
checkpoint_timeout触发。 可预测和分散。 -
已请求,触发,这是因为
max_wal_size即将超出限制。 频繁、突发性强、昂贵。
目标是达到 90% 或更高的计时检查点,间隔尽量接近 checkpoint_timeout。 请求检查点的比例过高,意味着对于您的写入速率来说,max_wal_size 过小。
根据测量结果确定 max_wal_size 的尺寸,而不是靠猜测。 在高峰时段:
-- Run once and note the result.
SELECT pg_current_wal_lsn();
-- Wait checkpoint_timeout seconds, then run again.
SELECT pg_current_wal_lsn();
-- Compute WAL generated over that interval, in GB.
SELECT round(
pg_wal_lsn_diff('<second_lsn>', '<first_lsn>') / 1024.0 / 1024.0 / 1024.0,
2
) AS wal_generated_gb;
将 max_wal_size 设置得明显高于该数值,这样检查点就会由时间而非 WAL 量来触发。
两个相关参数:
-
checkpoint_completion_target:设置为0.9,以将刷新分散在该时间间隔的大部分时段内。 采用五分钟的checkpoint_timeout时,刷写过程会在大约 270 秒内完成,而不是以尖峰的形式出现。 -
checkpoint_timeout:调高该值可降低检查点频率,但会延长崩溃恢复时间。 这就是权衡。
存储
存储利用率。 在此平台上,预配置 IOPS 会随着已分配存储容量的增加而提升,因此,扩容存储会提高 IOPS 上限。 有关二者之间的关系,请参阅计算和存储选项。
将此视为最后的手段,而不是第一种。 如果原因是数据膨胀或缺少索引,那么增加 IOPS 只会把你正在浪费的上限抬得更高,而不会减少浪费。
优秀的标准是什么样的
| 信号 | 正常 | 调查 |
|---|---|---|
| 已消耗的磁盘 I/O | 峰值时的余量 | 持续接近100% |
| 磁盘队列深度 | 大部分时间接近零 | 持续偏高 |
| 定时检查点与请求检查点 | 时间占比达到 90% 或以上 | 请求的多数 |
| 检查点间隔 | 接近于 checkpoint_timeout |
短得多 |
| I/O 时间与返回行数对比 | 成比例的 | 高 I/O,行数少 |
| 工作负荷与 IOPS | 一起移动 | IOPS 上升,而工作负载持平 |
本指南无法告诉你的内容
- 存储层本身是否出现退化。 这些是来宾端指标。
- I/O 等待所属的物理表。 你拿到查询语句后,需要自己将其映射到表。
- 子样本突发。 对等待事件进行采样。
- 读取副本 WAL 成因。 副本 WAL-write I/O 源自主写入活动。
- 增加 IOPS 是否才是解决之道。 它提高了上限;但并不能说明这种使用是否合法。