TL;DR: クイックフィックス
落ち着いてください。クエリがTOASTエラーでクラッシュする場合、ストレージ内のポインタが破損している可能性があります。安定性を取り戻す最も早い方法は、破損した特定の行を見つけ、その行を削除するか、破損したカラムをNULLにすることです。何かを削除する前に、まずは再インデックス(REINDEX)を試してください。これが「データ損失ゼロ」で解決できる唯一の道です。
-- 1. まずは安全な修正を試す: TOASTテーブルの再インデックス
REINDEX TABLE pg_toast.pg_toast_67890;
-- 2. エラーが解消されない場合は、破損したIDを特定する必要がある
-- (下の「外科的な特定方法」のセクションのスクリプトを参照)
原因について
PostgreSQLは、サイズが大きなフィールドを処理するためにTOASTと呼ばれる仕組みを使用します。行がデフォルトの2KBのページしきい値を超えると、PostgresはJSONBやTEXTなどの大きな値をサイドテーブル(TOASTテーブル)に移動します。そして、後で参照できるようにメインテーブルに小さな「ポインタ」を残します。
missing chunk number 0 エラーは、本質的にはリンク切れです。メインテーブルは「データはあっちにある」と言っていますが、Postgresが確認しに行くと、データが消えているか読み取れない状態になっています。90%のケースでは、書き込み操作中に発生したハードリブート、ディスクのビット腐敗(bit-rot)、またはファイルシステムのバグが原因です。
ステップ 1: 破損したテーブルを特定する
エラーメッセージには pg_toast_67890 のような難解なOIDが表示されます。修正を始める前に、これが実際にどのアプリケーションテーブルに属しているかを知る必要があります。
SELECT
c.relname AS main_table
FROM
pg_class c
JOIN
pg_class toast_c ON c.reltoastrelid = toast_c.oid
WHERE
toast_c.relname = 'pg_toast_67890';
ステップ 2: 再インデックスを試す(安全な策)
データ自体は完全に正常でも、そのデータチャンクを見つけるために使用されるインデックスが混乱している場合があります。再インデックスはデータ損失を伴わないため、最初に行うべき最善の策です。
REINDEX TABLE pg_toast.pg_toast_67890;
失敗していたクエリを再度実行してください。動作すれば完了です。まだクラッシュする場合は、破損がより深刻であるため、外科的な対応が必要になります。
ステップ 3: 破損した行の特定
標準的な SELECT * は、破損した行に到達した瞬間にクラッシュします。特定の原因を突き止めるには、各行を個別に調査するスクリプトが必要です。以下のブロックを使用して、失敗の原因となっている正確なプライマリキー(Primary Key)を特定します。
DO $$
DECLARE
rec record;
bad_count int := 0;
BEGIN
-- 'my_table' と 'id' を実際の名称に置き換えてください
FOR rec IN SELECT id FROM my_table LOOP
BEGIN
-- 'large_column' を実際の TEXT/JSONB/BYTEA カラムに置き換えてください
PERFORM (SELECT large_column FROM my_table WHERE id = rec.id);
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE '破損した行が見つかりました ID: %', rec.id;
bad_count := bad_count + 1;
END;
END LOOP;
RAISE NOTICE 'スキャン完了。破損行の合計: %', bad_count;
END;
$$;
ステップ 4: ダメージを修復する
破損したIDを特定したら、どの程度のデータ損失を許容できるかを判断する必要があります。一般的に2つの選択肢があります。
選択肢 A: カラムをNULLにする
行自体は保持したいが、その特定の大きなフィールド値がなくても困らない場合に使用します。破損したポインタをNULLに置き換えます。
UPDATE my_table
SET large_column = NULL
WHERE id = '破損したID';
選択肢 B: 行を削除する
TOASTされたデータがないとその行自体が無意味な場合は、レコード全体を削除します。
DELETE FROM my_table WHERE id = '破損したID';
ステップ 5: 残骸をクリーンアップする
行を修正できましたか?素晴らしい。次に、定期的なメンテナンス中の将来のエラーを防ぐために、TOASTテーブルに残っている孤立したデータチャンクをクリアする必要があります。
VACUUM ANALYZE my_table;
そのカラムに対してフルカウント(count)を実行して、修正を確認します。エラーではなく数値が返ってくれば、データベースは再び健全な状態に戻っています。
再発を防ぐ方法
ソフトウェアでハードドライブの故障を止めることはできませんが、早期に発見することは可能です。新しいデータベースをセットアップする場合は、常に data_checksums を有効にしてください。これは破損自体を止めるものではありませんが、クエリが失敗するのを待つのではなく、ディスク上でビットが反転した瞬間に警告を発してくれます。さらに、pg_dump による論理バックアップを確実に実行してください。バックアップ中に破損に遭遇すると失敗するため、意図せず「整合性チェック」の役割を果たしてくれます。

