sql >> Base de Datos >  >> RDS >> PostgreSQL

Una hoja de trucos de rendimiento para PostgreSQL

El rendimiento es una de las tareas más importantes y complejas a la hora de gestionar una base de datos. Puede verse afectado por la configuración, el hardware o incluso el diseño del sistema. Por defecto, PostgreSQL está configurado pensando en la compatibilidad y la estabilidad, ya que el rendimiento depende mucho del hardware y de nuestro propio sistema. Podemos tener un sistema con una gran cantidad de datos que se leen, pero la información no cambia con frecuencia. O podemos tener un sistema que escriba continuamente. Por este motivo, es imposible definir una configuración predeterminada que funcione para todos los tipos de cargas de trabajo.

En este blog, veremos cómo se analiza la carga de trabajo o las consultas que se están ejecutando. Luego revisaremos algunos parámetros básicos de configuración para mejorar el rendimiento de nuestra base de datos PostgreSQL. Como mencionamos, veremos solo algunos de los parámetros. La lista de parámetros de PostgreSQL es extensa, solo mencionaremos algunos de los clave. No obstante, siempre se puede consultar la documentación oficial para profundizar en los parámetros y configuraciones que nos parecen más importantes o útiles en nuestro entorno.

EXPLICAR

Uno de los primeros pasos que podemos dar para entender cómo mejorar el rendimiento de nuestra base de datos es analizar las consultas que se realizan.

PostgreSQL diseña un plan de consulta para cada consulta que recibe. Para ver este plan, usaremos EXPLAIN.

La estructura de un plan de consulta es un árbol de nodos de plan. Los nodos en el nivel inferior del árbol son nodos de exploración. Devuelven filas sin procesar de una tabla. Existen diferentes tipos de nodos de escaneo para diferentes métodos de acceso a la tabla. La salida EXPLAIN tiene una línea para cada nodo en el árbol del plan.

world=# EXPLAIN SELECT * FROM city t1,country t2 WHERE id>100 AND t1.population>700000 AND t2.population<7000000;
                               QUERY PLAN                                
--------------------------------------------------------------------------
Nested Loop  (cost=0.00..734.81 rows=50662 width=144)
  ->  Seq Scan on city t1  (cost=0.00..93.19 rows=347 width=31)
        Filter: ((id > 100) AND (population > 700000))
  ->  Materialize  (cost=0.00..8.72 rows=146 width=113)
        ->  Seq Scan on country t2  (cost=0.00..7.99 rows=146 width=113)
              Filter: (population < 7000000)
(6 rows)

Este comando muestra cómo se escanearán las tablas en nuestra consulta. Veamos a qué corresponden estos valores que podemos observar en nuestro EXPLAIN.

  • El primer parámetro muestra la operación que el motor está realizando en los datos de este paso.
  • Costo estimado de puesta en marcha. Este es el tiempo transcurrido antes de que pueda comenzar la fase de salida.
  • Coste total estimado. Esto se establece en el supuesto de que el nodo del plan se ejecuta hasta su finalización. En la práctica, el nodo principal de un nodo puede no llegar a leer todas las filas disponibles.
  • Número estimado de filas generadas por este nodo del plan. Una vez más, se supone que el nodo se ejecuta hasta su finalización.
  • Ancho promedio estimado de las filas generadas por este nodo del plan.

La parte más crítica de la pantalla es el costo estimado de ejecución de la declaración, que es la suposición del planificador sobre cuánto tiempo llevará ejecutar la declaración. Al comparar la efectividad de una consulta con la otra, en la práctica compararemos los valores de costo de las mismas.

Es importante comprender que el costo de un nodo de nivel superior incluye el costo de todos sus nodos secundarios. También es importante darse cuenta de que el costo solo refleja las cosas que le importan al planificador. En particular, el costo no considera el tiempo empleado en transmitir filas de resultados al cliente, lo que podría ser un factor importante en el tiempo real transcurrido; pero el planificador lo ignora porque no puede cambiarlo alterando el plan.

Los costos se miden en unidades arbitrarias determinadas por los parámetros de costos del planificador. La práctica tradicional es medir los costos en unidades de recuperación de páginas de disco; es decir, seq_page_cost se establece convencionalmente en 1.0 y los otros parámetros de costo se establecen en relación con eso.

EXPLICAR ANALIZAR

Con esta opción, EXPLAIN ejecuta la consulta y luego muestra los recuentos de filas reales y el tiempo de ejecución real acumulado dentro de cada nodo del plan, junto con las mismas estimaciones que muestra EXPLAIN simple.

Veamos un ejemplo del uso de esta herramienta.

