有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

由于 ClickHouse 是一个专门的系统,因此在使用它时,考虑其架构的特点非常重要。 在本报告中,Alexey 将讨论使用 ClickHouse 时常见错误的示例,这些错误可能导致工作效率低下。 实际示例将展示选择一种或另一种数据处理方案如何能够将性能改变几个数量级。

大家好! 我叫 Alexey,我制作 ClickHouse。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

首先我赶紧先拜托大家了,今天我就不告诉你们ClickHouse是什么了。 说实话,我已经厌倦了。 每次我都会告诉你那是什么。 也许每个人都已经知道了。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

相反,我会告诉你有哪些可能的错误,即如何错误地使用ClickHouse。 事实上,没有必要害怕,因为我们正在将 ClickHouse 开发为一个简单、方便、开箱即用的系统。 我安装了,没有问题。

但您仍然需要考虑到该系统是专门的,您很容易遇到不寻常的用例,该用例将使该系统脱离其舒适区。

那么,有什么样的耙子呢? 我主要会谈论显而易见的事情。 一切对每个人来说都是显而易见的,每个人都理解一切并可以庆幸自己如此聪明,而那些不理解的人会学到新的东西。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

第一个也是最简单的例子,不幸的是经常发生,是小批量的大量插入,即大量的小插入。

如果我们考虑 ClickHouse 如何执行插入,那么您可以在一个请求中发送至少 XNUMX TB 的数据。 这不是一个问题。

让我们看看典型的表现是什么。 例如,我们有一个来自 Yandex.Metrica 数据的表。 点击。 105 一些专栏。 未压缩 700 字节。 我们将以良好的方式批量插入一百万行。

我们将 MergeTree 插入表中,结果每秒产生 400 万行。 伟大的。 在复制表中,它会小一些,大约每秒 000 行。

如果您启用仲裁插入,您会获得更少的性能,但仍然具有不错的性能,每秒 250 个术语。 仲裁插入是 ClickHouse* 中一项未记录的功能。

* 截至 2020 年, 已经记录在案.

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

如果你做了坏事会怎样? 我们向 MergeTree 表中插入一行,每秒获得 59 行。 这要慢一万倍。 在 ReplicatedMergeTree 中 – 每秒 10 行。 如果启用了仲裁,则每秒会输出 000 行。 在我看来,这绝对是垃圾。 你怎么能这么慢下来? 我什至在我的 T 恤上写着 ClickHouse 不应该放慢速度。 但尽管如此,有时也会发生这种情况。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

其实这就是我们的缺点。 我们本可以轻松地让一切顺利进行,但我们没有。 我们没有这样做,因为我们的脚本不需要它。 我们已经有屠夫了。 我们刚刚在入口处收到了批次,没有任何问题。 我们插入它,一切正常。 但是,当然,各种情况都是可能的。 例如,当您有一堆生成数据的服务器时。 他们不会经常插入数据,但最终仍然会频繁插入。 我们需要以某种方式避免这种情况。

从技术角度来看,关键是当您在 ClickHouse 中执行插入时,数据不会最终出现在任何内存表中。 我们甚至没有真正的日志结构MergeTree,而只是一个MergeTree,因为既没有日志,也没有memTable。 我们只需立即将数据写入文件系统,这些数据已经按列排列。 如果您有 100 列,则需要将 200 多个文件写入单独的目录。 这一切都非常麻烦。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

那么问题来了:“如何正确地做?”如果情况是这样,你仍然需要以某种方式在 ClickHouse 中记录数据。

方法一。这是最简单的方法。 使用某种分布式队列。 例如,卡夫卡。 您只需从 Kafka 中提取数据并每秒批量处理一次。 一切都会好起来的,你记录一下,一切都会很好。

缺点是 Kafka 是另一个庞大的分布式系统。 我也了解您的公司是否已经有 Kafka。 挺好的,很方便。 但如果它不存在,那么在将另一个分布式系统拖入您的项目之前,您应该三思而行。 因此值得考虑替代方案。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

方法2。这是一种老式的替代方法,同时也非常简单。 您是否有某种可以生成日志的服务器。 它只是将您的日志写入文件。 例如,每秒一次,我们重命名该文件并撕下一个新文件。 一个单独的脚本通过 cron 或某个守护进程获取最旧的文件并将其写入 ClickHouse。 如果你每秒记录一次日志,那么一切都会好起来的。

但这种方法的缺点是,如果你的生成日志的服务器在某个地方消失了,那么数据也会消失。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

