跳到主要内容
跳到主要内容

字典

在 ClickHouse 中,字典提供了来自各种 内部和外部来源 的内存 键值 表示,优化以实现超低延迟的查找查询。

字典的用途包括:

  • 改善查询性能,特别是在与 JOIN 一起使用时
  • 在不减慢数据摄取过程的情况下,实时丰富摄取的数据
在 ClickHouse 中使用字典的用例

使用字典加速连接

字典可以用于加速特定类型的 JOINLEFT ANY 类型,在此类型中,连接键需要与基础键值存储的键属性匹配。

使用字典与 LEFT ANY JOIN

如果是这种情况,ClickHouse 可以利用字典执行 直接连接。这是 ClickHouse 的最快连接算法,适用于右侧表的基础 表引擎 支持低延迟的键值请求。ClickHouse 有三个表引擎提供此功能:Join(基本上是预计算的哈希表)、EmbeddedRocksDBDictionary。我们将描述基于字典的方法,但机制对所有三种引擎都是相同的。

直接连接算法要求右侧表由字典支持,使得要连接的数据在内存中以低延迟的键值数据结构的形式存在。

示例

使用 Stack Overflow 数据集,来回答这个问题: 在 Hacker News 上关于 SQL 的最具争议的帖子是什么?

我们定义“争议”是指帖子有相似的赞成票和反对票。我们计算这个绝对差值,越接近 0 的值表示越具有争议。我们假设帖子必须有至少 10 个赞同和反对票——没有人投票的帖子并不算特别有争议。

经过数据规范化后,这个查询当前需要使用 posts 表和 votes 表的 JOIN

JOIN 的右侧使用更小的数据集:这个查询可能看起来比实际需要的更冗长,因为在外部和子查询中对 PostIds 的过滤都出现了。这是一种性能优化,确保查询响应时间快速。为了获得最佳性能,请始终确保 JOIN 的右侧是较小的数据集,并尽可能小。有关优化 JOIN 性能和理解可用算法的提示,我们推荐 这一系列博客文章

虽然这个查询很快,但它依赖于我们仔细编写 JOIN 来实现良好的性能。理想情况下,我们只需在查看 UpVoteDownVote 计数之前,先过滤出标题中包含“SQL”的帖子。

应用字典

为了演示这些概念,我们对投票数据使用一个字典。由于字典通常保存在内存中(ssd_cache 是例外),用户应意识到数据的大小。确认我们的 votes 表大小:

数据将以未压缩形式存储在我们的字典中,因此如果我们存储所有列(我们不会)在字典中,至少需要 4GB 的内存。字典将在我们的集群中复制,因此每个节点都需要保留此内存量。

在下面的示例中,我们的字典数据来源于 ClickHouse 表。尽管这代表字典的最常见来源,但还支持 许多其他来源,包括文件、http 和数据库,包括 Postgres。如我们所示,字典可以自动刷新,为确保频繁更改的小数据集可以用于直接连接提供了理想的方法。

我们的字典需要一个作为查找的主键。概念上,这与事务数据库主键相同,并且应该是唯一的。我们上面的查询要求在连接键 - PostId 上进行查找。字典应填充来自 votes 表的每个 PostId 的总赞成票和反对票。以下是获取字典数据的查询:

要创建我们的字典,需要以下 DDL - 注意我们上面查询的使用:

在自管理 OSS 中,上述命令需要在所有节点上执行。在 ClickHouse Cloud 中,字典将自动复制到所有节点。上述命令在一个具有 64GB RAM 的 ClickHouse Cloud 节点上执行,耗时 36 秒加载。

确认我们的字典所消耗的内存:

现在可以使用简单的 dictGet 函数检索特定 PostId 的赞成票和反对票。以下是检索帖子 11227902 的值:

这个查询不仅简单得多,而且速度也超过了两倍!通过仅将超过 10 个赞成票和反对票的帖子加载到字典中,并仅存储预计算的争议值,进一步优化也是可能的。

查询时丰富

字典可用于在查询时查找值。这些值可以在结果中返回或用于聚合。假设我们创建一个字典来映射用户 ID 到他们的位置:

我们可以使用这个字典来丰富帖子结果:

类似于我们上面的连接示例,我们可以使用相同的字典高效地确定大多数帖子来自哪里:

索引时丰富

在上述示例中,我们在查询时使用了字典来去除一个连接。字典还可用于在插入时丰富行。这通常适用于丰富值没有变化且存在于外部源中,字典可以用于填充的情况。在这种情况下,在插入时丰富行可以避免在查询时查找字典。

假设 Stack Overflow 中用户的 Location 从不改变(实际上它们会变化)——具体来说是 users 表的 Location 列。假设我们想按位置对帖子表进行分析查询。这包含一个 UserId

字典提供了用户 ID 到位置的映射,支持 users 表:

我们省略了 Id < 0 的用户,从而允许我们使用 Hashed 字典类型。Id < 0 的用户是系统用户。

要在 posts 表的插入时利用这个字典,我们需要修改架构:

在上述示例中,Location 被声明为 MATERIALIZED 列。这意味着该值可以作为 INSERT 查询的一部分提供,并且将始终被计算。

ClickHouse 还支持 DEFAULT(在未提供时可以插入或计算该值)。

要填充该表,我们可以使用常规的 INSERT INTO SELECT 从 S3:

我们现在可以获取大多数帖子来自的地点的名称:

高级字典主题

选择字典的 LAYOUT

LAYOUT 子句控制字典的内部数据结构。存在多种选项,并在 此处 进行了文档记录,选择正确布局的一些建议可以在 这里 找到。

刷新字典

我们已经为字典指定了 LIFETIME = MIN 600 MAX 900。LIFETIME 是字典的更新时间间隔,此处的值导致在 600 到 900 秒之间的随机间隔进行定期重新加载。这个随机间隔是必要的,以便在更新大量服务器时分散对字典源的负载。在更新期间,可以查询旧版本的字典,只有初始加载会阻塞查询。请注意,设置 (LIFETIME(0)) 会阻止字典更新。 字典可以通过 SYSTEM RELOAD DICTIONARY 命令强制重新加载。

对于数据库源,如 ClickHouse 和 Postgres,您可以设置一个查询,该查询仅在实际更改的情况下更新字典(查询的响应会决定这一点),而不是在周期性间隔下更新。进一步的详细信息可以在 这里 找到。

其他字典类型

ClickHouse 还支持 层次型字典多边形字典正则表达式字典

更多阅读