Performance annoncée et contexte de comparaison

Avec la sortie de pgrust v0.4‑beta, le moteur de requêtes Gravity revendique une vitesse jusqu’à 3 × supérieure à celle de ClickHouse sur le benchmark ClickBench, conçu par les développeurs de ClickHouse pour illustrer leurs performances. Cette amélioration s’obtient tout en conservant la prise en charge complète du dialecte SQL et du système de types de PostgreSQL, ce qui élimine le compromis habituel entre vitesse brute et compatibilité.

Le gain de performance provient d’une chaîne d’optimisations accumulées depuis plusieurs versions : batching, operator fusion, utilisation de SIMD et un compilateur JIT « copy‑and‑patch » capable de générer du code machine en 5 µs. Ces techniques ont d’abord permis à pgrust d’être « centaines de fois plus rapide que PostgreSQL » pour les requêtes analytiques, avant d’être intégrées dans Gravity.

Architecture multi‑étapes du compilateur

Gravity ne remplace pas le parseur ou le planificateur de PostgreSQL. Le flux de compilation s’articule en cinq étapes : SQL Query → Postgres Plan → Pipeline Graph → Lisp IR → Assembly. Chaque étape affine la représentation du plan, du niveau logique à l’assemblage natif, afin de maximiser la liberté d’insertion d’optimisations bas‑niveau.

Le Pipeline Graph constitue une IR intermédiaire qui décrit le flux de données entre les opérations. Dans l’exemple fourni, la requête agrège, filtre et trie les enregistrements de la table hits en trois pipelines distincts (p0, p1, p2). Cette séparation explicite permet d’isoler les phases de lecture, de construction de hash‑table et de sélection du top‑K, facilitant ainsi la fusion d’opérations et la parallélisation.

IR Lisp et génération de code machine

Chaque pipeline est traduit en une fonction unique au sein d’une IR de type Lisp, proche d’un langage d’assemblage. Cette représentation générique autorise la fusion du scan, du filtre, de la projection et de l’agrégation dans une boucle optimisée. Le fragment suivant illustre le code généré pour le pipeline p0 :

(block b210 ((v2574 i64) ...)                   ; v2574 = row index; loop state rides along as block params
  (v2585 (icmp slt v2574 v103))                 ; more rows in this batch?
  (condbr v2585 (b211) (b35 ...)))              ;   no -> fetch the next batch
(block b213 ((v2587 i64))                       ; ── read the row's columns ── (Seq Scan)
  (v2591 (ptr_add v105 v2587 8 0))              ; &EventTime[row]
  (v2592 (ptr_add v109 v2587 8 0))              ; &UserID[row]
  (v2593 (ptr_add v113 v2587 4 0))              ; &URL[row]   (dictionary code)
  (v2612 (loadz i32 v2593 0))                   ; ── WHERE URL LIKE '%google%' ── (Filter)
  ...)
(block b227 ()                                  ; ── extract(minute FROM EventTime) ── (Projection)
  (v2695 (load i64 v2591 0))
  (v2711 (iadd v2695 211813660800000000))
  (v2715 (umulh (ushr v2711 10) 10746007285463371))
  (v2720 (isub v2711 (imul (ushr v2715 11) 3600000000)))
  (v2726 (ushr (imul (ushr v2720 8) 18764999) 42)) ; minute
  ...)

Cette IR permet d’appliquer des optimisations comme la vectorisation SIMD sur les boucles de lecture et le calcul de fonctions temporelles, réduisant le nombre d’instructions par ligne de données.

Objectif de proximité avec le code « hand‑tuned »

L’équipe a fixé comme cible que le code JIT généré reste dans les 20 % du temps d’exécution d’un programme Rust écrit à la main et accédant directement au format colonne‑stockage. Pour établir cette référence, ils ont demandé à Claude (un modèle de génération de code) d’écrire des programmes Rust multithreadés, sans passer par l’exécuteur SQL, qui se sont avérés ≈ 4 × plus rapides que les bases analytiques classiques, y compris ClickHouse. Gravity a ensuite extrait les motifs d’optimisation de ces programmes pour les réintégrer dans le compilateur.

En résumé, Gravity combine une compatibilité PostgreSQL totale, une chaîne de compilation à plusieurs niveaux et une génération de code machine ultra‑optimisée, ce qui explique les gains de performance mesurés sur ClickBench. La démarche « programming at light speed » repose sur une analyse descendante du plafond matériel, plutôt que sur des optimisations incrémentales, offrant ainsi une nouvelle référence pour les moteurs de bases de données analytiques.