Le partitionnement divise physiquement une table BigQuery en segments selon les valeurs d’une seule colonne. Quand une requête filtre sur la colonne de partition, BigQuery effectue le pruning de partition — en sautant les partitions qui ne correspondent pas. Les octets sautés ne sont pas scannés et ne sont pas facturés.
Contrairement au clustering, le partitionnement crée des frontières physiques strictes : une partition est soit entièrement scannée, soit entièrement sautée. Ce comportement binaire rend le coût prévisible — les octets scannés peuvent être déterminés avant l’exécution de la requête.
Les trois types de partitionnement
BigQuery prend en charge trois façons de partitionner une table. Chacune sert des patterns d’accès différents.
Partitionnement par colonne temporelle
Le type le plus courant. Vous choisissez une colonne DATE, TIMESTAMP ou DATETIME et une granularité : HOUR, DAY, MONTH ou YEAR.
CREATE TABLE `project.dataset.events`PARTITION BY DATE(event_timestamp)AS SELECT * FROM source_events;Ou dans dbt :
{{ config( partition_by={ 'field': 'event_date', 'data_type': 'date', 'granularity': 'day' }) }}C’est le choix standard pour les données d’événements, les charges de travail time-series, et tout ce qui filtre systématiquement par plage de dates. La plupart des tables analytics entrent dans cette catégorie.
La décision de granularité est significative. Un partitionnement quotidien sur une table recevant 100 Mo par jour crée 365 petites partitions par an, chacune trop petite pour être bien optimisée par BigQuery. Un partitionnement mensuel combiné au clustering sur la colonne de date offre des bénéfices de gestion du cycle de vie sans la surcharge des petites partitions. Voir le cadre de décision pour les seuils de taille.
Partitionnement par temps d’ingestion
Affecte les lignes aux partitions selon le moment où BigQuery les ingère, en utilisant les pseudo-colonnes _PARTITIONTIME et _PARTITIONDATE :
CREATE TABLE `project.dataset.raw_events`PARTITION BY _PARTITIONDATEAS SELECT * FROM source_events;C’est utile quand vos données sources n’ont pas d’horodatages fiables — pensez aux systèmes legacy avec des champs created_at peu fiables, ou aux pipelines de streaming où vous voulez des partitions basées sur l’heure d’arrivée plutôt que l’heure de l’événement. L’horodatage d’ingestion est toujours précis car BigQuery le contrôle.
La contrepartie est sémantique. Le temps d’ingestion n’est pas égal au temps de l’événement. Un enregistrement portant sur quelque chose qui s’est passé le 1er mars peut arriver le 5 mars et atterrir dans la partition du 5 mars. Si vos requêtes s’intéressent à quand les choses se sont passées plutôt qu’à quand elles sont arrivées, le partitionnement par colonne temporelle sur l’horodatage d’événement est plus approprié.
Dans dbt, le partitionnement par temps d’ingestion a une interaction spéciale avec l’optimisation copy_partitions pour la stratégie insert_overwrite. Activer copy_partitions: true utilise l’API de copie de table BigQuery plutôt que des instructions MERGE, éliminant les coûts d’insertion et offrant des performances environ 40 % plus rapides sur les grandes partitions :
{{ config( materialized='incremental', incremental_strategy='insert_overwrite', partition_by={ 'field': 'created_date', 'data_type': 'timestamp', 'granularity': 'day', 'time_ingestion_partitioning': true, 'copy_partitions': true }) }}Partitionnement par plage d’entiers
Divise les données par plages de colonnes INTEGER que vous définissez — début, fin et intervalle :
partition_by={ 'field': 'customer_id', 'data_type': 'int64', 'range': { 'start': 0, 'end': 1000000, 'interval': 1000 }}Cela fonctionne pour les patterns d’accès basés sur des ID ou les scénarios de segmentation par client. C’est moins courant que le partitionnement temporel, mais il résout bien des problèmes spécifiques — les systèmes multi-tenant où les requêtes filtrent toujours par ID de tenant, ou les architectures de données segmentées par tranches de clients.
L’intervalle définit le nombre de valeurs distinctes dans chaque partition. Un intervalle de 1 000 avec une plage de 0 à 1 000 000 crée 1 000 partitions. Les valeurs hors plage vont dans une partition spéciale __UNPARTITIONED__.
Contraintes clés
Trois contraintes conditionnent la conception des tables partitionnées :
Une seule colonne. Vous ne pouvez partitionner que sur exactement une colonne. Si vous avez besoin de filtrer sur plusieurs dimensions, partitionnez sur le filtre le plus courant et clustérisez sur les autres.
Limite de 10 000 partitions. Chaque table peut avoir au maximum 10 000 partitions. Avec une granularité quotidienne, c’est environ 27 ans de données — généralement suffisant. Mais si vous partitionnez par heure sur des données couvrant des années, vous atteindrez cette limite. Et le partitionnement par plage d’entiers peut l’atteindre rapidement avec de grandes plages et de petits intervalles.
Expressions constantes requises pour le pruning. C’est la contrainte qui surprend le plus souvent. Le pruning de partition ne fonctionne que si la valeur du filtre est connue au moment de la planification de la requête. Un filtre comme WHERE event_date = '2025-01-15' effectue le pruning car la valeur est un littéral. Mais WHERE event_date = (SELECT MAX(date) FROM other_table) n’effectue pas le pruning car la valeur dépend d’une sous-requête qui n’a pas encore été exécutée.
Cette contrainte s’étend aux fonctions. WHERE DATE(event_timestamp) = '2025-01-15' sur une table partitionnée par TIMESTAMP désactive le pruning car BigQuery ne peut pas évaluer la fonction au moment de la planification. Voir Patterns de pruning de partitions BigQuery pour le catalogue complet des anti-patterns de pruning et leurs corrections.
Ce que le partitionnement apporte
Au-delà de la réduction des coûts de requêtes, le partitionnement active plusieurs capacités opérationnelles que le clustering n’offre pas :
Estimations de coûts prévisibles avant l’exécution. Le pruning de partition détermine exactement combien d’octets seront scannés avant l’exécution de la requête. Les estimations de clustering ne sont finalisées qu’après l’exécution.
Gestion du cycle de vie des partitions. Expiration automatique via partition_expiration_days, suppression efficace de plages de dates entières, et écriture dans des partitions spécifiques sans toucher les autres. Supprimer un mois de données utilisateur est une suppression rapide de partition plutôt qu’un scan et réécriture complets de la table.
Écritures ciblées dans dbt. La stratégie insert_overwrite remplace des partitions entières de manière atomique — sans comparaison au niveau des lignes, sans scan complet de table. Cela nécessite le partitionnement.
Enforcement du filtre de partition. require_partition_filter = true empêche les requêtes sans filtre de partition de s’exécuter, rendant impossibles les scans complets accidentels sur les grandes tables.
La relation entre partitionnement et clustering
Le partitionnement et le clustering sont complémentaires, pas concurrents. Le partitionnement crée des frontières strictes qui éliminent des segments entiers. Le clustering organise les données à l’intérieur de ces segments pour un saut de blocs plus précis. Sur les grandes tables, utiliser les deux offre les meilleurs résultats — le pruning de partition donne la première réduction de coût de 10 à 100×, et le clustering ajoute encore 2 à 10× à l’intérieur de chaque partition.
Le cadre de décision explique exactement quand utiliser chacun, et quand les combiner.