0
128
0
0
首页/专栏/ 版本发布/ 查看内容

ClickHouse 24.6 版本发布说明

 admin   发表于  2024-10-15 14:49
专栏 版本发布


又到了发布新版本的时间!

发布概要

本次ClickHouse 24.6 版本包含了23个新功能🎁、24项性能优化🛷、59个bug修复🐛


新贡献者

我们向 24.6 版本的所有新贡献者表示特别欢迎!ClickHouse 的流行离不开社区的贡献,看到社区不断壮大,总是令人感动。

以下是新贡献者的名字:

Artem Mustafin、Danila Puzov、Francesco Ciocchetti、Grigorii Sokolik、HappenLee、Kris Buytaert、Lee sungju、Mikhail Gorshkov、Philipp Schreiber、Pratima Patel、Sariel、TTPO100AJIEX、Tim MacDonald、Xu Jia、ZhiHong Zhang、allegrinisante、anonymous、chloro、haohang、iceFireser、morning-color、pn、sarielwxm、wudidapaopao、xogoodnow


最佳表排序

由 Igor Markelov 贡献

MergeTree 表的物理磁盘顺序是由其 ORDER BY 键决定的。

ORDER BY 键和相应的物理行顺序有三个主要目的:

1. 构建稀疏索引以处理范围请求(也可以指定为 PRIMARY KEY)。

2. 定义合并模式的键,例如 AggregatingMergeTree 或 ReplacingMergeTree 表。

3. 通过在列文件中联合存储数据来提高压缩效果。

为了实现第三个目的,本次发布引入了一个新设置,称为 optimize_row_order。这个设置仅适用于普通的 MergeTree 引擎表。在按 ORDER BY 列排序后,它会根据列的基数对剩余列进行排序,以确保最佳压缩效果。

如果数据具有一定的模式,通用压缩编解码器如 LZ4ZSTD 可以实现最高的压缩率。例如,连续相同值的长序列通常能很好地压缩。通过按列值排序行,可以实现这样的长序列,从最低基数列开始。下图对此进行了说明:

如上图所示,行在磁盘上按列值排序,从最低基数列开始,每列中会有长时间运行相同的值。这样可以提高表部分列文件的压缩比。

作为反例,下图展示了首先按高基数列排序行在磁盘上的效果:

由于行按高基数列值排序,通常无法再根据其他列的值进行排序来形成长序列。因此,列文件的压缩比会不理想。

通过新的 optimize_row_order 设置,ClickHouse 自动实现最佳数据压缩。ClickHouse 仍然按 ORDER BY 列排序在磁盘上存储行。此外,在具有相同 ORDER BY 列值的行范围内,按剩余列的值进行排序,这些列按范围内列基数升序排列:

这种优化仅适用于插入时创建的数据部分,不适用于部分合并。由于普通 MergeTree 表的大多数合并仅连接 ORDER BY 键的非重叠范围,因此已优化的行顺序通常会保留。

根据数据特性,预计插入时间将增加 30-50%。

预计 LZ4ZSTD 的压缩率平均会提高 20-40%。

此设置最适用于没有 ORDER BY 键或低基数 ORDER BY 键的表,即仅有少数不同 ORDER BY 键值的表。具有高基数 ORDER BY 键的表,例如 DateTime64 类型的时间戳列,预计不会从此设置中受益。

为了展示这一新优化,我们将 10 亿行公共 PyPI 下载统计数据集加载到一个不使用 optimize_row_order 设置的表和一个使用 optimize_row_order 设置的表中。

我们创建了没有新设置的表:

