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

Cuando el vacío automático no aspira

Hace unas semanas expliqué los conceptos básicos del ajuste de vacío automático. Al final de esa publicación, prometí investigar los problemas con la aspiración pronto. Bueno, tomó un poco más de tiempo de lo planeado, pero aquí vamos.

Para recapitular rápidamente, autovacuum es un proceso en segundo plano que limpia filas muertas, p. versiones antiguas de filas eliminadas. También puede realizar la limpieza manualmente ejecutando VACUUM , pero autovacuum lo hace automáticamente dependiendo de la cantidad de filas muertas en la tabla, en el momento adecuado, no con demasiada frecuencia, pero con la frecuencia suficiente para mantener la cantidad de "basura" bajo control.

En términos generales, autovacuum no se puede ejecutar con demasiada frecuencia:la limpieza solo se realiza después de alcanzar un número de filas muertas acumuladas en la tabla. Pero puede retrasarse por varios motivos, lo que hace que las tablas y los índices sean más grandes de lo deseable. Y ese es exactamente el tema de esta publicación. Entonces, ¿cuáles son los culpables comunes y cómo identificarlos?

Aceleración

Como se explica en los conceptos básicos de ajuste, autovacuum los trabajadores están limitados para realizar solo cierta cantidad de trabajo por intervalo de tiempo. Los límites predeterminados son bastante bajos:alrededor de 4 MB/s de escritura, 8 MB/s de lectura. Eso es adecuado para máquinas diminutas como Raspberry Pi o servidores pequeños de hace 10 años, pero las máquinas actuales son mucho más potentes (tanto en términos de CPU como de E/S) y manejan muchos más datos.

Imagina que tienes unas cuantas mesas grandes y algunas pequeñas. Si los tres autovacuum los trabajadores comienzan a limpiar las mesas grandes, ninguna de las mesas pequeñas será aspirada independientemente de la cantidad de filas muertas que acumulen. Identificar esto no es particularmente difícil, suponiendo que tenga suficiente monitoreo. Busque períodos en los que todo autovacuum los trabajadores están ocupados mientras que las mesas no se aspiran a pesar de acumular muchas filas muertas.

Toda la información necesaria está en pg_stat_activity (número de autovacuum procesos de trabajo) y pg_stat_all_tables (last_autovacuum y n_dead_tup ).

Aumentar el número de autovacuum trabajadores no es una solución, ya que la cantidad total de trabajo sigue siendo la misma. Puede especificar límites de limitación por tabla, excluyendo a ese trabajador del límite total, pero eso aún no garantiza que habrá trabajadores disponibles cuando sea necesario.

La solución correcta es ajustar la aceleración, utilizando límites razonables con respecto a la configuración del hardware y los patrones de carga de trabajo. Algunas recomendaciones básicas de aceleración se mencionan en la publicación anterior. (Obviamente, si puede reducir la cantidad de filas muertas generadas en la base de datos, sería una solución ideal).

A partir de este punto, asumiremos que la limitación no es el problema, es decir, que el autovacuum los trabajadores no estén saturados durante largos períodos de tiempo y que la limpieza se active en todas las mesas sin demoras injustificadas.

Transacciones largas

Entonces, si la mesa se aspira regularmente, seguramente no puede acumular muchas filas muertas, ¿verdad? Lamentablemente no. Las filas no son realmente "removibles" inmediatamente después de ser eliminadas, sino solo cuando no hay transacciones que puedan verlas. El comportamiento exacto depende de lo que estén haciendo las otras transacciones y del nivel de serialización, pero en general:

LEER COMPROMETIDO

  • ejecutar limpieza de bloques de consultas
  • limpieza de bloques de transacciones inactivas solo si realizaron una escritura
  • las transacciones inactivas (sin ninguna escritura) no bloquearán la limpieza (pero no es una buena práctica mantenerlas de todos modos)

SERIALIZABLE

  • ejecutar limpieza de bloques de consultas
  • limpieza de bloques de transacciones inactivas (incluso si solo hicieron lecturas)

