¿Por qué la ACTUALIZACIÓN de Postgres tardó 39 horas?


17

Tengo una tabla de Postgres con ~ 2.1 millones de filas. Ejecuté la siguiente actualización:

WITH stops AS (
    SELECT id,
           rank() OVER (ORDER BY offense_timestamp,
                     defendant_dl,
                     offense_street_number,
                     offense_street_name) AS stop
    FROM   consistent.master
    WHERE  citing_jurisdiction=1
)

UPDATE consistent.master
SET arrest_id=stops.stop
FROM stops
WHERE master.id = stops.id;

Esta consulta tardó 39 horas en ejecutarse. Estoy ejecutando esto en un procesador de computadora portátil i7 Q720 de 4 núcleos (físico), mucha RAM, nada más funcionando la gran mayoría de las veces. Sin restricciones de espacio en el disco duro. La mesa había sido aspirada, analizada y reindexada recientemente.

Durante todo el tiempo en que se ejecutó la consulta, al menos después de que se WITHcompletó la inicial , el uso de la CPU generalmente era bajo y el HDD estaba en uso al 100%. El HDD se usaba tan fuerte que cualquier otra aplicación funcionaba mucho más lentamente de lo normal.

La configuración de energía de la computadora portátil estaba en Alto rendimiento (Windows 7 x64).

Aquí está el EXPLICAR:

Update on master  (cost=822243.22..1021456.89 rows=2060910 width=312)
  CTE stops
    ->  WindowAgg  (cost=529826.95..581349.70 rows=2060910 width=33)
          ->  Sort  (cost=529826.95..534979.23 rows=2060910 width=33)
                Sort Key: consistent.master.offense_timestamp, consistent.master.defendant_dl, consistent.master.offense_street_number, consistent.master.offense_street_name
                ->  Seq Scan on master  (cost=0.00..144630.06 rows=2060910 width=33)
                      Filter: (citing_jurisdiction = 1)
  ->  Hash Join  (cost=240893.51..440107.19 rows=2060910 width=312)
        Hash Cond: (stops.id = consistent.master.id)
        ->  CTE Scan on stops  (cost=0.00..41218.20 rows=2060910 width=48)
        ->  Hash  (cost=139413.45..139413.45 rows=2086645 width=268)
              ->  Seq Scan on master  (cost=0.00..139413.45 rows=2086645 width=268)

citing_jurisdiction=1solo excluye unas pocas decenas de miles de filas. Incluso con esa WHEREcláusula, sigo operando en más de 2 millones de filas.

El disco duro está encriptado con TrueCrypt 7.1a. Que ralentiza las cosas un poco, pero no lo suficiente para causar una consulta a tomar que muchas horas.

La WITHparte solo tarda unos 3 minutos en ejecutarse.

El arrest_idcampo no tenía índice para clave externa. Hay 8 índices y 2 claves foráneas en esta tabla. Todos los demás campos de la consulta están indexados.

El arrest_idcampo no tenía restricciones excepto NOT NULL.

La tabla tiene 32 columnas en total.

arrest_idEs de tipo carácter variable (20) . Me doy cuenta de que rank()produce un valor numérico, pero tengo que usar caracteres que varían (20) porque tengo otras filas citing_jurisdiction<>1que usan datos no numéricos para este campo.

El arrest_idcampo estaba en blanco para todas las filas con citing_jurisdiction=1.

Este es un portátil personal de alta gama (desde hace 1 año). Soy el único usuario. No se estaban ejecutando otras consultas u operaciones. El bloqueo parece poco probable.

No hay desencadenantes en ningún lugar de esta tabla ni en ningún otro lugar de la base de datos.

Otras operaciones en esta base de datos nunca toman una cantidad de tiempo anormal. Con una indexación adecuada, las SELECTconsultas suelen ser bastante rápidas.


Esos Seq Scanson un poco atemorizantes ...
rogerdpack

Respuestas:


18

Hace poco sucedió algo similar con una tabla de 3.5 millones de filas. Mi actualización nunca terminaría. Después de muchos experimentos y frustración, finalmente encontré al culpable. Resultó ser los índices en la tabla que se está actualizando.

La solución fue eliminar todos los índices en la tabla que se actualizaba antes de ejecutar la declaración de actualización. Una vez que hice eso, la actualización terminó en unos minutos. Una vez que se completó la actualización, volví a crear los índices y volví al negocio. Esto probablemente no lo ayudará en este momento, pero puede ser que alguien más esté buscando respuestas.

