在 ClickHouse 中即时克隆海量表,安全地进行试验

图片

本文字数:5305;估计阅读时间:14 分钟

作者:Tom Schreiber

本文在公众号【ClickHouseInc】首发

图片

TL;DR

CREATE TABLE staging CLONE AS prod; 

→ 立即创建一个克隆,无需复制一个字节。

它基本上是表的 Git fork:开始时完全相同,写入时分叉(写时复制)。对破坏性测试来说是安全的。

一种安全的实验方法

你的生产环境(production)中有一个庞大的 ClickHouse 表。

现在你想在预发环境(staging)中测试破坏性更改:删除、更新、模式调整,甚至可能是激进的优化。

但你绝对不想冒险触碰生产数据。

如果你能简单地即时、可重复地创建那个庞大表的精确副本,然后测试任何你想要的破坏性更改,那不是很好吗?而且完全不触碰生产数据,并且无需复制一个字节

在 ClickHouse 中,你可以。

这项功能令人感觉像是魔法。但它实际上是 ClickHouse 存储数据方式的一个顺理成章的产物。

尽管这项功能在 ClickHouse 文档中有记录,但在实践中仍出人意料地未被充分利用。

在 ClickHouse Cloud 中,这种模式甚至得到了进一步扩展。 克隆生产表,并在具有隔离计算资源的独立服务上运行测试。

在这篇博文中,我们将深入探讨表克隆的底层工作原理,以及为什么不可变数据片段(data part)使其成为可能。

想快速了解一下吗?

Mark 录制了一段关于表克隆的简短解释:

https://www.youtube.com/watch?v=y_9Q_Us-yhA

提醒:不可变数据片段(data part)模型

每次向 ClickHouse 表中插入数据,都会在磁盘上生成一个新的、独立的、不可变的数据片段。

为了说明这一点,我们使用以下 orders 表来跟踪每个客户的总收入:

CREATE TABLE orders
(
    order_id UInt32,
    customer String,
    total UInt32
)
ENGINE = MergeTree
ORDER BY order_id;

对于向 orders 表的这次插入操作:

INSERT INTO orders VALUES
    (1001, 'Liam', 31000),
    (1002, 'Ben',   7500),
    (1003, 'Anna', 12000);

ClickHouse 会在磁盘上创建一个新的片段:

图片

在底层,这个片段是磁盘上的一个目录,包含压缩的列文件,表中每列对应一个:order_idcustomer 和 total。片段内的行会根据表的排序键进行物理排序,在本例中是 order_id

数据分片(Data parts)是完全自包含的。它们包含解释其内容所需的所有元数据,无需依赖中央目录。虽然未在上图中显示,但每个数据分片还包含额外的元数据文件,例如稀疏主索引(sparse primary index)、辅助数据跳过索引(secondary data skipping indexes)、列统计信息、校验和、最小-最大索引(min-max indexes)(如果使用分区),等等。

当新数据到达时,ClickHouse 从不就地修改(in place)现有数据分片。它总是写入新的数据分片

在后台,数据分片会被合并成更大的分片,以控制分片数量并整合数据,但即使是合并操作,也会生成全新的数据分片

(这种设计也使ClickHouse能够实现非常高的插入吞吐量:数据可以作为独立的数据分片写入,无需全局同步,并在后台合并期间稍后整合。)

同样,删除行…

DELETE FROM orders WHERE order_id = 1001;

…或更新行…

UPDATE orders
SET total = 3600
WHERE order_id = 36043;

…实现方式是:识别包含受影响行的数据分片,写入一个应用了更改的新的数据分片(或分片片段),然后最终删除旧的数据分片。

这种严格不变性是实现即时、零拷贝(zero-copy)表克隆的关键架构特性。

从不可变的数据分片到即时克隆

当你执行…

CREATE TABLE staging CLONE AS production;

…ClickHouse 并复制生产环境表的数据。

无数据拷贝

由于数据分片是不可变的,并且从不就地(in place)修改,因此它们的内容可以安全地共享。

ClickHouse 不复制数据,而是为克隆表,为源表的每个现有数据分片创建一个对应的分片目录。如果源表有142个数据分片,克隆表也将有142个数据分片。

克隆表的数据分片目录中的文件硬链接到源表的数据分片目录中的相应文件(通过本地文件系统上的POSIX硬链接,或通过S3等对象存储上的元数据间接机制实现)。

这适用于 part 目录内的所有文件,包括列数据文件以及元数据文件,例如稀疏主索引(sparse primary index)、二级数据跳过索引(secondary data skipping indexes)、列统计信息(column statistics)、校验和(checksums)和最小-最大索引(min-max indexes)。

为清晰起见,下文将重点关注列文件来解释其工作机制。实际上,相同的硬链接行为适用于 part 目录内的每个文件。

克隆行为与源表完全一致

在克隆(cloning)时,新表与源表拥有完全相同数量的数据分片(data parts),且每个数据分片都包含相同的列文件和索引。

因此,克隆(clone)的行为与原始表完全一致:查询(queries)、变更(mutations)、后台合并(background merges)和存储布局(storage layout)都以相同方式运作。不存在“轻量级”或降级模式。克隆从一开始就是一个功能完备的表。

如果您熟悉 Git,这本质上是一个分支(fork):克隆的表最初与原始表完全相同,只有当您修改它时才会产生分歧。

通过不变性实现安全共享

生产表(production table)与克隆表(cloned table)之间共享列文件是安全的,因为任何修改(插入、删除或更新)总是会生成包含新列文件的新数据分片。

