clickhouse-aggregatingmergetree-state-merge

Par divinevideo · divine-mobile

Corrige les erreurs de requête ClickHouse lors de l'interrogation de tables AggregatingMergeTree utilisant des fonctions state. À utiliser quand : (1) La requête échoue avec une erreur de type sur des colonnes AggregateFunction, (2) Utilisation de sum/count sur des colonnes créées avec sumState/countState/uniqState, (3) Création de vues qui font des JOIN avec des vues matérialisées utilisant AggregatingMergeTree, (4) Obtention de résultats inattendus depuis des colonnes agrégées affichées comme type AggregateFunction(sum, ...). Les fonctions *State() stockent des états agrégés intermédiaires, pas les valeurs finales — vous devez utiliser les fonctions *Merge() pour les finaliser.

npx skills add https://github.com/divinevideo/divine-mobile --skill clickhouse-aggregatingmergetree-state-merge

ClickHouse AggregatingMergeTree Schéma State/Merge

Problème

Lors de l'interrogation de tables ou de vues matérialisées utilisant AggregatingMergeTree avec des fonctions state (sumState, uniqState, countState), les requêtes échouent ou retournent des résultats incorrects car les colonnes contiennent des objets d'état d'agrégation, non des valeurs numériques régulières.

Contexte / Conditions déclencheurs

  • La requête échoue avec des erreurs de type lors de l'utilisation de sum() sur les colonnes AggregateFunction
  • Création de vues qui interrogent des vues matérialisées AggregatingMergeTree
  • Les types de colonnes s'affichent comme AggregateFunction(sum, UInt64) au lieu de UInt64
  • La migration crée une vue joignant une vue matérialisée d'agrégation existante
  • Obtention de valeurs NULL ou inattendues lors de l'agrégation de colonnes pré-agrégées

Cause racine

AggregatingMergeTree stocke des états d'agrégation intermédiaires, non des valeurs finales. Quand tu définis :

CREATE MATERIALIZED VIEW stats
ENGINE = AggregatingMergeTree()
ORDER BY (user_id)
AS SELECT
    user_id,
    sumState(amount) as total_amount,      -- AggregateFunction(sum, UInt64)
    uniqState(session_id) as unique_sessions, -- AggregateFunction(uniq, String)
    countState() as event_count           -- AggregateFunction(count)
FROM events
GROUP BY user_id;

Les colonnes total_amount, unique_sessions et event_count ne sont PAS des nombres réguliers. Ce sont des blobs binaires représentant l'état intermédiaire de l'agrégation.

Incorrect :

SELECT user_id, sum(total_amount) FROM stats GROUP BY user_id;
-- Erreur : impossible d'utiliser sum() sur le type AggregateFunction

Correct :

SELECT user_id, sumMerge(total_amount) FROM stats GROUP BY user_id;
-- Retourne la valeur numérique finalisée

Solution

Mapping des fonctions State vers les fonctions Merge

Fonction State Fonction Merge Objectif
sumState(x) sumMerge(x) Agrégation de somme
countState() countMerge(x) Agrégation de comptage
uniqState(x) uniqMerge(x) Comptage unique (HyperLogLog)
avgState(x) avgMerge(x) Moyenne
minState(x) minMerge(x) Minimum
maxState(x) maxMerge(x) Maximum
anyState(x) anyMerge(x) N'importe quelle valeur
groupArrayState(x) groupArrayMerge(x) Agrégation de tableau

Exemple de correction

Avant (cassé) :

CREATE VIEW leaderboard AS
SELECT
    stats.user_id,
    sum(stats.daily_views) AS views,        -- INCORRECT
    sum(stats.daily_unique) AS uniques,     -- INCORRECT
    sum(stats.videos_watched) AS videos     -- INCORRECT
FROM daily_stats stats
GROUP BY stats.user_id;

Après (corrigé) :

CREATE VIEW leaderboard AS
SELECT
    stats.user_id,
    sumMerge(stats.daily_views) AS views,        -- Correct
    uniqMerge(stats.daily_unique) AS uniques,    -- Correct
    countMerge(stats.videos_watched) AS videos   -- Correct
FROM daily_stats stats
GROUP BY stats.user_id;

Identification des colonnes affectées

Vérifie le schéma de la table pour voir quelles colonnes sont des états d'agrégation :

DESCRIBE TABLE your_table;

La sortie affiche les types de colonne comme :

daily_views       AggregateFunction(sum, UInt64)
daily_unique      AggregateFunction(uniq, String)
videos_watched    AggregateFunction(count)

Toute colonne avec le type AggregateFunction(...) requiert la fonction *Merge() correspondante.

Vérification

  1. Vérifie la définition de ta vue matérialisée pour les fonctions *State()
  2. Assure-toi que toutes les requêtes utilisent les fonctions *Merge() correspondantes
  3. Teste que la requête retourne les valeurs numériques attendues, non NULL ou des blobs binaires
-- Devrait retourner des nombres réels
SELECT sumMerge(total_views), uniqMerge(unique_visitors)
FROM aggregated_stats
WHERE stat_date >= today() - 7;

Exemple

Migration 020 (crée la table d'agrégation) :

CREATE MATERIALIZED VIEW creator_daily_stats
ENGINE = AggregatingMergeTree()
ORDER BY (video_author_pubkey, stat_date)
AS SELECT
    video_author_pubkey,
    toDate(created_at) as stat_date,
    sumState(view_count) as daily_views,           -- Fonction state
    uniqState(viewer_hash) as daily_unique_viewers, -- Fonction state
    sumState(toFloat64(total_loops)) as daily_loops,-- Fonction state
    countState() as videos_watched                  -- Fonction state
FROM view_counts
GROUP BY video_author_pubkey, toDate(created_at);

Migration 033 (interroge la table d'agrégation - CORRIGÉE) :

CREATE VIEW leaderboard_creators_day AS
SELECT
    cds.video_author_pubkey AS pubkey,
    p.name,
    sumMerge(cds.daily_views) AS views,            -- Fonction merge
    uniqMerge(cds.daily_unique_viewers) AS unique_viewers, -- Fonction merge
    sumMerge(cds.daily_loops) AS loops,            -- Fonction merge
    countMerge(cds.videos_watched) AS videos_with_views -- Fonction merge
FROM creator_daily_stats cds
LEFT JOIN user_profiles p ON cds.video_author_pubkey = p.pubkey
WHERE cds.stat_date >= today() - 1
GROUP BY cds.video_author_pubkey, p.name
ORDER BY views DESC;

Notes

  • SummingMergeTree est différent : Il stocke des valeurs régulières et les somme lors des fusions. Avec SummingMergeTree, tu utilises sum() régulier dans les requêtes. Seul AggregatingMergeTree utilise le schéma State/Merge.

  • Pourquoi utiliser AggregatingMergeTree ? : Pour les comptages uniques (uniq), tu ne peux pas simplement additionner les comptages de différentes parties — cela causerait un surcomptage. AggregatingMergeTree préserve l'état HyperLogLog pour que la fusion donne des comptages uniques corrects sur les partitions.

  • Performance : Les fonctions *Merge() sont efficaces — elles sont conçues pour combiner des états d'agrégation pré-calculés, non pour retraiter les données brutes.

  • L'ordre des migrations compte : Si la migration A crée une vue AggregatingMergeTree, et la migration B crée une vue qui l'interroge, la migration B doit utiliser les fonctions *Merge().

Références

Skills similaires