La actualización 2.7.0.beta2 falló con ERROR: clave duplicada

Estaba actualizando desde la versión 2.7.0.beta1 y obtuve este mensaje de error:

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.

Y luego, al final de la salida:

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.

No tengo idea de cómo podría haber dos cuentas con el mismo nombre de usuario.

Nuestro sitio está completamente caído. ¿Qué debo hacer ahora?

Gracias,
Gunnar

Creo que puedes volver a poner el sitio en línea con ./launcher start app

Luego, echa un vistazo a formas de eliminar el duplicado:

y

Más información que probablemente debería proporcionar:

  • Esta es una instalación de un solo contenedor.
  • La primera reconstrucción se completó sin errores. Este error ocurrió cuando estaba ejecutando la segunda reconstrucción, después de que la primera se completara correctamente.

Gracias,
Gunnar

¿Tienes suficiente espacio en el disco? El error está relacionado con la actualización obligatoria de PostgreSQL a la versión 13, según tengo entendido. ¿Siguieste este método de actualización?

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

Si buscas en el foro esos errores, verás mucha información anterior y posibles soluciones.

Prueba esto.

Busca en el foro los errores que publicaste para ver las diversas soluciones que funcionaron para otros.

Logré que el sitio volviera a estar en línea renombrando postgres_data_old de nuevo a postgres_data y luego ejecutando ./launcher start app para iniciar la imagen antigua.

Así que he vuelto al punto de partida, pero al menos el sitio está funcionando.

Intenté encontrar la clave duplicada ejecutando esto:

select * from users WHERE username = 'DaveW';

(El username = DaveW era la clave problemática, como se muestra en el mensaje de error de mi primera publicación en este hilo.)

El comando devolvió solo una fila, un usuario. ¿Qué estoy pasando por alto?

Gracias,
Gunnar

Sí, tienes dos, posiblemente con diferente capitalización en la base de datos:

¿Visitaste mi sitio? :wink:

Entiendo lo que dices: cuando busco en la interfaz gráfica, efectivamente veo dos. Sin embargo, al hacer clic en ambos como administrador y abrir la página de detalles, parecen ser el mismo usuario. No encuentro ninguna diferencia entre ellos. Básicamente, sin importar cuál de los dos haga clic, obtengo los datos del segundo, el que tiene el nombre completo registrado.

Como ese usuario nunca había publicado y no había entrado en mucho tiempo, eliminé la cuenta desde la interfaz gráfica.

Curiosamente, todavía tengo el otro, el primero de tu lista, sin nombre completo. Pero ahora, si hago clic en ese usuario, no ocurre nada. Puedo ver un cuadro de diálogo intentando abrirse y luego cerrándose inmediatamente.

Buscar directamente en la base de datos con username = DaveW ahora devuelve cero filas. Sin embargo, si busco:

select * from users WHERE name = 'DaveW';

(name, no username), obtengo 1 fila:

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

Fíjate que tiene DaveW (con la misma ortografía) en el campo username. Esta cuenta también es tres años más antigua que la otra.

¿Puedo eliminarla con:

DELETE from users WHERE id = 19732;

sin ningún efecto adverso?

¡Gracias por toda tu ayuda!

Gunnar

¡Por supuesto! :grinning:

Eso debería eliminar el 19732, pero no puedo garantizar la ausencia de efectos adversos. En cualquier caso, es aconsejable hacer una copia de seguridad antes de continuar.

También puedes cambiar el nombre de usuario de uno de los duplicados.

Correcto.

Una vez que lo haga, ¿debería poder reiniciar la actualización desde cero? Es decir, ¿ejecutar rebuild dos veces?

¡Gracias!
Gunnar

Eso debería funcionar. A menos que haya otros usuarios duplicados.

Resulta que sí los hay, al menos uno.

PG::UniqueViolation: ERROR:  el valor de clave duplicada viola la restricción única "index_users_on_username_lower"
DETALLE:  La clave (username_lower)=(robs) ya existe.

¿Existe alguna manera de escanear la base de datos en busca de duplicados? No solo usuarios, sino cualquier tabla que pueda causar este error durante la actualización.

Me gustaría encontrarlos y solucionarlos todos antes de intentarlo de nuevo.

