<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>ReplacingMergeTree on </title>
    <link>/tags/replacingmergetree/</link>
    <description>Recent content in ReplacingMergeTree on </description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Sun, 11 Oct 2026 08:40:00 +0800</lastBuildDate><atom:link href="/tags/replacingmergetree/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>在线把明细表换成 ReplicatedReplacingMergeTree</title>
      <link>/posts/clickhouse-replacing-engine-migration/</link>
      <pubDate>Sun, 11 Oct 2026 08:40:00 +0800</pubDate>
      
      <guid>/posts/clickhouse-replacing-engine-migration/</guid>
      <description>背景 去重方案的主体是把明细表从 ReplicatedMergeTree 换成 ReplicatedReplacingMergeTree(create_time)，键不变。难处在于这张表一直在被 Kafka Connect 的 sink 写，两周前的分区还会被人工补发写进几千行。要做到四件事：不丢、不重、能回滚，sink 只停很短的一会儿。
下面的步骤在 clickhouse-lab 的实验 27 里整套跑过：25.3.14.14，三副本，库引擎和 Aiven 上的现有集群一样是 Replicated，sink 用脚本模拟、一直在写，中途还人工补发了一次。还没在现有集群上执行。
前提有五条，后四条和现有集群有关，动手前逐条过一遍：
只换引擎，别的一概不动。ATTACH PARTITION … FROM 和 REPLACE PARTITION 要求两张表结构相同，分区键、ORDER BY、主键、存储策略相同，新表包含旧表全部的跳数索引和 projection（ATTACH PARTITION FROM）。默认值和列序文档没列，照旧表抄是为了稳妥。现在那张明细表上还有一条 create_time 满 45 天搬到对象存储的 TTL，新表也要带上，不然切换之后的新数据不再往冷层搬；这条 TTL 在 Aiven 上看不到建表语句，是从 system.parts 的 move_ttl_info 读出来的，lab 没有对象存储，这一项没验过。排序键或分区键也要改的话，只能 INSERT … SELECT 重写数据，这份步骤不适用。 SYSTEM STOP MERGES 要能用，行数闸靠它。Aiven 的文档没提这条语句，只说主账号 avnadmin 拥有服务开放的全部权限、用户不能再给它加权限（Manage users and roles）。执行前用 SHOW GRANTS FINAL（连同角色带来的权限一起列）查一下，结果里要有 SYSTEM MERGES，或者覆盖它的 SYSTEM、ALL；后面用到的 SYSTEM SYNC REPLICA、SYSTEM FLUSH LOGS 也要在里面。 SYSTEM 语句在 Replicated 库里不会自动传播，下面写成 ON CLUSTER default。Aiven 的文档以前写着不支持 ON CLUSTER，2026-05-28 删掉了这一条，改成对 Replicated 库的 DDL 不必写（Query a database、aiven-docs #1585），示例里的集群名是 default。SYSTEM 语句文档没提，开始前先跑一条没有副作用的 SYSTEM FLUSH LOGS ON CLUSTER default，三个节点都返回、状态都是 0 再往下走。不能用的话，要连到每个节点各跑一遍，STOP MERGES 只落在一个副本上，行数闸就不成立了。 现有集群的 default profile 把 distributed_ddl_output_mode 设成了 null_status_on_timeout：带 ON CLUSTER 的语句在某个节点上超过 distributed_ddl_task_timeout 还没执行完，不报错，返回里那个节点的状态是 NULL（Settings.</description>
    </item>
    
    <item>
      <title>ClickHouse 明细表的去重方案</title>
      <link>/posts/clickhouse-dedup-solution/</link>
      <pubDate>Sun, 11 Oct 2026 08:30:00 +0800</pubDate>
      
      <guid>/posts/clickhouse-dedup-solution/</guid>
      <description>背景 这条链路是业务服务写 Kafka（单 topic、8 个分区，峰值每秒约 1 万条），Confluent Cloud 托管的 ClickHouse sink（clickhouse-kafka-connect v1.3.9）消费后写进 Aiven 上的三副本 ClickHouse 25.3。sink 是至少一次投递，没开 exactlyOnce；明细表是 ReplicatedMergeTree，ORDER BY (settle_ms, id, rev)，按结算日分区。
