使用 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_name
或group 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 实现最终聚合值(不是状态)的主要内容,如果未能解决你的问题,请参考以下文章