Monitoreamos la base de datos PostgreSQL: quién tiene la culpa y qué hacer

Ya he hablado sobre cómo "atrapamos" los problemas de PostgreSQL utilizando la supervisión de registros masivos en cientos de servidores al mismo tiempo. Pero además de los registros, este DBMS también nos proporciona muchas herramientas para analizar su estado ; es un pecado no usarlos.

Es cierto, si solo los mira desde la consola, puede dar la vuelta rápidamente sin ningún beneficio, porque la cantidad de datos disponibles supera todos los límites razonables.


Por lo tanto, para que la situación siga siendo controlable, desarrollamos un complemento para Zabbix que ofrece métricas, pantallas de formularios y establece reglas de monitoreo uniformes para todos los servidores y bases de datos en ellas.

El artículo de hoy trata sobre las conclusiones que se pueden extraer al observar dinámicamente las diversas métricas de las bases de servidores PostgreSQL y dónde se puede ocultar el problema.

Estado de conexión


Lo primero con lo que comienzan todos los desensambles sobre el tema "lo que sucedió en nuestra base de datos / fue malo" es monitorear el estado resumido de pg_stat_activity :



en el gráfico de la izquierda vemos todas las conexiones que están esperando algo, a la derecha, eso es algo hacer. Dependiendo de la versión PG, el estado de la conexión está determinado por pg_stat_activity.state/wait_eventy / o el texto de la solicitud en sí.

Qué buscar :

  • Demasiado pocoidle : en algún momento, es posible que su aplicación no tenga suficientes conexiones abiertas a la base de datos, y cuando intente abrir otra, terminará esperando que el proceso se inicialice para servir una nueva conexión.
  • idle «» , max_connections.
  • idle in transaction — , - pgbouncer. .

    , , idle_in_transaction_session_timeout.
  • wait — - «» . — .

    , «» pg_terminate_backend(pid).
  • active ( max-) , «». - (, « ») , , …

    — , «» .
  • maintenance — , - :

    query ~* E'^(\\s*(--[^\\n]*\\n|/\\*.*\\*/|\\n))*(autovacuum|VACUUM|ANALYZE|REINDEX|CLUSTER|CREATE|ALTER|TRUNCATE|DROP)'

    En la mayoría de los casos, habrá el número de autovacuum / autoanalyze funcionando al mismo tiempo, cuyo daño consiste solo en usar recursos del servidor para casos "extraños". Si esto es crítico para usted - giro autovacuum_max_workersy autovacuum_naptime, pero a su vez completamente fuera - que no debería .

    Pero si al mismo tiempo comienza a crecer wait, ymaintenance es una oportunidad para ver si alguien ha decidido implementar el DBA o el código de desarrollador, por ejemplo, bloqueando la mitad de las posibilidades de aplicaciones funcionales.

Dado que es importante para nosotros eliminar no solo una gran cantidad de métricas, sino también hacerlo de la manera más eficiente posible, intentamos disparar algunas de ellas sincrónicamente en el marco de una solicitud:

