> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ClickHouse 中 Replicated* 系列表引擎的数据复制概述

# Replicated* 系列表引擎

<Note>
  在 ClickHouse Cloud 中，复制由系统代为管理。创建表时请不要添加参数。例如，在下文中，您应将：

  ```sql theme={null}
  ENGINE = ReplicatedMergeTree(
      '/clickhouse/tables/{shard}/table_name',
      '{replica}'
  )
  ```

  替换为：

  ```sql theme={null}
  ENGINE = ReplicatedMergeTree
  ```
</Note>

复制仅支持 MergeTree 家族中的表

* ReplicatedSummingMergeTree
* ReplicatedCoalescingMergeTree
* ReplicatedVersionedCollapsingMergeTree
* ReplicatedCollapsingMergeTree
* ReplicatedGraphiteMergeTree
* ReplicatedMergeTree
* ReplicatedReplacingMergeTree
* ReplicatedAggregatingMergeTree

复制是在单表级别进行的，而不是在整个服务器级别。一个服务器可以同时存储复制表和非复制表。

复制不依赖分片。每个分片都有各自独立的复制机制。

`INSERT` 和 `ALTER` 查询中的压缩数据会被复制 (更多信息请参见 [ALTER](/docs/zh/reference/statements/alter/index) 文档).

`CREATE`、`DROP`、`ATTACH`、`DETACH` 和 `RENAME` 查询只在单台服务器上执行，不会被复制：

* `CREATE TABLE` 查询会在执行该查询的服务器上创建一个新的可复制表。如果该表已存在于其他服务器上，则会新增一个副本。
* `DROP TABLE` 查询会删除执行该查询的服务器上的副本。
* `RENAME` 查询会重命名某个副本上的表。换句话说，复制表在不同副本上可以有不同的名称。

ClickHouse 使用 [ClickHouse Keeper](/docs/zh/guides/oss/deployment-and-scaling/keeper/index) 存储副本元信息。也可以使用 3.4.5 或更高版本的 ZooKeeper，但推荐使用 ClickHouse Keeper。

