materialized_view doit être un SELECT à partir d’une table source existante. Contrairement à PostgreSQL, une vue matérialisée dans ClickHouse n’est pas « statique » (et ne dispose pas d’opération REFRESH correspondante). Elle agit plutôt comme un déclencheur d’insertion : elle insère de nouvelles lignes dans une table cible en appliquant la transformation SELECT définie aux lignes insérées dans la table source. Consultez la documentation ClickHouse sur les vues matérialisées pour en savoir plus sur le fonctionnement des vues matérialisées dans ClickHouse.
Pour les concepts généraux de matérialisation et les configurations partagées (engine, order_by, partition_by, etc.), consultez la page Materializations.
Gestion de la table cible
materialized_view, dbt-clickhouse doit créer à la fois une vue matérialisée et une table cible dans laquelle les lignes transformées sont insérées. Il existe deux façons de gérer la table cible :
L’approche choisie a une incidence sur la gestion des changements de schéma, des rafraîchissements complets et des configurations avec plusieurs MV. Les sections suivantes décrivent chaque approche en détail.
Matérialisation avec cible implicite
materialized_view, l’adapter va :
- Créer une table cible portant le nom du modèle
- Créer une vue matérialisée ClickHouse nommée
<model_name>_mv
SELECT de la MV. Toutes les ressources (table cible + MV) partagent la même configuration de modèle.
Plusieurs vues matérialisées
UNION dans votre fichier de modèle, en encadrant le SQL de chaque vue matérialisée par des commentaires de la forme --my_mv_name:begin et --my_mv_name:end.
Par exemple, ce qui suit créera deux vues matérialisées qui écrivent toutes deux des données dans la même table de destination du modèle. Les noms des vues matérialisées auront la forme <model_name>_mv1 et <model_name>_mv2 :
Comment faire évoluer le schéma de la table cible
dbt run rencontre des colonnes différentes dans le SQL de la MV.
ignore), mais vous pouvez modifier ce paramètre pour adopter le même comportement que la config on_schema_change dans les modèles incrémentiels.
Vous pouvez également utiliser ce paramètre comme mécanisme de sécurité. Si vous le définissez sur fail, le build échouera si les colonnes du SQL de la MV diffèrent de celles de la table cible créée lors du premier dbt run.
Rattrapage des données
catchup=True). Vous pouvez désactiver ce comportement en définissant la config catchup sur False.
Matérialisation avec cible explicite (Bêta)
- Toutes les ressources (table cible + MV) partagent la même configuration. Si plusieurs MV pointent vers la même table cible, elles doivent être définies ensemble à l’aide de la syntaxe
UNION ALL. - Aucune de ces ressources ne peut être gérée séparément ; elles doivent toutes être administrées à l’aide du même fichier de modèle.
- Vous ne pouvez pas facilement contrôler le nom de chaque MV.
- Tous les paramètres sont partagés entre la table cible et les MV, ce qui complique la configuration individuelle de chaque ressource et l’identification de la configuration associée à chacune.
table classique, puis d’y faire référence depuis vos modèles de vue matérialisée.
Avantages
- Ressources entièrement séparées : chaque ressource peut désormais être définie séparément, ce qui améliore la lisibilité
- Ressources 1:1 entre dbt et CH : vous pouvez désormais utiliser les outils dbt pour les gérer et les faire évoluer séparément.
- Différentes configurations désormais disponibles : une configuration différente peut désormais être appliquée à chacune.
- Plus besoin de respecter les conventions de nommage : toutes les ressources sont désormais créées avec le nom que vous leur donnez, et non avec un nom personnalisé suffixé par
_mvpour les MV.
Limitations
- La définition de la table cible n’est pas naturelle pour dbt : il ne s’agit pas d’une requête SQL qui lira depuis une table source, vous perdez donc ici les validations dbt. Le SQL de la MV sera tout de même validé à l’aide des utilitaires dbt, et sa compatibilité avec les colonnes de la table cible sera validée au niveau de CH.
- Nous avons identifié quelques problèmes liés aux limitations de la fonction
ref(): nous devons l’utiliser pour référencer les modèles entre eux, mais elle ne peut servir qu’à référencer des modèles upstream, pas downstream. Cela pose certains problèmes pour cette implémentation. Nous avons créé une issue dans le dépôt dbt-core et échangeons actuellement avec eux pour chercher des solutions possibles (dbt-labs/dbt-core#12319) :- Lorsque
ref()est appelé depuis l’intérieur du bloc config, il renvoie le modèle courant, et non celui partagé. Cela nous empêche de le définir dans la section config(), et nous oblige à utiliser un commentaire pour ajouter cette dépendance. Nous suivons le même modèle que celui défini dans la documentation dbt avec l’approche “—depends_on:”. ref()fonctionne dans notre cas car il force la création de la table cible en premier, mais dans le graphe des dépendances de la documentation générée, la table cible sera représentée comme une autre dépendance upstream, et non downstream, ce qui le rend un peu plus difficile à comprendre.unit-testnous oblige également à définir des données pour la table cible, même si l’idée n’est pas de la lire. Le contournement consiste simplement à laisser les données de cette table vides.
- Lorsque
Utilisation
events_daily.sql:
{{ materialization_target_table(ref('events_daily')) }}, qui configure la table cible pour la vue matérialisée.
Modèle page_events_aggregator.sql:
mobile_events_aggregator.sql :
Options de configuration
materialized='table') :
Pour la vue matérialisée (
materialized='materialized_view') :
En général, vous ne définirez que
catchup sur True pour les MV, ou repopulate_from_mvs_on_full_refresh sur True pour leurs tables cibles. Si vous définissez les deux sur True, cela peut dupliquer les données.Opérations courantes
full refresh avec cibles explicites
--full-refresh, les tables cibles explicites seront recréées (vous risquez donc de perdre des données si une ingestion est en cours pendant ce processus). Le comportement variera selon vos configurations :
Option 1 : comportement par défaut de --full-refresh. Tout est recréé, mais pendant la recréation des MV, la table cible sera vide ou partiellement chargée.
Tout est supprimé puis recréé. Si vous souhaitez réinsérer les données à l’aide du SQL des MV, conservez le paramètre catchup=True :
catchup=False, puis exécuter un dbt run ou un dbt run --full-refresh sur les MV. Assurez-vous que les MV sont créées avant d’exécuter --full-refresh sur la table cible, car cette opération s’appuie sur les définitions des MV dans ClickHouse.
Définissez repopulate_from_mvs_on_full_refresh=True dans le model de la table cible. Lors d’un dbt run --full-refresh, cela va :
- Créer une nouvelle table temporaire
- Exécuter INSERT-SELECT en utilisant le SQL de chaque MV
- Permuter les tables de manière atomique
Modifier la table cible
--full-refresh. Si vous essayez d’exécuter un dbt run normal après avoir modifié la référence materialization_target_table(), l’exécution échouera avec un message d’erreur indiquant que la cible a changé.
Pour modifier la cible :
- Mettez à jour l’appel à
materialization_target_table() - Exécutez
dbt run --full-refresh -s your_mv_model
Dépannage des problèmes courants
La table cible est vide pendant ou après l’exécution de run
- Les vues matérialisées peuvent être configurées avec
catchup=False, ou la table cible avecrepopulate_from_mvs_on_full_refresh=False; dans ce cas, aucun backfill n’est effectué lors de la création des vues matérialisées ou de la recréation de la table cible. C’est le comportement attendu. Si vous souhaitez réinsérer les données à l’aide du SQL des vues matérialisées, assurez-vous de définircatchup=Truedans la vue matérialisée (c’est la valeur par défaut) ourepopulate_from_mvs_on_full_refresh=Truedans la table cible. Veillez à ne pas activer les deux en même temps afin d’éviter les doublons. Consultez la section de configuration pour plus de détails. - Lors de l’exécution de
dbt run --full-refresh, si les vues matérialisées utilisent la valeur par défautcatchup=True, la cible sera recréée et les MV réinséreront les données de manière séquentielle. Pour éviter cette situation, consultez la section Full refresh with explicit targets.
dbt run --full-refresh sur une table cible avec repopulate_from_mvs_on_full_refresh=True utilise la logique d’anciennes versions de vues matérialisées, et non celle du SQL actuellement présent dans le projet
repopulate_from_mvs_on_full_refresh=True utilise le SQL MV existant déjà défini dans ClickHouse. Pour garantir que la nouvelle définition de la vue matérialisée est bien utilisée, exécutez un dbt run pour chaque vue matérialisée avant d’exécuter un dbt run --full-refresh sur la table cible.
Il y a des données en double après l’exécution d’un run
catchup=Truesur les vues matérialisées etrepopulate_from_mvs_on_full_refresh=Truesur la table cible peuvent être activés tous les deux : n’en gardez qu’un seul, selon les opérations que vous souhaitez exécuter. Consultez la section de configuration pour plus de détails.- La table cible n’est pas définie avec
WHERE 0: elle doit être créée vide, mais la requête interne peut insérer des données siWHERE 0n’est pas inclus. Assurez-vous que la clause est bien présente.
Perte de données lors d’une ingestion active après l’exécution d’un dbt run --full-refresh
dbt run --full-refresh.
Les vues matérialisées de ClickHouse agissent comme des déclencheurs à l’insertion — elles ne capturent les données que tant qu’elles existent. Lors d’un full refresh, il y a un court laps de temps pendant lequel la MV est supprimée puis recréée (la « fenêtre aveugle »). Les lignes insérées dans la table source pendant cette fenêtre ne sont donc pas capturées. Consultez la section Comportement lors d’une ingestion active pour plus de détails.
Techniques de débogage
Vérifier la cible actuelle d’une MV dans ClickHouse
system.tables pour voir dans quelle table une vue matérialisée écrit :
Vérifier si dbt reconnaît une table comme table cible d’une vue matérialisée
La table <table_name> est utilisée comme table cible par une vue matérialisée gérée par dbt. La valeur par défaut de mv_on_schema_change est définie sur “fail” afin d’éviter toute perte de données.
Si ce message apparaît, dbt a détecté que la table sert de table cible à au moins une vue matérialisée gérée par dbt. Si vous vous attendez à voir ce message mais qu’il n’apparaît pas, vérifiez que :
- Le modèle de vue matérialisée définit correctement
{{ materialization_target_table(ref('your_target')) }} - Le modèle de vue matérialisée a
materialized='materialized_view'dans sa config - La vue matérialisée et la table cible ont toutes deux été exécutées au moins une fois
Migration d’une cible implicite vers une cible explicite
materialized='table' qui définit le même schéma que la table cible de la MV actuelle. Utilisez une clause WHERE 0 pour créer une table vide. Utilisez le même nom que le modèle actuel de vue matérialisée implicite. Vous pourrez ensuite utiliser ce modèle pour faire évoluer la table cible.
materialization_target_table() pointant vers la nouvelle table cible. Si vous utilisiez auparavant UNION ALL, supprimez cette partie ainsi que les commentaires.
Pour les noms des modèles, vous devrez suivre cette convention de nommage :
- si une seule MV était définie, elle portera le nom :
<old_model_name>_mv - si plusieurs MV étaient définies, chacune portera le nom :
<old_model_name>_mv_<name_in_comments>
my_model.sql (table cible implicite, modèle unique avec UNION ALL) :
Comparaison du comportement des approches à cible implicite et explicite
Comportement général
Comportement pendant l’ingestion active
- Comme les vues matérialisées de ClickHouse agissent comme des déclencheurs d’insertion, elles ne capturent les données que tant qu’elles existent. Si une vue matérialisée est supprimée puis recréée (par exemple lors d’un
--full-refresh), les lignes insérées dans la table source pendant cet intervalle ne seront pas traitées par la vue matérialisée. On dit alors que la vue matérialisée est « aveugle ». - Les différents processus de
catchupreposent tous sur des opérationsINSERT INTO ... SELECTqui utilisent le SQL des vues matérialisées et sont indépendants de leur fonctionnement. Une fois l’INSERTdémarré, les nouvelles données ne sont pas capturées par celui-ci, mais elles le seront par la vue matérialisée attachée.
Opérations sur la cible implicite
Opérations sur cible explicite
Modèle de table cible :
Refreshable Materialized Views
config refreshable à votre modèle MV avec les options suivantes :
Exemple avec cible implicite
Exemple avec une cible explicite
Limites
- Lors de la création d’une vue matérialisée actualisable (MV) dans ClickHouse avec une dépendance, ClickHouse ne renvoie pas d’erreur si la dépendance spécifiée n’existe pas au moment de la création. À la place, la MV actualisable reste dans un état inactif, en attendant que la dépendance soit disponible avant de commencer à traiter les mises à jour ou à s’actualiser. Ce comportement est intentionnel, mais il peut retarder la disponibilité des données si la dépendance requise n’est pas mise en place rapidement. Assurez-vous que toutes les dépendances sont correctement définies et existent avant de créer une vue matérialisée actualisable.
- À ce jour, il n’existe pas de véritable « liaison dbt » entre la vue matérialisée et ses dépendances, de sorte que l’ordre de création n’est pas garanti.
- La fonctionnalité d’actualisation n’a pas été testée avec plusieurs vues matérialisées pointant vers le même modèle cible.