Índices corruptos en PG12, ¿cómo los arreglo?

Solo estoy especulando, pero aquí va mi teoría de la conspiración:

Ejecuté Reindex dos veces de forma concurrente (primero ccnew, luego ccnew1)
y en ambas ocasiones se presentaron errores.

Tal vez, cuando falla, simplemente no revierte los cambios; solo deja los residuos y termina.

Esto suena muy, muy, muy probable.

Solo quiero añadir mi granito de arena aquí.

Los índices TOAST no se pueden eliminar de esta manera;
Tuve que hacer lo siguiente:

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

Lo anterior solo aplica a pg_toast porque el usuario de Discourse no tiene acceso a ese índice.
PG::InsufficientPrivilege: ERROR: permission denied for schema pg_toast

Eliminar índices de toast da mucho miedo. Supongo que no tienes más remedio en este caso. Sí, necesitas hacerlo desde psql.

Y aquí llega el feliz reindexado:

discourse=# REINDEX SCHEMA CONCURRENTLY public;
REINDEX

:partying_face:

¡Gran hallazgo! Encontré todos los índices con “ccnew” en el nombre mediante esta consulta.

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

incoming_referer.csv (7.2 KB)

Resultó que tenía nada menos que 30 de ellos, todos en la tabla incoming_referers. Así que verifiqué que todos los índices ccnew fueran realmente duplicados mediante la columna indexdef en esta consulta.

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

ccnew.csv (6.6 KB)

Y luego los eliminé todos correctamente.

DROP INDEX incoming_referers_pkey_ccnew;
DROP INDEX incoming_referers_pkey_ccnew_ccnew;
DROP INDEX incoming_referers_pkey_ccnew1;
...y así sucesivamente con los 30

En ese momento, reindexé todo el esquema nuevamente y, una vez más, no pudo reconstruir dos de los mismos índices ccnew de incoming_referers, además de encontrar tres índices pg_toast. Los eliminé y luego reindexé todo el esquema de nuevo; otra vez más errores, encontré muchos índices ccnew adicionales en el esquema discourse, reindexé por tercera vez…

No puedo completar un reindexado completo sin errores; sigue creando y luego fallando al reconstruir nuevos índices ccnew cada vez. Después de 4 reconstrucciones completas, eliminé los índices ccnew y finalmente desistí. Supongo que podría intentar reconstruirlos de forma no concurrente, pero eso causaría bastante tiempo de inactividad.

En cualquier caso, mi suposición es que la mayoría de los usuarios que actualizan de PG10 a PG12 y que intentaron reindexar después tienen estos índices ccnew adicionales y deberían eliminarse todos. Solo ocuparán espacio y multiplicarán las operaciones de escritura en disco sin ningún beneficio.

Ejecuté su equivalente en Data-Explorer. Fue un poco más fácil de gestionar.

Lo que hice fue tomar todos los índices ccnew/ccnew1/ccnew2…ccnewn, eliminarlos y volver a indexar. Eso lo solucionó para mí.

c cnew es una característica de PostgreSQL para marcar los índices y creo que es alguna ineficiencia en el proceso lo que hace que estos queden si la indexación falla por alguna razón.

Solo puedo sugerirte que primero trabajes en la causa raíz del problema. Yo tenía etiquetas duplicadas que tuve que eliminar antes de proceder a eliminar los índices. Si me hubiera quedado incluso una sola etiqueta duplicada, no se indexaría correctamente y crearía otro índice ccnew'n' y fallaría.

Tienes razón, todavía tengo duplicados: no puedo reconstruir los índices que no son de ccnew de forma no concurrente. Necesito eliminar estas filas duplicadas.

discourse=# reindex index index_incoming_referers_on_path_and_incoming_domain_id;
ERROR:  no se pudo crear el índice único "index_incoming_referers_on_path_and_incoming_domain_id"
DETAIL:  La clave (path, incoming_domain_id)=(/search/, 3433) está duplicada.
discourse=# reindex index index_incoming_referers_on_path_and_incoming_domain_id;
ERROR:  no se pudo crear el índice único "index_incoming_referers_on_path_and_incoming_domain_id"
DETAIL:  La clave (path, incoming_domain_id)=(/search/, 1861) está duplicada.

