博客 / 工程

ClickHouse 24.2 版本发布

author avatar
ClickHouse 团队
2024 年 3 月 11 日 - 20 分钟阅读

您是否相信现在已经是三月了?!时光飞逝,但又一个月过去的好处是,我们又为您带来了一个 ClickHouse 版本,供您享用!

ClickHouse 24.2 版本包含 18 个新功能 🎁 18 项性能优化 🛷 49 个错误修复 🐛

新贡献者

与往常一样,我们特别欢迎 24.2 版本的所有新贡献者!ClickHouse 的受欢迎程度很大程度上归功于社区的贡献。看到社区不断壮大总是令人感到荣幸。

以下是新贡献者的名字

johnnymatthews, AlexeyGrezz, Aris Tritas, Charlie, Fille, HowePa, Joshua Hildred, Juan Madurga, Kirill Nikiforov, Nickolaj Jepsen, Nikolai Fedorovskikh, Pablo Musa, Ronald Bradford, YenchangChan, conicliu, jktng, mikhnenko, rogeryk, una, Кирилл Гарбар_

提示:如果您好奇我们如何生成此列表... 点击此处

如果您在这里看到您的名字,请联系我们...但我们也会在 Twitter 等平台上找到您。

您也可以查看演示文稿的幻灯片

好了,让我们进入功能介绍!

文件格式自动检测

由 Pavel Kruglov 贡献

在处理文件时,即使文件没有有效的扩展名,ClickHouse 也会自动检测文件类型。例如,以下文件 foo 包含 JSON Lines 格式的数据

$ cat foo

{"name": "John Doe", "age": 30, "city": "New York"}
{"name": "Jane Doe", "age": 25, "city": "Los Angeles"}
{"name": "Jim Beam", "age": 35, "city": "Chicago"}
{"name": "Jill Hill", "age": 28, "city": "Houston"}
{"name": "Jack Black", "age": 40, "city": "Philadelphia"}

让我们尝试使用 file 函数处理该文件

SELECT *
FROM file('foo')

┌─name───────┬─age─┬─city─────────┐
│ John Doe   │  30New York     │
│ Jane Doe   │  25 │ Los Angeles  │
│ Jim Beam   │  35 │ Chicago      │
│ Jill Hill  │  28 │ Houston      │
│ Jack Black │  40 │ Philadelphia │
└────────────┴─────┴──────────────┘

5 rows in set. Elapsed: 0.003 sec.

非常酷。现在让我们将内容写入 Parquet 格式

SELECT *
FROM file('foo')
INTO OUTFILE 'bar'
FORMAT Parquet

我们可以在不告诉 ClickHouse 格式的情况下读取它吗?

SELECT *
FROM file('bar')

┌─name───────┬─age─┬─city─────────┐
│ John Doe   │  30New York     │
│ Jane Doe   │  25 │ Los Angeles  │
│ Jim Beam   │  35 │ Chicago      │
│ Jill Hill  │  28 │ Houston      │
│ Jack Black │  40 │ Philadelphia │
└────────────┴─────┴──────────────┘

5 rows in set. Elapsed: 0.003 sec.

当然,我们可以!自动检测也适用于从 URL 读取的情况。因此,如果我们围绕上述文件启动本地 HTTP 服务器

python -m http.server

我们可以像这样读取它们

SELECT *
FROM url('https://127.0.0.1:8000/bar')

┌─name───────┬─age─┬─city─────────┐
│ John Doe   │  30New York     │
│ Jane Doe   │  25 │ Los Angeles  │
│ Jim Beam   │  35 │ Chicago      │
│ Jill Hill  │  28 │ Houston      │
│ Jack Black │  40 │ Philadelphia │
└────────────┴─────┴──────────────┘

现在示例就够了,但您也可以将此功能与 s3hdfsazureBlobStorage 表函数一起使用。

更漂亮的 Pretty 格式

RogerYK

如果您曾经需要快速解释查询结果中的大量数字,那么此功能适合您。当您返回单个数字列时,如果该列中的值大于 100 万,则可读数量将作为注释与值本身一起显示。

SELECT 765432198

┌─765432198─┐
│ 765432198-- 765.43 million
└───────────┘

视图的安全性

由 Artem Brustovetskii 贡献

