8 points par GN⁺ 2026-01-30 | 3 commentaires | Partager sur WhatsApp
  • PgDog, un proxy d’extension pour PostgreSQL, a introduit des liaisons directes Rust au lieu de la sérialisation Protobuf afin d’améliorer les performances de parsing SQL
  • L’ancienne architecture basée sur Protobuf a été remplacée par une conversion directe C–Rust (bindgen + wrappers générés par Claude), avec à la clé des performances 5,45× supérieures en parsing et 9,64× en déparsing
  • Le goulot d’étranglement de performance a été identifié dans la fonction pg_query_parse_protobuf ; après des tentatives de mise en cache, l’équipe a procédé à un changement structurel pour obtenir un gain durable
  • En utilisant le LLM Claude, l’équipe a généré automatiquement 6 000 lignes de code de conversion Rust–C et les a appliquées aux fonctions clés comme parse, deparse, fingerprint et scan
  • Grâce à cette optimisation, l’utilisation CPU et la latence de PgDog ont diminué, ce qui améliore fortement son efficacité comme proxy de scalabilité horizontale pour PostgreSQL

PgDog et les limites de Protobuf

  • PgDog est un proxy destiné à faire monter PostgreSQL en charge, et utilise en interne libpg_query pour parser les requêtes SQL
    • Écrit en Rust, il communiquait auparavant avec la bibliothèque C via la sérialisation/désérialisation Protobuf
  • Protobuf est rapide, mais les liaisons directes le sont davantage
    • L’équipe de PgDog a forké pg_query.rs pour supprimer Protobuf et implémenter des liaisons directes C–Rust
    • Résultat : le parsing des requêtes est devenu 5,45 fois plus rapide, et le déparsing 9,64 fois plus rapide

Résultats des benchmarks

  • Les benchmarks peuvent être reproduits dans le dépôt forké de PgDog
    • pg_query::parse (Protobuf) : 613 QPS
    • pg_query::parse_raw (C–Rust direct) : 3357 QPS
    • pg_query::deparse (Protobuf) : 759 QPS
    • pg_query::deparse_raw (Rust–C direct) : 7319 QPS

Analyse du goulot d’étranglement et tentative de cache

  • L’analyse du temps CPU avec le profiler samply a montré que la fonction pg_query_parse_protobuf constituait le principal goulot d’étranglement
  • Une amélioration partielle a été tentée via le cache
    • Utilisation d’un cache hashmap LRU stockant l’AST avec le texte de la requête comme clé
    • Réutilisation possible dans le cas des prepared statements
  • Cependant, certains ORM généraient des milliers de requêtes uniques, et d’anciens drivers PostgreSQL ne prenaient pas en charge les prepared statements, ce qui limitait fortement l’efficacité du cache

Suppression de Protobuf à l’aide d’un LLM

  • L’équipe de PgDog a utilisé le LLM Claude pour générer les liaisons Rust sans Protobuf
    • L’IA s’est montrée efficace sur une tâche au périmètre clair et vérifiable
  • À partir de la spécification Protobuf de libpg_query, Claude a mappé les structures C vers des structures Rust
    • Après 2 jours d’itérations, cela a abouti à 6 000 lignes de code Rust récursif
  • L’approche a été appliquée aux fonctions parse, deparse, fingerprint et scan, avec un gain de performance de 25 % selon pgbench

Détails d’implémentation

  • Les conversions entre Rust et C utilisent des fonctions unsafe pour mapper directement les structures
    • Les structures C sont transmises à l’API Postgres pour générer l’AST, puis converties récursivement en Rust
  • Chaque nœud de l’AST est traité par la fonction convert_node, qui mappe les centaines de tokens de la grammaire SQL
    • Des fonctions de conversion séparées existent pour chaque type de nœud, comme SELECT, INSERT, etc.
  • Le résultat de conversion réutilise la structure Protobuf existante (protobuf::ParseResult), ce qui permet une validation par comparaison octet par octet lors des tests
  • L’algorithme récursif est plus rapide qu’une implémentation itérative, car il effectue moins d’allocations mémoire et exploite mieux le cache CPU
    • Une implémentation itérative s’est révélée plus lente à cause d’allocations mémoire inutiles et de recherches dans une hashmap

Conclusion

  • En réduisant la surcharge du parseur Postgres, PgDog diminue à la fois la latence, la mémoire et l’utilisation CPU
  • Grâce à cette optimisation, PgDog devient un proxy d’extension PostgreSQL plus rapide et moins coûteux à exploiter
  • PgDog recrute actuellement des ingénieurs pour construire avec lui la prochaine itération de la scalabilité horizontale de PostgreSQL

