clickhouse聚合之内存不足怎么办?那就提升聚合性能
liuian 2025-01-08 15:17 22 浏览
大家好,我是奇想派,可以叫我奇奇。
当你遇到clickhouse内存溢出,内存不足报错,如包含「Exception: Memory limit (for query)「、」Exception: Memory limit (total) exceeded」等,这样的错误时候,是不是手足无措,不知如何下手,那么你就应该认真看看这篇文章啦,本文教你如何解决clickhouse内存溢出问题。
在本篇文章中,主要是讲解如何提升聚合性能。主要步骤是:
- 1、先带大家对clickhouse实际查询进行性能测试,这样我们可以先充分理解当前性能的耗时和资源使用情况。
- 2、根据实际情况,我们再进行一系列实用技巧,给聚合性能提高一个档次。让聚合更快、更有效益。
探索聚合性能
让我们详细看一下聚合,并使用示例查询测试性能。我们将改变三个不同的方面:GROUP BY 键、聚合函数的复杂性以及扫描中使用的线程数。通过保持示例相对简单,我们可以更轻松地理解围绕聚合的权衡。
分组依据中值的影响
我们最初的示例查询有几十个 GROUP BY 值。回过头来参考用于保存扫描结果的哈希表,这意味着此类哈希表具有少量键。如果我们使用不同的 GROUP BY 运行相同的查询,从而生成更多的值,该怎么办?较大的密钥数量是否会对聚合的资源或速度产生影响?
这很容易测试。首先,让我们重新运行示例查询,然后重新运行另一个具有不同 GROUP BY 的查询。
SET max_untracked_memory = 1
-- #1
SELECT Carrier, avg(DepDelay) AS Delay
FROM ontime
GROUP BY Carrier
ORDER BY Delay DESC LIMIT 3
;
-- #2
SELECT Carrier, FlightDate, avg(DepDelay) AS Delay
FROM ontime
GROUP BY Carrier, FlightDate
ORDER BY Delay DESC LIMIT 314
如果我们现在检查system.query_log查询速度和内存,我们会看到以下内容。
┌──────────event_time─┬──secs─┬─memory────┬─threads─┬─query───────────────┐
│ 2022-03-15 11:09:34 │ 0.725 │ 33.96 MiB │ 4 │ -- #2SELECT Carrier │
│ 2022-03-15 11:09:33 │ 0.831 │ 10.46 MiB │ 4 │ -- #1SELECT Carrier │
└─────────────────────┴───────┴───────────┴─────────┴─────────────────────┘
第二个查询使用的内存量存在很大差异。拥有更多密钥会占用更多 RAM,至少在这种情况下是这样。导致这种情况的密钥数量有什么区别?我们可以使用以下查询找出答案。方便的 uniqExact() 聚合计算行中值的唯一出现次数。
SELECT
uniqExact(Carrier),
uniqExact(Carrier, FlightDate)
FROM ontime
FORMAT Vertical
Row 1:
──────
uniqExact(Carrier): 35
uniqExact(Carrier, FlightDate): 169368
我们的第一个查询有 35 个 GROUP BY 键。我们的第二个查询必须处理 169,368 个密钥。
在离开此示例之前,值得注意的是,第二个查询始终比第一个查询快约 15%。这是令人惊讶的,因为您可能会认为更多的GROUP BY键意味着更多的工作。正如我们在其他地方所解释的那样,ClickHouse根据GROUP BY键的数量和类型选择不同的聚合方法以及不同的哈希表配置。反过来,它们的效率会有所不同。
如何知道?
我们可以使用 clickhouse-client –send_logs_message='trace' 选项启用跟踪日志记录,然后仔细查看消息。(有关如何执行此操作的说明,请参阅之前文章《clickhouse聚合之探索聚合内部机制》。它显示扫描线程完成的时间,还指示聚合方法。下面是第二个查询中来自其中一个线程的几个典型消息。
[chi-clickhouse101-clickhouse101-0-0-0] 2022.03.28 14:23:15.311284 [ 1887 ] {92d8506c-ae7c-4d01-84b6-3c927ed1d6a5} <Trace> Aggregator: Aggregation method: keys32
…
[chi-clickhouse101-clickhouse101-0-0-0] 2022.03.28 14:23:16.007341 [ 1887 ] {92d8506c-ae7c-4d01-84b6-3c927ed1d6a5} <Debug> AggregatingTransform: Aggregated. 48020348 to 42056 rows (from 366.37 MiB) in 0.698374248 sec. (68760192.887 rows/sec., 524.61 MiB/sec.)
在第二种情况下,扫描线程的运行速度更快,这可能意味着所选的聚合方法和/或哈希表实现更快。为了找出为什么我们需要从src/Interpreters/Aggregator.h开始深入研究代码。在那里,我们可以了解「keys32」和其他聚合方法。这项调查将不得不等待另一篇博客文章。
不同聚合函数的影响
avg() 函数非常简单,使用的内存相对较少 - 基本上它创建的每个部分聚合都有几个整数。如果我们使用更复杂的函数会发生什么?
uniqExact() 函数是一个很好的尝试。此聚合存储一个哈希表,其中包含它在块中看到的值,然后合并它们以获得最终答案。我们可以假设哈希表将需要更多的RAM来存储,并且速度也会更慢。让我们来看看!我们将使用少量和大量 GROUP BY 键运行以下查询。
SET max_untracked_memory = 1
SELECT Carrier, avg(DepDelay) AS Delay, uniqExact(TailNum) AS Aircraft
FROM ontime
GROUP BY Carrier ORDER BY Delay DESC
LIMIT 3
-- #2
SELECT Carrier, FlightDate, avg(DepDelay) AS Delay,
uniqExact(TailNum) AS Aircraft
FROM ontime
GROUP BY Carrier, FlightDate ORDER BY Delay DESC
LIMIT 3
当我们检查system.query_log中的查询性能时,我们会看到以下结果。
┌──────────event_time─┬──secs─┬─memory────┬─threads─┬─query────────────────┐
│ 2022-03-15 12:19:08 │ 3.324 │ 2.41 GiB │ 4 │ -- #2SELECT Carrier │
│ 2022-03-15 12:19:04 │ 2.657 │ 21.57 MiB │ 4 │ -- #1SELECT Carrier │
. . .
添加此新聚合的执行时间肯定存在差异,但这并不奇怪。更有趣的是RAM的增加。对于少量的密钥,它不是很多:21.57MiB,而我们原始示例查询中的10.46Mib。
但是,RAM 使用量会随着更多的 GROUP BY 键而爆炸式增长。如果您考虑查询,这是有道理的,该查询要求在每个组中进行航班的航空公司尾号(即飞机)的确切计数。将FlightDate添加到GROUP BY意味着同一架飞机被计数更多次,这意味着它们必须保留在哈希表中,直到ClickHouse可以合并并进行最终计数。
由于我们看到的是爆炸式增长,让我们再尝试一个查询。groupArray() 函数是 ClickHouse 独有的强大聚合,它将组中的列值收集到数组中。我们假设它将使用比uniqExact更多的内存。那是因为它将包含所有值,而不仅仅是我们计算的值。
下面是查询,后跟查询日志统计信息。
SELECT Carrier, FlightDate, avg(DepDelay) AS Delay,
length(groupArray(TailNum)) AS TailArrayLen
FROM ontime
GROUP BY Carrier, FlightDate ORDER BY Delay DESC
LIMIT 3
┌──────────event_time─┬──secs─┬─memory───┬─threads─┬─query───────────────┐
│ 2022-03-15 21:28:51 │ 2.957 │ 5.66 GiB │ 4 │ -- #3SELECT Carrier │
└─────────────────────┴───────┴──────────┴─────────┴─────────────────────┘
正如预期的那样,这比uniqExact占用更多的内存:5.66GiB。默认情况下,ClickHouse 将终止使用超过 10GiB 的查询,因此这接近一个有问题的级别。但是,请注意,鉴于我们正在扫描近200M行,所有这些仍然非常快。与以前一样,由于 ClickHouse 在每种情况下选择不同的聚合方法和哈希表实现,因此性能也存在差异。
执行线程数的影响
对于我们的最后一项调查,我们将研究线程的影响。我们可以使用max_threads来控制扫描阶段的线程数。我们将运行相同的查询三次,如下所示:
SET max_untracked_memory = 1
SET max_threads = 1
SELECT Origin, FlightDate, avg(DepDelay) AS Delay,
uniqExact(TailNum) AS Aircraft
FROM ontime
WHERE Carrier='WN'
GROUP BY Origin, FlightDate ORDER BY Delay DESC
LIMIT 3
SET max_threads = 2
(same query)
SET max_threads = 4
(same query)
好吧,让我们看一下生成的内存使用情况。我添加了注释,以便更容易分辨哪个查询是哪个查询。
┌──────────event_time─┬──secs─┬─memory───┬─threads─┬─query───────────────┐
│ 2022-03-15 22:12:02 │ 0.86 │ 1.71 GiB │ 8 │ -- #8SELECT Origin, │
│ 2022-03-15 22:12:01 │ 1.285 │ 1.72 GiB │ 4 │ -- #4SELECT Origin, │
│ 2022-03-15 22:11:59 │ 2.325 │ 1.75 GiB │ 2 │ -- #2SELECT Origin, │
│ 2022-03-15 22:11:56 │ 4.408 │ 1.91 GiB │ 1 │ -- #1SELECT Origin, │
. . .
如果我们把它们放在一个图表中,就更容易理解结果。
最大线程和聚合关系表
添加线程可显著提高聚合查询速度,最高可达一个点,在本例中为 4 个线程。在某些时候,扫描会随着线程的增加而停止加速 - 这里的情况是4到8个线程之间。有趣的是,ClickHouse在扫描运行后并行化合并操作。如果仔细检查调试级别的消息,则会看到这部分查询执行速度也会加快。
添加更多线程会影响内存使用量,但可能不会像您想象的那样。在这种情况下,当线程较多时,我们使用较少的 RAM 来处理查询。
提高聚合性能的实用技巧
我们已经详细研究了聚合响应时间和内存使用情况,这是生产 ClickHous 实现中的两大考虑因素。在最后一节中,我们将总结改进两者的实用方法。
加快聚合速度
有多种方法可以使具有聚合的查询速度更快。以下是主要建议。
删除或交换复杂的聚合函数
使用 uniqExact 来计算发生次数?请尝试uniq。它的速度更快,但代价是答案的精度较低。ClickHouse还具有特定的功能,可以用更具体的实现来取代昂贵的聚合。如果您正在处理网站访问者的数据以计算转化率,请尝试使用windowFunnel。
减少“分组依据”中的值
处理大量 GROUP BY 键可能会增加聚合时间。如果可能,按较少的项目分组。
使用max_threads提高并行度
增加max_threads可以导致接近线性响应时间的改进,尽管最终添加新线程不再增加吞吐量。此时,您正在添加的内核将被浪费。
推迟连接
如果要针对数据进行连接,则最好将关联查询推迟到聚合完成后,这样能减少初始扫描中的处理。下面是一个示例。ClickHouse 将首先运行子查询,然后将结果与表左连接。
SELECT Dest, airports.Name Name, c Flights, ad Delay
FROM
(
SELECT Dest, count(*) c, avg(ArrDelayMinutes) ad
FROM ontime
GROUP BY Dest HAVING c > 100000
ORDER BY ad DESC LIMIT 10
) a
LEFT JOIN airports ON IATA = Dest
如果我们直接连接表,ClickHouse将在主扫描期间连接行,这需要额外的CPU。相反,我们只是在子查询完成后的末尾加入 10 行。它大大减少了所需的处理时间。
这种方法还可以节省内存,但除非您向结果中添加大量列,否则效果可能不明显。该示例为每个 GROUP BY 键添加一个值,这在事物的宏伟方案中根本不是很大。
过滤不必要的数据
继上一点之后,如果处理的数据较少,则任何查询的运行速度都会更快。过滤掉不需要的行,完全删除不需要的列,以及所有这些好东西。
使用实例化视图
具体化视图预先聚合数据以服务于常见用例。在最好的情况下,他们只需减少需要扫描的数据量,就可以将查询响应时间缩短 1000 倍或更多。
更高效地使用内存
使用实例化视图(再次)
我们提到过它们是否节省了内存?例如,将基于秒的测量值预先聚合为小时的具体化视图可以大大减少 ClickHouse 在初始扫描期间拖动的部分聚合的大小和数量。实例化视图通常也会在外部存储中使用更少的空间。
删除或交换占用大量内存的聚合函数
像 max、min 和 avg 这样的函数对内存非常简洁。相比之下,像uniqExact这样的函数是记忆猪。要了解跨函数的内存使用情况以计算唯一发生次数,请查看此计数唯一发生次数的函数的表,如下表。
计数唯一发生次数的函数的表
减少“分组”里的字段数
GROUP BY 的聚合可能会出现一个情况,那就是随着字段数的增加,内存会爆炸式增长。如果无法减少 GROUP BY 中的字段数,则另一个方式是使用实例化视图对特定的 GROUP BY 组合进行预聚合。
调整内存参数
如果查询内存占用必不可免,那就修改参数,提高限制,让查询可以正常运行。ClickHouse 内存限制由以下参数控制,您可以在系统级别或在用户配置文件中更改这些参数(具体取决于设置)。
?
常见错误
?
User class threw exception: ru.yandex.clickhouse.except.ClickHouseException: ClickHouse exception, code: 241, host: 10.121.8.8, port: 8123; Code: 241, e.displayText() = DB::Exception: Memory limit (for query) exceeded: would use 9.31 GiB (attempt to allocate chunk of 1048591 bytes), maximum: 9.31 GiB (version 19.9.5.36)
?
解决涉及的参数
?
- max_memory_usage – 单个查询的最大内存字节数。默认值为10GiB,这对于大多数目的来说已经足够了,但可能不是你的。
- max_memory_usage_for_user – 单个用户在单个时间点的所有查询的最大字节数。默认值为无限制。
- max_server_memory_usage – 整个 ClickHouse 服务器的最大内存。默认值为可用 RAM 的 90%。
?
解决方案 配置文件一般修改用户列表文件/etc/clickhouse-server/users.xml (默认路径,具体根据自己文件目录来定)
?
#这里假设clickhouse所在服务器最大RAM内存:128G
<yandex>
<profiles>
<default>
<max_memory_usage>123000000000</max_memory_usage>
<max_server_memory_usage>123000000000</max_server_memory_usage>
...
</default>
</profiles>
<users>
...
</users>
</yandex>
调整聚合参数
如果所有其他方法都失败,则可以将部分聚合转储到外部存储。使用max_bytes_before_external_group_by设置。标准建议将其设置为max_memory_usage的50%。这可以确保您不会在合并阶段用完。
配置文件一般修改用户列表文件/etc/clickhouse-server/users.xml (默认路径,具体根据自己文件目录来定)
#这里假设clickhouse所在服务器最大RAM内存:128G
<yandex>
<profiles>
<default>
<max_memory_usage>123000000000</max_memory_usage>
<max_server_memory_usage>123000000000</max_server_memory_usage>
<max_bytes_before_external_group_by>61500000000</max_bytes_before_external_group_by>
...
</default>
</profiles>
<users>
...
</users>
</yandex>
结论
「聚合是数据仓库中从大数据中提取意义的基本操作」。ClickHouse聚合就像一辆高性能的赛车。它非常快,但你需要训练才能赢得比赛。做对了,你会在几分之一秒内得到结果;但做错了,你的查询会很慢或内存不足。
在这个由两部分组成的系列文章中,我们介绍了聚合的工作原理,展示了用于检查性能的简单工具,并探讨了聚合性能。
更多内容请关注微信公众号【「编程达人」】,分享优质好文章,编程黑科技,助你成为编程达人!
相关推荐
- 快速上手maven
-
Maven的作用在开发过程中需要用到各种各样的jar包,查找和下载这些jar包是件费时费力的事,特别是英文官方网站,可以将Maven看成一个整合了所有开源jar包的合集,我们需要jar包只需要从Mav...
- Windows系统——配置java环境变量
-
怎么配置java环境变量呢?首先是安装好jdk然后我的电脑右键选择属性然后选择左侧高级系统设置高级然后点环境变量然后在用户变量或系统变量中配置,用户变量指的是只有当前用户可用,系统变量指的是系统中...
- ollama本地部署更改默认C盘,Windows配置环境变量方法
-
ollama是一个大语言模型(LLM——LargeLanguageModel),本地电脑安装网上也要很多教程,看上去非常简单,一直下一步,然后直接就可以使用了。但是我在实操的时候并不是这样,安装完...
- # Windows 环境变量 Path 显示样式更改
-
#怎样学习Java##Windows环境变量Path显示样式更改##1、传统Path环境变量显示:```---》键盘上按【WIN+I】打开系统【设置】---》依次点击---》【系统...
- 如何在Windows中创建用户和系统环境变量
-
在Windows中创建环境变量之前您应该了解的事情在按照本指南中所示的任何步骤创建指向文件夹、文件或其他任何内容的用户和系统变量之前,您应该了解两件事。第一个也是最重要的一个是了解什么是环境变量。...
- Windows 中的环境变量是什么?
-
Windows中的环境变量是什么?那么,Windows中的环境变量是什么?简而言之,环境变量是描述应用程序和程序运行环境的变量。所有类型的程序都使用环境变量来回答以下问题:我安装的计算机的名称是什么...
- 【Python程序开发系列】谈一谈Windows环境变量:系统和用户变量
-
这是我的第350篇原创文章。一、引言环境变量(environmentvariables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,如:临时文件夹位置和系统文件夹位置等。环境变量是在操作...
- 系统小技巧:还原Windows10路径环境变量
-
有时,我们在Windows10的“运行”窗口中执行一些命令或运行一些程序,这时即便没有指定程序的具体路径,只输入程序的名称(如notepad.exe),便可以迅速调用成功。这是因为Windows默认...
- Windows10系统的“环境变量”在哪里呢?
-
当我们在操作系统是Windows10的电脑里安装了一些软件,要通过配置环境变量才能使用软件时,在哪里能找到“环境变量”窗口呢?可以按照下面的步骤找到“环境变量”。说明:下面的步骤和截图是在Window...
- 系统小技巧:彻底弄懂Windows 10环境变量
-
每当我们进行系统清理时,清理软件总能自动找到Windows的临时文件夹之所在,然后加以清理,即便是我们重定向了TEMP目录也是如此。究其原因,是因为清理软件会根据TEMP环境变量来判断现有临时文件夹的...
- MySQL 5.7 新特性大全和未来展望
-
本文转自微信公众号:高可用架构作者:杨尚刚引用美图公司数据库高级DBA,负责美图后端数据存储平台建设和架构设计。前新浪高级数据库工程师,负责新浪微博核心数据库架构改造优化,以及数据库相关的服务器存...
- MySQL系列-源码编译安装(v8.0.25)
-
一、前言生产环境建议使用二进制安装法,其优点是部署简单、快速、方便,并且相对"yum/rpm安装"方法能更方便地自定义文件存放的目录结构,方便用脚本批量部署,方便日后运维管理。在生产...
- MySQL如何实时同步数据到ES?试试这款阿里开源的神器!
-
前几天在网上冲浪的时候发现了一个比较成熟的开源中间件——Canal。在了解了它的工作原理和使用场景后,顿时产生了浓厚的兴趣。今天,就让我们跟随我的脚步,一起来揭开它神秘的面纱吧。简介canal翻译为...
- 技术老兵十年专攻MySQL:编写了763页核心总结,90%MySQL问题全解
-
MySQL是开放源码的关系数据库管理系统,由于性能高、成本低、可靠性好,成为现在最流行的开源数据库。MySQL学习指南笔记领取方式:关注、转发后私信小编【111】即可免费获得《MySQL进阶笔记》的...
- Mysql和Hive之间通过Sqoop进行数据同步
-
文章回顾理论大数据框架原理简介大数据发展历程及技术选型实践搭建大数据运行环境之一搭建大数据运行环境之二本地MAC环境配置CPU数和内存大小查看CPU数sysctl machdep.cpu...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
系统C盘清理:微信PC端文件清理,扩大C盘可用空间步骤
-
飞牛OS入门安装遇到问题,如何解决?
-
10款高性能NAS丨双十一必看,轻松搞定虚拟机、Docker、软路由
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)