方法3. 还有一个有趣的方法,它根本不需要临时文件。 例如,您有某种广告旋转器或其他一些生成数据的有趣守护程序。 你可以直接在RAM、缓冲区中积累一堆数据。 当足够的时间过去后,您将这个缓冲区放在一边,创建一个新的缓冲区,并在一个单独的线程中,将已经积累的内容插入 ClickHouse 中。

另一方面,随着kill -9,数据也会消失。 如果您的服务器崩溃,您将丢失这些数据。 另一个问题是,如果您无法写入数据库,那么您的数据将累积在 RAM 中。 要么 RAM 将耗尽,要么您将丢失数据。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

方法4. 另一个有趣的方法。 你有某种服务器进程吗? 它可以立即向 ClickHouse 发送数据,但是是在一个连接中完成的。 例如,我发送了一个带有transfer-encoding: chunked with insert的http请求。 它生成的块并不罕见,您可以发送每一行,尽管构建此数据会产生开销。

但是,在这种情况下,数据将立即发送到 ClickHouse。 ClickHouse 会自己缓冲它们。

但问题也随之而来。 现在你将丢失数据,包括当你的进程被终止时以及 ClickHouse 进程被终止时,因为这将是一个不完整的插入。 在 ClickHouse 中,插入在行大小达到某个指定阈值时是原子的。 原则上,这是一种有趣的方式。 也可以用。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

方法5. 这是另一个有趣的方法。 这是某种社区开发的用于数据批处理的服务器。 我自己没有看过,所以我不能保证任何事情。 但是,ClickHouse 本身不提供任何保证。 这也是开源的,但另一方面,您可能习惯了我们尝试提供的某些质量标准。 但对于这个东西——我不知道,去GitHub,看代码。 也许他们写了一些正常的东西。

* 自 2020 年起,也应纳入考虑范围 小猫屋.

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

方法6. 另一种方法是使用缓冲表。 这种方法的优点是非常容易上手。 创建一个Buffer表并将其插入其中。

缺点是问题没有完全解决。 如果在像 MergeTree 这样的速率中,您必须每秒对一批数据进行分组,那么在缓冲表中的速率中,您需要每秒至少分组数千个。 如果每秒超过10,那还是很糟糕。 如果你批量插入它,那么你会看到结果是每秒十万行。 这已经是相当大量的数据了。

而且缓冲表没有日志。 如果您的服务器出现问题,那么数据就会丢失。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

作为奖励,我们最近在 ClickHouse 获得了从 Kafka 检索数据的机会。 有一个表引擎——Kafka。 您只需创建。 你可以在上面挂上物化的表示。 在这种情况下,它会自行从 Kafka 中提取数据并将其插入到您需要的表中。

这个机会特别令人高兴的是,这不是我们做的。 这是一个社区功能。 当我说“社区功能”时​​,我的意思是没有任何蔑视。 我们阅读了代码,进行了审查,它应该可以正常工作。

* 截至 2020 年,类似的支持已经出现 的RabbitMQ.

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

插入数据时还有哪些不方便或意外的情况? 如果你发出一个插入values的请求,并在values中写入一些计算表达式。 例如,now()也是一个计算表达式。 在这种情况下,ClickHouse 被迫在每一行启动这些表达式的解释器,性能将下降几个数量级。 最好避免这种情况。

* 目前,问题已完全解决,在VALUES中使用表达式时不再出现任何性能下降。

另一个例子是,当您的一批数据属于一组分区时,可能会出现一些问题。 默认情况下,ClickHouse 分区是按月进行的。 如果你插入一批一百万行,并且有几年的数据,那么那里将有几十个分区。 这相当于会有小几十倍的批次,因为它们内部总是首先被划分为分区。

* 最近,ClickHouse 在实验模式下添加了对 chunk 的紧凑格式以及 RAM 中带有 write-ahead log 的 chunk 的支持,这几乎完全解决了问题。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

现在让我们看看第二类问题——数据类型。

数据类型可以是严格的或字符串的。 字符串是当你刚刚获取它并声明你的所有字段都是字符串类型时。 这太糟糕了。 没有必要这样做。

当您想说我们有某个字段(一个字符串)并让 ClickHouse 自行解决时,让我们弄清楚如何正确执行此操作,我不会打扰。 但仍然值得付出一些努力。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

例如,我们有一个IP地址。 在一种情况下,我们将其保存为字符串。 例如,192.168.1.1。 在另一种情况下,它将是 UInt32* 类型的数字。 对于 IPv32 地址来说 4 位就足够了。

首先,奇怪的是,数据将被大致同等地压缩。 当然会有差别,但不会那么大。 所以磁盘I/O没有什么特殊问题。

但处理器时间和查询执行时间存在严重差异。

