DoubleCloud 即将停止运营。迁移到 ClickHouse,限时免费迁移服务。立即联系我们 ->->

博客 / 工程

ClickHouse 24.5 版本

author avatar
ClickHouse 团队
2024 年 6 月 5 日

ClickHouse 24.5 版本包含 **19 个新功能** 🎁 **20 个性能优化** 🛷 **68 个错误修复** 🐛

新贡献者

一如既往,我们向 24.5 版本的所有新贡献者表示热烈欢迎!ClickHouse 的流行在很大程度上归功于贡献社区的努力。看到社区不断壮大,我们总是感到欣慰。

以下列出了新贡献者的姓名

Alex Katsman、Alexey Petrunyaka、Ali、Caio Ricciuti、Danila Puzov、Evgeniy Leko、Francisco Javier Jurado Moreno、Gabriel Martinez、Grégoire Pineau、KenL、Leticia Webb、Mattias Naarttijärvi、Maxim Alexeev、Michael Stetsyuk、Pazitiff9、Sariel、TTPO100AJIEX、Tomer Shafir、Vinay Suryadevara、Volodya、Volodya Giro、Volodyachan、Xiaofei Hu、ZhiHong Zhang、Zimu Li、匿名、[email protected]、p1rattttt、pet74alex、qiangxuhui、sarielwxm、v01dxyz、vinay92-ch、woodlzm、zhou、zzyReal666

提示:如果您想知道我们如何生成此列表…… 点击这里

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

动态数据类型

贡献者:Pavel Kruglov

此版本引入了一种新的实验性数据类型,用于处理半结构化数据。Dynamic 数据类型类似于 24.1 版本中引入的 Variant 数据类型,但您无需预先指定接受的数据类型。

例如,如果我们想要允许 StringUInt64Array(String) 值,我们需要定义一个 Variant(String, UInt64, Array(String)) 列类型。动态数据类型的等效项将是 Dynamic,并且如果需要,我们可以添加任何其他类型的值。

如果您想使用此数据类型,则需要设置以下配置参数

SET allow_experimental_dynamic_type = 1;

假设我们有一个名为 sensors.json 的文件,其中包含传感器读数。这些读数并非始终如一地收集,这意味着我们拥有各种类型的值。

{"sensor_id": 1, "reading_time": "2024-06-05 12:00:00", "reading": 23.5}
{"sensor_id": 2, "reading_time": "2024-06-05 12:05:00", "reading": "OK"}
{"sensor_id": 3, "reading_time": "2024-06-05 12:10:00", "reading": 100}
{"sensor_id": 4, "reading_time": "2024-06-05 12:15:00", "reading": "62F"}
{"sensor_id": 5, "reading_time": "2024-06-05 12:20:00", "reading": 45.7}
{"sensor_id": 6, "reading_time": "2024-06-05 12:25:00", "reading": "ERROR"}
{"sensor_id": 7, "reading_time": "2024-06-05 12:30:00", "reading": "22.5C"}

这看起来像是 Dynamic 类型的良好用例,因此让我们创建一个表

CREATE TABLE sensor_readings (
    sensor_id UInt32,
    reading_time DateTime,
    reading Dynamic
) ENGINE = MergeTree()
ORDER BY (sensor_id, reading_time);

现在,我们可以运行以下查询来摄取数据

INSERT INTO sensor_readings
select * FROM 'sensors.json';

接下来,让我们查询该表,返回所有列,以及存储在 reading 列中的值的底层类型

SELECT
    sensor_id,
    reading_time,
    reading,
    dynamicType(reading) AS type
FROM sensor_readings

Query id: eb6bf220-1c08-42d5-8e9d-1f77247897c3

   ┌─sensor_id─┬────────reading_time─┬─reading─┬─type────┐
1.12024-06-05 12:00:0023.5Float642.22024-06-05 12:05:00OKString3.32024-06-05 12:10:00100Int644.42024-06-05 12:15:00 │ 62F     │ String5.52024-06-05 12:20:0045.7Float646.62024-06-05 12:25:00ERRORString7.72024-06-05 12:30:0022.5C   │ String  │
   └───────────┴─────────────────────┴─────────┴─────────┘

如果我们只想检索使用特定类型的行,可以使用 dynamicElement 函数或等效的点语法

