Cómo acelerar el rendimiento de inserción en PostgreSQL


Estoy probando el rendimiento de inserción de Postgres. Tengo una tabla con una columna con el número como su tipo de datos. Hay un índice en él también. Llené la base de datos usando esta consulta:

insert into aNumber (id) values (564),(43536),(34560) ...

Inserté 4 millones de filas muy rápidamente 10,000 a la vez con la consulta anterior. Después de que la base de datos alcanzara los 6 millones de filas, el rendimiento disminuyó drásticamente a 1 millón de filas cada 15 minutos. ¿Hay algún truco para aumentar el rendimiento de inserción? Necesito un rendimiento de inserción óptimo en esto proyecto.

Usando Windows 7 Pro en una máquina con 5 GB de RAM.

Author: Erwin Brandstetter, 2012-08-31

6 answers

Ver llenar una base de datosen el manual de PostgreSQL, el excelente artículo de depeszsobre el tema, y esta pregunta.

(Tenga en cuenta que esta respuesta es sobre la carga masiva de datos en una base de datos existente o para crear una nueva. Si está interesado en restaurar el rendimiento de la base de datos con pg_restore o psql ejecución de la salida pg_dump, gran parte de esto no se aplica ya que pg_dump y pg_restore ya hacen cosas como crear disparadores e índices después de esquema+restauración de datos) .

Hay mucho que hacer. La solución ideal sería importar a una tabla UNLOGGED sin índices, luego cambiarla a registrada y agregar los índices. Desafortunadamente en PostgreSQL 9.4 no hay soporte para cambiar las tablas de UNLOGGED a logged. 9.5 agrega ALTER TABLE ... SET LOGGED para permitirle hacer esto.

Si puede desconectar su base de datos para la importación masiva, use pg_bulkload.

De lo contrario:

  • Desactivar cualquier disparador en el cuadro

  • Soltar índices antes de iniciar la importación, volver a crearlos después. (Toma mucho menos tiempo construir un índice en una pasada que agregar los mismos datos progresivamente, y el índice resultante es mucho más compacto).

  • Si realiza la importación dentro de una sola transacción, es seguro eliminar las restricciones de clave externa, realizar la importación y volver a crear las restricciones antes de confirmar. No haga esto si la importación está dividida múltiples transacciones, ya que puede introducir datos no válidos.

  • Si es posible, utilice COPY en lugar de INSERT s

  • Si no puede usar COPY considere usar INSERTs de múltiples valores si es práctico. Parece que ya estás haciendo esto. Sin embargo, no intente enumerar también muchos valores en un solo VALUES; esos valores tienen que caber en la memoria un par de veces, así que manténgalo en unos pocos cientos por declaración.

  • Agrupe sus inserciones en explicit transacciones, haciendo cientos de miles o millones de insertos por transacción. No hay límite práctico AFAIK, pero el procesamiento por lotes le permitirá recuperarse de un error marcando el inicio de cada lote en sus datos de entrada. De nuevo, parece que ya estás haciendo esto.

  • Use synchronous_commit=off y un enorme commit_delay para reducir los costos de fsync (). Sin embargo, esto no ayudará mucho si has agrupado tu trabajo en grandes transacciones.

  • INSERT o COPY en paralelo desde varias conexiones. La cantidad depende del subsistema de disco de su hardware; como regla general, desea una conexión por disco duro físico si utiliza almacenamiento conectado directamente.

  • Establezca un valor checkpoint_segments alto y habilite log_checkpoints. Mire los registros de PostgreSQL y asegúrese de que no se queja de que los puntos de control ocurren con demasiada frecuencia.

  • Si y solo si no le importa perder todo su clúster de PostgreSQL (su base de datos y cualquier otra en el mismo clúster) a una corrupción catastrófica si el sistema se bloquea durante la importación, puede detener Pg, establecer fsync=off, iniciar Pg, hacer su importación, luego (vitalmente) detener Pg y establecer fsync=on de nuevo. Ver Configuración WAL. No haga esto si ya hay algún dato que le importe en cualquier base de datos en su instalación de PostgreSQL. Si configura fsync=off también puede configurar full_page_writes=off; nuevamente, solo recuerde volver a activarlo después de la importación para evitar la corrupción de la base de datos y la pérdida de datos. Ver configuración no duradera en el Pg manual.