让我们计算一下以数字形式存储的唯一 IP 地址的数量。 计算结果为每秒 137 亿行。 如果同样是字符串形式,则每秒 37 万行。 我不知道为什么会发生这样的巧合。 我亲自执行了这些要求。 但仍然慢了大约4倍。

如果计算磁盘空间的差异,那么也存在差异。 差异约为四分之一,因为有相当多的唯一 IP 地址。 而如果有少量不同含义的行,那么它们很容易根据字典被压缩成大致相同的体积。

而四倍的时差并不在于路上。 当然,也许你根本不在乎,但当我看到这样的差异时,我感到很难过。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

让我们看看不同的案例。

1. 一种情况是你有几个不同的唯一值。 在本例中,我们使用您可能知道并且可以用于任何 DBMS 的简单实践。 这不仅对 ClickHouse 有意义。 只需将数字标识符写入数据库即可。 您可以在应用程序一侧转换为字符串并返回。

例如,您有一个区域。 并且您正在尝试将其保存为字符串。 它将写在那里:莫斯科和莫斯科地区。 当我看到上面写着“莫斯科”时,这没什么,但当它是莫斯科时,它不知何故变得完全悲伤。 这是多少字节。

相反,我们只需写下数字 Ulnt32 和 250。我们在 Yandex 中有 250,但您的可能不同。 为了以防万一,我会说 ClickHouse 具有使用地理数据库的内置功能。 您只需写下一个包含区域的目录,包括分层目录,即,将有莫斯科、莫斯科地区以及您需要的一切。 您可以在请求级别进行转换。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

第二个选项大致相同,但在 ClickHouse 内部提供支持。 这是枚举数据类型。 您只需将所需的所有值写入 Enum 中即可。 例如,设备类型并在那里写:台式机、移动设备、平板电脑、电视。 总共有4个选项。

缺点是需要定期更换。 仅添加了一个选项。 让我们修改表。 事实上,ClickHouse 中的 alter table 是免费的。 对于 Enum 尤其免费,因为磁盘上的数据不会改变。 但尽管如此,alter 仍会获取表上的锁*,并且必须等到所有选择都执行完毕。 而且这个改动之后才会执行,即还是有一些不便。

* 在最新版本的 ClickHouse 中,ALTER 已完全非阻塞。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

ClickHouse 的另一个非常独特的选项是连接外部字典。 您可以在 ClickHouse 中写入数字,并将目录保存在任何您方便的系统中。 例如,您可以使用:MySQL、Mongo、Postgres。 您甚至可以创建自己的微服务,通过 http 发送此数据。 在 ClickHouse 级别,您编写一个函数来将该数据从数字转换为字符串。

这是一种在外部表上执行联接的专用但非常有效的方法。 有两种选择。 在一个实施例中,该数据将被完全缓存、完全存在于RAM中并以某一频率更新。 在另一个选项中,如果这些数据无法放入 RAM,那么您可以部分缓存它。

这是一个例子。 有 Yandex.Direct。 还有一家广告公司和横幅。 广告公司大概有几千万家。 它们大致适合 RAM。 还有数十亿条横幅,但它们都不合适。 我们使用 MySQL 的缓存字典。

唯一的问题是,如果命中率接近 100%,缓存的字典就能正常工作。 如果它更小,那么在处理每批数据的查询时,您实际上必须取出丢失的键并从 MySQL 获取数据。 关于ClickHouse,我仍然可以保证——是的,它不会变慢,我不会谈论其他系统。

另外,字典是在 ClickHouse 中追溯更新数据的一种非常简单的方法。 也就是说,您有一份关于广告公司的报告,用户只是更改了广告公司,并且在所有旧数据中,在所有报告中,该数据也发生了变化。 如果将行直接写入表中,将无法更新它们。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

当您不知道从哪里获取字符串的标识符时,还有另一种方法。 你可以简单地散列它。 此外,最简单的选择是采用 64 位哈希。

唯一的问题是,如果哈希值是 64 位,那么几乎肯定会发生冲突。 因为如果那里有十亿条线,那么概率就已经变得很明显了。

而且以这种方式对广告公司的名称进行哈希处理也不太好。 如果不同公司的广告活动混在一起,就会出现难以理解的情况。

还有一个简单的技巧。 确实,它也不太适合严肃的数据,但如果事情不是很严重,那么只需将客户端标识符添加到字典键中即可。 然后就会发生冲突,但仅限于一个客户端内。 我们将此方法用于 Yandex.Metrica 中的链接映射。 我们在那里有 URL,我们存储哈希值。 我们知道,当然存在冲突。 但是当页面显示时,某个用户的一个页面上一些URL粘在一起而被注意到的概率可以忽略不计。

