La mise à niveau 2.7.0.beta2 a échoué avec l'erreur : clé dupliquée

J’ai tenté de mettre à jour depuis la version 2.7.0.beta1 et j’ai obtenu ce message d’erreur :

2021-01-22 20:16:22.015 UTC [4055] discourse@discourse LOG:  duration: 75335.241 ms  statement: UPDATE notifications SET processed = true
2021-01-22 20:16:23.792 UTC [4055] discourse@discourse LOG:  duration: 1776.591 ms  statement: ALTER TABLE "notifications" ALTER COLUMN "processed" SET NOT NULL
2021-01-22 20:16:25.198 UTC [4055] discourse@discourse LOG:  duration: 1323.298 ms  statement: CREATE  INDEX  "index_notifications_on_processed" ON "notifications"  ("processed")
2021-01-22 20:16:25.458 UTC [4055] discourse@discourse LOG:  duration: 241.063 ms  statement: CREATE TABLE "user_notification_schedules" ("id" bigserial primary key, "user_id" integer NOT NULL, "enabled" boolean DEFAULT FALSE NOT NULL, "day_0_start_time" integer NOT NULL, "day_0_end_time" integer NOT NULL, "day_1_start_time" integer NOT NULL, "day_1_end_time" integer NOT NULL, "day_2_start_time" integer NOT NULL, "day_2_end_time" integer NOT NULL, "day_3_start_time" integer NOT NULL, "day_3_end_time" integer NOT NULL, "day_4_start_time" integer NOT NULL, "day_4_end_time" integer NOT NULL, "day_5_start_time" integer NOT NULL, "day_5_end_time" integer NOT NULL, "day_6_start_time" integer NOT NULL, "day_6_end_time" integer NOT NULL)
2021-01-22 20:16:25.560 UTC [4055] discourse@discourse LOG:  duration: 100.868 ms  statement: CREATE  INDEX  "index_user_notification_schedules_on_user_id" ON "user_notification_schedules"  ("user_id")
2021-01-22 20:16:25.782 UTC [4055] discourse@discourse LOG:  duration: 142.180 ms  statement: CREATE  INDEX  "index_do_not_disturb_timings_on_scheduled" ON "do_not_disturb_timings"  ("scheduled")
2021-01-22 20:16:26.414 UTC [4055] discourse@discourse LOG:  duration: 361.514 ms  statement: UPDATE users
	SET locale = 'en_GB'
	WHERE locale = 'en'
	
2021-01-22 20:16:26.656 UTC [4055] discourse@discourse LOG:  duration: 132.778 ms  statement: UPDATE theme_translation_overrides
	SET locale = 'en_GB'
	WHERE locale = 'en'
	
2021-01-22 20:16:42.745 UTC [4055] discourse@discourse ERROR:  duplicate key value violates unique constraint "index_users_on_username"
2021-01-22 20:16:42.745 UTC [4055] discourse@discourse DETAIL:  Key (username)=(DaveW) already exists.
2021-01-22 20:16:42.745 UTC [4055] discourse@discourse STATEMENT:  UPDATE users
	SET locale = 'en'
	WHERE locale = 'en_US'
	
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint "index_users_on_username"
DETAIL:  Key (username)=(DaveW) already exists.

Puis, à la fin de la sortie :

