Échec de l'importation avec « could not create unique index »

TL;DR : Nous avons fait une erreur lors d’une mise à niveau et cherchons de l’aide


Avec Home Assistant, nous utilisons Discourse pour alimenter notre communauté. Nous l’exécutons avec la méthode discourse_docker sur une instance EC2 chez AWS.

En tant que projet open source, la maintenance du forum a été négligée et nous nous sommes retrouvés avec une ancienne version, dernière mise à jour début 2019.

Pour aggraver les choses, lors d’une précédente mise à niveau, nous avions figé Postgres sur la version 9.5 car nous n’avions pas l’espace disque nécessaire pour passer à Postgres 10. Nous n’avons jamais résolu ce problème.

Nous avions également modifié le modèle Cloudflare et l’avions engagé dans le dépôt, ce qui a empêché la branche docker_discourse de se mettre à jour vers la dernière version.

Hier, nous avons décidé de procéder à la mise à niveau…

Lors de la migration de la base de données, nous avons rencontré un problème : une syntaxe utilisée n’était pas compatible avec la version 9.5 :

== 20200429095034 AddTopicThumbnailInformation: migrating =====================
-- execute("ALTER TABLE posts\nADD COLUMN IF NOT EXISTS image_upload_id bigint\n")

Nous avons rapidement réalisé le problème de la version 9.5 figée. Nous avons donc décidé de migrer vers Postgres 10. Cela n’a pas fonctionné et nous avons reçu l’erreur suivante :

