「数据湖也能变实时?」揭秘 Delta Lake 到 ClickHouse 的 CDC 黑科技

图片

本文字数:15391;估计阅读时间:39 分钟

作者:Pete Hampton & Kelsey Schlarman

本文在公众号【ClickHouseInc】首发

图片

ClickHouse 的 ClickPipes 团队专注于开发高性能的托管式连接器,用于将来自各类数据源的数据高效地导入 ClickHouse。在我们已经构建了用于 Postgres(https://clickhouse.com/docs/integrations/clickpipes/postgres%20)、MySQL(https://clickhouse.com/docs/integrations/clickpipes/mysql) 和 MongoDB(https://clickhouse.com/cloud/clickpipes/mongodb-cdc-connector) 的变更数据捕获(Change Data Capture,CDC)连接器之后,我们开始探索如何支持来自数据湖的数据源的 CDC,首个目标就是 Delta Lake(https://delta.io/)。

本文将分享我们在研究 Delta Lake 的变更数据馈送(Change Data Feed,CDF)(https://docs.delta.io/latest/delta-change-data-feed.html) 过程中积累的经验与实践。同时,我们也 开源了一个基于 MIT 许可证的参考实现,使用 Python 将 Delta Lake 的 CDC 数据同步到 ClickHouse。(https://github.com/ClickHouse/deltalake-cdc)

未来几个月内,我们计划在 ClickPipes 中加入生产可用级别的 Delta Lake CDC 支持。如果你希望作为设计合作伙伴参与其中,欢迎发送邮件至 [clickpipes@clickhouse.com](https://clickhouse.com/blog/) 与我们联系。

Delta Lake 与 ClickHouse

Delta Lake 是构建在对象存储之上的事务性存储层,特别适合处理 PB 级别的大规模数据摄取与计算。而 ClickHouse 则是一个开源的列式数据库,专为高性能分析查询优化设计。两者结合,可以充分发挥各自优势:Delta Lake 适用于湖仓架构中的存储与管理需求,而 ClickHouse 能提供快速的实时数据访问。

目前,ClickHouse 已支持通过 DeltaLake 或 deltaLakeCluster 表引擎以只读方式查询 Delta Lake 中的 Parquet 数据,同时也在推进 写入能力的支持(https://github.com/ClickHouse/ClickHouse/issues/79603)。

CREATE TABLE my_delta_table
ENGINE = DeltaLake('s3://path/to/deltalake/table', 'access-key', 'secret-key')

ClickHouse 会根据 Delta Lake 的元数据自动推断出表结构,用户可以直接执行查询操作:

SELECT col1, col2, col3, _file, _time, …etc 
FROM my_delta_table

对于更灵活的使用场景,还可以通过表函数 deltaLake 和 deltaLakeCluster 就地查询数据:

SELECT col1, col2, col3, _file, _time, …etc 
FROM deltaLake('s3://path/to/deltalake/table', 'access-key', 'secret-key')

当数据主要是冷数据、访问频率较低时,使用 ClickHouse 作为 Delta 表的查询层是一个不错的选择。如果你能够接受远程对象存储读取带来的性能影响,同时不希望在湖仓之间复制数据,这种方案非常合适。

当然,也有用户对读取的延迟要求较高,比如用于 API 接口、数据看板、推荐系统或其他用户端应用的实时查询场景。即便结合了缓存等优化策略,远程对象存储的读取成本和速度依然难以满足这类需求,此时将数据同步至 ClickHouse,可为这类应用提供更可靠、更高效的访问路径。

变更数据捕获(Change Data Capture,CDC)是一种在获取初始数据快照后,仅同步 Delta Lake 表中增量变更的机制。这种方式可以显著减少数据传输量,并确保 ClickHouse 能以极低的延迟始终保持与 Delta 表中数据状态的一致性。

CDC 管道的核心组成部分

要构建一个从 Delta Lake 到 ClickHouse 的 CDC(变更数据捕获)数据管道,通常需要以下几个关键组件:

  • Delta Lake 表: 数据原始存储的位置,作为数据湖的来源。

  • 变更数据馈送(Change Data Feed,CDF): Delta Lake 用于捕捉行级数据变更的机制。

  • ClickHouse: 数据的落地目标数据库,用于实现实时分析与访问。

数据建模

考虑到上游数据状态是以最终一致性的方式进行同步,我们选用了 ClickHouse 的 ReplacingMergeTree 表引擎。该引擎是 MergeTree 的一种变体,尤其适用于 CDC 场景,能够有效处理重复或乱序的数据写入(比如先插入、后删除、再更新的操作顺序)。这一策略基本 遵循了我们此前针对 Postgres 所提出的数据建模建议(https://clickhouse.com/blog/postgres-to-clickhouse-data-modeling-tips-v2)。

下方是一个示例 DDL,其中以 name 和 age 作为排序键,Delta Lake CDF 中的 _commit_version 则作为版本控制字段。如果你的数据是追加写入(即不包含更新或删除操作),使用常规 MergeTree 表则能带来更好的查询性能。

CREATE TABLE default.new_cdc_table
(
    `id` String,
    `name` String,
    `age` Int64,
    `created_at` DateTime,
    `_change_type` String,
    `_commit_version` Int64,
    `_commit_timestamp` DateTime
)
ENGINE = ReplacingMergeTree(`_commit_version`)
PARTITION BY toYYYYMM(`created_at`)
ORDER BY (`name`, `age`)

该表结构通过主键实现数据去重。当在后台合并过程中发现重复记录时,ClickHouse 会保留每组主键唯一组合中的一条数据。如果设置了版本字段(这里是 _commit_version),ClickHouse 会优先保留提交版本号最大的那条记录。若未指定版本字段,则默认保留最后插入的记录——这种行为通常不适合 CDC 这种依赖最终一致性的场景。

这种数据建模模式已被广泛应用,也是我们在 ClickPipes 和 PeerDB 实现 Postgres(https://clickhouse.com/docs/integrations/clickpipes/postgres) 与 MySQL(https://clickhouse.com/docs/integrations/clickpipes/mysql) CDC 功能时所采用的方式。

在使用 ReplacingMergeTree 处理 CDC 数据时,还有一些需特别注意的点。首先是合并时机问题——与传统数据库的 UPSERT 不同,重复数据在插入时不会立刻被消除,而是在 ClickHouse 后台触发的合并操作中完成去重。为获取去重后的结果,可以在查询中使用 FINAL 关键字。

另一个常见情况是关于删除记录的处理。由于采用软删除机制,被标记删除的记录仍然会以原始值或默认值保留在 ClickHouse 表中。目前我们的示例脚本尚不支持对删除事件的完整处理。

Delta Lake 中的数据准备

我们提供了一个 示例数据加载脚本(https://github.com/ClickHouse/deltalake-cdc?tab=readme-ov-file
#1-generate-sample-data),用于创建一个未分区的 Delta Lake 表(对于超大数据集建议启用分区),并通过持续流的方式生成模拟数据。数据的生成采用了 Polars 这个 DataFrame 库,能够按指定的批量大小生成记录。

从性能角度来看,该脚本一次可以生成并写入 20,000 条记录(约 10MB 数据)到 Delta Lake 表中。在配置为 2 核 CPU 和 4 GB 内存的 c5.large AWS EC2 实例上(与目标 S3 存储桶处于同一地区),这些数据每秒即可生成一次。这个批量大小是可调的,脚本也可以在普通笔记本等低成本硬件上运行。

数据加载脚本会在 Delta Lake 表不存在时自动创建表,并在创建的同时启用变更数据馈送(Change Data Feed,CDF)功能。启用 CDF 对写入性能几乎没有影响。当表中累计了足够多的提交记录后,即可将这些变更数据导入 ClickHouse。

变更数据捕获

根据数据源及使用协议的不同,实现 CDC 的难度也各不相同。与 Postgres 这样的传统事务型数据库相比,Delta Lake 和 Apache Iceberg 等开放表格式(Open Table Formats,OTF)在 CDC 实现上更具挑战性。

Postgres 内部维护了预写日志(Write-Ahead Log,WAL),用于在事务边界内按顺序记录每次数据变更。而开放表格式则无法依赖这种机制,它们必须通过解析文件层级的操作,从分布式存储中重建出完整的变更流。这就依赖于元数据层和提交版本之间的差异对比,从而追踪数据变更。这样的实现方式在保持变更顺序、处理并发写入,以及确保下游系统能够准确识别不同版本之间的变化方面都存在一定难度。

好消息是,Delta Lake 提供了 变更数据馈送(Change Data Feed,CDF)(https://docs.delta.io/latest/delta-change-data-feed.html) 功能,它支持基于行级别的数据捕获,并可供 ClickHouse 和 ClickPipes 等下游系统进行消费处理。CDF 中的变更数据与事务一起被写入,并在新数据提交后立即可见。

你可以在创建 Delta Lake 表时启用 CDF 功能:

CREATE TABLE my_cdc_table (id STRING, name STRING, age INT, created_at TIMESTAMP) TBLPROPERTIES (delta.enableChangeDataFeed = true)

或者也可以对已有的表按如下方式启用:

ALTER TABLE my_cdc_table SET TBLPROPERTIES (delta.enableChangeDataFeed = true)

与传统关系型数据库的 CDC 机制不同,Delta Lake 的 CDF(变更数据馈送)是一种逻辑上的抽象结构,它并不是直接从物理存储中读取变更记录,而是通过多个组件组合而成的查询结果。一旦数据被提交,它就会记录在事务日志中(位于 _delta_log 目录),该日志引用了相关的数据片段,可被读取为一系列批量记录流。这种方式适用于仅包含插入(INSERT)操作的追加型数据流程。如下图所示,变更记录包含了特定时间段内的变更数据。

Delta Lake 利用 最小值和最大值的统计信息跳过不包含变更的数据文件(https://docs.databricks.com/aws/en/delta/data-skipping),这大大减少了处理变更事件时所需读取的数据量。

事务日志示例如下:

{
  "add": {
    "path": "part-00001-bcb81f71-8bd9-4626-9186-0580428941d0-c000.snappy.parquet",
    "partitionValues": {},
    "size": 489819,
    "modificationTime": 1752068897913,
    "dataChange": true,
    "stats": {
      "numRecords": 10000,
      "minValues": {
        "id": "000bcedc-d231-40f7-9897-189010d9d7f5",
        "name": "AABCX",
        "age": 18,
        "created_at": "2025-07-09T13:48:13.648587Z"
      },
      "maxValues": {
        "id": "fff6a0e8-bccb-46be-9af4-0d7d3c5d37172",
        "name": "ZZZTR",
        "age": 65,
        "created_at": "2025-07-09T13:48:13.650625Z"
      },
      "nullCount": {
        "id": 0,
        "name": 0,
        "age": 0,
        "created_at": 0
      }
    },
    "tags": null,
    "baseRowId": null,
    "defaultRowCommitVersion": null,
    "clusteringProvider": null
  }
}
{
  "commitInfo": {
    "timestamp": 1752068897913,
    "operation": "WRITE",
    "operationParameters": {
      "mode": "Append"
    },
    "clientVersion": "delta-rs.py-1.0.2",
    "operationMetrics": {
      "execution_time_ms": 1213,
      "num_added_files": 1,
      "num_added_rows": 10000,
      "num_partitions": 0,
      "num_removed_files": 0
    }
  }
}

当表中发生更新(UPDATE)或删除(DELETE)操作时,变更内容会被写入到 _change_data 子目录中,这些目录中保存的都是仅包含变更记录的 Parquet 文件。如果事务中包含更新操作,事务日志会呈现如下结构:

{ "add": { "path": "part-00001-bf9a82c5-0eda-433a-a2e6-8ec7c8757f23-c000.snappy.parquet", ... }}
{ "cdc": { "path": "_change_data/part-00001-5482b246-deb0-4338-a82c-d56a32d38ac3-c000.snappy.parquet", ... }}
{ "add": { "path": "part-00001-e653da14-92ee-4b6d-a03d-86333a1a56a5-c000.snappy.parquet", ... }}
{ "cdc": { "path": "_change_data/part-00001-3acf8a12-71aa-4f0c-8c61-e24046633a98-c000.snappy.parquet", ... }}
{ "remove": { "path": "part-00001-ec06b026-ca3b-48ec-8687-bafd2706a877-c000.snappy.parquet", ... }}
{ "remove": { "path": "part-00001-93252f4b-72ae-4bf3-8be0-ae2031441ab8-c000.snappy.parquet", ... }}
{ "commitInfo": { "timestamp": 1753784155897, "operation": "MERGE", ... }}

这个示例表示执行了一次 merge 操作,根据条件 target.id = source.id,将 100 条源数据与目标表中的 100 条记录进行了匹配更新。系统生成了 2 个新的数据文件用于存储更新后的数据,同时也生成了 2 个 CDC 文件来追踪变更内容,并删除了 2 个旧数据文件。

要查看变更事件对应的文件,可以直接读取原始数据。这些数据也可以通过 ClickHouse 查询,方式如下:

select _file from s3(
   's3://path/to/deltalake/table/_change_data/*.parquet',
   '[HIDDEN]',
   '[HIDDEN]'
)
part-00001-08d3c1e5-87e9-4455-8b48-ea7398277003-c000.snappy.parquet
part-00001-08d3c1e5-87e9-4455-8b48-ea7398277003-c000.snappy.parquet
part-00001-08d3c1e5-87e9-4455-8b48-ea7398277003-c000.snappy.parquet
part-00001-08d3c1e5-87e9-4455-8b48-ea7398277003-c000.snappy.parquet
part-00001-08d3c1e5-87e9-4455-8b48-ea7398277003-c000.snappy.parquet
part-00001-08d3c1e5-87e9-4455-8b48-ea7398277003-c000.snappy.parquet
part-00001-08d3c1e5-87e9-4455-8b48-ea7398277003-c000.snappy.parquet
part-00001-08d3c1e5-87e9-4455-8b48-ea7398277003-c000.snappy.parquet
…

ClickHouse 支持直接查询 _change_data 路径下的文件:

select *, _file, _time from s3(
   's3://path/to/deltalake/table/_change_data/*.parquet',
   '[HIDDEN]',
   '[HIDDEN]'
)
431cbe31-e33f-4a8c-9d4d-5d0b0f1b38c1	XUBSG	53	2025-07-09 14:38:21.646311	update_preimage	part-00001-08d3c1e5-87e9-4455-8b48-ea7398277003-c000.snappy.parquet	2025-07-28 13:04:52
431cbe31-e33f-4a8c-9d4d-5d0b0f1b38c1	WDMGN	43	2025-07-28 13:03:49.198168	update_postimage	part-00001-08d3c1e5-87e9-4455-8b48-ea7398277003-c000.snappy.parquet	2025-07-28 13:04:52
07edd0c9-adc2-4110-8cc3-448b99ec1900	OJSSL	29	2025-07-09 13:48:50.254035	update_preimage	part-00001-08d3c1e5-87e9-4455-8b48-ea7398277003-c000.snappy.parquet	2025-07-28 13:04:52
07edd0c9-adc2-4110-8cc3-448b99ec1900	GEKKK	38	2025-07-28 13:03:49.198218	update_postimage	part-00001-08d3c1e5-87e9-4455-8b48-ea7398277003-c000.snappy.parquet	2025-07-28 13:04:52
…
286e746a-eb0f-4474-a72f-0323a95b79d9	TURGH	46	2025-07-09 13:56:11.702183	delete	part-00001-8421b723-9407-47aa-826e-1c594698e5cc-c000.snappy.parquet	2025-07-28 13:05:55
26c54093-5aae-4bf6-93a8-58912ec41655	SFYVN	53	2025-07-09 14:15:59.204611	delete	part-00001-8421b723-9407-47aa-826e-1c594698e5cc-c000.snappy.parquet	2025-07-28 13:05:55
26c54093-5aae-4bf6-93a8-58912ec41655	SFYVN	53	2025-07-09 14:15:59.204611	delete	part-00001-8421b723-9407-47aa-826e-1c594698e5cc-c000.snappy.parquet	2025-07-28 13:05:55

在更新场景中,通常会记录两个变更事件:update_preimage 表示更新前的原始数据,而 update_postimage 则表示更新后的结果。对于删除操作,CDC 只记录一条变更事件,即被删除的那一行数据。下游系统也可以据此进行相应处理。

这些变更记录可进一步写入 ClickHouse。与前文提到的元字段类似,我们在写入数据时会一并添加 _change_type、_commit_version 和 _commit_type 等字段。结合前一节中定义的表结构,这些字段可以帮助我们在遇到更新操作时解析出数据的最新状态。

总体建议是,尽可能利用 ClickHouse 的 ReplacingMergeTree 表引擎来处理数据的最终一致性,而不是在代码逻辑或数据源层面进行版本冲突的比对与解析,因为那样不仅效率低,而且资源消耗大。

Delta Lake 到 ClickHouse 的 CDC Python 原型

我们提供了一个 参考实现(https://github.com/ClickHouse/deltalake-cdc),使用 DeltaLake 的 Python 库(https://pypi.org/project/deltalake/),该库基于 delta-rs(https://github.com/delta-io/delta-rs) 的绑定,支持从 Delta Lake 读取数据。

整体流程是这样的:程序会遍历用户指定的版本范围(从起始版本开始持续轮询),逐个读取对应版本的事务日志,并将其中的操作归类为 CDC 变更、添加记录或删除记录。针对不同类型的文件,系统会构建对应的 DataFusion 执行计划,最终合并三类操作,并生成包含 CDF 元数据字段(如 _change_type、_commit_version、_commit_timestamp)的最终输出表结构。

底层库支持按版本或时间戳消费 CDF 数据,但我们的原型目前只实现了按版本方式。每个版本提交的变更数量不同,因此一次读取到的记录数也会有所差异。为保持性能稳定,我们采用约 80,000 条为一批次的处理策略。在收集到一个批次的变更数据后,就将其写入 ClickHouse。

在一台配置为 2 核 vCPU、4 GiB 内存、10 Gbps 网络带宽的 c5.large AWS EC2 实例上(前提是 Delta Lake 的 S3 存储桶和 ClickHouse 实例处于同一区域),我们的参考程序可以在约 1.2 秒内将这一批次的数据写入 ClickHouse。即使将实例配置升级为更高规格,吞吐量也未显著提升。

为了进一步优化性能,我们可以引入多个并发写入进程,根据实际读取到的数据量,将数据按 1 万到 10 万条的批次写入 ClickHouse。这种策略特别适用于像批处理任务引起的突发性数据写入场景,而非持续不断的数据流写入。

$ python main.py 
 -p "s3://path/to/deltalake/table" 
 -r "us-east-2"
 -t "default.my_cdc_table"
 -H "[service].us-east-2.aws.clickhouse.cloud"
 -v 28000
 -b 80000
2025-07-23 10:56:54,143 - INFO - Using provided AWS credentials
2025-07-23 10:56:54,625 - INFO - Starting continuous processing from version 28000
2025-07-23 10:56:55,064 - INFO - Processing changes from version 28000 to 28252
2025-07-23 10:57:02,185 - INFO - Processed batch 1: 81920 rows (total: 81920) in 1.55 seconds
2025-07-23 10:57:03,411 - INFO - Processed batch 2: 83616 rows (total: 165536) in 1.23 seconds
2025-07-23 10:57:04,535 - INFO - Processed batch 3: 83616 rows (total: 249152) in 1.12 seconds
2025-07-23 10:57:05,651 - INFO - Processed batch 4: 81920 rows (total: 331072) in 1.12 seconds
2025-07-23 10:57:06,754 - INFO - Processed batch 5: 81920 rows (total: 412992) in 1.10 seconds
...

快照处理

在数据湖中实现变更捕获是一项技术挑战,特别是在表原本没有启用 CDF 功能的情况下,若要对历史数据进行补录(backfill)就需要额外的处理步骤。

如果在创建 Delta Lake 表时就启用了 CDF,那么可以从版本 0 开始读取变更数据,无需额外进行快照。不过,当表的版本数非常多时,从头读取效率可能较低。

Delta Lake 每 100 次提交会自动写入一次检查点,作为当前 Delta 表的聚合状态。我们可以利用这些检查点,从指定版本开始查询,从而更高效地进行数据补录。如果表中包含了更新或删除操作,还可以借助 Delta 的数据跳过(data-skipping)能力,进一步减少读取的数据量。

许多 ClickHouse 用户同时使用数据湖,他们的表通常达到 TB 甚至 PB 级别。这么大的数据量如果一次性导入,负担会很大。因此,可以通过 ClickHouse 的异步处理机制分批迁移,使用如下查询语句即可:

CREATE TABLE my_delta_table
   ENGINE = DeltaLakeCluster('"s3://path/to/deltalake/table"');

INSERT INTO
 `default`.`my_cdc_table`
SELECT
 *, 'snapshot', 0, now()
FROM
 `default`.`my_delta_table``
LIMIT 2000000000;

这个 INSERT INTO SELECT 查询会将写入的数据附带元数据列,例如 snapshot、0 和 now(),以便 ReplacingMergeTree 表引擎能够正确识别上游的实际状态,并在后台自动执行数据合并。

作为性能参考,这条查询曾将 20 亿行数据从一个 Delta Lake 表导入到 ClickHouse Cloud 服务(未开启自动扩缩容)中,且数据源与 ClickHouse 服务部署在同一区域。整个过程中内存使用稳定在 700MB 至 2GB 之间。之所以能达到如此高效的性能,主要得益于 Delta Lake 表函数背后使用的对象存储引擎,它们支持并行读取与预取操作。本次快照总耗时 1,936 秒,约合 32 分钟。

图片

这条查询几乎不会对 ClickHouse 集群的资源造成负担。若希望进一步提高吞吐量,还可以通过增加查询使用的线程数量来优化。

图片

另一个显著优势是:如果 Delta Lake 表基于对象存储,那么这些读取操作不会像其他 CDC ClickPipes 那样对源系统造成压力。在传统场景中,一旦读取不当,可能导致数据库负载骤升,甚至宕机。而使用 Delta Lake 时,你可以放心地进行读取,例如通过以下查询验证数据是否已成功写入 ClickHouse:

SELECT
   hostName(),
   database,
   table,
   sum(rows) AS rows,
   formatReadableSize(sum(bytes_on_disk)) AS total_bytes_on_disk,
   formatReadableSize(sum(data_compressed_bytes)) AS total_data_compressed_bytes,
   formatReadableSize(sum(data_uncompressed_bytes)) AS total_data_uncompressed_bytes,
round(sum(data_compressed_bytes) / sum(data_uncompressed_bytes), 3) AS compression_ratio
FROM system.parts
WHERE database != 'system'
GROUP BY
   hostName(),
   database,
   table
ORDER BY sum(bytes_on_disk) DESC FORMAT Vertical
hostName():                    c-creamaws-by-66-server-pzltzls-0
database:                      default
table:                         new_cdc_table
rows:                          2000000000 -- 2.00 billion
total_bytes_on_disk:           45.44 GiB
total_data_compressed_bytes:   45.43 GiB
total_data_uncompressed_bytes: 141.56 GiB
compression_ratio:             0.321

若希望进一步提升快照性能,还可以同时运行多条类似的 INSERT INTO SELECT 查询,每条查询分别处理一个分区(前提是数据已做分区)。若未做分区,系统则需推断一个合适的字段作为分区键。这虽然需要额外跟踪每批处理,但换来的性能提升是值得的,代价仅是更高的 CPU 和内存消耗。

将这种快照方案与前述 CDF 读取流程结合使用,还有一个非常有价值的特点:两者可以同时运行,不需要串行等待。这与传统 RDBMS 的 CDC ClickPipes 不同——后者一般要先做完整快照,再开始消费 CDC 日志。例如在 MySQL 中,由于缺乏标准化的快照或复制槽机制,无法确保并行处理快照与 binlog 时事件的时间顺序一致。

而 Delta Lake CDC 并不存在这个问题。即使某个表需要重新同步,也不会影响其他表的 CDF 消费过程,因此可以显著提升整个系统跨表的变更数据处理吞吐量。

参考实现的局限性

本项目中的参考实现属于原型阶段,主要用于探索构建生产级 ClickPipe 的技术路线,因此仍存在一些明显的限制。它目前尚未适合直接投入生产环境使用,但提供了一个可拓展的起点,方便你集成到已有的数据编排与处理系统中,例如 Spark、Airflow 等,以实现从 Delta Lake 到 ClickHouse 的数据同步或变更捕获(CDC)流程。

容错机制

当前在启动 CDC 流程时,用户需要手动指定起始版本号。如果脚本运行过程中出现错误中断,用户必须记录上次处理的版本,并手动重启或恢复脚本。偏移量(offset)管理是所有 CDC ClickPipe 的关键能力之一。

同样地,对于大规模快照任务来说,如果中间某个查询因故失败,也需要用户能够恢复到失败前的位置继续执行。

关于更新与删除操作  

我们将更新(UPDATE)和删除(DELETE)视为插入(INSERT)操作,并通过 ReplacingMergeTree 表来实现数据的最终一致性。这种方式虽然不是强一致,但已被验证在生产环境中稳定可靠。

需要注意的是,ClickHouse 作为列式数据库,起初并不专注于高频行级更新操作。但 从 2025 年 6 月发布的 v25.7 开始,ClickHouse 已支持高性能的标准 SQL UPDATE 语法(https://clickhouse.com/blog/updates-in-clickhouse-2-sql-style-updates)。

在目前所有 ClickPipe CDC 连接器中,我们都采用了 ReplacingMergeTree,并在多种使用场景中表现出优异的性能,因此这一设计也延续到了该实现中。

模式演进支持

初始快照阶段通常不会遇到 schema(模式)变更问题,因为表结构在那时是固定的。但 Delta Lake 的 CDF 机制在处理表结构演进时仍有一定局限,尤其是在进行非增量更改(例如重命名列、删除字段、修改数据类型)时,这些 DDL 操作不会记录在 CDF 协议中,从而导致 CDC 流程中断。

因此,用户需要特别留意表结构变化的时机安排。否则可能需要暂停 CDC,同步最新结构,再重新开始数据消费

一种缓解方案是借助 catalog 系统来统一管理表结构。我们当前脚本仅支持使用 IAM 凭证进行访问,但为了适配更多企业用户需求,未来将考虑支持包括 Amazon Glue、Open Metadata、Hive MetaStore 和 Unity Catalog 在内的多种 catalog 机制以及不同的认证协议。

删除支持

在 CDC 管道中,处理删除操作是一个重要功能。对于快照过程来说,删除记录并不需要特别处理,因为 Delta Lake 表具备 ACID 属性,删除操作已在快照数据中体现。

目前,我们的原型实现暂时忽略了变更日志中的删除记录。未来,我们计划像在其他 CDC ClickPipes 中一样,引入软删除机制:即在标记删除的记录中,保留主键和相关元数据字段,而其他字段则会被重置为 ClickHouse 类型对应的默认值。

出于性能考虑,我们建议不要使用 null 值来表示墓碑记录,因为这会对压缩效率产生负面影响。

后续改进计划

在构建这个原型的过程中,我们也发现了 ClickHouse 当前在对接 Delta 表时的一些限制。例如,理想情况下我们希望能够直接执行如下查询

SELECT * FROM table_changes(‘default.my_delta_table’, 5, 10);

即可返回版本 5 到版本 10 之间发生的变更数据。又或者能够像当前 ClickHouse 对 Iceberg 支持那样,直接查询某个特定快照:

SELECT * FROM deltaLakeCluster(‘s3://path/to/deltalake/table’) SETTINGS snapshot_version=5;

幸运的是,这些能力即将加入 ClickHouse:#73704(https://github.com/ClickHouse/ClickHouse/issues/73704) 和 #85070(https://github.com/ClickHouse/ClickHouse/issues/85070) 将为 CDC 场景提供更丰富的查询能力。

总结

构建一个 基于 Delta Lake 的 CDC 数据管道同步到 ClickHouse(https://github.com/ClickHouse/deltalake-cdc),为打造实时分析型应用提供了强有力的技术方案。通过结合两种技术的优势,并合理规划数据架构与落地方式,企业可以实现可靠、可扩展的数据同步体系,从而更快获取洞察力,并做出基于最新数据的决策。

目前,ClickPipes(https://clickhouse.com/cloud/clickpipes) 已经为 Postgres(https://clickhouse.com/docs/integrations/clickpipes/postgres%20)、MySQL(https://clickhouse.com/docs/integrations/clickpipes/mysql) 和 MongoDB(https://clickhouse.com/cloud/clickpipes/mongodb-cdc-connector) 提供了稳定的 CDC 连接器,也支持从 对象存储(https://clickhouse.com/docs/integrations/clickpipes/object-storage%20) 和 流式数据源(https://clickhouse.com/docs/integrations/clickpipes/kafka/) 进行数据接入。

我们计划在未来几个月内,正式推出 Delta Lake CDC 在 ClickPipes 中的生产级支持。如果你希望作为设计合作伙伴参与共建,欢迎发送邮件至 [clickpipes@clickhouse.com](https://clickhouse.com/blog/) 与我们联系。

征稿启示

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值