PG12 の破損インデックスの修復方法について

単なる推測ですが、私のコンspiracy理論は以下の通りです。

Reindex を同時に 2 回実行しました(最初は ccnew、次に ccnew1)。
両方ともエラーが発生しました。

おそらく、失敗すると元に戻さず、ゴミを残したまま終了してしまうのでしょう。

それは非常に非常に非常に可能性が高いようです。

ここに私の意見($0.02)を付け加えます。

Toast インデックスはこの方法では削除できません。
私は以下の手順を踏みました。

su postgres
psql 
\connect discourse
drop index pg_toast.pg_toast_309322_index_ccnew;
drop index pg_toast.pg_toast_309322_index_ccnew1;

上記は pg_toast にのみ適用されます。discourse ユーザーにはそのインデックスへのアクセス権がないためです。
PG::InsufficientPrivilege: ERROR: permission denied for schema pg_toast

トーストインデックスの削除は非常に危険です。ただ、ここでは他に選択肢がないようですね。はい、psql からそれを行う必要があります。

さて、楽しい再インデックスの登場です:

discourse=# REINDEX SCHEMA CONCURRENTLY public;
REINDEX

:partying_face:

素晴らしい発見です!このクエリで「ccnew」を含むすべてのインデックス名を見つけました。

psql
\connect discourse
select tablename,indexname,indexdef from pg_indexes where indexname LIKE '%ccnew%';

incoming_referer.csv (7.2 KB)

なんと、incoming_referers テーブルに 30 個もの重複インデックスが存在していました。そこで、indexdef カラムを使ってこれらすべての ccnew インデックスが実際に重複していることを確認するクエリを実行しました。

select indexname,indexdef from pg_indexes where tablename = 'incoming_referers';

ccnew.csv (6.6 KB)

その後、それらすべてを正常に削除しました。

DROP INDEX incoming_referers_pkey_ccnew;
DROP INDEX incoming_referers_pkey_ccnew_ccnew;
DROP INDEX incoming_referers_pkey_ccnew1;
...以下、30 個すべてについて同様

その時点でスキーマ全体を再インデックスしましたが、再び 2 つの同じ incoming_referers ccnew インデックスの再構築に失敗し、さらに 3 つの pg_toast インデックスも発見されました。それらを削除して再度スキーマ全体を再インデックスしましたが、またエラーが発生。discourse スキーマ内にさらに多くの ccnew インデックスが見つかり、3 回目の再インデックスを実行しました……

エラーなしで完全な再インデックスを完了させることができません。毎回新しい ccnew インデックスが作成され、再構築に失敗し続けています。4 回フル再構築を試した後、ccnew インデックスを削除してあきらめました。非同時実行(non-concurrently)で再構築を試みることも可能ですが、それでは多大なダウンタイムが発生してしまいます。

いずれにせよ、PG10 から PG12 へアップグレードしてその後再インデックスを試みたユーザーの多くは、これらの余分な ccnew インデックスを持っていると思われます。それらはすべて削除すべきです。単にディスク容量を圧迫するだけでなく、何の利益もないディスク書き込み I/O を増大させるだけです。

Data-Explorer で同等の操作を実行しました。少し管理しやすかったです。

私が行ったことは、すべての ccnew/ccnew1/ccnew2…ccnewn インデックスを削除し、再インデックスを実行することでした。これで私の場合は解決しました。

ccnew は Postgres のインデックスをマークする仕組みであり、インデックス作成が何らかの理由で失敗した場合にこれらが残ってしまうのは、プロセスの非効率性によるものだと考えています。

まず問題の根本原因に対処することをお勧めします。私の場合は、インデックスを削除する前に重複タグを削除する必要がありました。たとえ重複タグが 1 つでも残っていると、正しくインデックスが作成されず、別の ccnew'n' インデックスが生成されて失敗します。

その通りです、まだ重複が残っています。非並列で非 ccnew インデックスを再構築することはできません。これらの重複行を削除する必要があります。

discourse=# reindex index index_incoming_referers_on_path_and_incoming_domain_id;
ERROR:  could not create unique index "index_incoming_referers_on_path_and_incoming_domain_id"
DETAIL:  Key (path, incoming_domain_id)=(/search/, 3433) is duplicated.
discourse=# reindex index index_incoming_referers_on_path_and_incoming_domain_id;
ERROR:  could not create unique index "index_incoming_referers_on_path_and_incoming_domain_id"
DETAIL:  Key (path, incoming_domain_id)=(/search/, 1861) is duplicated.

本当に奇妙なのは、incoming_referer テーブルにはこれらの incoming_domain_id 値それぞれを持つ行が 1 行しか表示されないことです。なぜ重複しているのでしょうか?

discourse=# select * from incoming_referers where path='/search/' AND incoming_domain_id IN (1861,3433);
  id   |   path   | incoming_domain_id 
-------+----------+--------------------
 42845 | /search/ |               1861
 40763 | /search/ |               3433
(2 rows)

@sam または @riking さん、以下の通りこの 2 行を削除してもよいでしょうか?

DELETE FROM incoming_referers WHERE path='/search/' AND incoming_domain_id IN (1861,3433);

…結局、Postgres を学びつつあるようです(笑)。

いいえ、それでは両方が削除されてしまいます。WHERE句を使って2つのidを検索し、削除するidを1つだけ選んでください。

現在のクエリは破損したインデックスを使用しているため、それぞれ1行しか表示されていません。以下のように試してみてください。

... where path LIKE '%/search/' ...

OK、これで 43 行が取得されました。