现有集群上见过三种重复，都是除写入时间 create_time 之外整行相同：
来源 规模 特征 producer 重试 一百多行 集中在一个 45 分钟的窗口里，每个键正好 2 份，broker 里存了两条 offset 不同的消息 sink 超时重投 两百多行 同一批原样再写一次，间隔 32 到 124 秒 sink 重启重放 几万行 几分钟内的一段 offset 整段重放，批次边界会变 人工补发是第四个入口，那次补发前先按键查过 ClickHouse，没有补出新的重复。
超时重投怎么产生、为什么没被服务端拦下，见超时重投和块级去重窗口；producer 重试和重启重放见四个入口。这篇回答的是选了什么方案：明细表在对账口径上没有重复，下游报表不因为重复多算，出了重复能发现、能修。
方案在 clickhouse-lab 的三副本实验台上验过（实验 02、03、14、18、21、23、24、25、27），还没在现有集群上执行。
为什么不靠 sink 的 exactlyOnce 官方 sink 有一个 exactlyOnce 开关，按 offset 区间加一张 KeeperMap 状态表判断一批该不该写。lab 上用真的 Kafka Connect 测过两件事（实验 21、23）：</description>
    </item>
    
    <item>
      <title>ClickHouse 的查询路径和 FINAL 的代价</title>
      <link>/posts/clickhouse-query-path-final/</link>
      <pubDate>Sun, 11 Oct 2026 08:10:00 +0800</pubDate>
      
      <guid>/posts/clickhouse-query-path-final/</guid>
      <description>背景 明细表 events 按结算日分区，单日从一千多万行到三亿多行不等。两亿行那套部署一个日分区约 30 GiB，按全表落盘字节除以行数算，每行落盘 120 到 150 字节。表的键是在 Aiven 上现有集群的 system.tables 里查到的；列和跳数索引出自一份较早的建表语句（34 列、8 个跳数索引），现表有没有变还没核。下面列出和查询有关的部分：
CREATE TABLE events ( id String, -- ……共 34 列，金额是五个 Decimal(20, 8)，维度列是租户、币种、来源等 tenant_id UInt32, currency_id UInt32, start_ms UInt64, settle_ms UInt64, create_time DateTime DEFAULT now(), INDEX settle_ms_minmax_idx settle_ms TYPE minmax GRANULARITY 12, INDEX start_ms_minmax_idx start_ms TYPE minmax GRANULARITY 12, INDEX tenant_id_bf_idx tenant_id TYPE bloom_filter GRANULARITY 4, INDEX currency_id_bf_idx currency_id TYPE bloom_filter GRANULARITY 4, INDEX src_id_bf_idx src_id TYPE bloom_filter GRANULARITY 4, INDEX create_time_minmax_idx create_time TYPE minmax GRANULARITY 12, INDEX id_bf_idx id TYPE bloom_filter GRANULARITY 12, INDEX batch_src_ref_bf_idx (batch_ref, src_ref) TYPE bloom_filter GRANULARITY 12 ) ENGINE = ReplicatedMergeTree PARTITION BY toYYYYMMDD(toDateTime(settle_ms / 1000)) ORDER BY (settle_ms, id, rev) PRIMARY KEY (settle_ms, id) SETTINGS index_granularity = 8192; 从排序键和跳数索引看，查询大致有三类：按结算时间范围取一段明细或做对账；按 id 查单笔；按租户、币种、来源筛。ClickHouse 处理它们的思路一样：尽量不读不需要的数据。要跳过数据，有三层可用，跳完之后只读用到的列。这篇按这个顺序过一遍，最后单独看明细换成 ReplacingMergeTree 之后，读的时候要付的 FINAL 代价。</description>
    </item>
    
  </channel>
</rss>
