Index corrompus dans PG12, comment les réparer ?

Je ne fais que spéculer, mais voici ma théorie du complot :

J’ai lancé Reindex deux fois en parallèle (donc d’abord ccnew, puis ccnew1)
et les deux fois, des erreurs sont apparues.

Peut-être que lorsqu’il échoue, il ne revient pas en arrière, il laisse simplement les déchets et quitte.

Cela semble très, très, très probable.

Je me permets d’ajouter mon grain de sel,

Les index TOAST ne peuvent pas être supprimés de cette manière,
J’ai dû :

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

Ce qui précède ne s’applique qu’aux index TOAST de PostgreSQL, car l’utilisateur discourse n’a pas accès à cet index.
PG::InsufficientPrivilege: ERROR: permission denied for schema pg_toast

Supprimer les index de type toast fait vraiment peur. Je suppose que vous n’avez pas le choix dans ce cas. Oui, vous devez le faire depuis psql.

Et voici le reindexage heureux :

discourse=# REINDEX SCHEMA CONCURRENTLY public;
REINDEX

:partying_face:

Bonne trouvaille ! J’ai trouvé tous les index contenant « ccnew » dans leur nom grâce à cette requête.

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

incoming_referer.csv (7,2 Ko)

Il s’avère que j’en avais pas moins de 30, tous sur la table incoming_referers. J’ai donc vérifié que tous les index ccnew étaient en fait des doublons via la colonne indexdef dans cette requête.

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

ccnew.csv (6,6 Ko)

Ensuite, je les ai tous supprimés avec succès.

DROP INDEX incoming_referers_pkey_ccnew;
DROP INDEX incoming_referers_pkey_ccnew_ccnew;
DROP INDEX incoming_referers_pkey_ccnew1;
...et ainsi de suite pour les 30

À ce stade, j’ai relancé une réindexation complète du schéma, mais elle n’a de nouveau pas pu reconstruire deux des mêmes index ccnew sur incoming_referers, et a également détecté trois index pg_toast. Je les ai supprimés, puis j’ai relancé une réindexation complète du schéma, mais là encore des erreurs sont survenues. J’ai trouvé encore plus d’index ccnew dans le schéma discourse, j’ai réindexé une troisième fois…

Je n’arrive pas à obtenir une réindexation complète sans erreur : à chaque fois, de nouveaux index ccnew sont créés puis échouent à être reconstruits. Après 4 réindexations complètes, j’ai supprimé les index ccnew et abandonné. Je pourrais essayer de reconstruire sans l’option CONCURRENTLY, mais cela entraînerait un temps d’arrêt important.

En tout cas, je suppose que la plupart des utilisateurs passant de PG10 à PG12 qui ont tenté une réindexation par la suite se retrouvent avec ces index ccnew supplémentaires, qu’ils devraient tous supprimer. Ils ne font qu’occuper de l’espace et multiplier les écritures disque I/O sans aucun avantage.

J’ai exécuté son équivalent dans Data-Explorer. C’était un peu plus facile à gérer.

Ce que j’ai fait, c’est prendre tous les index ccnew/ccnew1/ccnew2…ccnewn, les supprimer (nuked) et réindexer. Cela a réglé le problème pour moi.

ccnew est un mécanisme de PostgreSQL pour marquer les index, et je pense que c’est une certaine inefficacité dans le processus qui fait que ces index restent si l’indexation échoue pour une raison quelconque.

Je ne peux que vous suggérer de d’abord travailler sur la cause racine du problème. J’avais des tags en double que j’ai dû supprimer avant de procéder à la suppression des index. Si je laissais ne serait-ce qu’un seul tag en double, l’indexation ne se ferait pas correctement et créerait un autre index ccnew'n' avant d’échouer.

Tu as raison, j’ai toujours des doublons : je ne peux pas reconstruire les index non-ccnew de manière non concurrente. Je dois supprimer ces lignes en double.