CREATE OR REPLACE TABLE pypi(    `timestamp` DateTime64(6),    `date` Date MATERIALIZED timestamp,    `country_code` LowCardinality(String),    `url` String,    `project` String,    `file` Tuple(filename String, project String, version String, type Enum8('bdist_wheel' = 0, 'sdist' = 1, 'bdist_egg' = 2, 'bdist_wininst' = 3, 'bdist_dumb' = 4, 'bdist_msi' = 5, 'bdist_rpm' = 6, 'bdist_dmg' = 7)),    `installer` Tuple(name LowCardinality(String), version LowCardinality(String)),    `python` LowCardinality(String),    `implementation` Tuple(name LowCardinality(String), version LowCardinality(String)),    `distro` Tuple(name LowCardinality(String), version LowCardinality(String), id LowCardinality(String), libc Tuple(lib Enum8('' = 0, 'glibc' = 1, 'libc' = 2), version LowCardinality(String))),    `system` Tuple(name LowCardinality(String), release String),    `cpu` LowCardinality(String),    `openssl_version` LowCardinality(String),    `setuptools_version` LowCardinality(String),    `rustc_version` LowCardinality(String),    `tls_protocol` Enum8('TLSv1.2' = 0, 'TLSv1.3' = 1),    `tls_cipher` Enum8('ECDHE-RSA-AES128-GCM-SHA256' = 0, 'ECDHE-RSA-CHACHA20-POLY1305' = 1, 'ECDHE-RSA-AES128-SHA256' = 2, 'TLS_AES_256_GCM_SHA384' = 3, 'AES128-GCM-SHA256' = 4, 'TLS_AES_128_GCM_SHA256' = 5, 'ECDHE-RSA-AES256-GCM-SHA384' = 6, 'AES128-SHA' = 7, 'ECDHE-RSA-AES128-SHA' = 8, 'AES128-GCM' = 9))Engine = MergeTreeORDER BY (project);
并将数据加载到该表中。注意,为了更好地展示优化效果,我们增加了 min_insert_block_size_rows 的值:
INSERT INTO pypiSELECT    *FROM s3(    'https://storage.googleapis.com/clickhouse_public_datasets/pypi/file_downloads/sample/2023/{0..61}-*.parquet')SETTINGS    input_format_null_as_default = 1,    input_format_parquet_import_nested = 1,    min_insert_block_size_bytes = 0,    min_insert_block_size_rows = 60_000_000;
接下来,我们使用新设置创建相同的表:
CREATE TABLE pypi_opt(    `timestamp` DateTime64(6),    `date` Date MATERIALIZED timestamp,    `country_code` LowCardinality(String),    `url` String,    `project` String,    `file` Tuple(filename String, project String, version String, type Enum8('bdist_wheel' = 0, 'sdist' = 1, 'bdist_egg' = 2, 'bdist_wininst' = 3, 'bdist_dumb' = 4, 'bdist_msi' = 5, 'bdist_rpm' = 6, 'bdist_dmg' = 7)),    `installer` Tuple(name LowCardinality(String), version LowCardinality(String)),    `python` LowCardinality(String),    `implementation` Tuple(name LowCardinality(String), version LowCardinality(String)),    `distro` Tuple(name LowCardinality(String), version LowCardinality(String), id LowCardinality(String), libc Tuple(lib Enum8('' = 0, 'glibc' = 1, 'libc' = 2), version LowCardinality(String))),    `system` Tuple(name LowCardinality(String), release String),    `cpu` LowCardinality(String),    `openssl_version` LowCardinality(String),    `setuptools_version` LowCardinality(String),    `rustc_version` LowCardinality(String),    `tls_protocol` Enum8('TLSv1.2' = 0, 'TLSv1.3' = 1),    `tls_cipher` Enum8('ECDHE-RSA-AES128-GCM-SHA256' = 0, 'ECDHE-RSA-CHACHA20-POLY1305' = 1, 'ECDHE-RSA-AES128-SHA256' = 2, 'TLS_AES_256_GCM_SHA384' = 3, 'AES128-GCM-SHA256' = 4, 'TLS_AES_128_GCM_SHA256' = 5, 'ECDHE-RSA-AES256-GCM-SHA384' = 6, 'AES128-SHA' = 7, 'ECDHE-RSA-AES128-SHA' = 8, 'AES128-GCM' = 9))Engine = MergeTreeORDER BY (project)SETTINGS optimize_row_order = 1;
并将数据加载到该表中:
INSERT INTO pypi_optSELECT    *FROM s3(    'https://storage.googleapis.com/clickhouse_public_datasets/pypi/file_downloads/sample/2023/{0..61}-*.parquet')SETTINGS    input_format_null_as_default = 1,    input_format_parquet_import_nested = 1,    min_insert_block_size_bytes = 0,    min_insert_block_size_rows = 60_000_000;
让我们比较两个表的存储大小和压缩比:
SELECT    `table`,    formatReadableQuantity(sum(rows)) AS rows,    formatReadableSize(sum(data_uncompressed_bytes)) AS uncompressed,    formatReadableSize(sum(data_compressed_bytes)) AS compressed,    round(sum(data_uncompressed_bytes) / sum(data_compressed_bytes), 0) AS ratioFROM system.partsWHERE active AND (database = 'default') AND startsWith(`table`, 'pypi')GROUP BY `table`ORDER BY `table` ASC;

