Les vues matérialisées en temps réel de Hologres préagrègent les données de la table de base et stockent les résultats. Les requêtes lisent directement la vue au lieu de recalculer l'agrégation à la volée, ce qui réduit la latence et la charge de calcul.
Contrairement aux vues matérialisées traditionnelles qui nécessitent une actualisation manuelle, celles de Hologres se mettent à jour automatiquement. Les modifications apportées à la table de base sont immédiatement répercutées dans la vue : aucune tâche d'actualisation planifiée n'est nécessaire.

La table qui reçoit les écritures en temps réel est appelée table de base. Toutes les opérations INSERT, UPDATE et DELETE ciblent la table de base. La vue matérialisée est définie par des règles d'agrégation sur la table de base et reflète les insertions en temps réel. La prise en charge de la synchronisation des mises à jour et des suppressions sera ajoutée dans une prochaine version.
Quand utiliser les vues matérialisées
Les vues matérialisées constituent une solution adaptée lorsque :
Les requêtes agrègent régulièrement de grands volumes de données brutes (tableaux de bord, rapports, analyses).
Le résultat d'agrégation est consulté beaucoup plus fréquemment que la table de base n'est mise à jour.
La latence des requêtes sur la table de base représente un goulot d'étranglement, même avec des index.
Évitez les vues matérialisées si le résultat d'agrégation contient à peu près autant de lignes que la table de base (faible taux de compression) ou si le modèle de requête change fréquemment, rendant la clé GROUP BY fixe peu pratique.
Fonctions d'agrégation prises en charge
| Function | Notes |
|---|---|
SUM |
|
COUNT |
|
AVG |
|
MIN |
|
MAX |
|
RB_BUILD_CARDINALITY_AGG |
Type de données BIGINT uniquement. Nécessite l'extension roaringbitmap. |
Prérequis
Avant de commencer, assurez-vous de disposer des éléments suivants :
Une instance Hologres avec un accès en écriture
L'extension
roaringbitmapcréée (requise uniquement pourRB_BUILD_CARDINALITY_AGG)
Créer une vue matérialisée en temps réel
Créez la table de base et la vue matérialisée dans une seule transaction. La table de base doit avoir le type de mutation appendonly.
BEGIN;
CREATE TABLE base_sales(
day text NOT NULL,
hour int,
ts timestamptz,
amount float,
pk text NOT NULL PRIMARY KEY
);
CALL SET_TABLE_PROPERTY('base_sales', 'mutate_type', 'appendonly');
-- After dropping the materialized view, remove the appendonly property if needed:
-- CALL SET_TABLE_PROPERTY('base_sales', 'mutate_type', 'none');
CREATE MATERIALIZED VIEW mv_sales AS
SELECT
day,
hour,
avg(amount) AS amount_avg
FROM base_sales
GROUP BY day, hour;
COMMIT;
Insérez des données dans la table de base. La vue reflète immédiatement les nouvelles données.
INSERT INTO base_sales VALUES (to_char(now(),'YYYYMMDD'), '12', now(), 100, 'pk1');
INSERT INTO base_sales VALUES (to_char(now(),'YYYYMMDD'), '12', now(), 200, 'pk2');
INSERT INTO base_sales VALUES (to_char(now(),'YYYYMMDD'), '12', now(), 300, 'pk3');
Interroger une vue matérialisée
Interrogez directement la vue à l'aide du SQL standard.
SELECT * FROM mv_sales WHERE day = to_char(now(),'YYYYMMDD') AND hour = 12;
Vous pouvez également interroger la table de base et laisser le routage intelligent rediriger automatiquement vers la vue. Consultez Routage intelligent pour les vues matérialisées.
Créer une vue matérialisée pour une table partitionnée
Lorsque la table de base est partitionnée, la clé GROUP BY de la vue matérialisée doit inclure la colonne de clé de partition. Créez la vue matérialisée sur la table parente, et non sur les tables enfants individuelles.
BEGIN;
CREATE TABLE base_sales_p(
day text NOT NULL,
hour int,
ts timestamptz,
amount float,
pk text NOT NULL,
PRIMARY KEY (day, pk)
) PARTITION BY LIST(day);
CALL SET_TABLE_PROPERTY('base_sales_p', 'mutate_type', 'appendonly');
-- day is the partition key and must appear in the GROUP BY clause
CREATE MATERIALIZED VIEW mv_sales_p AS
SELECT
day,
hour,
avg(amount) AS amount_avg
FROM base_sales_p
GROUP BY day, hour;
COMMIT;
-- Use CREATE TABLE PARTITION OF to add partitions (ATTACH PARTITION is not supported)
CREATE TABLE base_sales_20220101 PARTITION OF base_sales_p FOR VALUES IN ('20220101');
Autres opérations
Vérifier la taille de stockage
Vérifiez la taille de stockage d'une seule vue matérialisée :
SELECT pg_relation_size('mv_sales');
Listez toutes les vues matérialisées triées par taille de stockage :
SELECT schemaname || '.' || matviewname AS mv_full_name,
pg_size_pretty(pg_relation_size('"' || schemaname || '"."' || matviewname || '"')) AS mv_size,
pg_relation_size('"' || schemaname || '"."' || matviewname || '"') AS order_size
FROM pg_matviews
ORDER BY order_size DESC;
Supprimer une vue matérialisée
DROP MATERIALIZED VIEW mv_sales;
Calcul précis des visiteurs uniques (UV) avec RoaringBitmap
Le calcul précis des visiteurs uniques (UV), qui consiste à compter les ID utilisateur distincts, est coûteux en calcul et constitue un goulot d'étranglement courant en termes de performances. RB_BUILD_CARDINALITY_AGG préagrège les champs d'ID métier BIGINT dans un RoaringBitmap au sein de la vue matérialisée, permettant une déduplication en temps réel. Seuls les champs BIGINT sont pris en charge pour cette agrégation.
-- The roaringbitmap extension must be created before use
CREATE EXTENSION IF NOT EXISTS roaringbitmap;
BEGIN;
CREATE TABLE base_sales_r(
day text NOT NULL,
hour int,
ts timestamptz,
amount float,
userid bigint,
pk text NOT NULL PRIMARY KEY
);
CALL SET_TABLE_PROPERTY('base_sales_r', 'mutate_type', 'appendonly');
CREATE MATERIALIZED VIEW mv_sales_r AS
SELECT
day,
hour,
avg(amount) AS amount_avg,
rb_build_cardinality_agg(userid) AS user_count
FROM base_sales_r
GROUP BY day, hour;
COMMIT;
INSERT INTO base_sales_r VALUES (to_char(now(),'YYYYMMDD'), '12', now(), 100, 1, 'pk1');
INSERT INTO base_sales_r VALUES (to_char(now(),'YYYYMMDD'), '12', now(), 200, 2, 'pk2');
INSERT INTO base_sales_r VALUES (to_char(now(),'YYYYMMDD'), '12', now(), 300, 3, 'pk3');
-- user_count holds the count of distinct userid values for that day+hour
SELECT user_count AS UV FROM mv_sales_r WHERE day = to_char(now(),'YYYYMMDD') AND hour = 12;
Agrégation multidimensionnelle avec des fonctions partielles
Pourquoi AVG standard échoue entre les dimensions
Une vue matérialisée stocke des résultats préagrégés. Si vous définissez mv_sales avec avg(amount) groupé par (day, hour), l'interrogation portant uniquement sur la dimension day produit des résultats incorrects : la moyenne des moyennes n'est pas égale à la moyenne totale.
Compte tenu de ces données de base :
| Day | Hour | Amount | PK |
|---|---|---|---|
| 20210101 | 12 | 2 | pk1 |
| 20210101 | 12 | 4 | pk2 |
| 20210101 | 13 | 6 | pk3 |
Une requête directe sur la vue renvoie :
postgres=> SELECT * FROM mv_sales;
day | hour | amount_avg
-----------+------+------------
20210101 | 12 | 3
20210101 | 13 | 6
La réagrégation par day donne un résultat incorrect :
postgres=> SELECT day, avg(amount_avg) FROM mv_sales GROUP BY day;
day | avg
-----------+------
20210101 | 4.5 -- incorrect: true average is 4
Utiliser l'état d'agrégation intermédiaire
Au lieu de stocker la moyenne finale, stockez l'état d'agrégation intermédiaire à l'aide de avg_partial. Lors de l'interrogation selon une dimension différente, finalisez le résultat avec avg_final.
BEGIN;
CREATE TABLE base_sales(
day text NOT NULL,
hour int,
ts timestamptz,
amount float,
pk text NOT NULL PRIMARY KEY
);
CALL SET_TABLE_PROPERTY('base_sales', 'mutate_type', 'appendonly');
CREATE MATERIALIZED VIEW mv_sales_partial AS
SELECT
day,
hour,
avg(amount) AS avg,
avg_partial(amount) AS amt_avg_partial -- stores intermediate state
FROM base_sales
GROUP BY day, hour;
COMMIT;
Interrogez selon une autre dimension d'agrégation à l'aide de avg_final :
postgres=> SELECT day, avg(avg) AS avg_avg, avg_final(amt_avg_partial) AS real_avg
FROM mv_sales_partial
GROUP BY day;
day | avg_avg | real_avg
-----------+---------+----------
20210101 | 4.5 | 4 -- real_avg is correct
Référence des fonctions partielles et finales
| Standard function | Partial function | Final function |
|---|---|---|
| AVG | AVG_PARTIAL | AVG_FINAL |
| RB_BUILD_CARDINALITY_AGG | RB_BUILD_AGG | RB_OR_CARDINALITY_AGG |
Routage intelligent pour les vues matérialisées
Interrogez directement la table de base : l'optimiseur réécrit automatiquement la requête pour utiliser une vue matérialisée correspondante. Aucune modification de la syntaxe de la requête n'est requise.
L'optimiseur sélectionne une vue lorsque toutes les conditions suivantes sont remplies :
La vue contient toutes les colonnes référencées dans la requête (ou les colonnes à partir desquelles les valeurs interrogées peuvent être dérivées).
Les colonnes GROUP BY de la vue incluent toutes les colonnes GROUP BY de la requête d'origine.
Si plusieurs vues sont éligibles, l'optimiseur choisit celle qui possède le moins de colonnes GROUP BY.
Fonctions d'agrégation prises en charge pour le routage intelligent : SUM, COUNT, MIN, MAX.
AVG et RB_BUILD_CARDINALITY_AGG ne prennent pas en charge le routage intelligent.
Considérations relatives au TTL
Les données sous-jacentes d'une vue matérialisée partagent la même durée de vie (TTL) que sa table de base. Ne définissez pas de TTL séparé sur la vue matérialisée : des valeurs de TTL incohérentes entraînent une incohérence des données entre la vue et la table de base.
Lorsque le TTL de la table de base expire et que les données sont récupérées, une requête sur la table de base et une requête sur la vue peuvent renvoyer des résultats différents. Par exemple :
Requête sur la table de base après expiration du TTL :
postgres=> SELECT day, hour, avg(amount) AS amount_avg FROM base_sales GROUP BY day, hour;
day | hour | amount_avg
-----------+------+------------
20210101 | 12 | 4
20210101 | 13 | 6
Requête sur la vue — les données récupérées avaient déjà été matérialisées et restent dans la vue :
postgres=> SELECT * FROM mv_sales;
day | hour | amount_avg
-----------+------+------------
20210101 | 12 | 3 -- stale; reflects pre-reclamation state
20210101 | 13 | 6
Pour éviter cette incohérence, utilisez l'une des approches suivantes :
Ne définissez pas de TTL sur la table de base.
Si un TTL est requis, incluez un champ temporel dans la clé GROUP BY de la vue. Lors de l'interrogation, évitez les plages horaires proches de la limite d'expiration du TTL.
Utilisez une table partitionnée sans TTL. Récupérez les anciennes données en supprimant les partitions enfants.
Limitations
Contraintes de création
Les vues matérialisées doivent être créées dans la même transaction que la table de base. La création asynchrone n'est pas prise en charge.
Une vue matérialisée ne peut faire référence qu'à une seule table. Les expressions de table communes (CTE), les jointures multitable, les sous-requêtes ainsi que les clauses WHERE, ORDER BY, LIMIT et HAVING ne sont pas prises en charge.
La clé GROUP BY et les valeurs d'agrégat ne prennent pas en charge les expressions. Par exemple,
SUM(CASE WHEN cond THEN a ELSE b END),SUM(col1 + col2)etGROUP BY date_trunc('hour', ts)ne sont pas pris en charge.Maximum de 10 vues matérialisées par table de base. La consommation de ressources augmente proportionnellement au nombre de vues.
Pour les tables partitionnées : la clé GROUP BY doit inclure la colonne de clé de partition ; la vue doit être créée sur la table parente.
Pour les tables partitionnées :
ATTACH PARTITIONn'est pas pris en charge pour l'ajout de partitions ; utilisez plutôtCREATE TABLE PARTITION OF.
Contraintes d'exploitation
Les opérations DELETE et UPDATE sur une table de base ne sont pas prises en charge. Définissez la propriété
appendonlysur la table de base. Toute tentative de DELETE ou UPDATE renvoie l'erreurTable XXX is append-only.Lors de l'écriture en temps réel dans la table de base à l'aide de Flink, définissez
mutateTypesurInsertOrIgnore.DROP COLUMNsur une table de base possédant une vue matérialisée n'est pas pris en charge.Ne définissez pas manuellement de TTL sur une vue matérialisée. La vue hérite du TTL de la table de base. La définition d'un TTL différent entraîne une incohérence des données.
Bonnes pratiques
Alignez la clé GROUP BY sur la clé de distribution. Définissez la clé GROUP BY de la vue matérialisée de manière à ce qu'elle corresponde à la clé de distribution de la table de base. Cela améliore à la fois le taux de compression des données et les performances des requêtes.
Placez les colonnes de filtre en premier dans la clé GROUP BY. Placez les colonnes fréquemment utilisées dans les filtres WHERE au début de la clé GROUP BY. Cela suit le principe de correspondance la plus à gauche de la clé de regroupement et permet un élagage plus efficace.