额外的好处是,对于许多操作来说,仅哈希就足够了,并且字符串本身不需要存储在任何地方。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

另一个例子是字符串是否较短,例如网站域名。 它们可以按原样存储。 或者,例如,浏览器语言ru是2个字节。 当然,我确实为这些字节感到遗憾,但别担心,2个字节并不可惜。 请保持原样,不用担心。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

相反,另一种情况是,有很多行,并且其中有很多独特的行,甚至集合也可能是无限的。 一个典型的例子是搜索短语或 URL。 搜索短语,包括拼写错误。 让我们看看每天有多少个独特的搜索短语。 事实证明,它们几乎占所有事件的一半。 在这种情况下,您可能认为需要对数据进行规范化,对标识符进行计数,并将其放入单独的表中。 但你不需要这样做。 只需保持这些线不变即可。

最好不要发明任何东西,因为如果单独存储它,则需要进行连接。 如果该连接仍然适合内存,那么这种连接充其量只是对内存的随机访问。 如果不适合,就会出现问题。

如果数据存储在适当的位置,那么只需从文件系统中按所需的顺序读取数据就可以了。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

如果您有 URL 或其他一些复杂的长字符串,那么值得考虑的是您可以提前计算某种摘录并将其写入单独的列中。

例如,对于 URL,您可以单独存储域。 如果您确实需要一个域名,那么只需使用此列,URL 就会放在那里,您甚至不需要碰它们。

让我们看看有什么不同。 ClickHouse 有一个专门的函数来计算域。 它非常快,我们已经对其进行了优化。 而且,说实话,它甚至不符合 RFC,但它仍然考虑了我们需要的一切。

在一种情况下,我们将简单地获取 URL 并计算域。 计算结果为 166 毫秒。 如果您采用现成的域,则结果仅为 67 毫秒,即几乎快了三倍。 它更快并不是因为我们需要做一些计算,而是因为我们读取的数据更少。

这就是为什么一个较慢的请求具有每秒千兆字节的较高速度。 因为它读取更多千兆字节。 这完全是不必要的数据。 该请求似乎运行得更快,但完成时间更长。

而如果查看磁盘上的数据量,结果发现 URL 为 126 兆字节,而域只有 5 兆字节。 结果减少了 25 倍。 但尽管如此,请求的执行速度仅快了 4 倍。 但那是因为数据很热。 如果是冷的,由于磁盘 I/O,速度可能会快 25 倍。

顺便说一句,如果您估计一个域比 URL 小多少,结果大约小 4 倍。但由于某种原因,数据在磁盘上占用的空间减少了 25 倍。 为什么? 由于压缩。 URL 被压缩,域名也被压缩。 但 URL 通常包含一堆垃圾。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

当然,使用专门为所需值或合适的数据类型设计的正确数据类型是值得的。 如果您使用的是 IPv4,则存储 UInt32*。 如果是IPv6,则FixedString(16),因为IPv6地址是128位,即直接以二进制格式存储。

但是,如果有时有 IPv4 地址,有时有 IPv6 地址怎么办? 是的,您可以同时存储两者。 一列用于 IPv4,另一列用于 IPv6。 当然,有一个选项可以在 IPv4 中显示 IPv6。 这也可行,但如果您经常在请求中需要 IPv4 地址,那么最好将其放在单独的列中。

* ClickHouse 现在拥有独立的 IPv4、IPv6 数据类型,它们可以像数字一样高效地存储数据,但又像字符串一样方便地表示数据。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

还需要注意的是,值得提前对数据进行预处理。 例如,您收到一些原始日志。 也许您不应该立即将它们放入 ClickHouse 中,尽管很容易什么都不做,一切都会正常。 但仍然值得进行可能的计算。

例如,浏览器版本。 在附近的某个部门,我不想指手画脚,浏览器版本是这样存储的,即作为字符串:12.3。 然后,为了制作报告,他们将这个字符串分成一个数组,然后分成数组的第一个元素。 自然地,一切都会慢下来。 我问他们为什么这样做。 他们告诉我他们不喜欢过早的优化。 我不喜欢过早悲观。

所以在这种情况下,分成 4 列会更正确。 在这里不要害怕,因为这是ClickHouse。 ClickHouse 是一个列式数据库。 而且小柱子越整齐越好。 将有 5 个浏览器版本,共 5 列。 这可以。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

现在让我们看看如果有很多很长的字符串、很长的数组该怎么办。 它们根本不需要存储在 ClickHouse 中。 相反,您只能在 ClickHouse 中存储标识符。 并将这些长线放入其他系统中。

例如,我们的一项分析服务有一些事件参数。 如果事件的参数很多,我们只需保存遇到的第一个512即可,因为512并不可惜。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