在此版本之前,如果您在表上定义视图,用户要访问该视图,他们还需要具有对表的访问权限。这并不理想,在 24.2 版本中,我们为 CREATE VIEW 查询添加了 SQL SECURITYDEFINER 规范,以解决此问题。

假设我们有一个公司的工资表,其中包含员工姓名、部门、工资和地址详细信息。我们可能希望 HR 团队能够访问所有信息,但也允许其他用户查看员工姓名和部门。

让我们首先创建一个表并填充它

CREATE TABLE payroll (
  name String,
  address String,
  department LowCardinality(String),
  salary UInt32
)
Engine = MergeTree
ORDER BY name;
INSERT INTO payroll (`name`, `address`, `department`, `salary`) VALUES
('John Doe', '123 Maple Street, Anytown, AT 12345', 'HR', 50000),
('Jane Smith', '456 Oak Road, Sometown, ST 67890', 'Marketing', 55000),
('Emily Jones', '789 Pine Lane, Thistown, TT 11223', 'IT', 60000),
('Michael Brown', '321 Birch Blvd, Othertown, OT 44556', 'Sales', 52000),
('Sarah Davis', '654 Cedar Ave, Newcity, NC 77889', 'HR', 53000),
('Daniel Wilson', '987 Elm St, Oldtown, OT 99000', 'IT', 62000),
('Laura Martinez', '123 Spruce Way, Mytown, MT 22334', 'Marketing', 56000),
('James Garcia', '456 Fir Court, Yourtown, YT 33445', 'Sales', 51000);

我们有两个用户 - Alice,她在 HR 团队,Bob,他在工程团队。Alice 在 HR 团队,可以访问工资表,Bob 则不能!

CREATE USER alice IDENTIFIED WITH sha256_password BY 'alice';
GRANT SELECT ON default.payroll TO alice;
GRANT SELECT ON default.employees TO alice  WITH GRANT OPTION;

CREATE USER bob IDENTIFIED WITH sha256_password BY 'bob';

Alice 创建了一个名为 employees 的视图

CREATE VIEW employees	
DEFINER = alice SQL SECURITY DEFINER
AS 
SELECT name, department
FROM payroll;

然后 Alice 将该视图的访问权限授予她自己和 Bob

GRANT SELECT ON default.employees TO alice;
GRANT SELECT ON default.employees TO bob;

如果我们然后以 Bob 的身份登录

clickhouse client -u bob

我们可以查询 employees

SELECT *
FROM employees

┌─name───────────┬─department─┐
│ Daniel Wilson  │ IT         │
│ Emily Jones    │ IT         │
│ James Garcia   │ Sales      │
│ Jane Smith     │ Marketing  │
│ John Doe       │ HR         │
│ Laura Martinez │ Marketing  │
│ Michael Brown  │ Sales      │
│ Sarah Davis    │ HR         │
└────────────────┴────────────┘

但他无法查询底层的工资表,这正是我们所期望的

SELECT *
FROM payroll

Received exception from server (version 24.3.1):
Code: 497. DB::Exception: Received from localhost:9000. DB::Exception: bob: Not enough privileges. To execute this query, it's necessary to have the grant SELECT(name, address, department, salary) ON default.payroll. (ACCESS_DENIED)

向量化距离函数

由 Robert Schulze 贡献

在最近的博客文章中,我们探讨了当用户需要高性能线性扫描以获得准确结果和/或能够通过 SQL 将向量搜索与元数据过滤和聚合结合使用时,ClickHouse 如何用作向量数据库。用户可以使用这些功能,通过检索增强生成 (RAG) 管道为基于 LLM 的应用程序提供上下文。我们对向量搜索底层支持的投入仍在继续,最近的工作重点是提高线性扫描的性能 - 特别是计算两个向量之间距离的 距离函数系列。有关背景信息,我们推荐 这篇文章 和我们自己的 Mark 最近的一个视频

虽然对于较大的数据集,向量搜索的查询性能很容易受到 I/O 限制,但许多用户只需要使用 SQL 搜索适合内存的较小数据集。在这些情况下,ClickHouse 中的性能可能会受到 CPU 限制。因此,确保此代码正确向量化并使用最新的指令集可以提供显着的改进,并将性能提升到受内存带宽限制的水平 - 对于配备 DDR-5 的机器来说,这可能意味着更高的扫描性能。

