Agregar una columna anulable a la tabla cuesta más de 10 minutos

11

Tengo problemas para agregar una nueva columna en una tabla.
Intenté ejecutarlo un par de veces, pero después de más de 10 minutos de ejecución, decidí cancelar la consulta debido al tiempo de bloqueo.

ALTER TABLE mytable ADD mycolumn VARCHAR(50);

Información útil:

  • Versión PostgreSQL: 9.1
  • Número de filas: ~ 250K
  • Número de columnas: 38
  • Número de columnas anulables: 32
  • Número de restricciones: 5 (1 PK, 3 FK, 1 ÚNICO)
  • Número de índices: 1
  • Tipo de sistema operativo: Debian Squeeze 64

Encontré información interesante sobre la forma en que PostgreSQL administra las columnas anulables (a través de HeapTupleHeader).

Mi primera suposición es que debido a que esta tabla ya tiene 32 columnas anulables con 8 bits MAXALIGN, HeapTupleHeader tiene una longitud de 4 bytes (no verificado, y no sé cómo hacerlo).

Por lo tanto, agregar una nueva columna anulable podría necesitar una actualización de HeapTupleHeader en cada fila para agregar un nuevo 8 bits MAXALIGN, lo que podría causar problemas de rendimiento.

Así que traté de alterar una de las columnas anulables (que en realidad no es anulable en realidad) para disminuir a 31 el número de columnas anulables, para verificar si mi suposición podría ser cierta.

ALTER TABLE mytable ALTER myothercolumn SET NOT NULL;

Desafortunadamente, este cambio también lleva mucho tiempo, más de 5 minutos, así que también lo aborté.

¿Tiene una idea de lo que podría causar este costo de rendimiento?

Matthieu Verrecchia
fuente
1
Bueno, puedo decirle parte de esto: alterar un tipo de columna a otro tipo que no sea compatible con binarios en realidad crea una nueva columna, copia los datos y establece la columna anterior como descartada. Sin embargo, SET NOT NULLno altera el tipo, solo agrega una restricción, pero la restricción debe verificarse contra la tabla, y eso requiere un escaneo completo de la tabla. 9.4 mejora algunos de estos casos al tomar bloqueos más débiles, pero sigue siendo bastante pesado.
Craig Ringer
1
Antes de sospechar que está funcionando lentamente, debe asegurarse de que ALTER TABLE no solo esté esperando un bloqueo. Mencione en la pregunta si ha marcado.
Daniel Vérité
Gracias Craig y Daniel. Cuando ejecuto el comando alter, aparece en pg_stat_activity con la espera "true", supongo que eso significa que espera un bloqueo. Es la buena manera de comprobar? Por cierto, antes de ejecutar este alter, todo va bien, pero unos segundos después del inicio, el número de bloqueos crece
Pruebe la consulta en wiki.postgresql.org/wiki/Lock_dependency_information para obtener una mejor vista. O tiene transacciones persistentes que olvidan comprometerse, o actividad intensa con esta tabla que la mantiene siempre ocupada.
Daniel Vérité
Podría encajar mejor en dba.SE.
Erwin Brandstetter

Respuestas:

8

Aquí hay un par de malentendidos:

El mapa de bits nula es no parte de la cabecera montón tupla. Por documentación:

Hay un encabezado de tamaño fijo (que ocupa 23 bytes en la mayoría de las máquinas), seguido de un mapa de bits nulo opcional ...

Sus 32 columnas anulables no son sospechosas por dos razones:

  • El mapa de bits nulo se agrega por fila , y solo si hay al menos un NULLvalor real en la fila. Las columnas anulables no tienen impacto directo, solo los NULLvalores reales sí. Si se asigna el mapa de bits nulo, siempre se asigna completamente (todo o nada). El tamaño real del mapa de bits nulo es de 1 bit por columna, redondeado al siguiente byte . Por código de fuente actual:

    #define BITMAPLEN(NATTS) (((int)(NATTS) + 7) / 8)
  • El mapa de bits nulo se asigna después del encabezado de la tupla de montón y seguido por un OID opcional y luego datos de fila. El inicio de un OID o datos de fila se indica mediante t_hoffel encabezado. Por código fuente de comentario :

    Tenga en cuenta que t_hoff debe ser un múltiplo de MAXALIGN.

  • Hay un byte libre después del encabezado de la tupla de montón, que ocupa 23 bytes. Por lo tanto, el mapa de bits nulo para filas de hasta 8 columnas efectivamente no tiene costo adicional. Con la novena columna de la tabla, t_hoffse avanzan otros MAXALIGN(normalmente 8) bytes para proporcionar otras 64 columnas. Entonces el próximo borde estaría en 72 columnas.