world=# EXPLAIN ANALYZE SELECT * FROM city t1,country t2 WHERE id>100 AND t1.population>700000 AND t2.population<7000000;
                                                     QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
Nested Loop  (cost=0.00..734.81 rows=50662 width=144) (actual time=0.081..22.066 rows=51100 loops=1)
  ->  Seq Scan on city t1  (cost=0.00..93.19 rows=347 width=31) (actual time=0.069..0.618 rows=350 loops=1)
        Filter: ((id > 100) AND (population > 700000))
        Rows Removed by Filter: 3729
  ->  Materialize  (cost=0.00..8.72 rows=146 width=113) (actual time=0.000..0.011 rows=146 loops=350)
        ->  Seq Scan on country t2  (cost=0.00..7.99 rows=146 width=113) (actual time=0.007..0.058 rows=146 loops=1)
              Filter: (population < 7000000)
              Rows Removed by Filter: 93
Planning time: 0.136 ms
Execution time: 24.627 ms
(10 rows)

Si no encontramos el motivo por el que nuestras consultas tardan más de lo debido, podemos consultar este blog para obtener más información.

VACÍO

El proceso VACUUM es responsable de varias tareas de mantenimiento dentro de la base de datos, una de ellas recupera el almacenamiento ocupado por tuplas muertas. En el funcionamiento normal de PostgreSQL, las tuplas eliminadas u obsoletas por una actualización no se eliminan físicamente de su tabla; permanecen presentes hasta que se realiza un VACÍO. Por lo tanto, es necesario hacer el VACUUM periódicamente, especialmente en las tablas que se actualizan con frecuencia.

Si el VACÍO está consumiendo demasiado tiempo o recursos, significa que debemos hacerlo con más frecuencia, para que cada operación tenga menos que limpiar.

En cualquier caso, es posible que deba desactivar el VACÍO, por ejemplo, al cargar datos en grandes cantidades.

El VACUUM simplemente recupera espacio y lo pone a disposición para su reutilización. Esta forma del comando puede operar en paralelo con la lectura y escritura normal de la tabla, ya que no se obtiene un bloqueo exclusivo. Sin embargo, el espacio adicional no se devuelve al sistema operativo (en la mayoría de los casos); solo está disponible para su reutilización dentro de la misma tabla.

VACUUM FULL reescribe todo el contenido de la tabla en un nuevo archivo de disco sin espacio adicional, lo que permite que el espacio no utilizado regrese al sistema operativo. Este formulario es mucho más lento y requiere un bloqueo exclusivo en cada tabla durante el procesamiento.

VACUUM ANALYZE realiza un VACUUM y luego un ANALYZE para cada tabla seleccionada. Esta es una forma práctica de combinar scripts de mantenimiento de rutina.

ANALYZE recopila estadísticas sobre el contenido de las tablas en la base de datos y almacena los resultados en pg_statistic. Posteriormente, el planificador de consultas utiliza estas estadísticas para ayudar a determinar los planes de ejecución más eficientes para las consultas.

Descargue el documento técnico hoy Administración y automatización de PostgreSQL con ClusterControlObtenga información sobre lo que necesita saber para implementar, monitorear, administrar y escalar PostgreSQLDescargar el documento técnico

Parámetros de configuración

Para modificar estos parámetros debemos editar el archivo $PGDATA/postgresql.conf. Debemos tener en cuenta que algunos de ellos requieren un reinicio de nuestra base de datos.

máx_conexiones

Determina el número máximo de conexiones simultáneas a nuestra base de datos. Hay recursos de memoria que se pueden configurar por cliente, por lo tanto, la cantidad máxima de clientes puede sugerir la cantidad máxima de memoria utilizada.

superusuario_conexiones_reservadas

En caso de llegar al límite de max_connection, estas conexiones quedan reservadas para superusuario.

búferes_compartidos

Establece la cantidad de memoria que utiliza el servidor de la base de datos para los búferes de memoria compartida. Si tiene un servidor de base de datos dedicado con 1 GB o más de RAM, un valor inicial razonable para shared_buffers es el 25% de la memoria de su sistema. Las configuraciones más grandes para shared_buffers generalmente requieren un aumento correspondiente en max_wal_size, para extender el proceso de escritura de grandes cantidades de datos nuevos o modificados durante un período de tiempo más largo.

temp_buffers

Establece el número máximo de búferes temporales utilizados para cada sesión. Estos son búferes de sesión locales que se usan solo para acceder a tablas temporales. Una sesión asignará los búferes temporales según sea necesario hasta el límite dado por temp_buffers.

trabajo_mem