I, [2021-01-22T20:16:42.805286 #1] INFO -- : Terminating async processes

I, [2021-01-22T20:16:42.805333 #1] INFO -- : Sending INT to HOME=/var/lib/postgresql USER=postgres exec chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/13/bin/postmaster -D /etc/postgresql/13/main pid: 49

I, [2021-01-22T20:16:42.805381 #1] INFO -- : Sending TERM to exec chpst -u redis -U redis /usr/bin/redis-server /etc/redis/redis.conf pid: 166

166:signal-handler (1611346602) Received SIGTERM scheduling shutdown...

2021-01-22 20:16:42.805 UTC [49] LOG: received fast shutdown request

2021-01-22 20:16:42.835 UTC [49] LOG: aborting any active transactions

2021-01-22 20:16:42.857 UTC [49] LOG: background worker "logical replication launcher" (PID 58) exited with exit code 1

166:M 22 Jan 2021 20:16:42.876 # User requested shutdown...

166:M 22 Jan 2021 20:16:42.876 * Saving the final RDB snapshot before exiting.

166:M 22 Jan 2021 20:16:44.758 * DB saved on disk

166:M 22 Jan 2021 20:16:44.758 # Redis is now ready to exit, bye bye...

2021-01-22 20:16:45.563 UTC [53] LOG: shutting down

I, [2021-01-22T20:16:52.806177 #1] INFO -- : HOME=/var/lib/postgresql USER=postgres exec chpst -u postgres:postgres:ssl-cert -U postgres:postgres:ssl-cert /usr/lib/postgresql/13/bin/postmaster -D /etc/postgresql/13/main pid:49 did not terminate cleanly, forcing termination!

FAILED

--------------------

Pups::ExecError: cd /var/www/discourse && su discourse -c 'bundle exec rake db:migrate' failed with return #<Process::Status: pid 4032 exit 1>

Location of failure: /pups/lib/pups/exec_command.rb:112:in `spawn'

exec failed with the params {"cd"=>"$home", "hook"=>"db_migrate", "cmd"=>["su discourse -c 'bundle exec rake db:migrate'"]}

d627ad17d1f22d839a7dc8099878e6272eb3ea1772539f6628e2a23dd830aca2

** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one.

./discourse-doctor may help diagnose the problem.

Je ne comprends pas comment il pourrait y avoir deux comptes avec le même nom d’utilisateur.

Notre site est complètement hors ligne. Que dois-je faire maintenant ?

Merci,
Gunnar

2 « J'aime »

Je pense que vous pouvez remettre le site en ligne avec ./launcher start app

Ensuite, jetez un œil aux méthodes pour supprimer la doublon :

et

5 « J'aime »

Plus d’informations que je devrais probablement fournir :

  • Il s’agit d’une installation avec un seul conteneur.
  • La première reconstruction s’est déroulée sans aucune erreur. Cette erreur s’est produite lors de l’exécution de la deuxième reconstruction, après que la première reconstruction ait été terminée avec succès.

Merci,
Gunnar

1 « J'aime »

Avez-vous suffisamment d’espace disque ? L’erreur est liée à la mise à jour requise de PostgreSQL vers la version 13, d’après ce que je sais. Avez-vous suivi cette méthode de mise à niveau ?

cd /var/discourse
git pull
 ./launcher rebuild app

Si vous recherchez ces erreurs sur le forum, vous verrez beaucoup d’informations passées et des solutions possibles.

Essayez ceci.

Recherchez sur le forum les erreurs que vous avez publiées pour voir les différentes corrections qui ont fonctionné pour d’autres.

1 « J'aime »

J’ai remis le site en ligne en renommant postgres_data_old en postgres_data, puis en exécutant ./launcher start app pour démarrer l’ancienne image.

Je suis donc de nouveau à zéro, mais au moins le site est opérationnel.

J’ai essayé de trouver la clé en double en entrant ceci :

select * from users WHERE username = 'DaveW';

(Le nom d’utilisateur DaveW était la clé problématique, comme indiqué dans le message d’erreur de mon premier message dans ce fil de discussion.)

La commande a renvoyé une seule ligne, un seul utilisateur. Qu’est-ce que je rate ?

Merci,
Gunnar

1 « J'aime »

Vous en avez bien deux, peut-être avec une casse différente dans la base de données :

1 « J'aime »

Avez-vous visité mon site ? :wink:

Je vois ce que vous voulez dire : lorsque je cherche dans l’interface graphique, je vois effectivement deux comptes. Cependant, lorsque je clique sur les deux en tant qu’administrateur et que j’ouvre la page de détails, ils semblent être le même utilisateur. Je ne trouve aucun détail différent entre les deux. En gros, peu importe lequel des deux je clique, j’obtiens les données du second, celui avec le nom complet saisi.

Puisque cet utilisateur n’avait jamais publié et n’était pas venu depuis longtemps, j’ai supprimé le compte via l’interface graphique.

Curieusement, j’ai toujours l’autre, le premier de votre liste, sans nom complet. Mais maintenant, si je clique sur cet utilisateur, rien ne se passe. Je vois une boîte de dialogue essayer de s’ouvrir, puis se fermer immédiatement.

Une recherche directe dans la base de données pour username = DaveW ne renvoie désormais aucune ligne. Cependant, si je cherche :

select * from users WHERE name = 'DaveW';

(name, pas username), je reçois une ligne :

 19732 | DaveW    | 2016-11-15 12:43:02.708166 | 2016-11-15 12:43:02.708166 | DaveW |                    0 |                |               |      | t      | davew          | 2016-11-15 12:43:02.708166 | f     | 2017-06-01 18:09:45.018058 |           1 | f        |                |             |                   |              |                |               |     0 |          0 |            | f         |       |                    |        |                  |                         | f      |               |               |                          |                           | 
(1 ligne)

Remarquez qu’il contient DaveW (même orthographe) dans le champ username ! Ce compte est également plus ancien de trois ans que l’autre.

Puis-je m’en débarrasser avec :

DELETE from users WHERE id = 19732;

sans aucun effet néfaste ?

Merci pour toute votre aide !

Gunnar

1 « J'aime »

Bien sûr ! :grinning:

Cela devrait supprimer 19732, mais je ne peux pas garantir l’absence de tout effet indésirable. Dans tous les cas, il est conseillé de faire une sauvegarde avant de poursuivre.

3 « J'aime »

Vous pouvez également modifier le nom d’utilisateur de l’un des doublons.

2 « J'aime »

Exactement.

Une fois fait, devrais-je pouvoir relancer la mise à jour depuis le début ? C’est-à-dire exécuter rebuild deux fois ?

Merci !
Gunnar

4 « J'aime »

Cela devrait fonctionner. Sauf s’il y a d’autres utilisateurs en double.

2 « J'aime »

Il s’avère qu’il y en a, au moins un.

PG::UniqueViolation: ERREUR : la valeur de clé en double viole la contrainte d'unicité "index_users_on_username_lower"
DÉTAIL : La clé (username_lower)=(robs) existe déjà.

Existe-t-il un moyen de scanner la base de données pour détecter les doublons ? Pas seulement pour les utilisateurs, mais pour n’importe quelle table susceptible de provoquer cette erreur lors de la mise à niveau ?

Je voudrais les trouver et les corriger tous avant de réessayer.

De plus, il serait préférable que cela fasse partie du processus de mise à niveau. Peut-être exécuter d’abord une analyse et arrêter le processus avant qu’aucune modification n’ait été apportée. Avertir l’utilisateur et fournir une liste des doublons, avec un lien vers une page ici sur Meta contenant des instructions sur la façon de les corriger ?

2 « J'aime »

Une approche moins invasive consiste à réindexer la base de données avant la mise à niveau, ce qui devrait vous indiquer lesquels doivent être corrigés.

2 « J'aime »

Je ne suis pas un expert des bases de données, alors cela a l’air effrayant. Et si j’ai bien compris, vous vous retrouvez alors avec des index dupliqués et devez les supprimer manuellement ?

Par ailleurs, voici la partie étrange. Comme vous l’avez constaté sur mon site avec le doublon précédent, j’ai trouvé un utilisateur dupliqué, RobS, dans l’interface graphique maintenant. Mais, tout comme avant, peu importe lequel d’entre eux je cliquais, je finissais sur la page de profil d’un seul d’entre eux. C’est-à-dire :

  • Listez les utilisateurs nommés RobS dans l’interface graphique. Trouvez-en deux. Remarquez que les deux ont des statistiques différentes.
  • Cliquez sur l’utilisateur #1. Voyez le profil et les statistiques appartenant à l’utilisateur #2.
  • Cliquez sur l’utilisateur #2. Voyez le profil et les statistiques appartenant à l’utilisateur #2.

select * from users WHERE username_lower = 'robs';

Renvoie 1 ligne : Ce qui ressemble à l’utilisateur #2 (les dates correspondent).

select * from users WHERE username = 'RobS';

Renvoie également 1 ligne uniquement : Ce qui ressemble à l’utilisateur #1 (encore une fois, les dates correspondent). Cet utilisateur a un ID différent de l’autre.

À en juger par la sortie, ils avaient tous les deux le même username et username_lower, pourtant une seule ligne a été renvoyée pour chaque instruction SELECT. Ma base de données est-elle en grave danger ?

Une réindexation pourrait-elle résoudre ce problème ?

2 « J'aime »

J’ai essayé de reconstruire à nouveau et j’ai trouvé un autre doublon avec le nom d’utilisateur “drc”. Dans l’interface graphique, j’ai repéré ces deux profils :

Deborah C
David C

Essayez de cliquer sur l’un ou l’autre utilisateur pour voir leurs détails sur leur « carte » de profil. Vous constaterez que peu importe lequel vous cliquez, vous verrez toujours les détails de Deborah C.

Je ne sais pas comment cela a pu arriver, mais quelle est la meilleure démarche à suivre maintenant ? J’ai abandonné la mise à niveau tant que je ne peux pas résoudre ce problème.

Comment procéder à une réindexation ? Comme ceci ?

REINDEX SCHEMA CONCURRENTLY public;

Cela permettrait-il de savoir quelles clés sont en double ?

1 « J'aime »

Je me demande ce que la requête suivante retournerait :

select id, username FROM users WHERE username_lower = 'robs' OR username = 'RobS':

Est-ce quelque chose d’insignifiant, comme l’un des noms d’utilisateur contenant des espaces, un caractère non imprimable ou un caractère Unicode d’une manière ou d’une autre ?

1 « J'aime »

Je pense que oui, et je ne sais pas s’il existe une meilleure façon de faire. Sur mon instance de test, cela s’exécute simplement sans aucune sortie. Lors de la mise à jour de mon site de production, il n’y avait qu’une seule duplication à gérer, donc mon expérience pourrait ne pas être un bon exemple pour votre situation. Dans tous les cas, faites une sauvegarde avant les opérations sur la base de données — je crée généralement un instantané chez DigitalOcean afin que la restauration en cas de problème soit rapide et facile.

1 « J'aime »

Je pense que c’est ainsi que j’ai résolu des problèmes similaires par le passé. Reconstruisez l’index ; lorsqu’il échoue, il vous indiquera l’élément en double, puis vous le corrigez et vous réessayez. Répétez l’opération.

2 « J'aime »

Merci. Lorsqu’il échoue, comment puis-je trouver et supprimer le nouvel index incomplet ? Est-ce aussi simple que d’ajouter « _ccnew » au nom de la table qui a échoué ?

C’est-à-dire, lorsqu’il plante sur « users » à cause d’une duplication, dois-je d’abord corriger la duplication, puis :

DROP index ‘users_ccnew’;

Ensuite, recommencer ? Est-ce aussi simple que cela ?

Merci,
Gunnar

1 « J'aime »

Ah, oui. Je me retrouve maintenant dans une situation où j’ai trouvé deux utilisateurs valides (et actifs) avec le même nom d’utilisateur et le même username_lower :

  id   | username | (redacted) | username_lower |
 42379 | DrC      | (redacted) | drc            |
 47695 | DRC      | (redacted) | drc            |

Il semble que je doive modifier à la fois le nom d’utilisateur et le username_lower du second utilisateur. Comment puis-je faire cela dans psql ?

Merci !
Gunnar

1 « J'aime »