惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
The Hacker News
The Hacker News
D
Darknet – Hacking Tools, Hacker News & Cyber Security
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
L
Lohrmann on Cybersecurity
MyScale Blog
MyScale Blog
GbyAI
GbyAI
博客园 - 【当耐特】
J
Java Code Geeks
Jina AI
Jina AI
I
InfoQ
The Register - Security
The Register - Security
Cyberwarzone
Cyberwarzone
Scott Helme
Scott Helme
S
Schneier on Security
WordPress大学
WordPress大学
月光博客
月光博客
C
Cybersecurity and Infrastructure Security Agency CISA
AWS News Blog
AWS News Blog
The Cloudflare Blog
Google DeepMind News
Google DeepMind News
P
Privacy & Cybersecurity Law Blog
P
Proofpoint News Feed
T
The Exploit Database - CXSecurity.com
Blog — PlanetScale
Blog — PlanetScale
博客园 - Franky
Simon Willison's Weblog
Simon Willison's Weblog
T
Threatpost
Project Zero
Project Zero
I
Intezer
Know Your Adversary
Know Your Adversary
A
About on SuperTechFans
S
Security Affairs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 聂微东
P
Proofpoint News Feed
Cloudbric
Cloudbric
V2EX - 技术
V2EX - 技术
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
IT之家
IT之家
Forbes - Security
Forbes - Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The Last Watchdog
The Last Watchdog
H
Heimdal Security Blog
Schneier on Security
Schneier on Security
Webroot Blog
Webroot Blog
Apple Machine Learning Research
Apple Machine Learning Research

土法炼钢兴趣小组的算法知识备份