如果你无法决定数据类型,那么你也可以将数据记录在ClickHouse中,但是记录在Log类型的临时表中,专门用于临时数据。 之后,您可以分析那里的值的分布,一般情况是什么,并创建正确的类型。

*ClickHouse 现在有一个数据类型 低基数 这使您可以轻松高效地存储字符串。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

现在让我们看另一个有趣的案例。 有时事情对人们来说很奇怪。 我进来看到这个。 看起来这是由一些非常有经验、聪明的管理员完成的,他们在设置 MySQL 3.23 版本方面拥有丰富的经验。

这里我们看到一千个表,每个表都记录了谁知道什么除以一千的余数。

原则上,我尊重别人的经历,包括理解通过这种经历可以获得的痛苦。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

原因或多或少是清楚的。 这些是旧的刻板印象,可能是在使用其他系统时积累的。 例如,MyISAM 表没有聚集主键。 而这种划分数据的方式可能是为了获得相同功能而孤注一掷的尝试。

另一个原因是很难对大表进行任何更改操作。 一切都会被封锁。 尽管在现代版本的 MySQL 中这个问题不再那么严重了。

或者,例如微分片,稍后会详细介绍。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

ClickHouse中不需要这样做,因为首先主键是聚簇的,数据是按主键排序的。

有时人们会问我:“ClickHouse 中范围查询的性能如何根据表大小而变化?” 我说它根本没有改变。 例如,您有一个包含 XNUMX 亿行的表,并且您读取的范围为 XNUMX 万行。 一切安好。 如果一个表中有一万亿行,而你读取一百万行,那几乎是一样的。

其次,不需要诸如手动分区之类的各种事情。 如果您查看文件系统上的内容,您会发现该表非常重要。 而且里面还有隔断之类的东西。 也就是说,ClickHouse 为你做一切,你不必受苦。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

如果更改添加/删除列,则 ClickHouse 中的更改是免费的。

而且你不应该创建小表,因为如果表中有 10 行或 10 行,那么根本没有关系。 ClickHouse 是一个优化吞吐量而不是延迟的系统,因此处理 000 行是没有意义的。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

使用一张大桌子是正确的。 摆脱旧的刻板印象,一切都会好起来的。

作为奖励,在最新版本中,我们现在能够创建任意分区键,以便对各个分区执行各种维护操作。

比如说,你需要很多小表,比如当需要处理一些中间数据的时候,你收到了一些chunk,你需要对它们进行一个转换,然后再写入最终的表。 对于这种情况,有一个很棒的表引擎——StripeLog。 它有点像 TinyLog,只是更好。

* 现在ClickHouse也有 表函数输入.

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

另一个反模式是微分片。 比如你需要对数据进行分片,你有5台服务器,明天就有6台服务器。 然后您考虑如何重新平衡这些数据。 相反,您不会分解为 5 个分片,而是分解为 1 个分片。 然后将每个微分片映射到单独的服务器。 例如,您将在一台服务器上获得 000 个 ClickHouse。 不同端口或不同数据库上的单独实例。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

但这在ClickHouse中不太好。 因为即使是一个 ClickHouse 实例也会尝试使用所有可用的服务器资源来处理一个请求。 也就是说,您有某种服务器,例如,它有 56 个处理器内核。 您正在运行一个需要一秒钟的查询,它将使用 56 个核心。 如果您在一台服务器上放置 200 个 ClickHouse,那么就会启动 10 个线程。 一般来说,一切都会很糟糕。

另一个原因是这些实例之间的工作分配不均匀。 有的会早点完成,有的会晚点完成。 如果所有这些都发生在一个实例中,那么 ClickHouse 本身就会弄清楚如何在线程之间正确分配数据。

另一个原因是您将通过 TCP 进行处理器间通信。 数据必须被序列化、反序列化,这是大量的微分片。 它根本不会有效地发挥作用。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

另一种反模式,尽管它很难被称为反模式。 这是大量的预聚合。

一般来说,预聚合是好的。 您有 1 亿行,您将其聚合后变成了 000 行,现在查询立即执行。 一切都很好。 你可以这样做。 为此,甚至ClickHouse也有一个特殊的表类型AggregatingMergeTree,它在插入数据时执行增量聚合。

但有的时候你会觉得我们会这样聚合数据,这样聚合数据。 而在一些邻近的部门,我也不想说是哪一个,他们使用SummingMergeTree表通过主键进行汇总,大约有20列作为主键。 为了以防万一,为了保密,我更改了一些列的名称,但仅此而已。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

