Optimización del rendimiento de la actualización masiva en PostgreSQL


37

Usando PG 9.1 en Ubuntu 12.04.

Actualmente, lleva hasta 24 horas ejecutar un gran conjunto de sentencias ACTUALIZAR en una base de datos, que son de la siguiente forma:

UPDATE table
SET field1 = constant1, field2 = constant2, ...
WHERE id = constid

(Solo estamos sobrescribiendo los campos de los objetos identificados por ID). Los valores provienen de una fuente de datos externa (no está ya en el DB en una tabla).

Las tablas tienen un puñado de índices y no hay restricciones de clave externa. No se hace ningún COMPROMISO hasta el final.

Se necesitan 2 horas para importar una pg_dumpde toda la base de datos. Esto parece una línea de base a la que deberíamos apuntar razonablemente.

A falta de producir un programa personalizado que de alguna manera reconstruya un conjunto de datos para que PostgreSQL vuelva a importar, ¿hay algo que podamos hacer para acercar el rendimiento de ACTUALIZACIÓN masiva al de la importación? (Esta es un área que creemos que los árboles de fusión con estructura de registro se manejan bien, pero nos preguntamos si hay algo que podamos hacer dentro de PostgreSQL).

Algunas ideas:

  • eliminando todos los índices que no son de ID y reconstruyendo después?
  • aumentando checkpoint_segments, pero ¿esto realmente ayuda a un rendimiento sostenido a largo plazo?
  • utilizando las técnicas mencionadas aquí ? (Cargue los datos nuevos como tabla, luego "fusione" los datos antiguos donde la ID no se encuentra en los datos nuevos)

Básicamente hay un montón de cosas para probar y no estamos seguros de cuáles son las más efectivas o si estamos pasando por alto otras cosas. Pasaremos los próximos días experimentando, pero pensamos en preguntar aquí también.

Tengo una carga concurrente en la tabla pero es de solo lectura.


Falta información crucial en su pregunta: ¿Su versión de Postgres? ¿De dónde vienen los valores? Suena como un archivo fuera de la base de datos, pero aclare. ¿Tiene carga concurrente en la tabla de destino? En caso afirmativo, ¿qué es exactamente? ¿O puede darse el lujo de dejarse caer y recrear? No hay claves foráneas, ok, pero ¿hay otros objetos dependientes como las vistas? Edite su pregunta con la información que falta. No lo exprimas en un comentario.
Erwin Brandstetter

@ErwinBrandstetter Gracias, actualicé mi pregunta.
Yang

¿Asumo que has comprobado explain analyzeque está usando un índice para la búsqueda?
rogerdpack

Respuestas:


45

Supuestos

Como falta información en la Q, supondré:

  • Sus datos provienen de un archivo en el servidor de bases de datos.
  • Los datos están formateados como COPYsalida, con un único id por fila para coincidir con la tabla de destino.
    Si no es así, primero formatee correctamente o use las COPYopciones para manejar el formato.
  • Está actualizando cada fila de la tabla de destino o la mayoría de ellas.
  • Puede permitirse soltar y recrear la tabla de destino.
    Eso significa que no hay acceso concurrente. De lo contrario, considere esta respuesta relacionada:
  • No hay objetos dependientes en absoluto, excepto los índices.

Solución

Le sugiero que siga un enfoque similar al descrito en el enlace de su tercera viñeta . Con grandes optimizaciones.

Para crear la tabla temporal, hay una forma más simple y rápida:

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

Un solo grande UPDATEde una tabla temporal dentro de la base de datos será más rápido que las actualizaciones individuales desde fuera de la base de datos en varios órdenes de magnitud.

En el modelo MVCC de PostgreSQL , un UPDATEmedio para crear una nueva versión de fila y marcar la antigua como eliminada. Eso es casi tan caro como un INSERTy un DELETEcombinado. Además, te deja con muchas tuplas muertas. Como está actualizando toda la tabla de todos modos, sería más rápido crear una nueva tabla y soltar la anterior.

Si tiene suficiente RAM disponible, configure temp_buffers(¡solo para esta sesión!) Lo suficientemente alto como para mantener la tabla temporal en RAM, antes de hacer cualquier otra cosa.

Para obtener una estimación de la cantidad de RAM necesaria, ejecute una prueba con una muestra pequeña y use funciones de tamaño de objeto db :

SELECT pg_size_pretty(pg_relation_size('tmp_tbl'));  -- complete size of table
SELECT pg_column_size(t) FROM tmp_tbl t LIMIT 10;  -- size of sample rows

Guión completo

SET temp_buffers = '1GB';        -- example value

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

COPY tmp_tbl FROM '/absolute/path/to/file';