国密算法与国密 TLS 系列索引 【系统架构设计】架构质量属性:不只是"高可用高性能" 【系统架构设计百科】告警策略:如何避免"狼来了" 【系统架构设计】CQRS:读写分离的架构哲学 【系统架构设计】空间架构:极端扩展场景的解法 【系统架构设计】微服务架构深度审视:优势、代价与适用边界 【系统架构设计】扩展性原理:水平、垂直与对角扩展 【系统架构设计】无状态设计:扩展的第一步也是最难的一步 【系统架构设计】缓存架构:从本地到分布式的多级缓存体系 【系统架构设计】管道与过滤器:Unix 哲学的架构表达 【系统架构设计】复杂性管理:架构的核心战场 【系统架构设计】消息队列架构:异步解耦的设计与陷阱 【系统架构设计】CDN 架构:全球加速的设计原理 【系统架构设计】连接池设计:被忽视的性能杀手 【系统架构设计】弹性设计模式:熔断器、舱壁与超时 【系统架构设计】高可用设计模式:冗余、故障转移与仲裁 【系统架构设计】容量规划:从拍脑袋到数据驱动 【系统架构设计】数据库扩展:分库分表的工程实践与替代方案 【系统架构设计】SLO 工程:可靠性的量化管理 【系统架构设计】性能建模:用数学思维分析系统瓶颈 【系统架构设计】混沌工程:主动验证系统的韧性 【系统架构设计】零拷贝与内存映射:数据搬运的极致优化 【系统架构设计】线程模型:从 thread-per-request 到协程 【系统架构设计】容灾架构:多活与灾备设计 【系统架构设计】数据库性能模式:索引、查询与连接管理 【系统架构设计】数据建模:从关系范式到文档模型的真实权衡 【系统架构设计】吞吐量优化:批处理、流水线与并发模型 【系统架构设计】流处理架构:从批处理到实时的范式迁移 【系统架构设计】搜索引擎架构:倒排索引之上的系统设计 【系统架构设计】时序数据架构:监控与 IoT 的存储设计 【系统架构设计】数据迁移与版本化:在线不停机的数据演进 【系统架构设计】数据湖与数据仓库:分析架构的演进路线 【系统架构设计】API 网关设计:入口层的职责边界 【系统架构设计】应用层数据一致性模式:在正确性与性能之间走钢丝 【系统架构设计】多模数据库选型:Polyglot Persistence 的工程实践 【系统架构设计】服务发现与注册:动态拓扑的基础设施 【系统架构设计】配置管理架构:从配置文件到配置中心 【系统架构设计】全链路压测:大规模系统的性能验证 【系统架构设计】幂等性设计:分布式环境下的安全重试 【系统架构设计】契约测试与 Schema 演进:服务间的信任协议 【系统架构设计】长连接与推送架构:WebSocket、SSE 与 MQTT 【系统架构设计】延迟分析:从 P50 到 P999 的全链路追踪 【系统架构设计百科】DDD 战术模式:聚合、实体与值对象 【系统架构设计百科】防腐层与开放主机服务:系统集成的 DDD 方案 【系统架构设计百科】领域事件与事件风暴:从业务到架构的桥梁 【系统架构设计百科】CQRS + Event Sourcing 完整实战:从领域建模到部署 【系统架构设计百科】DDD 与微服务:用领域模型划分服务边界 【系统架构设计】DDD 战略设计:限界上下文与上下文映射 【系统架构设计百科】认证架构:从 Session 到 JWT 到 OIDC 【系统架构设计】API 设计哲学:REST vs GraphQL vs gRPC 的真实权衡 排序算法专题:从 TimSort 到并行排序 【密码学百科】国密算法体系:SM2/SM3/SM4/SM9 全景解读 【密码学百科】承诺方案:Pedersen 承诺、向量承诺与多项式承诺 【密码学百科】不经意传输与隐私信息检索:OT、OT 扩展与 PIR 【密码学百科】门限密码学:门限签名、门限解密与分布式密钥生成 完美哈希:从理论到 gperf 实践 【密码学百科】安全多方计算:从 Yao 的混淆电路到实用 MPC 【密码学百科】同态加密:从 Paillier 到全同态加密(FHE) 【密码学百科】零知识证明系统:zk-SNARKs、zk-STARKs 与 Bulletproofs 【密码学百科】概率论与密码分析:生日攻击、差分分析与线性分析 【密码学百科】计算复杂性与归约:密码安全性证明的基石 【密码学百科】秘密共享:Shamir 方案、VSS 与安全多方计算入口 【密码学百科】椭圆曲线代数:Weierstrass 方程、点群运算与曲线选择 【密码学百科】离散对数与配对密码学:从 DLP 到 BLS 签名 【密码学百科】格密码数学基础:SVP、LWE 与格基约化 【密码学百科】抽象代数:群、环、域的密码学视角 【密码学百科】有限域算术:GF(2^n) 运算与在 AES/ECC 中的应用 【密码学百科】数论进阶:二次剩余、椭圆曲线上的 Weil 配对 【密码学百科】密码学简史:从凯撒密码到量子时代 【密码学百科】威胁模型与安全目标:CIA 三要素之外 【密码学百科】Kerckhoffs 原则与现代密码设计哲学 【密码学百科】随机性:密码学的基石 【密码学百科】信息论入门:熵、完美保密与 Shannon 定理 【密码学百科】分组密码原理:Feistel 网络与 SPN 结构 【密码学百科】AES 逐步拆解:SubBytes 到 MixColumns 的数学 【密码学百科】分组密码工作模式全览:ECB/CBC/CTR/OFB/CFB 【密码学百科】流密码:RC4 的兴衰与 ChaCha20 的崛起 【密码学百科】密码学哈希函数:MD5→SHA-2→SHA-3 的进化之路 【密码学百科】MAC 与 HMAC:消息认证的正确姿势 【密码学百科】认证加密(AEAD):GCM、ChaCha20-Poly1305 与 OCB 【密码学百科】密钥派生函数:HKDF、PBKDF2、Argon2 与密码存储 【密码学百科】公钥密码的数论基础:模运算、群、原根 【密码学百科】RSA 从原理到攻击:教科书 RSA 为什么不安全 【密码学百科】Diffie-Hellman 密钥交换与离散对数问题 【密码学百科】椭圆曲线密码学(ECC):从几何直觉到点群运算 【密码学百科】数字签名:ECDSA、EdDSA 与 Schnorr 签名 【密码学百科】现代密钥交换:X25519、ECDHE 与前向保密 【密码学百科】混合加密与 KEM/DEM 范式:ECIES 与 HPKE 【密码学百科】填充方案:PKCS#1 v1.5、OAEP 与 PSS 【密码学百科】TLS 协议全解析:从握手到 0-RTT 【密码学百科】PKI 与数字证书:信任链的构建与崩塌 【密码学百科】密码认证协议:从 SRP 到 OPAQUE 【密码学百科】零知识证明入门:如何证明你知道而不泄露 【密码学百科】安全信道构造:Noise 协议框架与 Signal 协议 【密码学百科】密钥管理工程:HSM、KMS 与密钥生命周期 【密码学百科】侧信道攻击:从时序攻击到功耗分析 【密码学百科】密码学实现陷阱:三层漏洞分类、审计工具链与系统性预防 密码敏捷性:如何设计可升级的密码系统 【密码学百科】OpenSSL/BoringSSL 架构剖析:ENGINE、Provider 与 FIPS 模块 排序基准测试:用数据说话
【列存引擎内核】Distributed 引擎与分布式查询路由
Liao Tonglang · 2026-06-18 · via 土法炼钢兴趣小组的算法知识备份

