
La mise Ă l'Ă©chelle d'un SGBD est un avenir en constante Ă©volution. Les SGBD s'amĂ©liorent et Ă©voluent mieux sur les plates-formes matĂ©rielles, tandis que les plates-formes matĂ©rielles elles-mĂȘmes augmentent la productivitĂ©, le nombre de cĆurs et la mĂ©moire - Achilles rattrape la tortue, mais ne l'a toujours pas fait. Le problĂšme de la mise Ă l'Ă©chelle du SGBD bat son plein.
Postgres Professional avait un problÚme avec la mise à l'échelle non seulement théoriquement, mais aussi pratiquement: avec ses clients. Et plus d'une fois. Un de ces cas sera discuté dans cet article.
PostgreSQL Ă©volue bien sur les systĂšmes NUMA s'il s'agit d'une seule carte mĂšre avec plusieurs processeurs et plusieurs bus de donnĂ©es. Quelques optimisations peuvent ĂȘtre lues
ici et
ici . Cependant, il existe une autre classe de systĂšmes, ils ont plusieurs cartes mĂšres, dont l'Ă©change de donnĂ©es s'effectue via une interconnexion, tandis qu'une instance du systĂšme d'exploitation fonctionne sur eux et pour l'utilisateur, cette conception ressemble Ă une seule machine. Et bien que formellement, ces systĂšmes puissent Ă©galement ĂȘtre attribuĂ©s Ă NUMA, mais ils sont essentiellement plus proches des superordinateurs, comme l'accĂšs Ă la mĂ©moire locale du nĆud et l'accĂšs Ă la mĂ©moire du nĆud voisin diffĂšrent radicalement. La communautĂ© PostgreSQL estime que la seule instance Postgres exĂ©cutĂ©e sur de telles architectures est une source de problĂšmes, et il n'y a pas encore d'approche systĂ©matique pour les rĂ©soudre.
En effet, l'architecture logicielle utilisant la mĂ©moire partagĂ©e est fondamentalement conçue pour le fait que le temps d'accĂšs des diffĂ©rents processus Ă leur mĂ©moire propre et distante est plus ou moins comparable. Dans le cas oĂč nous travaillons avec de nombreux nĆuds, le pari sur la mĂ©moire partagĂ©e en tant que canal de communication rapide cesse de se justifier, car en raison de la latence, il est beaucoup "moins cher" d'envoyer une demande pour effectuer une certaine action sur le nĆud (nĆud) oĂč des donnĂ©es intĂ©ressantes que d'envoyer ces donnĂ©es sur le bus. Par consĂ©quent, pour les supercalculateurs et, en gĂ©nĂ©ral, les systĂšmes Ă nombreux nĆuds, les solutions de cluster sont pertinentes.
Cela ne signifie pas qu'il faut mettre un terme Ă la combinaison de systĂšmes multi-nĆuds et d'une architecture de mĂ©moire partagĂ©e Postgres classique. AprĂšs tout, si les processus postgres passent la plupart de leur temps Ă effectuer des calculs complexes localement, cette architecture sera mĂȘme trĂšs efficace. Dans notre situation, le client avait dĂ©jĂ achetĂ© un puissant serveur multi-nĆuds, et nous devions y rĂ©soudre les problĂšmes de PostgreSQL.
Mais les problĂšmes Ă©taient sĂ©rieux: les demandes d'Ă©criture les plus simples (changer plusieurs valeurs de champ dans un enregistrement) ont Ă©tĂ© exĂ©cutĂ©es en quelques minutes Ă une heure. Comme il a Ă©tĂ© confirmĂ© par la suite, ces problĂšmes se sont manifestĂ©s dans toute leur splendeur prĂ©cisĂ©ment en raison du grand nombre de cĆurs et, par consĂ©quent, du parallĂ©lisme radical dans l'exĂ©cution des requĂȘtes avec un Ă©change relativement lent entre les nĆuds.
Par conséquent, l'article se révélera, pour ainsi dire, à deux fins:
- Partager l'expĂ©rience: que faire si dans un systĂšme Ă plusieurs nĆuds la base de donnĂ©es ralentit sĂ©rieusement. Par oĂč commencer, comment diagnostiquer oĂč aller.
- DĂ©crivez comment les problĂšmes du SGBD PostgreSQL lui-mĂȘme peuvent ĂȘtre rĂ©solus avec un niveau de concurrence Ă©levĂ©. Notamment comment la modification de l'algorithme de prise de verrous affecte les performances de PostgreSQL.
Serveur et DB
Le systĂšme Ă©tait composĂ© de 8 lames avec 2 prises chacune. Au total, plus de 300 cĆurs (hors hypertreading). Un pneu rapide (technologie propriĂ©taire du fabricant) relie les pales. Ce n'est pas un supercalculateur, mais pour une instance du SGBD, la configuration est impressionnante.
La charge est également assez importante. Plus d'un téraoctet de données. Environ 3000 transactions par seconde. Plus de 1000 connexions aux postgres.
Ayant commencé à faire face aux attentes d'enregistrement horaire, la premiÚre chose que nous avons faite a été d'écrire sur le disque comme cause de retards. DÚs que des retards incompréhensibles ont commencé, les tests ont commencé à se faire exclusivement sur
tmpfs
. L'image n'a pas changé. Le disque n'y est pour rien.
Prise en main des diagnostics: vues
Les problĂšmes Ă©tant probablement dus Ă la forte concurrence des processus qui «frappent» les mĂȘmes objets, la premiĂšre chose Ă vĂ©rifier est les verrous. Dans PostgreSQL, il existe une vue
pg.catalog.pg_locks
et
pg_stat_activity
pour une telle vérification. Le second, déjà dans la version 9.6, a ajouté des informations sur ce que le processus attend (
Amit Kapila, Ildus Kurbangaliev ) -
wait_event_type
. Les valeurs possibles pour ce champ sont décrites
ici .
Mais d'abord, comptez:
postgres=
Ce sont de vrais chiffres. Atteint jusqu'Ă 200 000 verrous.
Dans le mĂȘme temps, de tels verrous Ă©taient suspendus Ă la demande malheureuse:
SELECT COUNT(mode), mode FROM pg_locks WHERE pid =580707 GROUP BY mode; count | mode â
Lors de la lecture du tampon, le SGBD utilise le verrouillage de
share
, tout en écrivant -
exclusive
. Autrement dit, les verrous en écriture représentaient moins de 1% de toutes les demandes.
Dans la vue
pg_locks
, les types de verrous ne ressemblent pas toujours à ceux décrits
dans la documentation utilisateur.
Voici la plaque d'allumettes:
AccessShareLock = LockTupleKeyShare RowShareLock = LockTupleShare ExclusiveLock = LockTupleNoKeyExclusive AccessExclusiveLock = LockTupleExclusive
La requĂȘte SELECT mode FROM pg_locks a montrĂ© que CREATE INDEX (sans CONCURRENTLY) attendrait 234 INSERTs et 390 INSERTs pour le
buffer content lock
. Une solution possible consiste à «apprendre» aux INSERT de différentes sessions à se croiser moins dans les tampons.
Il est temps d'utiliser la perf
L'utilitaire
perf
collecte de nombreuses informations de diagnostic. En mode
record
... il écrit les statistiques des événements systÚme dans des fichiers (par défaut, ils sont en
./perf_data
), et en mode
report
, il analyse les données collectées, par exemple, vous pouvez filtrer les événements qui ne concernent que les
postgres
ou un
pid
donné:
$ perf record -u postgres $ perf record -p 76876 , $ perf report > ./my_results
En conséquence, nous verrons quelque chose comme

