En bref. À la demande, BigQuery facture les octets lus, pas le temps de calcul. Sur 23 millions de lignes (jeu public Stack Overflow), deux écritures qui lisent exactement les mêmes octets ont montré des écarts de temps de slot d’un facteur 5 à 10 : invisibles dans une facture à la demande, mais directs sous réservation de capacité. Trois idées reçues tombent (QUALIFY plus rapide, LIMIT moins cher, ROW_NUMBER stable), une tient (l’approximation des quantiles). La règle : choisir ses optimisations selon le modèle de tarification, et mesurer le temps de slot en plus des octets.

Que facture BigQuery, et que ne voit-il pas ?

BigQuery propose deux modèles de calcul. À la demande, on paie le nombre d’octets traités par requête (le premier TiB du mois est gratuit). Par capacité, on paie des slots, une capacité de calcul mesurée dans le temps (tarifs BigQuery). Deux requêtes qui lisent les mêmes octets coûtent donc pareil à la demande, même si l’une mobilise bien plus de calcul. Sous capacité, c’est l’inverse : les octets importent peu, le temps de slot est la facture.

L’inverse existe aussi. Dans mon retour d’expérience sur une vue relancée par ses tests, le coût était un volume d’octets : il se voyait dans la facture, pas dans les modèles.

Méthode : mesures du 19 octobre 2026, sans cache, 10 exécutions par cas (temps de slot médian) sur bigquery-public-data.stackoverflow.posts_questions, soit 23 millions de lignes. Sur un petit jeu, la même requête variait d’un facteur 4 d’une exécution à l’autre : une mesure unique ne prouve rien.

Quelles écritures coûtent plus de calcul pour les mêmes octets ?

ComparaisonTemps de slotDuréeÀ noter
CTE lue deux fois, contre une fois6,0 s contre 1,1 snon relevéeoctets identiques (0,18 Go)
LAG, contre self-join (question précédente du même utilisateur)50,6 s contre 502,8 s0,73 s contre 7,6 smême résultat (mêmes lignes, même hash)
ROW_NUMBER global, contre PARTITION BY95 s contre 44 s13,3 s contre 0,74 sdurée ×18

La CTE référencée deux fois est exécutée à chaque référence : la documentation dit que les CTE non récursives servent surtout à la lisibilité. Une partie du surcoût vient du doublement des lignes par l’UNION ALL de mon test.

Le rapport LAG contre jointure n’est pas une constante : 2,5 à 1 % des utilisateurs, 10,5 à 10 %, 9,9 à 100 %. Il se stabilise parce que la plupart des utilisateurs ont peu de questions ; il serait plus lourd si quelques entités concentraient beaucoup de lignes, ce que je n’ai pas testé.

Ces trois écarts n’apparaissent pas dans une facture à la demande. Sous réservation, le deuxième représente dix fois plus de temps de slot pour le même résultat.

Quelles idées reçues tiennent, lesquelles tombent ?

  • QUALIFY serait plus rapide qu’une CTE de filtrage : faux. Dernière question de chaque utilisateur : même résultat (4 657 919 lignes, même hash), même plan en 3 étapes, mêmes 0,55 Go lus. Temps de slot médian de 38,8 s (35,9 à 43,6 s) contre 38,1 s (36,9 à 41,9 s). Le choix est une affaire de lisibilité.
  • LIMIT réduirait le coût : faux. Sur thelook_ecommerce.order_items (180 842 lignes), le --dry_run donne 13 575 706 octets pour select *, le même chiffre avec limit 10, 10 534 522 avec select * except sur quatre colonnes de dates (−22 %) et 2 893 472 pour deux colonnes (−79 %). Seule la liste des colonnes réduit les octets.
  • ROW_NUMBER sans départage serait stable : vrai à petite échelle, faux à grande. Sur 124 898 lignes, 6 exécutions donnent 1 seule affectation. Sur 23 020 127 lignes triées par score, 10 exécutions donnent 10 affectations différentes, avec le même nombre de lignes et sans erreur. La documentation annonce l’ordre des ex æquo comme non déterministe : un test sur un échantillon passe, la production non.
  • APPROX_QUANTILES serait trop imprécis : faux pour un seuil, vrai pour un chiffre à auditer. Médiane identique (357), P90 entre −0,46 % et +0,08 % de la valeur exacte (3 927), P99 entre −1,10 % et +0,21 % (40 346,74). Pour 6,6 s de slot contre 149,9 s avec le calcul exact en fenêtre (23 fois moins), et 0,36 s de durée contre 14,0 s (39 fois moins). PERCENTILE_CONT reste le choix quand la valeur doit être exacte.

Quelle règle de décision en tirer ?

  1. Savoir quel modèle de tarification s’applique avant d’optimiser. À la demande, on optimise les octets (colonnes lues, partitionnement, vues relancées). Sous capacité, on optimise le temps de slot (jointures d’inégalité, fenêtres sans PARTITION BY, CTE réutilisées).
  2. Mesurer les deux. Le --dry_run donne les octets ; les jobs réels donnent le temps de slot :
select job_id, total_bytes_billed, total_slot_ms,
       timestamp_diff(end_time, start_time, millisecond) as duree_ms
from `region-eu`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
where creation_time >= timestamp_sub(current_timestamp(), interval 1 day)
  and job_type = 'QUERY' and state = 'DONE'
order by total_slot_ms desc
limit 10;
  1. Répéter la mesure. 10 exécutions sans cache, et la médiane avec la plage.
  2. Ne pas confondre style et performance. Quand deux écritures sont indiscernables (QUALIFY et CTE), la lisibilité décide.

Limites de ces mesures : un seul jeu public, une seule région, aucune table partitionnée ou clusterisée, et aucune facture de capacité réelle. Les rapports de temps de slot sont des ordres de grandeur, pas des prix en euros. Mon propre projet de prix des carburants se compte en euros par mois : il illustre un mécanisme, pas une échelle.

Avant un chantier d’optimisation, trois questions : quel modèle de tarification, quel poste domine la facture (octets ou slots), et à partir de quel gain l’effort se justifie. La réponse est rarement dans la syntaxe.

À propos. Je suis Camille Lebrun, Data & IA engineer à Toulouse. Je bâtis et fiabilise des plateformes data : fondations, migration, IA vérifiable. Je documente ici ce que j’applique en mission et dans mes projets. Une migration ou une plateforme à fiabiliser ? Écrivez-moi. camillelebrun.fr · LinkedIn