Introduction
- Une réorganisation complète
- Un sous-ensemble de la table d’origine avec un ordre différent
- Une agrégation précalculée (semblable à une vue matérialisée), mais avec un ordre aligné sur l’agrégation.
Comment fonctionnent les projections ?
- Utiliser correctement les index primaires
- Pré-calculer les agrégats
Un stockage plus intelligent avec _part_offset
_part_offset dans les projections, ce qui offre une nouvelle façon de définir une projection.
Il existe désormais deux façons de définir une projection :
- Stocker des colonnes complètes (comportement d’origine) : la projection contient l’intégralité des données et peut être lue directement, ce qui offre de meilleures performances lorsque les filtres correspondent à l’ordre de tri de la projection.
-
Stocker uniquement la clé de tri +
_part_offset: la projection fonctionne comme un index. ClickHouse utilise l’index primaire de la projection pour localiser les lignes correspondantes, mais lit les données réelles depuis la table de base. Cela réduit la surcharge de stockage, au prix d’un peu plus d’E/S au moment de la requête.
_part_offset.
Quand utiliser les projections ?
- Les projections ne permettent pas d’utiliser des TTL différents pour la table source et la table cible (masquée), tandis que les vues matérialisées autorisent des TTL différents.
- Les lightweight updates et les suppressions ne sont pas prises en charge pour les tables avec projections.
- Les vues matérialisées peuvent être chaînées : la table cible d’une vue matérialisée peut être la table source d’une autre vue matérialisée, et ainsi de suite. Ce n’est pas possible avec les projections.
- Les définitions de projections ne prennent pas en charge les jointures, contrairement aux vues matérialisées. Cependant, les requêtes sur des tables avec projections peuvent librement utiliser des jointures.
- Les définitions de projections ne prennent pas en charge les filtres (clause
WHERE), contrairement aux vues matérialisées. Cependant, les requêtes sur des tables avec projections peuvent librement appliquer des filtres.
- Une réorganisation complète des données est nécessaire. Bien que l’expression de la
projection puisse, en théorie, utiliser un
GROUP BY,les vues matérialisées sont plus efficaces pour maintenir des agrégats. L’optimiseur de requêtes est également plus susceptible d’exploiter des projections qui reposent sur une simple réorganisation, c.-à-d.SELECT * ORDER BY x. Vous pouvez sélectionner un sous-ensemble de colonnes dans cette expression afin de réduire l’empreinte de stockage. - Les utilisateurs acceptent l’augmentation potentielle de l’empreinte de stockage ainsi que le surcoût lié à l’écriture des données en double. Testez l’impact sur la vitesse d’insertion et évaluez le surcoût de stockage.
Exemples
Filtrage sur des colonnes qui ne font pas partie de la clé primaire
pickup_datetime.
Écrivons une requête simple pour trouver tous les identifiants de trajet pour lesquels les passagers
ont laissé un pourboire de plus de 200 $ à leur chauffeur :
Notez que, comme nous filtrons sur tip_amount, qui ne figure pas dans ORDER BY, ClickHouse
a dû parcourir l’ensemble de la table. Accélérons cette requête.
Afin de préserver la table d’origine et les résultats, nous allons créer une nouvelle table et copier les données à l’aide d’un INSERT INTO SELECT :
ALTER TABLE avec l’instruction ADD PROJECTION
suivante :
MATERIALIZE PROJECTION
pour que les données qu’elle contient soient physiquement ordonnées et réécrites
selon la requête spécifiée ci-dessus :
system.query_log :
Utiliser les projections pour accélérer les requêtes sur les prix de l’immobilier au Royaume-Uni
town ni price ne figuraient dans notre instruction ORDER BY lorsque nous
avons créé la table :
INSERT INTO SELECT :
prj_oby_town_price, qui produit une
table supplémentaire (masquée) dotée d’un index primaire, ordonnée par ville et par prix, afin
d’optimiser la requête qui répertorie les comtés d’une ville donnée pour les prix
payés les plus élevés :
mutations_sync est
utilisé pour forcer l’exécution synchrone.
Nous créons et peuplons la projection prj_gby_county — une table supplémentaire (masquée)
qui précalcule de façon incrémentielle les valeurs agrégées de avg(price) pour les
130 comtés existants du Royaume-Uni :
S’il existe une clause
GROUP BY dans une projection comme la projection
prj_gby_county ci-dessus, le moteur de stockage sous-jacent de la table
(masquée) devient alors AggregatingMergeTree, et toutes les fonctions d’agrégation sont converties en
AggregateFunction. Cela garantit une agrégation incrémentielle correcte des données.uk_price_paid_with_projections
et de ses deux projections :
Si nous exécutons maintenant de nouveau la requête qui affiche les comtés de Londres pour les trois prix
les plus élevés, nous constatons une amélioration des performances de la requête :
De même, pour la requête qui liste les comtés du Royaume-Uni avec les trois prix moyens
les plus élevés :
Notez que les deux requêtes ciblent la table d’origine et que, dans les deux cas,
elles ont entraîné un parcours complet de la table (les 30,03 millions de lignes ont été lues depuis le disque) avant la
création des deux projections.
Notez également que la requête qui liste les comtés de Londres pour les trois prix les plus
élevés nécessite la lecture de 2,17 millions de lignes. Lorsque nous avons utilisé directement une seconde table
optimisée pour cette requête, seules 81 920 lignes ont été lues depuis le disque.
La raison de cette différence est qu’actuellement, l’optimisation optimize_read_in_order
mentionnée ci-dessus n’est pas prise en charge pour les projections.
Nous examinons la table system.query_log pour vérifier que ClickHouse
a automatiquement utilisé les deux projections pour les deux requêtes ci-dessus (voir la
colonne projections ci-dessous) :
Autres exemples
CREATE AS et de INSERT INTO SELECT.
Créer une projection
toYear(date), district et town :
optimize_use_projections, activé par défaut.
Requête 1. Prix moyen par année
Requête 2. Prix moyen par année à Londres
Requête 3. Les quartiers les plus chers
toYear(date) >= 2020)) :
Là encore, le résultat est identique, mais notez l’amélioration des performances d’exécution de la 2e requête.
Combiner plusieurs projections dans une même requête
_part_offset introduite dans
la version précédente, ClickHouse peut désormais utiliser plusieurs projections pour accélérer
une même requête comportant plusieurs filtres.
Il est important de noter que ClickHouse lit toujours les données à partir d’une seule projection (ou de la table de base),
mais peut utiliser les index primaires d’autres projections pour écarter les parties inutiles avant la lecture.
Cela est particulièrement utile pour les requêtes qui filtrent sur plusieurs colonnes, chacune
pouvant potentiellement correspondre à une projection différente.
Actuellement, ce mécanisme n’écarte que des parties entières. L’exclusion au niveau des granules n’est pas encore prise en charge.Pour le démontrer, nous définissons la table (avec des projections utilisant des colonnes
_part_offset)
et insérons cinq lignes d’exemple correspondant aux schémas ci-dessus.
Remarque : la table utilise des paramètres personnalisés à des fins d’illustration, comme des granules d’une seule ligne
et des fusions de parts désactivées, ce qui n’est pas recommandé en production.
- Cinq parts distinctes (une par ligne insérée)
- Une entrée d’index primaire par ligne (dans la table de base et chaque projection)
- Chaque part contient exactement une ligne
region et user_id.
Comme l’index primaire de la table de base est construit à partir de event_date et id, il
n’est d’aucune utilité ici. ClickHouse utilise donc :
region_projpour élaguer les parts par régionuser_id_projpour élaguer davantage les parts selonuser_id
EXPLAIN projections = 1, qui montre comment
ClickHouse sélectionne et applique les projections.
EXPLAIN (affichée ci-dessus) présente le plan de requête logique, de haut en bas :
Au final, 1 seule part sur 5 est lue dans la table de base.
En combinant l’analyse des index de plusieurs projections, ClickHouse réduit considérablement le volume de données analysées,
ce qui améliore les performances tout en maintenant un faible surcoût de stockage.