Comment utiliser
perf
pour diagnostiquer PostgreSQL est décrit, par exemple,
ici , ainsi que dans le
wiki pg .
Dans notre cas, mĂȘme le mode le plus simple a donnĂ© des informations importantes Ă
perf top
-
perf top
, qui fonctionne, bien sûr, dans l'esprit du systÚme d'exploitation le
top
performant. Avec
perf top
nous avons vu que la plupart du temps, le processeur passe dans les
PinBuffer()
base, ainsi que dans les fonctions
PinBuffer()
et
LWLockAttemptLock().
.
PinBuffer()
est une fonction qui augmente le compteur de rĂ©fĂ©rences au tampon (mappage d'une page de donnĂ©es Ă la RAM), grĂące Ă laquelle les processus postgres savent quels tampons peuvent ĂȘtre forcĂ©s et lesquels ne le peuvent pas.
LWLockAttemptLock()
-
LWLock
de capture de
LWLock
.
LWLock
est une sorte de verrou avec deux niveaux de
shared
et d'
exclusive
, sans définir d'
deadlock
, les verrous sont prĂ©-allouĂ©s Ă
shared memory
, les processus en attente attendent dans une file d'attente.
Ces fonctions ont déjà été sérieusement optimisées dans PostgreSQL 9.5 et 9.6. Les verrous à l'intérieur d'eux ont été remplacés par l'utilisation directe des opérations atomiques.
Graphes de flamme
Câest impossible sans eux: mĂȘme sâils Ă©taient inutiles, il vaudrait quand mĂȘme la peine dâen parler - ils sont dâune beautĂ© exceptionnelle. Mais ils sont utiles. Voici une illustration de
github
, pas de notre cas (ni nous ni le client ne sommes encore prĂȘts Ă divulguer les dĂ©tails).

Ces belles images montrent trĂšs clairement ce que prennent les cycles du processeur. Le mĂȘme
perf
peut collecter des données, mais le
flame graph
visualise intelligemment les données et construit des arbres basés sur les piles d'appels collectées. Vous pouvez en savoir plus sur le profilage avec des graphiques de flamme, par exemple,
ici , et télécharger tout ce dont vous avez besoin
ici .
Dans notre cas, une énorme quantité de
nestloop
était visible sur les graphiques de flamme. Apparemment, les jointures d'un grand nombre de tables dans de nombreuses demandes de lecture simultanées ont provoqué un grand nombre de verrous de
access share
.
Les statistiques collectées par
perf
montrent oĂč vont les cycles du processeur. Et bien que nous ayons vu que la plupart du temps du processeur passe sur les verrous, nous n'avons pas vu ce qui conduit exactement Ă de si longues attentes en matiĂšre de verrous, car nous ne voyons pas exactement oĂč les attentes de verrouillage se produisent, car Le temps CPU n'est pas perdu Ă attendre.
Afin de voir les attentes elles-mĂȘmes, vous pouvez crĂ©er une demande Ă la vue systĂšme
pg_stat_activity
.
SELECT wait_event_type, wait_event, COUNT(*) FROM pg_stat_activity GROUP BY wait_event_type, wait_event;
a révélé que:
LWLockTranche | buffer_content | UPDATE ************* LWLockTranche | buffer_content | INSERT INTO ******** LWLockTranche | buffer_content | \r | | insert into B4_MUTEX | | values (nextval('hib | | returning ID Lock | relation | INSERT INTO B4_***** LWLockTranche | buffer_content | UPDATE ************* Lock | relation | INSERT INTO ******** LWLockTranche | buffer_mapping | INSERT INTO ******** LWLockTranche | buffer_content | \r
(les astérisques ici remplacent simplement les détails de la demande que nous ne divulguons pas).
Vous pouvez voir les valeurs
buffer_content
(blocage du contenu des tampons) et
buffer_mapping
(blocage des composants de la plaque de hachage
shared_buffers
).
Pour obtenir de l'aide sur gdb
Mais pourquoi tant d'attentes pour ces types de serrures? Pour des informations plus détaillées sur les attentes, j'ai dû utiliser le débogueur
GDB
. Avec
GDB
nous pouvons obtenir une pile d'appels de processus spécifiques. En appliquant l'échantillonnage, c'est-à -dire AprÚs avoir collecté un certain nombre de piles d'appels aléatoires, vous pouvez vous faire une idée des piles qui ont les attentes les plus longues.
Considérez le processus de compilation des statistiques. Nous considérerons la collecte «manuelle» de statistiques, bien que dans la vie réelle des scripts spéciaux soient utilisés pour le faire automatiquement.
Tout d'abord,
gdb
doit ĂȘtre attachĂ© au processus PostgreSQL. Pour ce faire, recherchez le
pid
processus serveur, par exemple
$ ps aux | grep postgres
Disons que nous avons trouvé:
postgres 2025 0.0 0.1 172428 1240 pts/17 S 23 0:00 /usr/local/pgsql/bin/postgres -D /usr/local/pgsql/data
et maintenant insérez le
pid
dans le débogueur:
igor_le:~$gdb -p 2025
Une fois à l'intérieur du débogueur, nous écrivons
bt
[c'est-Ă -dire
backtrace
] ou
where
. Et nous obtenons beaucoup d'informations sur ce type:
(gdb) bt #0 0x00007fbb65d01cd0 in __write_nocancel () from /lib64/libc.so.6 #1 0x00000000007c92f4 in write_pipe_chunks ( data=0x110e6e8 "2018â06â01 15:35:38 MSK [524647]: [392â1] db=bp,user=bp,app=[unknown],client=192.168.70.163 (http://192.168.70.163) LOG: relation 23554 new block 493: 248.389503\n2018â06â01 15:35:38 MSK [524647]: [393â1] db=bp,user=bp,app=["..., len=409, dest=dest@entry=1) at elog.c:3123 #2 0x00000000007cc07b in send_message_to_server_log (edata=0xc6ee60 <errordata>) at elog.c:3024 #3 EmitErrorReport () at elog.c:1479
AprÚs avoir collecté des statistiques, y compris des piles d'appels de tous les processus postgres, collectées à plusieurs reprises à différents moments, nous avons vu que le
buffer partition lock
à l'intérieur du
relation extension lock
duré 3706 secondes (environ une heure), c'est-à -dire qu'il se verrouille sur un morceau de la table de hachage du tampon , qui était nécessaire pour remplacer l'ancien tampon, afin de le remplacer par la suite par un nouveau correspondant à la partie étendue de la table. Un certain nombre de verrous de
buffer content lock
de
buffer content lock
étaient également perceptibles, ce qui correspondait à l'attente de verrouillage des pages de l'index de l'
B-tree
pour l'insertion.

Au début, deux explications sont venues pour un temps d'attente aussi monstrueux:
- Quelqu'un d'autre a pris ce
LWLock
et est resté. Mais c'est peu probable. Parce que rien de compliqué ne se passe à l'intérieur du verrou de partition tampon. - Nous avons rencontré un comportement pathologique de
LWLock
. Autrement dit, malgré le fait que personne n'a pris le verrou trop longtemps, son attente a duré trop longtemps.
Patchs de diagnostic et traitement des arbres
En réduisant le nombre de connexions simultanées, nous déchargerions probablement le flux de demandes de verrous. Mais ce serait comme se rendre. Au lieu de cela,
Alexander Korotkov , l'architecte en chef de Postgres Professional (bien sûr, il a aidé à préparer cet article), a proposé une série de correctifs.
Tout d'abord, il était nécessaire d'avoir une image plus détaillée de la catastrophe. Peu importe la qualité des outils finis, les correctifs de diagnostic de leur propre fabrication seront également utiles.
Un correctif a été écrit qui ajoute une journalisation détaillée du temps passé dans l'
relation extension
, ce qui se passe à l'intérieur de la fonction
RelationAddExtraBlocks()
. Nous découvrons donc ce que le temps est passé à l'intérieur de
RelationAddExtraBlocks().
Et à l'appui de lui, un autre correctif a été écrit dans
pg_stat_activity
sur ce que nous faisons maintenant en ce qui
relation extension
. Cela a été fait de cette façon: lorsque la
relation
développe,
application_name
devient
RelationAddExtraBlocks
. Ce processus est désormais facilement analysé avec un maximum de détails à l'aide de
gdb bt
et
perf
.
En fait, les correctifs médicaux (et non diagnostiques) ont été écrits deux fois. Le premier correctif a changé le comportement des verrous de feuille de
Bâtree
: auparavant, lors de la demande d'insertion, la feuille était bloquée en tant que
share
, puis elle devenait
exclusive
. Maintenant, il devient immédiatement
exclusive
. Maintenant, ce correctif
a déjà été validé pour
PostgreSQL 12 . Heureusement, cette année,
Alexander Korotkov a reçu le
statut de committer - le deuxiĂšme committer PostgreSQL en Russie et le second dans l'entreprise.
La valeur
NUM_BUFFER_PARTITIONS
a également été augmentée de 128 à 512 pour réduire la charge sur les verrous de mappage: la table de hachage du gestionnaire de tampons a été divisée en morceaux plus petits, dans l'espoir que la charge sur chaque piÚce spécifique soit réduite.
AprÚs avoir appliqué ce correctif, les verrous sur le contenu des tampons ont disparu, mais malgré l'augmentation de
NUM_BUFFER_PARTITIONS
,
buffer_mapping
est resté, c'est-à -dire que nous vous rappelons de bloquer des morceaux de la table de hachage du gestionnaire de tampons:
locks_count | active_session | buffer_content | buffer_mapping ----âââ--âââ+â------âââââââââ+âââ------âââââââ+ââ------âââ 12549 | 1218 | 0 | 15
Et mĂȘme ce n'est pas grand-chose. B - l'arbre n'est plus un goulot d'Ă©tranglement. L'extension du
heap-
est apparue.
Traitement de la conscience
Ensuite, Alexander a avancé l'hypothÚse et la solution suivantes:
Nous attendons beaucoup de temps sur le
buffer parittion lock
du
buffer parittion lock
lors de l'
buffer parittion lock
tampon. Peut-ĂȘtre sur le mĂȘme
buffer parittion lock
il y a une page trÚs demandée, par exemple, la racine d'un
Bâtree
Ă ce stade, il existe un flux continu de demandes de
shared lock
partir des demandes de lecture.
La file d'attente Ă
LWLock
«pas juste». Ătant donnĂ© que
shared lock
peuvent ĂȘtre pris autant de fois que nĂ©cessaire, alors si le
shared lock
déjà pris, les
shared lock
suivants passent sans file d'attente. Ainsi, si le flux de verrous partagĂ©s est d'une intensitĂ© suffisante pour qu'il n'y ait pas de «fenĂȘtres» entre eux, alors l'attente d'un
exclusive lock
va presque Ă l'infini.
Pour résoudre ce problÚme, vous pouvez essayer d'offrir - un patch de comportement "gentlemanly" des verrous. Cela éveille la conscience des
shared locker
et ils font honnĂȘtement la queue lorsqu'ils ont dĂ©jĂ une
exclusive lock
(il est intéressant que les serrures lourdes -
hwlock
- n'aient pas de problĂšmes de conscience: elles font toujours la queue honnĂȘtement)
locks_count | active_session | buffer_content | buffer_mapping | reladdextra | inserts>30sec ââââââ-âââââ+ââââââââââââââââ+ââââââââââââââââ+âââââââââââ--â-â+ââââââ-ââââââ+ââââ------ 173985 | 1802 | 0 | 569 | 0 | 0
Tout va bien! Il n'y a pas de longues
insert
. Bien que les verrous sur les morceaux des plaques de hachage soient restés. Mais que faire, ce sont les propriétés des pneus de notre petit supercalculateur.
Ce patch a également été
offert Ă la communautĂ© . Mais quelle que soit l'Ă©volution du sort de ces correctifs dans la communautĂ©, rien ne les empĂȘche d'accĂ©der aux prochaines versions de
Postgres Pro Enterprise , conçues spécifiquement pour les clients avec des systÚmes lourdement chargés.
Moral
Des verrous de
share
légers à haute moralité -
exclusive
blocs
exclusive
sautent la file d'attente - ont rĂ©solu le problĂšme des retards horaires dans un systĂšme Ă plusieurs nĆuds. La balise de hachage du
buffer manager
n'a pas fonctionné en raison d'un flux de
share lock
trop important, ce qui n'a laissé aucune chance aux verrous nécessaires pour remplacer les anciens tampons et en charger de nouveaux. Les problÚmes avec l'extension du tampon pour les tables de base de données n'étaient qu'une conséquence de cela. Avant cela, il était possible d'élargir le goulot d'étranglement avec l'accÚs à la racine de l'
B-tree
PostgreSQL n'a pas Ă©tĂ© conçu pour les architectures NUMA et les superordinateurs. L'adaptation Ă de telles architectures Postgres est un travail Ă©norme qui nĂ©cessiterait (et Ă©ventuellement nĂ©cessiterait) les efforts coordonnĂ©s de nombreuses personnes et mĂȘme d'entreprises. Mais les consĂ©quences dĂ©sagrĂ©ables de ces problĂšmes architecturaux peuvent ĂȘtre attĂ©nuĂ©es. Et nous devons le faire: les types de charges qui ont entraĂźnĂ© des retards similaires Ă ceux dĂ©crits sont assez typiques, des signaux de dĂ©tresse similaires provenant d'autres endroits continuent de nous parvenir. Des problĂšmes similaires sont apparus plus tĂŽt - sur les systĂšmes avec moins de cĆurs, seules les consĂ©quences n'Ă©taient pas si monstrueuses et les symptĂŽmes ont Ă©tĂ© traitĂ©s avec d'autres mĂ©thodes et d'autres correctifs. Maintenant, un autre mĂ©dicament est apparu - pas universel, mais clairement utile.
Ainsi, lorsque PostgreSQL travaille avec la mĂ©moire de l'ensemble du systĂšme en tant que local, aucun bus Ă grande vitesse entre les nĆuds ne peut se comparer au temps d'accĂšs Ă la mĂ©moire locale. Les tĂąches surgissent Ă cause de cela difficile, souvent urgent, mais intĂ©ressant. Et l'expĂ©rience de les rĂ©soudre est utile non seulement pour les dĂ©cisifs, mais aussi pour toute la communautĂ©.