如果您修改克隆表
 ClickHouse 只为克隆表写入新的分片。原始生产表继续引用未改变的分片。

反之,如果生产表被修改
 它也会生成包含新列文件的新分片,而先前引用的列文件只要克隆表的数据分片仍然引用它们,就会保留在原处(如下文所述,列文件仅在没有任何表引用它们时才会被删除)。

写时复制与存储效率

因此,只要两个表均不修改共享数据,克隆表几乎不消耗任何额外的数据磁盘空间。两个表所引用的相应数据分片目录(data part directories)只是共享相同的基础列文件。

只有当其中一个表的数据被修改时,才会写入新的分片;即便如此,也仅限于受影响的数据。

分片级写时复制(Part-level copy-on-write)
 即使在一个 PB 规模的表中,更新单行也只会重写包含该行的数据分片,而非整个数据集。所有其他分片将保持共享且不受影响。

这是存储设计的一个基本特性:修改是在分片层面进行隔离,而非表层面。

请注意,这种数据分段级别(part-level)的写时复制(copy-on-write)并非克隆操作所独有。它是ClickHouse中所有数据修改所使用的基本机制,包括源表(source table)自身的数据修改。

如前所述,数据修改的实现方式是:首先识别包含受影响行的数据分段(part),然后写入一个应用了更改的新数据分段,并最终移除旧的数据分段。

就是数据分段级别的写时复制。克隆并未引入新的写入路径。它复用了支撑所有数据修改的完全相同的存储机制。

源表不再特殊

由于数据分段(part)是独立共享的,源表在克隆后就不再特殊了。它只是引用相同列文件(column files)的另一个表。你可以删除源表,只要克隆表的数据分段(data parts)仍然引用这些文件,列文件就会保留在磁盘上。底层的列文件只有在没有任何表(也没有任何正在运行的查询)再引用它们时才会被删除。

即时克隆,与表大小无关

克隆时间实际而言与表大小无关。

无论源表包含数百万行还是数万亿行,甚至数PB的数据,克隆都是近乎即时的

从技术上讲,克隆时间与数据分段(data parts)的数量成正比(而不是行数或字节数),因为ClickHouse只创建相应的数据分段目录(part directories)并对现有文件创建硬链接(hard links)。由于这些操作属于轻量级的文件系统操作,因此在实践中,即使对于非常大的表,克隆仍然非常快。

为了了解这在实践中是如何工作的,让我们通过可视化方式来了解。

写时复制的工作原理(可视化演练)

下面的图表说明了上面描述的数据分段级别(part-level)的写时复制行为。

第一个图表描绘了克隆表数据分段(data parts)内部的列文件(column files)如何通过硬链接(hard-linked)指向源表数据分段的列文件。

图片

为简单起见,图表只突出显示了列文件。实际上,一个数据分段(data part)还包含元数据文件,例如稀疏主索引(sparse primary index)、辅助数据跳过索引(secondary data skipping indexes)、列统计信息(column statistics)、校验和(checksums)以及最小-最大索引(min-max indexes)。数据分段目录(part directory)内的所有文件都被硬链接(hard-linked)。

当克隆表被更新时…

UPDATE cloned_orders
SET total = 3600
WHERE order_id = 36043;

…就会发生写时复制(copy-on-write)操作:

图片

如前所述,此重写发生在数据分区(part)层面:只有包含受影响行的数据分区被替换。所有其他数据分区保持共享且未被触动。

克隆表(cloned table)中受影响的数据分区被重写为一个包含更新行的新物理分区。克隆表现在引用这个新写入的分区目录,而不是其之前的目录,其列文件曾硬链接(hard-linked)自源表。

克隆表的原始分区目录,其中包含指向源数据分区列文件的硬链接,被移除(因为它已被新写入的数据分区替换)。

原始生产表(production table)继续引用其未改变的数据分区目录。

由于原始表仍引用其未改变的数据分区目录(其列文件曾硬链接自克隆表的旧分区目录),因此底层文件仍保留在磁盘上。

列文件仅在不再有任何表依赖它们时才会被移除。

这就是ClickHouse中表克隆(table cloning)背后的核心机制。

不可变性(Immutability)使得共享变得安全。分区层面(part-level)的写时复制(copy-on-write)保证了隔离性,无需重写整个表

既然我们已经了解了其底层工作原理,现在让我们退一步,看看这在实践中能实现什么。

综合应用

ClickHouse中的表克隆是一个出人意料地强大且实用的功能。

无论何时你需要一张表的精确副本

• 用于暂存环境(staging environments)

• 模式(schema)或索引(index)实验

• 数据回填(backfills)

• 迁移(migrations)

• 测试破坏性变更

你可以即时创建它,而无需冒生产数据受损的风险。

克隆表开始时是一个精确副本。它可以独立演化。只要没有数据被修改,它几乎不消耗额外的存储空间

克隆功能犹如魔法。

这是ClickHouse存储数据方式的自然结果:可安全共享的不可变数据分区(immutable parts),辅以分区层面(part-level)的写时复制(copy-on-write)语义。

由于数据分区从不在原地修改,因此它们可以被重用。由于修改总是创建新的数据分区,因此隔离性得到了保证。

其结果是提供了一个简单、安全、存储效率高且实际上与表大小无关的功能。

而在ClickHouse Cloud中,克隆表可以从隔离的计算服务(isolated compute services)中进行可选查询,因此你的生产数据和计算资源都保持未受影响。

征稿启示

面向社区长期正文,文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出&图文并茂。质量合格的文章将会发布在本公众号,优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至:Tracy.Wang@clickhouse.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值