Un moteur de table qui stocke des séries temporelles, c’est-à-dire un ensemble de valeurs associées à des horodatages et à des tags (ou labels) :
metric_name1[tag1=value1, tag2=value2, ...] = {timestamp1: value1, timestamp2: value2, ...}
metric_name2[...] = ...Syntaxe
CREATE TABLE name [(columns)] ENGINE=TimeSeries
[SETTINGS var1=value1, ...]
[SAMPLES db.samples_table_name | [SAMPLES INNER COLUMNS (...)] [SAMPLES INNER ENGINE engine(arguments)]]
[TAGS db.tags_table_name | [TAGS INNER COLUMNS (...)] [TAGS INNER ENGINE engine(arguments)]]
[METRICS db.metrics_table_name | [METRICS INNER COLUMNS (...)] [METRICS INNER ENGINE engine(arguments)]]Utilisation
Il est plus facile de commencer en laissant tous les paramètres par défaut (il est possible de créer une table TimeSeries sans préciser de liste de colonnes) :
CREATE TABLE my_table ENGINE=TimeSeriesCette table peut ensuite être utilisée avec les protocoles suivants (un port doit être défini dans la configuration du serveur) :
Colonnes externes
Les colonnes d’une table TimeSeries sont générées automatiquement. Ce sont des colonnes externes : elles ne stockent aucune donnée et servent uniquement d’interface pour SELECT/INSERT. Les données réelles sont stockées dans les tables cibles. Voici la liste des colonnes externes :
| Name | Type | Description |
|---|---|---|
metric_name |
String |
Le nom de la métrique |
tags |
Map(String, String) |
Map de tags (labels) pour la série temporelle |
time_series |
Array(Tuple(DateTime64(3), Float64)) par défaut |
Tableau de paires (horodatage, valeur) pour une série temporelle. Les types d’élément de l’horodatage et du scalaire du tuple peuvent être déduits de la déclaration INNER COLUMNS des échantillons (voir Spécification des colonnes externes) |
metric_family |
String |
Le nom de la famille de métriques (pour les métadonnées de métriques) |
type |
String |
Le type de la métrique (par ex. "counter", "gauge") |
unit |
String |
L’unité de la métrique |
help |
String |
La description de la métrique |
Exemple :
INSERT INTO my_table (metric_name, tags, time_series) VALUES
('cpu_usage', {'job': 'node_exporter', 'instance': 'host1:9100'},
[(toDateTime64('2024-01-01 00:00:00', 3), 0.5), (toDateTime64('2024-01-01 00:01:00', 3), 0.7)])metric_name peut être vide lors de l’insertion, ce qui signifie que le nom de la métrique est indiqué dans tags sous __name__, par exemple :
INSERT INTO my_table (tags, time_series) VALUES
({'__name__': 'cpu_usage', 'job': 'test'},
[(toDateTime64('2024-01-01 00:00:00', 3), 0.5)])Pour insérer les métadonnées des métriques, insérez-les dans les colonnes metric_family, type, unit et help :
INSERT INTO my_table (metric_name, tags, time_series, metric_family, type, unit, help) VALUES
('http_requests_total', {'method': 'GET'}, [(now64(), 100.0)],
'http_requests_total', 'counter', 'requests', 'Total HTTP requests')Spécification des colonnes externes
La colonne externe time_series peut être déclarée explicitement dans une instruction CREATE TABLE afin de remplacer son type par défaut Array(Tuple(DateTime64(3), Float64)). ClickHouse extrait du tuple le type d’horodatage et le type scalaire, puis les propage à la table d’échantillons interne :
CREATE TABLE my_table (time_series Array(Tuple(UInt32, Float32))) ENGINE=TimeSeriesCela revient à déclarer directement les types des colonnes timestamp et value dans la clause INNER COLUMNS de samples :
CREATE TABLE my_table ENGINE=TimeSeries
SAMPLES INNER COLUMNS (timestamp UInt32 CODEC(DoubleDelta, ZSTD(1)), value Float32 CODEC(ZSTD(3)))Si les deux formes sont utilisées dans la même instruction CREATE TABLE, les types déclarés doivent être identiques.
Tables cibles
Une table TimeSeries ne possède pas ses propres données : tout est stocké dans ses tables cibles.
Son fonctionnement est similaire à celui d’une vue matérialisée,
à la différence qu’une vue matérialisée n’a qu’une seule table cible,
tandis qu’une table TimeSeries a trois tables cibles nommées samples, tags et metrics.
Les tables cibles peuvent être spécifiées explicitement dans la requête CREATE TABLE,
ou le moteur de table TimeSeries peut générer automatiquement des tables cibles internes.
Les lignes insérées dans une table TimeSeries sont transformées, découpées en blocs, puis insérées dans ces trois tables cibles.
Les tables cibles sont les suivantes :
Table samples
La table samples contient des séries temporelles associées à un certain identifiant.
La table samples doit comporter les colonnes suivantes :
| Nom | Obligatoire ? | Type par défaut | Types possibles | Description |
|---|---|---|---|---|
id |
[x] | Tuple(UInt64, UUID) |
tout type | Identifie une combinaison de noms de métriques et de tags |
timestamp |
[x] | DateTime64(3) |
DateTime64(X) |
Un instant donné |
value |
[x] | Float64 |
Float32 ou Float64 |
Une valeur associée au timestamp |
Les colonnes créées par le moteur lui-même reçoivent des codecs de compression pour séries temporelles :
timestamp CODEC(DoubleDelta, ZSTD(1)) et value CODEC(ZSTD(3)). Les horodatages quasi monotones se
compressent très peu avec des codecs génériques et peuvent sinon représenter l'essentiel de la taille sur disque de la table samples.
Voir aussi Ajustement des types de colonnes.
La table tags contient des identifiants calculés pour chaque combinaison d'un nom de métrique et de tags.
La table tags doit contenir les colonnes suivantes :
| Nom | Obligatoire ? | Type par défaut | Types possibles | Description |
|---|---|---|---|---|
id |
[x] | Tuple(UInt64, UUID) |
tout type (doit correspondre au type de id dans la table samples) |
Un id identifie une combinaison d'un nom de métrique et de tags. L'expression DEFAULT indique comment calculer un tel identifiant |
metric_name |
[x] | LowCardinality(String) |
String ou LowCardinality(String) |
Le nom d'une métrique |
<tag_value_column> |
[ ] | String |
String ou LowCardinality(String) ou LowCardinality(Nullable(String)) |
La valeur d'un tag spécifique ; le nom du tag et celui de la colonne correspondante sont indiqués dans le paramètre tags_to_columns |
tags |
[x] | Map(LowCardinality(String), String) |
Map(String, String) ou Map(LowCardinality(String), String) ou Map(LowCardinality(String), LowCardinality(String)) |
Map de tous les tags, y compris le tag __name__ qui contient le nom d'une métrique et les tags dont les noms sont énumérés dans le paramètre tags_to_columns. Les tables créées par des versions antérieures de ClickHouse ne stockaient dans cette colonne que les tags sans colonnes dédiées et sans le nom de la métrique ; la lecture gère les deux cas |
min_time |
[ ] | Nullable(DateTime64(3)) |
DateTime64(X) ou Nullable(DateTime64(X)) |
Horodatage minimal des séries temporelles associées à cet id. La colonne est créée si store_min_time_and_max_time vaut true |
max_time |
[ ] | Nullable(DateTime64(3)) |
DateTime64(X) ou Nullable(DateTime64(X)) |
Horodatage maximal des séries temporelles associées à cet id. La colonne est créée si store_min_time_and_max_time vaut true |
Table metrics
La table metrics contient des informations sur les métriques collectées, leurs types et leurs descriptions.
La table metrics doit comporter les colonnes suivantes :
| Nom | Obligatoire ? | Type par défaut | Types possibles | Description |
|---|---|---|---|---|
metric_family_name |
[x] | String |
String ou LowCardinality(String) |
Le nom d’une famille de métriques |
type |
[x] | LowCardinality(String) |
String ou LowCardinality(String) |
Le type d’une famille de métriques : « counter », « gauge », « summary », « stateset », « histogram » ou « gaugehistogram » |
unit |
[x] | LowCardinality(String) |
String ou LowCardinality(String) |
L’unité utilisée pour une métrique |
help |
[x] | String |
String ou LowCardinality(String) |
La description d’une métrique |
Création
Il existe plusieurs façons de créer une table avec le moteur de table TimeSeries.
L’instruction la plus simple
CREATE TABLE my_table ENGINE=TimeSeriescréera en fait la table suivante (vous pouvez le vérifier en exécutant SHOW CREATE TABLE my_table) :
CREATE TABLE my_table
(
`metric_name` String,
`tags` Map(String, String),
`time_series` Array(Tuple(DateTime64(3), Float64)),
`metric_family` String,
`type` String,
`unit` String,
`help` String
)
ENGINE = TimeSeries
SAMPLES INNER COLUMNS
(
`id` Tuple(UInt64, UUID),
`timestamp` DateTime64(3) CODEC(DoubleDelta, ZSTD(1)),
`value` Float64 CODEC(ZSTD(3))
)
SAMPLES INNER ENGINE = MergeTree ORDER BY (id, timestamp) SETTINGS index_granularity = 32768
TAGS INNER COLUMNS
(
`id` Tuple(UInt64, UUID) DEFAULT tuple(sipHash64(metric_name), reinterpretAsUUID(sipHash128(tags))),
`metric_name` LowCardinality(String),
`tags` Map(LowCardinality(String), String),
`min_time` SimpleAggregateFunction(min, Nullable(DateTime64(3))),
`max_time` SimpleAggregateFunction(max, Nullable(DateTime64(3)))
)
TAGS INNER ENGINE = AggregatingMergeTree PRIMARY KEY metric_name ORDER BY (metric_name, id) SETTINGS allow_dimensions_outside_sorting_key = 1, index_granularity = 8192
METRICS INNER COLUMNS
(
`metric_family_name` String,
`type` LowCardinality(String),
`unit` LowCardinality(String),
`help` String
)
METRICS INNER ENGINE = ReplacingMergeTree ORDER BY metric_family_nameLes colonnes ont donc été générées automatiquement, et il existe également trois tables cibles internes avec leurs propres définitions de colonnes
stockées dans les clauses INNER COLUMNS.
Les tables cibles internes portent des noms tels que .inner_id.samples.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,
.inner_id.tags.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, .inner_id.metrics.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
et chaque table cible possède son propre jeu de colonnes :
CREATE TABLE default.`.inner_id.samples.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
(
`id` Tuple(UInt64, UUID),
`timestamp` DateTime64(3) CODEC(DoubleDelta, ZSTD(1)),
`value` Float64 CODEC(ZSTD(3))
)
ENGINE = MergeTree
ORDER BY (id, timestamp)
SETTINGS index_granularity = 32768CREATE TABLE default.`.inner_id.tags.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
(
`id` Tuple(UInt64, UUID) DEFAULT tuple(sipHash64(metric_name), reinterpretAsUUID(sipHash128(tags))),
`metric_name` LowCardinality(String),
`tags` Map(LowCardinality(String), String),
`min_time` SimpleAggregateFunction(min, Nullable(DateTime64(3))),
`max_time` SimpleAggregateFunction(max, Nullable(DateTime64(3)))
)
ENGINE = AggregatingMergeTree
PRIMARY KEY metric_name
ORDER BY (metric_name, id)
SETTINGS allow_dimensions_outside_sorting_key = 1, index_granularity = 8192CREATE TABLE default.`.inner_id.metrics.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
(
`metric_family_name` String,
`type` LowCardinality(String),
`unit` LowCardinality(String),
`help` String
)
ENGINE = ReplacingMergeTree
ORDER BY metric_family_name
SETTINGS index_granularity = 8192Création d’une table à partir d’une table existante
L’instruction CREATE TABLE new_table AS existing_table copie les éléments suivants de existing_table :
SETTINGSINNER COLUMNSpour chaque typeINNER ENGINEpour chaque type
L’instruction n’est pas autorisée si existing_table comporte des cibles externes.
La liste des colonnes externes est régénérée et non copiée.
Ajustement des types de colonnes
Vous pouvez modifier le type des colonnes dans les tables cibles internes à l’aide de la clause INNER COLUMNS. Par exemple, pour stocker les horodatages en microsecondes et les valeurs en Float32, utilisez :
CREATE TABLE my_table ENGINE=TimeSeries
SAMPLES INNER COLUMNS (timestamp DateTime64(6) CODEC(DoubleDelta, ZSTD(1)), value Float32 CODEC(ZSTD(3)))Spécifier des colonnes internes sans codec revient à utiliser le codec par défaut pour celles-ci :
CREATE TABLE my_table ENGINE=TimeSeries
SAMPLES INNER COLUMNS (timestamp DateTime64(6), value Float32)La colonne id
La colonne id contient des identifiants ; chacun d’eux est calculé à partir d’une combinaison d’un nom de métrique et de tags.
Le type et l’expression DEFAULT utilisés pour générérer les identifiants peuvent être personnalisés via la clause TAGS INNER COLUMNS :
CREATE TABLE my_table ENGINE=TimeSeries
TAGS INNER COLUMNS (id UInt64 DEFAULT sipHash64(tags))La colonne id peut être de tout type comparable non-Nullable. Les types de id déclarés dans les tables internes samples et tags doivent correspondre.
Si aucune expression DEFAULT n’est définie pour la colonne id et que le paramètre id_generator n’est pas défini, ClickHouse choisira automatiquement l’expression DEFAULT en fonction du type de id, mais uniquement si celui-ci est UUID, UInt64, UInt128, FixedString(16) ou un tuple de deux de ces types. Pour un tel tuple, l’expression choisie automatiquement calcule un hash du nom de métrique dans le premier composant et un hash de tous les tags dans le second composant.
Le paramètre id_generator offre la même possibilité de personnalisation sans utiliser la clause INNER COLUMNS :
CREATE TABLE my_table ENGINE=TimeSeries
SETTINGS id_generator = 'sipHash64(tags)'Si ce paramètre est défini, il est utilisé pour générer id, même si le DEFAULT de la colonne contient une expression différente.
La colonne tags contient tous les tags d’une série temporelle, y compris le tag __name__ avec le nom d’une métrique.
Le paramètre tags_to_columns permet de spécifier qu’un tag donné doit également être stocké dans une colonne distincte
en plus de la map au sein de la colonne tags :
CREATE TABLE my_table
ENGINE = TimeSeries
SETTINGS tags_to_columns = {'instance': 'instance', 'job': 'job'}Cette instruction ajoute les colonnes instance et job à la table cible interne des tags.
Les valeurs des tags instance et job seront stockées à la fois dans ces colonnes et dans la colonne tags.
Moteurs des tables cibles internes
Par défaut, les tables cibles internes utilisent les moteurs de table suivants :
- la table samples utilise MergeTree ;
- la table tags utilise AggregatingMergeTree, car les mêmes données sont souvent insérées plusieurs fois dans cette table ; il faut donc
un moyen de supprimer les doublons, et ce moteur est également nécessaire pour effectuer une agrégation sur les colonnes
min_timeetmax_time; - la table metrics utilise ReplacingMergeTree, car les mêmes données sont souvent insérées plusieurs fois dans cette table ; il faut donc un moyen de supprimer les doublons.
D’autres moteurs de table peuvent également être utilisés pour les tables cibles internes si cela est explicitement spécifié :
CREATE TABLE my_table ENGINE=TimeSeries
SAMPLES ENGINE=ReplicatedMergeTree
TAGS ENGINE=ReplicatedAggregatingMergeTree
METRICS ENGINE=ReplicatedReplacingMergeTreeLa table tags conserve les colonnes de tag (et le Map tags) en dehors de sa clé de tri,
ce que AggregatingMergeTree refuse par défaut (voir allow_dimensions_outside_sorting_key).
C’est sans danger ici, car ces colonnes dépendent fonctionnellement de id, qui fait partie de la clé de tri, de sorte que toutes les
lignes qu’une fusion en arrière-plan regroupe partagent les mêmes valeurs. Lorsque la table interne de tags est générée ou que son
moteur est spécifié en intégré comme ci-dessus, TimeSeries y définit automatiquement allow_dimensions_outside_sorting_key = 1 ;
pour une table de tags d’agrégation externe créée manuellement, vous devez le définir vous-même.
Tables cibles externes
Il est possible de faire en sorte qu’une table TimeSeries utilise une table créée manuellement :
CREATE TABLE samples_for_my_table
(
`id` UUID,
`timestamp` DateTime64(3),
`value` Float64
)
ENGINE = MergeTree
ORDER BY (id, timestamp);
CREATE TABLE tags_for_my_table ...
CREATE TABLE metrics_for_my_table ...
CREATE TABLE my_table ENGINE=TimeSeries SAMPLES samples_for_my_table TAGS tags_for_my_table METRICS metrics_for_my_table;Les types de colonnes des tables externes (id, timestamp, value et les <tag_value_column> répertoriées dans tags_to_columns) doivent correspondre à ceux que la table TimeSeries générerait sinon en interne (voir Samples table, Tags table et Metrics table pour les contraintes de type). Les incompatibilités de type sont signalées lors de CREATE.
L'expression du générateur d'identifiant pour une cible de tags externe est évaluée au moment de l'INSERT, dans l'ordre suivant : le paramètre id_generator (s'il est défini), puis la valeur DEFAULT déclarée sur la colonne id de la table externe (le cas échéant), puis le générateur canonique dérivé du type de id. Le paramètre remplace donc toute valeur DEFAULT déclarée sur la table externe — voir la colonne id pour plus de détails.
Modifier les paramètres
Deux paramètres peuvent être modifiés après CREATE :
id_generatorfilter_by_min_time_and_max_time
ALTER TABLE my_table MODIFY SETTING id_generator = 'sipHash64(tags)';
ALTER TABLE my_table MODIFY SETTING filter_by_min_time_and_max_time = 0;Notez que la modification de id_generator alors que des données sont déjà présentes dans la table Tags peut produire des ID différents pour la même combinaison métrique+tag — les anciennes lignes conservent leurs anciens ID, les nouvelles lignes utilisent le nouveau générateur.
Les autres paramètres ne peuvent pas être modifiés avec ALTER ... MODIFY SETTING, car ils sont figés dans le schéma des tables internes au moment du CREATE.
Paramètres
Voici la liste des paramètres qui peuvent être spécifiés lors de la définition d'une table TimeSeries :
| Nom | Type | Par défaut | Description |
|---|---|---|---|
id_generator |
Expression | dépend du type de id |
Expression qui calcule l'identifiant (empreinte) d'une série temporelle à partir de ses tags. Si elle n'est pas définie, l'expression par défaut de la colonne id est utilisée. Si l'expression par défaut de la colonne id n'est pas définie non plus, l'expression est choisie automatiquement |
tags_to_columns |
Map | Map indiquant quels tags doivent être placés dans des colonnes distinctes de la table tags. Syntaxe : {'tag1': 'column1', 'tag2' : column2, ...} |
|
use_all_tags_column_to_generate_id |
Bool | false | Paramètre obsolète, ne fait rien |
store_min_time_and_max_time |
Bool | true | Si la valeur est true, la table stocke min_time et max_time pour chaque série temporelle |
aggregate_min_time_and_max_time |
Bool | true | Lors de la création de la table cible interne tags, ce paramètre active l'utilisation de SimpleAggregateFunction(min, Nullable(DateTime64(3))) au lieu de Nullable(DateTime64(3)) comme type de la colonne min_time, et de même pour la colonne max_time |
filter_by_min_time_and_max_time |
Bool | true | Si la valeur est true, la table utilise les colonnes min_time et max_time pour filtrer les séries temporelles |
samples_index_granularity |
UInt64 | 32768 | Définit index_granularity de la table interne samples. Lorsqu'il est défini explicitement, il surcharge index_granularity de la déclaration du moteur. Ignoré pour une table samples externe et un moteur autre que MergeTree |
tags_index_granularity |
UInt64 | 8192 | Définit index_granularity de la table interne tags. Lorsqu'il est défini explicitement, il surcharge index_granularity de la déclaration du moteur. Ignoré pour une table tags externe et un moteur autre que MergeTree |
recent_samples_ttl_seconds |
UInt64 | 345600 | Durée de conservation de la table cible supplémentaire recent samples, dans laquelle chaque échantillon inséré est également écrit. Une table interne recent samples reçoit toujours le TTL toDateTime(timestamp) + toIntervalSecond(recent_samples_ttl_seconds) dérivé de ce paramètre (surchargeant tout TTL de la déclaration du moteur) ; une table externe recent samples doit conserver au moins ce nombre de secondes de données. Les requêtes dont l'intervalle de temps tient dans la fenêtre TTL privilégient la table recent samples à la table samples principale (voir le paramètre au niveau de la requête time_series_prefer_recent_samples_table). La valeur par défaut est de 4 jours ; la valeur effective est figée dans la définition de la table lors de CREATE. Définissez la valeur sur 0 pour désactiver la table recent samples |
recent_samples_partition_by |
Expression | toStartOfInterval(toDateTime(timestamp), toIntervalHour(5)) |
Clé de partition de la table interne recent samples, par exemple toStartOfHour(timestamp). Nécessite que recent_samples_ttl_seconds soit différent de zéro |
recent_samples_index_granularity |
UInt64 | 8192 | Définit index_granularity de la table interne recent samples. Nécessite que recent_samples_ttl_seconds soit différent de zéro |
Fonctions
Voici une liste de fonctions qui acceptent une table TimeSeries comme argument :