Para mostrar información de control de un clúster de base de datos PostgreSQL (incl. MAXALIGN), Ejemplo para una instalación típica de Postgres 9.3 en una máquina Debian:

    sudo /usr/lib/postgresql/9.3/bin/pg_controldata /var/lib/postgresql/9.3/main

Actualicé las instrucciones en la respuesta relacionada que usted citó .

Aparte de eso, incluso si su ALTER TABLEdeclaración desencadena una reescritura de una tabla completa (lo que probablemente hace, cambiando un tipo de datos), 250K realmente no son tanto y serían cuestión de segundos en cualquier máquina medianamente decente (a menos que las filas sean inusualmente grandes) . 10 minutos o más indican un problema completamente diferente. Su estado de cuenta está esperando que se bloquee la mesa, muy probablemente.

El creciente número de entradas en pg_stat_activitysignifica más transacciones abiertas: indica acceso concurrente en la tabla (muy probablemente) que tiene que esperar a que finalice la operación.

Algunas tomas en la oscuridad

Verifique la posible hinchazón de la tabla, pruebe con una suave VACUUM mytableo más agresiva VACUUM FULL mytable, que podría encontrar los mismos problemas de concurrencia, ya que este formulario también adquiere un bloqueo exclusivo. Podrías probar pg_repack en su lugar ...

Comenzaría por inspeccionar posibles problemas con índices, disparadores, claves foráneas u otras restricciones, especialmente las relacionadas con la columna. ¿Especialmente un índice dañado podría estar involucrado? Pruebe REINDEX TABLE mytable;o DROPtodos ellos y vuelva a agregarlos después ALTER TABLE de la misma transacción .

Intente ejecutar el comando en la noche o cuando no haya mucha carga.

Un método de fuerza bruta sería detener el acceso al servidor, luego intente nuevamente:

Sin poder precisarlo, la actualización a la versión actual o la próxima 9.4 en particular podría ayudar. Se han realizado varias mejoras para las mesas grandes y para los detalles de bloqueo. Pero si hay algo roto en su base de datos, probablemente debería resolverlo primero.

Erwin Brandstetter
fuente
2
Es casi seguro cerraduras. Pero, como prueba, siempre puede crear una copia de la tabla e intentar modificarla. Si eso no lleva mucho tiempo, entonces sabes que no es la modificación real el problema.
Gracias por las explicaciones Erwin. Creo que tienes razón, parece ser un problema de bloqueo. Cuando verifico pg_stat_activity, puedo ver que mi ALTER tiene una "espera" verdadera. Lo que no puedo entender es por qué ALTER no puede obtener el bloqueo en la tabla, porque incluso cuando no puedo encontrar ninguna consulta ejecutándose, parece que no puede obtenerla. Pero tan pronto como mi ALTER comienza a ejecutarse, todas las demás consultas están esperando que termine. Entonces, la actividad parece indicar que ALTER bloquea todas las demás consultas, pero también indica que ALTER no obtuvo el bloqueo. ¡Creo que hay algo que no entiendo bien!
@MatthieuVerrecchia: ¿Intentaste la prueba que sugirió Richard?
Erwin Brandstetter
1
Acabo de clonar mi tabla en una nueva (con pg_dump -> pg_sql). La nueva columna se agrega correctamente en 50 ms, lo que confirma el problema de bloqueo. Por cierto, todavía no entiendo por qué ALTER no puede bloquearse con una actividad db realmente estándar.
1
@ErwinBrandstetter Seguí tus sugerencias y probé un VACÍO, luego un REINDEX. El REINDEX también estaba bloqueando, porque tampoco pudo adquirir el bloqueo. Después de algunas investigaciones, el problema era más simple de lo que pensábamos. Quedaba una semana <IDLE> con una transacción abierta. El problema está resuelto, gracias. para todo, la información fue muy útil.