Comment valider les données dans Amazon Redshift

Un guide pratique de la validation de la qualité des données dans Amazon Redshift — les six contrôles dont toute table a besoin, comment les tenir à l'écart de la file WLM, le piège des contraintes non appliquées, et comment les exécuter sous forme de contrat de données versionné.

· 9 min read

Pour valider les données dans Amazon Redshift, exprimez chaque attente sous forme de requête d'agrégation renvoyant un nombre de violations, et exécutez le lot après chaque chargement. Les six contrôles qui valent la peine sont : valeurs nulles dans les colonnes obligatoires, clés en double, valeurs hors de l'ensemble autorisé, nombres hors d'une plage plausible, chaînes malformées et lignes périmées. L'ascendance PostgreSQL de Redshift fait que l'essentiel du SQL est familier — mais deux choses ne le sont pas : ses contraintes sont déclarées et jamais appliquées, de sorte que l'optimiseur peut produire de mauvaises réponses à partir d'une clé dupliquée, et les requêtes de validation se disputent des créneaux dans une file WLM que votre ETL utilise aussi.

Pourquoi Redshift demande des précautions

Les contraintes sont des indices, pas des règles. Redshift accepte les déclarations PRIMARY KEY, UNIQUE et FOREIGN KEY et n'en applique jamais aucune. Pire : le planificateur leur fait confiance. Si vous déclarez order_id comme unique alors qu'il ne l'est pas, une requête qui s'appuie sur cette hypothèse peut renvoyer des résultats incorrects, et pas seulement s'exécuter lentement. Dans Redshift, valider l'unicité ne relève pas de l'hygiène défensive — c'est protéger la justesse de toutes les requêtes en aval.

Tout partage une même file. Redshift exécute les requêtes via la gestion de charge de travail (WLM). Un balayage de validation fait de parcours complets de tables, soumis en même temps que l'ETL nocturne, se mettra en file derrière lui ou, pire, lui prendra des créneaux. Donnez à la validation sa propre file WLM, avec une concurrence modeste et une règle de surveillance des requêtes qui interrompt tout ce qui dépasse un seuil de durée.

Des statistiques périmées changent le sens de « peu coûteux ». Après un gros COPY, les statistiques de table sont obsolètes jusqu'à l'exécution d'ANALYZE. Des plans choisis à partir de statistiques périmées peuvent transformer une agrégation rapide en jointure par diffusion. Lancez la validation après ANALYZE, pas avant.

Ces trois points mis à part, la validation sur Redshift a la même forme que partout ailleurs, ce que le guide complet de la validation des données expose de bout en bout.

Les six contrôles dont toute table a besoin

1. Complétude — valeurs nulles dans les colonnes obligatoires

SELECT COUNT(*) AS violations
FROM sales.orders
WHERE order_id IS NULL;

Redshift est orienté colonnes : cette requête ne lit donc que la colonne order_id — peu coûteux, même sur une table de faits très large. N'écrivez jamais SELECT * dans un contrôle ; vous perdriez précisément cet avantage.

2. Unicité — clés métier en double

SELECT COUNT(*) AS violations
FROM (
    SELECT order_id
    FROM sales.orders
    WHERE order_id IS NOT NULL
    GROUP BY order_id
    HAVING COUNT(*) > 1
) dupes;

C'est le contrôle à exécuter en premier sur n'importe quelle table Redshift. Les doublons arrivent couramment : un COPY relancé après un échec partiel, un MERGE implémenté en delete-puis-insert dont le filtre de suppression a manqué sa cible, une extraction amont rejouée sur la même fenêtre. Comme la clé primaire déclarée ne fait rien, rien d'autre ne les attrapera.

Si la table possède une clé de tri sur la clé métier, le GROUP BY travaille sur des blocs triés et coûte nettement moins cher. Cela vaut la peine de choisir ses clés de tri en gardant cela à l'esprit.

3. Conformité — valeurs hors de l'ensemble autorisé

SELECT COUNT(*) AS violations
FROM sales.orders
WHERE status IS NOT NULL
  AND status NOT IN ('pending', 'paid', 'shipped', 'refunded');

Gardez la protection contre les valeurs nulles — NULL NOT IN (...) vaut NULL, la ligne disparaît, et une colonne riche en valeurs nulles s'annonce impeccable.

Les longueurs VARCHAR de Redshift s'expriment en octets, pas en caractères. Un emoji de quatre octets dans une colonne VARCHAR(10) est tronqué au chargement, et une valeur tronquée échoue au contrôle des valeurs autorisées pour une raison qui n'a rien à voir avec le système source. Dimensionnez généreusement vos colonnes de texte.