Conexión y estado de bloqueo
WITH event_types(wait_event_type) AS(
  VALUES
    ('lwlock')
  , ('lock')
  , ('bufferpin')
  , ('client')
  , ('extension')
  , ('ipc')
  , ('timeout')
  , ('io')
)
, events(wait_event) AS(
  VALUES
    ('walwritelock')
  , ('wal_insert')
  , ('buffer_content')
  , ('buffer_io')
  , ('lock_manager')
  , ('relation')
  , ('extend')
  , ('page')
  , ('tuple')
  , ('transactionid')
  , ('virtualxid')
  , ('speculative token')
  , ('object')
  , ('userlock')
  , ('advisory')
  , ('clientread')
  , ('datafileextend')
  , ('datafileread')
  , ('datafilewrite')
  , ('slruread')
  , ('slruwrite')
)
, states(state) AS(
  VALUES
    ('running')
  , ('maintenance')
  , ('waiting')
  , ('transaction')
  , ('idle')
)
, stats AS(
  SELECT
    pid
  , datname
  , state
  , lower(wait_event_type) wait_event_type
  , lower(wait_event) wait_event
  , query
  FROM
    pg_stat_activity
  WHERE
    pid <> pg_backend_pid()
)
, dbs AS(
  SELECT
    datname
  FROM
    pg_database db
  WHERE
    NOT db.datistemplate
)
  SELECT
    date_part('epoch', now())::integer ts
  , coalesce(s.qty, 0) val
  , dbs.datname dbname
  , states.state
  , true total
  FROM
    dbs
  CROSS JOIN
    states
  NATURAL LEFT JOIN
    (
      SELECT
        datname
      , CASE
          WHEN query ~* E'^(\\s*(--[^\\n]*\\n|/\\*.*\\*/|\\n))*(autovacuum|VACUUM|ANALYZE|REINDEX|CLUSTER|CREATE|ALTER|TRUNCATE|DROP)' THEN
            'maintenance'
          WHEN wait_event IS NOT NULL AND
            wait_event <> 'clientread' AND
            state = 'active' THEN
            'waiting'
          WHEN state = 'active' THEN
            'running'
          WHEN state = 'idle' THEN
            'idle'
          WHEN state IN ('idle in transaction', 'idle in transaction (aborted)') THEN
            'transaction'
          WHEN state = 'fastpath function call' THEN
            'fastpath'
          ELSE
            'disabled'
        END state
      , count(*) qty
      FROM
        stats
      GROUP BY
        1, 2
    ) s
UNION
  SELECT
    date_part('epoch', now())::integer ts
  , coalesce(t.qty, 0) val
  , dbs.datname dbname
  , event_types.wait_event_type
  , false total
  FROM
    dbs
  CROSS JOIN
    event_types
  NATURAL LEFT JOIN
    (
      SELECT
        datname
      , wait_event_type
      , count(*) qty
      FROM
        stats
      WHERE
        wait_event_type IS NOT NULL
      GROUP BY
        1, 2
    ) t
UNION
  SELECT
    date_part('epoch', now())::integer ts
  , coalesce(e.qty, 0) val
  , dbs.datname dbname
  , events.wait_event
  , false total
  FROM
    dbs
  CROSS JOIN
    events
  NATURAL LEFT JOIN
    (
      SELECT
        datname
      , wait_event
      , count(*) qty
      FROM
        stats
      WHERE
        wait_event IS NOT NULL
      GROUP BY
        1, 2
    ) e;

Cerraduras


Dado que tocamos el bloqueo del monitoreo en el párrafo anterior, vale la pena señalar que a PostgreSQL le gusta superponerlos de derecha a izquierda:



estamos más interesados ​​en dos tipos de ellos:

  • Exclusive - Suele ocurrir al bloquear un registro en particular.
  • AccessExclusive - al realizar operaciones de mantenimiento en la mesa.

Pero no olvide que el número total de cerraduras no es de goma :
Tanto los bloqueos de aviso como los normales se almacenan en el área de memoria compartida, cuyo tamaño está determinado por los parámetros de configuración max_locks_per_transactiony max_connections. Es importante que esta memoria sea suficiente, porque de lo contrario el servidor no podrá emitir ningún bloqueo . Por lo tanto, el número de bloqueos recomendados que puede emitir un servidor generalmente se limita a decenas o cientos de miles, dependiendo de la configuración del servidor.
Por lo general, esta situación surge si su aplicación "fluye" y no se liberan recursos: conexiones a la base de datos, contextos de transacción o bloqueos de aviso . Por lo tanto, preste atención a la dinámica general.

Transacciones por segundo (TPS)


Para obtener información sobre los cambios en el contexto de la base de datos actual, puede usar la vista del sistema pg_stat_database . Pero si hay muchas bases de datos en el servidor, es conveniente hacer esto inmediatamente para todas ellas, conectándose a ellaspostgres .

