使用 ClickHouse 实现最终聚合值(不是状态)

Posted

技术标签:

【中文标题】使用 ClickHouse 实现最终聚合值(不是状态)【英文标题】:Materializing the final aggregation value (not state) with ClickHouse 【发布时间】:2021-09-30 16:13:24 【问题描述】:

我想在 ClickHouse 中创建一个物化视图,用于存储聚合函数的最终结果。最佳实践是存储状态和查询时间以计算最终产品,但对于我的用例而言,在查询时间内执行此操作成本太高。

基表:

CREATE TABLE IF NOT EXISTS active_events
(
`event_name` LowCardinality(String),
`user_id` String,
`post_id` String
)

我目前的具体化:

CREATE MATERIALIZED VIEW IF NOT EXISTS inventory
(
    `post_id` String,
    `event_name` LowCardinality(String),
    `unique_users_state` AggregateFunction(uniq, String)
)
ENGINE = AggregatingMergeTree
ORDER BY (event_name, post_id)
POPULATE AS
SELECT
    post_id,
    event_name,
    uniqState(user_id) unique_users_state
FROM active_events
GROUP BY post_id, event_name
FROM test_sessions
GROUP BY session_id;

然后在查询时,我可以使用uniqMerge 来计算完成某个事件的用户的确切数量。 我不介意具体化的小延迟,但我希望在摄取而不是查询期间计算完整的产品。

这是查询:

SELECT post_id, sumIf(total, event_name = 'click') / sumIf(total, event_name = 'impression') as ctr
FROM (
SELECT post_id, event_name, uniqMerge(unique_users_state) as total
    FROM inventory
    WHERE event_name IN ('click', 'impression')
    GROUP BY post_id, event_name
) as res
GROUP BY post_id
HAVING ctr > 0.1
ORDER BY ctr DESC

【问题讨论】:

【参考方案1】:

这简直是不可能的。

假设你在一个表中插入一些 user_id - 3456,有多少个 uniq? 1,好的,但你不能存储1,因为如果你插入3456,它仍然应该是1。所以CH存储状态,它们是HLL(hyperloglog)结构,它们没有完全聚合/计算。因为您可以查询group by event_namegroup by event_name, post_id 或不使用groupby。

查询速度慢的另一个问题。您没有提供您的查询,所以我只能猜测问题是 index_granularity 并且 CH 从您查询的磁盘 IF where event_name = ...

中读取了很多过多的数据

可以这样解决

CREATE MATERIALIZED VIEW IF NOT EXISTS inventory
(
    `post_id` String,
    `event_name` LowCardinality(String),
    `unique_users_state` AggregateFunction(uniq, String) CODEC(NONE) -- uniq is not compressible in 99% cases 
)
ENGINE = AggregatingMergeTree
ORDER BY (event_name, post_id) 
Settings index_granularity=256 -- 256 instead of default 8192.

使用另一个 HLL 函数的另一种方法,因为 uniq 太重了。

试试这个:

CREATE MATERIALIZED VIEW IF NOT EXISTS inventory
(
    `post_id` String,
    `event_name` LowCardinality(String),
    `unique_users_state` AggregateFunction(uniqCombined64(14), String) CODEC(NONE)
)
ENGINE = AggregatingMergeTree
ORDER BY (event_name, post_id)
Settings index_granularity=256
POPULATE AS
SELECT
    post_id,
    event_name,
    uniqCombined64State(14)(user_id) unique_users_state
FROM active_events
GROUP BY post_id, event_name


select uniqCombined64Merge(14)(unique_users_state)
from inventory
where event_name = ...

注意:三个地方都需要使用(14)uniqCombined64(14) / uniqCombined64State(14) / uniqCombined64Merge(14)

uniqCombined64(14) 比 uniq 更不准确,但在某些情况下可以快 10-100 倍并且错误率

【讨论】:

我回家后会尽力提供查询。我确实尝试设置索引粒度和 uniqcombined,因为它不必是准确的。但是性能仍然是数百毫秒,如果我每分钟实现唯一用户,查询可能需要不到 5 毫秒。这是因为我可以使用唯一用户作为排序键的一部分。我不介意将状态存储在中间表中,然后计算最终产品。 我添加了查询 这个查询主要读取所有数据,而 Mat.View 和 AggregatingMergeTree 几乎什么都不做。可能您不需要 Mat.View,而 crond 是您的解决方案。

以上是关于使用 ClickHouse 实现最终聚合值(不是状态)的主要内容,如果未能解决你的问题,请参考以下文章

ClickHouse 聚合函数

ClickHouse 聚合函数

ClickHouse 聚合函数

ClickHouse 聚合函数

Clickhouse - 如何按日期在数组中聚合超出此日期的数据?

天啦,从Mongo到ClickHouse我到底经历了什么?