2009-06-07 18 views
19

Tenemos una aplicación bastante específica que usa PostgreSQL 8.3 como un back-end de almacenamiento (usando Python y psycopg2). Las operaciones que realizamos para las tablas importantes son, en la mayoría de los casos, inserciones o actualizaciones (rara vez se eliminan o seleccionan).¿Cómo puedo acelerar las operaciones de actualización/reemplazo en PostgreSQL?

Por motivos de cordura, hemos creado nuestra propia capa similar a Data Mapper que funciona razonablemente bien, pero tiene un gran cuello de botella, el rendimiento de la actualización. Por supuesto, no espero que el escenario de actualización/reemplazo sea tan rápido como el de "insertar en una mesa vacía", pero sería bueno acercarse un poco más.

Tenga en cuenta que este sistema está libre de actualizaciones simultáneas

Siempre nos fijamos todos los campos de cada fila en una actualización, que se pueden ver en la terminología en uso la palabra 'reemplazar' en mis pruebas. Yo he probado hasta ahora dos enfoques a nuestro problema de actualización:

  1. crear un procedimiento replace() que toma una matriz de filas para actualizar:

    CREATE OR REPLACE FUNCTION replace_item(data item[]) RETURNS VOID AS $$ 
    BEGIN 
        FOR i IN COALESCE(array_lower(data,1),0) .. COALESCE(array_upper(data,1),-1) LOOP 
         UPDATE item SET a0=data[i].a0,a1=data[i].a1,a2=data[i].a2 WHERE key=data[i].key; 
        END LOOP; 
    END; 
    $$ LANGUAGE plpgsql 
    
  2. crear una regla de insert_or_replace para que todo pero el ocasional de eliminación se convierte en varias filas inserta

    CREATE RULE "insert_or_replace" AS 
        ON INSERT TO "item" 
        WHERE EXISTS(SELECT 1 FROM item WHERE key=NEW.key) 
        DO INSTEAD 
         (UPDATE item SET a0=NEW.a0,a1=NEW.a1,a2=NEW.a2 WHERE key=NEW.key); 
    

Th ESE tanto acelera las actualizaciones un poco justo, aunque este último se ralentiza inserta un bit:

Multi-row insert   : 50000 items inserted in 1.32 seconds averaging 37807.84 items/s 
executemany() update  : 50000 items updated in 26.67 seconds averaging 1874.57 items/s 
update_andres    : 50000 items updated in 3.84 seconds averaging 13028.51 items/s 
update_merlin83 (i/d/i) : 50000 items updated in 1.29 seconds averaging 38780.46 items/s 
update_merlin83 (i/u)  : 50000 items updated in 1.24 seconds averaging 40313.28 items/s 
replace_item() procedure : 50000 items replaced in 3.10 seconds averaging 16151.42 items/s 
Multi-row insert_or_replace: 50000 items inserted in 2.73 seconds averaging 18296.30 items/s 
Multi-row insert_or_replace: 50000 items replaced in 2.02 seconds averaging 24729.94 items/s 

notas al azar sobre la marcha de prueba:

  • Todas las pruebas se ejecutan en el mismo equipo que la base de datos reside; conectando a localhost.
  • Las inserciones y actualizaciones se aplican a la base de datos en lotes de 500 elementos, cada uno enviado en su propia transacción (ACTUALIZADO).
  • Todas las pruebas de actualización/reemplazo usaron los mismos valores que ya estaban en la base de datos.
  • Se han escapado todos los datos utilizando la función psycopg2 adapt().
  • Todas las tablas se truncan y la aspiradora antes de su uso (AÑADIDO, en carreras anteriores ocurrió solamente truncamiento)
  • La tabla es el siguiente:

    CREATE TABLE item (
        key MACADDR PRIMARY KEY, 
        a0 VARCHAR, 
        a1 VARCHAR, 
        a2 VARCHAR 
    ) 
    

Por lo tanto, la verdadera pregunta es: ¿Cómo puedo acelerar las operaciones de actualización/reemplazo un poco más? (Creo que estos hallazgos podrían ser "lo suficientemente buenos", pero no quiero darme por vencido sin tocar el grupo SO :)

También hay sugerencias para un replace_item más elegante(), o evidencia de que mis pruebas son completamente roto sería muy bienvenido.

El script de prueba está disponible here si quiere intentar reproducir. Recuerde verificarlo primero aunque ... es WorksForMe, pero ...

Deberá editar el db.connect() línea para adaptarse a su configuración.

EDITAR

Gracias a Andrés en #postgresql @ freenode tengo otra prueba con una actualización de una sola consulta; muy parecido a un inserto de varias filas (listado como update_andres arriba).

UPDATE item 
SET a0=i.a0, a1=i.a1, a2=i.a2 
FROM (VALUES ('00:00:00:00:00:01', 'v0', 'v1', 'v2'), 
      ('00:00:00:00:00:02', 'v3', 'v4', 'v5'), 
      ... 
    ) AS i(key, a0, a1, a2) 
WHERE item.key=i.key::macaddr 

EDITAR

Gracias a merlin83 en #postgresql @ freenode y el jarro/GTM continuación tengo otra prueba con un inserto-a-temp/borrar/enfoque de inserción (que aparece como "update_merlin83 (i/d/i) "arriba).

INSERT INTO temp_item (key, a0, a1, a2) 
    VALUES (
     ('00:00:00:00:00:01', 'v0', 'v1', 'v2'), 
     ('00:00:00:00:00:02', 'v3', 'v4', 'v5'), 
     ...); 

