veilletech.fr
2 oct. Feed du jour
#08 DUCKDB Article

DuckDB : grouper sur un entier, pas sur la chaîne

Le texte se lit à la fin. Le calcul se fait sur des entiers.

L'équipe DuckDB détaille pourquoi un GROUP BY sur des chaînes longues et répétées coûte cher — hachage et comparaison octet par octet, copie dans la table de hachage — et recommande une table de dimension : chaque valeur reçoit un petit entier trié, l'agrégation se fait sur l'entier, les chaînes reviennent par jointure à la fin. Le motif entre dans le guide de performance officiel.

3 min de lectureintermédiairevidéo 1:14
Partager
Sommaire7 sections
  1. Ce qui se passe
  2. Pourquoi une chaîne coûte
  3. La table de dimension
  4. Variantes
  5. Quand ça ne suffit pas
  6. Quand s'abstenir
  7. À retenir

Ce qui se passe

Les données analytiques regorgent de libellés répétés : produits, pays, user agents, catégories. L'équipe DuckDB montre ce qu'ils coûtent dans un GROUP BY et ajoute la parade à son guide de performance. L'idée vient de Richard Wesley, à l'occasion d'un rapport de consommation mémoire.

Pourquoi une chaîne coûte

DuckDB agrège par table de hachage. Une valeur texte y occupe une structure de 16 octets : jusqu'à 12 octets, la chaîne tient dedans ; au-delà, on garde un préfixe de 4 octets et un pointeur. Pour une chaîne longue, il faut hacher tous ses octets à chaque ligne, suivre le pointeur pour confirmer une égalité, et recopier la chaîne dans la table à chaque nouveau groupe. La table s'élargit, tient moins bien dans les caches et déborde plus tôt sur disque. Le dictionnaire que DuckDB applique aux colonnes sur disque n'y change rien : à l'agrégation, chaque clé redevient une chaîne complète.

La table de dimension

Le jeu public des trains néerlandais compte 380 959 lignes pour 537 gares distinctes. Trois étapes :

  1. mesurer la cardinalité : 537, trop pour UTINYINT (255), assez pour USMALLINT (65 535) ;
  2. attribuer les clés dans l'ordre alphabétique, ce qui permet de trier sur l'entier et rend la numérotation reproductible ;
  3. réécrire la table de faits avec la clé à la place du texte, une fois pour toutes.
SQL
CREATE TABLE stations AS
    SELECT station_name,
           (row_number() OVER (ORDER BY station_name))::USMALLINT AS station_id
    FROM (SELECT DISTINCT station_name FROM train_services
          WHERE station_name IS NOT NULL);

CREATE TABLE trains AS
    SELECT t.* EXCLUDE (station_name), s.station_id
    FROM train_services t LEFT JOIN stations s USING (station_name);

On agrège ensuite sur station_id et on joint stations sur le résultat réduit — après le LIMIT pour un top N. Sur une plage aussi étroite, l'optimiseur peut choisir un agrégat à hachage parfait (perfect_ht_threshold) qui indexe directement par la clé.

Variantes

Quand ça ne suffit pas

Des clés étroites rendent chaque groupe plus petit, pas moins nombreux. Le rapport duckdb#14584 agrégeait 9,2 milliards de lignes en 320 millions de groupes, sur 8 threads. Chaque thread tenant sa propre table, la mémoire tendait vers threads × groupes. Réduire threads échange de la vitesse contre de la mémoire.

Quand s'abstenir

Chaînes courtes, colonnes presque uniques, requête ponctuelle, ou colonne jamais agrégée : le gain ne paie pas la complexité. Le gain reste d'ailleurs modeste sur ces 380 000 lignes. Comparer les deux versions avec EXPLAIN ANALYZE, et la mémoire avec un memory_limit réduit.

Source : Faster String Aggregations with Dimension Tables, DuckDB, 2 octobre 2026.