4. Exactitude — nombres hors d'une plage plausible

SELECT COUNT(*) AS violations
FROM sales.orders
WHERE total_amount IS NOT NULL
  AND (total_amount < 0 OR total_amount > 100000);

Utilisez DECIMAL/NUMERIC pour les montants. L'arithmétique DECIMAL de Redshift peut aussi déborder silencieusement vers une échelle plus large lors d'une agrégation : un contrôle de plage sur la colonne brute est donc plus fiable qu'un contrôle sur un total calculé.

5. Conformité — identifiants malformés

Redshift conserve les opérateurs d'expressions régulières de PostgreSQL :

SELECT COUNT(*) AS violations
FROM sales.orders
WHERE reference IS NOT NULL
  AND reference !~ '^ORD-[0-9]{6}
#39;;

~ est sensible à la casse, ~* insensible, !~ nie la correspondance. SIMILAR TO et REGEXP_COUNT/REGEXP_SUBSTR sont également disponibles. Aucune traduction de motif n'est nécessaire — contrairement à SQL Server, le motif que vous écrivez est le motif qui s'exécute.

6. Ponctualité — fraîcheur

SELECT COUNT(*) AS violations
FROM sales.orders
WHERE created_at < GETDATE() - INTERVAL '24 hours';

Le GETDATE() de Redshift renvoie de l'UTC, à l'inverse du comportement de SQL Server, et c'est une vraie source de confusion quand on porte des règles de l'un à l'autre. SYSDATE renvoie également de l'UTC. Stockez les horodatages en TIMESTAMP (Redshift ne conserve pas de fuseau) et tenez-vous-en à l'UTC par convention.

Pour un contrôle de fin de chargement qui ne touche à aucune donnée de table, SVV_TABLE_INFO expose à peu de frais des métadonnées par table.

Intégrité référentielle

SELECT COUNT(*) AS violations
FROM sales.orders o
LEFT JOIN sales.customers c ON o.customer_id = c.id
WHERE o.customer_id IS NOT NULL
  AND c.id IS NULL;

Comme les clés étrangères ne sont pas appliquées, les lignes orphelines sont fréquentes après une reprise d'historique partielle. Le style de distribution détermine le coût de ce contrôle : si les deux tables ont un DISTKEY sur la colonne de jointure, la jointure est locale à chaque slice ; sinon Redshift redistribue l'un des côtés sur tout le cluster. Sur de grandes tables, c'est la différence entre quelques secondes et plusieurs minutes.

Un utilisateur en lecture seule sans risque

CREATE USER catalyst_ro PASSWORD '<generated>';
GRANT USAGE ON SCHEMA sales TO catalyst_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA sales TO catalyst_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA sales
  GRANT SELECT ON TABLES TO catalyst_ro;

La ligne de privilèges par défaut compte ici autant que dans Postgres : sans elle, les tables recréées par l'ETL de demain sont invisibles pour l'utilisateur de validation, et les contrôles se mettent à échouer sur des erreurs de permission qui ressemblent à des défauts de données.

Isolez ensuite la charge de travail :

-- In the WLM configuration, give the validation user group its own queue with
-- low concurrency, and a query monitoring rule that aborts on long runtime.
CREATE GROUP validators WITH USER catalyst_ro;

Du SQL ponctuel au contrat de données

Déclarer les attentes les rend relisibles, portables et indépendantes de l'outil qui charge la table. Catalyst utilise l'Open Data Contract Standard :

apiVersion: v3.0.0
kind: DataContract
info:
  title: orders
  version: 1.3.0
  owner: data-platform
schema:
  - name: orders
    physicalName: orders
    physicalType: table
    properties:
      - name: order_id
        logicalType: string
        physicalType: varchar(36)
        required: true
        primaryKey: true
        quality:
          - rule: nullCount
            dimension: completeness
            severity: error
            mustBe: "0"
          - rule: duplicateCount
            dimension: uniqueness
            severity: error
            mustBe: "0"
      - name: customer_id
        logicalType: string
        quality:
          - rule: referentialIntegrity
            dimension: consistency
            severity: error
            mustBe: "customers.id"
      - name: status
        logicalType: string
        quality:
          - rule: validValues
            dimension: conformity
            severity: error
            mustBe: "['pending', 'paid', 'shipped', 'refunded']"
      - name: total_amount
        logicalType: number
        physicalType: numeric(12,2)
        quality:
          - rule: between
            dimension: accuracy
            severity: error
            mustBe: "[0, 100000]"
      - name: reference
        logicalType: string
        quality:
          - rule: regex
            dimension: conformity
            severity: warning
            mustBe: "'^ORD-[0-9]{6}
