Clickhouse作为时间序列存储

7
我想知道ClickHouse是否可以用于存储时间序列数据,例如包含以下列的模式:"some_entity_id"、"timestamp"、"metric1"、"metric2"、"metric3"、...、"metricN"。每个新列都包含指标名称,可以在添加此指标名称的条目时动态地添加到表中。
官方文档中没有找到有关动态表扩展的任何信息。
那么,这种情况能否在Clickhouse中实现?
更新:经过一些基准测试,我们发现ClickHouse比我们当前的时间序列存储更快地写入新数据,但读取数据要慢得多。

关于慢速问题,您是否尝试使用LowCardinality字段来处理指标数据? - xmar
那是很久以前的事了,我已经不再从事那个项目了。 - Filipp Shestakov
4个回答

22

使用CH作为时间序列数据库有多种方法。 我个人偏好的是使用一个字符串数组来存储指标名称,一个Float64数组来存储指标值。

这是一个示例时间序列表:

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:5015092320102540.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:501509232010254 │ [('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:501509232010254 │ ('load',0.85) │
│ cpu    │ 2017-10-28 23:06:501509232010254 │ ('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:501509232010254 │ ('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:501509232010254 │ ('load',0.85) │ load        │         0.85 │
│ cpu    │ 2017-10-28 23:06:501509232010254 │ ('temp',68)   │ temp        │           68 │
└────────┴─────────────────────┴───────────────┴───────────────┴─────────────┴──────────────┘

由于ClickHouse拥有许多有用的日期和时间函数,以及高阶函数元组,我认为它几乎是一个天然的时间序列数据库。


你们在生产环境中使用 ClickHouse 吗? - mbaxi
@mbaxi 不,我不是。 - ramazan polat

6

修改您的模式以具有4列可能更好:

"some_entity_id","timestamp","metric_name","metric_value"

您可以在MergeTree索引中包含"metric_name",以在搜索实体的特定度量时提高性能。测试一下是否有用,看看您进行的查询类型。


这种方法会增加存储的数据量吗? - Alexander Fresh
2
它可能会增加数据量,例如,如果您正在存储5个不同的指标,则会将实体ID和时间戳重复5次。但是ClickHouse可以压缩列数据,因此差异可能是可以忽略的。 - zooglash

1

是的,我看过了。但它并不完全符合我的需求。如果所有列都存在,那么简单地插入数据并添加列将是完美的,如果数据具有一些需要新列的新指标,则添加列,然后插入数据。但我不想在每次插入时检查列是否存在,因为时间序列插入会经常发生。 - Filipp Shestakov
4
ClickHouse不是无模式(schemaless)数据库。 - Slach

1

编辑:

警告

在我自己使用这种方法来处理几个表格后,我发现使用Array(Tuple(String,String,String))定义查询列会导致大型表格(10亿行以上)崩溃,因此需要谨慎对待。我在这里描述的可能是未定义行为,但我还没有从开发人员那里得到官方消息。

原始答案:

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

此外,一旦添加了列,您总是需要将新内容插入其中,尽管您可以始终有一个“默认”值。

话虽如此... 我发现自己需要动态插入值,有一个“Hack”可以做到,即使用这个列:

Array(Tuple(String,String))

这基本上意味着您可以拥有任意数量的值的数组,并将“描述”“值”的元组插入其中。

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

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

对于其他人:

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

这里的想法是你可以将字符串的值转换为任何其他类型,因此实质上你可以在其中存储任何类型的值。
如果你需要了解每个操作的类型,并且类型可能不同?...那么:
array(Tuple(String,String,String))

在元组存储中,有"name"、"type"和"value"。

这是我能想到的最接近你想要的东西。当然,你应该查看数组操作函数,看看它们是否提供了你想要的功能(它们非常通用,你几乎可以像处理表格本身的行一样处理数组)。

缺点是什么?

速度慢。

这将使查询非常缓慢。根据你想要做什么,这可能是或不是你的问题。如果你足够好地过滤数据,并且很少需要对数十亿行甚至最多数百万行进行查询(并且有足够好的机器来处理查询),那么这些动态数组扩展可能会起作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接