SELECT
    sensor_id, reading_time, 
    dynamicElement(reading, 'Float64') AS f1, 
    reading.Float64 AS f2,
    dynamicElement(reading, 'Int64') AS f3, 
    reading.Int64 AS f4
FROM sensor_readings;

Query id: add6fbca-6dcd-4413-9f1f-0566c94c1aab

   ┌─sensor_id─┬────────reading_time─┬───f1─┬───f2─┬───f3─┬───f4─┐
1.12024-06-05 12:00:0023.523.5 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │
2.22024-06-05 12:05:00 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │
3.32024-06-05 12:10:00 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │  1001004.42024-06-05 12:15:00 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │
5.52024-06-05 12:20:0045.745.7 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │
6.62024-06-05 12:25:00 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │
7.72024-06-05 12:30:00 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │
   └───────────┴─────────────────────┴──────┴──────┴──────┴──────┘

如果我们想在类型为 Int64Float64 时计算 reading 列的平均值,我们可以编写以下查询

SELECT avg(assumeNotNull(reading.Int64) + assumeNotNull(reading.Float64)) AS avg
FROM sensor_readings
WHERE dynamicType(reading) != 'String'

Query id: bb3fd8a6-07b3-4384-bd82-7515b7a1706f

   ┌──avg─┐
1.56.4 │
   └──────┘

动态类型是更长期项目的一部分,该项目旨在 在 ClickHouse 中添加半结构化列

从 S3 上的存档中读取

贡献者:Dan Ivanik

从 23.8 版本开始,就可以 从存档文件中的文件读取数据(例如,zip/tar),只要存档文件位于您的本地文件系统中。在 24.5 版本中,该功能扩展到位于 S3 上的存档文件。

让我们通过查询包含 CSV 文件的 ZIP 文件来了解其工作原理。我们将首先编写一个列出嵌入式 CSV 文件的查询

SELECT _path
FROM s3('s3://umbrella-static/top-1m-2024-01-*.csv.zip :: *.csv', One)
ORDER BY _path ASC

Query id: 07de510c-229f-4223-aeb9-b2cd36224228

    ┌─_path─────────────────────────────────────────────────┐
 1. │ umbrella-static/top-1m-2024-01-01.csv.zip::top-1m.csv │
 2. │ umbrella-static/top-1m-2024-01-02.csv.zip::top-1m.csv │
 3. │ umbrella-static/top-1m-2024-01-03.csv.zip::top-1m.csv │
 4. │ umbrella-static/top-1m-2024-01-04.csv.zip::top-1m.csv │
 5. │ umbrella-static/top-1m-2024-01-05.csv.zip::top-1m.csv │
 6. │ umbrella-static/top-1m-2024-01-06.csv.zip::top-1m.csv │
 7. │ umbrella-static/top-1m-2024-01-07.csv.zip::top-1m.csv │
 8. │ umbrella-static/top-1m-2024-01-08.csv.zip::top-1m.csv │
 9. │ umbrella-static/top-1m-2024-01-09.csv.zip::top-1m.csv │
10. │ umbrella-static/top-1m-2024-01-10.csv.zip::top-1m.csv │
11. │ umbrella-static/top-1m-2024-01-11.csv.zip::top-1m.csv │
12. │ umbrella-static/top-1m-2024-01-12.csv.zip::top-1m.csv │
13. │ umbrella-static/top-1m-2024-01-13.csv.zip::top-1m.csv │
14. │ umbrella-static/top-1m-2024-01-14.csv.zip::top-1m.csv │
15. │ umbrella-static/top-1m-2024-01-15.csv.zip::top-1m.csv │
16. │ umbrella-static/top-1m-2024-01-16.csv.zip::top-1m.csv │
17. │ umbrella-static/top-1m-2024-01-17.csv.zip::top-1m.csv │
18. │ umbrella-static/top-1m-2024-01-18.csv.zip::top-1m.csv │
19. │ umbrella-static/top-1m-2024-01-19.csv.zip::top-1m.csv │
20. │ umbrella-static/top-1m-2024-01-20.csv.zip::top-1m.csv │
21. │ umbrella-static/top-1m-2024-01-21.csv.zip::top-1m.csv │
22. │ umbrella-static/top-1m-2024-01-22.csv.zip::top-1m.csv │
23. │ umbrella-static/top-1m-2024-01-23.csv.zip::top-1m.csv │
24. │ umbrella-static/top-1m-2024-01-24.csv.zip::top-1m.csv │
25. │ umbrella-static/top-1m-2024-01-25.csv.zip::top-1m.csv │
26. │ umbrella-static/top-1m-2024-01-26.csv.zip::top-1m.csv │
27. │ umbrella-static/top-1m-2024-01-27.csv.zip::top-1m.csv │
28. │ umbrella-static/top-1m-2024-01-28.csv.zip::top-1m.csv │
29. │ umbrella-static/top-1m-2024-01-29.csv.zip::top-1m.csv │
30. │ umbrella-static/top-1m-2024-01-30.csv.zip::top-1m.csv │
31. │ umbrella-static/top-1m-2024-01-31.csv.zip::top-1m.csv │
    └───────────────────────────────────────────────────────┘