discourse=# select * from incoming_referers where path LIKE '%/search/' ORDER BY incoming_domain_id;
  id   |    path     | incoming_domain_id 
-------+-------------+--------------------
   878 | /search/    |                 63
 33457 | /do/search/ |                567
  1580 | /search/    |                602
  1888 | /search/    |                663
 42983 | /search/    |               1259
  4896 | /search/    |               1788
 42845 | /search/    |               1861
  5162 | /search/    |               1861
  5176 | /search/    |               1866
 43350 | /search/    |               1905
 17238 | /search/    |               1905
 20689 | /search/    |               1982
  5781 | /hg/search/ |               1987
  8031 | /search/    |               2665
 10325 | /search/    |               3192
 11289 | /search/    |               3414
 40763 | /search/    |               3433
 42849 | /search/    |               3433
 13087 | /search/    |               3895
 13159 | /search/    |               3949
 13802 | /do/search/ |               4051
 14407 | /search/    |               4209
 14507 | /search/    |               4211
 15394 | /search/    |               4230
 15533 | /search/    |               4258
 45274 | /search/    |               5303
 20923 | /search/    |               5400
 21317 | /search/    |               5534
 22928 | /search/    |               5918
 22956 | /search/    |               5926
 37448 | /search/    |               6393
 25094 | /search/    |               6412
 25594 | /search/    |               6547
 39655 | /search/    |               6596
 27371 | /search/    |               6986
 27452 | /a/search/  |               7003
 27623 | /search/    |               7041
 31041 | /search/    |               7767
 36943 | /search/    |               8622
 37381 | /search/    |               8711
 37411 | /search/    |               8716
 40424 | /search/    |               9124
 44451 | /search/    |               9525
(43 rows)

それでは、以下のコマンドを実行すべきでしょうか?

DELETE FROM incoming_referers WHERE path LIKE '%/search/' AND id IN (42845,43350,42849);

はい、その DELETE は正しいようです。クリーンなバックアップを取得できるはずです。

信じられないかもしれませんが、また失敗が。最初のセットでは、何らかの理由で %/search/ ワイルドカードによって検出されなかった /m/search に別の重複キーがあり、それを削除しました。再度インデックスを再構築しましたが(毎回ほぼ1時間かかりました!)、users(username_lower) で重複キーを示す失敗がさらに発生しました。

discourse=# reindex index index_users_on_username_lower;              
ERROR:  could not create unique index "index_users_on_username_lower"
DETAIL:  Key (username_lower)=(john_smith) is duplicated.

しかし、実際には username_lower=john_smith の行は1つだけだったのです!探偵の帽子をかぶる時が来ました。

フォーラムの管理 UI を見ると、「john_smith」と「John_Smith」という2つの別々のユーザーがいました。大文字と小文字の違いに注意してください。そこで、4年間活動していなかったため、フォーラムレベルで全て小文字のユーザーを削除し、その後そのインデックスは無事に再構築されました。

さらに別の完全な再構築を実行しました。それぞれがほぼ1時間かかりましたが、ついに問題は解決したと思います。エラーは1つだけですが、重複はなく、pg_toast ccnew だけです。それを削除しました。

discourse=# REINDEX SCHEMA CONCURRENTLY public;               
WARNING:  cannot reindex invalid index "pg_toast.pg_toast_2783329_index_ccnew" concurrently, skipping
REINDEX

長い旅でした。

これらのデータベース修正は、将来の Discourse アップグレードで自動化される見込みはありますか?

Peter が一貫した再現手順を得られれば、将来の PostgreSQL アップグレードで PostgreSQL インデックス破損が解消される可能性が高いです。

同時実行中の REINDEX がテーブルに無効なデータがあるために失敗した場合、以下の手順を実行する必要があります。

  1. こちらで行われたように、無効なデータを修正します。

  2. 以下のクエリを使用して無効なインデックスをリストします。
    SELECT pg_class.relname FROM pg_class, pg_index WHERE pg_index.indisvalid = false AND pg_index.indexrelid = pg_class.oid;

  3. 上記でリストされた各無効なインデックスを DROP INDEX <indexname>; を使用して削除します。

  4. 再度 REINDEX を実行します。

2.5.0beta5 へのアップグレードを実行し、データベースの再インデックス作成 という更新後のガイダンスに従ったところ、以下のエラーが発生しました。

discourse=# REINDEX SCHEMA CONCURRENTLY public;
ERROR:  could not create unique index "index_plugin_store_rows_on_plugin_name_and_key_ccnew"
DETAIL:  Key (plugin_name, key)=(discourse-data-explorer, q:-10) is duplicated.

この件については試行錯誤したくないのですが、重複データを安全に削除する方法をご教示ください。

いいえ、アップグレードがインデックスを破損させるわけではありません。単にインデックスが破損していることを指摘しているだけです。別のサーバー(pg10 を実行しているサーバーでも構いません)にバックアップを復元して確認するか、既存のインストールでインデックスを再構築してみてください。破損したインデックスの原因は明確ではありませんが、pg12 ではそれが起こる可能性が低くなるという希望があります。

アップグレードにはいくつかのパフォーマンス上の利点がありますが、それを延期することも悪い考えではありません。

今では手遅れなので、次のステップについて引き続きアドバイスをお願いします。

以下のようなクエリを実行すると、該当する行が取得できます。

select id, plugin_name, key from plugin_store_rows where plugin_name like '%discourse-data-explorer%'

それらの行を削除しても安全だと思います。

ilike が必要になると思います