┌─table────┬─rows─────────┬─uncompressed─┬─compressed─┬─ratio─┐1. │ pypi │ 1.01 billion │ 227.97 GiB │ 25.36 GiB │ 9 │2. │ pypi_opt │ 1.01 billion │ 227.97 GiB │ 17.52 GiB │ 13 │ └──────────┴──────────────┴──────────────┴────────────┴───────┘
可以看到,使用新设置的表的数据压缩率提高了约 30%。


chDB 2.0

由 Auxten Wang 贡献

chDB 是 ClickHouse 的一种进程内版本,支持多种语言,尤其是 Python。它在今年早些时候加入 ClickHouse 家族,并在本周发布了 Python 库的 2.0 版本。

要安装该版本,您需要在安装时指定版本,如下所示:

pip install chdb==2.0.0b1
在此版本中,ClickHouse 引擎已升级到 24.5 版本,现在可以直接查询 Pandas DataFrame、Arrow 表和 Python 对象。

首先,我们生成一个包含 1 亿行的 CSV 文件:

import pandas as pdimport datetime as dtimport random
rows = 100_000_000now = dt.date.today()
df = pd.DataFrame({ "score": [random.randint(0, 1_000_000) for _ in range(0, rows)], "result": [random.choice(['win', 'lose', 'draw']) for _ in range(0, rows)], "dateOfBirth": [now - dt.timedelta(days = random.randint(5_000, 30_000)) for _ in range(0, rows)]})
df.to_csv("scores.csv", index=False)
然后我们可以编写以下代码,将数据加载回 Pandas DataFrame 并使用 chDB 的 Python 表引擎查询 DataFrame:
import pandas as pdimport chdbimport time
df = pd.read_csv("scores.csv")
start = time.time()print(chdb.query("""SELECT sum(score), avg(score), median(score), avgIf(score, dateOfBirth > '1980-01-01') as avgIf, countIf(result = 'win') AS wins, countIf(result = 'draw') AS draws, countIf(result = 'lose') AS losses, count()FROM Python(df)""", "Vertical"))end = time.time()print(f"{end-start} seconds")
输出如下:
Row 1:──────sum(score):    49998155002154avg(score):    499981.55002154median(score): 508259avgIf:         499938.84709508wins:          33340305draws:         33334238losses:        33325457count():       100000000
0.4595322608947754 seconds
我们可以将 CSV 文件加载到 PyArrow 表中并查询该表:
import pyarrow.csv
table = pyarrow.csv.read_csv("scores.csv")
start = time.time()print(chdb.query("""SELECT sum(score), avg(score), median(score), avgIf(score, dateOfBirth > '1980-01-01') as avgIf, countIf(result = 'win') AS wins, countIf(result = 'draw') AS draws, countIf(result = 'lose') AS losses, count()FROM Python(table)""", "Vertical"))end = time.time()print(f"{end-start} seconds")
代码块的输出如下所示:
Row 1:──────sum(score):    49998155002154avg(score):    499981.55002154median(score): 493265avgIf:         499955.15289763256wins:          33340305draws:         33334238losses:        33325457count():       100000000
3.0047709941864014 seconds
我们还可以查询 Python 字典,只要字典的值是一个列表:
x = {"col1": [random.uniform(0, 1) for _ in range(0, 1_000_000)]}
print(chdb.query("""SELECT avg(col1), max(col1)FROM Python(x)""", "DataFrame"))
avg(col1) max(col1)0 0.499888 0.999996

