Clickhouse 作为时间序列存储

问题描述 投票:0回答:5

我只是想知道ClickHouse是否可以用于在这样的情况下存储时间序列数据:具有列的架构:“some_entity_id”,“timestamp”,“metric1”,“metric2”,“metric3”,...,“公制N”。其中包含指标名称的每个新列都可以动态添加到表中,同时添加具有该指标名称的条目。

官方文档中没有找到关于动态表扩展的信息。

那么这个案例可以在Clickhouse中实现吗?

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

database time-series clickhouse
5个回答
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: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 有很多有用的日期和时间函数,以及高阶函数元组,我认为它几乎是一个自然的时间序列数据库。


6
投票

将架构修改为 4 列可能会更好:

“some_entity_id”,“时间戳”,“metric_name”,“metric_value”

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


1
投票

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

仅用于 *MergeTree clickhouse 表引擎


1
投票

编辑:

警告

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

原答案:

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

此外,添加列后,您始终需要向其中插入新内容,尽管您始终可以拥有“默认”值。

话虽这么说...我发现自己需要动态插入值,并且有一个“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))

并在元组中存储“名称”,“类型”,“值”

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

有什么缺点?

嗯,速度。

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


0
投票

这是开始使用 ClickHouse 作为时间序列数据库的简单方法。

  1. 在ClickHouse中创建时间序列表

使用双 Delta Gorilla 编码以获得最佳压缩效果:

set allow_suspicious_codecs = 1;
CREATE TABLE time_series_dd_g
(
    `timeseries` DateTime CODEC(DoubleDelta),
    `data` UInt64 CODEC(Gorilla)
)
ENGINE = MergeTree
ORDER BY timeseries;

  1. 将数据插入表中:

INSERT INTO time_series_dd_g SELECT now() - number, number FROM numbers(10000000);

SELECT formatReadableSize(sum(bytes)) FROM system.parts WHERE active AND table = 'time_series_dd_g';

  1. 随意询问。

--

您还可以创建一个具有 TTL(生存时间)策略的时间序列数据库来保留数据,并使用

AggregatedMergeTree
存储引擎通过物化视图对其进行查询。以下是如何执行此操作。

CREATE TABLE time_series
(
    `timeseries` DateTime CODEC(T64),
    `data` UInt64 CODEC(T64)
)
ENGINE = MergeTree
ORDER BY timeseries
TTL timeseries + INTERVAL 5 DAY DELETE;

CREATE MATERIALIZED VIEW timeseries_mw
(
    `date` Date,
    `data` UInt64
)
ENGINE = AggregatingMergeTree
ORDER BY date AS
SELECT
    DATE(timeseries) AS date,
    sum(data) AS value
FROM time_series
GROUP BY date;

此MV将根据指定的查询自动聚合

time_series
表中的所有数据(最近5天)。

在附加链接中阅读有关如何使用 ClickHouse 作为时间序列数据库的更多信息。

© www.soinside.com 2019 - 2024. All rights reserved.