Clickhouse 作为时序存储

Clickhouse as time-series storage

我只是想知道在这种情况下 ClickHouse 是否可以用于存储时间序列数据:具有列的架构:"some_entity_id"、"timestamp"、"metric1"、"metric2", "metric3", ..., "metricN"。每个包含指标名称的新列都可以动态添加到 table,同时添加具有此指标名称的条目。

在官方文档中没有找到任何关于动态table扩展的信息。

那么这个案例是否可以在Clickhouse中实现呢?

更新: 经过一些基准测试后,我们发现 ClickHouse 写入新数据的速度比我们当前的时间序列存储更快,但读取数据的速度要慢得多。

你看到了吗https://clickhouse.yandex/reference_en.html#ALTER

它仅用于 *MergeTree clickhouse table 引擎

最好将您的架构修改为具有 4 列:

"some_entity_id"、"timestamp"、"metric_name"、"metric_value"

您可以在 MergeTree 索引中包含 "metric_name",以提高搜索实体特定指标时的性能。使用和不使用它进行测试,看看它是否对您进行的查询有用。

编辑:

警告

我自己对几个 table 使用此方法后,我观察到使用 Array(Tuple(String,String,String)) 定义查询列似乎会使大型 tables 上的数据库崩溃( 1+ 十亿行),所以请对此持保留态度,我在这里描述的很可能是 UB,但我还没有从开发人员那里得到官方消息

原回答:

您可以更改 tables,但不能动态更改。

此外,添加一列后,您总是需要向其中插入新内容,尽管您始终可以有一个 "default" 值。

话虽这么说...我发现自己需要动态插入值,并且有一个 "Hack" 可以这样做,即使用此列:

Array(Tuple(String,String))

这基本上意味着您可以拥有任意数量的值的数组,并向其中插入 "description" "value".

的元组

因此,对于一行,您的数组可能是:

[("metric_1":"val1"), ("metric_2":"val2")]

另一个:

[("metric_1":"val3"), ("metric_3":"val4"), ("metric_4":"val5")]

这里的想法是您可以将值从字符串转换为任何其他类型,因此本质上您可以在其中存储您想要的任何类型。

如果您需要知道每个操作的类型并且类型可能不同怎么办?...嗯:

array(Tuple(String,String,String))

并且在 tuples 商店中 "name"、"type"、"value"

这是我能想到的最接近您想要的东西。当然,你应该看看数组操作函数,看看它们是否为你提供了你想要的东西(它们非常通用,你或多或少可以用数组做任何你可以用一行 table本身)。

缺点是什么?

嗯,速度。

这会使查询变得很慢。根据您要执行的操作,这对您来说可能是问题,也可能不是问题。如果你足够好地过滤你的数据并且几乎不需要对超过几十行或最多数亿行进行查询(并且有足够好的机器来处理查询)那么这些动态数组扩展就可以工作。

将 CH 用作时间序列数据库的方法不止一种。 我个人的偏好是对指标名称使用一个字符串数组,对指标值使用一个 Float64 数组。

这是一个样本时间序列table:

CREATE TABLE ts1(
    entity String,
    ts UInt64, -- timestamp, milliseconds from January 1 1970
    m Array(String), -- names of the metrics
    v Array(Float32), -- values of the metrics
    d Date MATERIALIZED toDate(round(ts/1000)), -- auto generate date from ts column
    dt DateTime MATERIALIZED toDateTime(round(ts/1000)) -- auto generate date time from ts column
) ENGINE = MergeTree(d, entity, 8192)

这里我们正在为一个实体加载两个指标(负载、温度)(cpu):

INSERT INTO ts1(entity, ts, m, v) 
VALUES ('cpu', 1509232010254, ['load','temp'], [0.85, 68])

并查询cpu加载:

SELECT 
    entity, 
    dt, 
    ts, 
    v[indexOf(m, 'load')] AS load
FROM ts1 
WHERE entity = 'cpu'

┌─entity─┬──────────────────dt─┬────────────ts─┬─load─┐
│ cpu    │ 2017-10-28 23:06:50 │ 1509232010254 │ 0.85 │
└────────┴─────────────────────┴───────────────┴──────┘

获取元组数组形式的数据:

SELECT 
    entity, 
    dt, 
    ts, 
    arrayMap((mm, vv) -> (mm, vv), m, v) AS metrics
FROM ts1 

┌─entity─┬──────────────────dt─┬────────────ts─┬─metrics─────────────────────┐
│ cpu    │ 2017-10-28 23:06:50 │ 1509232010254 │ [('load',0.85),('temp',68)] │
└────────┴─────────────────────┴───────────────┴─────────────────────────────┘

以元组行的形式获取数据:

SELECT 
    entity, 
    dt, 
    ts, 
    arrayJoin(arrayMap((mm, vv) -> (mm, vv), m, v)) AS metric
FROM ts1 

┌─entity─┬──────────────────dt─┬────────────ts─┬─metric────────┐
│ cpu    │ 2017-10-28 23:06:50 │ 1509232010254 │ ('load',0.85) │
│ cpu    │ 2017-10-28 23:06:50 │ 1509232010254 │ ('temp',68)   │
└────────┴─────────────────────┴───────────────┴───────────────┘

获取包含所需指标的行:

SELECT 
    entity, 
    dt, 
    ts, 
    arrayJoin(arrayMap((mm, vv) -> (mm, vv), m, v)) AS metrics
FROM ts1 
WHERE metrics.1 = 'load'

┌─entity─┬──────────────────dt─┬────────────ts─┬─metrics───────┐
│ cpu    │ 2017-10-28 23:06:50 │ 1509232010254 │ ('load',0.85) │
└────────┴─────────────────────┴───────────────┴───────────────┘

获取指标名称和值作为列:

SELECT 
    entity, 
    dt, 
    ts, 
    arrayJoin(arrayMap((mm, vv) -> (mm, vv), m, v)) AS metric, 
    metric.1 AS metric_name, 
    metric.2 AS metric_value
FROM ts1 

┌─entity─┬──────────────────dt─┬────────────ts─┬─metric────────┬─metric_name─┬─metric_value─┐
│ cpu    │ 2017-10-28 23:06:50 │ 1509232010254 │ ('load',0.85) │ load        │         0.85 │
│ cpu    │ 2017-10-28 23:06:50 │ 1509232010254 │ ('temp',68)   │ temp        │           68 │
└────────┴─────────────────────┴───────────────┴───────────────┴─────────────┴──────────────┘

由于CH有很多useful date and time functions, along with higher order functions and tuples,我想这几乎是一个天然的时间序列数据库。