Performances PostgreSQL avec io_uring, latence I/O et CPU scheduler sur infra bare metal

Posté par wlambert le 07/02/2026
RÉSOLU

wlambert

Membre depuis le 26/12/2019

actif

Hello

On a migré notre `PostgreSQL 15` sur une nouvelle infra bare metal, avec des NVMe sur PCIe Gen4 et un kernel 6.6. On a activé `io_uring` pour les `WALs` en mode `fdatasync`.

Le souci c'est que sous forte charge d'écriture (genre 20k TPS), on voit des pics de latence `fsync` qui montent à 500ms-1s, alors que les disques ont l'air sous-utilisés d'après `iostat` (queue depth basse, %util < 30%). Le `CPU` du serveur n'est pas saturé non plus.

Quelqu'un a déjà vu ce genre de comportement avec `io_uring` et `PostgreSQL` ?

Commentaires

michele-berger

Membre depuis le 07/03/2025

actif secouriste

Intéressant. `io_uring` + `PostgreSQL` sur NVMe devrait voler. Si `iostat` est clean, ça sent le `kernel` ou le `scheduler CPU`.

Tu utilises `IORING_SETUP_SQPOLL` ? Et `kernel` `sched_debug` ou `perf sched` montrent quoi pendant ces pics ?

baubert

Membre depuis le 23/01/2025

actif secouriste

J'irais regarder le `run queue latency` avec `bpftrace`. C'est possible que les threads `PostgreSQL` se fassent préempter par le `scheduler` même si l'utilisation globale `CPU` est basse.

Genre un `bpftrace -e 'kprobe:finish_task_switch { @runqlat[comm] = hist(ktime - @start[prev_comm]) } kprobe:schedule { @start[prev_comm] = ktime }'` pour voir.

wlambert

Membre depuis le 26/12/2019

actif

Oui, `IORING_SETUP_SQPOLL` est activé, et les threads `sqpoll` sont bindés sur des `cores isolés` via `isolcpus`. Le kernel est 6.6.10.

J'ai lancé le `bpftrace` pendant le test. J'ai des histogrammes bizarres pour `postgres` :

@runqlat[postgres]:
[4ms, 8ms)             @ 32
[8ms, 16ms)            @ 15
[16ms, 32ms)           @ 8
[32ms, 64ms)           @ 2

Normalement ça devrait être beaucoup plus bas non ? Genre sous le milliseconde.

michele-berger

Membre depuis le 07/03/2025

actif secouriste

Clairement. Des latences de 32-64ms dans la `run queue` c'est énorme pour `PostgreSQL` surtout avec des `WALs`. Ça indique une contention CPU, même si l'utilisation globale est basse.

Quelles sont tes options de boot `kernel` ? `transparent_hugepage` est en `always` ? Quid des `C-states` et `P-states` ?

baubert

Membre depuis le 23/01/2025

actif secouriste

Ah oui les `C-states` agressifs peuvent poser problème. Les CPUs se mettent en veille profonde trop vite et le temps de réveil ajoute de la latence.

Essayez de désactiver `THP` (`transparent_hugepage=never`) et limiter les `C-states` profonds (`processor.max_cstate=1` ou `intel_idle.max_cstate=1` si Intel) dans les paramètres du `GRUB`.

wlambert

Membre depuis le 26/12/2019

actif

Ok j'ai vérifié : `transparent_hugepage` est bien sur `always`. Les `C-states` sont par défaut donc ça va jusqu'à `C6`.

J'applique les changements dans `GRUB_CMDLINE_LINUX_DEFAULT` :

transparent_hugepage=never processor.max_cstate=1 intel_idle.max_cstate=1

Je reboot le serveur et relance le test.

michele-berger

Membre depuis le 07/03/2025

actif secouriste

Pense aussi à `swappiness` (`vm.swappiness`). Si c'est trop haut et que tu tapes un peu dans la mémoire, le `kernel` peut commencer à swapper des pages alors que tu as de la RAM libre, juste pour libérer du cache `disk`.

