<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>REPLACE PARTITION on </title>
    <link>/tags/replace-partition/</link>
    <description>Recent content in REPLACE PARTITION on </description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Wed, 16 Sep 2026 22:40:00 +0800</lastBuildDate><atom:link href="/tags/replace-partition/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>清理 ClickHouse 重复行的 REPLACE PARTITION runbook</title>
      <link>/posts/clickhouse-replace-partition-dedup-job/</link>
      <pubDate>Wed, 16 Sep 2026 22:40:00 +0800</pubDate>
      
      <guid>/posts/clickhouse-replace-partition-dedup-job/</guid>
      <description>上一次在这个集群上评审补数方案，结论里专门拦下了 REPLACE PARTITION：它会把做快照到换分区之间插进生产表的行一起覆盖掉，而那张表的老分区上确实还在进迟到行（用只读权限评审 ClickHouse 补数方案）。这次反过来用它，因为条件变了：要删的是 234 行内容逐字节相同的重复数据，来自 Kafka Connect sink 在 INSERT 超时后重投同一批（ClickHouse 里的重复行来自 Kafka Connect 超时重投），而且重复会再来、规模事前不知道，要的是一个能反复跑的 runbook。分区旧了并不代表快照风险消失，它变成下面要先验的一条。
这套 runbook 在生产上还没有执行。下面每一步都在本地一套同版本的三副本集群上跑过，实测结果在文末。
只删这一次的 234 行，lightweight delete 更便宜：重写的只是 _row_exists 那一列掩码，不碰业务列的文件，不用把 30GiB 从 S3 拉回来，也没有快照窗口的问题，代价是掩码要一直参与后续查询。按分区重写是一次性的，读侧没有额外开销，也不用改表结构，换来的是整个分区读一遍写一遍，而这个分区已经沉到 S3 层，30GiB 要拉回来，新 part 还会落到热盘上。多余行多到掩码不划算时它才占优。换 ReplacingMergeTree 这条路没走，理由在那次评审里写全了（合并异步、FINAL 是永久读开销），对账口径的表不合适。
低成本地数出有多少重复 清理之前要先知道有多少、集中在哪几个小时。直接对整个分区 GROUP BY (id, version) HAVING count() &amp;gt; 1 也能数，但这张表单个日分区两亿行、30GiB，七周前的分区已经沉到 S3 层，在生产库上这么扫一次代价太大。
换一个省的办法。重复只需要一个数字就能看出来：
SELECT count() AS rows, uniqExact(id, version) AS keys FROM events WHERE _partition_id = &amp;#39;20260730&amp;#39; AND settle_time &amp;gt;= {hour_start_ms} AND settle_time &amp;lt; {hour_start_ms} + 3600000 count() - uniqExact(.</description>
    </item>
    
  </channel>
</rss>