在 24.2 版本中,我们很高兴地宣布 cosineDistance、dotProduct 和 L2Distance(欧几里得距离)函数都已得到优化,可以利用最新的指令集。对于 x86 架构,这意味着使用融合乘加 (FMA) 和水平加法归约操作以及 AVX-512 指令和 ARM 的自动向量化。

作为潜在改进的一个例子,请考虑 glove 数据集,该数据集在几乎官方的 ANN 基准测试中广受欢迎。这个特定的子集,我们已将其 以 Parquet 格式提供 (2.5GiB),由从 840B CommonCrawl 令牌训练的 210 万个向量组成。此集合中的每个向量都有 300 个维度,代表一个单词。鉴于简单的架构,这需要几秒钟才能加载到 ClickHouse 中

CREATE TABLE glove
(
  `word` String,
  `vector` Array(Float32)
)
ENGINE = MergeTree
ORDER BY word;
INSERT INTO glove SELECT *
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/glove/glove_840b_300d.parquet')

0 rows in set. Elapsed: 49.779 sec. Processed 2.20 million rows, 2.66 GB (44.12 thousand rows/s., 53.44 MB/s.)
Peak memory usage: 1.03 GiB.

机器规格:i3en.3xlarge - 12vCPU Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz, 96GiB RAM

我们可以通过查找与特定单词的向量最接近的单词来比较 ClickHouse 版本之间的性能。例如,在 23.12 版本中

WITH 'dog' AS search_term,
(
  SELECT vector
  FROM glove
  WHERE word = search_term
  LIMIT 1
) AS target_vector
SELECT word, cosineDistance(vector, target_vector) AS score
FROM glove
WHERE lower(word) != lower(search_term)
ORDER BY score ASC
LIMIT 5

┌─word────┬──────score─┐
│ dogs    │ 0.11640692 │
│ puppy   │ 0.14147866 │
│ pet     │ 0.19425482 │
│ cat     │ 0.19831467 │
│ puppies │ 0.24826884 │
└─────────┴────────────┘

5 rows in set. Elapsed: 0.407 sec. Processed 2.14 million rows, 2.60 GB (5.25 million rows/s., 6.38 GB/s.)
Peak memory usage: 248.94 MiB.

重要的是,此数据集适合 FS 缓存,如其在磁盘上的压缩大小所示

SELECT
	name,
	formatReadableSize(sum(data_compressed_bytes)) AS compressed_size,
	formatReadableSize(sum(data_uncompressed_bytes)) AS uncompressed_size,
	round(sum(data_uncompressed_bytes) / sum(data_compressed_bytes), 2) AS ratio
FROM system.columns
WHERE table LIKE 'glove'
GROUP BY name
ORDER BY name DESC

┌─name───┬─compressed_size─┬─uncompressed_size─┬─ratio─┐
│ word   │ 13.41 MiB       │ 18.82 MiB         │   1.4 │
│ vector │ 2.46 GiB        │ 2.47 GiB          │     1 │
└────────┴─────────────────┴───────────────────┴───────┘

2 rows in set. Elapsed: 0.003 sec.

单词和向量列的总大小约为 2.65 GB,完全适合内存。

在 24.2 版本中,相同的查询提高了 25% 以上

5 rows in set. Elapsed: 0.286 sec. Processed 1.91 million rows, 2.32 GB (6.68 million rows/s., 8.12 GB/s.)
Peak memory usage: 216.89 MiB.

这些差异会因处理器、数据集大小、向量基数和 RAM 性能而异。

关于点积的小提示

在 24.2 版本之前,dotProduct 函数 未向量化。在确保尽可能高效的同时,我们注意到该函数还执行了任何 const 参数的必要解包(最常见的用例,即我们传递一个常量向量进行比较),这导致了不必要的内存复制。这意味着实际的函数运行时主要受内存操作支配 - 在这种情况下,向量化带来的收益相对较小。一旦 消除后,性能在自动化基准测试中惊人地提高了 270 倍

虽然将此函数的性能与以前的版本进行比较是不值得的(它们有点令人尴尬 :)),但我们认为我们应该借此机会指出用户现在可以利用的一个很好的性能优化。

读者可能还记得,余弦距离和点积密切相关。更具体地说,余弦距离衡量多维空间中两个向量之间角度的余弦值。它源自余弦相似度,余弦距离定义为 1 - 余弦相似度。余弦相似度计算为向量的点积除以其幅度的乘积,即 1- ((a.b)/||a||||b||)。相反,点积衡量两个数字序列(即向量 A 和 B,分量为 aibi)的对应项乘积之和,点积为 A⋅B=∑ai​bi​