CREATE TABLE tbl_new AS
SELECT t.col1, t.col2, u.field1, u.field2
FROM   tbl     t
JOIN   tmp_tbl u USING (id);

-- Create indexes like in original table
ALTER TABLE tbl_new ADD PRIMARY KEY ...;
CREATE INDEX ... ON tbl_new (...);
CREATE INDEX ... ON tbl_new (...);

-- exclusive lock on tbl for a very brief time window!
DROP TABLE tbl;
ALTER TABLE tbl_new RENAME TO tbl;

DROP TABLE tmp_tbl; -- will also be dropped at end of session automatically

Carga concurrente

Las operaciones concurrentes en la tabla (que descarté en los supuestos al inicio) esperarán, una vez que la tabla esté bloqueada cerca del final y fallarán tan pronto como se confirme la transacción, porque el nombre de la tabla se resuelve en su OID inmediatamente, pero la nueva tabla tiene un OID diferente. La tabla se mantiene constante, pero las operaciones concurrentes pueden obtener una excepción y deben repetirse. Detalles en esta respuesta relacionada:

ACTUALIZAR ruta

Si (tiene que) ir por la UPDATEruta, suelte cualquier índice que no sea necesario durante la actualización y vuelva a crearlo después. Es mucho más barato crear un índice en una sola pieza que actualizarlo para cada fila individual. Esto también puede permitir actualizaciones CALIENTES .

Esbocé un procedimiento similar usando UPDATEen esta respuesta estrechamente relacionada en SO .

 


1
En realidad, solo estoy actualizando el 20% de las filas en la tabla de destino, no todas, pero una porción lo suficientemente grande como para que una fusión sea probablemente mejor que la búsqueda aleatoria de actualizaciones.
Yang

1
@AryehLeibTaurog: Eso no debería estar sucediendo desde que DROP TABLEsaca un Access Exclusive Lock. De cualquier manera, ya mencioné el requisito previo en la parte superior de mi respuesta: You can afford to drop and recreate the target table.podría ayudar bloquear la mesa al comienzo de la transacción. Le sugiero que comience una nueva pregunta con todos los detalles relevantes de su situación para que podamos llegar al fondo de esto.
Erwin Brandstetter

1
@ErwinBrandstetter Interesante. Parece depender de la versión del servidor. He reproducido el error en 8.4 y 9.1 usando el adaptador psycopg2 y usando el cliente psql . En 9.3 no hay error. Ver mis comentarios en el primer guión. No estoy seguro de si hay una pregunta para publicar aquí, pero puede valer la pena solicitar alguna información en una de las listas de postgresql.
Aryeh Leib Taurog

1
Escribí una clase de ayuda simple en Python para automatizar el proceso.
Aryeh Leib Taurog

3
Muy útil respuesta. Como una ligera variación, uno puede crear la tabla temporal con solo las columnas que se actualizarán y las columnas de referencia, eliminar las columnas que se actualizarán de la tabla original, luego combinar las tablas usando CREATE TABLE tbl_new AS SELECT t.*, u.field1, u.field2 from tbl t NATURAL LEFT JOIN tmp_tbl u;, LEFT JOINlo que permite mantener filas para las que no hay actualización. Por supuesto, NATURALse puede cambiar a cualquier válido USING()o ON.
Skippy le Grand Gourou

2

Si los datos pueden estar disponibles en un archivo estructurado, puede leerlos con un contenedor de datos ajeno y realizar una fusión en la tabla de destino.


3
¿Qué quieres decir específicamente con "fusionar en la tabla de destino"? ¿Por qué es mejor usar FDW que COPIAR en una tabla temporal (como se sugiere en el tercer punto de la pregunta original)?
Yang

"Fusionar" como en la instrucción MERGE sql. El uso de FDW le permite hacerlo sin el paso adicional de copiar los datos en una tabla temporal. Supongo que no está reemplazando todo el conjunto de datos, y que habría una cierta cantidad de datos en el archivo que no representaría un cambio con respecto al conjunto de datos actual; si una cantidad significativa ha cambiado, entonces El reemplazo de la mesa puede valer la pena.
David Aldridge

1
@DavidAldridge: si bien está definido en el estándar SQL: 2003, todavía MERGEno se implementa en PostgreSQL . Las implementaciones en otros RDBMS varían bastante. Considere la información de etiqueta para MERGEy UPSERT.
Erwin Brandstetter

@ErwinBrandstetter [glotón] Oh, sí, bastante. Bueno, Merge es la guinda del pastel, supongo. Acceder a los datos sin el paso de importación a tabla temporal es realmente el quid de la técnica FDW.
David Aldridge
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.