Mantendría los índices en la tabla de la que está extrayendo los datos. Ese no tendrá que seguir actualizando ningún índice y debería ayudar a encontrar los datos que desea actualizar. Funcionó bien en una computadora portátil lenta.


3
Te estoy cambiando la mejor respuesta. Desde que publiqué esto, me he encontrado con otras situaciones donde los índices son el problema, incluso si la columna que se está actualizando ya tiene un valor y no tiene índice (!). Parece que Postgres tiene un problema con la forma en que administra los índices en otras columnas. No hay ninguna razón para que estos otros índices aumenten el tiempo de consulta de una actualización cuando la única alteración de una tabla es actualizar una columna no indexada y no está aumentando el espacio asignado para ninguna fila de esa columna.
Aren Cambre

1
¡Gracias! Espero que ayude a los demás. Me habría ahorrado horas de dolores de cabeza por algo aparentemente muy simple.
JC Avena

55
@ArenCambre: hay una razón: PostgreSQL copia toda la fila en una ubicación diferente y marca la versión anterior como eliminada. Así es como PostgreSQL implementa el Control de concurrencia de versiones múltiples (MVCC).
Piotr Findeisen

Mi pregunta es ... ¿por qué es el culpable? Ver también stackoverflow.com/a/35660593/32453
rogerdpack

15

Su mayor problema es hacer grandes cantidades de trabajo pesado de escritura y búsqueda en el disco duro de una computadora portátil. Eso nunca va a ser rápido, no importa lo que haga, especialmente si es el tipo de unidad de 5400 RPM más lenta que se envía en muchas computadoras portátiles.

TrueCrypt ralentiza las cosas más de "un poco" para las escrituras. Las lecturas serán razonablemente rápidas, pero las escrituras hacen que RAID 5 parezca rápido. Ejecutar una base de datos en un volumen TrueCrypt será una tortura para las escrituras, especialmente las escrituras aleatorias.

En este caso, creo que estaría perdiendo el tiempo tratando de optimizar la consulta. Estás reescribiendo la mayoría de las filas de todos modos, y va a ser lento con tu horrible situación de escritura. Lo que recomendaría es:

BEGIN;
SELECT ... INTO TEMPORARY TABLE master_tmp ;
TRUNCATE TABLE consistent.master;
-- Now DROP all constraints on consistent.master, then:
INSERT INTO consistent.master SELECT * FROM master_tmp;
-- ... and re-create any constraints.

Sospecho que será más rápido que simplemente soltar y volver a crear las restricciones solo, porque una ACTUALIZACIÓN tendrá patrones de escritura bastante aleatorios que matarán su almacenamiento. Dos inserciones masivas, una en una tabla no registrada y otra en una tabla registrada WAL sin restricciones, probablemente serán más rápidas.

Si tiene copias de seguridad absolutamente actualizadas y no le importa tener que restaurar su base de datos a partir de copias de seguridad , también puede reiniciar PostgreSQL con el fsync=offparámetro y full_page_writes=off temporalmente para esta operación masiva. Cualquier problema inesperado como pérdida de energía o un bloqueo del sistema operativo dejará su base de datos irrecuperable mientras fsync=off.

El POSTGreSQL equivalente a "sin registro" es usar tablas no registradas. Estas tablas no registradas se truncan si la base de datos se cierra sin limpiar mientras están sucias. El uso de tablas no registradas al menos reducirá a la mitad su carga de escritura y reducirá el número de búsquedas, por lo que pueden ser MUCHO más rápidas.

Al igual que en Oracle, puede ser una buena idea eliminar un índice y luego volver a crearlo después de una gran actualización por lotes. El planificador de PostgreSQL no puede determinar que se está llevando a cabo una gran actualización, pausar las actualizaciones del índice y luego reconstruir el índice al final; incluso si pudiera, sería muy difícil averiguar en qué punto valía la pena hacerlo, especialmente de antemano.


Esta respuesta es acertada en la gran cantidad de escrituras y el terrible rendimiento del cifrado, más la lenta unidad portátil. También señalaría que la presencia de 8 índices produce muchas escrituras adicionales y anula la aplicabilidad de las actualizaciones de fila en bloque HOT , por lo que la caída de índices y el uso de un factor de relleno inferior en la tabla puede evitar una tonelada de migración de fila
dbenhur