就会出现这样的问题。 首先,你的数据量不会减少太多。 例如,它减少了三倍。 如果您的数据未聚合,那么三倍的价格将是一个不错的价格,可以承受无限的分析功能。 如果数据是聚合的,那么你得到的不是分析,而是可怜的统计数据。

它有什么特别之处? 事实上,这些邻近部门的人有时会要求在主键中添加另一列。 也就是说,我们像这样聚合了数据,但现在我们想要更多一点。 但 ClickHouse 没有更改主键。 因此,我们必须用C++编写一些脚本。 而且我不喜欢脚本,即使它们是用 C++ 编写的。

如果你看看 ClickHouse 的创建目的,那么非聚合数据正是它诞生的场景。 如果您将 ClickHouse 用于非聚合数据,那么您的做法是正确的。 如果你汇总的话,有时这是可以原谅的。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

另一个有趣的情况是无限循环中的查询。 有时我会去一些生产服务器并查看那里的显示进程列表。 每次我发现可怕的事情正在发生。

例如,像这样。 很明显,所有事情都可以在一个请求中完成。 只需写下 url 和列表即可。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

为什么许多这样的无限循环查询都是不好的? 如果不使用索引,那么您将多次遍历相同的数据。 但是,例如,如果使用索引,则您有 ru 的主键,并且您可以在其中写入 url = some 。 而你认为如果只从表中读取一个URL,一切都会好起来的。 但实际上没有。 因为ClickHouse是批量做所有事情的。

当他需要读取一定范围的数据时,他就读多一点,因为ClickHouse中的索引是稀疏的。 该索引不允许您在表中查找单个行,而只能查找某种范围。 并且数据被压缩成块。 为了阅读一行,您需要取出整个块并将其松开。 如果您正在进行大量查询,则会有很多重叠,并且您将有大量工作需要一遍又一遍地完成。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

作为奖励,您可以注意到,在 ClickHouse 中,您不应该害怕将兆字节甚至数百兆字节传输到 IN 部分。 我记得在我们的实践中,如果在MySQL中我们将一堆值传输到IN部分,例如我们在那里传输100兆字节的一些数字,那么MySQL就吃掉了10兆字节的内存,并且没有其他任何反应,一切效果不佳。

第二个是,在 ClickHouse 中,如果您的查询使用索引,那么它总是不会比全扫描慢,即如果您需要读取几乎整个表,它将按顺序读取整个表。 一般来说,他都会自己想办法。

但仍然存在一些困难。 例如,带有子查询的 IN 不使用索引。 但这是我们的问题,我们需要解决它。 这里没有什么根本性的东西。 我们会修复它*。

而且另一个有趣的事情是,如果你有一个很长的请求并且正在进行分布式请求处理,那么这个很长的请求将被发送到每个服务器而不进行压缩。 例如,100兆字节和500台服务器。 因此,您将通过网络传输 50 GB 的数据。 它将被传输,然后一切都会成功完成。

* 已经使用; 一切都按照承诺解决了。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

一个相当常见的情况是请求来自 API。 例如,您创建了某种您自己的服务。 如果有人需要你的服务,那么你打开 API,两天后你就会看到一些难以理解的事情正在发生。 一切都超载了,一些本来不应该发生的可怕请求也随之而来。

而且只有一种解决方案。 如果你开放了API,那么你就得砍掉它。 例如,引入某种配额。 没有其他正常的选择。 不然马上写脚本就会出问题。

而且ClickHouse有一个特别的功能——配额计算。 此外,您可以转移您的配额密钥。 例如,这是内部用户 ID。 并且每个人的配额将独立计算。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

现在又一件有趣的事情。 这是手动复制。

我知道很多情况下,尽管 ClickHouse 具有内置的复制支持,但人们还是手动复制 ClickHouse。

原理是什么? 您有一个数据处理管道。 它可以独立工作,例如在不同的数据中心。 您在 ClickHouse 中以相同的方式写入相同的数据。 确实,实践表明,由于代码中的某些功能,数据仍然会出现偏差。 我希望它在你的手中。

有时您仍然需要手动同步。 例如,管理员每月执行一次 rsync。

事实上,使用 ClickHouse 内置的复制要容易得多。 但可能存在一些禁忌,因为为此您需要使用 ZooKeeper。 我不会说 ZooKeeper 的任何坏话,原则上,该系统可以工作,但碰巧人们因为 java 恐惧症而不使用它,因为 ClickHouse 是一个很好的系统,用 C++ 编写,您可以使用它一切都会好起来的。 ZooKeeper是用java编写的。 不知怎的,你甚至不想看,但你可以使用手动复制。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

ClickHouse是一个实用的系统。 她会考虑你的需求。 如果您有手动复制,则可以创建一个分布式表来查看手动副本并在它们之间进行故障转移。 甚至还有一个特殊的选项可以让您避免失败,即使您的线路系统性地出现分歧。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