要使用复制，请在服务器配置的 [zookeeper](/docs/zh/reference/settings/server-settings/settings#zookeeper) 部分中设置相关参数。

<Note>
  请勿忽视安全设置。ClickHouse 支持 ZooKeeper 安全子系统的 `digest` [ACL scheme](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_ZooKeeperAccessControl)。
</Note>

ClickHouse Keeper 集群 地址配置示例：

```xml theme={null}
<zookeeper>
    <node>
        <host>example1</host>
        <port>2181</port>
    </node>
    <node>
        <host>example2</host>
        <port>2181</port>
    </node>
    <node>
        <host>example3</host>
        <port>2181</port>
    </node>
</zookeeper>
```

ClickHouse 还支持将副本元信息存储在辅助 ZooKeeper 集群中。为此，请将 ZooKeeper 集群名称和 path 作为引擎参数传入。
换句话说，它支持将不同表的元数据存储在不同的 ZooKeeper 集群中。

设置辅助 ZooKeeper 集群地址的示例：

```xml theme={null}
<auxiliary_zookeepers>
    <zookeeper2>
        <node>
            <host>example_2_1</host>
            <port>2181</port>
        </node>
        <node>
            <host>example_2_2</host>
            <port>2181</port>
        </node>
        <node>
            <host>example_2_3</host>
            <port>2181</port>
        </node>
    </zookeeper2>
    <zookeeper3>
        <node>
            <host>example_3_1</host>
            <port>2181</port>
        </node>
    </zookeeper3>
</auxiliary_zookeepers>
```

要将表元数据存储在辅助 ZooKeeper 集群中，而不是默认的 ZooKeeper 集群中，我们可以使用 SQL 按如下方式创建使用
ReplicatedMergeTree 引擎的表：

```sql theme={null}
CREATE TABLE table_name ( ... ) ENGINE = ReplicatedMergeTree('zookeeper_name_configured_in_auxiliary_zookeepers:path', 'replica_name') ...
```

您可以指定任何现有的 ZooKeeper 集群，系统会使用其中的一个目录来存储自身数据 (该目录在创建可进行复制的表时指定) 。

如果未在配置文件中设置 ZooKeeper，您将无法创建复制表，且任何现有的复制表都将处于只读状态。

ZooKeeper 不参与 `SELECT` 查询，因为复制不会影响 `SELECT` 的性能，查询速度与非复制表相同。查询分布式复制表时，ClickHouse 的行为由设置 [max\_replica\_delay\_for\_distributed\_queries](/docs/zh/reference/settings/session-settings#max_replica_delay_for_distributed_queries) 和 [fallback\_to\_stale\_replicas\_for\_distributed\_queries](/docs/zh/reference/settings/session-settings#fallback_to_stale_replicas_for_distributed_queries) 控制。

对于每个 `INSERT` 查询，大约会通过多个事务向 ZooKeeper 添加十个条目。 (更准确地说，这是针对每个插入的数据块；一条 INSERT 查询包含一个块，或者每 `max_insert_block_size = 1048576` 行包含一个块。) 因此，与非复制表相比，`INSERT` 的延迟会略高一些。但如果遵循建议，以批次方式插入数据，并将频率控制在每秒不超过一次 `INSERT`，就不会造成任何问题。用于协调单个 ZooKeeper 集群的整个 ClickHouse 集群，总共可以达到每秒数百次 `INSERT`。数据插入的吞吐量 (即每秒插入的行数) 与非复制数据一样高。

对于非常大的集群，可以为不同分片使用不同的 ZooKeeper 集群。不过根据我们的经验，基于约 300 台服务器的生产集群来看，这并无必要。

复制是异步且多主的。`INSERT` 查询 (以及 `ALTER`) 可以发送到任何可用服务器。数据先写入执行该查询的服务器，然后再复制到其他服务器。由于是异步的，最近插入的数据会在一段延迟后才出现在其他副本上。如果部分副本不可用，则会在它们恢复可用后写入数据。如果某个副本可用，则延迟就是通过网络传输压缩数据块所需的时间。执行复制表后台任务的线程数可通过设置 [background\_schedule\_pool\_size](/docs/zh/reference/settings/server-settings/settings#background_schedule_pool_size) 进行配置。

`ReplicatedMergeTree` 引擎为复制拉取使用单独的线程池。该线程池的大小受设置 [background\_fetches\_pool\_size](/docs/zh/reference/settings/server-settings/settings#background_fetches_pool_size) 限制，可在重启服务器时调整。

默认情况下，一条 INSERT 查询只会等待一个副本确认数据写入。如果数据只成功写入一个副本，而承载该副本的服务器随后不复存在，那么已存储的数据将会丢失。若要启用多个副本的数据写入确认，请使用 `insert_quorum` 选项。

每个数据块都会以原子方式写入。INSERT 查询会被拆分为多个块，每个块最多 `max_insert_block_size = 1048576` 行。换句话说，如果 `INSERT` 查询少于 1048576 行，它就是原子执行的。

数据块会去重。对于同一个数据块的多次写入 (大小相同、包含相同行且顺序相同的数据块) ，该块只会写入一次。这样设计的原因是，在发生网络故障时，客户端应用可能不知道数据是否已写入数据库，因此可以直接重试 `INSERT` 查询。带有相同数据的 `INSERT` 发送到哪个副本并不重要。`INSERT` 是幂等的。去重参数由 [merge\_tree](/docs/zh/reference/settings/server-settings/settings#merge_tree) 服务器设置控制。

复制期间，只有待插入的源数据会通过网络传输。后续的数据转换 (合并) 会以相同方式在所有副本上协调并执行。这可以最大限度减少网络使用量，因此即使副本位于不同数据中心，复制也能良好运行。 (请注意，在不同数据中心保留数据副本正是复制的主要目标。)

同一份数据可以有任意数量的副本。根据我们的经验，在生产环境中，一个相对可靠且便于使用的方案是采用双副本复制，并让每台服务器使用 RAID-5 或 RAID-6 (某些情况下使用 RAID-10) 。

系统会监控各副本之间的数据同步状态，并能在故障后恢复。故障转移可以是自动的 (当数据差异较小时) ，也可以是半自动的 (当数据差异过大时，这可能表明存在配置错误) 。

<div id="creating-replicated-tables">
  ## 创建复制表
</div>

<Note>
  在 ClickHouse Cloud 中，复制由系统自动完成。

  创建表时，请使用不带复制参数的 [`MergeTree`](/docs/zh/reference/engines/table-engines/mergetree-family/mergetree)。系统会在内部将 [`MergeTree`](/docs/zh/reference/engines/table-engines/mergetree-family/mergetree) 重写为 [`SharedMergeTree`](/docs/zh/products/cloud/features/infrastructure/shared-merge-tree)，以实现复制和数据分布。

  请避免使用 `ReplicatedMergeTree` 或指定复制参数，因为复制由平台统一管理。
</Note>

<div id="replicatedmergetree-parameters">
  ### Replicated\*MergeTree 参数
</div>

| 参数                 | 说明                                             |
| ------------------ | ---------------------------------------------- |
| `zoo_path`         | ClickHouse Keeper 中该表的路径。                      |
| `replica_name`     | ClickHouse Keeper 中的副本名称。                      |
| `other_parameters` | 创建复制版本时所用引擎的参数，例如 `ReplacingMergeTree` 中的版本参数。 |

示例：

```sql theme={null}
CREATE TABLE table_name
(
    EventDate DateTime,
    CounterID UInt32,
    UserID UInt32,
    ver UInt16
)
ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{layer}-{shard}/table_name', '{replica}', ver)
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID);
```

<details markdown="1">
  <summary>已弃用语法示例</summary>

  ```sql theme={null}
  CREATE TABLE table_name
  (
      EventDate DateTime,
      CounterID UInt32,
      UserID UInt32
  ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/table_name', '{replica}', EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID), EventTime), 8192);
  ```
</details>

如示例所示，这些参数中可以包含 `{}` 形式的替换内容。替换后的值取自配置文件的 [macros](/docs/zh/reference/settings/server-settings/settings#macros) 部分。

示例：

```xml theme={null}
<macros>
    <shard>02</shard>
    <replica>example05-02-1</replica>
</macros>
```

ClickHouse Keeper 中的表路径对于每个 Replicated 表都应唯一。不同分片上的表应使用不同的路径。
在这种情况下，路径由以下部分组成：

`/clickhouse/tables/` 是通用前缀。我们建议严格使用这个前缀。

`{shard}` 会扩展为分片标识符。

`table_name` 是 ClickHouse Keeper 中该表对应节点的名称。最好将它设为与表名相同。之所以显式定义它，是因为它与表名不同，在执行 RENAME 查询后不会发生变化。
*提示*：你也可以在 `table_name` 前加上数据库名。例如：`db_name.table_name`

可以使用两个内置替换 `{database}` 和 `{table}`，它们会分别扩展为数据库名和表名 (除非这些宏是在 `macros` 部分中定义的) 。因此，ZooKeeper 路径可以指定为 `'/clickhouse/tables/{shard}/{database}/{table}'`。
使用这些内置替换时，要特别注意表重命名。ClickHouse Keeper 中的路径无法更改，而当表被重命名时，这些宏会扩展为不同的路径，导致该表引用 ClickHouse Keeper 中不存在的路径，并进入只读模式。

副本名称用于标识同一张表的不同副本。你可以像示例中那样使用服务器名称。该名称只需在每个分片内唯一即可。

你可以显式定义这些参数，而不是使用替换。这样做在测试以及配置小型集群时可能更方便。不过，在这种情况下你不能使用分布式 DDL 查询 (`ON CLUSTER`)。

在处理大型集群时，我们建议使用替换，因为这样可以降低出错的概率。

你可以在服务器配置文件中为 `Replicated` 表引擎指定默认参数。例如：

```xml theme={null}
<default_replica_path>/clickhouse/tables/{shard}/{database}/{table}</default_replica_path>
<default_replica_name>{replica}</default_replica_name>
```

在这种情况下，创建表时可省略参数：

```sql theme={null}
CREATE TABLE table_name (
    x UInt32
) ENGINE = ReplicatedMergeTree
ORDER BY x;
```

相当于：

```sql theme={null}
CREATE TABLE table_name (
    x UInt32
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/{database}/table_name', '{replica}')
ORDER BY x;
```

在每个副本上运行 `CREATE TABLE` 查询。该查询会创建一个新的复制表，或向现有复制表添加一个新副本。

如果在其他副本上的该表已经包含一些数据之后再添加新副本，那么运行该查询后，数据会从其他副本复制到新副本。换句话说，新副本会自动与其他副本同步。

要删除一个副本，请运行 `DROP TABLE`。但是，只会删除一个副本——也就是你运行该查询所在 server 上的那个副本。

<div id="recovery-after-failures">
  ## 故障后的恢复
</div>

如果服务器启动时 ClickHouse Keeper 不可用，复制表会切换为只读模式。系统会定期尝试连接 ClickHouse Keeper。

如果在 `INSERT` 过程中 ClickHouse Keeper 不可用，或者与 ClickHouse Keeper 交互时发生错误，则会抛出异常。

连接到 ClickHouse Keeper 后，系统会检查本地文件系统中的数据集是否与预期数据集一致 (这些信息存储在 ClickHouse Keeper 中) 。如果存在轻微不一致，系统会通过与各副本同步数据来解决。

如果系统检测到损坏的数据分区片段 (文件大小不正确) 或无法识别的 parts (已写入文件系统但未记录到 ClickHouse Keeper 中) ，会将其移动到 `detached` 子目录中 (不会删除) 。任何缺失的 parts 都会从各副本复制。

请注意，ClickHouse 不会执行任何破坏性操作，例如自动删除大量数据。

当服务器启动时 (或与 ClickHouse Keeper 建立新会话时) ，它只检查所有文件的数量和大小。如果文件大小一致，但中间某处的字节发生了变化，这种情况不会立即被检测到，而只会在尝试为 `SELECT` 查询读取数据时才会发现。该查询会抛出校验和不匹配或压缩块大小不匹配的异常。在这种情况下，数据分区片段会被加入校验队列，并在必要时从各副本复制。

如果本地数据集与预期数据集差异过大，就会触发安全机制。服务器会将此情况记入日志并拒绝启动。原因是，这种情况可能表明存在配置错误，例如某个分片上的副本被误配置成另一个分片上的副本。不过，这一机制的阈值设得相当低，因此这种情况也可能在正常的故障恢复过程中出现。在这种情况下，数据会以半自动方式恢复——即“按下一个按钮”。

要开始恢复，请在 ClickHouse Keeper 中创建节点 `/path_to_table/replica_name/flags/force_restore_data` 并写入任意内容，或者运行以下命令以恢复所有复制表：

```bash theme={null}
sudo -u clickhouse touch /var/lib/clickhouse/flags/force_restore_data
```

然后重启服务器。启动时，服务器会清除这些标志并开始恢复。

<div id="recovery-after-complete-data-loss">
  ## 完全数据丢失后的恢复
</div>

如果某台服务器上的所有数据和元数据都丢失了，请按以下步骤进行恢复：

1. 在该服务器上安装 ClickHouse。如果使用了替换，请在包含分片标识符和副本信息的 config 文件中正确进行定义。
2. 如果你有未复制的表，且这些表需要在各服务器之间手动复制，请从某个副本复制其数据 (位于目录 `/var/lib/clickhouse/data/db_name/table_name/` 中) 。
3. 从某个副本复制位于 `/var/lib/clickhouse/metadata/` 中的表定义。如果表定义中显式指定了分片或副本标识符，请将其改为与当前副本对应。 (或者，启动 server，并执行所有本应写入 `/var/lib/clickhouse/metadata/` 中 .sql 文件的 `ATTACH TABLE` 查询。)
4. 要开始恢复，请创建 ClickHouse Keeper 节点 `/path_to_table/replica_name/flags/force_restore_data` 并写入任意内容，或者运行以下命令以恢复所有复制表：`sudo -u clickhouse touch /var/lib/clickhouse/flags/force_restore_data`

然后启动 server (如果已经在运行，则重启) 。数据将从其他副本下载。

另一种恢复方式是从 ClickHouse Keeper 中删除丢失副本的信息 (`/path_to_table/replica_name`) ，然后按照“[创建复制表](#creating-replicated-tables)”中的说明重新创建该副本。

恢复期间不会限制网络带宽。如果你要同时恢复多个副本，请留意这一点。

<div id="converting-from-mergetree-to-replicatedmergetree">
  ## 从 MergeTree 转换为 ReplicatedMergeTree
</div>

这里的 `MergeTree` 一词指的是 `MergeTree 家族`中的所有表引擎，`ReplicatedMergeTree` 也是同理。

如果你有一个通过手动方式进行复制的 `MergeTree` 表，可以将其转换为复制表。如果你已经在 `MergeTree` 表中积累了大量数据，现在想启用复制，就可能需要这样做。

[ATTACH TABLE ... AS REPLICATED](/docs/zh/reference/statements/attach#attach-mergetree-table-as-replicatedmergetree) 语句允许将处于 detached 状态的 `MergeTree` 表 Attach 为 `ReplicatedMergeTree`。

如果在表的数据目录中设置了 `convert_to_replicated` 标志，`MergeTree` 表可以在 server 重启时自动转换 (对于 `Atomic` database，目录为 `/store/xxx/xxxyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy/`) 。
创建一个空的 `convert_to_replicated` 文件后，该表会在下一次 server 重启时作为 replicated 表加载。

此查询可用于获取表的数据路径。如果表有多个数据路径，则必须使用第一个。

```sql theme={null}
SELECT data_paths FROM system.tables WHERE table = 'table_name' AND database = 'database_name';
```

请注意，ReplicatedMergeTree 表会使用 `default_replica_path` 和 `default_replica_name` 设置的值来创建。
要在其他副本上创建转换后的表，需要在 `ReplicatedMergeTree` 引擎 的第一个 argument 中显式指定其 path。可以使用以下查询获取该 path。

```sql theme={null}
SELECT zookeeper_path FROM system.replicas WHERE table = 'table_name';
```

也可以手动完成此操作。

如果各个副本上的数据不一致，请先进行同步，或者删除除一个副本外所有副本上的这些数据。

先重命名现有的 MergeTree 表，然后使用原来的名称创建一个 `ReplicatedMergeTree` 表。
将旧表中的数据移动到新表数据目录 (`/var/lib/clickhouse/data/db_name/table_name/`) 下的 `detached` 子目录中。
然后在其中一个副本上运行 `ALTER TABLE ATTACH PARTITION`，将这些数据分区片段添加到活动数据集中。

<div id="converting-from-replicatedmergetree-to-mergetree">
  ## 从 ReplicatedMergeTree 转换为 MergeTree
</div>

使用 [ATTACH TABLE ... AS NOT REPLICATED](/docs/zh/reference/statements/attach#attach-mergetree-table-as-replicatedmergetree) 语句，可在单台服务器上将处于 detached 状态的 `ReplicatedMergeTree` 表附加为 `MergeTree` 表。

另一种方法需要重启服务器。先创建一个名称不同的 MergeTree 表。将 `ReplicatedMergeTree` 表数据目录中的所有数据移动到新表的数据目录中。然后删除 `ReplicatedMergeTree` 表并重启服务器。

如果你想在不启动服务器的情况下移除 `ReplicatedMergeTree` 表：

* 删除元数据目录 (`/var/lib/clickhouse/metadata/`) 中对应的 `.sql` 文件。
* 删除 ClickHouse Keeper 中对应的路径 (`/path_to_table/replica_name`) 。

完成后，你可以启动服务器，创建一个 `MergeTree` 表，将数据移动到其目录中，然后再次重启服务器。

<div id="recovery-when-metadata-in-the-zookeeper-cluster-is-lost-or-damaged">
  ## ClickHouse Keeper 集群中的元数据丢失或损坏时的恢复
</div>

如果 ClickHouse Keeper 中的数据丢失或损坏，可以按上述说明将数据移动到非复制表中，以保留数据。

**另请参阅**

* [background\_schedule\_pool\_size](/docs/zh/reference/settings/server-settings/settings#background_schedule_pool_size)
* [background\_fetches\_pool\_size](/docs/zh/reference/settings/server-settings/settings#background_fetches_pool_size)
* [execute\_merges\_on\_single\_replica\_time\_threshold](/docs/zh/reference/settings/merge-tree-settings#execute_merges_on_single_replica_time_threshold)
* [max\_replicated\_fetches\_network\_bandwidth](/docs/zh/reference/settings/merge-tree-settings#max_replicated_fetches_network_bandwidth)
* [max\_replicated\_sends\_network\_bandwidth](/docs/zh/reference/settings/merge-tree-settings#max_replicated_sends_network_bandwidth)
