-
2025-02-20 PostgreSQLPostgreSQL バージョンアップ
PostgreSQL 17 で VACUUM がより効率よく実行されるようになりました。
本記事の概要
本記事では、PostgreSQL 17 で行われた VACUUM の改善について、実際に検証を行った結果を記載しています。
PostgreSQL 17 で行われた VACUUM の改善
PostgreSQL 16 以前では、VACUUM 時にインデックスをメンテナンスする際に利用できるメモリが 1 GB までという制約がありました。
これは内部的な仕様に基づく制限で、具体的には、不要タプルの TID (タプルを一意に特定する ID)を配列で持っていることが理由となります。
PostgreSQL 17 では、Adaptive Radix Tree (ART) と呼ばれるデータ構造に不要タプルの TID を保持するように改善され、その結果、効率的にメモリ領域を利用できるようになりました。
実際に検証
では、実際に PostgreSQL 16 と 17 で、VACUUM を行い、その実行速度を確認してみます。
検証に利用した環境
検証に利用した環境は下記となります。
- RockyLinux 9.3
- PostgreSQL 16.6
- PostgreSQL 17.2
VACUUM の速度比較
pgbench のデータを利用して、VACUUM の実行速度を比較しました。
検証の手順は下記となります。
- pgbench の初期化(pgbench -i)
- 以下の 2 手順を 3 回繰り返す
- pgbench_accounts テーブルに対して更新
- pgbench_accounts テーブルに対して VACUUM(←この時間を計測)
上記の検証を行った結果をまとめたものを下記に記載します。
PostgreSQL 16 と 17 の VACUUM 実行時間の比較(平均)
VACUUM を 3 回 行い、その 3 回の実行時間の平均をまとめたものが下記となります。
※長いので下の方にある「結果から分かること」を見ていただければと思います。
No | テーブルサイズ | maintenance_work_mem | 廃止行の割合 | v16の実行時間(平均) | v17の実行時間(平均) | v16との差 | v16との比率 |
---|---|---|---|---|---|---|---|
1 | 128MB | 100MB | 3% | 390.93 ms | 267.12 ms | -123.81 ms | 68.33% |
2 | 128MB | 100MB | 20% | 577.25 ms | 381.33 ms | -195.92 ms | 66.06% |
3 | 128MB | 100MB | 50% | 920.76 ms | 509.63 ms | -411.13 ms | 55.35% |
4 | 128MB | 100MB | 70% | 1,391.13 ms | 728.74 ms | -662.39 ms | 52.38% |
5 | 128MB | 1GB | 3% | 300.69 ms | 299.27 ms | -1.42 ms | 99.53% |
6 | 128MB | 1GB | 20% | 514.78 ms | 347.94 ms | -166.84 ms | 67.59% |
7 | 128MB | 1GB | 50% | 896.07 ms | 577.41 ms | -318.66 ms | 64.44% |
8 | 128MB | 1GB | 70% | 1,198.84 ms | 796.36 ms | -402.48 ms | 66.43% |
9 | 128MB | 2GB | 3% | 366.10 ms | 315.88 ms | -50.22 ms | 86.28% |
10 | 128MB | 2GB | 20% | 508.27 ms | 339.60 ms | -168.67 ms | 66.81% |
11 | 128MB | 2GB | 50% | 846.66 ms | 516.81 ms | -329.85 ms | 61.04% |
12 | 128MB | 2GB | 70% | 948.73 ms | 717.32 ms | -231.40 ms | 75.61% |
13 | 1.2GB | 100MB | 3% | 1,255.49 ms | 737.16 ms | -518.32 ms | 58.72% |
14 | 1.2GB | 100MB | 20% | 1,899.89 ms | 1,341.61 ms | -558.27 ms | 70.62% |
15 | 1.2GB | 100MB | 50% | 3,672.44 ms | 3,404.83 ms | -267.62 ms | 92.71% |
16 | 1.2GB | 100MB | 70% | 6,105.10 ms | 5,231.51 ms | -873.59 ms | 85.69% |
17 | 1.2GB | 1GB | 3% | 1,466.26 ms | 667.71 ms | -798.55 ms | 45.54% |
18 | 1.2GB | 1GB | 20% | 2,172.92 ms | 1,355.85 ms | -817.07 ms | 62.40% |
19 | 1.2GB | 1GB | 50% | 3,644.15 ms | 3,031.56 ms | -612.60 ms | 83.19% |
20 | 1.2GB | 1GB | 70% | 6,436.27 ms | 5,500.77 ms | -935.49 ms | 85.47% |
21 | 1.2GB | 2GB | 3% | 1,451.44 ms | 720.35 ms | -731.09 ms | 49.63% |
22 | 1.2GB | 2GB | 20% | 1,829.87 ms | 1,367.44 ms | -462.43 ms | 74.73% |
23 | 1.2GB | 2GB | 50% | 4,157.50 ms | 2,746.27 ms | -1,411.23 ms | 66.06% |
24 | 1.2GB | 2GB | 70% | 6,188.14 ms | 4,995.54 ms | -1,192.60 ms | 80.73% |
25 | 6.4GB | 100MB | 3% | 1,781.20 ms | 1,178.79 ms | -602.41 ms | 66.18% |
26 | 6.4GB | 100MB | 20% | 5,146.64 ms | 3,901.61 ms | -1,245.02 ms | 75.81% |
27 | 6.4GB | 100MB | 50% | 18,773.99 ms | 13,423.18 ms | -5,350.81 ms | 71.50% |
28 | 6.4GB | 100MB | 70% | 95,888.70 ms | 53,907.74 ms | -41,980.96 ms | 56.22% |
29 | 6.4GB | 1GB | 3% | 3,011.47 ms | 1,334.37 ms | -1,677.10 ms | 44.31% |
30 | 6.4GB | 1GB | 20% | 7,101.87 ms | 3,767.97 ms | -3,333.90 ms | 53.06% |
31 | 6.4GB | 1GB | 50% | 16,404.66 ms | 14,138.50 ms | -2,266.16 ms | 86.19% |
32 | 6.4GB | 1GB | 70% | 88,320.60 ms | 43,842.30 ms | -44,478.31 ms | 49.64% |
33 | 6.4GB | 2GB | 3% | 2,938.97 ms | 1,070.92 ms | -1,868.05 ms | 36.44% |
34 | 6.4GB | 2GB | 20% | 6,806.48 ms | 3,380.05 ms | -3,426.43 ms | 49.66% |
35 | 6.4GB | 2GB | 50% | 16,694.91 ms | 13,610.78 ms | -3,084.14 ms | 81.53% |
36 | 6.4GB | 2GB | 70% | 88,428.00 ms | 51,531.61 ms | -36,896.39 ms | 58.28% |
PostgreSQL 16 と 17 の VACUUM 実行時間の比較(最小)
VACUUM を 3 回 行い、その 3 回の実行時間のうち、一番実行時間が小さかったものをまとめたものが下記となります。
※長いので下の方にある「結果から分かること」を見ていただければと思います。
No | テーブルサイズ | maintenance_work_mem | 廃止行の割合 | v16の実行時間(最小) | v17の実行時間(最小) | v16との差 | v16との比率 | |
---|---|---|---|---|---|---|---|---|
1 | 128MB | 100MB | 3% | 345.03 ms | 197.04 ms | -147.99 ms | 57.11% | |
2 | 128MB | 100MB | 20% | 550.83 ms | 317.57 ms | -233.26 ms | 57.65% | |
3 | 128MB | 100MB | 50% | 811.89 ms | 376.69 ms | -435.20 ms | 46.40% | |
4 | 128MB | 100MB | 70% | 1,242.48 ms | 631.82 ms | -610.66 ms | 50.85% | |
5 | 128MB | 1GB | 3% | 275.71 ms | 291.66 ms | 15.95 ms | 105.78% | |
6 | 128MB | 1GB | 20% | 425.07 ms | 259.84 ms | -165.23 ms | 61.13% | |
7 | 128MB | 1GB | 50% | 841.99 ms | 550.52 ms | -291.46 ms | 65.38% | |
8 | 128MB | 1GB | 70% | 999.68 ms | 749.50 ms | -250.18 ms | 74.97% | |
9 | 128MB | 2GB | 3% | 331.10 ms | 298.45 ms | -32.65 ms | 90.14% | |
10 | 128MB | 2GB | 20% | 495.26 ms | 258.04 ms | -237.22 ms | 52.10% | |
11 | 128MB | 2GB | 50% | 784.74 ms | 426.77 ms | -357.97 ms | 54.38% | |
12 | 128MB | 2GB | 70% | 912.72 ms | 583.05 ms | -329.67 ms | 63.88% | |
13 | 1.2GB | 100MB | 3% | 968.59 ms | 557.47 ms | -411.12 ms | 57.55% | |
14 | 1.2GB | 100MB | 20% | 1,540.69 ms | 1,268.17 ms | -272.52 ms | 82.31% | |
15 | 1.2GB | 100MB | 50% | 3,416.94 ms | 3,194.94 ms | -222.00 ms | 93.50% | |
16 | 1.2GB | 100MB | 70% | 5,577.01 ms | 4,696.38 ms | -880.63 ms | 84.21% | |
17 | 1.2GB | 1GB | 3% | 970.80 ms | 551.41 ms | -419.39 ms | 56.80% | |
18 | 1.2GB | 1GB | 20% | 1,946.36 ms | 1,327.45 ms | -618.91 ms | 68.20% | |
19 | 1.2GB | 1GB | 50% | 3,545.97 ms | 2,832.72 ms | -713.26 ms | 79.89% | |
20 | 1.2GB | 1GB | 70% | 6,209.32 ms | 5,433.36 ms | -775.96 ms | 87.50% | |
21 | 1.2GB | 2GB | 3% | 1,017.17 ms | 368.57 ms | -648.60 ms | 36.23% | |
22 | 1.2GB | 2GB | 20% | 1,600.13 ms | 1,341.08 ms | -259.05 ms | 83.81% | |
23 | 1.2GB | 2GB | 50% | 3,997.26 ms | 2,518.52 ms | -1,478.74 ms | 63.01% | |
24 | 1.2GB | 2GB | 70% | 5,632.03 ms | 4,040.26 ms | -1,591.77 ms | 71.74% | |
25 | 6.4GB | 100MB | 3% | 750.96 ms | 483.44 ms | -267.51 ms | 64.38% | |
26 | 6.4GB | 100MB | 20% | 4,880.01 ms | 3,147.09 ms | -1,732.92 ms | 64.49% | |
27 | 6.4GB | 100MB | 50% | 17,287.59 ms | 12,782.95 ms | -4,504.64 ms | 73.94% | |
28 | 6.4GB | 100MB | 70% | 88,237.58 ms | 41,811.85 ms | -46,425.74 ms | 47.39% | |
29 | 6.4GB | 1GB | 3% | 2,003.05 ms | 702.85 ms | -1,300.19 ms | 35.09% | |
30 | 6.4GB | 1GB | 20% | 6,735.83 ms | 3,139.77 ms | -3,596.06 ms | 46.61% | |
31 | 6.4GB | 1GB | 50% | 15,545.11 ms | 13,828.69 ms | -1,716.42 ms | 88.96% | |
32 | 6.4GB | 1GB | 70% | 79,510.03 ms | 31,694.82 ms | -47,815.21 ms | 39.86% | |
33 | 6.4GB | 2GB | 3% | 1,958.47 ms | 692.47 ms | -1,266.00 ms | 35.36% | |
34 | 6.4GB | 2GB | 20% | 6,522.35 ms | 3,198.40 ms | -3,323.95 ms | 49.04% | |
35 | 6.4GB | 2GB | 50% | 16,282.01 ms | 13,033.16 ms | -3,248.85 ms | 80.05% | |
36 | 6.4GB | 2GB | 70% | 78,676.40 ms | 42,381.07 ms | -36,295.33 ms | 53.87% |
「v16との差」には「PostgreSQL 17 の実行時間 - PostgreSQL 16 の実行時間」したものを記載しています。数値が小さいほど、PostgreSQL 17 の方が高速化に実行されたことを表しています。
「v16との比率」には「PostgreSQL 17 の実行時間 / PostgreSQL 16 の実行時間」したものを記載しています。数値が小さいほど、PostgreSQL 17 の方が高速化に実行されたことを表しています。
結果から分かること
PostgreSQL 16 では「テーブルサイズが大きい」「廃止行の割合が少ない」場合、maintenance_work_mem が大きくなるにつれて VACUUM の実行時間も大きくなることを確認できました(No.25、No.29、No33)。
こういった事象は PostgreSQL 17 では見られませんので、PostgreSQL 17 では、やはり VACUUM 処理が効率よく行われていると判断できます。
その他の改善内容
今回は検証しておりませんが、VACUUM の実行速度のほかにメモリ使用量の削減や WAL 出力量の削減などが行われているようです。
逆にいうと、そういったメモリ使用量を削減するように改善されているから VACUUM の処理速度が向上している、とも言えます。
おおよそ PostgreSQL 16 と比較してメモリの使用量は 1/20 程度になっているとの情報があります(ページ内のどの部分に廃止行が存在しているかによって、効率化の度合いが変わるようです)。
また、PostgreSQL 16 以前では、maintenance_work_mem に 1 GB を超える値を設定しても、VACUUM 時にディスク領域が利用される可能性がありました。
この制約がPostgreSQL 17 での改善により無くなり、処理対象のデータが maintenance_work_mem に収まるサイズであれば、すべてメモリ領域で処理されるようになっています。
まとめ
本記事では、PostgreSQL 16 と PostgreSQL 17 での VACUUM の実行時間を検証し、比較した結果を確認しました。
VACUUM 処理は PostgreSQL を安定運用するためにも定期的に行う重要な処理となっています。VACUUM 処理が効率よく行えるようになったということは、より安定的な運用が可能になったことに繋がります。
PostgreSQL 16 以前と PostgreSQL 17 以降で maintenance_work_mem に設定する値(の目安や基準)も変わると思いますので、次回以降の記事で、そのあたりを確認できればと考えています。