Contexte et charge de travail

Conviva traite plusieurs billions d’événements quotidiens grâce à un moteur d’analyse basé sur DataFusion, Arrow et Rust. Les fichiers Arrow IPC, de 3 à 5 Go chacun, sont stockés sur NVMe puis lus en mémoire locale. Un scénario typique implique la lecture de six colonnes réparties sur huit fichiers, soit environ 1,6 Go par lot et 13 Go de données par jour. Le serveur de test possède 192 cœurs, 750 Go de RAM et deux configurations de disque : 2 × NVMe LVM en bande (débit maximal ~5,5 GB/s) et 32 × NVMe RAID‑0 (~21 GB/s). Le noyau était en version 5.15 pendant les mesures, 6.x en production.

Mécanique du mmap et goulots d’étranglement

Le mmap fournit un accès zéro copie, mais chaque accès déclenche un page fault si la page n’est pas déjà présente dans le cache. Sous charge, le processus a généré plus de 2 millions de minor faults par seconde et plusieurs centaines de major faults (ex. 571 / s, 1352 / s). Ces fautes obligent le noyau à acquérir le mmap_lock (ou le verrou par VMA depuis Linux 6.4) et à insérer ou évincer des pages du cache. Le tableau suivant montre l’évolution du RSS : 652 GB (87,9 %) à 734 GB (98,9 %) avant que le kernel commence l’éviction, provoquant une « tempête de faults majeurs ».

23:27:46  RSS = 734 GB (98.91%)
23:27:48  major faults: 571/s, 1352/s, 975/s
23:27:09  1,255,709 minor faults/sec
23:27:46  2,354,383 minor faults/sec

Chaque minor fault coûte 0,5‑1 µs ; à 2 M/s, le coût total approche la capacité maximale du mmap. De plus, le nombre de commutations de contexte dépasse 2 M/s, contre 14 k/s en situation de cache chaud, indiquant que les threads sont constamment bloqués en attente de pages.

Essai avec io_uring et résultats

Pour réduire la dépendance au cache partagé, l’équipe a remplacé le mmap par io_uring, qui permet des I/O asynchrones sans passer par le même verrou de page‑cache. Malgré cette modernisation, les mesures de latence p95 sont passées de ~30 s à plus de 150 s sous charge réelle, et les performances restent inférieures à celles obtenues avec mmap lorsqu’un seul pod était déployé. Le test contrôlé (1 pod vs 4 pods) montre que le pod unique était 41 % plus rapide au maximum et plus de 20 % plus rapide au p95, confirmant que le partage du cache reste le facteur limitant.

Analyse des métriques et limites

Les données indiquent que le problème principal n’est pas le débit du disque (le RAID‑0 atteint 21 GB/s) mais la contention sur le page‑cache partagé et les verrous du noyau. L’augmentation du nombre de VMA (≈ 3 TB d’espace virtuel) alourdit chaque recherche de VMA, même avec le verrou rapide par VMA. Le profil perf top montre que la fonction __filemap_add_folio domine les cycles CPU en situation froide, tandis que le code applicatif ne représente plus que 5 % du temps CPU. Les temps off‑CPU révèlent que 30,9 % du temps d’attente provient de futexes liés à la gestion des faults, et 29,3 % à la préemption par les kthreads de readahead.

En résumé, remplacer mmap par io_uring n’élimine pas la contrainte fondamentale : le cache de pages du noyau reste une ressource unique et fortement contentionnée lorsqu’un grand nombre de processus lisent simultanément de gros fichiers Arrow. Toute optimisation future devra soit isoler le cache (par exemple via des namespaces de mémoire), soit réduire la taille des fichiers ou la granularité des accès afin de limiter le nombre de faults et la pression sur les verrous du noyau.