试试看,并告诉我们你的使用情况!


Hilbert 曲线

由 Artem Mustafin 贡献

有数学背景的用户可能对空间填充曲线的概念比较熟悉。在 24.6 版本中,我们增加了对 Hilbert 曲线的支持,以补充现有的 Morton 编码函数。这些曲线有助于加速某些查询,特别是在时间序列和地理数据中。

空间填充曲线是一种连续曲线,可以经过给定多维空间内的每一个点,通常在一个单位平方或立方体内,从而完全填充空间。这些曲线对很多人来说都非常吸引人,主要是因为它们挑战了我们直觉中一维线条无法覆盖二维区域或更高维体积的想法。最著名的例子包括 Hilbert 曲线和较早期的 Peano 曲线,二者都是通过迭代方式增加在空间内的复杂性和密度。空间填充曲线在数据库中有实际应用,因为它们能够有效地将多维数据映射到一维,同时保持局部性。

考虑一个二维坐标系,一个函数将任何点映射到一维线上,同时保持局部性,确保原空间中接近的点在该线上仍然接近。可以想象一个二维空间被分成四个象限,每个象限进一步分成四个,形成 16 个部分。这个递归过程虽然概念上是无限的,但在有限深度上更容易理解,例如深度 3,生成一个 8x8 的网格,有 64 个象限。该函数生成的曲线经过所有象限,保持原点在最终线上的接近性。

重要的是,递归和分割空间越深入,线上的点在其对应的二维位置上的稳定性越高。最终,这种曲线在无穷深度时可以完全“填满”空间。

用户可能熟悉来自 Delta Lake 等湖泊格式中的 Z-ordering 术语——也称为 Morton 顺序。这是另一种空间填充曲线,类似于上面的 Hilbert 例子,它也保留了空间局部性。ClickHouse 中的 mortonEncode 函数支持的 Morton 顺序,虽然局部性保留不如 Hilbert 曲线,但由于计算和实现更简单,在某些情况下更受欢迎。

对于空间填充曲线及其与无限数学的关系的精彩介绍,我们推荐这段视频。(https://www.youtube.com/watch?v=3s7h2MHQtxc)

那么这对优化数据库查询有什么用呢?

我们可以将多列有效地编码为单个值,通过保持数据的空间局部性,将结果值作为表中的排序顺序。这对于提高范围查询和最近邻搜索的性能非常有用,尤其当编码列在范围和分布上具有相似属性且具有大量不同值时。这些空间填充曲线的特性确保了高维空间中接近的值在相同的范围查询中会有相似的排序值,并属于相同的颗粒。因此,需要读取的颗粒更少,查询速度更快!

这些列不应有相关性——在这种情况下,传统的字典排序(如 ClickHouse 排序键使用的排序)更有效,因为按第一列排序会自然地对第二列排序。

虽然数值物联网数据(如时间戳和传感器值)通常满足这些属性,但更直观的例子是地理纬度和经度坐标。让我们看一个简单的例子,看看 Hilbert 编码如何加速这些类型的查询。


路过

雷人

握手

鲜花

鸡蛋

版权声明:本文为 clickhouse 社区用户原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接和本声明。

评论