I, [2020-06-12T00:30:55.448351 #1]  INFO -- : Upgrading PostgreSQL from version 9.5 to 10
WARNING: Upgrading PostgresSQL would require an addtional 89M of disk space
Please free up some space, or expand your disk, before continuing.

Nous avions 47 Go disponibles, ce qui était étrange. Nous avons alors réalisé que discourse_docker était obsolète et l’avons mis à jour vers la dernière version. Surprise, Postgres 12 venait tout juste d’être publié.

Après avoir relancé rebuild, cette fois nous avons obtenu cette erreur :

I, [2020-06-12T00:41:17.378129 #1]  INFO -- : Upgrading PostgreSQL from version 9.5 to 12
WARNING: Upgrading PostgresSQL would require an addtional 92G of disk space
Please free up some space, or expand your disk, before continuing.

C’est un peu plus d’espace, mais bon. Augmentons simplement notre espace disque à 300 Go et relançons.

Cette fois, pg_upgrade a échoué pendant la migration :

Restoring database schemas in the new cluster
  template1
  discourse

*failure* Consult the last few lines of "pg_upgrade_dump_16384.log" for the probable cause of the failure. Failure, exiting

En examinant le fichier pg_upgrade_dump_16384.log, nous avons constaté l’erreur suivante :

pg_restore: creating VIEW "postgres_exporter.pg_stat_activity"
pg_restore: [archiver (db)] Error while PROCESSING TOC:
pg_restore: [archiver (db)] Error from TOC entry 721; 1259 678554 VIEW pg_stat_activity postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  column pg_stat_activity.waiting does not exist
LINE 27:     "pg_stat_activity"."waiting",
             ^
    Command was:
-- For binary upgrade, must preserve pg_type oid
SELECT pg_catalog.binary_upgrade_set_next_pg_type_oid('678556'::pg_catalog.oid);


-- For binary upgrade, must preserve pg_type array oid
SELECT pg_catalog.binary_upgrade_set_next_array_pg_type_oid('678555'::pg_catalog.oid);


-- For binary upgrade, must preserve pg_class oids
SELECT pg_catalog.binary_upgrade_set_next_heap_pg_class_oid('678554'::pg_catalog.oid);

CREATE VIEW "postgres_exporter"."pg_stat_activity" AS
 SELECT "pg_stat_activity"."datid",
    "pg_stat_activity"."datname",
    "pg_stat_activity"."pid",
    "pg_stat_activity"."usesysid",
    "pg_stat_activity"."usename",
    "pg_stat_activity"."application_name",
    "pg_stat_activity"."client_addr",
    "pg_stat_activity"."client_hostname",
    "pg_stat_activity"."client_port",
    "pg_stat_activity"."backend_start",
    "pg_stat_activity"."xact_start",
    "pg_stat_activity"."query_start",
    "pg_stat_activity"."state_change",
    "pg_stat_activity"."waiting",
    "pg_stat_activity"."state",
    "pg_stat_activity"."backend_xid",
    "pg_stat_activity"."backend_xmin",
    "pg_stat_activity"."query"
   FROM "pg_stat_activity";

Oh non.

C’est à ce moment-là que nous avons décidé de faire quelques pas en arrière. Pourrions-nous simplement remettre les forums en ligne et les passer en mode lecture seule pendant que nous réglons ce problème de sauvegarde ? Nous y sommes parvenus en corrigeant certains problèmes de permissions pour postgres et redis, et les forums sont revenus en ligne sur l’ancienne version. Tout ne fonctionne pas, par exemple aller dans admin → utilisateur → groupes nous donne cette erreur :

NoMethodError (undefined method `automatic_membership_retroactive' for #<Group:0x00007fcaca3045e8>)
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'

Mais le reste semble fonctionner.

À ce stade, nous avons décidé que, puisque nous avions dû changer les propriétaires (chown) pour revenir à une instance fonctionnelle, nous devions simplement démarrer une nouvelle instance et importer notre sauvegarde.

Nous avons donc lancé une nouvelle instance EC2, suivi les instructions de démarrage de discourse_docker et lancé notre import. Ensuite, nous avons rencontré un problème étrange : il n’a pas pu créer un index car les données ne correspondaient pas aux exigences d’unicité de l’index :

ERROR:  could not create unique index "index_incoming_domains_on_name_and_https_and_port"
DETAIL:  Key (name, https, port)=(homeassistant.home, f, 8123) is duplicated.
EXCEPTION: psql failed: DETAIL:  Key (name, https, port)=(homeassistant.home, f, 8123) is duplicated.
/var/www/discourse/lib/backup_restore/database_restorer.rb:95:in `restore_dump'

Mais lorsque nous avons accédé à la console Rails de notre instance en cours d’exécution, il n’y avait pas de doublon :

[7] pry(main)> IncomingDomain.where(name: "homeassistant.home")
=> [#<IncomingDomain:0x000055e5cabc3760 id: 8648, name: "homeassistant.home", https: false, port: 8123>]

Voilà où nous en sommes actuellement. Et nous sommes un peu perdus.

  • Nous avons une instance en cours d’exécution avec une base de données défectueuse par rapport au code Ruby, incapable de migrer vers des versions plus récentes de Postgres
  • Nous avons une sauvegarde qui ne peut pas être importée dans une nouvelle instance

Nous avons exploré la possibilité de passer à un Discourse hébergé payant, mais avec 3 millions de vues de pages et un million de messages, la tarification entreprise représente un engagement trop important pour nous.

Nous devons donc trouver une issue, de préférence en important notre sauvegarde, mais la migration de notre ancienne instance fonctionnerait aussi.

Des idées ? Nous ne sommes pas contre payer quelqu’un pour nous aider.

4 « J'aime »

Je pense que la solution la plus simple pour vous est d’obtenir une sauvegarde fonctionnelle et de l’importer dans une nouvelle instance, comme vous l’avez tenté précédemment.

Essayons de corriger les données en double :

# se connecter à la machine via ssh
cd /var/discourse
./launcher enter app
su postgres
psql
\connect discourse
SELECT * FROM incoming_domains WHERE name LIKE '%homeassistant.home%';

# cela devrait afficher plusieurs lignes
# utilisez des instructions SQL DELETE pour corriger le problème
# puis quittez avec \q

Pourriez-vous essayer la procédure ci-dessus et demander de l’aide supplémentaire si vous rencontrez un blocage ?

5 « J'aime »

Devrais-je également nettoyer IncomingLink et IncomingReferrer, étant donné que referrer pointe vers IncomingDomain et IncomingLink pointe vers IncomingReferrer ?

J’exécute la requête maintenant et vais essayer d’importer une autre sauvegarde. L’exécution de la requête via PostgreSQL me donne en fait des résultats différents de ceux obtenus avec Rails. Mais je suppose que cela pourrait être dû à une portée par défaut ?

2 « J'aime »

Nous avons tenté d’importer une autre sauvegarde, mais l’opération a échoué en raison d’un autre index corrompu. Nous avons réindexé tous les index uniques sur l’instance d’origine et rencontrons maintenant ce problème avec quelques utilisateurs.

Nous vous tiendrons informés.

1 « J'aime »

Bon, nous avons réussi à sortir de cette situation et nous sommes de nouveau en ligne. Merci pour les indices, @Falco.

Pour aider d’autres personnes à résoudre leurs problèmes, voici un récapitulatif des actions que nous avons entreprises.

Nous avions plusieurs index corrompus, ce qui a provoqué l’échec de l’importation. Nous avons pu résoudre le problème en supprimant manuellement les doublons. Nous avions également 8 utilisateurs avec un username_lower en double (trop de « mike » et de « marco »). Nous avons renommé ces comptes en mettant à jour à la fois username et username_lower. À partir des données utilisateurs, nous avons constaté que la première corruption remontait à décembre 2019.

Au lieu de suivre le cycle « faire une sauvegarde » → « restaurer la sauvegarde » → « échec dû aux doublons » → « corriger », nous avons décidé de réindexer tous les index. Nous avons trouvé tous les index avec des contraintes d’unicité en exécutant la requête suivante :

select idx.relname as index_name, 
       insp.nspname as index_schema,
       tbl.relname as table_name,
       tnsp.nspname as table_schema
from pg_index pgi
  join pg_class idx on idx.oid = pgi.indexrelid
  join pg_namespace insp on insp.oid = idx.relnamespace
  join pg_class tbl on tbl.oid = pgi.indrelid
  join pg_namespace tnsp on tnsp.oid = tbl.relnamespace
where pgi.indisunique --<< uniquement les index uniques
  and tnsp.nspname = 'public'

Une fois tous les index fonctionnels, nous avons pu effectuer une sauvegarde et l’importer correctement dans la nouvelle instance. Les migrations se sont déroulées comme prévu, nous avons échangé les instances et tout est de nouveau opérationnel :+1: À la résilience de Discourse :beers:

Encore merci, @Falco.

Bon week-end à tous :slight_smile:

6 « J'aime »

Un dernier conseil pour ceux qui déboguent des problèmes de corruption de données. Au début, lorsque notre importation a échoué en raison de données en double, je me suis lancé dans la console Rails et j’ai recherché les données qui ont empêché la création de l’index.

Cependant, en interrogeant à l’aide des champs indexés, Postgres utilisait l’index cassé pour générer les résultats ! Ma requête initiale a donc affiché 1 résultat, et plus tard, lors de la suppression de cette entrée, elle a affiché 0 résultat.

Les requêtes qui effectuent des scans complets de table sont gagnantes :slight_smile:

4 « J'aime »

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.