单节点 MergeTree 能撑住不少 OLAP 场景,但日志、指标、宽表事实一旦超过单机磁盘或 CPU 上限,就要把 同一张逻辑表 切到多个 shard 上。ClickHouse 的 Distributed 引擎不是「又一种存储格式」,而是 查询与写入的路由层:本地表(MergeTree / ReplicatedMergeTree)才真正持有 Part;Distributed 表只保存集群拓扑与分片函数。

ReplicatedMergeTree(第 08 篇) 解决 副本一致性与 HA;本文解决 水平分片与跨节点查询。读完应能回答:分片键怎么选、INSERT 落到哪、SELECT 如何在 shard 上并行、何时必须 GLOBAL IN,以及和 PostgreSQL Citus 的本质差异。

版本锚定:ClickHouse 24.x LTS。集群协调默认写法以 ClickHouse Keeper(兼容 ZooKeeper 协议)为准;仍使用 ZooKeeper 的集群概念相同,路径前缀不同。


一、三层对象:Cluster、Local、Distributed

典型生产拓扑:

flowchart TB
  subgraph client [客户端]
    CHC[clickhouse-client / JDBC]
  end
  subgraph coord [协调节点 可选]
    DT[Distributed 表 dist.events]
  end
  subgraph shard1 [Shard 1]
    L1[本地表 events_local]
    R1[Replica A / B]
  end
  subgraph shard2 [Shard 2]
    L2[本地表 events_local]
    R2[Replica A / B]
  end
  CHC --> DT
  DT -->|INSERT 按 sharding key| L1
  DT -->|INSERT| L2
  DT -->|SELECT 下发子查询| L1
  DT -->|SELECT| L2
  L1 --- R1
  L2 --- R2
对象 引擎 磁盘数据 典型命名
本地表 ReplicatedMergeTree 有 Part events_local
分布式表 Distributed 无 Part,仅元数据 events
集群定义 配置 <cluster> in config.xml

关键结论Distributed 表是 视图 + 路由器,不是第三套存储。所有列存文件格式仍遵循 MergeTree Part 格式(第 02 篇)

1.1 最小 DDL 示例

-- 在每个 shard 的每个 replica 上创建本地表(简化,省略 ON CLUSTER)
CREATE TABLE events_local ON CLUSTER '{cluster}'
(
    event_date Date,
    user_id    UInt64,
    event_type LowCardinality(String),
    payload    String
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events_local', '{replica}')
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id);

-- 在「入口」库创建 Distributed 表(可在任意节点,常放在统一 query 节点)
CREATE TABLE events ON CLUSTER '{cluster}'
AS events_local
ENGINE = Distributed('{cluster}', currentDatabase(), events_local, cityHash64(user_id));

第三参数 events_local各 shard 上的本地表名;第四参数 cityHash64(user_id)分片键表达式

