Il y a quelques semaines, j'ai expliqué les bases du réglage de l'autovacuum. À la fin de cet article, j'ai promis d'examiner bientôt les problèmes d'aspiration. Eh bien, cela a pris un peu plus de temps que prévu, mais c'est parti.
Pour récapituler rapidement, autovacuum
est un processus d'arrière-plan qui nettoie les lignes mortes, par ex. anciennes versions de lignes supprimées. Vous pouvez également effectuer le nettoyage manuellement en exécutant VACUUM
, mais autovacuum
le fait automatiquement en fonction de la quantité de lignes mortes dans le tableau, au bon moment - pas trop souvent mais assez fréquemment pour garder la quantité de "déchets" sous contrôle.
De manière générale, autovacuum
ne peut pas être exécuté trop souvent - le nettoyage n'est effectué qu'après avoir atteint un certain nombre de lignes mortes accumulées dans la table. Mais il peut être retardé pour diverses raisons, entraînant des tables et des index de plus en plus volumineux. Et c'est exactement le sujet de ce post. Alors, quels sont les coupables courants et comment les identifier ?
Limitation
Comme expliqué dans les bases du réglage, autovacuum
les travailleurs sont limités pour n'effectuer qu'une certaine quantité de travail par intervalle de temps. Les limites par défaut sont assez basses - environ 4 Mo/s d'écritures, 8 Mo/s de lectures. Cela convient aux petites machines comme Raspberry Pi ou aux petits serveurs d'il y a 10 ans, mais les machines actuelles sont bien plus puissantes (à la fois en termes de CPU et d'E/S) et gèrent beaucoup plus de données.
Imaginez que vous avez quelques grandes tables et quelques petites. Si les trois autovacuum
les travailleurs commencent à nettoyer les grandes tables, aucune des petites tables ne sera aspirée, quelle que soit la quantité de rangées mortes qu'elles accumulent. L'identifier n'est pas particulièrement difficile, en supposant que vous disposez d'une surveillance suffisante. Recherchez les périodes où tous les autovacuum
les travailleurs sont occupés alors que les tables ne sont pas aspirées malgré l'accumulation de nombreuses lignes mortes.
Toutes les informations nécessaires sont dans pg_stat_activity
(nombre de autovacuum
processus de travail) et pg_stat_all_tables
(last_autovacuum
et n_dead_tup
).
Augmenter le nombre de autovacuum
travailleurs n'est pas une solution, car la quantité totale de travail reste la même. Vous pouvez spécifier des limites de limitation par table, en excluant ce travailleur de la limite totale, mais cela ne garantit toujours pas qu'il y aura des travailleurs disponibles en cas de besoin.
La bonne solution consiste à ajuster la limitation, en utilisant des limites raisonnables par rapport à la configuration matérielle et aux modèles de charge de travail. Certaines recommandations de limitation de base sont mentionnées dans le post précédent. (Évidemment, si vous pouvez réduire le nombre de lignes mortes générées dans la base de données, ce serait une solution idéale.)
À partir de ce point, nous supposerons que la limitation n'est pas le problème, c'est-à-dire que le autovacuum
les travailleurs ne sont pas saturés pendant de longues périodes et que le nettoyage est déclenché sur toutes les tables sans retard déraisonnable.
Transactions longues
Donc, si la table est aspirée régulièrement, elle ne peut sûrement pas accumuler beaucoup de rangées mortes, n'est-ce pas ? Malheureusement non. Les lignes ne sont pas réellement "supprimables" immédiatement après avoir été supprimées, mais uniquement lorsqu'il n'y a aucune transaction susceptible de les voir. Le comportement exact dépend de ce que les autres transactions font (faisaient) et du niveau de sérialisation, mais en général :
LIRE COMMIT
- exécuter le nettoyage des blocs de requêtes
- les transactions inactives bloquent le nettoyage uniquement si elles ont effectué une écriture
- les transactions inactives (sans aucune écriture) ne bloqueront pas le nettoyage (mais ce n'est pas une bonne pratique de les conserver de toute façon)
SÉRIALISABLE
- exécuter le nettoyage des blocs de requêtes
- nettoyage des blocs de transactions inactives (même s'ils n'ont fait que des lectures)
En pratique, c'est plus nuancé bien sûr, mais expliquer tous les différents éléments nécessiterait d'abord d'expliquer comment fonctionnent les XID et les instantanés, et ce n'est pas le but de cet article. Ce que vous devriez vraiment retenir de cela, c'est que les transactions longues sont une mauvaise idée, en particulier si ces transactions ont pu entraîner des écritures.
Bien sûr, il existe des raisons parfaitement valables pour lesquelles vous devrez peut-être conserver des transactions pendant de longues périodes (par exemple, si vous devez garantir ACID pour tous les changements). Mais assurez-vous que cela ne se produise pas inutilement, par ex. en raison d'une mauvaise conception de l'application.
Une conséquence quelque peu inattendue de cela est une utilisation élevée du processeur et des E/S, en raison de autovacuum
courir encore et encore, sans nettoyer les rangées mortes (ou juste quelques-unes d'entre elles). À cause de cela, les tables sont toujours éligibles pour le nettoyage au tour suivant, causant plus de mal que de bien.
Comment détecter cela ? Tout d'abord, vous devez surveiller les transactions de longue durée, en particulier celles qui sont inactives. Tout ce que vous avez à faire est de lire les données de pg_stat_activity
. La définition de la vue change un peu avec la version PostgreSQL, vous devrez donc peut-être l'ajuster un peu :
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'
Vous pouvez également simplement utiliser un plugin de surveillance existant, par ex. check_postgres.pl. Ceux-ci incluent déjà ce type de contrôles de santé mentale. Vous devrez décider quelle est une durée de transaction/requête raisonnable, qui est spécifique à l'application.
Depuis PostgreSQL 9.6, vous pouvez également utiliser idle_in_transaction_session_timeout
afin que les transactions inactives trop longtemps soient automatiquement terminées. De même, pour les longues requêtes, il y a statement_timeout
.
Une autre chose utile est VACUUM VERBOSE
qui vous dira en fait combien de lignes mortes n'ont pas encore pu être supprimées :
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. ...
Il ne vous dira pas quel backend empêche le nettoyage, mais c'est un signe assez clair de ce qui se passe.
Remarque : . Vous ne pouvez pas facilement obtenir ces informations à partir de autovacuum
car il n'est enregistré qu'avec DEBUG2
par défaut (et vous ne voulez sûrement pas exécuter avec ce niveau de journalisation en production).
Longues requêtes sur les serveurs de secours
Supposons que les tables soient aspirées en temps opportun, mais ne suppriment pas les tuples morts, ce qui entraîne un gonflement de la table et de l'index. Vous surveillez pg_stat_activity
et il n'y a pas de transactions de longue durée. Quel pourrait être le problème ?
Si vous avez une réplique en streaming, il y a de fortes chances que le problème soit là. Si le réplica utilise hot_standby_feedback=on
, les requêtes sur le réplica agissent à peu près comme des transactions sur le primaire, y compris le blocage du nettoyage. Bien sûr, hot_standby_feedback=on
est utilisé exactement lors de l'exécution de longues requêtes (par exemple, des charges de travail d'analyse et de BI) sur des réplicas, pour éviter les annulations dues à des conflits de réplication.
Malheureusement, vous devrez choisir - soit garder hot_standby_feedback=on
et accepter les retards de nettoyage ou traiter les requêtes annulées. Vous pouvez également utiliser max_standby_streaming_delay
pour limiter l'impact, même si cela n'empêche pas complètement les annulations (vous devez donc toujours réessayer les requêtes).
En fait, il existe maintenant une troisième option - la réplication logique. Au lieu d'utiliser la réplication physique en continu pour le réplica BI, vous pouvez copier les modifications à l'aide de la nouvelle réplication logique, disponible dans PostgreSQL 10. La réplication logique assouplit le couplage entre le primaire et le réplica, et rend les clusters pour la plupart indépendants (sont nettoyés indépendamment, etc.).
Cela résout les deux problèmes associés à la réplication physique en continu :nettoyage retardé sur les requêtes principales ou annulées sur le réplica BI. Pour les répliques servant à des fins de DR, la réplication en continu reste cependant le bon choix. Mais ces répliques n'exécutent pas (ou ne devraient pas exécuter) de longues requêtes.
Remarque : Bien que j'ai mentionné que la réplication logique sera disponible dans PostgreSQL 10, une partie importante de l'infrastructure était disponible dans les versions précédentes (en particulier PostgreSQL 9.6). Vous pourrez donc peut-être le faire même sur des versions plus anciennes (nous l'avons fait pour certains de nos clients), mais PostgreSQL 10 le rendra beaucoup plus pratique et confortable.
Problème avec autoanalyze
Un détail que vous pourriez manquer est que autovacuum
les travailleurs effectuent en fait deux tâches différentes. Tout d'abord le nettoyage (comme si vous exécutiez VACUUM
), mais aussi en collectant des statistiques (comme si vous exécutiez ANALYZE
). Et les deux les pièces sont limitées à l'aide de autovacuum_cost_limit
.
Mais il y a une grande différence dans le traitement des transactions. Chaque fois que le VACUUM
la pièce atteint autovacuum_cost_limit
, le travailleur publie l'instantané et dort pendant un certain temps. Le ANALYZE
doit cependant s'exécuter dans un seul instantané/transaction, ce qui fait bloquer le nettoyage.
C'est une façon élégante de vous tirer une balle dans le pied, surtout si vous faites aussi cela :
- augmenter
default_statistics_target
pour créer des statistiques plus précises à partir d'échantillons plus importants - réduire
autovacuum_analyze_scale_factor
pour collecter des statistiques plus fréquemment
La conséquence involontaire est bien sûr que ANALYZE
se produira plus fréquemment, prendra beaucoup plus de temps et (contrairement au VACUUM
partie) empêcher le nettoyage. La solution est généralement assez simple - ne baissez pas autovacuum_analyze_scale_factor
trop. Exécution de ANALYZE
à chaque fois, 10 % des changements de table devraient être plus que suffisants dans la plupart des cas.
n_dead_tup
Une dernière chose que je voudrais mentionner concerne les changements dans pg_stat_all_tables.n_dead_tup
valeurs. Vous pourriez penser que la valeur est un simple compteur, incrémenté chaque fois qu'un nouveau tuple mort est créé et décrémenté chaque fois qu'il est nettoyé. Mais ce n'est en fait qu'une estimation du nombre de lignes mortes, mise à jour par ANALYZE
. Pour les petites tables (moins de 240 Mo), ce n'est pas vraiment une grande différence, car ANALYZE
lit tout le tableau et donc c'est assez exact. Pour les grandes tables, cela peut cependant changer un peu en fonction du sous-ensemble de table échantillonné. Et en abaissant autovacuum_vacuum_scale_factor
le rend plus aléatoire.
Soyez donc prudent lorsque vous regardez n_dead_tup
dans un système de surveillance. Les baisses ou augmentations soudaines de la valeur peuvent être simplement dues à ANALYZE
en recalculant une estimation différente, et non en raison d'un nettoyage réel et/ou de nouveaux tuples morts apparaissant dans le tableau.
Résumé
Pour résumer cela en quelques points simples :
autovacuum
ne peut fonctionner que s'il n'y a pas de transactions qui pourraient avoir besoin des tuples morts.- Les requêtes longues bloquent le nettoyage. Envisagez d'utiliser
statement_timeout
pour limiter les dégâts. - Les transactions de longue durée peuvent bloquer le nettoyage. Le comportement exact dépend de facteurs tels que le niveau d'isolement ou ce qui s'est passé dans la transaction. Surveillez-les et résiliez-les si possible.
- Requêtes de longue durée sur les répliques avec
hot_standby_feedback=on
peut également bloquer le nettoyage. autoanalyze
est également limité, mais contrairement auVACUUM
partie, il conserve un seul instantané (et bloque ainsi le nettoyage).n_dead_tup
n'est qu'une estimation maintenue parANALYZE
, attendez-vous donc à quelques fluctuations (en particulier sur les grandes tables).