Lo realmente extraño es que solo veo una fila con cada uno de esos valores de incoming_domain_id en incoming_referer. ¿Entonces por qué están duplicados?

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

@sam o @riking, ¿debería eliminar estas dos filas de la siguiente manera:

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

… Supongo que al fin estoy aprendiendo postgres, jaja.

No, eso eliminará ambos: quieres usar WHERE para encontrar los dos id y seleccionar solo uno de los id para eliminar.

Tu consulta está usando el índice corrupto, por eso solo ves una fila para cada uno. Intenta esto:

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

OK, eso muestra 43 filas.

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

¿Entonces debería ejecutar lo siguiente?

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

Sí, ese DELETE parece correcto y debería permitirte hacer una copia de seguridad limpia.

Créalo o no, más fallos. En el primer intento, encontré otra clave duplicada en /m/search que, por alguna razón, no fue detectada por el comodín %/search/ y la eliminé. Vuelví a reindexar (cada vez tardando casi una hora) y aparecieron más fallos indicando una clave duplicada en users(username_lower).

discourse=# reindex index index_users_on_username_lower;              
ERROR:  no se pudo crear el índice único "index_users_on_username_lower"
DETALLE:  La clave (username_lower)=(john_smith) está duplicada.

Pero lo cierto es que solo había una fila con username_lower=john_smith. ¡A ponerse el sombrero de detective.

Al revisar la interfaz de administración del foro, vimos dos usuarios distintos llamados “john_smith” y “John_Smith”— nota la diferencia en las mayúsculas. Así que eliminé el que estaba todo en minúsculas a nivel del foro, ya que no había estado activo en 4 años, y luego ese índice se reconstruyó correctamente.

Ejecuté otra reconstrucción completa, cada una tardando casi una hora, y FINALMENTE creo que ya estamos bien: solo un error, pero sin duplicados, solo un pg_toast ccnew. Lo eliminé.

discourse=# REINDEX SCHEMA CONCURRENTLY public;               
WARNING:  no se puede reindexar concurrentemente el índice inválido "pg_toast.pg_toast_2783329_index_ccnew", omitiendo
REINDEX

Ha sido un viaje.

¿Se pueden/Se podrán automatizar estas correcciones de base de datos en una futura actualización de Discourse?

Es más probable que la corrupción de índices de pg se detenga en una futura actualización de pg, si Peter logra una reproducción consistente.

Si un REINDEX concurrente falla porque la tabla tiene datos inválidos, debes:

  1. Corregir los datos inválidos, como se hizo aquí.

  2. Listar los índices inválidos usando SELECT pg_class.relname FROM pg_class, pg_index WHERE pg_index.indisvalid = false AND pg_index.indexrelid = pg_class.oid;

  3. Eliminar cada índice inválido listado anteriormente usando DROP INDEX <indexname>;

  4. Intentar el REINDEX nuevamente.

Después de ejecutar la actualización a 2.5.0beta5 y seguir las instrucciones posteriores a la actualización para reindexar la base de datos, obtengo esto:

discourse=# REINDEX SCHEMA CONCURRENTLY public;
ERROR:  no se pudo crear el índice único "index_plugin_store_rows_on_plugin_name_and_key_ccnew"
DETALLE:  La clave (plugin_name, key)=(discourse-data-explorer, q:-10) está duplicada.

Prefiero no experimentar con esto… entonces, ¿cómo elimino de forma segura el duplicado?

No. La actualización no corrompe tu índice, simplemente señala que tu índice ya está corrupto. Puedes verificarlo intentando restaurar tu copia de seguridad en otro servidor (incluso uno que ejecute pg10) o intentando reconstruir el índice en tu instalación actual. No está claro qué está causando los índices corruptos, pero hay esperanzas de que pg12 sea menos propenso a que esto ocurra.

Hay algunas mejoras de rendimiento en la actualización, pero posponerla no es una mala idea.

Ahora es demasiado tarde, así que sigo buscando orientación sobre los siguientes pasos.

Algo como

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

debería devolverte las filas. Creo que es lo suficientemente seguro borrarlas.

Creo que sería necesario ilike