Schema-on-Write vs Schema-on-Read

Résumé

La question est quand on impose la structure des données. En schema-on-write, on la définit avant le chargement et les données sont validées en entrant : les requêtes sont rapides et fiables, mais changer la structure coûte. En schema-on-read, on dépose les données brutes et on les interprète au moment de la requête : l’ingestion est souple, mais la qualité n’est pas garantie. La plupart des plateformes modernes combinent les deux.

Prérequis

Savoir ce qu’est un schéma (colonnes, types, contraintes) et la différence entre ETL et ELT (voir Glossaire data engineering). Les exemples de code sont écrits pour BigQuery.

AspectSchema-on-WriteSchema-on-Read
Schéma définiAvant le chargementÀ la requête
IngestionPlus lente : il faut transformer pour respecter le schémaRapide : on copie les fichiers
RequêteRapide : données déjà typéesPlus lente : le schéma est appliqué à la volée
ÉvolutionCoûteuse, parfois réécriture de la tableFacile : on change la définition de lecture
QualitéValidée à l’écritureNon garantie : valeurs manquantes, invalides, doublons
Usage typiqueBases transactionnelles, BI sur données stablesExploration, données non structurées, zone brute

Schema-on-Write

On conçoit le schéma (tables, types, clés de partitionnement), on transforme les données pour le respecter, on les charge, puis on les interroge.

Exemple, une table native BigQuery :

CREATE TABLE `my_project.clinical.patients` (
  patient_id STRING NOT NULL,
  date_of_birth DATE NOT NULL,
  blood_type STRING,
  admitted_at TIMESTAMP
)
PARTITION BY DATE(admitted_at)
CLUSTER BY patient_id
OPTIONS (require_partition_filter = true);

Un chargement avec un type incompatible échoue, ou ignore les lignes en erreur si l’option max_bad_records l’autorise : l’erreur est vue à l’ingestion, pas trois requêtes plus tard. PARTITION BY et CLUSTER BY réduisent les octets lus, donc le coût.

Coût d’évolution. Dans un RDBMS classique, changer une structure peut imposer une réécriture complète. BigQuery est plus souple : ajouter une colonne nullable est une opération de métadonnées, et ALTER TABLE … DROP COLUMN aussi, mais elle ne libère pas le stockage tout de suite et ne s’applique pas aux colonnes de partitionnement ou de clustering, aux clés primaires ou étrangères, ni aux champs imbriqués. Élargir un type est possible dans certains cas ; rendre un champ obligatoire ou changer un type de façon incompatible demande de recréer la table (CREATE OR REPLACE TABLE … AS SELECT).

Limite : les données sont façonnées pour un usage connu, donc plus difficiles à réutiliser pour un besoin imprévu.

Schema-on-Read

On dépose les fichiers tels quels et on définit leur structure au moment de les lire. Changer l’interprétation revient à changer la définition de la table ou de la requête, pas les fichiers.

Exemple : des événements JSON (un document par ligne, format JSON délimité par retour à la ligne) arrivent dans un bucket GCS, partitionnés par date.

gs://clickstream-raw/year=2026/month=07/day=19/events.json

Une table externe BigQuery les interroge sans les copier :

CREATE EXTERNAL TABLE `my_project.raw.clickstream`
WITH PARTITION COLUMNS (
  year INT64,
  month INT64,
  day INT64
)
OPTIONS (
  format = 'JSON',
  uris = ['gs://clickstream-raw/*'],
  hive_partition_uri_prefix = 'gs://clickstream-raw/'
);

Pour une exploration ponctuelle sans créer de table, on peut passer par une table externe temporaire, définie à la volée via la CLI bq (option --external_table_definition) ou l’API.

À ne pas confondre

EXTERNAL_QUERY n’est pas la bonne fonction ici : elle sert aux requêtes fédérées vers Cloud SQL, Spanner ou AlloyDB à travers une connexion, pas à lire des fichiers dans GCS.

Limites. Les requêtes sur table externe sont en général plus lentes qu’en table native, parce que BigQuery ne gère ni le stockage ni le clustering des fichiers. L’auto-détection du schéma peut mal deviner les types sur des fichiers hétérogènes. Et il faut quand même comprendre la structure pour s’en servir.

Atout : la copie brute est conservée, donc on peut retraiter si la logique de transformation était fausse.

Technique liée : le partitionnement Hive

Les fichiers sont rangés en dossiers par valeur de colonne (year=2026/month=07/day=19/). Le moteur ne lit que les dossiers utiles : c’est le partition pruning, le même principe que PARTITION BY sur une table native, appliqué à des fichiers.

Où se situent les produits

La frontière n’est pas nette, et les produits sont hybrides.

  • BigQuery : les tables natives imposent un schéma, mais l’évolution additive est peu coûteuse ; les tables externes reportent le schéma à la requête.
  • Snowflake : tables à schéma imposé, mais le type VARIANT stocke du semi-structuré interrogé sans schéma préalable.
  • MongoDB : pas de schéma imposé avant l’écriture ; chaque valeur est typée dans le document, et une validation de schéma est possible en option.
  • Elasticsearch : le mapping (souvent déduit dynamiquement) fixe le type d’un champ à la première valeur vue.
  • Data lakes, Hive, Trino : schema-on-read par construction.
  • Schema registries (Avro, Protobuf, JSON Schema, avec Kafka ; Pub/Sub accepte Avro et Protobuf) : elles imposent des règles de compatibilité sur des flux, donc un schéma qui évolue sans tout casser.

Le médaillon comme compromis

L’architecture médaillon (détaillée dans la note suivante du parcours) applique ce compromis par étapes : on garde la copie brute dans la couche bronze (peu ou pas de contrainte de schéma), on la nettoie et on la type dans silver, on construit les tables modélisées pour la BI dans gold. La bronze reste intacte, ce qui sert de filet si une transformation était fausse.

Nuance

Ce qui distingue silver de bronze est le niveau de raffinement et de qualité, pas seulement la présence d’un schéma. Le détail est dans Staging, ODS, DWH et architecture médaillon.

Notes liées