首页 时政热点 科技头条 智能AI 安全攻防 数码硬件 开发者生态 归档 标签 关于

每一毫秒都很重要

2026-09-09 1 阅读 约6分钟阅读 theanonymousone
分享:
字号:
想象一下,您加入一个具有明确目标的新项目,提高 ClickHouse 性能,以便我们最大的客户可以在合理的时间内运行他们的查询。这是你以前做过很多次的事情。您要求最昂贵的查询,因为它通常会产生最大的影响和一些容易实现的改进。你运行它,需要一分多钟才能完成。一组 1 行。经过:85.715 秒。处理了 19.6 亿行,198.69 GB 峰值内存使用量:23.05 GiB。该查询是并行运行的 12 个查询之一,每个查询都计算自己的指标或迷你图。它们位于客户端访问应用程序时登陆的第一个屏幕中。想象一下,客户在登录后至少会盯着旋转器几分钟。这是关于我们在过去四个月中为将该查询延迟降至亚秒级而做出的所有更改、学习和优化的说明。改变是简单且渐进的。它们都不是聪明的技巧,只是相互结合的基本改进。替换MergeTree 并不有趣 设置很简单,事件直接从 Kafka 写入 ClickHouse 表。事件是可变的,任何给定时间点的任何事件都可以更新或删除 1 。因此,改变过去历史的事件的接收速度与新事件相似甚至更快。这正是基于不可变存储的数据库所不具备的。我们被迫使用 ReplacingMergeTree 引擎并大量使用 FINAL ,这就是大部分时间的用途。最重要的是,有一些指标需要遍历客户端事件的整个历史记录才能计算。历史总是在增加,因为他们每天都保持活动并产生事件。因此,本文中的某些数字可能会有所不同。由于一次优化和下一次优化之间接收到的数据量,将查询从 200GB 提高到 150GB 的优化稍后可能会读取 210GB。奇怪的分区键 该表最初是按月分区的: PARTITION BY toYYYYMM(event_created_at) 这是显而易见的选择,但对于这种情况来说,在很多方面都是错误的。事件成批出现,这些批次会影响任何时间点。单个批次将包含来自多个月的事件,每次插入写入大量文件并导致通常的滚雪球效应:增加实时查询必须读取的文件数量,增加合并压力等。此外,FINAL 将行合并在一起,其并行性在很大程度上取决于键在分区之间的分布方式以及它们如何重叠,最坏的情况是单个线程执行最终合并并花费大量时间。按时间分区意味着客户端的事件分布在表中的每个分区中,因此 ClickHouse 必须花费大量时间来构建管道和分割范围以减少交集。此分区导致最终由单个线程执行的性能影响的示例: 使用单线程的最终性能 使用正确并行性的最终性能 为了解决这两个问题,同时获得一点性能,我们应用了以下分区策略: PARTITION BY (client_customer_id % 36 ) 请注意,client_customer_id 不是我们客户端的 id,而是我们客户端的客户端 id,我们在查询中不使用此列。该策略是违反直觉的 3,但它非常适合我们的用例:通过“仅”为每个插入创建 36 个零件来控制插入性能 分区均匀分布。不存在包含鲸鱼客户端的巨大分区,并且会减慢登陆同一分区的每个客户端的查询确保最终并行性,而不需要 ClickHouse 计算范围交集。这意味着我们可以禁用 split_parts_ranges_into_intersecting_and_non_intersecting_final 和 split_intersecting_parts_ranges_into_layers_final ,从而在数据管道构建步骤上节省几秒钟的时间。我们完全放弃分区修剪,因为我们从不按 client_customer_id 进行过滤。这是有成本的,但对于必须读取客户完整历史记录的指标来说则不然。按月分区也没有修剪任何东西。如上所述,我们在并行性方面获益匪浅 Order By Slightly Changed 在检查每个 event_type 对每个指标的贡献时,我们发现其中一些没有贡献,但我们仍在读取和处理它们。它们约占表中行数的 16%。虽然差别不大,但明显的变化可以提高查询性能。将 event_type 提升到较早的位置不是 ALTER 。创建表时排序键是固定的,因此这意味着一个新表和完全重写。行为保持不变,因为 event_type 已经在键中,并且仅移动了其位置,因此 ReplacingMergeTree 仍会准确折叠之前折叠的行。更改后,我们看到查询性能有了显着提升:Before 4 1 row in set。经过:13.43 秒
这篇文章对您有帮助吗?

订阅66必读

每日精选科技资讯,直达你的邮箱