1.2 宏 {cluster}{shard}{replica}

ON CLUSTERReplicatedMergeTree ZK 路径依赖 macros(通常在 /etc/clickhouse-server/config.d/macros.xml):

<clickhouse>
    <macros>
        <cluster>prod</cluster>
        <shard>01</shard>
        <replica>replica_a</replica>
    </macros>
</clickhouse>

每个物理节点 macros 不同;ON CLUSTER prod 会把 DDL 广播到 remote_serversprod 的所有 host。工程判断:macros 与 remote_servers 不一致是分布式集群最常见的「表存在但路由错 shard」根因。


二、集群配置:remote_servers 与副本权重

config.xml(或 config.d/cluster.xml)定义逻辑 cluster:

<remote_servers>
    <prod>
        <shard>
            <internal_replication>true</internal_replication>
            <replica>
                <host>ch1.example.com</host>
                <port>9000</port>
            </replica>
            <replica>
                <host>ch2.example.com</host>
                <port>9000</port>
            </replica>
        </shard>
        <shard>
            <internal_replication>true</internal_replication>
            <replica>
                <host>ch3.example.com</host>
                <port>9000</port>
            </replica>
            <replica>
                <host>ch4.example.com</host>
                <port>9000</port>
            </replica>
        </shard>
    </prod>
</remote_servers>
字段 含义
shard 水平分片;不同 shard 各持数据子集
replica 同一 shard 内的副本;internal_replication=true 时写入走副本协议
internal_replication true:Distributed INSERT 只写一份到 shard 内一 replica,由副本链复制
weight 可选;非均匀 shard 容量时调整查询/写入权重

与第 08 篇衔接internal_replication=true 时,Distributed 引擎 不向同一 shard 的所有 replica 各写一份——否则 ReplacingMergeTree 去重语义会被打破。写入目标由 insert_replica 或负载策略选择。


三、分片键:数据分布与查询局部性

分片键是 Distributed 表 DDL 的第四个参数(或 SHARDING_KEY 别名语法,视版本文档)。ClickHouse 计算:

\[ \text{shard\_index} = \bigl(\text{hash}(\text{sharding\_expr}) \bmod N_{\text{shards}}\bigr) + 1 \]

常用 hash:cityHash64sipHash64jumpConsistentHash(扩容场景)。

3.1 选型原则

目标 推荐分片键 风险
用户级查询局部性 cityHash64(user_id) 超大用户热点 shard
时间范围 + 实体 (event_date, user_id) 组合 hash 表达式过复杂难调试
近似均匀 高基数 UUID / 雪花 ID 无法按业务键 colocate
日志按天 不推荐toDate(ts) 单日流量打满单 shard

工程判断:分片键首要保证 写入均匀;其次考虑 最常见 WHERE 能否只扫单 shard。ClickHouse 没有 Citus 的「distribution column = 主键必须」硬约束,但选错键的代价一样:跨 shard 聚合、GLOBAL JOIN 爆炸。

3.2 与 PARTITION BY 正交

  • PARTITION BY(本地表):Part 目录按月/日切 —— 影响 merge、TTL、删分区。
  • 分片键:行落到哪个 shard —— 影响分布式路由。

同一 event_date 的行可以分布在所有 shard(分片键含 user_id 时)。常见误区:把 PARTITION BY toYYYYMM(d) 误当分片键,导致每月一个 shard 热点。

3.3 扩容与 resharding

增加 shard 后,旧数据 不会自动重分片。官方路径:

  1. 新 shard 建空本地表;
  2. 历史数据 INSERT SELECTclickhouse-copier / 第三方工具迁移;
  3. 双写或读路径合并。

jumpConsistentHash 在扩 shard 时比 naive mod 更少迁移行——仍是工程级迁移,无在线透明 resharding(与 Citus rebalance 对比见 §九)。


四、INSERT 路由

INSERT INTO events ... 进入 Distributed 表时,协调节点:

  1. 按 block 计算每行的 sharding expr;
  2. 按 shard 分组;
  3. 向各 shard 发送 sub-insert(internal_replication=true 时每个 shard 选一个 replica)。