En la práctica, por supuesto, tiene más matices, pero explicar todas las partes requeriría primero explicar cómo funcionan los XID y las instantáneas, y ese no es el objetivo de esta publicación. Lo que realmente debería sacar de esto es que las transacciones largas son una mala idea, especialmente si esas transacciones podrían haber hecho escrituras.

Por supuesto, existen razones perfectamente válidas por las que es posible que deba mantener las transacciones durante largos períodos de tiempo (por ejemplo, si necesita garantizar ACID para todos los cambios). Pero asegúrese de que no suceda innecesariamente, p. debido a un mal diseño de la aplicación.

Una consecuencia un tanto inesperada de esto es un alto uso de CPU y E/S, debido a autovacuum corriendo una y otra vez, sin limpiar ninguna fila muerta (o solo algunas de ellas). Debido a eso, las mesas todavía son elegibles para limpieza en la siguiente ronda, causando más daño que bien.

¿Cómo detectar esto? En primer lugar, debe monitorear las transacciones de larga duración, particularmente las inactivas. Todo lo que necesita hacer es leer datos de pg_stat_activity . La definición de la vista cambia un poco con la versión de PostgreSQL, por lo que es posible que deba modificar esto un poco:

SELECT xact_start, state FROM pg_stat_activity;

-- count 'idle' transactions longer than 15 minutes (since BEGIN)
SELECT COUNT(*) FROM pg_stat_activity
 WHERE state = 'idle in transaction'
  AND (now() - xact_start) > interval '15 minutes'

-- count transactions 'idle' for more than 5 minutes
SELECT COUNT(*) FROM pg_stat_activity
 WHERE state = 'idle in transaction'
  AND (now() - state_change) > interval '5 minutes'

También puede simplemente usar algún complemento de monitoreo existente, p. check_postgres.pl. Esos ya incluyen este tipo de controles de cordura. Tendrá que decidir cuál es una duración razonable de transacción/consulta, que es específica de la aplicación.

Desde PostgreSQL 9.6 también puede usar idle_in_transaction_session_timeout para que las transacciones inactivas durante demasiado tiempo finalicen automáticamente. Del mismo modo, para consultas largas existe statement_timeout .

Otra cosa útil es VACUUM VERBOSE que en realidad le dirá cuántas filas muertas aún no se pudieron eliminar:

db=# VACUUM verbose z;
INFO:  vacuuming "public.z"
INFO:  "z": found 0 removable, 66797 nonremovable row versions in 443 out of 443 pages
DETAIL:  12308 dead row versions cannot be removed yet.
...

No le dirá qué backend está impidiendo la limpieza, pero es una señal bastante clara de lo que está sucediendo.

Nota: . No puede obtener fácilmente esta información de autovacuum porque solo se registra con DEBUG2 de forma predeterminada (y seguramente no desea ejecutar con ese nivel de registro en producción).

Consultas largas en modo de espera activo

Supongamos que las tablas se aspiran de manera oportuna, pero no se eliminan las tuplas muertas, lo que da como resultado un aumento en la tabla y el índice. Estás monitoreando pg_stat_activity y no hay transacciones de larga duración. ¿Cuál podría ser el problema?

Si tiene una réplica de transmisión, es probable que el problema esté ahí. Si la réplica usa hot_standby_feedback=on , las consultas en la réplica actúan prácticamente como transacciones en la principal, incluida la limpieza de bloqueo. Por supuesto, hot_standby_feedback=on se usa exactamente cuando se ejecutan consultas largas (por ejemplo, análisis y cargas de trabajo de BI) en réplicas, para evitar cancelaciones debido a conflictos de replicación.

Desafortunadamente, tendrás que elegir:mantener hot_standby_feedback=on y aceptar retrasos en la limpieza, o atender consultas canceladas. También puede usar max_standby_streaming_delay para limitar el impacto, aunque eso no evita las cancelaciones por completo (por lo que aún debe volver a intentar las consultas).

En realidad, ahora hay una tercera opción:la replicación lógica. En lugar de usar la replicación de transmisión física para la réplica de BI, puede copiar los cambios usando la nueva replicación lógica, disponible en PostgreSQL 10. La replicación lógica relaja el acoplamiento entre el principal y la réplica, y hace que los clústeres sean en su mayoría independientes (se limpian de forma independiente, etc.).

