ServicesÀ proposNotesContact Me contacter →
EN FR
Note

Application des contraintes dbt selon les warehouses

Comment les types de contraintes dbt se comportent entre Postgres, Snowflake, BigQuery, Redshift et Databricks — quelles contraintes rejettent réellement les mauvaises données et lesquelles sont seulement métadonnées.

Planté
dbtdata qualitydata modeling

dbt supporte six types de contraintes : not_null, unique, primary_key, foreign_key, check et custom. La complication réside dans le fait que l’application dépend entièrement de votre warehouse. Déclarer une contrainte dans votre YAML ne signifie pas que le warehouse rejettera les mauvaises données. Cette divergence piège presque toutes les équipes qui adoptent les contrats de modèles dbt pour la première fois.

Application par plateforme

Postgres applique les six types de contraintes. Chaque contrainte que vous déclarez rejettera effectivement les données invalides au moment de l’insertion. Sur Postgres, les contraintes se comportent exactement comme on le prévoirait dans une perspective de base de données relationnelle.

Snowflake n’applique que not_null. Les clés primaires, les contraintes d’unicité et les clés étrangères sont acceptées dans le DDL mais traitées uniquement comme métadonnées. Elles ne rejetteront pas les mauvaises données. Snowflake utilise néanmoins les métadonnées de clés primaires et de contraintes d’unicité pour l’optimisation des requêtes (via le mot-clé rely), donc les déclarer n’est pas inutile — mais elles ne sont pas protectrices.

BigQuery suit un pattern similaire à Snowflake : not_null est appliqué, tout le reste est informationnel. Les contraintes de clés primaires et étrangères existent comme métadonnées pour l’optimisation des requêtes et à des fins de documentation.

Redshift reflète le comportement de BigQuery : not_null est appliqué, les autres contraintes sont acceptées dans le DDL mais non validées au moment de l’insertion.

Databricks applique les contraintes not_null et check, mais les applique via ALTER TABLE après la création de la table. Si une contrainte échoue sur Databricks, la table avec les données incriminées existe toujours dans le warehouse. C’est une différence subtile mais importante par rapport au comportement fail-fast du preflight check de dbt — les données atterrissent avant que la contrainte ne les capture.

La matrice d’application

ContraintePostgresSnowflakeBigQueryRedshiftDatabricks
not_nullAppliquéeAppliquéeAppliquéeAppliquéeAppliquée
uniqueAppliquéeMétadonnées uniquementMétadonnées uniquementMétadonnées uniquementNon supportée
primary_keyAppliquéeMétadonnées uniquementMétadonnées uniquementMétadonnées uniquementNon supportée
foreign_keyAppliquéeMétadonnées uniquementMétadonnées uniquementMétadonnées uniquementNon supportée
checkAppliquéeNon supportéeNon supportéeNon supportéeAppliquée (post-création)
customVariableVariableVariableVariableVariable

La conclusion pratique : not_null est la seule contrainte sur laquelle on peut compter sur toutes les plateformes majeures. Tout le reste nécessite une vérification via des tests dbt.

Le pattern défensif

En pratique, vous déclarez des contraintes pour la documentation et l’optimisation, puis vous les associez à des tests dbt pour la validation réelle :

columns:
- name: customer__id
data_type: integer
constraints:
- type: not_null
- type: primary_key
warn_unenforced: false
data_tests:
- unique
- not_null

Définir warn_unenforced: false indique à dbt que vous comprenez que la contrainte n’est pas appliquée sur votre plateforme et que vous la gérez avec des tests à la place. Sans ce flag, dbt génère un avertissement à chaque build, ce qui crée du bruit qui masque les vrais problèmes.

La duplication est intentionnelle. La contrainte documente l’intention (cette colonne est une clé primaire) et active l’optimisation des requêtes. Le test valide la réalité (cette colonne a effectivement des valeurs uniques et non nulles). Sur Postgres, les deux mécanismes appliquent la même règle. Sur Snowflake ou BigQuery, seul le test offre une protection.

Contraintes personnalisées

Les contraintes personnalisées permettent d’attacher des expressions spécifiques à la plateforme. Sur Snowflake, par exemple, vous pouvez appliquer une politique de masquage directement dans le contrat :

constraints:
- type: custom
expression: "masking policy my_policy"

C’est utile pour intégrer des politiques de gouvernance dans vos définitions de modèles. La politique de masquage est appliquée via le DDL, donc elle est appliquée par le warehouse indépendamment des limitations d’application des contraintes. Les autres utilisations courantes incluent les politiques d’accès aux lignes, le chiffrement au niveau des colonnes et les fonctionnalités de gouvernance des données spécifiques à la plateforme.

Les contraintes personnalisées sont la trappe d’évacuation pour tout ce que les six types de contraintes standard de dbt ne couvrent pas. Elles passent l’expression directement au DDL, donc vous êtes responsable de vérifier que l’expression est valide pour votre warehouse cible.

Pourquoi cela compte pour la stratégie de contrats

Comprendre le comportement d’application façonne votre approche des contrats en pratique :

  1. Sur Postgres : Les contraintes seules offrent une protection robuste. Les tests ajoutent une défense en profondeur mais ne sont pas strictement nécessaires pour ce que les contraintes couvrent déjà.

  2. Sur Snowflake, BigQuery, Redshift : Les contraintes sont des aides à la documentation et à l’optimisation. Les tests sont votre seul mécanisme d’application pour l’unicité, l’intégrité référentielle et la validation des valeurs. Ne comptez jamais sur une contrainte pour la protection des données sur ces plateformes.

  3. Sur Databricks : Le modèle d’application post-création signifie que vous pourriez brièvement avoir de mauvaises données dans les tables avant que les contraintes ne les capturent. Les tests s’exécutant dans le même build détecteront les mêmes problèmes, mais les modèles en aval qui s’exécutent entre la création de la table et la vérification de la contrainte pourraient voir les données invalides.

Le modèle de validation à trois couches s’applique ici avec une force particulière. Les contrats gèrent la structure (existence et types des colonnes). Les contraintes gèrent un sous-ensemble de vérifications d’intégrité (de manière fiable uniquement not_null). Les tests gèrent tout le reste. Traiter les contraintes comme mécanisme d’application plutôt que comme métadonnées sur les warehouses cloud est l’une des erreurs les plus courantes dans l’adoption des contrats dbt.