Además, sería bueno que esto formara parte del proceso de actualización. Quizás ejecutar un escaneo primero y detener el proceso antes de realizar cualquier cambio. Advertir al usuario y proporcionar una lista de los duplicados, con un enlace a una página aquí en meta con instrucciones sobre cómo solucionarlo.

Un enfoque menos invasivo podría ser reindexar la base de datos antes de actualizar, lo que debería indicarte cuáles necesitan ser corregidos.

No soy experto en bases de datos, así que eso parece intimidante. Y si lo entiendo bien, ¿terminarías con índices duplicados y tendrías que eliminarlos manualmente?

Además, aquí está la parte extraña. Al igual que encontraste en mi sitio con el duplicado anterior, ahora encontré un usuario duplicado llamado RobS en la interfaz gráfica. Pero, al igual que antes, sin importar cuál de ellos hiciera clic, terminaba en la página de perfil de solo uno de ellos. Es decir:

  • Listar usuarios llamados RobS en la interfaz gráfica. Encontrar dos. Ver que ambos tienen estadísticas diferentes.
  • Hacer clic en el usuario #1. Ver el perfil y las estadísticas que pertenecen al usuario #2.
  • Hacer clic en el usuario #2. Ver el perfil y las estadísticas que pertenecen al usuario #2.

select * from users WHERE username_lower = 'robs';

Devuelve 1 fila: Lo que parece ser el usuario #2 (las fechas coinciden).

select * from users WHERE username = 'RobS';

También devuelve solo 1 fila: Lo que parece ser el usuario #1 (nuevamente, las fechas coinciden). Este usuario tiene un ID diferente al otro.

A juzgar por la salida, ambos tenían el mismo username y username_lower, pero solo se devolvió 1 fila para cada sentencia SELECT. ¿Está mi base de datos en serios problemas?

¿Reindexar solucionaría esto?

Intenté reconstruir de nuevo y encontré otro duplicado con el nombre de usuario “drc”. En la interfaz gráfica, encontré estos dos:

Deborah C
David C

Intenta hacer clic en cualquiera de los dos usuarios para ver sus detalles en su “tarjeta” de perfil. Verás que, sin importar cuál de ellos hagas clic, siempre verás los detalles de Deborah C.

No tengo idea de cómo ocurrió esto, pero ¿cuál es la mejor acción que debo tomar ahora? He dejado de lado la actualización hasta que pueda resolver esto.

¿Cómo reindexaría? ¿Así?

REINDEX SCHEMA CONCURRENTLY public;

¿Eso me indicaría qué claves están duplicadas?

Me pregunto qué devolvería lo siguiente:

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

¿No será algo tonto, como que una de las entradas de usuario tenga espacios en blanco o algún carácter no imprimible o unicode de alguna manera?

Creo que sí, y no sé si hay una mejor manera de hacerlo. En mi instancia de prueba simplemente se ejecuta sin generar ninguna salida. Cuando actualicé mi sitio de producción, solo había un duplicado que atender, por lo que mi experiencia podría no ser un buen ejemplo para tu situación. En cualquier caso, haz una copia de seguridad antes de realizar operaciones en la base de datos; por lo general, creo una instantánea en DigitalOcean para que la restauración en caso de problemas sea rápida y sencilla.

Creo que así es como he resuelto casos similares en el pasado. Reconstruye el índice; cuando falle, te indicará el elemento duplicado, lo corriges y lo intentas de nuevo. Repite el proceso.

Gracias. Cuando falla, ¿cómo encuentro y elimino el nuevo índice incompleto? ¿Es tan sencillo como añadir “_ccnew” al nombre de la tabla que falló?

Es decir, cuando se bloquea en “users” debido a un duplicado, ¿primero arreglo el duplicado y luego:

DROP index ‘users_ccnew’;

¿Luego repito el proceso? ¿Es tan sencillo como eso?

Gracias,
Gunnar

Ah, sí. Me encuentro en la situación de haber encontrado dos usuarios válidos (y activos) con el mismo nombre de usuario y username_lower:

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

Parece que necesito cambiar tanto el nombre de usuario como username_lower del segundo usuario. ¿Cómo lo haría en psql?

¡Gracias!
Gunnar