ClickHouse 24.1 版本发布说明

图片

本文字数:10871;估计阅读时间:28 分钟

作者:ClickHouse Team

审校:庄晓东(魏庄)

本文在公众号【ClickHouseInc】首发

发布概要:

ClickHouse 在 2024 年的首个新版本发布来了,v24.1 包含了26个新功能🎁、22个性能优化🛷、47个bug修复🐛

和往常一样,我们将在本篇博客中突出展示一小部分新功能和改进,但此版本还包括了生成shingles、Punycode函数、Datadog的分位数草图、写入文件时的压缩控制、HTTP输出和并行副本的加速以及keeper和merges的内存优化。

在集成方面,我们还很高兴地宣布了ClickHouse Grafana插件v4的GA,该插件有重大改进,重点是将ClickHouse用于可观察性用例。

新贡献者

和往常一样,我们特别欢迎所有24.1版本的新贡献者!ClickHouse的流行很大程度上归功于社区的努力贡献。看到社区增长总是令人印象深刻的。

以下是新贡献者的名字:

Aliaksei Khatskevich, Artem Alperin, Blacksmith, Blargian, Eyal Halpern Shalev, Jayme Bird, Lino Uruñuela, Maksim Alekseev, Mark Needham, Mathieu Rey, MochiXu, Nikolay Edigaryev, Roman Glinskikh, Shaun Struwig, Tim Liou, Waterkin, Zheng Miao, avinzhang, chenwei, edpyt, mochi, and sunny19930321.