当您对向量进行归一化时(即,当两个向量的幅度都为 1 时),余弦距离和点积变得特别相关。在这种情况下,余弦相似度正好是点积,因为余弦相似度公式中的分母(向量的幅度)都为 1,因此被抵消了。因此,我们的余弦距离简单地变为 1-(a.b)

我们如何利用这一点?

我们可以在插入时使用 L2Norm 函数(向量幅度)对向量进行归一化,从而允许我们在查询时使用 dotProduct 函数。这样做的主要动机是 dotProduct 函数在计算上更简单(我们不必计算每个向量的幅度),因此可能会节省一些查询时间。

要在查询时执行归一化

INSERT INTO glove
SETTINGS schema_inference_make_columns_nullable = 0
SELECT
	word,
	vector / L2Norm(vector) AS vector
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/glove/glove_840b_300d.parquet')
SETTINGS schema_inference_make_columns_nullable = 0

0 rows in set. Elapsed: 51.699 sec. Processed 2.20 million rows, 2.66 GB (42.48 thousand rows/s., 51.46 MB/s.)

我们之前的查询因此变为

WITH
	'dog' AS search_term,
	(
    	SELECT vector
    	FROM glove
    	WHERE word = search_term
    	LIMIT 1
	) AS target_vector
SELECT
	word,
	1 - dotProduct(vector, target_vector) AS score
FROM glove
WHERE lower(word) != lower(search_term)
ORDER BY score ASC
LIMIT 5

┌─────────┬─────────────────────┐
│ word    │ score               │
├─────────┼─────────────────────┤
│ dogs    │ 0.11640697717666626 │
│ puppy   │ 0.1414787769317627  │
│ pet     │ 0.19425475597381592 │
│ cat     │ 0.19831448793411255 │
│ puppies │ 0.24826878309249878 │
└─────────┴─────────────────────┘


5 rows in set. Elapsed: 0.262 sec. Processed 1.99 million rows, 2.42 GB (7.61 million rows/s., 9.25 GB/s.)
Peak memory usage: 226.29 MiB.

此查询花费 0.262 秒,而使用余弦相似度时花费 0.286 秒 - 与我们的原始查询相比,节省的时间很少,但每一毫秒都很重要!

有一个 PR 仍在等待合并,它将修复 dotProduct 函数的正确性和性能,应该很快就会合并。

自适应异步插入

由 Julia Kartseva 贡献

使用传统的插入查询,数据 同步 插入到表中:当 ClickHouse 接收到查询时,数据会立即以数据部分的形式写入数据库存储。为了获得最佳性能,数据需要批量处理,通常,我们应避免过于频繁地创建太多小插入。

异步插入将数据批处理从客户端转移到服务器端:来自插入查询的数据首先插入到缓冲区中,然后稍后或 异步 写入数据库存储。这非常方便,尤其是在许多并发客户端频繁地向表中插入数据的情况下,数据应实时分析,并且客户端批处理引起的延迟是不可接受的。对于 示例,可观察性用例通常涉及数千个监控代理持续发送少量事件和指标数据。此类场景可以使用异步插入模式,如下图所示

01.png

在上图的示例场景中,在一段时间内没有针对特定表的插入活动之后,ClickHouse 接收到针对该表的异步插入查询 1。接收到插入查询 1 后,查询的数据将插入到内存缓冲区中,并且默认的 缓冲区刷新超时 计数器开始计数。在计数器结束之前,因此缓冲区被刷新之前,来自同一客户端或其他客户端的其他异步插入查询的数据可以收集在缓冲区中。刷新缓冲区将在磁盘上创建一个数据部分,其中包含在刷新之前接收到的所有插入查询的组合数据。

请注意,使用 默认返回行为,所有插入查询仅在缓冲区刷新发生后才返回对发送方的插入确认。换句话说,发送插入查询的客户端调用在下一次缓冲区刷新发生之前会被 阻止。因此,不频繁的插入具有更高的延迟。我们在下面草拟了这一点

02.png