1
Buen llamado para aumentar las posibilidades de HOTs con un factor de relleno, aunque con TrueCrypt forzando ciclos de lectura-reescritura de bloques en bloques enormes, no estoy seguro de que ayude mucho; la migración de filas podría incluso ser más rápida porque al hacer crecer la tabla al menos se realizan bloques de escritura lineales.
Craig Ringer

2.5 años después estoy haciendo algo similar pero en una mesa más grande. Solo para asegurarme, ¿es una buena idea eliminar todos los índices, incluso si la única columna que estoy actualizando no está indexada?
Aren Cambre

1
@ArenCambre En ese caso ... bueno, es complicado. Si la mayoría de sus actualizaciones serán elegibles, HOTentonces es mejor dejar los índices en su lugar. De lo contrario, es probable que desee soltar y volver a crear. La columna no está indexada, pero para poder realizar una actualización CALIENTE también debe haber espacio libre en la misma página, por lo que depende un poco de cuánto espacio muerto haya en la tabla. Si se trata principalmente de escritura, diría que descarte todos los índices. Si se actualiza mucho, puede tener agujeros y puede estar bien. Herramientas como pageinspecty pg_freespacemappueden ayudar a determinar esto.
Craig Ringer

Gracias. En este caso, es una columna booleana que ya tenía una entrada en cada fila. Estaba cambiando la entrada en algunas filas. Acabo de confirmar: la actualización tardó solo 2 horas después de eliminar todos los índices. De antemano, tuve que detener la actualización después de 18 horas porque estaba tardando demasiado. Esto a pesar del hecho de que la columna que se estaba actualizando definitivamente no estaba indexada.
Aren Cambre

2

Alguien dará una mejor respuesta para Postgres, pero aquí hay algunas observaciones desde una perspectiva de Oracle que pueden aplicarse (y los comentarios son demasiado largos para el campo de comentarios).

Mi primera preocupación sería tratar de actualizar 2 millones de filas en una transacción. En Oracle, estaría escribiendo una imagen anterior de cada bloque que se está actualizando para que otra sesión aún tenga una lectura consistente sin leer sus bloques modificados y tenga la capacidad de revertir. Esa es una larga reversión que se está construyendo. Por lo general, es mejor hacer las transacciones en pequeños trozos. Diga 1,000 registros a la vez.

Si tiene índices en la tabla, y la tabla se considerará fuera de servicio durante el mantenimiento, a menudo es mejor eliminar los índices antes de una operación grande y luego volver a crearla después. Más barato entonces constantemente tratando de mantener los índices con cada registro actualizado.

Oracle permite sugerencias de "no registro" en las declaraciones para detener el diario. Acelera mucho las declaraciones, pero deja su base de datos en una situación "irrecuperable". Por lo tanto, desearía hacer una copia de seguridad antes y otra vez inmediatamente después. No sé si Postgres tiene opciones similares.


PostgreSQL no tiene problemas con una reversión larga, no existe. ROLBACK es muy rápido en PostgreSQL, no importa cuán grande sea su transacción. Oracle! = PostgreSQL
Frank Heikens

@FrankHeikens Gracias, eso es interesante. Tendré que leer sobre cómo funciona el diario en Postgres. Para que todo el concepto de transacciones funcione, de alguna manera se deben mantener dos versiones diferentes de los datos durante una transacción, la imagen anterior y la posterior y ese es el mecanismo al que me refiero. De una forma u otra, supongo que hay un umbral más allá del cual los recursos para mantener la transacción serán demasiado caros.
Glenn

2
@Glenn postgres mantiene las versiones de una fila en la tabla misma; consulte aquí para obtener una explicación. El compromiso es que obtienes tuplas 'muertas' dando vueltas, que se limpian de forma asincrónica con lo que se llama 'vacío' en postgres (Oracle no tiene necesidad de vacío porque nunca tiene filas 'muertas' en la mesa)
dice Jack trate topanswers.xyz

De nada, y bastante tarde: bienvenido al sitio :-)
Jack dice que intente topanswers.xyz

@Glenn El documento canónico para el control de concurrencia de versiones de filas de PostgreSQL es postgresql.org/docs/current/static/mvcc-intro.html y vale la pena leerlo. Ver también wiki.postgresql.org/wiki/MVCC . Tenga en cuenta que MVCC con filas muertas y VACUUMes solo la mitad de la respuesta; PostgreSQL también utiliza el llamado "registro de escritura anticipada" (efectivamente un diario) para proporcionar confirmaciones atómicas y proteger contra escrituras parciales, etc. Ver postgresql.org/docs/current/static/wal-intro.html
Craig Ringer
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.