El aumento de work_mem y shared_buffers en Postgres 9.2 ralentiza significativamente las consultas

39

Tengo una instancia de PostgreSQL 9.2 que se ejecuta en RHEL 6.3, máquina de 8 núcleos con 16 GB de RAM. El servidor está dedicado a esta base de datos. Dado que el postgresql.conf predeterminado es bastante conservador con respecto a la configuración de memoria, pensé que podría ser una buena idea permitir que Postgres use más memoria. Para mi sorpresa, seguir consejos en wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server ralentizó significativamente prácticamente todas las consultas que ejecuto, pero obviamente es más notable en las consultas más complejas.

También intenté ejecutar pgtune que dio la siguiente recomendación con más parámetros ajustados, pero eso no cambió nada. Sugiere shared_buffers de 1/4 de tamaño de RAM que parece estar en línea con los consejos en otros lugares (y en PG wiki en particular).

default_statistics_target = 50
maintenance_work_mem = 960MB
constraint_exclusion = on
checkpoint_completion_target = 0.9
effective_cache_size = 11GB
work_mem = 96MB
wal_buffers = 8MB
checkpoint_segments = 16
shared_buffers = 3840MB
max_connections = 80

Intenté reindexar toda la base de datos después de cambiar la configuración (usando reindex database), pero eso tampoco ayudó. Jugué con shared_buffers y work_mem. Cambiarlos gradualmente de los valores predeterminados muy conservadores (128k / 1MB) disminuyó gradualmente el rendimiento.

Realicé EXPLAIN (ANALYZE,BUFFERS)algunas consultas y el culpable parece ser que Hash Join es significativamente más lento. No me queda claro por qué.

Para dar un ejemplo específico, tengo la siguiente consulta. Se ejecuta en ~ 2100 ms en la configuración predeterminada y ~ 3300 ms en la configuración con tamaños de búfer aumentados:

select count(*) from contest c
left outer join contestparticipant cp on c.id=cp.contestId
left outer join teammember tm on tm.contestparticipantid=cp.id
left outer join staffmember sm on cp.id=sm.contestparticipantid
left outer join person p on p.id=cp.personid
left outer join personinfo pi on pi.id=cp.personinfoid
where pi.lastname like '%b%' or pi.firstname like '%a%';

EXPLAIN (ANALYZE,BUFFERS) para la consulta anterior:

La pregunta es ¿por qué estoy observando un rendimiento disminuido cuando aumento los tamaños de búfer? La máquina definitivamente no se está quedando sin memoria. La asignación si la memoria compartida en el sistema operativo es ( shmmaxy shmall) se establece en valores muy grandes, eso no debería ser un problema. Tampoco obtengo ningún error en el registro de Postgres. Estoy ejecutando autovacuum en la configuración predeterminada, pero no espero que tenga nada que ver con eso. Todas las consultas se ejecutaron en la misma máquina con unos segundos de diferencia, solo con una configuración modificada (y reiniciando PG).

Editar: acabo de encontrar un hecho particularmente interesante: cuando realizo la misma prueba en mi iMac de mediados de 2010 (OSX 10.7.5) también con Postgres 9.2.1 y 16 GB de RAM, no experimento la desaceleración. Específicamente:

set work_mem='1MB';
select ...; // running time is ~1800 ms
set work_mem='96MB';
select ...' // running time is ~1500 ms

Cuando hago exactamente la misma consulta (la anterior) con exactamente los mismos datos en el servidor, obtengo 2100 ms con work_mem = 1MB y 3200 ms con 96 MB.

La Mac tiene SSD, por lo que es comprensiblemente más rápido, pero muestra un comportamiento que esperaría.

Consulte también la discusión de seguimiento sobre pgsql-performance .

Petr Praus
fuente
1
Parece que en el caso más lento cada paso es consistentemente más lento. ¿Otras configuraciones permanecieron iguales?
dezso
1
Probablemente valga la pena preguntar esto en un foro más especializado en lugar del genérico que es. En este caso, sugiero la lista de correo pgsql-general archives.postgresql.org/pgsql-general
Colin 't Hart
1
¡Ah, e informe y responda su propia pregunta si encuentra la respuesta! (Esto está permitido, incluso alentado).
Colin 't Hart
1
Me pregunto cuán similar es Postgres a Oracle en este sentido: recuerdo un curso de Jonathan Lewis (gurú de Oracle) en el que demostró que asignar más memoria a los tipos a veces los hacía más lento. Olvidé los detalles, pero fue algo que tuvo que ver con que Oracle hace ordenamientos parciales y luego los escribe en el almacenamiento temporal y luego los combina. De alguna manera, más memoria hizo que este proceso fuera más lento.
Colin 't Hart
2
La pregunta ahora está publicada en pgsql-performance: archives.postgresql.org/pgsql-performance/2012-11/msg00004.php
Petr Praus

Respuestas:

28

En primer lugar, tenga en cuenta que work_mem es por operación y, por lo tanto, puede volverse excesivo rápidamente. En general, si no tiene problemas con la lentitud, dejaría work_mem solo hasta que lo necesite.

Al observar sus planes de consulta, una cosa que me sorprende es que los resultados del búfer son muy diferentes al observar los dos planes, y que incluso los análisis secuenciales son más lentos. Sospecho que el problema tiene que ver con el almacenamiento en caché de lectura anticipada y tener menos espacio para eso. Lo que esto significa es que está sesgando la memoria para la reutilización de índices y contra la lectura de tablas en el disco.


