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

字典

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

字典的用途包括:

  • 提高查询性能,特别是在与 JOIN 一起使用时
  • 在不中断摄取过程的情况下动态丰富摄取的数据

使用字典加速 JOIN

字典可以用来加速特定类型的 JOINLEFT ANY 类型,其中连接键需要匹配底层键值存储的键属性。

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

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

示例

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

我们将争议定义为帖子有相似的支持和反对票数。我们计算这个绝对差值,接近 0 的值意味着更大的争议。我们假设帖子必须至少有 10 个支持和反对票 - 没有人投票的帖子并不是很有争议。

在数据标准化之后,这个查询目前需要使用 postsvotes 表的 JOIN

JOIN 的右侧使用较小的数据集:这个查询看起来可能比必要的要冗长,因为 PostId 的过滤发生在外部和子查询中。这是一种性能优化,确保查询响应时间快速。为了最佳性能,请始终确保 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 的用户是系统用户。

为了在帖子表的插入时间利用该字典,我们需要修改架构:

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

ClickHouse 还支持 DEFAULT(如果未提供,可以插入或计算_value)。

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

我们现在可以获得来自大多数帖子的位置名称:

高级字典主题

选择字典 LAYOUT

LAYOUT 子句控制字典的内部数据结构。有多种选项,文档记录 在这里。选择正确布局的一些提示可以在 这里 找到。

刷新字典

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

对于 ClickHouse 和 Postgres 等数据库源,可以设置一个查询,该查询仅在字典确实发生更改时更新字典(查询的响应决定这一点),而不是在定期间隔内。进一步的细节可以在 这里找到。

其他字典类型

ClickHouse 还支持 分层多边形正则表达式 字典。

进一步阅读