因此,我们有 31 个文件可以使用。接下来,让我们计算这些文件中存在多少行数据

SELECT
    _path,
    count()
FROM s3('s3://umbrella-static/top-1m-2024-01-*.csv.zip :: *.csv', CSV)
GROUP BY _path

Query id: 07de510c-229f-4223-aeb9-b2cd36224228

    ┌─_path─────────────────────────────────────────────────┬─count()─┐
 1. │ umbrella-static/top-1m-2024-01-21.csv.zip::top-1m.csv │ 1000000 │
 2. │ umbrella-static/top-1m-2024-01-07.csv.zip::top-1m.csv │ 1000000 │
 3. │ umbrella-static/top-1m-2024-01-30.csv.zip::top-1m.csv │ 1000000 │
 4. │ umbrella-static/top-1m-2024-01-16.csv.zip::top-1m.csv │ 1000000 │
 5. │ umbrella-static/top-1m-2024-01-10.csv.zip::top-1m.csv │ 1000000 │
 6. │ umbrella-static/top-1m-2024-01-27.csv.zip::top-1m.csv │ 1000000 │
 7. │ umbrella-static/top-1m-2024-01-01.csv.zip::top-1m.csv │ 1000000 │
 8. │ umbrella-static/top-1m-2024-01-29.csv.zip::top-1m.csv │ 1000000 │
 9. │ umbrella-static/top-1m-2024-01-13.csv.zip::top-1m.csv │ 1000000 │
10. │ umbrella-static/top-1m-2024-01-24.csv.zip::top-1m.csv │ 1000000 │
11. │ umbrella-static/top-1m-2024-01-02.csv.zip::top-1m.csv │ 1000000 │
12. │ umbrella-static/top-1m-2024-01-22.csv.zip::top-1m.csv │ 1000000 │
13. │ umbrella-static/top-1m-2024-01-18.csv.zip::top-1m.csv │ 1000000 │
14. │ umbrella-static/top-1m-2024-01-04.csv.zip::top-1m.csv │ 1000001 │
15. │ umbrella-static/top-1m-2024-01-15.csv.zip::top-1m.csv │ 1000000 │
16. │ umbrella-static/top-1m-2024-01-09.csv.zip::top-1m.csv │ 1000000 │
17. │ umbrella-static/top-1m-2024-01-06.csv.zip::top-1m.csv │ 1000000 │
18. │ umbrella-static/top-1m-2024-01-20.csv.zip::top-1m.csv │ 1000000 │
19. │ umbrella-static/top-1m-2024-01-17.csv.zip::top-1m.csv │ 1000000 │
20. │ umbrella-static/top-1m-2024-01-31.csv.zip::top-1m.csv │ 1000000 │
21. │ umbrella-static/top-1m-2024-01-11.csv.zip::top-1m.csv │ 1000000 │
22. │ umbrella-static/top-1m-2024-01-26.csv.zip::top-1m.csv │ 1000000 │
23. │ umbrella-static/top-1m-2024-01-12.csv.zip::top-1m.csv │ 1000000 │
24. │ umbrella-static/top-1m-2024-01-28.csv.zip::top-1m.csv │ 1000000 │
25. │ umbrella-static/top-1m-2024-01-03.csv.zip::top-1m.csv │ 1000001 │
26. │ umbrella-static/top-1m-2024-01-25.csv.zip::top-1m.csv │ 1000000 │
27. │ umbrella-static/top-1m-2024-01-05.csv.zip::top-1m.csv │ 1000000 │
28. │ umbrella-static/top-1m-2024-01-19.csv.zip::top-1m.csv │ 1000000 │
29. │ umbrella-static/top-1m-2024-01-23.csv.zip::top-1m.csv │ 1000000 │
30. │ umbrella-static/top-1m-2024-01-08.csv.zip::top-1m.csv │ 1000000 │
31. │ umbrella-static/top-1m-2024-01-14.csv.zip::top-1m.csv │ 1000000 │
    └───────────────────────────────────────────────────────┴─────────┘