discourse=# reindex index index_incoming_referers_on_path_and_incoming_domain_id;
ERREUR : impossible de créer l'index unique "index_incoming_referers_on_path_and_incoming_domain_id"
DÉTAIL : La clé (path, incoming_domain_id)=(/search/, 3433) est dupliquée.
discourse=# reindex index index_incoming_referers_on_path_and_incoming_domain_id;
ERREUR : impossible de créer l'index unique "index_incoming_referers_on_path_and_incoming_domain_id"
DÉTAIL : La clé (path, incoming_domain_id)=(/search/, 1861) est dupliquée.

Ce qui est vraiment étrange, c’est que je ne vois qu’une seule ligne avec chacune de ces valeurs incoming_domain_id dans incoming_referer. Alors pourquoi sont-elles en double ?

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 lignes)

@sam ou @riking, devrais-je supprimer ces deux lignes comme suit :

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

… Je suppose que j’apprends Postgres après tout, hein.

Non, cela les supprimera tous les deux – vous devez utiliser WHERE pour trouver les deux id, puis n’en choisir qu’un seul à supprimer.

Votre requête utilise l’index corrompu, c’est pourquoi vous ne voyez qu’une seule ligne pour chacun. Essayez ceci :

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

OK, cela affiche 43 lignes.

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 lignes)

Donc, devrais-je exécuter la commande suivante ?

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

Oui, cette commande DELETE semble correcte et devrait vous permettre de réaliser une sauvegarde propre.

Croyez-le ou non, encore des échecs. Pour le premier lot, j’ai découvert une autre clé dupliquée sur /m/search qui, pour une raison quelconque, n’avait pas été détectée par le joker %/search/ ; je l’ai donc supprimée. J’ai réindexé à nouveau (ce qui prend près d’une heure à chaque fois !), et de nouveaux échecs sont apparus, indiquant une clé dupliquée sur 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.

Pourtant, il n’y avait qu’une seule ligne avec username_lower=john_smith ! Il est temps de mettre le chapeau de détective.

En examinant l’interface d’administration du forum, nous avons constaté qu’il y avait deux utilisateurs distincts nommés respectivement “john_smith” et “John_Smith” — notez la différence de capitalisation. J’ai donc supprimé celui entièrement en minuscules au niveau du forum, car il n’avait pas été actif depuis quatre ans, et cette indexation s’est reconstruite correctement.

J’ai lancé un autre rebuild complet, chacun prenant près d’une heure, et FINALEMENT, je pense que nous sommes bons : une seule erreur, mais aucune duplication, juste un pg_toast ccnew. Je l’ai supprimé.

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

Ce fut un parcours.

Ces correctifs de base de données pourront-ils être automatisés lors d’une future mise à jour de Discourse ?

Il est plus probable que la corruption des index PostgreSQL s’arrête lors d’une future mise à niveau de PostgreSQL, si Peter obtient une reproduction fiable.

Si un REINDEX concurrent échoue car la table contient des données invalides, vous devez :

  1. Corriger les données invalides, comme cela a été fait ici.

  2. Lister les index invalides en utilisant SELECT pg_class.relname FROM pg_class, pg_index WHERE pg_index.indisvalid = false AND pg_index.indexrelid = pg_class.oid;

  3. Supprimer chaque index invalide listé ci-dessus en utilisant DROP INDEX <nom_index>;

  4. Réessayer le REINDEX.

Après avoir exécuté la mise à niveau vers la version 2.5.0beta5 et suivi les recommandations post-mise à jour pour réindexer la base de données, j’obtiens le message suivant :

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.

Je préfère ne pas expérimenter avec cela… alors, comment supprimer en toute sécurité la duplication ?

Non. La mise à niveau ne corrompt pas votre index ; elle indique simplement que votre index est corrompu. Vous pouvez le vérifier en essayant de restaurer votre sauvegarde sur un autre serveur (même un serveur exécutant pg10). Ou essayez de reconstruire votre index sur votre installation actuelle. Il n’est pas clair ce qui cause la corruption des index, mais il est possible que pg12 soit moins susceptible de rencontrer ce problème.

Il existe certains avantages de performance liés à la mise à niveau, mais attendre avant de l’effectuer n’est pas une mauvaise idée.

Il est trop tard maintenant, je cherche donc toujours des conseils concernant les prochaines étapes.

Quelque chose comme

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

devrait vous donner les lignes. Je pense qu’il est suffisamment sûr de les supprimer.

Je pense que ilike serait nécessaire