Nous surveillons la base de données PostgreSQL - qui est à blâmer et que faire

J'ai déjà parlé de la façon dont nous «détectons» les problèmes PostgreSQL en utilisant la surveillance des journaux de masse sur des centaines de serveurs en même temps. Mais en plus des journaux, ce SGBD nous fournit également de nombreux outils pour analyser son état - c'est un péché de ne pas les utiliser.

Certes, si vous les regardez simplement depuis la console, vous pouvez très rapidement faire le tour sans aucun avantage, car la quantité de données à notre disposition dépasse toutes les limites raisonnables.


Par conséquent, afin que la situation reste contrôlable, nous avons développé un module complémentaire pour Zabbix qui fournit des métriques, des écrans de formulaires et définit des règles de surveillance uniformes pour tous les serveurs et bases de données sur eux.

L'article d'aujourd'hui porte sur les conclusions qui peuvent être tirées en observant en dynamique les différentes métriques des bases de serveurs PostgreSQL, et où le problème peut être caché.

Statut de connexion


La toute première chose avec laquelle tous les désassemblements sur le sujet «qu'est-il arrivé à notre base de données / c'était mauvais» commence par la surveillance de l'état récapitulatif de pg_stat_activity :



sur le graphique de gauche, nous voyons toutes les connexions qui attendent quelque chose, à droite - c'est quelque chose faire. Selon la version de PG, l'état de la connexion est déterminé par pg_stat_activity.state/wait_eventet / ou le texte de la demande lui-même.

Que rechercher :

  • Trop peuidle - à un moment donné, votre application peut ne pas avoir suffisamment de connexions déjà ouvertes à la base de données, et lorsque vous essayez d'en ouvrir une autre, vous vous retrouverez à attendre que le processus s'initialise pour servir une nouvelle connexion.
  • 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)'

    Dans la plupart des cas, il y aura le nombre d'auto-vide / auto-analyse fonctionnant en même temps, dont le mal ne réside que dans l'utilisation des ressources du serveur pour les cas "étrangers". Si cela est essentiel pour vous - tournez autovacuum_max_workerset autovacuum_naptime, mais désactivez-le complètement - vous ne devriez pas .

    Mais si en même temps commence à croître wait, etmaintenance , c'est une chance de voir si quelqu'un a décidé de déployer le code DBA ou développeur, par exemple, bloquant la moitié des chances d'applications fonctionnelles.

Comme il est important pour nous de supprimer non seulement un grand nombre de métriques, mais aussi de le faire le plus efficacement possible, nous essayons de filmer certaines d'entre elles de manière synchrone dans le cadre d'une seule demande:

État de connexion et de verrouillage
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;

Serrures


Puisque nous avons abordé le blocage de la surveillance dans le paragraphe précédent, il convient de noter que PostgreSQL aime les superposer à droite et à gauche:



nous sommes plus intéressés par deux types d'entre eux:

  • Exclusive - se produisent généralement lors du verrouillage sur un enregistrement particulier.
  • AccessExclusive - lors des opérations de maintenance sur la table.

Mais n'oubliez pas que le nombre total de serrures n'est pas en caoutchouc :
Les verrous consultatifs et réguliers sont stockés dans la zone de mémoire partagée, dont la taille est déterminée par les paramètres de configuration max_locks_per_transactionet max_connections. Il est important que cette mémoire soit suffisante, car sinon le serveur ne pourra émettre aucun verrou . Ainsi, le nombre de verrous recommandés qu'un serveur peut émettre est généralement limité à des dizaines ou des centaines de milliers, selon la configuration du serveur.
En règle générale, cette situation se produit si votre «flux» d'application et les ressources ne sont pas libérées: connexions à la base de données, contextes de transaction ou verrous consultatifs . Par conséquent, faites attention à la dynamique globale.

Transactions par seconde (TPS)


Pour obtenir des informations sur les modifications dans le contexte de la base de données actuelle, vous pouvez utiliser la vue système pg_stat_database . Mais s'il existe de nombreuses bases de données sur le serveur, il est pratique de le faire immédiatement pour toutes, en se connectant àpostgres .

TPS & tuples
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;

Je veux souligner séparément - ne négligez pas la sortie des valeurs maximales des métriques!



Dans ce graphique, nous pouvons clairement voir la situation d'un pic soudain d'augmentation du nombre de committransactions effectuées ( ). Ce n'est pas un à un correspond à la charge sur le serveur et les transactions peuvent être de complexité variable, mais une croissance de 4 fois montre clairement que le serveur doit avoir une certaine réserve de performances afin de survivre à un tel pic sans problème.

Eh bien, la restauration ( rollback) de la transaction est une occasion de vérifier si votre application s'exécute consciemment ROLLBACK, ou si le serveur le fait automatiquement à la suite d'une erreur.

Nombre d'opérations sur les enregistrements


Tout d'abord, faites attention aux enregistrements que nous soustrayons des index / tables:



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

Si vous observez un pic pointutuples.ratio , vous pouvez être sûr qu'une demande inefficace de la catégorie décrite dans l' article sur les recettes pour leur traitement vous parviendra dans le journal .

Cependant, même si ratioidéalement égal à 1, mais le pic est tombéreturned/fetched - ne vous attendez pas non plus à bien. Habituellement, cela peut signifier qu'il y a une sorte de problème dans le plan, comme:

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

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

Depuis que nous avons commencé à vérifier ce qui s'y lit, voyons comment cela se passe. C'est-à-dire, combien d'enregistrements nous lisons par index, et combien en conséquence Seq Scan:



Il est clair qu'ici toute croissance imprévue d'indicateurs devrait susciter des soupçons. Par exemple, si pour une raison quelconque vous avez besoin de lire une plaque entière de 10 millions d'enregistrements chaque nuit, alors l'apparition d'un tel pic pendant la journée est une raison de démontage.

Ainsi que toutes les insertions / mises à jour / suppressions de masse anormales:



Utilisation du cache de données


Pour comprendre comment la relecture en masse des enregistrements aggrave vraiment la vie du serveur, examinons comment le serveur fonctionne avec les pages de données et le rapportblock.read/hit . Dans un monde idéal, le serveur ne devrait pas «lire» depuis le disque ( shared readsur le nœud du plan) absolument rien, tout devrait déjà être en mémoire ( shared hit), car l' accès au disque est toujours lent .

En réalité, ce n'est pas entièrement vrai, et c'est la raison d'une analyse approfondie des demandes aux heures de pointe:



Demande / transaction la plus longue


Pour MVCC, les requêtes et transactions de longue durée dans les systèmes occupés sont un désastre de performances. Les détails et les images à ce sujet peuvent être lus ici et ici - comment pouvez-vous survivre dans de telles conditions.



Attraper de tels méchants nous aide pg_stat_activity.query_start/xact_start.

Comme notre expérience le montre, une représentation visuelle de ces mesures est déjà suffisante pour représenter approximativement où creuser davantage:

  • rechercher des fuites de ressources dans l'application
  • optimiser les demandes ayant échoué
  • mettre du matériel plus productif
  • ... ou assurez-vous que la charge est correctement espacée dans le temps

All Articles