如果您使用原始表引擎,可能会出现更多问题。 ClickHouse 是一个具有许多不同表引擎的构造函数。 对于所有严重的情况,如文档中所述,请使用 MergeTree 系列中的表。 其余的 - 对于个别情况或测试都是如此。

在 MergeTree 表中,您不需要任何日期和时间。 您仍然可以使用它。 如果没有日期和时间,则默认为 2000。 这将起作用并且不需要资源。

在新版本的服务器中,您甚至可以指定您进行自定义分区而无需分区键。 会是一样的。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

另一方面,您可以使用原始表引擎。 例如,填写数据一次,然后查看、扭曲和删除。 您可以使用日志。

或者存储小卷以进行中间处理是 StripeLog 或 TinyLog。

如果数据量很小并且您可以简单地在 RAM 中调整一些东西,则可以使用内存。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

ClickHouse 并不真正喜欢重新规范化的数据。

这是一个典型的例子。 这是大量的 URL。 你把它们放在旁边的桌子上。 然后他们决定与他们进行 JOIN,但这通常行不通,因为 ClickHouse 只支持 Hash JOIN。 如果没有足够的 RAM 来容纳需要连接的大量数据,则 JOIN 将无法工作*。

如果数据基数很高,那么不用担心,以非规范化形式存储它,URL 直接位于主表中。

* 现在 ClickHouse 还具有合并连接,并且它可以在中间数据无法放入 RAM 的情况下工作。 但这是无效的,建议仍然有效。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

还有几个例子,但我已经怀疑它们是否是反模式。

ClickHouse 有一个已知的缺陷。 它不知道如何更新*。 从某些方面来说,这甚至是好的。 如果您有一些重要数据,例如会计数据,那么没有人能够发送它,因为没有更新。

* 很早之前就添加了对批量模式更新和删除的支持。

但有一些特殊的方法可以让更新就像在后台一样。 例如,像 ReplaceMergeTree 这样的表。 他们在后台合并期间进行更新。 您可以使用优化表强制执行此操作。 但不要经常这样做,因为它会完全覆盖分区。

ClickHouse 中的分布式 JOIN 也不能被查询规划器处理得很差。

不好,但有时还可以。

仅使用 ClickHouse 使用 select* 读回数据。

我不建议使用 ClickHouse 进行繁琐的计算。 但这并不完全正确,因为我们已经放弃了这一建议。 我们最近在 ClickHouse - Catboost 中添加了应用机器学习模型的功能。 这让我很困扰,因为我想,“太恐怖了。 这就是每个字节有多少个周期! 我真的很讨厌在字节上浪费时钟。

有效使用ClickHouse。 阿列克谢·米洛维多夫 (Yandex)

但不要害怕,安装ClickHouse,一切都会好起来的。 如果有的话,我们有一个社区。 顺便说一句,社区就是你。 如果你有任何问题,你至少可以去我们的聊天室,希望他们能帮助你。

问题

感谢您的报告! 我可以在哪里投诉 ClickHouse 崩溃?

你现在可以亲自向我投诉。

我最近开始使用 ClickHouse。 我立即放弃了cli界面。

你真幸运

过了一会儿,我用一个小选择使服务器崩溃了。

你有才华。

我打开了 GitHub bug,但被忽略了。

我们会看到。

阿列克谢诱骗我参加报告,并承诺告诉我如何访问其中的数据。

很简单。

我昨天意识到了这一点。 更多细节。

那里没有什么可怕的伎俩。 只是逐块压缩。 默认为LZ4,您可以启用ZSTD*。 块从 64 KB 到 1 MB。

* 还支持专门的压缩编解码器,可以与其他算法一起使用。

这些块只是原始数据吗?

不完全是生的。 有数组。 如果有数字列,则行中的数字将放置在数组中。

我明白了

Alexey,一个使用 uniqExact over IP 的示例,即 uniqExact 按行计算比按数字计算花费的时间更长,等等。 如果我们在校对的时候用耳朵佯攻、施法呢? 也就是说,你好像说过,在我们的磁盘上它并没有太大的不同。 如果我们从磁盘读取行并进行转换,我们的聚合会更快吗? 或者我们还会在这里略有收获吗? 在我看来,您对此进行了测试,但由于某种原因没有在基准测试中指出。

我认为它会比没有铸造慢。 在这种情况下,必须从字符串中解析 IP 地址。 当然,在ClickHouse,我们的IP地址解析也进行了优化。 我们非常努力,但你看到的数字是用万分之一形式写的。 非常不舒服。 另一方面,uniqExact 函数在字符串上运行速度会较慢,不仅因为这些是字符串,而且还因为选择了不同的专门化算法。 字符串的处理方式只是不同。

