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

wlambert 07/02/2026
RÉSOLU
wlambert
Auteur Actif
Avatar de wlambert
wlambert
Auteur 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 ?

07/02/2026 à 11:00

15 commentaires

michele-berger
Membre Actif
Avatar de michele-berger
michele-berger
Membre Actif

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 ?

Modifié le 23/05/2026 à 16:20
baubert
Membre Secouriste
Avatar de baubert
baubert
Membre 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.

Modifié le 23/05/2026 à 16:20
wlambert
Auteur Actif
Avatar de wlambert
wlambert
Auteur 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.

Modifié le 23/05/2026 à 16:20
michele-berger
Membre Actif
Avatar de michele-berger
michele-berger
Membre Actif

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 ?

Modifié le 23/05/2026 à 16:20
baubert
Membre Secouriste
Avatar de baubert
baubert
Membre 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.

Modifié le 23/05/2026 à 16:20
wlambert
Auteur Actif
Avatar de wlambert
wlambert
Auteur 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.

Modifié le 23/05/2026 à 16:20
michele-berger
Membre Actif
Avatar de michele-berger
michele-berger
Membre Actif

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é.

Modifié le 23/05/2026 à 16:20
baubert
Membre Secouriste
Avatar de baubert
baubert
Membre 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.

Modifié le 23/05/2026 à 16:20
wlambert
Auteur Actif
Avatar de wlambert
wlambert
Auteur 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.

Modifié le 23/05/2026 à 16:20
michele-berger
Membre Actif
Avatar de michele-berger
michele-berger
Membre Actif

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é ?

Modifié le 23/05/2026 à 16:20
baubert
Membre Secouriste
Avatar de baubert
baubert
Membre 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 ?

Modifié le 23/05/2026 à 16:20
wlambert
Auteur Actif
Avatar de wlambert
wlambert
Auteur 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 cgroups 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.

Modifié le 23/05/2026 à 16:20
michele-berger
Membre Actif
Avatar de michele-berger
michele-berger
Membre Actif

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.

Modifié le 23/05/2026 à 16:20
baubert
Membre Secouriste
Avatar de baubert
baubert
Membre 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.

Modifié le 23/05/2026 à 16:20
wlambert
Auteur Actif
Avatar de wlambert
wlambert
Auteur 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 !

Modifié le 23/05/2026 à 16:20

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