sequenceDiagram
  participant C as Client
  participant D as Distributed 协调
  participant S1 as Shard 1 replica
  participant S2 as Shard 2 replica
  C->>D: INSERT block
  D->>D: 按 cityHash64 分组
  D->>S1: INSERT 子 block
  D->>S2: INSERT 子 block
  S1->>S1: ReplicatedMergeTree 副本链
  S2->>S2: ReplicatedMergeTree 副本链

4.1 批量与 async_insert

小 batch 高频 INSERT 会在 每个 shard 各产生 small parts——放大 too many parts(第 15 篇) 风险。缓解:

  • 客户端攒 batch(建议 ≥ 10k 行,视列宽调整);
  • 表级 async_insert + wait_for_async_insert
  • 入口 Buffer 表(注意 Buffer 自身坑,见官方 Buffer table)。

不在此给出吞吐数字——须在本集群测 system.eventsInsertedRows / InsertedBytes

4.2 insert_distributed_sync

insert_distributed_sync = 1(session 或 profile)时,Distributed INSERT 等待所有 shard 子 insert 完成再返回。默认异步更快但客户端难以及时捕获单 shard 失败。

4.3 直接写本地表

ETL 若已知 target shard,可绕过 Distributed 直写 events_local——须保证 不会写错 shard。运维脚本常用;应用层一般仍写 Distributed。


五、SELECT:子查询下发与二次聚合

SELECT ... FROM events 的典型计划:

  1. 协调节点把查询改写成对 每个 shard 的本地表 的子查询(可能带相同 WHERE);
  2. 各 shard 并行执行(受 max_threads、副本选择影响);
  3. 协调节点对 shard 结果 再聚合SUMCOUNT 等)。

这与 向量化执行(第 04 篇) 的 pipeline 在协调节点再串一层 Merge 算子

5.1 副本读:load_balancing

remote_servers 里 replica 的读取策略由 settings 控制,例如:

  • load_balancing = random(默认类行为)
  • load_balancing = nearest_hostname(同 rack 优先)
  • load_balancing = in_order(故障转移)

只读副本readonly 用户 + 副本延迟监控)可把 analytics 流量从 leader 拉开——延迟见 system.replicas(第 14 篇)。

5.2 谓词下推与限制

Distributed 会尝试把 WHEREPREWHERE(见 查询读取路径(第 05 篇))下推到 shard 子查询。但:

  • 依赖分片键 无法 剪枝 shard(仍扫全 cluster);
  • 某些函数/UDF 可能阻止下推;
  • LIMIT 下推需 optimize_skip_unused_shards 等优化配合。
-- 若 sharding key 含 user_id,且 WHERE user_id = 123 常数,可只打单 shard(视优化器版本)
SET optimize_skip_unused_shards = 1;
SELECT count() FROM events WHERE user_id = 123 AND event_date >= today() - 7;

是否生效以 EXPLAIN / EXPLAIN PLAN 为准——须本地执行,本文不粘贴计划文本。

5.3 GLOBAL 二次聚合边界

shard 本地执行 GROUP BY k 后,协调节点收到 部分聚合状态,可能再 GROUP BY k 合并。若聚合函数非 decomposable(如 quantileExact 中间态不能简单相加),会触发不同 plan 或报错——设计分布式 SQL 时要查函数是否 combinable


六、GLOBAL IN 与 GLOBAL JOIN

6.1 普通 IN 的问题

SELECT * FROM events
WHERE user_id IN (SELECT user_id FROM dim_vip_users);

dim_vip_users仅存在于协调节点 的小表,或未 Distributed 复制,默认 IN 可能在 每个 shard 子查询里本地执行子查询——子表为空或不全,结果错误。

6.2 GLOBAL IN

SELECT * FROM events
WHERE user_id GLOBAL IN (SELECT user_id FROM dim_vip_users);

语义:先在 协调节点 执行子查询,把结果 广播 到每个 shard,再各 shard 过滤。