Tengo entendido que PostgreSQL buscará en la memoria caché una página antes de leerla desde el disco porque no sabe realmente si la memoria caché del sistema operativo contendrá esa página. Debido a que las páginas permanecen en el caché y porque ese caché es más lento que el caché del sistema operativo, esto cambia el tipo de consultas que son rápidas frente a las que son lentas. De hecho, al leer los planes, además de los problemas de work_mem, parece que toda la información de su consulta proviene del caché, pero es una cuestión de qué caché.

work_mem : cuánta memoria podemos asignar para una operación de combinación u ordenación relacionada. Esto es por operación, no por declaración o por back-end, por lo que una sola consulta compleja puede usar muchas veces esta cantidad de memoria. No está claro que esté alcanzando este límite, pero vale la pena señalarlo y tenerlo en cuenta. Si aumenta esto demasiado, pierde memoria que podría estar disponible para la memoria caché de lectura y los búferes compartidos.

shared_buffers : cuánta memoria asignar a la cola real de la página PostgreSQL. Ahora, idealmente, el conjunto interesante de su base de datos permanecerá en la memoria caché aquí y en los búferes de lectura. Sin embargo, lo que esto hace es garantizar que la información utilizada con más frecuencia en todos los servidores se almacene en caché y no se vacíe al disco. En Linux, este caché es significativamente más lento que el caché de disco del sistema operativo, pero ofrece garantías de que el caché de disco del sistema operativo no funciona y es transparente para PostgreSQL. Esto es claramente donde está tu problema.

Entonces, lo que sucede es que cuando tenemos una solicitud, primero verificamos los búferes compartidos ya que PostgreSQL tiene un conocimiento profundo de este caché y buscamos las páginas. Si no están allí, le pedimos al sistema operativo que los abra desde el archivo, y si el sistema operativo ha almacenado en caché el resultado, devuelve la copia en caché (esto es más rápido que los buffers compartidos, pero Pg no puede decir si está en caché o encendido disco, y el disco es mucho más lento, por lo que PostgreSQL generalmente no aprovechará esa oportunidad). Tenga en cuenta que esto también afecta el acceso a la página aleatorio versus secuencial. Por lo tanto, puede obtener un mejor rendimiento con configuraciones más bajas de shared_buffers.

Mi intuición es que probablemente obtenga un mejor rendimiento, o al menos más consistente, en entornos de alta concurrencia con configuraciones más grandes de shared_buffer. También tenga en cuenta que PostgreSQL toma esta memoria y la guarda, por lo que si tiene otras cosas ejecutándose en el sistema, los búferes de lectura mantendrán archivos leídos por otros procesos. Es un tema muy amplio y complejo. La configuración de búfer compartido más grande proporciona mejores garantías de rendimiento, pero en algunos casos puede ofrecer menos rendimiento.

Chris Travers
fuente
10

Además del efecto aparentemente paradójico de que el aumento work_memdisminuye el rendimiento ( @Chris podría tener una explicación), puede mejorar su función al menos de dos maneras.

  • Reescribe dos falsificaciones LEFT JOINcon JOIN. Eso podría confundir al planificador de consultas y conducir a planes inferiores.

SELECT count(*) AS ct
FROM   contest            c
JOIN   contestparticipant cp ON cp.contestId = c.id
JOIN   personinfo         pi ON pi.id = cp.personinfoid
LEFT   JOIN teammember    tm ON tm.contestparticipantid = cp.id
LEFT   JOIN staffmember   sm ON sm.contestparticipantid = cp.id
LEFT   JOIN person        p  ON p.id = cp.personid
WHERE (pi.firstname LIKE '%a%'
OR     pi.lastname  LIKE '%b%')
  • Suponiendo que sus patrones de búsqueda reales son más selectivos, use índices de trigrama en pi.firstnamey pi.lastnamepara admitir LIKEbúsquedas no ancladas . ( '%a%'También se admiten patrones más cortos como , pero no es probable que un índice ayude para predicados no selectivos):

CREATE INDEX personinfo_firstname_gin_idx ON personinfo USING gin (firstname gin_trgm_ops);
CREATE INDEX personinfo_lastname_gin_idx  ON personinfo USING gin (lastname gin_trgm_ops);

O un índice de varias columnas:

CREATE INDEX personinfo_name_gin_idx ON personinfo USING gin (firstname gin_trgm_ops, lastname gin_trgm_ops);

Debería hacer su consulta un poco más rápido. Necesita instalar el módulo adicional pg_trgm para esto. Detalles bajo estas preguntas relacionadas:


Además, ¿ha intentado configurar work_mem localmente, solo para la transacción actual ?

SET LOCAL work_mem = '96MB';

Esto evita que las transacciones concurrentes también consuman más RAM, posiblemente muriéndose de hambre.

Erwin Brandstetter
fuente
3
Quiero respaldar la sugerencia local de work_mem de Erwin. Debido a que work_mem cambia el tipo de consultas que son más rápidas, es posible que deba cambiarlo para algunas consultas. Es decir, los niveles bajos de work_mem son mejores para consultas que ordenan / unen un pequeño número de registros de manera compleja (es decir, muchas uniones), mientras que los niveles altos de work_mem son mejores para consultas que tienen algunos tipos pero que clasifican o unen un gran número de filas a la vez .
Chris Travers
Mientras tanto, mejoré la consulta (la pregunta es de octubre del año pasado) pero gracias :) Esta pregunta es más sobre el efecto inesperado que la consulta en particular. La consulta sirve principalmente para demostrar el efecto. Gracias por la sugerencia en el índice, ¡lo intentaré!
Petr Praus