3 commentaires

 
a1eng0 2026-01-31

Il se peut que je déforme le sens du texte original, mais j’ai l’impression que, dans les articles sur Rust en particulier, on écrit souvent comme si c’était « parce que c’est du Rust » que c’était devenu plus rapide, en laissant de côté l’essentiel.

Le point principal de cet article, c’est pourtant que les performances se sont améliorées en réduisant une surcharge de sérialisation inutile.

En le relisant maintenant, je n’ai pas non plus l’impression que c’est un article qui fait particulièrement l’éloge de Rust, mais est-ce que c’est parce que d’autres articles m’ont laissé une perception négative ?

 
xguru 2026-01-31

Moi aussi, j’ai trouvé que le titre original était un peu trop centré sur Rust par rapport au contenu réel, au point de donner l’impression que l’accent était mis sur le gain de performances, donc je l’ai légèrement modifié.
On retrouve assez souvent cette tendance dans les articles sur Rust, donc j’ai l’impression qu’il faut les lire en gardant un petit filtre.

 
GN⁺ 2026-01-30
Commentaires sur Hacker News
  • Le titre donne l’impression que Rust a apporté une amélioration de performances de 5x, alors qu’en réalité la situation était devenue plus lente, ce qui est assez ironique
    Le problème venait du fait qu’un logiciel écrit en Rust devait utiliser libpg_query, une bibliothèque en C, mais comme ils ne pouvaient pas la relier directement, ils ont utilisé des bindings Rust–C basés sur Protobuf
    Cette approche étant lente, ils ont finalement réécrit des bindings nouveaux, non portables mais bien mieux optimisés, avec l’aide d’un LLM
    Si tout avait été écrit en C dès le départ, cette étape de conversion n’aurait pas été nécessaire. Autrement dit, un titre comme « on a réduit la perte de performances causée par l’usage de Rust » aurait été plus exact
    Les couches de conversion apportent de la portabilité et de la sécurité, mais à force de copies, conversions et sérialisations, elles finissent aussi par ralentir les applications

    • Ce n’est pas Rust qui était lent, mais la conception inefficace de la bibliothèque externe
      Appeler une bibliothèque C depuis Rust est très simple, et il existe déjà beaucoup de wrappers sûrs
      Une architecture avec Protobuf au milieu est quelque chose qu’on voit rarement, et c’était là le goulot d’étranglement
      Le titre ressemble surtout à un mème du type « réécrit en Rust » destiné à attirer des clics
    • Dire que cela aurait été plus rapide en C n’est pas vraiment juste
      À l’origine, la bibliothèque reposait sur une mauvaise conception avec sérialisation/désérialisation répétée, et c’est la suppression de cette couche qui a fait la différence
      Un titre du genre « remplacer Protobuf par une API classique l’a rendu 5x plus rapide » serait plus précis
    • Je me demande pourquoi ils n’ont pas utilisé directement la FFI
      Les bindings C sont parmi les plus simples en Rust, et tant que l’API n’est pas énorme, cela reste assez direct
      Protobuf me semble être un outil inadapté pour échanger des données en mémoire
    • S’ils ont utilisé un LLM pour optimiser, je me demande s’il n’aurait pas mieux valu l’utiliser pour porter entièrement la bibliothèque C en Rust
      Avec les LLM, j’ai l’impression qu’on va voir exploser les portages vers toutes sortes de langages
    • Mettre Protobuf entre Rust et Postgres relève du cauchemar de performance. Je suis surpris qu’une telle bibliothèque ait pu devenir populaire
  • Le titre est quelque peu trompeur
    En pratique, c’est surtout « on a retiré l’étape de sérialisation Protobuf, donc c’est devenu plus rapide »

    • Protobuf fournit une compatibilité de version qu’une simple copie mémoire ne permet pas
      Cela permet au client et au serveur d’être mis à jour indépendamment tout en continuant à fonctionner, et facilite la communication entre plusieurs langages
      Dans les grands systèmes, ce type de flexibilité est très important
    • Le fait que la sérialisation Protobuf ne soit « que » 5 fois plus lente qu’une simple copie mémoire donne presque l’impression que c’est plus rapide que prévu
    • memcpy ou mmap sont bien plus rapides, mais dans l’écosystème Rust, ce genre de méthodes non sûres est généralement mal vu
    • Dans un cas comme celui-ci, un format zero-copy standardisé comme Arrow serait probablement un bon choix. Il gère automatiquement les problèmes de padding selon les langages ainsi que les vérifications de sécurité
  • La lenteur vient peut-être moins de Rust que de l’usage de Protobuf comme format de stockage généralisé
    Au fond, l’essentiel a été de simplifier l’implémentation pour l’adapter à un objectif précis

    • Un titre comme « Protobuf remplacé par une implémentation native optimisée » aurait attiré moins d’attention
      Le fait d’avoir mis Rust dans le titre ressemble à un choix purement destiné à générer des clics
    • Le titre de l’article suscite la polémique, mais le texte lui-même en est conscient
    • En réalité, cela n’a presque rien à voir avec Rust, mais sans Rust, l’article ne serait probablement pas arrivé en page d’accueil
  • L’auteur original de pg_query explique le contexte
    À l’origine, chez pganalyze, l’outil servait à analyser des requêtes Postgres pour trouver les références aux tables, puis à les réécrire et les formater
    Au début, ils utilisaient JSON, puis ils sont passés à Protobuf afin de proposer plus facilement des bindings typés dans plusieurs langages (Ruby, Go, Rust, Python, etc.)
    Pour un langage comme Rust, la FFI est préférable, mais pour d’autres langages, le coût de maintenance est plus élevé
    Il soutient l’approche de Lev et prévoit d’ajouter à l’avenir des fonctions permettant d’accéder directement à libpg_query via la FFI
    Cela dit, lorsque les performances ne sont pas critiques, Protobuf reste encore un choix plus pratique

  • Le « 5x plus rapide » rappelle la blague de Cap’n Proto, « infiniment plus rapide »

    • Cap’n Proto a été créé par l’auteur de Protobuf, avec une structure qui ne nécessite pas de parsing, d’où cette formule
    • Mais en pratique, à l’usage, Cap’n Proto est moins agréable à utiliser
  • Le titre est exagéré, mais le travail réalisé est impressionnant
    Ils n’ont pas complètement supprimé Protobuf, ils ont surtout optimisé la façon de l’utiliser
    La formule « on a remplacé X et c’est devenu 5x plus rapide » veut souvent dire en réalité « on a corrigé une implémentation bancale »
    Les principales leçons sont :

    1. La sérialisation/désérialisation devient facilement un goulot d’étranglement caché
    2. Les implémentations par défaut sont rarement optimisées pour tous les cas
    3. Il faut profiler pour identifier précisément les goulots d’étranglement
      La FFI Rust a elle aussi un coût, donc le vrai gain vient moins du langage que de la refonte du flux de données et du travail d’optimisation
  • FlatBuffers est plus rapide, mais si Protobuf reste utilisé, c’est parce qu’il est maintenu par une grande entreprise

    • Mais FlatBuffers est lui aussi maintenu par Google
      Au final, l’idée que « c’est sûr parce que c’est fait par Google » n’a pas vraiment de fondement
    • J’ai moi-même déjà publié du code sur l’ancienne plateforme Google (code.google.com) pour voir ensuite le service disparaître
      Une simple structure zero-copy avec mémoire partagée et champs de version suffirait largement, il n’y a pas forcément de raison d’utiliser Protobuf
    • Google n’a toujours pas publié son optimisation zero-copy pour les champs de chaîne
  • Les performances de Protobuf sont, à mon avis, une plaisanterie
    Il faut utiliser des formats zero-copy où la sérialisation est quasiment gratuite
    Par exemple, Lite³, que j’ai créé, est 242 fois plus rapide que FlatBuffers

    • Mais cette bibliothèque n’est apparue qu’après novembre 2025
      Si Protobuf continue d’être utilisé, c’est pour tout un ensemble de raisons très concrètes : l’écosystème, les schémas, l’outillage selon les langages, etc.
  • En réalité, le problème ne venait ni de Rust ni de Protobuf, mais d’une implémentation de sérialisation inefficace dans la couche d’abstraction PostgreSQL
    pgdog a supprimé cette couche et a transmis les données directement à l’API C
    Éliminer des fonctionnalités inutiles rend forcément les choses plus rapides
    Mais certaines personnes ont encore besoin de sérialisation dans leur cas d’usage
    Pour elles, un titre disant « passez à Rust » envoie un mauvais message
    Dans la plupart des cas, JSON suffit, et si l’on a vraiment besoin d’aller plus vite, il vaut mieux éviter la sérialisation elle-même

  • C’est une comparaison injuste
    Utiliser un protocole de sérialisation pour des communications IPC implique forcément un surcoût
    C’est un cas qui illustre bien la formule : « si c’est 20 % plus rapide, c’est une amélioration ; si c’est 10 fois plus rapide, c’est que c’était mal conçu dès le départ »