上面的极端情况场景显示了不频繁的插入查询。ClickHouse 在一段时间内没有针对表的插入活动后,接收到异步插入查询 1。这会触发一个新的缓冲区刷新周期,并使用默认的 缓冲区刷新超时,这意味着该查询的发送方需要等待完整的默认缓冲区刷新时间(OSS 为 200 毫秒,ClickHouse Cloud 为 1000 毫秒),然后才能收到插入确认。同样,插入查询 2 的发送方也会遇到较高的插入延迟。

在 ClickHouse 24.2 中引入的 自适应异步插入缓冲区刷新超时 通过使用自适应算法自动调整缓冲区刷新超时来解决此问题,该算法基于插入频率

03.png

对于之前显示的一些不频繁插入查询的极端情况场景,缓冲区刷新超时计数器现在以 最小值(50 毫秒)开始。因此,这些查询中的数据会更快地写入磁盘。几乎立即将少量数据写入磁盘是可以的,因为频率很低。因此,不存在 Too many parts 安全措施 启动的风险。

频繁插入的工作方式与以前相同。它们将被延迟并组合在一起

04.png

当 ClickHouse 在一段时间内没有针对表的插入活动后接收到插入查询 1 时,缓冲区刷新超时计数器将以 最小值 开始,当频繁发生其他插入时,该值会自动 向上 调整到 最大值 (并且当插入频率降低时也会 向下 调整)。

总而言之,频繁插入的工作方式与以前相同。它们将被延迟并组合在一起。但是,不频繁的插入不会被延迟太多,并且表现得像同步插入。您只需启用异步插入,即可停止担心。 😀

让我们用一个例子来演示这一点。为此,我们启动一个 ClickHouse 24.2 实例,并从我们的 UpClick 可观察性示例应用程序 中创建一个简化版本的表

CREATE TABLE default.upclick_metrics (
    `url` String,
    `status_code` UInt8,
    `city_name_en` String
) ENGINE = MergeTree
ORDER BY (url, status_code, city_name_en);

现在我们可以使用这个简单的 Python 脚本(使用 ClickHouse Connect Python 驱动程序)向我们的 ClickHouse 实例发送 10 个小的异步插入。请注意,我们通过将 wait_for_async_insert 设置为 1 来显式启用 默认返回行为,并将缓冲区刷新超时增加到 2 秒(以更好地演示自适应异步插入)。

import clickhouse_connect
import time

client = clickhouse_connect.get_client(...)

for _ in range(10):
    start_time = time.time()
    client.insert(
        database='default',
        table='upclick_metrics',
        data=[['clickhouse.com', 200, 'Amsterdam']],
        column_names=['url', 'status_code', 'city_name_en'],
        settings={
            'async_insert':1,
            'wait_for_async_insert':1,
            'async_insert_busy_timeout_ms':2000,
            'async_insert_use_adaptive_busy_timeout':0}
    )
    end_time = time.time()
    print(str(round(end - start, 2)) + ' seconds')
    time.sleep(1)

该脚本测量并打印每次插入的插入延迟(以秒为单位),然后休眠一秒钟以模拟不频繁的插入。

我们运行禁用自适应异步插入缓冲区刷新超时的脚本(async_insert_use_adaptive_busy_timeout 设置为 0),输出为

2.03 seconds
2.02 seconds
2.03 seconds
2.02 seconds
2.04 seconds
2.02 seconds
2.03 seconds
2.03 seconds
2.03 seconds
2.03 seconds

我们可以看到不频繁插入的高延迟,为 2 秒。

但是,当我们运行启用自适应异步插入缓冲区刷新超时的脚本(async_insert_use_adaptive_busy_timeout 设置为 1),并且所有其他设置与上面的运行相同时,输出为

0.07 seconds
0.06 seconds
0.08 seconds
0.08 seconds
0.07 seconds
0.07 seconds
0.07 seconds
0.08 seconds
0.07 seconds
0.08 seconds

不频繁插入的延迟大大降低。

由于 Python 驱动程序机制、服务器端查询解析以及将查询发送到 ClickHouse 的时间等额外的延迟开销,我们在这里看不到精确的 最小缓冲区刷新超时 50 毫秒。

分享这篇文章

订阅我们的新闻邮件

随时了解功能发布、产品路线图、支持和云产品!
正在加载表单...
关注我们
X imageSlack imageGitHub image
Telegram imageMeetup imageRss image
©2025ClickHouse, Inc. 总部位于加利福尼亚州湾区和荷兰阿姆斯特丹。