模式 网络 内存 适用
IN 子表已复制到各 shard
GLOBAL IN 高(广播) 协调节点持全集 小子表、临时过滤集
JOIN + 复制引擎 各 shard 本地 维表定期同步

代价:GLOBAL IN 把协调节点变成 单点内存瓶颈——维表百万行级可能 OOM(max_memory_usage)。

6.3 GLOBAL JOIN

SELECT e.*, d.name
FROM events AS e
GLOBAL INNER JOIN dim_users AS d ON e.user_id = d.id
WHERE e.event_date >= today() - 1;

协调节点先拉 dim_users(或子计划),广播到 shard 与 events_local join。宽维表 + 事实表大 scan 时 极慢

替代架构(工程上更常见):

  1. 维表复制:每个 shard 建相同 ReplicatedMergeTree 维表,定时 INSERT SELECT 同步;查询用普通 JOIN
  2. DictionaryENGINE = Dictionary + dictGet):适合小维表、键值查找。
  3. 预 join 宽表:ETL 阶段打平——牺牲存储换查询简单。

6.4 distributed_product_mode

global / local / allow 控制分布式 JOIN 行为(见官方 Distributed product mode)。误用 local 可能导致 shard 间笛卡尔积——经典事故模式,第 15 篇会列 symptom。


七、Distributed DDL 与 ON CLUSTER

CREATE TABLE metrics_local ON CLUSTER prod (...);
CREATE TABLE metrics ON CLUSTER prod AS metrics_local
ENGINE = Distributed(prod, currentDatabase(), metrics_local, rand());
操作 注意
CREATE ... ON CLUSTER 依赖 DDLWorker + Keeper/ZK 队列;失败会留 system.distributed_ddl_queue
DROP TABLE 先删 Distributed 再删 local,或反之需团队规范
RENAME 24.x 支持有限;生产倾向建新表迁移
ALTER mutation 各 shard 独立 mutation 队列——大表 ALTER 在 N shard 上放大为 N 倍后台压力

检查 DDL 队列:

SELECT entry, query, initiator, status, exception_code, exception_text
FROM system.distributed_ddl_queue
ORDER BY entry DESC
LIMIT 20;

八、源码与实现锚点

ClickHouse 24.x 源码(A 级):

路径 职责
src/Storages/StorageDistributed.cpp Distributed 存储引擎入口
src/Storages/Distributed/DistributedSink.cpp INSERT 路由与发送
src/QueryPipeline/RemoteQueryExecutor.cpp 远程 subquery 执行
src/Interpreters/Cluster.cpp cluster 拓扑解析
src/Interpreters/DDLWorker.cpp ON CLUSTER DDL

阅读 StorageDistributed::readDistributedSink::write 可串起 读写下推 全路径。


九、与 PostgreSQL Citus / 其它分布式边界

维度 ClickHouse Distributed Citus
定位 OLAP 分片 + 路由 PG 扩展,OLTP+HTAP
分片单位 Part(列存) 行 / 分片表
跨 shard JOIN GLOBAL / 复制维表 Colocated / repartition
事务 无跨 shard 单事务 有限分布式事务
Reshard 离线迁移为主 rebalance_table_shards
协调节点 任意能访问 cluster 的 CH 节点 Coordinator

PostgreSQL 内核系列 的 B-Tree、MVCC 语义 不迁移 到 ClickHouse——用 Citus 做 PG 水平扩展,用 ClickHouse 做分析副本或独立 OLAP,是常见分工(第 13 篇 展开选型)。

不写 Spark/Flink 全链路——PLAN 边界外。


十、设计模式与反模式

10.1 推荐模式

星型 + 复制维表

  • 事实表 Distributed + cityHash64(user_id)
  • 维表每 shard 完整副本(小表 ReplicatedMergeTree);
  • 查询普通 JOIN,避免 GLOBAL。

专用集群入口

  • BI 只连 query 节点(无本地 heavy data);
  • 写入走 load balancer 到多入口 Distributed;
  • 减少业务直连 random shard host。