每个文件大约有 100 万行。让我们看看其中的一些行。我们可以使用 DESCRIBE 子句来了解数据的结构

DESCRIBE TABLE s3('s3://umbrella-static/top-1m-2024-01-*.csv.zip :: *.csv', CSV)
SETTINGS describe_compact_output = 1

Query id: d5afed03-6c51-40f4-b457-1065479ef1a8

   ┌─name─┬─type─────────────┐
1. │ c1   │ Nullable(Int64)  │
2. │ c2   │ Nullable(String) │
   └──────┴──────────────────┘

最后,让我们看看其中的一些行本身

SELECT *
FROM s3('s3://umbrella-static/top-1m-2024-01-*.csv.zip :: *.csv', CSV)
LIMIT 5

Query id: bd0d9bd9-19cb-4b2e-9f63-6b8ffaca85b1

   ┌─c1─┬─c2────────────────────────┐
1.1 │ google.com                │
2.2 │ microsoft.com             │
3.3 │ data.microsoft.com        │
4.4 │ events.data.microsoft.com │
5.5 │ netflix.com               │
   └────┴───────────────────────────┘

CROSS JOIN 改进

贡献者:Maksim Alekseev

上一个版本已经 带来了 重大的 JOIN 性能改进。从现在开始,您实际上将在每个 ClickHouse 版本中看到 JOIN 改进。🚀

在此版本中,我们重点关注 CROSS JOIN 的内存使用情况改进。

提醒一下,CROSS JOIN 生成两个表的完全 笛卡尔积,不考虑联接键。这意味着来自左侧表的每一行都与来自右侧表的每一行组合。

因此,CROSS JOIN 的实现未利用 哈希表。相反,① ClickHouse 将右侧表的所有 加载到主内存中,然后,② 针对左侧表的每一行,联接所有右侧表行

CROSS_JOIN_01.png

现在,使用 ClickHouse 24.5,来自右侧表的块可以根据需要以压缩 (LZ4) 格式加载到主内存中,或者如果它们不适合内存,则可以暂时写入磁盘。

下图显示了如何根据两个新的阈值设置 - cross_join_min_rows_to_compresscross_join_min_bytes_to_compress - ClickHouse 将压缩 (LZ4) 的右侧表块加载到主内存中,然后再执行 CROSS JOIN

CROSS_JOIN_02.png

为了演示这一点,我们从 公共 PyPI 下载统计信息 中加载 10 亿行到 ClickHouse 24.424.5 中的表中

CREATE TABLE pypi_1b
(
    `timestamp` DateTime,
    `country_code` LowCardinality(String),
    `url` String,
    `project` String
)
ORDER BY (country_code, project, url, timestamp);

INSERT INTO pypi_1b
SELEC timestamp, country_code, url, project
FROM s3('https://storage.googleapis.com/clickhouse_public_datasets/pypi/file_downloads/sample/2023/{0..61}-*.parquet');

现在,我们使用 ClickHouse 24.4 运行一个 CROSS JOIN。请注意,我们使用只有一行的临时表作为左侧表,因为我们只关心查看将右侧表行加载到主内存中的内存使用情况

SELECT
    country_code,
    project
FROM
(
    SELECT 1
) AS L, pypi_1b AS R
ORDER BY (country_code, project) DESC
LIMIT 1000
FORMAT Null;

0 rows in set. Elapsed: 59.186 sec. Processed 1.01 billion rows, 20.09 GB (17.11 million rows/s., 339.42 MB/s.)
Peak memory usage: 51.77 GiB.

ClickHouse 使用了 **51.77 GiB** 的主内存来运行此 CROSS JOIN。