Mets-le à 1 ou 5 max pour `PostgreSQL` sur un serveur dédié.

baubert

Membre depuis le 23/01/2025

actif secouriste

Et si ça persiste, regarde si tu n'as pas un `cgroup` ou `systemd unit` qui limite les `CPU shares` ou le `quota` pour le processus `PostgreSQL`.

Ça m'est arrivé qu'une règle de `cgroup` mal faite limite un service sans que ce soit évident, et `PostgreSQL` souffrait de micro-stalls.

wlambert

Membre depuis le 26/12/2019

actif

Bon j'ai rebooté avec `transparent_hugepage=never`, `processor.max_cstate=1`, `intel_idle.max_cstate=1` et `vm.swappiness=1`. Les résultats du `bpftrace runqlat` sont bien meilleurs, on est majoritairement sous le milliseconde.

Cependant, les pics de latence `fsync` ne sont pas totalement partis. Ils sont moins fréquents et moins hauts (max 200ms) mais encore présents. Les `IOPS` et `CPU` restent bas.

michele-berger

Membre depuis le 07/03/2025

actif secouriste

Ok, donc le `scheduler` est moins un problème. On a écarté les `C-states` et `THP`.

Le problème pourrait être lié à `NUMA` et la distribution des `interrupts` des NVMe. Tes `NVMe` sont sur quels `PCIe lanes` par rapport aux `sockets CPU` ? Est-ce que `irqbalance` est actif et bien configuré ?

baubert

Membre depuis le 23/01/2025

actif secouriste

Où alors c'est un truc plus bas niveau dans la gestion de l'`I/O` par `io_uring`.

Si les `io_uring` threads sont isolés, le problème peut venir du `kernel block layer` ou du `driver NVMe`. `dmesg` ne crache rien ? Pas de `NVMe reset` ou `timeout` ?

wlambert

Membre depuis le 26/12/2019

actif

J'ai fouillé `dmesg`, aucun `NVMe reset` ou erreur. `irqbalance` est actif et semble bien distribuer les `IRQs` des NVMe.

En fait je viens de checker les `cgroup`s plus en détail. On a un `systemd unit` pour un `microservice` critique qui utilise :

CPUQuota=90%
CPUAccounting=yes

Ce service a des bursts d'activité. Il est dans le même `slice` `systemd` que `PostgreSQL`.

michele-berger

Membre depuis le 07/03/2025

actif secouriste

Bingo ! `CPUQuota` à 90% pour un service critique dans le même `slice` que `PostgreSQL` ça peut être mortel.

Quand ce service fait son burst, il peut très bien accaparer 90% du temps CPU disponible pour le `slice`, et `PostgreSQL` se retrouve avec les miettes, même si le CPU global est bas. Les latences que tu vois sont dues à la starvation CPU des threads `PostgreSQL`.

baubert

Membre depuis le 23/01/2025

actif secouriste

Exactement. `CPUQuota` est une limite absolue. Si ce `microservice` en a besoin, il prend. Le `scheduler` va ensuite attribuer ce qui reste aux autres. Pour `PostgreSQL` qui est très sensible à la latence, c'est un tueur.

Le mieux est de mettre `PostgreSQL` dans un `cgroup` dédié avec des `CPU shares` élevées ou même de l'`CPUQuota` si tu veux lui garantir une part minimale.

wlambert

Membre depuis le 26/12/2019

actif

C'était ça. J'ai déplacé le `microservice` dans un `slice systemd` séparé avec son propre `CPUQuota`.

Depuis on a relancé le test de charge. Les latences `fsync` sont maintenant stables, majoritairement sous les 5ms, et le `bpftrace runqlat` est nickel.

Merci beaucoup à vous deux pour avoir creusé ça avec moi. C'était un mélange de `kernel` tuning et de `cgroup` qui m'a fait tourner en bourrique !

Laisser une réponse

Vous devez être connecté pour poster un message !

Rejoindre la communauté

Recevoir les derniers articles gratuitement en créant un compte !

S'inscrire