与 Kafka + MV 配合

  • 物化视图(第 10 篇)
  • Kafka Engine 表 不要 直接 Distributed——通常 Kafka → MV → 本地 MergeTree,Distributed 只包本地表。

10.2 反模式

反模式 后果
分片键 rand() 且无业务过滤 任何 GROUP BY 全 cluster 扫描
大维表 GLOBAL JOIN 协调节点 OOM、查询超时
每行 INSERT N shard × small parts → merge 崩溃
忽略 internal_replication 双写 replica 重复数据、副本冲突
Distributed 当唯一备份 无 Part 文件,恢复从 local 表

十一、排查清单

  1. 数据倾斜system.partshostName() / 表统计各 shard 行数(需集群级监控或逐 shard 查)。
  2. 查询慢system.query_logread_rowsProfileEventsDistributedConnection*
  3. INSERT 失败单 shardinsert_distributed_sync=1 复现;查目标 shard system.errors
  4. DDL 卡住system.distributed_ddl_queue + Keeper 连通。
  5. 结果不一致:检查 IN vs GLOBAL IN、维表是否全 shard 同步。

详细系统表见 监控(第 14 篇)


十二、实验框架(须本地执行)

以下命令框架 未在本环境执行——读者在测试集群验证。

12.1 验证分片分布

-- 在每个 shard 的 events_local 上分别执行,对比 count()
SELECT count() FROM events_local;

-- 或通过 Distributed 查 system 表(若配置允许)
SELECT hostName(), count()
FROM cluster('prod', currentDatabase(), events_local)
GROUP BY hostName();

12.2 对比 IN 与 GLOBAL IN 计划

EXPLAIN PLAN
SELECT count() FROM events
WHERE user_id IN (SELECT user_id FROM dim WHERE tier = 'vip');

EXPLAIN PLAN
SELECT count() FROM events
WHERE user_id GLOBAL IN (SELECT user_id FROM dim WHERE tier = 'vip');

对比 Expression 节点是否含 GLOBAL 广播——以本地输出为准

12.3 制造 skew 观察

-- 测试库:故意用常量分片键观察单 shard 膨胀(勿在生产)
CREATE TABLE skew_test_local (...) ENGINE = MergeTree ...;
CREATE TABLE skew_test AS skew_test_local
ENGINE = Distributed('test_cluster', currentDatabase(), skew_test_local, 1);  -- 常数键

十三、配置项速查

Setting 作用
optimize_skip_unused_shards 常量分片键过滤时跳过无关 shard
optimize_skip_unused_shards_limit 跳过优化的 shard 数上限
insert_distributed_sync 同步等待各 shard insert
distributed_product_mode JOIN 分布式行为
max_query_size 大 IN 列表解析
distributed_group_by_no_merge 高级:控制二次 GROUP BY
prefer_localhost_replica 协调节点本地 replica 优先

完整容量相关见 配置与容量(第 16 篇)


十四、小结

问题 答案
Distributed 存数据吗? 否,只路由
分片键影响什么? INSERT 目标 shard、部分查询剪枝
GLOBAL IN 何时用? 小子表未复制到各 shard
与副本关系? internal_replication=true 时 INSERT 不双写 replica
扩容? 离线 resharding,无透明 rebalance

附录 A、GLOBAL JOIN 改写清单

原写法 问题 改写
GLOBAL JOIN 大维表 协调节点 OOM 每 shard 复制维表 + 普通 JOIN
IN (SELECT ...) 协调库小表 shard 子查询为空 GLOBAL IN 或复制
两 fact 表 JOIN 跨 shard _shuffle 预聚合 / 同 sharding key colocate
DISTINCT 子查询过滤 广播大 set JOIN 复制表或 Dictionary

附录 B、分片键评审问卷

  1. 写入 QPS 与 batch 大小?小 batch 则先优化 ingest 再谈分片数。
  2. Top 10 查询是否带 sharding key 等值过滤?
  3. 是否存在「大用户」skew?是否需要 salt(如 cityHash64(user_id, bucket_id))?
  4. 扩容计划:是否接受 offline copy?
  5. 维表大小:是否适合每 shard 全量复制?

