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:
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
crear una regla de
insert_or_replace
para que todo pero el ocasional de eliminación se convierte en varias filas insertaCREATE 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.
¿Cómo son los índices de las tablas pertinentes? ¿Alguna llave extranjera? –
No en el script de prueba, no. En el mundo real, uno. –
¿Puedes publicar un 'EXPLICAR ANÁLISIS' de tu 'ACTUALIZACIÓN'? Quiero saber qué piensa el estimador que debería suceder. – Sean