我们非常激动地分享 23.11 版本中的大量精彩功能
版本摘要
25 个新功能。24 项性能优化。70 个错误修复。
以下仅为部分重点功能……但此版本涵盖了以下功能:连接任意类型的能力、fileCluster 函数、Keeper 改进、表的异步加载、system.numbers 上的索引、并发控制机制、S3 上请求的积极重试以及比以往更小的二进制文件大小!以及更多……更多。
如果您想预览本月即将到来的一些特别的“礼物”,请参加我们 12 月 28 日即将举行的社区电话会议。
新贡献者
与往常一样,我们特别欢迎 23.11 版本的所有新贡献者!ClickHouse 的受欢迎程度很大程度上归功于社区的贡献。看到社区不断壮大总是令人感到荣幸。
如果您在这里看到您的名字,请与我们联系……但我们也会在 Twitter 等平台上找到您。
Andrej Hoos, Arvind Pj, Chuan-Zheng Lee, James Seymour, Kevin Mingtarja, Oleg V. Kozlyuk, Philip Hallstrom, Sergey Kviatkevich, Shri Bodas, abakhmetev, edef, joelynch, johnnymatthews, konruvikt, melvynator, pppeace, rondo_1895, ruslandoga, slu, takakawa, tomtana, xleoken, 袁焊忠
S3Queue 已可用于生产环境
由 Sergei Katkovskiy 和 Kseniia Sumarokova 贡献
在 23.8 版本中,我们发布了S3Queue 表引擎的实验性版本,以大幅简化从 S3 进行的增量加载。这种新的表引擎允许从 S3 流式消费数据。当文件添加到存储桶时,ClickHouse 将自动处理这些文件并将它们插入到指定的表中。借助此功能,用户可以设置简单的增量管道,而无需额外的代码。
我们很高兴地宣布,自实验性版本发布以来,此功能已得到显著改进,现在已可用于生产环境!为了庆祝,我们的 YouTube 名人 Mark 准备了一个视频
PREWHERE 的列统计信息
由 Han Fei 贡献
列统计信息是 ClickHouse 中一项新的实验性功能,可实现更好的查询优化。借助此功能,您可以让 ClickHouse 为具有 MergeTree 系列引擎的表中的列创建(并自动更新)统计信息。这些统计信息存储在表分区的内部,位于一个小的 statistics_(column_name).stat
文件中,该文件是为每个启用统计信息的列存储不同类型统计信息的通用容器文件。这确保了对列统计信息的轻量级访问。截至目前,唯一支持的列统计信息类型是 t-digest。不过,计划支持其他类型。
列统计信息实现更好优化的第一个示例是多阶段 PREWHERE
过滤中的列处理顺序。我们用一个图表来概括这一点
图表左上角的查询具有一个 WHERE
子句,该子句由多个 AND 连接的筛选条件组成。ClickHouse 具有一项优化,旨在评估扫描数据量最少的过滤器。此优化称为多阶段 PREWHERE,它基于以下思想:我们可以按顺序读取筛选列,即逐列读取,并且每次迭代时,仅检查包含至少一行“幸存”(= 匹配)先前筛选器的块。每个过滤器要评估的块数单调递减。
毫不奇怪,当首先评估产生最少幸存块的过滤器时,此优化效果最佳 - 在这种情况下,ClickHouse 只需要扫描少量块即可评估其余过滤器。当然,不可能知道每个过滤器有多少个具有匹配行的块幸存下来,因此 ClickHouse 需要猜测以确定执行过滤器的最佳顺序。借助列统计信息,ClickHouse 能够更精确地估计匹配行/幸存块的数量,因此,作为一种优化的多阶段 PREWHERE 变得更加有效。
在示例中,ClickHouse 利用列统计信息自动确定列 c2
上的筛选条件是最具选择性的条件,即它丢弃的块最多。因此,处理从 c2
开始。扫描列 c2
中的所有块,并为每一行评估筛选谓词。接下来,对列 c3
执行筛选评估,但仅针对在 c2
的相应块中 c2
筛选器上至少有一个匹配项的行所在的块。由于列 c1
上的筛选条件的选择性最差,因此最后处理此列的块。同样,仅扫描那些块(并为每一行评估筛选谓词),其中来自 c2
和 c3
的相应块都具有谓词匹配项。从查询运行需要扫描和处理的所有其他列中,ClickHouse 仅需要扫描磁盘中所有相应 PREWHERE 列都具有谓词匹配项的那些块。
让我们用一个具体的例子来演示这一点。
我们创建一个示例表并插入 1000 万行
CREATE OR REPLACE TABLE example
(
`a` Float64,
`b` Int64,
`c` Decimal64(4),
`pk` String
)
ENGINE = MergeTree
ORDER BY pk;
INSERT INTO example SELECT
number,
number,
number,
generateUUIDv4()
FROM system.numbers
LIMIT 10_000_000
接下来,我们在 WHERE
子句中运行一个具有多个 AND 连接的筛选条件的查询。请注意,我们禁用了 PREWHERE
优化
SELECT count()
FROM example
WHERE b < 10 AND a < 10 AND c < 10
SETTINGS optimize_move_to_prewhere = 0
┌─count()─┐
│ 10 │
└─────────┘
1 row in set. Elapsed: 0.057 sec. Processed 10.00 million rows, 240.00 MB (176.00 million rows/s., 4.22 GB/s.)
Peak memory usage: 162.42 KiB.
我们可以看到查询处理了 240 MB 的列数据。
现在,我们运行相同的查询,并启用(多阶段)PREWHERE
SELECT count()
FROM example
WHERE b < 10 AND a < 10 AND c < 10
┌─count()─┐
│ 10 │
└─────────┘
1 row in set. Elapsed: 0.032 sec. Processed 10.00 million rows, 160.42 MB (308.66 million rows/s., 4.95 GB/s.)
Peak memory usage: 171.74 KiB.
这次,查询处理了 160 MB 的列数据。
接下来,我们启用列统计信息功能,并为我们表的三个列启用和物化基于 t-digest 的统计信息
SET allow_experimental_statistic = 1;
ALTER TABLE example ADD STATISTIC a, b, c TYPE tdigest;
ALTER TABLE example MATERIALIZE STATISTIC a, b, c TYPE tdigest;
运行启用列统计信息优化的示例查询
SELECT count()
FROM example
WHERE b < 10 AND a < 10 AND c < 10
SETTINGS allow_statistic_optimize = 1
┌─count()─┐
│ 10 │
└─────────┘
1 row in set. Elapsed: 0.012 sec. Processed 10.00 million rows, 80.85 MB (848.47 million rows/s., 6.86 GB/s.)
Peak memory usage: 160.25 KiB.
查询处理了 80 MB 的列数据。
但这仅仅是开始。列统计信息还将用于其他有影响力的优化,例如连接重新排序或使低基数数据类型成为自动决策。
敬请关注!
并行窗口函数
由 Dmitriy Novik 贡献
任何使用 SQL 进行过严肃数据分析的人都会体会到窗口函数的价值。自 21.5 版本以来,ClickHouse 中已提供窗口函数。PostgreSQL 的文档在总结此 SQL 功能方面做得非常出色
窗口函数对一组与当前行以某种方式相关的表行执行计算。这类似于可以使用聚合函数完成的计算类型。但是,与常规聚合函数不同,窗口函数的使用不会导致行被分组到单个输出行中 - 行保留其单独的标识。窗口函数能够访问的不仅仅是查询结果的当前行。
虽然窗口函数可以应用于一些非常复杂的问题,但大多数用户会在需要执行简单操作(例如移动平均值(需要考虑多行)或累积总和)时遇到它们。由于这些特定查询通常在 Grafana 等流行工具中可视化,因此我们总是很高兴宣布它们的性能得到显著提高。在 23.11 版本中,ClickHouse 在窗口函数的实现方面向前迈进了一大步,确保可以并行化其执行。
这种并行化是通过利用窗口函数固有的分桶功能(分区)来执行的。当用户指定应按列对窗口函数进行分区时,实际上会为每个分区创建一个单独的逻辑窗口,即,如果列包含 N 个不同的值,则需要创建 N 个窗口。在 23.11 版本中,可以有效地并行构造和评估这些分区。
例如,考虑以下查询,该查询使用 NOAA 天气数据集。
CREATE TABLE noaa
(
`station_id` LowCardinality(String),
`date` Date32,
`tempAvg` Int32 COMMENT 'Average temperature (tenths of a degrees C)',
`tempMax` Int32 COMMENT 'Maximum temperature (tenths of degrees C)',
`tempMin` Int32 COMMENT 'Minimum temperature (tenths of degrees C)',
`precipitation` UInt32 COMMENT 'Precipitation (tenths of mm)',
`snowfall` UInt32 COMMENT 'Snowfall (mm)',
`snowDepth` UInt32 COMMENT 'Snow depth (mm)',
`percentDailySun` UInt8 COMMENT 'Daily percent of possible sunshine (percent)',
`averageWindSpeed` UInt32 COMMENT 'Average daily wind speed (tenths of meters per second)',
`maxWindSpeed` UInt32 COMMENT 'Peak gust wind speed (tenths of meters per second)',
`weatherType` Enum8('Normal' = 0, 'Fog' = 1, 'Heavy Fog' = 2, 'Thunder' = 3, 'Small Hail' = 4, 'Hail' = 5, 'Glaze' = 6, 'Dust/Ash' = 7, 'Smoke/Haze' = 8, 'Blowing/Drifting Snow' = 9, 'Tornado' = 10, 'High Winds' = 11, 'Blowing Spray' = 12, 'Mist' = 13, 'Drizzle' = 14, 'Freezing Drizzle' = 15, 'Rain' = 16, 'Freezing Rain' = 17, 'Snow' = 18, 'Unknown Precipitation' = 19, 'Ground Fog' = 21, 'Freezing Fog' = 22),
`location` Point,
`elevation` Float32,
`name` LowCardinality(String),
`country` LowCardinality(String)
)
ENGINE = MergeTree
ORDER BY (country, date)
INSERT INTO noaa SELECT *
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/noaa/noaa_with_country.parquet')
此处可以使用简单的窗口函数来计算每天和每个国家/地区的温度移动平均值。这要求我们按国家/地区(数据集中有 214 个国家/地区)进行分区,并按日期排序。在计算移动平均值时,我们考虑最近 5 个数据点。
SELECT
country,
day,
max(tempAvg) AS temperature,
avg(temperature) OVER (PARTITION BY country ORDER BY day ASC ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS moving_avg_temp
FROM noaa
WHERE country != ''
GROUP BY
country,
date AS day
ORDER BY
country ASC,
day ASC
此简单函数的意图最好用简单的可视化来解释。
在 23.11 版本之前,ClickHouse 大部分会并行执行此函数 - 但窗口函数除外。在查询不受其他因素(例如 I/O)限制的情况下,这可能会限制性能。
在 23.10 版本中,在一台具有 96GiB RAM 的 12 核机器上,此查询需要大约 8.8 秒才能在完整的 10 亿行上运行。
SELECT
country,
day,
max(tempAvg) AS avg_temp,
avg(avg_temp) OVER (PARTITION BY country ORDER BY day ASC ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS moving_avg_temp
FROM noaa
WHERE country != ''
GROUP BY
country,
date AS day
ORDER BY
country ASC,
day ASC
LIMIT 10
┌─country─────┬────────day─┬─avg_temp─┬─────moving_avg_temp─┐
│ Afghanistan │ 1900-01-01 │ -81 │ -81 │
│ Afghanistan │ 1900-01-02 │ -145 │ -113 │
│ Afghanistan │ 1900-01-03 │ -139 │ -121.66666666666667 │
│ Afghanistan │ 1900-01-04 │ -107 │ -118 │
│ Afghanistan │ 1900-01-05 │ -44 │ -103.2 │
│ Afghanistan │ 1900-01-06 │ 0 │ -86 │
│ Afghanistan │ 1900-01-07 │ -71 │ -84.33333333333333 │
│ Afghanistan │ 1900-01-08 │ -85 │ -74.33333333333333 │
│ Afghanistan │ 1900-01-09 │ -114 │ -70.16666666666667 │
│ Afghanistan │ 1900-01-10 │ -71 │ -64.16666666666667 │
└─────────────┴────────────┴──────────┴─────────────────────┘
10 rows in set. Elapsed: 8.515 sec. Processed 1.05 billion rows, 7.02 GB (123.61 million rows/s., 824.61 MB/s.)
Peak memory usage: 1.13 GiB.
从 23.11 版本开始,通过并行执行每个分区来提高性能。同样,最好用简单的图示来描述这一点。
您在此处的实际收益取决于许多因素 - 尤其是在并行化提供显著改进的情况下,是否具有足够的分区和每个分区的工作量。它还假设查询不受其他因素的限制。在我们的示例如下,我们无需执行任何操作即可获得超过 10% 的改进!
SELECT
country,
day,
max(tempAvg) AS avg_temp,
avg(avg_temp) OVER (PARTITION BY country ORDER BY day ASC ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS moving_avg_temp
FROM noaa
WHERE country != ''
GROUP BY
country,
date AS day
ORDER BY
country ASC,
day ASC
LIMIT 10
┌─country─────┬────────day─┬─avg_temp─┬─────moving_avg_temp─┐
│ Afghanistan │ 1900-01-01 │ -81 │ -81 │
│ Afghanistan │ 1900-01-02 │ -145 │ -113 │
│ Afghanistan │ 1900-01-03 │ -139 │ -121.66666666666667 │
│ Afghanistan │ 1900-01-04 │ -107 │ -118 │
│ Afghanistan │ 1900-01-05 │ -44 │ -103.2 │
│ Afghanistan │ 1900-01-06 │ 0 │ -86 │
│ Afghanistan │ 1900-01-07 │ -71 │ -84.33333333333333 │
│ Afghanistan │ 1900-01-08 │ -85 │ -74.33333333333333 │
│ Afghanistan │ 1900-01-09 │ -114 │ -70.16666666666667 │
│ Afghanistan │ 1900-01-10 │ -71 │ -64.16666666666667 │
└─────────────┴────────────┴──────────┴─────────────────────┘
10 rows in set. Elapsed: 7.571 sec. Processed 1.05 billion rows, 7.02 GB (139.03 million rows/s., 927.47 MB/s.)
Peak memory usage: 1.13 GiB.