附录 C、网络与防火墙

Distributed 查询在 shard 间建立 大量 outbound 连接(端口通常 9000/native 或 9440/TLS)。协调节点到各 shard、shard 间 interserver_http(复制)需放通。跨 AZ 部署时 RTT 增加会放大 scatter-gather 延迟——无量化延迟,但架构上应同 AZ 放 query 与 data。

附录 D、术语表

术语 含义
Scatter-gather 协调节点下发子查询再合并
Sharding key Distributed 第四参数表达式
Global subquery 协调执行后广播到 shard

阅读 EXPLAIN PLANEXPLAIN 时关注:Distributed 节点子计划是否含 GLOBALReadFromStorage 是否只在 subset shard;聚合是否两层 Aggregating。若见 Double aggregation,确认 combinator 正确。协调节点 limit 下推失败会导致各 shard 返回全量再 truncate——高 limit 也危险。

附录 F、双集群 DR

Active-passive 两 cluster 靠对象存储或 replication queue 同步——非 Distributed 内建。DR 演练测 RPO/RTO 用业务指标,本文不写 SLA 数字。

附录 G、负载均衡 insert

多入口节点写同一 Distributed 表时,各入口独立路由——无 central coordinator。入口前 L4 LB 即可;避免 sticky 导致单入口热点,除非 cache 亲和需要。

附录 · 深度补充(系列交叉索引)

深度 1

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 2

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 3

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 4

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 5

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 6

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 7

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 8

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 9

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 10

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 11

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 12

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 13

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 14

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 15

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 16

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 17

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 18

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 19

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 20

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 21

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 22

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 23

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。

深度 24

第 09 章与系列其它篇章的交叉:读路径见 第 05 篇;merge 见 第 06 篇;索引见 第 07 篇;副本见 第 08 篇;Distributed 见 第 09 篇;监控见 第 14 篇;故障见 第 15 篇;配置见 第 16 篇。 实施任何 setting 变更前,在 staging 用 system.parts / query_log 建立 24h 基线,变更后再对比——避免无证据调参。


上一篇ReplicatedMergeTree

下一篇物化视图与增量管道

参考资料

  1. ClickHouse Documentation, Distributed table engine, clickhouse.com/docs, 24.x
  2. ClickHouse Documentation, Distributed DDL, GLOBAL IN/JOIN
  3. ClickHouse Source, release-24.x, src/Storages/StorageDistributed.cpp
  4. ClickHouse Source, src/Storages/Distributed/DistributedSink.cpp
  5. Citus Documentation, Distributed Tables — 对照边界

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-06-18 · database / distributed

【列存引擎内核】ReplicatedMergeTree

ReplicatedMergeTree 副本协调:Log entry、ClickHouse Keeper/ZooKeeper 路径、副本同步与 recovery。双节点实验步骤(本环境未部署)。24.x LTS 默认推荐 Keeper。

2026-06-18 · database / storage

【列存引擎内核】ClickHouse 与 DuckDB 源码级拆解

主选 ClickHouse 拆解 MergeTree 存储格式、向量化执行与分布式协调;DuckDB 作为嵌入式 OLAP 对照。覆盖列存文件布局、merge 机制、跳数索引与生产故障模式,面向数据平台工程师与从 PG/MySQL 转 OLAP 的 DBA。

2026-06-18 · database / storage

【列存引擎内核】列存基础与 ClickHouse 架构

行存 vs 列存的带宽、压缩与向量化三角;ClickHouse Server 进程模型、线程池与 MergeTree 引擎家族地图;src/Storages 与 src/Processors 源码入口。对照 PG 行存与 LSM 写优化路径,版本锚定 ClickHouse 24.x LTS。

2026-06-18 · database / storage

【列存引擎内核】MergeTree Part 文件格式

ClickHouse MergeTree Part 目录结构:columns.txt、checksums.txt、.bin、.mrk2、primary.idx 语义,Granule 与 Mark 的定位作用,Wide/Compact 布局与 MergeTreeDataPart 源码入口。版本锚定 24.x LTS。