web-dev-qa-db-ja.com

VACUUM VERBOSE出力、削除不可「デッドローバージョンはまだ削除できません」?

私はPostgres 9.2 DBを使用していますが、特定のテーブルに削除できないデッド行がたくさんあります。

# SELECT * FROM public.pgstattuple('mytable');
 table_len  | Tuple_count | Tuple_len | Tuple_percent | dead_Tuple_count | dead_Tuple_len | dead_Tuple_percent | free_space | free_percent 
------------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
 2850512896 |      283439 | 100900882 |          3.54 |          2537195 |     2666909495 |              93.56 |   50480156 |         1.77
(1 row)

通常のバキューム処理では、削除できないデッド行が多数表示されます。

# VACUUM VERBOSE mytable;
[...]
INFO:  "mytable": found 0 removable, 2404332 nonremovable row versions in 309938 out of 316307 pages
DETAIL:  2298005 dead row versions cannot be removed yet.
There were 0 unused item pointers.
0 pages are entirely empty.
CPU 1.90s/2.05u sec elapsed 16.79 sec.
[...]

テーブルには約300.000の実際のデータ行しかありませんが、230万のデッド行があります(これにより、特定のクエリが非常に遅くなるようです)。

による SELECT * FROM pg_stat_activity where xact_start is not null and datname = 'mydb' order by xact_start;データベースにアクセスする古いトランザクションはありません。最も古いトランザクションは数分前のものであり、まだテーブルの何も変更していません。

私もチェックしましたselect * from pg_prepared_xacts(準備されたトランザクションをチェックするため)およびselect * from pg_stat_replication(保留中のレプリケーションをチェックするため)、どちらも空です。

そのテーブルで実行される挿入、更新、削除はたくさんあるので、死んだ行がたくさん作成されていることがわかります。しかし、なぜそれらはVACUUMコマンドによって削除されないのですか?

8
oliver

最も古いトランザクションは数分前のものであり、まだテーブルの何も変更していません。

それだけでは不十分です。これらの行をデッドとしてマークするために必要なのは、これらのトランザクションが開始されたときに、これらの行に触れた(UPDATEまたはDELETEを実行した)他のトランザクションがなかったことです。

行を更新または削除すると、以前のバージョンの行が物理的に元の場所に保持され、その xmax フィールドに現在のトランザクションのTXIDが設定されます。他のトランザクションの観点からは、この古いバージョンの行は、スナップショットの一部である場合は引き続き表示されます。各スナップショットには、行バージョンのxminおよびxmaxを比較できるxminおよびxmaxがあります。ポイントは、行の変更が確実にコミットされているかどうかを単にチェックするのではなく、VACUUMが行のバージョンをすべてのライブスナップショットの組み合わせた可視性と比較する必要があることです。後者は必要ですが、古いバージョンで使用されていたスペースをリサイクルするには十分ではありません。

たとえば、次のイベントのシーケンスは、それらを変更したトランザクションが終了しても、VACUUMがデッド行をクリーンアップできないようにするものです。

  • t0:長時間実行トランザクションTX1が開始します
  • t0+30mn:TX2が起動し、REPEATABLE READモードに設定されます。
  • t0+35mn:TX1が終了します。
  • t0+40mn:pg_stat_activityは、10分前のTX2のみを表示します
  • t0+45mn:VACUUMは実行されますが、TX1によって変更された古いバージョンの行は削除されません(TX2がそれらを必要とする場合があるため)。
7
Daniel Vérité

これを再現することができました。基本的に、トランザクション内では、

  • READ COMMITTEDでは、デフォルトのトランザクションレベル:
  • SERIALIZABLEまたはREPEATABLE READトランザクションレベル:
    • SELECTAccessShareLockを取得します
    • VACUUMできませんできませんデッドローバージョンをクリーンアップします
    • pg_stat_activity.backend_xmin IS NOT NULL トランザクション用
    • VERBOSEは、これらの行を「削除できない行バージョン」および「デッド行バージョン」として報告します

サンプルデータ

CREATE TABLE bar AS
SELECT x::int FROM generate_series(1,10) AS t(x);

補足として、テーブルの作成後にbarから何かを削除すると、それらの行はremovableになり、VACUUMに表示されます。

INFO:  "bar": removed # row versions in # pages

トランザクションシーケンス

ここで、シナリオを再作成するためのtxnテーブルがあります。

txn1       - BEGIN; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
txn1       - SELECT * FROM bar;
      txn2 - DELETE FROM bar;      -- We delete after the select
      txn2 - VACUUM VERBOSE bar;   -- Can't remove the "dead row versions"

VACUUMはこれらの行バージョンを削除できません。SELECT * FROM bar;の下の後続のREPEATABLE READが引き続きそれらを表示するためです!上記のVACUUMは、

# VACUUM VERBOSE bar;
INFO:  vacuuming "public.bar"
INFO:  "bar": found 0 removable, 10 nonremovable row versions in 1 out of 1 pages
DETAIL:  10 dead row versions cannot be removed yet.
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.

これはまさにあなたが見ているものです。

問題のデバッグ

VACUUMが無効な行をクリーンアップするのを妨げているクエリを見つけるには、これを実行します。

SELECT query, state,locktype,mode
FROM pg_locks
JOIN pg_stat_activity
  USING (pid)
WHERE relation::regclass = 'bar'::regclass
  AND granted IS TRUE
  AND backend_xmin IS NOT NULL;

これはこのようなものを返します。

       query        │        state        │ locktype │      mode       
────────────────────┼─────────────────────┼──────────┼─────────────────
 SELECT * FROM bar; │ idle in transaction │ relation │ AccessShareLock

解決

それでは、TXNに戻りましょう。txn1をkill/commit/rollbackし、VACUUMを再実行する必要があります。

txn1       - COMMIT;
      txn2 - VACUUM VERBOSE bar;

そして今、私たちは、

# VACUUM VERBOSE bar;
INFO:  vacuuming "public.bar"
INFO:  "bar": removed 10 row versions in 1 pages
INFO:  "bar": found 10 removable, 0 nonremovable row versions in 1 out of 1 pages
DETAIL:  0 dead row versions cannot be removed yet.
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
INFO:  "bar": truncated 1 to 0 pages
DETAIL:  CPU 0.00s/0.00u sec elapsed 0.01 sec.

特記事項

  1. 削除された行や選択した行は関係ありません。 selectはテーブルのACCESS SHAREロックを取得します。そして、VACUUMはデッド行を削除できないため、「削除不可」としてマークされます。
  2. これはVACUUM VERBOSEのかなり悪い動作だと思います。見たかったのに….

    DETAIL:  10 dead row versions cannot be removed yet
             could not aquire SHARE UPDATE EXCLUSIVE lock on %TABLE
    

参考文献

また、システムカタログを調べさせてくれた DanielVérité と、このカタログでのVACUUMの動作に感謝します。

6
Evan Carroll

データベースのアクティブなトランザクションまたは特定の「foo」テーブルに対するアクティブなロックがないことを確認した後でも、この問題に直面していました。

次のメソッドは、「foo」からすべてのそれらのリムーバブルではないデッド行を正常に削除しました:

CREATE TEMP TABLE temp_foo AS SELECT * FROM "foo";
TRUNCATE TABLE "foo";
INSERT INTO "foo" SELECT * FROM temp_foo;
DROP table temp_foo;

行が多すぎる大きなテーブルがある場合、すべてのテーブル行が一時テーブルに転送されてから元のテーブルに転送されるため、これは実行可能なソリューションではない可能性があることに注意してください。