博客 / 工程

ClickHouse 24.5 版本发布

author avatar
ClickHouse 团队
2024年6月5日 - 17 分钟阅读

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

新贡献者

与往常一样,我们特别欢迎 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, anonymous, [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 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │
   └───────────┴─────────────────────┴──────┴──────┴──────┴──────┘

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

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 在执行 CROSS join 之前将压缩 (LZ4) 右表块加载到主内存中

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 运行此 CROSS JOIN 使用了 51.77 GiB 的主内存。

我们使用 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 和启用的 test 级别日志记录级别来运行我们的示例 CROSS JOIN 来演示这一点。请注意,我们禁用了压缩并使用了有限的 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 网络研讨会的全部详细信息

分享这篇文章

订阅我们的新闻通讯

随时了解功能发布、产品路线图、支持和云产品!
正在加载表单...
关注我们
X imageSlack imageGitHub image
Telegram imageMeetup imageRss image