提示:如果你好奇我们是如何生成这个列表的……请点击这里。(https://gist.github.com/gingerwizard/5a9a87a39ba93b422d8640d811e269e9)

如果你在这里看到了你的名字,请与我们联系......但我们也会通过Twitter等渠道找到你。

变体类型

由Pavel Kruglov贡献

此版本引入了变体类型,它仍处于实验模式,所以您需要进行以下设置才能使其工作。

SET allow_experimental_variant_type=1, 
   use_variant_as_common_type = 1;

变体类型是ClickHouse添加半结构化列的长期项目的一部分。该类型是嵌套列的区分联合。例如,Variant(Int8, Array(String))的每个值都是Int8Array(String)

当我们处理地图时,这种新类型会很方便。例如,想象一下你想要创建一个具有不同类型值的地图:

SELECT
    map('Hello', 1, 'World', 'Mark') AS x,
    toTypeName(x) AS type
FORMAT Vertical;

通常会抛出异常:

Received exception:
Code: 386. DB::Exception: There is no supertype for types UInt8, String because some of them are String/FixedString and some of them are not: While processing map('Hello', 1, 'World', 'Mark') AS x, toTypeName(x) AS type. (NO_COMMON_TYPE)

而现在它返回一个Variant类型:

Row 1:
──────
x:    {'Hello':1,'World':'Mark'}
type: Map(String, Variant(String, UInt8))

我们还可以在读取CSV文件时使用这种类型。例如,假设我们有以下混合类型的文件:

$ cat foo.csv
value
1
"Mark"
2.3

在处理文件时,我们可以添加一个模式推断提示,让它使用Variant类型:

SELECT *, * APPLY toTypeName
FROM file('foo.csv', CSVWithNames)
SETTINGS 
  schema_inference_make_columns_nullable = 0, 
  schema_inference_hints = 'value Variant(Int, Float32, String)'

┌─value─┬─toTypeName(value)───────────────┐
│ 1     │ Variant(Float32, Int32, String) │
│ Mark  │ Variant(Float32, Int32, String) │
│ 2.3   │ Variant(Float32, Int32, String) │
└───────┴─────────────────────────────────┘

目前,它不适用于字面数组,所以下面的代码会抛出异常:

SELECT
    arrayJoin([1, true, 3.4, 'Mark']) AS value,
    toTypeName(value)


Received exception:
Code: 386. DB::Exception: There is no supertype for types UInt8, Bool, Float64, String because some of them are String/FixedString and some of them are not: While processing arrayJoin([1, true, 3.4, 'Mark']) AS value, toTypeName(value). (NO_COMMON_TYPE)

但你可以使用array函数,然后Variant类型将会被使用:

select arrayJoin(array(1, true, 3.4, 'Mark')) AS value, toTypeName(value);


┌─value─┬─toTypeName(arrayJoin([1, true, 3.4, 'Mark']))─┐
│ 1     │ Variant(Bool, Float64, String, UInt8)         │
│ true  │ Variant(Bool, Float64, String, UInt8)         │
│ 3.4   │ Variant(Bool, Float64, String, UInt8)         │
│ Mark  │ Variant(Bool, Float64, String, UInt8)         │
└───────┴───────────────────────────────────────────────┘

我们也可以通过Variant对象按类型读取单个值:

SELECT
    arrayJoin([1, true, 3.4, 'Mark']) AS value,
    variantElement(value, 'Bool') AS bool,
    variantElement(value, 'UInt8') AS int,
    variantElement(value, 'Float64') AS float,
    variantElement(value, 'String') AS str;


┌─value─┬─bool─┬──int─┬─float─┬─str──┐
│ 1     │ ᴺᵁᴸᴸ │    1 │  ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │
│ true  │ true │ ᴺᵁᴸᴸ │  ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │
│ 3.4   │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │   3.4 │ ᴺᵁᴸᴸ │
│ Mark  │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │  ᴺᵁᴸᴸ │ Mark │
└───────┴──────┴──────┴───────┴──────┘

字符串相似度函数

由prashantr36和Robert Schulze贡献

初次接触ClickHouse的用户,尝试使用LIKE运算符和match运算符时,通常会对其性能感到惊讶。根据被匹配的表达式,这可能会被映射到正则表达式,或者使用效率高的Volnitsky的字符串搜索算法执行子字符串搜索。ClickHouse还会尽力利用主键和跳过索引来加速LIKE / regex匹配。

虽然字符串匹配有很多应用,从数据清理到在可观察性用例中搜索日志,但很难将两个字符串之间的“模糊”关系表达为LIKE模式或正则表达式。现实世界的数据集通常更加“混乱”,需要比子字符串搜索提供的更灵活的功能,例如,查找拼写错误的字符串或由光学字符识别(OCR)导致的错误。

为解决这些挑战,存在许多知名的字符串相似性算法,包括Levenshtein、Damerau Levenshtein、Jaro Similarity和Jaro Winkler。这些算法广泛用于拼写检查、抄袭检测等应用,以及自然语言处理、计算语言学和生物信息学等领域。

所有这些算法都计算搜索字符串与目标上的一组标记之间的字符串相似度(编辑距离)。该指标旨在通过计算将一个字符串转换为另一个字符串所需的最小操作数,来量化两个字符串之间的不相似程度。在计算此距离时,每个算法允许的操作不同,有些还会在计算计数时对特定操作进行加权。

在24.1中,我们通过为Damerau Levenshtein、Jaro Similarity和Jaro Winkler提供新函数,来扩展对Levenshtein距离的现有支持。

可能是最著名的实现此概念的算法(以至于通常与编辑距离可以互换使用)是Levenshtein距离。该指标计算将一个单词变成另一个单词,所需的最小单字符编辑操作数。编辑操作仅限于3种类型:

  • 插入:向字符串添加一个字符。

  • 删除:从字符串中删除一个字符。

  • 替换:将字符串中的一个字符替换为另一个字符。

两个字符串之间的Levenshtein距离是指:将一个字符串转换为另一个字符串所需的最小操作数。Damerau-Levenshtein基于此概念,添加了转置,即相邻字符的交换。

例如,考虑“example”和“exmalpe”的Levenshtein和Damerau Levenshtein之间的差异。

使用Levenshtein距离,我们需要3个操作:字符串相似度

图片

在ClickHouse上确认:

SELECT levenshteinDistance('example', 'exmalpe') AS d
┌─d─┐
│ 3 │
└───┘

使用Damerau Levenshtein距离只需要2个,由于转置:字符串相似度

图片

在ClickHouse用新的 damerauLevenshteinDistance函数确认:

SELECT damerauLevenshteinDistance('example', 'exmalpe') AS d
┌─d─┐
│ 2 │
└───┘

Jaro Similarity和Jaro Winkler算法具有等效的函数,并通过考虑转置以及具有定义的距离位置的公共字符的数量来提供计算编辑距离度量的替代方法。

为了说明这种功能及其可能的应用,让我们考虑拼写错误问题,也称为URL劫持。这是一种网络抢注(以他人的品牌或版权下的站点)的形式,针对将网站地址错误输入其Web浏览器的互联网用户(例如,“gooogle.com”而不是“google.com”)。由于这些站点通常是恶意的,了解哪些是最常访问的域名可能对品牌有所帮助。

检测这些拼写错误是字符串相似度函数的一个经典应用。我们只需要找到目标站点的最受欢迎的域名,这些域名的编辑距离小于N。为此,我们需要一个排名过的域名集合。Tranco数据集解决了这个问题,提供了最受欢迎的域名的排名集。

排名的域名列表在Web安全和互联网检测中具有应用,但经典易于操纵和影响。Tranco旨在解决这个问题,并提供具有可重复方法的最新列表。

我们可以将完整列表(包括子域名)插入到ClickHouse中,包括每个站点的排名,只需两个简单的命令:

CREATE TABLE domains
(
  `domain` String,
  `rank` Float64
)
ENGINE = MergeTree
ORDER BY domain

INSERT INTO domains SELECT
  c2 AS domain,
  1 / c1 AS rank
FROM url('https://tranco-list.eu/download/PNZLJ/full', CSV)

0 rows in set. Elapsed: 4.374 sec. Processed 7.02 million rows, 204.11 MB (1.60 million rows/s., 46.66 MB/s.)
Peak memory usage: 116.77 MiB.

请注意,我们使用Tranco建议的1/rank,即

“首个域名获得1分,第二个1/2分,...,最后1/N分,未排名的域名0分。这种方法大致反映了Zipf定律和网站流行度分布中的“长尾效应”的观察。”

前10个域名应该很熟悉:

SELECT *
FROM domains
ORDER BY rank DESC
LIMIT 10

┌─domain─────────┬────────────────rank─┐
│ google.com    │                   1 │
│ amazonaws.com  │                  0.5 │
│ facebook.com   │  0.3333333333333333 │
│ a-msedge.net   │                0.25 │
│ microsoft.com  │                  0.2 │
│ apple.com     │ 0.16666666666666666 │
│ googleapis.com │ 0.14285714285714285 │
│ youtube.com   │              0.125 │
│ www.google.com │  0.1111111111111111 │
│ akamaiedge.net │                  0.1 │
└────────────────┴─────────────────────┘

10 rows in set. Elapsed: 0.313 sec. Processed 7.02 million rows, 254.36 MB (22.44 million rows/s., 813.00 MB/s.)
Peak memory usage: 34.56 MiB.

我们可以使用一个简单的查询,来测试字符串距离函数在识别拼写错误方面的有效性,以“facebook.com”为例:

SELECT
  domain,
  levenshteinDistance(domain, 'facebook.com') AS d1,
  damerauLevenshteinDistance(domain, 'facebook.com') AS d2,
  jaroSimilarity(domain, 'facebook.com') AS d3,
  jaroWinklerSimilarity(domain, 'facebook.com') AS d4,
  rank
FROM domains
ORDER BY d1 ASC
LIMIT 10

┌─domain────────┬─d1─┬─d2─┬─────────────────d3─┬─────────────────d4─┬────────────────────rank─┐
│ facebook.com  │  0 │  0 │               1  │                  1 │      0.3333333333333333 │
│ facebook.cm   │  1 │  1 │ 0.9722222222222221 │ 0.9833333333333333 │   1.4258771318823703e-7 │
│ acebook.com   │  1 │  1 │ 0.9722222222222221 │ 0.9722222222222221 │ 0.000002449341494539193 │
│ faceboook.com │  1 │  1 │ 0.9188034188034188 │ 0.9512820512820512 │ 0.000002739643462799751 │
│ faacebook.com │  1 │  1 │ 0.9743589743589745 │ 0.9794871794871796 │    5.744693196042826e-7 │
│ faceboom.com  │  1 │  1 │ 0.8838383838383838 │ 0.9303030303030303 │   3.0411914171495823e-7 │
│ facebool.com  │  1 │  1 │ 0.9444444444444443 │ 0.9666666666666666 │    5.228971429945901e-7 │
│ facebooks.com │  1 │  1 │ 0.9743589743589745 │ 0.9846153846153847 │   2.7956239539124616e-7 │
│ facebook.co   │  1 │  1 │ 0.9722222222222221 │ 0.9833333333333333 │  0.00000286769597834316 │
│ facecbook.com │  1 │  1 │ 0.9049145299145299 │ 0.9429487179487179 │    5.685177604948379e-7 │
└───────────────┴────┴────┴────────────────────┴────────────────────┴─────────────────────────┘

10 rows in set. Elapsed: 0.304 sec. Processed 5.00 million rows, 181.51 MB (16.44 million rows/s., 597.38 MB/s.)
Peak memory usage: 38.87 MiB.

这些似乎是可信的拼写错误,尽管我们不建议测试它们!

品牌所有者可能希望针对其中最受欢迎的域名进行操作,将这些站点停止,甚至尝试获取DNS条目并将重定向添加到正确的站点。例如,在facebool.com的情况下,情况已经是这样。

开发一个稳健的度量标准来识别要定位的列表远远超出了本文的范围。为了演示目的,我们将找到所有Damerau-Levenshtein距离为1的域名,并按其实际受欢迎程度排序,排除任何首要子域名为“facebook”的情况:

SELECT domain, rank, damerauLevenshteinDistance(domain, 'facebook.com') AS d
FROM domains
WHERE (d <= 1) AND (firstSignificantSubdomain(domain) != 'facebook')
ORDER BY rank DESC
LIMIT 10

┌─domain────────┬─────────────────────rank─┬─d─┐
│ facebok.com   │  0.000005683820436744763 │ 1 │
│ facbook.com   │  0.000004044178607104004 │ 1 │
│ faceboook.com │  0.000002739643462799751 │ 1 │
│ acebook.com   │  0.000002449341494539193 │ 1 │
│ faceboo.com   │ 0.0000023974606097221825 │ 1 │
│ facebbook.com │  0.000001914476505544324 │ 1 │
│ facebbok.com  │ 0.0000014273133538010068 │ 1 │
│ faceook.com   │     7.014964321891459e-7 │ 1 │
│ faceboock.com │     6.283680527628087e-7 │ 1 │
│ faacebook.com │     5.744693196042826e-7 │ 1 │
└───────────────┴──────────────────────────┴───┘

10 rows in set. Elapsed: 0.318 sec. Processed 6.99 million rows, 197.65 MB (21.97 million rows/s., 621.62 MB/s.)
Peak memory usage: 12.77 MiB.

这似乎是一个合理的初始列表。请随意使用您自己的域名重复此操作,并告诉我们它是否有用!

FINAL with ReplacingMergeTree的竖直算法

由Duc Canh Le贡献

上个月的发布已经为使用FINAL修饰符的SELECT查询带来了重大优化。我们当前的版本在FINAL与ReplacingMergeTree表引擎一起使用时带来了一些额外的优化。

提醒一下,FINAL可以用作为使用ReplacingMergeTree、AggregatingMergeTree和CollapsingMergeTree引擎创建的表的查询修饰符,以便在查询时转换在飞行中应用缺失的数据。自ClickHouse 23.12以来,根据排序键值,匹配查询的WHERE子句的表数据,被划分为非交叉和交叉范围。非交叉范围是仅存在于单个部分中的数据区域,因此不需要转换。相反,交叉范围中的行(基于排序键值)可能存在于多个部分中,并且需要特殊处理。所有非交叉数据范围都按照与查询中未使用FINAL修饰符时相同的方式并行处理。这样,只有交叉数据范围,才会在查询时应用表引擎的合并逻辑。

提醒一下,以下图表显示了通过查询管道,在查询时合并这些数据范围的方式:

图片

从所选数据范围中的数据以块(将多个相邻行合并为一个数据范围)的物理顺序流入,并使用k路归并排序算法进行合并。

在合并过程中,ReplacingMergeTree表引擎仅保留行的最近插入版本(基于其包含部分的创建时间戳),旧版本被丢弃。为了合并来自流式数据块的行,算法在块的列上逐行迭代,并将数据复制到新块中。为了使CPU高效执行此逻辑,块需要驻留在CPU缓存中,例如L1/L2/L3缓存。块中包含的列越多,块需要被重复驱逐出CPU缓存的机会就越高,导致缓存崩溃。下一个图表说明了这一点:

图片

我们假设CPU缓存可以同时容纳两个来自我们示例数据的块。当上述合并算法按顺序迭代所有三个选定的匹配数据范围的块以逐行合并它们的数据时,运行时将受到每次迭代一个缓存驱逐的最坏情况的影响。这需要将数据从主存储器复制到CPU缓存中,导致由于不必要的内存访问而导致整体性能变慢。

ClickHouse 24.1尝试通过为ReplacingMergeTree提供更友好的缓存查询时间合并算法来防止这种情况,该算法与竖直后台合并算法类似。下一个图表概述了这种算法的工作方式:

图片

在合并排序期间,算法不再为每一行复制所有列值,而是分为两个阶段。在第1阶段,算法仅合并排序关键列中的数据。我们假设在上面的示例中,列c1是排序关键列。此外,基于排序关键列合并,算法创建一个临时的行级过滤位图,用于指示哪些行将在常规合并中幸存。在第2阶段,这些位图根据需要过滤数据范围,并从进一步的处理步骤中删除所有旧行。此过滤按列进行,并且仅用于所有非排序关键列。请注意,阶段1和阶段2都需要比之前的23.12版本合并算法更少的CPU缓存空间,从而减少了CPU缓存驱逐,从而减少了内存延迟。

我们通过一个具体的示例演示FINAL的新垂直查询时间合并算法。与上一个版本的发布文章类似,我们稍微修改了英国房地产价格示例数据集中的表,并假设该表存储的是当前房产报价的数据,而不是以前售出的房产。我们使用了一个ReplacingMergeTree表引擎,这样我们就可以通过简单地插入具有相同排序关键值的新行来更新提供的房产的价格和其他特征:

CREATE OR REPLACE TABLE uk_property_offers
(
    id UInt32,
    price UInt32,
    date Date,
    postcode1 LowCardinality(String),
    postcode2 LowCardinality(String),
    type Enum8('terraced' = 1, 'semi-detached' = 2, 'detached' = 3, 'flat' = 4, 'other' = 0),
    is_new UInt8,
    duration Enum8('freehold' = 1, 'leasehold' = 2, 'unknown' = 0),
    addr1 String,
    addr2 String,
    street LowCardinality(String),
    locality LowCardinality(String),
    town LowCardinality(String),
    district LowCardinality(String),
    county LowCardinality(String)
)
ENGINE = ReplacingMergeTree(date)
ORDER BY (id);

接下来,我们向表中插入了大约1500万行。

我们在ClickHouse版本24.1上运行了一个典型的分析查询,使用了新的垂直查询时间合并算法禁用,并选择了三个最昂贵的主要邮政编码:

SELECT
    postcode1,
    formatReadableQuantity(avg(price))
FROM uk_property_offers
GROUP BY postcode1
ORDER BY avg(price) DESC
LIMIT 3
SETTINGS enable_vertical_final = 0;

┌─postcode1─┬─formatReadableQuantity(avg(price))─┐
│ W1A       │ 163.58 million                     │
│ NG90      │ 68.59 million                      │
│ CF99      │ 47.00 million                      │
└───────────┴────────────────────────────────────┘

0 rows in set. Elapsed: 0.011 sec. Processed 9.29 thousand rows, 74.28 KB (822.68 thousand rows/s., 6.58 MB/s.)
Peak memory usage: 1.10 MiB.

我们使用新的垂直查询时间合并算法运行相同的查询:

SELECT
    postcode1,
    formatReadableQuantity(avg(price))
FROM uk_property_offers
GROUP BY postcode1
ORDER BY avg(price) DESC
LIMIT 3
SETTINGS enable_vertical_final = 1;

┌─postcode1─┬─formatReadableQuantity(avg(price))─┐
│ W1A       │ 163.58 million                     │
│ NG90      │ 68.59 million                      │
│ CF99      │ 47.00 million                      │
└───────────┴────────────────────────────────────┘

0 rows in set. Elapsed: 0.004 sec. Processed 9.29 thousand rows, 111.42 KB (2.15 million rows/s., 25.81 MB/s.)
Peak memory usage: 475.21 KiB.

请注意,第二个查询运行速度更快,内存消耗更少。

国际域名世界

最后,Alexey使用此版本中新增的punycode函数,探索了国际域名的质量问题。我们在下面的视频中剪辑了该部分内容,供您观看。(https://www.youtube.com/watch?v=l5XlQCQDiLI)

图片

​​联系我们

手机号:13910395701

邮箱:Tracy.Wang@clickhouse.com

满足您所有的在线分析列式数据库管理需求

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值