如果我们采用更原始的数据类型怎么办? 比如我们把里面的用户id写下来,写成一行,然后打乱,是不是更好玩呢?

我怀疑。 我觉得会更难过,因为毕竟解析数字是一个严重的问题。 在我看来,这位同事甚至给出了解析千分之一形式的数字有多么困难的报告,但也许不是。

阿列克谢,非常感谢您的报告! 非常感谢 ClickHouse! 我有一个关于计划的问题。 是否有计划推出不完全更新词典的功能?

也就是说,部分重启?

是的是的。 就像在那里设置 MySQL 字段的能力一样,即更新后,以便在字典非常大时仅加载此数据。

一个非常有趣的功能。 我认为有人在我们的聊天中建议了它。 也许甚至是你。

我不这么认为。

太好了,现在发现有两个请求。 而且你可以慢慢开始做。 但我想立即警告您,此功能的实现非常简单。 也就是理论上只要在表中写出版本号,然后写上:版本小于某某即可。 这意味着,我们很可能会将其提供给爱好者。 您是爱好者吗?

是的,但不幸的是,不是在 C++ 中。

你的同事知道如何用C++编写吗?

我会找人。

伟大的*。

* 该功能是在报告两个月后添加的 - 问题的作者开发了它并发送了他的 拉请求.

谢谢大家!

你好! 感谢您的报告! 您提到 ClickHouse 非常擅长消耗所有可用资源。 Luxoft 旁边的演讲者谈到了他为俄罗斯邮政提供的解决方案。 他说他们真的很喜欢ClickHouse,但他们没有使用它来代替他们的主要竞争对手,正是因为它占用了所有的CPU。 他们无法将其插入到他们的架构中,插入到带有 Docker 的 ZooKeeper 中。 是否可以以某种方式限制 ClickHouse,使其不消耗可用的所有内容?

是的,这是可能的,而且非常容易。 如果你想消耗更少的核心,那么就写 set max_threads = 1。 就是这样,它将在一个核心中执行请求。 此外,您可以为不同的用户指定不同的设置。 所以没问题。 并告诉 Luxoft 的同事,他们在文档中没有找到此设置,这是不好的。

阿列克谢,你好! 我想问一下这个问题。 这不是我第一次听说很多人开始使用 ClickHouse 作为日志存储。 在报告中你说不要这样做,即你不需要存储长字符串。 你怎么看待这件事?

首先,日志通常不是长字符串。 当然也有例外。 例如,某些用java编写的服务抛出异常,它会被记录下来。 如此无限循环,硬盘空间耗尽。 解决方案非常简单。 如果线条很长,则将其剪掉。 长是什么意思? 几十千字节是不好的*。

* 在ClickHouse的最新版本中,启用了“自适应索引粒度”,这在很大程度上消除了存储长行的问题。

千字节正常吗?

正常。

你好! 感谢您的报告! 我已经在聊天中询问过这个问题,但我不记得是否收到答案。 是否有计划以 CTE 的方式扩展WITH 部分?

还没有。 我们的WITH部分有点无聊。 这对我们来说就像一个小功能。

我明白。 谢谢你!

感谢您的报告! 很有意思! 全球问题。 是否有任何计划来修改数据删除,也许以某种存根的形式?

一定。 这是我们队列中的第一个任务。 我们现在正在积极思考如何正确地做好每一件事。 您应该开始按下键盘*。

*按下键盘上的按钮并完成所有操作。

这是否会以某种方式影响系统性能? 插入会像现在一样快吗?

也许删除本身和更新本身会非常繁重,但这不会影响选择的性能或插入的性能。

还有一个小问题。 在演示中您谈到了主键。 因此,我们有分区,默认情况下按月分区,对吗? 当我们设置一个适合一个月的日期范围时,那么只有这个分区被读取,对吗?

是。

一个问题。 如果我们无法选择任何主键,那么是否可以根据“日期”字段专门进行选择,以便在后台减少对这些数据的重新排列,从而使其以更有序的方式进行排列? 如果您没有范围查询,甚至无法选择任何主键,是否值得在主键中添加日期?

是。

也许在主键中放置一个字段是有意义的,如果按该字段排序,可以更好地压缩数据。 例如,用户 ID。 例如,用户访问同一站点。 在本例中,输入用户 ID 和时间。 然后你的数据将得到更好的压缩。 至于日期,如果你确实没有也从来没有对日期进行范围查询,那么你就不必把日期放在主键中。

好的,非常感谢!

来源: habr.com

添加评论