我们使用 ClickHouse 24.5 运行相同的 CROSS JOIN,但禁用压缩(通过将压缩阈值设置为 0

SELECT
    country_code,
    project
FROM
(
    SELECT 1
) AS L, pypi_1b AS R
ORDER BY (country_code, project) DESC
LIMIT 1000
FORMAT Null
SETTINGS 
    cross_join_min_bytes_to_compress = 0, 
    cross_join_min_rows_to_compress = 0;

0 rows in set. Elapsed: 39.419 sec. Processed 1.01 billion rows, 20.09 GB (25.69 million rows/s., 509.63 MB/s.)
Peak memory usage: 19.06 GiB.

如您所见,在 24.5 版本中,即使没有压缩右侧表块,CROSS JOIN 的内存使用情况也得到了优化:ClickHouse 使用了 **19.06 GiB** 的主内存,而不是上述查询的 51.77 GiB。此外,CROSS JOIN 的运行速度比上一个版本快 20 秒。

最后,我们使用 ClickHouse 24.5 运行示例 CROSS JOIN,并(默认情况下)启用压缩

SELECT
    country_code,
    project
FROM
(
    SELECT 1
) AS L, pypi_1b AS R
ORDER BY (country_code, project) DESC
LIMIT 1000
FORMAT Null;

0 rows in set. Elapsed: 69.311 sec. Processed 1.01 billion rows, 20.09 GB (14.61 million rows/s., 289.84 MB/s.)
Peak memory usage: 5.36 GiB.

由于右侧表的行数或数据大小超过了cross_join_min_rows_to_compresscross_join_min_bytes_to_compress 的阈值,ClickHouse 正在将右侧表中使用 LZ4 压缩的块加载到内存中,导致内存使用率为 **5.36 GiB**,这比在 24.4 中运行 CROSS JOIN 时使用的内存少约 **10 倍**。请注意,压缩开销会增加查询的运行时间。

此外,从本版本开始,如果右侧表块的大小超过了 max_bytes_in_joinmax_rows_in_join 的阈值,那么 ClickHouse 会将右侧表块从内存中溢出到磁盘。

CROSS_JOIN_03.png

我们通过使用 ClickHouse 24.5 运行示例 CROSS JOIN 并启用 test 级别的日志记录级别来演示这一点。请注意,我们禁用了压缩,并使用了有限的 max_rows_in_join 值来强制将右侧表块溢出到磁盘。

./clickhouse client --send_logs_level=test --query "
SELECT
    country_code,
    project
FROM
(
    SELECT 1
) AS L, pypi_1b AS R
ORDER BY (country_code, project) DESC
LIMIT 1000
FORMAT Null
SETTINGS
    cross_join_min_rows_to_compress=0,
    cross_join_min_bytes_to_compress=0,
    max_bytes_in_join=0,
    max_rows_in_join=10_000;
" 2> log.txt

当我们检查生成的 log.txt 文件时,我们可以看到这样的条目

TemporaryFileStream: Writing to temporary file ./tmp/tmpe8c8a301-ee67-40c4-af9a-db8e9c170d0c

以及整体内存使用量和运行时间

Peak memory usage (for query): 300.52 MiB.
Processed in 58.823 sec.

我们可以看到,此查询运行的内存使用量非常低,仅为 **300.52 MiB**,因为在 CROSS JOIN 处理期间,每次仅将右侧表块的一部分保存在内存中。

非等值 JOIN

由 Lgbo-USTC 贡献

24.5 版本之前,ClickHouse 只允许在 JOIN 的 ON 子句中使用等值条件。

例如,当我们在 ClickHouse 24.4 中运行此 JOIN 查询时,会收到异常

SELECT L.*, R.*
FROM pypi_1b AS L INNER JOIN pypi_1b AS R 
ON (L.country_code = R.country_code) AND (L.timestamp < R.timestamp)
LIMIT 10
FORMAT Null;

Received exception from server (version 24.4.1): … INVALID_JOIN_ON_EXPRESSION

现在,在 24.5 版本中,ClickHouse 实验性地支持在 ON 子句中使用非等值条件。请注意,目前我们需要启用 allow_experimental_join_condition 设置才能实现此功能。

SELECT
    L.*,
    R.*
FROM pypi_1b AS L
INNER JOIN pypi_1b AS R ON 
     L.country_code = R.country_code AND 
     L.timestamp < R.timestamp
LIMIT 10
FORMAT Null
SETTINGS
    allow_experimental_join_condition = 1;

敬请期待未来版本中更多 JOIN 改进!

关于 24.5 版本的介绍就到这里。我们诚邀您参加 6 月 27 日的 24.6 版本发布会。请确保您注册,以便获得 Zoom 网络研讨会的详细信息

分享此文章

订阅我们的新闻稿

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