Usted también debe mirar el ajuste de su sistema:

  • Utilice SSD de buena calidad para el almacenamiento tanto como sea posible. Los buenos SSD con cachés de escritura confiables y protegidos con energía hacen que las tasas de confirmación sean increíblemente más rápidas. Son menos beneficiosos cuando sigue los consejos anteriores, lo que reduce las descargas de disco / el número de fsync()s, pero aún puede ser una gran ayuda. No use SSD baratos sin una protección adecuada contra fallas de energía a menos que no le importe mantener sus datos.

  • Si está utilizando RAID 5 o RAID 6 para el almacenamiento con conexión directa, deténgase ahora. Haga una copia de seguridad de sus datos, reestructure su matriz RAID a RAID 10 e inténtelo de nuevo. RAID 5/6 no tiene esperanza para el rendimiento de escritura masiva, aunque un buen controlador RAID con una gran caché puede ayudar.

  • Si tiene la opción de usar un controlador RAID de hardware con una gran caché de escritura respaldada por batería, esto realmente puede mejorar el rendimiento de escritura para cargas de trabajo con cometer. No ayuda tanto si estás usando commit async con un commit_delay o si estás haciendo menos transacciones grandes durante la carga masiva.

  • Si es posible, almacene WAL (pg_xlog) en una matriz disk / disk separada. No tiene mucho sentido usar un sistema de archivos separado en el mismo disco. La gente a menudo elige usar un par RAID1 para WAL. Una vez más, esto tiene más efecto en los sistemas con altas tasas de confirmación, y tiene poco efecto si está utilizando una tabla no registrada como la carga de datos objetivo.

También puede interesarle Optimizar PostgreSQL para pruebas rápidas.

 380
Author: Craig Ringer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:30

Use COPY table TO ... WITH BINARYque según la documentación es " algo más rápido que los formatos de texto y CSV."Solo haga esto si tiene millones de filas para insertar y si se siente cómodo con los datos binarios.

Aquí hay una receta de ejemplo en Python, usando psycopg2 con entrada binaria.

 10
Author: Mike T,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:55:03

Además de la excelente publicación de Craig Ringer y la publicación de blog de depesz, si desea acelerar sus inserciones a través de la interfaz ODBC (psqlodbc) mediante el uso de inserciones de declaración preparada dentro de una transacción, hay algunas cosas adicionales que debe hacer para que funcione rápidamente:

  1. Establezca el nivel de reversión de errores en "Transacción" especificando Protocol=-1 en la cadena de conexión. De forma predeterminada, psqlodbc utiliza el nivel "Statement", que crea un SAVEPOINT para cada sentencia en lugar de una transacción completa, lo que hace que las inserciones sean más lentas.
  2. Utilice instrucciones preparadas del lado del servidor especificando UseServerSidePrepare=1 en la cadena de conexión. Sin esta opción, el cliente envía la instrucción insert completa junto con cada fila que se inserta.
  3. Desactivar la confirmación automática en cada instrucción usando SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
  4. Una vez insertadas todas las filas, confirme la transacción usando SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);. No hay necesidad de abrir explícitamente una transacción.

Desafortunadamente, psqlodbc "implementa" SQLBulkOperations emitiendo una serie de instrucciones insert no preparadas, de modo que para lograr el insert más rápido uno necesita codificar los pasos anteriores manualmente.

 8
Author: Maxim Egorushkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-22 11:43:58

Pasé alrededor de 6 horas en el mismo tema hoy. Los insertos van a una velocidad "regular" (menos de 3 segundos por 100K) hasta 5MI (de un total de 30MI) filas y luego el rendimiento se hunde drásticamente (hasta 1 minuto por 100K).

No voy a enumerar todas las cosas que no funcionaron y cortaron directamente a la reunión.

I dejó caer una clave primaria en la tabla de destino (que era un GUID) y mis 30MI o filas fluyeron felizmente a su destino a una velocidad constante de menos superior a 3 segundos por 100K.

 2
Author: Dennis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-10 23:19:26

Para un rendimiento de inserción óptimo, deshabilite el índice si es una opción para usted. Aparte de eso, un mejor hardware (disco, memoria) también es útil

 0
Author: Icarus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-30 22:43:06

También encontré este problema de rendimiento de inserción. Mi solución es generar algunas rutinas para terminar el trabajo de inserción. Mientras tanto, SetMaxOpenConns debe recibir un número adecuado, de lo contrario se alertarían demasiados errores de conexión abierta.

db, _ := sql.open() 
db.SetMaxOpenConns(SOME CONFIG INTEGER NUMBER) 
var wg sync.WaitGroup
for _, query := range queries {
    wg.Add(1)
    go func(msg string) {
        defer wg.Done()
        _, err := db.Exec(msg)
        if err != nil {
            fmt.Println(err)
        }
    }(query)
}
wg.Wait()

La velocidad de carga es mucho más rápida para mi proyecto. Este fragmento de código acaba de dar una idea de cómo funciona. Los lectores deben ser capaces de modificarlo fácilmente.

 0
Author: Patrick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-13 21:29:04