Especifica la cantidad de memoria que utilizarán las operaciones internas de las tablas ORDER BY, DISTINCT, JOIN y hash antes de escribir en los archivos temporales del disco. Al configurar este valor, debemos tener en cuenta que varias sesiones estarán ejecutando estas operaciones al mismo tiempo y cada operación podrá usar tanta memoria como especifica este valor antes de comenzar a escribir datos en archivos temporales.

Esta opción se llamaba sort_mem en versiones anteriores de PostgreSQL.

mantenimiento_trabajo_mem

Especifica la cantidad máxima de memoria que usarán las operaciones de mantenimiento, como VACUUM, CREATE INDEX y ALTER TABLE ADD FOREIGN KEY. Dado que una sesión solo puede ejecutar una de estas operaciones al mismo tiempo, y una instalación generalmente no tiene muchas de ellas ejecutándose simultáneamente, puede ser más grande que el archivo work_mem. Las configuraciones más grandes pueden mejorar el rendimiento de VACUUM y restauraciones de bases de datos.

Cuando se ejecuta el autovacuum, a esta memoria se le puede asignar la cantidad de veces que se configura el parámetro autovacuum_max_workers, por lo que debemos tener esto en cuenta, o en su defecto, configurar el parámetro autovacuum_work_mem para gestionar esto por separado.

fsync

Si fsync está habilitado, PostgreSQL intentará asegurarse de que las actualizaciones se escriban físicamente en el disco. Esto garantiza que el clúster de la base de datos se pueda recuperar a un estado coherente después de un bloqueo del sistema operativo o del hardware.

Si bien deshabilitar fsync generalmente mejora el rendimiento, puede causar la pérdida de datos en caso de una falla de energía o un bloqueo del sistema. Por lo tanto, solo es recomendable desactivar fsync si puede recrear fácilmente toda su base de datos a partir de datos externos.

segmentos_de_punto_de_control (PostgreSQL <9.5)

Número máximo de segmentos de archivos de registro entre puntos de control WAL automáticos (cada segmento es normalmente de 16 megabytes). El aumento de este parámetro puede aumentar la cantidad de tiempo necesario para recuperar las fallas. En un sistema con mucho tráfico, puede afectar el rendimiento si se establece en un valor muy bajo. Se recomienda aumentar el valor de checkpoint_segments en sistemas con muchas modificaciones de datos.

Además, una buena práctica es guardar los archivos WAL en un disco que no sea PGDATA. Esto es útil tanto para equilibrar la escritura como para la seguridad en caso de falla del hardware.

A partir de PostgreSQL 9.5, se eliminó la variable de configuración "checkpoint_segments" y se reemplazó por "max_wal_size" y "min_wal_size"

tamaño_máx_wal_(PostgreSQL>=9.5)

Tamaño máximo que se le permite crecer a la WAL entre los puntos de control. El tamaño de WAL puede exceder max_wal_size en circunstancias especiales. Aumentar este parámetro puede aumentar la cantidad de tiempo necesario para recuperar fallas.

min_wal_size (PostgreSQL>=9.5)

Cuando el archivo WAL se mantiene por debajo de este valor, se recicla para uso futuro en un punto de control, en lugar de eliminarse. Esto se puede usar para garantizar que se reserve suficiente espacio WAL para manejar picos en el uso de WAL, por ejemplo, cuando se ejecutan trabajos por lotes grandes.

método_wal_sync_

Método utilizado para forzar actualizaciones de WAL en el disco. Si fsync está deshabilitado, esta configuración no tiene efecto.

wal_buffers

La cantidad de memoria compartida utilizada para los datos WAL que aún no se han escrito en el disco. La configuración predeterminada es aproximadamente el 3% de shared_buffers, no menos de 64 KB o más que el tamaño de un segmento WAL (generalmente 16 MB). Establecer este valor en al menos unos pocos MB puede mejorar el rendimiento de escritura en un servidor con muchas transacciones simultáneas.

tamaño_caché_efectivo

El planificador de consultas utiliza este valor para tener en cuenta los planes que pueden o no caber en la memoria. Esto se tiene en cuenta en las estimaciones de costos del uso de un índice; un valor alto hace que sea más probable que se utilicen exploraciones de índice y un valor bajo hace que sea más probable que se utilicen exploraciones secuenciales. Un valor razonable sería el 50% de la RAM.

objetivo_estadístico_predeterminado

PostgreSQL recopila estadísticas de cada una de las tablas de su base de datos para decidir cómo se ejecutarán las consultas en ellas. De forma predeterminada, no recopila demasiada información y, si no obtiene buenos planes de ejecución, debe aumentar este valor y luego ejecutar ANALYZE en la base de datos nuevamente (o esperar el AUTOVACUUM).

