Heroku Postgres での VACUUM の管理
最終更新日 2024年06月04日(火)
Table of Contents
Postgres は MVCC と呼ばれるメカニズムを使用してデータベースの変更を追跡します。副作用として、一部の行が “使用されない” 状態になり、実行中のどのトランザクションにも表示されなくなります。使用されなくなった行は DELETE
操作だけでなく、UPDATE
や、ロールバックする必要があるトランザクションによっても生成されます。
これらの使用されなくなった行を消去するために、データベースには定期的なメンテナンスが必要です。これは基本的に、ガベージコレクションの形式になります。通常、このメンテナンスは自動的に実行されますが、その詳細を理解し、必要に応じてメンテナンス設定をチューニングすると有効な場合があります。
データベースのバキューム
このクリーンアップを管理するための組み込みのメカニズムは VACUUM
と呼ばれます。これは通常のコマンドとして実行できますが、Postgres には、VACUUM
プロセスをメンテナンスタスクとしてバックグラウンドで自動的に実行し、必要に応じて古いデータの定期的な消去を試みるための機能も含まれています。このプロセスは、一連の設定パラメータに基づいてメンテナンスを実行します。
多くのアプリケーションでは Heroku のデフォルト設定で十分ですが、状況によっては、いくつかの変更を行ったり、手動のアクションを実行したりすることが必要です。
肥大化の確認
VACUUM を実行する必要があるかどうかを確認するには、テーブルとインデックスの “肥大化” に関する情報を提供するクエリを実行できます。肥大化とは、使用されなくなった行のために、ディスク上のこれらのデータベースオブジェクトによって占有される余分な領域のことです。これを確認するための最も簡単な方法は、Heroku CLI の pg-extras プラグインのインストールです。
インストールしたら、次のコマンドを実行して肥大化を確認できます。
$ heroku pg:bloat DATABASE_URL --app example-app
type | schemaname | object_name | bloat | waste
-------+------------+-------------------------+-------+-----------
table | public | users | 1.0 | 109 MB
table | public | logs | 1.0 | 47 MB
index | public | queue_classic_jobs_pkey | 3.1 | 25 MB
table | public | reviews | 2.2 | 16 MB
table | public | queue_classic_jobs | 32.5 | 1512 kB
...
“bloat” 列は、肥大化として存在する元のテーブルの部分の係数である肥大化係数を示しています。これは比率であるため、単位はありません。"waste" 列は、システム内の各テーブルとインデックスの合計の肥大化 (バイト単位) を示しています。
Postgres では肥大化を考慮せずにクエリが計画されるため、テーブルまたはインデックスの肥大化係数が非常に大きいと、一部のクエリでパフォーマンスが低下する場合があります。
過剰な肥大化のしきい値はクエリパターンやテーブルのサイズによって異なります。特に 100 MB を超えるテーブルの場合、一般に肥大化係数が 10 を超えるときは常に調査する必要があります。
データベースでのバキュームを確認するには、別の pg-extras コマンドを使用できます。
$ heroku pg:vacuum-stats DATABASE_URL --app example-app
schema | table | last_vacuum | last_autovacuum | rowcount | dead_rowcount | autovacuum_threshold | expect_autovacuum
--------+--------------------+-------------+------------------+----------------+----------------+----------------------+-------------------
public | queue_classic_jobs | | 2013-05-20 16:54 | 82,617 | 36,056 | 16,573 | yes
public | logs | | 2013-05-20 16:27 | 1 | 18 | 50 |
public | reviews | | 2013-05-20 01:36 | 87 | 0 | 67 |
public | users | | 2013-05-20 16:28 | 0 | 23 | 50 |
...
これにより、各テーブルが最後にバキュームされた日時と、それが手動のアクションまたは自動バキュームバックグラウンドワーカーのどちらで行われたかが通知されます。また、その特定のテーブルの自動バキュームをトリガーする使用されなくなった行のしきい値の行数と、自動バキュームの実行が予測されるかどうかも表示されます。
VACUUM のバリアント
VACUUM
を定期的に実行することで、肥大化を抑えることができます。自動バキュームプロセスは、通常の FULL ではない VACUUM
コマンドのみを実行します。制御不能になった場合は、VACUUM FULL
を実行することで肥大化を軽減できます。
VACUUM FULL
はより徹底的なクリーンアップを行い、通常の VACUUM
のように空き容量をフラグ付けするだけでなく、実際に肥大化を軽減します。ただし、その分非常に重い操作でもあります。VACUUM FULL
はテーブル全体を書き換えるため、単純な SELECT
クエリのような他のステートメントが同時に実行されるのを防ぎます。
一般には、VACUUM FULL
を実行する必要がないように、積極的に自動バキュームを行うように設定しておくことをお勧めします。VACUUM FULL
は時間がかかる可能性があるため、代わりに特定のテーブルに対して VACUUM
を実行できます。たとえば、VACUUM table_name
とします。
一時的なデータ (ワークキューなど) を追跡するためにのみテーブルを使用している場合は、代わりに TRUNCATE
コマンドを実行すると役立ちます。このコマンドにより、テーブル内のすべてのデータがバッチ操作で削除されます。非常に肥大化したテーブルの場合は、これが DELETE
や VACUUM FULL
よりはるかに高速になる場合があります。
自動バキュームを使用した自動的なバキューム
肥大化を管理するための最も効果的な方法として、必要に応じて自動バキューム設定を調整します。
テーブルが VACUUM
の対象になるタイミングは、4 つの設定から変更できます。Heroku では、これらの変更はテーブルごとにしか行えません。
$ heroku pg:psql
=> ALTER TABLE users SET (autovacuum_vacuum_threshold = 50);
ALTER TABLE
=> ALTER TABLE users SET (autovacuum_vacuum_scale_factor = 0.2);
ALTER TABLE
=> ALTER TABLE users SET (autovacuum_vacuum_insert_threshold = 1000);
ALTER TABLE
=> ALTER TABLE users SET (autovacuum_vacuum_insert_scale_factor = 0.2);
ALTER TABLE
Postgres は、前回の VACUUM
操作以降の使用されなくなった行数が定義されたしきい値を超えた場合、および最後の VACUUM
操作以降に挿入された行数が定義された挿入しきい値を超えた場合に、自動バキュームをトリガーします。
バキュームしきい値は、必要とされる使用されなくなった行の未加工の数であり、バキュームスケール係数は、使用されなくなった行として存在する必要があるテーブル内のライブ行の部分の係数です。これらのデフォルト値は 50
と 0.2
です。
挿入しきい値はバキュームを開始する前の最小の行挿入数であり、挿入スケール係数は挿入バキュームを開始するテーブルサイズに対する挿入数の割合です。これらのデフォルト値は 1000
と 0.2
です。
これらの設定を組み合わせることで、次の式に従って実際のしきい値と挿入しきい値が構成されます。
vacuum threshold = autovacuum_vacuum_threshold +
autovacuum_vacuum_scale_factor * number of rows
vacuum insert threshold = autovacuum_vacuum_insert_threshold +
autovacuum_vacuum_insert_scale_factor * number of inserts
大きなテーブルでは、このスケール係数を減らしてバキュームの進行をより早く開始できるようにする必要があります。非常に小さなテーブルの場合は、このしきい値を増やすことができますが、一般には必要ありません。
さらに、自動バキュームにはコストベースの速度制限メカニズムが組み込まれており、VACUUM
アクティビティによるシステムの過負荷を回避します。ただし、ビジー状態のデータベースでは、このメカニズムによって自動バキュームの進行が遅くなり、過剰な肥大化につながる場合があります。
これを回避するには、より積極的にバックオフを行うようにバックアップ設定を変更できます。これらの変更は、データベースレベルで行うことができます。
$ heroku pg:psql
=> select current_database();
current_database
------------------
dd5ir2j6frrtr0
(1 row)
=> ALTER DATABASE dd5ir2j6frrtr0 SET vacuum_cost_limit = 300;
ALTER DATABASE
=> ALTER DATABASE dd5ir2j6frrtr0 SET vacuum_cost_page_dirty = 25;
ALTER DATABASE
=> ALTER DATABASE dd5ir2j6frrtr0 SET vacuum_cost_page_miss = 7;
ALTER DATABASE
=> ALTER DATABASE dd5ir2j6frrtr0 SET vacuum_cost_page_hit = 0;
ALTER DATABASE
コスト制限によって、自動バキュームが強制的に中断されるまでに獲得できる “コスト” の大きさ (I/O 操作の単位) が決定されます。また、コスト遅延によって、その中断の期間 (ミリ秒単位) が決定されます。これらの設定は、自動バキュームと手動バキュームの両方に影響を与えます (自動バキュームのみのバリアントが存在しますが、現時点で Heroku Postgres ではテーブルごとにしか設定できません)。コスト制限は、デフォルトでは 200
に設定されています。コスト制限を増やす (最大 1000
程度) か、vacuum_cost_page_*
パラメータを調整すると、自動バキュームの進行がより効率的になります。
手動バキューム
データベースに定期性の高いワークロードが存在する場合は、単純なワーカープロセスを使用して “手動で”VACUUM
(ロックが問題にならない場合は VACUUM FULL
でも) を実行し、ピーク時間外に Heroku Scheduler などのツールでそれをトリガーする方がより効率的です。
手動 VACUUM
には、いつ “開始するか” に関するしきい値はありません。VACUUM
コマンドを実行すると必ずトリガーされます。コストベースのバックオフも (自動バキュームと同様に) 適用されますが、これはデフォルトで無効になっています (vacuum_cost_delay
は 0 に設定)。手動の VACUUM
が通常のワークロードに与える影響が大きすぎることがわかった場合は、この値をテーブルごとに増やすことができます。
VACUUM
を実行するには、目的のデータベースへの psql シェルを開き、次のコマンドを入力します。
$ heroku pg:psql
=> VACUUM;
WARNING: skipping "pg_authid" --- only superuser can vacuum it
WARNING: skipping "pg_database" --- only superuser can vacuum it
WARNING: skipping "pg_tablespace" --- only superuser can vacuum it
WARNING: skipping "pg_pltemplate" --- only superuser can vacuum it
WARNING: skipping "pg_auth_members" --- only superuser can vacuum it
WARNING: skipping "pg_shdepend" --- only superuser can vacuum it
WARNING: skipping "pg_shdescription" --- only superuser can vacuum it
WARNING: skipping "pg_db_role_setting" --- only superuser can vacuum it
VACUUM
表示される警告は予測されるものであり、無視できます。また、手動バキュームを必要とするテーブルが 1 つまたは 2 つだけの場合は、VACUUM
を特定のテーブルに制限することもできます。
$ heroku pg:psql
=> VACUUM users;
VACUUM
VACUUM
を実行する場合は、その進行状況に関するより詳細な情報を得るために VERBOSE
キーワードを追加できます。
$ heroku pg:psql
d7lrq1eg4otc3i=> VACUUM VERBOSE;
INFO: vacuuming "public.reviews"
INFO: index "reviews_pkey" now contains 0 row versions in 1 pages
DETAIL: 0 index row versions were removed.
0 index pages have been deleted, 0 are currently reusable.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
INFO: index "reviews_user_index" now contains 0 row versions in 1 pages
DETAIL: 0 index row versions were removed.
0 index pages have been deleted, 0 are currently reusable.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
INFO: "users": found 0 removable, 0 nonremovable row versions in 0 out of 0 pages
DETAIL: 0 dead row versions cannot be removed yet.
There were 0 unused item pointers.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
...
VACUUM
自動バキューム設定を慎重に管理すれば、手動バキュームが必要になることはめったにありませんが、その仕組みを理解しておくことが重要です。