Esto resuelve los dos problemas asociados con la replicación de transmisión física:limpieza retrasada en consultas primarias o canceladas en la réplica de BI. Sin embargo, para las réplicas que sirven para DR, la replicación de transmisión sigue siendo la opción correcta. Pero esas réplicas no están (o no deberían estar) ejecutando consultas largas.

Nota: Si bien mencioné que la replicación lógica estará disponible en PostgreSQL 10, una parte importante de la infraestructura estaba disponible en versiones anteriores (en particular, PostgreSQL 9.6). Por lo tanto, es posible que pueda hacer esto incluso en versiones anteriores (lo hicimos para algunos de nuestros clientes), pero PostgreSQL 10 lo hará mucho más conveniente y cómodo.

Problemas con autoanalyze

Un detalle que podría perderse es que autovacuum los trabajadores en realidad realizan dos tareas diferentes. En primer lugar, la limpieza (como si ejecutara VACUUM ), pero también recolectando estadísticas (como si estuviera ejecutando ANALYZE ). Y ambos las partes se aceleran usando autovacuum_cost_limit .

Pero hay una gran diferencia en el manejo de transacciones. Siempre que el VACUUM parte alcanza autovacuum_cost_limit , el trabajador libera la instantánea y duerme un rato. El ANALYZE sin embargo, tiene que ejecutarse en una sola instantánea/transacción, lo que limpieza de bloques.

Esta es una manera elegante de pegarte un tiro en el pie, especialmente si también haces algo de esto:

  • aumentar default_statistics_target para generar estadísticas más precisas a partir de muestras más grandes
  • menor autovacuum_analyze_scale_factor para recopilar estadísticas con más frecuencia

La consecuencia no deseada, por supuesto, es que ANALYZE ocurrirá con más frecuencia, llevará mucho más tiempo y (a diferencia del VACUUM parte) evitar la limpieza. La solución suele ser bastante simple:no reduzca el autovacuum_analyze_scale_factor demasiado. Ejecutando ANALYZE cada vez que cambia el 10% de la tabla debería ser más que suficiente en la mayoría de los casos.

n_dead_tup

Una última cosa que me gustaría mencionar es sobre los cambios en pg_stat_all_tables.n_dead_tup valores. Podría pensar que el valor es un contador simple, que se incrementa cada vez que se crea una nueva tupla inactiva y se reduce cada vez que se limpia. Pero en realidad es solo una estimación de la cantidad de tuplas muertas, actualizada por ANALYZE . Para tablas pequeñas (menos de 240 MB) no es realmente una gran diferencia, porque ANALYZE lee toda la tabla, por lo que es bastante exacta. Sin embargo, para tablas grandes, puede cambiar un poco según el subconjunto de la tabla que se muestree. Y bajando autovacuum_vacuum_scale_factor lo hace más aleatorio.

Así que tenga cuidado al mirar n_dead_tup en un sistema de monitoreo. Las caídas o aumentos repentinos en el valor pueden deberse simplemente a ANALYZE recalculando una estimación diferente, y no debido a la limpieza real y/o nuevas tuplas muertas que aparecen en la tabla.

Resumen

Para resumir esto en unos pocos puntos simples:

  • autovacuum solo puede hacer su trabajo si no hay transacciones que puedan necesitar las tuplas muertas.
  • Las consultas de ejecución prolongada bloquean la limpieza. Considere usar statement_timeout para limitar el daño.
  • La transacción de ejecución prolongada puede bloquear la limpieza. El comportamiento exacto depende de cosas como el nivel de aislamiento o lo que sucedió en la transacción. Supervisarlos y eliminarlos si es posible.
  • Consultas de ejecución prolongada en réplicas con hot_standby_feedback=on también puede bloquear la limpieza.
  • autoanalyze también está limitado, pero a diferencia del VACUUM parte, mantiene una sola instantánea (y por lo tanto bloquea la limpieza).
  • n_dead_tup es solo una estimación mantenida por ANALYZE , así que espere alguna fluctuación (especialmente en tablas grandes).