commit_sincrónico

Especifica si la confirmación de la transacción esperará a que los registros WAL se escriban en el disco antes de que el comando devuelva una indicación de "éxito" al cliente. Los valores posibles son:"on", "remote_apply", "remote_write", "local" y "off". La configuración predeterminada es "activada". Cuando está deshabilitado, puede haber una demora entre el momento en que el cliente regresa y cuando se garantiza que la transacción es segura contra un bloqueo del servidor. A diferencia de fsync, deshabilitar este parámetro no crea ningún riesgo de inconsistencia en la base de datos:un bloqueo del sistema operativo o de la base de datos puede resultar en la pérdida de algunas transacciones recientes supuestamente cometidas, pero el estado de la base de datos será exactamente el mismo que si esas transacciones había sido cancelado limpiamente. Por lo tanto, desactivar synchronous_commit puede ser una alternativa útil cuando el rendimiento es más importante que la certeza exacta sobre la durabilidad de una transacción.

Registro

Hay varios tipos de datos para registrar que pueden ser útiles o no. Veamos algunos de ellos:

  • log_min_error_statement:establece el nivel de registro mínimo.
  • log_min_duration_statement:se utiliza para registrar consultas lentas en el sistema.
  • log_line_prefix:adhiere información al comienzo de cada línea de registro.
  • log_statement:puede elegir entre NINGUNO, DDL, MOD, TODO. El uso de "todos" puede causar problemas de rendimiento.

Diseño

En muchos casos, el diseño de nuestra base de datos puede afectar el rendimiento. Debemos tener cuidado en nuestro diseño, normalizando nuestro esquema y evitando datos redundantes. En muchos casos es conveniente tener varias mesas pequeñas en lugar de una mesa grande. Pero como decíamos antes, todo depende de nuestro sistema y no existe una única solución posible.

También debemos usar los índices de manera responsable. No debemos crear índices para cada campo o combinación de campos, ya que, aunque no tenemos que recorrer toda la tabla, estamos usando espacio en disco y agregando sobrecarga para escribir operaciones.

Otra herramienta muy útil es la gestión del pool de conexiones. Si tenemos un sistema con mucha carga, podemos usar esto para evitar saturar las conexiones en la base de datos y poder reutilizarlas.

Hardware

Como mencionamos al comienzo de este blog, el hardware es uno de los factores importantes que afectan directamente el rendimiento de nuestra base de datos. Veamos algunos puntos a tener en cuenta.

  • Memoria:cuanta más RAM tengamos, más datos de memoria podremos manejar y eso significa un mejor rendimiento. La velocidad de escritura y lectura en disco es mucho más lenta que en memoria, por lo tanto, cuanta más información podamos tener en memoria, mejor rendimiento tendremos.
  • CPU:Quizá no tenga mucho sentido decir esto, pero cuanto más CPU tengamos, mejor. En cualquier caso no es lo más importante en cuanto a hardware, pero si podemos tener una buena CPU, nuestra capacidad de procesamiento mejorará y eso repercutirá directamente en nuestra base de datos.
  • Disco duro:Tenemos varios tipos de discos que podemos usar, SCSI, SATA, SAS, IDE. También disponemos de discos de estado sólido. Debemos comparar calidad/precio, que debemos usar para comparar su velocidad. Pero el tipo de disco no es lo único a tener en cuenta, también hay que ver cómo configurarlos. Si queremos un buen rendimiento, podemos usar RAID10, manteniendo los WAL en otro disco fuera del RAID. No se recomienda utilizar RAID5 ya que el rendimiento de este tipo de RAID para bases de datos no es bueno.

Conclusión

Luego de tomar en cuenta los puntos mencionados en este blog, podemos realizar un benchmark para verificar el comportamiento de la base de datos.

También es importante tener monitorizada nuestra base de datos para determinar si estamos ante un problema de rendimiento y poder solucionarlo lo antes posible. Para esta tarea existen varias herramientas como Nagios, ClusterControl o Zabbix, entre otras, que nos permiten no solo monitorear, sino que con algunas de ellas, nos permite tomar acciones proactivas antes de que ocurra el problema. Con ClusterControl, además de monitoreo, administración y varias otras utilidades, podemos recibir recomendaciones sobre qué acciones podemos tomar al recibir alertas de rendimiento. Esto nos permite tener una idea de cómo resolver problemas potenciales.

Este blog no pretende ser una guía exhaustiva sobre cómo mejorar el rendimiento de la base de datos. Con suerte, ofrece una imagen más clara de qué cosas pueden volverse importantes y algunos de los parámetros básicos que se pueden configurar. No dude en informarnos si nos hemos perdido alguno importante.