TPS y tuplas
SELECT
  extract(epoch from now())::integer ts
, datname dbname
, pg_stat_get_db_tuples_returned(oid) tup_returned
, pg_stat_get_db_tuples_fetched(oid) tup_fetched
, pg_stat_get_db_tuples_inserted(oid) tup_inserted
, pg_stat_get_db_tuples_updated(oid) tup_updated
, pg_stat_get_db_tuples_deleted(oid) tup_deleted
, pg_stat_get_db_xact_commit(oid) xact_commit
, pg_stat_get_db_xact_rollback(oid) xact_rollback
FROM
  pg_database
WHERE
  NOT datistemplate;

Quiero enfatizar por separado: ¡no descuide la salida de valores máximos de métricas!



En este gráfico, podemos ver claramente la situación de un aumento pico repentino en el número de committransacciones realizadas ( ). Esto no es uno a uno corresponde a la carga en el servidor y las transacciones pueden ser de complejidad variable, pero un crecimiento de 4 veces muestra claramente que el servidor debe tener una cierta reserva de rendimiento para sobrevivir sin problemas a ese pico.

Bueno, la reversión ( rollback) de la transacción es una ocasión para verificar si su aplicación se está ejecutando conscientemente ROLLBACKo si el servidor lo hace automáticamente como resultado de un error.

Número de operaciones en registros


Primero, preste atención a los registros que restamos de los índices / tablas:



  • tuples.returned — , «» .
  • tuples.fetched — , « » Rows Removed by Filter, «» .
  • tuples.ratio — , , 1, — . , , , .

Si observa un pico agudotuples.ratio , puede estar seguro de que encontrará alguna solicitud ineficaz de la categoría descrita en el artículo sobre recetas para su tratamiento al lado del registro .

Sin embargo, incluso si es ratioidealmente igual a 1, pero el pico cayóreturned/fetched , tampoco esperes nada bueno. Por lo general, esto puede significar que hay algún tipo de problema en el plan, como:

Hash Join
  - Hash
    - Seq Scan on BIG_TABLE
  - Index Scan ...

Merge Join
  - Index Scan on BIG_INDEX
  - Index Scan ...

Desde que comenzamos a verificar lo que se lee allí, veamos cómo sucede. Es decir, cuántos registros leemos por índices y cuánto como resultado Seq Scan:



está claro que aquí cualquier crecimiento no planificado de indicadores debería causar sospecha. Por ejemplo, si por alguna razón necesita leer un plato completo de registros de 10M cada noche, la aparición de ese pico durante el día es una razón para el desmontaje.

Además de cualquier inserción / actualización / eliminación anómala de masas:



Usar caché de datos


Para comprender cómo la revisión masiva de registros realmente empeora la vida del servidor, veamos el trabajo del servidor con las páginas de datos y la proporciónblock.read/hit . En un mundo ideal, el servidor no debe "leer" del disco ( shared readen el nodo del plan) absolutamente nada, todo ya debe estar en la memoria ( shared hit), ya que el acceso al disco siempre es lento .

En realidad, esto no es del todo cierto, y es la razón de un análisis exhaustivo de las solicitudes en el momento pico:



Solicitud / Transacción más larga


Para MVCC, las consultas y transacciones de larga duración en sistemas ocupados son un desastre de rendimiento. Los detalles y las imágenes sobre esto se pueden leer aquí y aquí : ¿cómo puede sobrevivir en tales condiciones?



Atrapar a esos villanos nos ayuda pg_stat_activity.query_start/xact_start.

Como muestra nuestra experiencia, una representación visual de estas métricas ya es suficiente para representar más o menos dónde "excavar" aún más:

  • buscar fugas de recursos en la aplicación
  • optimizar solicitudes fallidas
  • poner hardware más productivo
  • ... o asegúrese de que la carga esté correctamente espaciada en el tiempo

All Articles