DELETE FROM item 
USING temp_item 
WHERE item.key=temp_item.key; 

INSERT INTO item (key, a0, a1, a2) 
    SELECT key, a0, a1, a2 
    FROM temp_item; 

Mi primera impresión es que estas pruebas no son muy representativos de la actuación en el escenario del mundo real, pero creo que las diferencias son lo suficientemente grande como para dar una indicación de los enfoques más prometedores para una mayor investigación. El script perftest.py también contiene todas las actualizaciones para aquellos que deseen verificarlo. Es bastante feo, sin embargo, así que no olvide sus gafas :)

EDIT

Andrés en #postgresql @ freenode señaló que debería probar con una variante de inserción a temp/actualización (catalogado como "update_merlin83 (i/u)" arriba).

INSERT INTO temp_item (key, a0, a1, a2) 
    VALUES (
     ('00:00:00:00:00:01', 'v0', 'v1', 'v2'), 
     ('00:00:00:00:00:02', 'v3', 'v4', 'v5'), 
     ...); 

UPDATE item 
SET a0=temp_item.a0, a1=temp_item.a1, a2=temp_item.a2 
FROM temp_item 
WHERE item.key=temp_item.key 

EDITAR

edición Probablemente definitiva: he cambiado de secuencia de comandos para que coincida con nuestro escenario de carga mejor, y parece que los números tienen incluso al escalar un poco las cosas y añadiendo algo de aleatoriedad. Si alguien obtiene números muy diferentes de algún otro escenario, estaría interesado en saberlo.

+0

¿Cómo son los índices de las tablas pertinentes? ¿Alguna llave extranjera? –

+0

No en el script de prueba, no. En el mundo real, uno. –

+0

¿Puedes publicar un 'EXPLICAR ANÁLISIS' de tu 'ACTUALIZACIÓN'? Quiero saber qué piensa el estimador que debería suceder. – Sean

Respuesta

1

En su insert_or_replace. intente esto:

WHERE EXISTS(SELECT 1 FROM item WHERE key=NEW.key LIMIT 1) 

en lugar de

WHERE EXISTS(SELECT 1 FROM item WHERE key=NEW.key) 

Como se señaló en los comentarios, que probablemente no hará nada. Todo lo que tengo que agregar, entonces, es que siempre se puede acelerar el rendimiento INSERT/UPDATE mediante la eliminación de índices. Es probable que esto no sea algo que quieras hacer a menos que descubras que tu tabla está sobreindexada, pero al menos debería ser revisada.

+0

Lo más probable es que es innecesaria - extracto de los documentos (http://www.postgresql.org/docs/current/static/functions -subquery.html # AEN15270): "Por lo general, la subconsulta solo se ejecutará lo suficiente como para determinar si se devuelve al menos una fila, pero no hasta la finalización". –

+0

Ah, gracias. No sabía qué tan inteligente era EXISTS. Ahora lo hago. :) – chaos

+0

La clave es única, por lo que solo devolverá una fila. Sin embargo, lo intenté, y no hubo un cambio notable en el rendimiento de ninguna manera. ¡Gracias! –

1

En Oracle, bloquear la mesa definitivamente ayudaría. Es posible que desee probar eso con PostgreSQL, también.

+0

Intenté ejecutar todas las pruebas con la tabla bloqueada en todas las transacciones; ningún cambio. –

2

Tuve una situación similar hace unos meses y terminé obteniendo el impulso de velocidad más grande a partir de un tamaño de transacción/fragmento sintonizado. También es posible que desee comprobar el registro de una advertencia de punto de control durante la prueba y sintonizar adecuadamente.

+0

Definitivamente buscaré las advertencias del punto de control, parece muy relevante; ¡Gracias! –

2

Parece que obtendría beneficios del uso de WAL (escritura anticipada de registro) con un UPS para almacenar en caché las actualizaciones entre las grabaciones de disco.

wal_buffers Esta configuración decide el número de almacenamientos intermedios que WAL (Write ahead Log) puede tener. Si su base de datos tiene muchas transacciones de escritura, establecer este valor un poco más alto que el predeterminado podría dar como resultado un mejor uso del espacio en disco. Experimenta y decideUn buen comienzo sería alrededor de 32-64 correspondiente a 256-512K de memoria.

http://www.varlena.com/GeneralBits/Tidbits/perf.html

4

La forma habitual hago estas cosas en pg es: cargar datos en bruto que emparejan tabla de destino en la tabla temporal (sin restricciones) usando copiar, fusionar (la parte divertida), el beneficio.

escribí una función merge_by_key específicamente para estas situaciones:

http://mbk.projects.postgresql.org/

Los documentos no son terriblemente amable, pero me gustaría sugerir lo que le da un buen aspecto.

+0

Los puntos del proceso general son: carga en la temperatura para evitar los costos de ida y vuelta de la red y evitar crear múltiples cursores (portales) para cada fila cargada (sí, es rápido con ejecutar muchos, pero COPY wtf-pwns-it wrt a la eficiencia). Utilice una función/proceso de combinación para evitar la creación de reglas/activadores que alteren la semántica de una instrucción de inserción. Lo hice de las dos maneras, y siempre he preferido el proceso de fusión porque es explícito. Si el proceso de fusión no es lo suficientemente eficiente, usted tiene que mirar a la suspensión de índice (recreación)/partición o http://pgfoundry.org/projects/pgbulkload/ – jwp

Cuestiones relacionadas