#39;" - name: created_at logicalType: timestamp quality: - rule: freshness dimension: timeliness severity: error mustBe: "<= 24h" quality: - rule: rowCount dimension: consistency severity: warning mustBe: "> 0"

Catalyst importe les colonnes depuis information_schema, propose un contrat de base, compile chaque règle en SQL Redshift, et enregistre pass, warn ou fail pour chaque contrôle avec un échantillon des lignes en échec. Le YAML fait l'aller-retour avec le constructeur visuel — commentaires et ordre préservés — de sorte que le fichier relu en pull request est celui qui s'exécute.

Pièges Redshift à connaître

PiègeCe qui se passeQue faire
PRIMARY KEY non appliquéeLe planificateur lui fait confiance et renvoie de mauvais résultatsToujours ajouter une règle duplicateCount
FOREIGN KEY non appliquéeOrphelins après des reprises d'historique partiellesAjouter une règle referentialIntegrity
File WLM partagéeLes contrôles concurrencent l'ETLFile dédiée + règle de surveillance des requêtes
Statistiques périméesL'agrégation devient une jointure par diffusionValider après ANALYZE
Longueurs VARCHAR en octetsLe texte multi-octets est tronqué au chargementDimensionner généreusement les colonnes de texte
Pas d'ALTER DEFAULT PRIVILEGESLes nouvelles tables sont illisibles dès demainAccorder les privilèges par défaut sur le schéma
GETDATE() renvoie de l'UTCL'inverse de SQL ServerTout conserver en UTC
Vues à liaison tardiveLe contrôle échoue si la table de base est suppriméeValider les tables de base, pas les vues

Planification et alertes

Déclenchez la validation à la fin du COPY ou du job de transformation, après ANALYZE, plutôt qu'à une heure fixe. Sur Redshift Serverless, le même conseil vaut avec une incitation supplémentaire : la capacité inactive ne coûte rien, mais un balayage de validation qui réveille le workgroup toutes les quinze minutes le maintient chaud — et facturé.

Alertez sur les transitions plutôt que sur un état persistant, et différenciez les gravités de façon à réserver error aux contrôles qui doivent réellement bloquer les consommateurs en aval. Sur Redshift, la règle d'unicité relève presque toujours du niveau error, vu ce qu'une clé primaire non appliquée fait à la justesse des requêtes.

Questions fréquentes

Redshift applique-t-il les clés primaires ?

Non. PRIMARY KEY, UNIQUE et FOREIGN KEY sont acceptées et enregistrées, mais jamais appliquées à l'écriture. Le planificateur de requêtes, lui, leur fait confiance : une clé dupliquée peut donc faire renvoyer des résultats incorrects, et pas seulement des lignes en trop. Valider l'unicité avec un contrôle explicite est le seul moyen de contrainte disponible.

Puis-je valider des données Redshift sans écrire de SQL ?

Oui. Déclarez l'attente — obligatoire, unique, valeurs autorisées, plage numérique, expression régulière, âge maximal, clé étrangère — et laissez l'outil la compiler en SQL Redshift. Catalyst importe le schéma depuis information_schema, suggère un ensemble de règles de base, et réserve le SQL écrit à la main à la logique réellement sur mesure, via une règle customSql.

Comment empêcher les requêtes de validation de ralentir mon ETL ?

Placez l'utilisateur de validation dans sa propre file WLM, avec une faible concurrence et une règle de surveillance des requêtes qui interrompt les requêtes longues, et planifiez les contrôles à la suite du chargement plutôt qu'en parallèle. Gardez les contrôles limités à une colonne — Redshift est orienté colonnes, donc une agrégation sur une seule colonne coûte radicalement moins cher que tout ce qui touche à la ligne entière.

Cela fonctionne-t-il avec Redshift Serverless et Redshift Spectrum ?

Redshift Serverless se comporte de façon identique pour tout ce que couvre ce guide ; la seule différence est la facturation, évitez donc les planifications qui maintiennent le workgroup éveillé sans nécessité. Les tables externes Spectrum se valident sans problème, mais il n'y a ni stockage local ni clé de tri à exploiter : chaque contrôle parcourt donc les objets S3 sous-jacents — limitez-les à une partition.

De quelles permissions un outil de validation a-t-il besoin ?

USAGE sur le schéma et SELECT sur les tables, plus les privilèges par défaut pour que les futures tables héritent de l'autorisation. Aucun accès en écriture n'est requis. Catalyst se connecte en lecture seule et ne stocke que des métadonnées et des résultats de contrôles.