上一篇【第52篇】ClickHouse熔断机制_系统过载保护策略
下一篇【第54篇】ClickHouse服务监控_系统表与监控指标体系


摘要

本文是《ClickHouse原理解析与应用实践》系列博客的第53篇文章,深入解析ClickHouse生态中的各类数据备份方案。与传统关系型数据库(如MySQL)相比,ClickHouse基于列式存储与MergeTree引擎架构,其备份策略的设计复杂度远超简单的逻辑导出——需要同时考虑数据分片、副本同步、增量备份、零丢失恢复等多个维度。本文将系统梳理ClickHouse 22.4+原生BACKUP/RESTORE命令、FREEZE PARTITION快照备份、第三方开源工具clickhouse-backup以及逻辑导出等多种方案,并通过对比表格与实战案例,帮助读者构建一套完整的ClickHouse数据保护体系。

关键词:ClickHouse备份、BACKUP RESTORE、FREEZE PARTITION、clickhouse-backup、S3、增量备份、快照备份


1. 引言

1.1 为什么ClickHouse备份比MySQL更复杂

在MySQL等传统关系型数据库中,数据通常以行式存储在独立的表空间中,备份方案(如mysqldump或XtraBackup)已经非常成熟,生态工具链丰富。然而,ClickHouse的设计哲学与上述系统存在本质差异,理解这些差异是制定有效备份策略的前提。

ClickHouse的数据存储具有以下特点:第一,数据按列压缩存储在多个*.bin文件中,每个列独立一个文件,数据按mark(标记)分组;第二,MergeTree引擎持续进行后台合并操作(merge),同一分区的多个数据部件(parts)会合并为更大的parts;第三,ClickHouse支持表级与数据库级的副本复制(ReplicatedMergeTree),备份过程中需要考虑副本间的一致性;第四,ClickHouse支持多种存储后端(本地磁盘、S3、HDFS、Azure Blob Storage),备份目标位置的选择直接影响恢复粒度和成本。

因此,ClickHouse的备份不能简单套用MySQL的经验,而需要根据数据规模、恢复时效要求、存储成本等因素综合选择方案。本文的第6节将给出各方案的对比分析,帮助读者做出决策。


2. BACKUP/RESTORE命令(ClickHouse 22.4+原生备份)

2.1 语法概述

从ClickHouse 22.4版本开始,官方引入了原生的BACKUPRESTORE命令,这是迄今为止最完善的原生备份机制。相比之前的FREEZE方案,BACKUP/RESTORE支持增量备份粒度、原子性操作、细粒度表级控制,并且支持通过ON CLUSTER语法一次性备份整个集群。

基本语法如下:

-- 备份整个数据库
BACKUP DATABASE default TO Disk('backup_disk', 'backup_name_20260519')

-- 备份指定表
BACKUP TABLE mydb.events TO Disk('backup_disk', 'backup_name_20260519')

-- 备份多个表
BACKUP TABLE mydb.events, mydb.metrics TO Disk('backup_disk', 'backup_name_20260519')

-- 带密码加密的备份(24.3+新增功能)
BACKUP TABLE mydb.events TO Disk('backup_disk', 'backup_name_encrypted')
SETTINGS password = 'secure_password_123'

恢复操作使用RESTORE命令:

-- 恢复到新表(默认行为,不覆盖已存在的表)
RESTORE TABLE mydb.events FROM Disk('backup_disk', 'backup_name_20260519')

-- 恢复到指定位置
RESTORE TABLE mydb.events TO mydb.events_restored
FROM Disk('backup_disk', 'backup_name_20260519')

-- 如果目标表已存在,可以替换
RESTORE TABLE mydb.events AS mydb.events_restored
FROM Disk('backup_disk', 'backup_name_20260519')

-- 恢复整个数据库
RESTORE DATABASE default FROM Disk('backup_disk', 'backup_name_20260519')

2.2 增量备份(BASE BACKUP语法)

原生BACKUP命令支持增量备份,这是通过BASE BACKUP参数实现的。首次全量备份后,后续增量备份只存储自上次备份以来发生变化的数据块,极大节省了存储空间和备份时间。

-- 第一次全量备份(BASE BACKUP)
BACKUP DATABASE analytics TO Disk('backup_disk', 'base_backup_v1')
SETTINGS base_backup = NULL  -- 表示这是全量基准备份

-- 第二次增量备份
BACKUP DATABASE analytics TO Disk('backup_disk', 'incremental_backup_v2')
SETTINGS base_backup = Disk('backup_disk', 'base_backup_v1')

-- 第三次增量备份(基于前一次增量)
BACKUP DATABASE analytics TO Disk('backup_disk', 'incremental_backup_v3')
SETTINGS base_backup = Disk('backup_disk', 'incremental_backup_v2')

增量备份的工作原理基于ClickHouse内部的数据块标识。每个MergeTree表的数据变更都会产生新的parts或对现有parts进行合并,base_backup参数告诉ClickHouse只备份那些在基准备份之后新产生或被修改的parts。这种方式对大型表的增量备份尤为有效——假设一张表有10亿行数据,其中只有100万行发生了变更,增量备份只需处理这100万行的数据块,而非整个10亿行。

2.3 备份元数据与数据文件

当执行BACKUP命令时,ClickHouse在备份目标位置生成以下结构:

backup_name_20260519/
├── metadata/                          -- 元数据目录
│   ├── default/
│   │   ├── events.sql                -- 表结构DDL
│   │   └── metrics.json              -- 元信息
│   └── ...
├── data/                              -- 数据目录
│   ├── default/
│   │   └── events/                    -- 各表数据
│   │       ├── part_uuid_1/
│   │       │   ├── columns.bin
│   │       │   ├── primary.idx
│   │       │   ├── checksums.txt
│   │       │   └── ...
│   │       └── part_uuid_2/
│   └── ...
└── .backup                              -- 备份元信息文件

需要注意的是,BACKUP命令会锁定被备份的表(通过ALTER TABLE ... LOCK),在备份期间对该表的写入操作会被暂时阻塞。对于高并发写入的生产环境,建议在业务低峰期执行备份,或使用副本机制在备副本上执行备份以避免影响主副本的写入性能。

2.4 ON CLUSTER 集群级备份

在生产环境中,ClickHouse通常以集群模式部署(Shard × Replica)。原生BACKUP命令支持ON CLUSTER子句,可以一次性对整个集群的所有节点执行一致的备份:

BACKUP DATABASE analytics ON CLUSTER 'default_cluster'
TO Disk('backup_disk', 'cluster_backup_20260519')
SETTINGS async = true  -- 异步执行,避免超时

执行ON CLUSTER备份时,ClickHouse会在每个分片上独立执行备份操作,并在协调节点(coordinator)记录全局的备份元数据。恢复时,同样可以使用ON CLUSTER将数据恢复到集群中:

RESTORE DATABASE analytics ON CLUSTER 'default_cluster'
FROM Disk('backup_disk', 'cluster_backup_20260519')

3. FREEZE PARTITION(冻结快照备份)

3.1 FREEZE命令原理

ALTER TABLE ... FREEZE PARTITION是ClickHouse早期版本中提供的快照备份机制。虽然22.4+版本的原生BACKUP命令更为强大,但FREEZE方案在某些场景下仍有不可替代的价值——它直接操作底层存储文件,不需要额外的备份磁盘配置,可以与传统的文件系统快照工具(如LVM或ZFS快照)配合使用。

FREEZE操作的核心原理是:将指定分区的数据文件的硬链接(hard link)创建到/path/to/clickhouse/data/{database}/{table}/shadow/目录中。硬链接的特性保证了源文件与快照文件共享同一个inode,即使源文件被删除(随着数据合并被淘汰),快照中的文件依然保留。

-- 冻结指定分区
ALTER TABLE mydb.events FREEZE PARTITION '2024-01'

-- 冻结多个分区(使用通配符)
ALTER TABLE mydb.events FREEZE PARTITION '2024*'

-- 冻结所有分区
ALTER TABLE mydb.events FREEZE PARTITION ''

-- 使用TTL删除旧数据后立即冻结
ALTER TABLE mydb.events FREEZE PARTITION '2023-01'

3.2 shadow目录结构

执行FREEZE后,ClickHouse会在数据目录下创建shadow目录:

/var/lib/clickhouse/data/mydb/events/shadow/
├── 1/                                    -- 快照序号,每次FREEZE递增
│   ├── data/                            -- 快照数据
│   │   ├── default/
│   │   │   └── events/
│   │   │       ├── 20240101_20240101_100_100_0/
│   │   │       │   ├── checksums.txt
│   │   │       │   ├── columns.bin
│   │   │       │   ├── primary.idx
│   │   │       │   ├── minmax_idx.idx
│   │   │       │   └── ...
│   │   │       └── ...
│   └── metadata/
│       └── default/
│           └── events.sql
└── ...

shadow目录中的数据格式与正常数据目录完全一致,这意味着可以直接使用ALTER TABLE ... ATTACH PARTITION将快照中的数据恢复到表中,或者通过简单的文件复制操作将快照迁移到其他服务器。

3.3 基于FREEZE的定期备份脚本

以下是一个生产环境中常用的基于FREEZE的定期备份脚本,它将FREEZE快照复制到备份目录,并自动清理过期快照:

#!/bin/bash
# clickhouse_freeze_backup.sh

set -e

CLICKHOUSE_HOST="${CLICKHOUSE_HOST:-localhost}"
CLICKHOUSE_PORT="${CLICKHOUSE_PORT:-9000}"
CLICKHOUSE_USER="${CLICKHOUSE_USER:-default}"
CLICKHOUSE_PASSWORD="${CLICKHOUSE_PASSWORD:-}"
DATA_PATH="/var/lib/clickhouse/data"
BACKUP_BASE="/backup/clickhouse/shadow"
RETENTION_DAYS=30
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# 创建当日备份目录
BACKUP_DIR="${BACKUP_BASE}/${TIMESTAMP}"
mkdir -p "${BACKUP_DIR}"

# 数据库和表列表
DATABASES_TABLES=(
    "default.events"
    "analytics.metrics"
    "logs.access_log"
)

for db_table in "${DATABASES_TABLES[@]}"; do
    db="${db_table%.*}"
    table="${db_table#*.}"
    echo "[$(date)] Freezing ${db}.${table} ..."

    # 执行FREEZE命令
    clickhouse-client \
        --host "${CLICKHOUSE_HOST}" \
        --port "${CLICKHOUSE_PORT}" \
        --user "${CLICKHOUSE_USER}" \
        --password "${CLICKHOUSE_PASSWORD}" \
        --query "ALTER TABLE ${db}.${table} FREEZE PARTITION ''"

    # 复制shadow目录中的快照到备份位置
    shadow_path="${DATA_PATH}/${db}/${table}/shadow"
    if [ -d "${shadow_path}" ]; then
        rsync -av --include='*/' --include='*.bin' --include='*.idx' \
            --include='*.mrk*' --include='*.txt' --include='*.sql' \
            --include='*.json' --exclude='*' \
            "${shadow_path}/" "${BACKUP_DIR}/${db}/${table}/"
        echo "[$(date)] Copied snapshot for ${db}.${table}"
    fi
done

# 清理超过保留期的旧备份
find "${BACKUP_BASE}" -maxdepth 1 -type d -mtime +${RETENTION_DAYS} -exec rm -rf {} \;
echo "[$(date)] Cleanup completed. Removed backups older than ${RETENTION_DAYS} days."

echo "[$(date)] Backup completed: ${BACKUP_DIR}"

3.4 跨机器复制shadow文件

在实际生产环境中,为了实现灾难恢复,备份数据通常需要复制到异地的存储服务器上。由于FREEZE生成的shadow文件与源数据路径完全独立(通过硬链接),可以直接使用rsyncscp将整个备份目录传输到远程服务器,而不会影响生产服务器的正常运行:

# 将备份快照通过rsync传输到远程备份服务器
rsync -avz --progress \
    --exclude='*.log' \
    --exclude='temporary.*' \
    /backup/clickhouse/shadow/20260519_143000/ \
    rsync://backup_user@remote-backup-server:/clickhouse_backups/incremental/

需要特别注意的是,FREEZE快照是通过硬链接实现的,这意味着只要源数据文件(parts)没有被合并淘汰,快照就占用最小的额外磁盘空间。但一旦后台合并删除了源parts文件,快照中的数据就变为独立文件,开始占用实际存储空间。因此,FREEZE快照不能无限期保留,必须在TTL(通常是几天到几周)内将快照数据迁移到独立的备份存储中。


4. clickhouse-backup工具(第三方开源工具)

4.1 工具概述与安装

clickhouse-backup是由Alex Akulov等人开发的第三方开源备份工具,是目前ClickHouse生态中最流行、功能最全面的备份解决方案之一。它解决了原生BACKUP命令在老版本ClickHouse中的局限性,并提供了开箱即用的S3/GCS/Azure Blob Storage集成、差异化备份、自动清理等企业级功能。

安装clickhouse-backup非常简单,有多种方式:

# 方式1: 直接下载二进制(推荐)
# https://github.com/Altinity/clickhouse-backup/releases
curl -sL https://github.com/Altinity/clickhouse-backup/releases/download/v2.4.0/clickhouse-backup-linux-amd64 \
    -o /usr/local/bin/clickhouse-backup
chmod +x /usr/local/bin/clickhouse-backup

# 方式2: 从源码编译
# git clone https://github.com/Altinity/clickhouse-backup.git
# cd clickhouse-backup && go build -o /usr/local/bin/clickhouse-backup .

# 验证安装
clickhouse-backup --version

4.2 配置文件详解

clickhouse-backup的行为通过/etc/clickhouse-backup/config.yml文件控制。以下是生产环境中一个典型的完整配置:

general:
  remote_storage: s3                # 远程存储类型: none, s3, gcs, azure, ftp, sftp
  backup_name_template: "{shard}_{date}"  # 备份名称模板
  listen_host: "0.0.0.0"
  port: 7171                        # 内置HTTP API端口
  create_retry_count: 3
  upload_retry_count: 3

clickhouse:
  host: localhost
  port: 9000
  username: default
  password: "${CLICKHOUSE_PASSWORD}"  # 从环境变量读取密码
  secure: false
  skip_verify: false
  timeout: 10m                      # 操作超时时间
  shutdown_timeout: 15m
  restart_replication: true
  check_replicas_before_restore: true
  backup_by_part: true              # 按part级别备份
  freeze_by_part: true              # 按part级别冻结

s3:
  enabled: true
  region: cn-north-1
  bucket: my-clickhouse-backups
  path: backups/                    # S3中的路径前缀
  access_key: "${AWS_ACCESS_KEY_ID}"
  secret_key: "${AWS_SECRET_ACCESS_KEY}"
  session_token: ""
  endpoint: ""                      # 如果使用S3兼容存储(如MinIO),填入endpoint
  acl: private
  compressionlevel: 3               # gzip压缩级别(1-9)
  part_size: 104857600              # 分片大小(100MB)
  allow_multipart_download: true
  storage_class: STANDARD_IA        # 低频访问存储类,降低成本
  sse: AES256                       # 服务端加密
  disable_ssl_verification: false

gcs:
  enabled: false
  bucket: my-clickhouse-backups
  path: backups/
  credentials_file: /etc/clickhouse-backup/gcs_credentials.json
  # 或使用环境变量:GOOGLE_APPLICATION_CREDENTIALS_CONTENT_B64

azure:
  enabled: false
  account_name: "${AZURE_ACCOUNT_NAME}"
  account_key: "${AZURE_ACCOUNT_KEY}"
  container: clickhouse-backups
  path: backups/

ftp:
  enabled: false
  address: "ftp.backup-server:21"
  username: "${FTP_USERNAME}"
  password: "${FTP_PASSWORD}"
  tls: false
  path: /clickhouse/backups/

sftp:
  enabled: false
  address: "sftp.backup-server:22"
  username: "${SFTP_USERNAME}"
  password: "${SFTP_PASSWORD}"
  key: /etc/clickhouse-backup/sftp_key
  path: /clickhouse/backups/

redis:
  enabled: false
  host: localhost
  port: 6379
  db: 3
  password: "${REDIS_PASSWORD}"

log:
  level: info
  format: text
  output: /var/log/clickhouse-backup/clickhouse-backup.log

upload:
  method: put                      # 上传方法: put, multipart
  max_size: 1073741824             # 单文件最大1GB,超过则分片

download:
  method: get                      # 下载方法: get, multipart

allowed_destinations:
  - name: "local"
    enabled: true
    path: /var/lib/clickhouse-backup/all-backups
  - name: "s3"
    enabled: true
  - name: "gcs"
    enabled: false

4.3 常用命令详解

clickhouse-backup提供了丰富命令,涵盖备份全生命周期:

# 查看所有可用命令
clickhouse-backup --help

# 列出所有备份(本地和远程)
clickhouse-backup list
clickhouse-backup list --latest 3          # 只显示最近的3个备份
clickhouse-backup list --remote            # 只显示远程备份

# 创建本地备份(freeze + copy)
# 等同于:对每个表执行 FREEZE PARTITION,然后将所有数据复制到本地备份目录
clickhouse-backup create --name=backup_20260519
clickhouse-backup create --name=incremental_20260519_1 \
    --base-backup=backup_20260519           # 增量备份

# 上传本地备份到远程存储(S3/GCS等)
clickhouse-backup upload backup_20260519

# 从远程存储下载备份
clickhouse-backup download backup_20260519

# 删除本地备份
clickhouse-backup delete local backup_20260519

# 删除远程备份
clickhouse-backup delete remote backup_20260519

# 恢复到指定备份
# 如果使用ReplicatedMergeTree,需要在所有副本节点上执行
clickhouse-backup restore backup_20260519
clickhouse-backup restore backup_20260519 --restore-database=mydb  # 仅恢复指定数据库

# 恢复到新表(不会覆盖现有表)
clickhouse-backup restore backup_20260519 \
    --restore-database=mydb \
    --restore-table=events_restored

# 查看备份详情
clickhouse-backup show backup_20260519

# 配置API服务器(可与Prometheus集成)
clickhouse-backup server &
# 启动后可通过 http://localhost:7171/rbac 访问备份管理API

4.4 定时自动备份方案

通过cron调度,可以实现无人值守的自动备份策略。以下是一个生产环境推荐的crontab配置:

# /etc/cron.d/clickhouse-backup-schedule

# 每天凌晨2点执行全量本地备份
0 2 * * * root clickhouse-backup create full_backup_$(date +\%Y\%m\%d) >> /var/log/clickhouse-backup/backup.log 2>&1

# 每天凌晨3点将本地备份上传到S3
0 3 * * * root clickhouse-backup upload full_backup_$(date +\%Y\%m\%d --date='yesterday') >> /var/log/clickhouse-backup/upload.log 2>&1

# 每周一凌晨4点创建周全量备份(保留8周)
0 4 * * 1 root clickhouse-backup create weekly_full_$(date +\%Y\%m\%d) >> /var/log/clickhouse-backup/weekly_backup.log 2>&1
0 5 * * 1 root clickhouse-backup upload weekly_full_$(date +\%Y\%m\%d --date='yesterday') >> /var/log/clickhouse-backup/upload.log 2>&1

# 每小时增量备份(保留48小时)
0 * * * * root clickhouse-backup create incremental_hourly_$(date +\%Y\%m\%d\%H) --base-backup=$(clickhouse-backup list --latest 1 | grep incremental | head -1 | awk '{print $2}') >> /var/log/clickhouse-backup/hourly_incr.log 2>&1

# 每周一凌晨6点清理超过30天的本地备份
0 6 * * 1 root find /var/lib/clickhouse-backup/all-backups -maxdepth 1 -type d -mtime +30 -name 'full_backup_*' -exec clickhouse-backup delete local {} \; >> /var/log/clickhouse-backup/cleanup.log 2>&1

4.5 配合S3/GCS/Azure Blob Storage

clickhouse-backup与云对象存储的集成是其最大的优势之一。以S3为例,一旦完成配置文件中的S3参数配置,上传和下载操作就变得极为简单:

# 上传到S3(自动分片压缩上传)
clickhouse-backup upload full_backup_20260519

# 从S3下载并恢复
clickhouse-backup download full_backup_20260519
clickhouse-backup restore full_backup_20260519

# 查看S3上的备份占用空间
aws s3 ls s3://my-clickhouse-backups/backups/ --summarize --human-readable

# 利用S3生命周期规则自动归档旧备份
# 在S3控制台或通过CLI设置:
aws s3api put-bucket-lifecycle-configuration \
    --bucket my-clickhouse-backups \
    --lifecycle-configuration '{
        "Rules": [{
            "ID": "ArchiveOldBackups",
            "Prefix": "backups/",
            "Status": "Enabled",
            "Transitions": [{
                "Days": 7,
                "StorageClass": "GLACIER"
            }, {
                "Days": 90,
                "StorageClass": "DEEP_ARCHIVE"
            }]
        }]
    }'

对于多副本集群,建议采用如下架构:在每个Shard的Primary Replica上运行clickhouse-backup执行本地FREEZE备份,然后统一上传到共享S3存储。其他Replicas可以通过从S3下载备份进行恢复,保持与Primary Replica的一致性。


5. INSERT INTO … SELECT导出备份(逻辑备份)

5.1 导出为CSV/Parquet

对于小规模数据或需要与其他系统交换数据的场景,可以使用INSERT INTO ... SELECT将数据导出到文件。ClickHouse的clickhouse-client提供了方便的--query参数,支持将查询结果直接导出:

# 导出为CSV格式
clickhouse-client --query "SELECT * FROM mydb.events WHERE date >= '2024-01-01' AND date < '2024-02-01' FORMAT CSV" \
    > /backup/logical_backup/events_202401.csv

# 导出为TabSeparated格式(适合后续快速导入)
clickhouse-client --query "SELECT * FROM mydb.events FORMAT TabSeparated" \
    > /backup/logical_backup/events_all.tsv

# 导出为Parquet格式(列式存储,压缩率高)
clickhouse-client --query "SELECT * FROM mydb.events FORMAT Parquet" \
    > /backup/logical_backup/events_all.parquet

# 导出为JSONEachRow格式(便于流式处理)
clickhouse-client --query "SELECT * FROM mydb.events FORMAT JSONEachRow" \
    > /backup/logical_backup/events_all.json

# 带表头导出CSV
clickhouse-client --query "SELECT 'id', 'event_type', 'date', 'value' UNION ALL SELECT toString(id), event_type, toString(date), toString(value) FROM mydb.events FORMAT CSV" \
    > /backup/logical_backup/events_with_header.csv

5.2 使用clickhouse-client批量导出脚本

对于大型表的分批导出,可以使用脚本来控制导出粒度:

#!/bin/bash
# logical_backup.sh

BACKUP_DIR="/backup/logical"
TABLE="mydb.events"
START_DATE="2024-01-01"
END_DATE="2024-06-01"

current_date="${START_DATE}"
while [ "$(date -d "${current_date}" +%Y%m%d)" -le "$(date -d "${END_DATE}" +%Y%m%d)" ]; do
    month_start=$(date -d "${current_date}" +%Y-%m-01)
    month_end=$(date -d "${current_date}+1 month-1 day" +%Y-%m-%d)
    output_file="${BACKUP_DIR}/events_${month_start}.csv.gz"

    echo "[$(date)] Exporting ${month_start} to ${output_file} ..."

    clickhouse-client --time --query "
        SELECT id, event_type, date, toFloat64(value) AS value,
               created_at, metadata
        FROM ${TABLE}
        WHERE date >= '${month_start}' AND date <= '${month_end}'
        FORMAT CSV
    " | gzip > "${output_file}"

    # 验证导出结果
    rows=$(clickhouse-client --query "SELECT count() FROM ${TABLE} WHERE date >= '${month_start}' AND date <= '${month_end}'")
    file_size=$(stat -c%s "${output_file}")
    echo "[$(date)] Exported ${rows} rows, file size: ${file_size} bytes"

    # 推进到下一个月
    current_date=$(date -d "${current_date}+1 month" +%Y-%m-%d)
done

echo "[$(date)] All exports completed."

5.3 从导出文件恢复

逻辑备份的恢复同样使用INSERT INTO

-- 从CSV恢复(需要先创建目标表)
INSERT INTO mydb.events_restore FROM INFILE '/backup/logical/events_202401.csv'
FORMAT CSV;

-- 从Parquet恢复
INSERT INTO mydb.events_restore FROM INFILE '/backup/logical/events_all.parquet'
FORMAT Parquet;

-- 从压缩CSV恢复
zcat /backup/logical/events_202401.csv.gz | clickhouse-client --query "
INSERT INTO mydb.events_restore FORMAT CSV
"

逻辑备份的主要限制在于:数据以文本格式导出,缺少原始数据的列级压缩信息,因此恢复后的数据需要重新压缩和构建索引。对于百GB级别的数据,逻辑备份的恢复时间可能是物理备份的5-10倍。但逻辑备份也有独特优势:导出的文件完全平台无关,可以跨版本、跨架构恢复,甚至可以在导出时进行数据转换(如修改列类型或添加派生字段)。


6. 各备份方案对比表

以下表格从多个维度对上述四种主要备份方案进行横向对比:

对比维度 BACKUP/RESTORE(原生22.4+) FREEZE PARTITION clickhouse-backup 逻辑导出(CSV/Parquet)
最低支持版本 22.4 1.1(所有版本) 所有版本(需单独安装) 所有版本
备份方式 物理(读取底层数据块) 物理(硬链接) 物理(FREEZE + 压缩上传) 逻辑(SQL查询结果)
增量备份支持 是(BASE BACKUP) 间接(需配合外部机制) 是(基于checksums差异) 否(全量导出)
备份速度 最快(仅创建硬链接) 快(压缩传输) 慢(需要全表扫描)
恢复速度 快(直接attach parts) 快(复制文件+attach) 快(解压+attach) 慢(重写全部数据)
备份存储空间 中等(数据压缩存储) 最小(硬链接仅占metadata) 中等(gzip压缩) 大(无压缩/低压缩)
S3/GCS支持 原生(Disk定义) 需手动脚本 原生(开箱即用) 需手动脚本
集群级备份 支持(ON CLUSTER) 需逐节点脚本 支持(配置中指定all节点) 不支持
数据加密 支持(24.3+) 不支持 不支持(靠存储层加密) 不支持
备份原子性 完全原子 非原子(按分区逐步冻结) 半原子(可通过事务保证) 非原子
对写入性能影响 低(有LOCK机制) 低(仅创建硬链接) 低(FREEZE为轻量操作) 无影响
跨版本恢复支持 有限(仅同版本或相邻版本) 支持(仅数据文件部分) 支持(表结构DDL单独存储) 完全支持(纯数据格式)
配置复杂度 低(纯SQL命令) 中(需要脚本+文件系统权限) 中(YAML配置+cron) 低(简单SQL命令)
适用场景 中大型生产数据库 快速快照+灾备 企业级生产环境 小表、跨平台迁移

从对比表中可以看出,对于生产级别的ClickHouse部署,推荐的核心方案是:以原生BACKUP/RESTORE为主(22.4+环境),以clickhouse-backup为辅(用于S3上传、自动清理、跨版本恢复),以FREEZE为应急快照手段。逻辑导出适用于数据迁移和小表备份,但不建议作为主要生产备份方案。


7. 备份策略建议

7.1 全量备份周期

全量备份的频率取决于两个因素:数据变更量和恢复点目标(RPO)。如果业务可以容忍最多1天的数据丢失,则每天至少执行一次全量备份;如果RPO要求更高(如1小时),则需要引入增量备份机制。

对于大多数生产环境,建议的基准策略如下:

-- 每日全量备份(凌晨业务低峰期)
-- 配合S3存储,保留策略:最近7个每日备份保留在STANDARD存储,
-- 第8-30天的备份自动转换到GLACIER存储类

-- 每周全量备份(周一凌晨)
-- 保留最近8周的周备份,用于月级RPO需求

7.2 增量备份策略

在两个全量备份之间,通过增量备份捕获数据变更。增量备份的频率建议根据写入量确定:

  • 高写入场景(每秒>10万行):每小时增量备份
  • 中等写入场景(每小时>100万行):每4-6小时增量备份
  • 低写入场景(每天<1000万行):每日增量备份
# 增量备份策略示例:基于base_backup的链式增量
# 第1天:全量备份
clickhouse-backup create daily_20260519

# 第2天:基于前一天的增量
clickhouse-backup create daily_20260520 --base-backup=daily_20260519

# 第3天:基于第2天的增量
clickhouse-backup create daily_20260521 --base-backup=daily_20260520

# 如果第5天需要恢复,只需:
clickhouse-backup restore daily_20260525
# ClickHouse会自动按顺序恢复全量 + 所有中间增量

7.3 备份验证

备份的最终价值在于能否成功恢复。定期进行备份恢复验证是备份策略中不可或缺的一环:

-- 定期验证备份可恢复性(在独立的测试环境中执行)

-- 1. 从备份恢复到一个独立的测试数据库
clickhouse-backup restore full_backup_20260519 --restore-database=verify_test

-- 2. 验证数据完整性:比较行数、checksum等关键指标
SELECT
    'source' AS source_db,
    count() AS row_count,
    uniqExact(id) AS unique_ids,
    sum(value) AS total_value
FROM mydb.events
WHERE date >= '2024-01-01'

UNION ALL

SELECT
    'backup' AS source_db,
    count() AS row_count,
    uniqExact(id) AS unique_ids,
    sum(value) AS total_value
FROM verify_test.events
WHERE date >= '2024-01-01';

-- 3. 抽样数据比对(随机抽取100条记录逐字段比较)
SELECT *
FROM mydb.events
WHERE rand() % 1000 = 0
LIMIT 100;

建议每月至少执行一次完整的备份恢复验证,并将验证结果记录到运维日志中。对于金融、医疗等数据敏感行业,备份验证的频率应提升到每周甚至每次备份后。


8. 实战案例:基于clickhouse-backup + S3的自动化定时备份方案

8.1 背景与需求

某在线分析平台使用ClickHouse集群存储用户行为日志,数据量约2TB/天,总数据量约50TB。业务要求RPO≤4小时(即最多允许4小时的数据丢失),RTO≤30分钟(即灾难发生后30分钟内恢复服务)。平台运行在3个数据中心的6节点集群上,需要实现跨数据中心的异地灾备。

8.2 架构设计

采用分层备份架构:

[生产集群]
  ├── 实时复制(ReplicatedMergeTree,跨数据中心3副本)
  ├── 本地快照(clickhouse-backup local,每日全量)
  └── 上传S3(跨区域备份存储,GLACIER_IA)
        ├── 热层:最近7天备份(Instant Access)
        └── 冷层:7-90天备份(GLACIER Instant Retrieval)

8.3 完整Shell脚本实现

#!/bin/bash
# clickhouse_backup_automation.sh
# 基于clickhouse-backup + S3的全自动备份方案
# Crontab: 0 2,6,10,14,18,22 * * * /opt/scripts/clickhouse_backup_automation.sh

set -euo pipefail

# ==================== 配置区 ====================
BACKUP_NAME_PREFIX="prod_cluster_backup"
S3_BUCKET="s3://company-clickhouse-backups/production"
RETENTION_DAYS_LOCAL=7
RETENTION_DAYS_REMOTE=90
LOG_FILE="/var/log/clickhouse-backup/automation_$(date +\%Y\%m).log"
ALERT_EMAIL="ops-team@company.com"

# ClickHouse连接配置
CLICKHOUSE_HOSTS=(
    "clickhouse-node1:9000"
    "clickhouse-node2:9000"
    "clickhouse-node3:9000"
)

# Slack/Webhook通知(可选)
SLACK_WEBHOOK_URL="${SLACK_WEBHOOK_URL:-}"
# ==================== 配置结束 ====================

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${LOG_FILE}"
}

send_alert() {
    local message="$1"
    log "ALERT: ${message}"

    # 邮件通知
    echo "${message}" | mail -s "[ClickHouse Backup Alert] $(hostname)" "${ALERT_EMAIL}"

    # Slack通知
    if [[ -n "${SLACK_WEBHOOK_URL}" ]]; then
        curl -s -X POST "${SLACK_WEBHOOK_URL}" \
            -H 'Content-type: application/json' \
            --data "{\"text\": \"[ClickHouse Backup] ${message}\"}"
    fi
}

# 确定备份类型:每天凌晨2点为全量,其余为增量
hour=$(date +%H)
if [[ "${hour}" == "02" ]]; then
    BACKUP_TYPE="full"
    backup_name="${BACKUP_NAME_PREFIX}_full_$(date +\%Y\%m\%d)"
else
    BACKUP_TYPE="incremental"
    backup_name="${BACKUP_NAME_PREFIX}_incr_$(date +\%Y\%m\%d_\%H\%M)"
fi

log "========== Starting ${BACKUP_TYPE} backup: ${backup_name} =========="

# 确定base_backup(用于增量备份)
if [[ "${BACKUP_TYPE}" == "incremental" ]]; then
    # 查找最近一次全量或增量备份作为base
    base_backup=$(clickhouse-backup list --latest 1 | head -2 | tail -1 | awk '{print $2}')
    if [[ -z "${base_backup}" ]]; then
        log "No existing backup found, creating full backup instead"
        BACKUP_TYPE="full"
        backup_name="${BACKUP_NAME_PREFIX}_full_$(date +\%Y\%m\%d)"
        CREATE_CMD="clickhouse-backup create ${backup_name}"
    else
        log "Using base backup: ${base_backup}"
        CREATE_CMD="clickhouse-backup create ${backup_name} --base-backup=${base_backup}"
    fi
else
    CREATE_CMD="clickhouse-backup create ${backup_name}"
fi

# 步骤1:创建本地备份
log "Step 1/4: Creating local backup..."
start_time=$(date +%s)
${CREATE_CMD}
create_duration=$(( $(date +%s) - start_time ))
log "Local backup created in ${create_duration}s"

# 步骤2:上传到S3(仅对全量和手动触发的增量)
if [[ "${BACKUP_TYPE}" == "full" ]] || [[ "${hour}" == "10" ]]; then
    log "Step 2/4: Uploading to S3..."
    start_time=$(date +%s)
    if ! clickhouse-backup upload "${backup_name}"; then
        send_alert "Failed to upload backup ${backup_name} to S3"
        exit 1
    fi
    upload_duration=$(( $(date +%s) - start_time ))
    log "Upload to S3 completed in ${upload_duration}s"

    # 步骤3:验证S3上的备份
    log "Step 3/4: Verifying S3 backup..."
    s3_size=$(aws s3 ls "${S3_BUCKET}/${backup_name}/" --recursive --summarize | tail -1 || echo "Size: 0")
    log "S3 backup size: ${s3_size}"

    # 步骤4:清理过期备份
    log "Step 4/4: Cleaning up old backups..."
    clickhouse-backup delete local "$(clickhouse-backup list | awk '{print $2}' | head -n 1)" \
        || log "Failed to delete old local backup (may not exist)"

    # 清理超过保留期的远程备份
    for old_backup in $(clickhouse-backup list --remote | awk '{print $2}'); do
        backup_date=$(echo "${old_backup}" | grep -oE '[0-9]{8}' || echo "")
        if [[ -n "${backup_date}" ]]; then
            backup_timestamp=$(date -d "${backup_date}" +%s 2>/dev/null || echo "0")
            cutoff_timestamp=$(date -d "${RETENTION_DAYS_REMOTE} days ago" +%s)
            if [[ "${backup_timestamp}" -lt "${cutoff_timestamp}" ]]; then
                log "Deleting old remote backup: ${old_backup}"
                clickhouse-backup delete remote "${old_backup}" || true
            fi
        fi
    done
else
    log "Step 2-4: Skipped (incremental backup, not uploading to remote)"
fi

log "========== Backup completed successfully: ${backup_name} =========="
log "Local backup size: $(du -sh /var/lib/clickhouse-backup/all-backups/${backup_name} | cut -f1)"

# 发送成功通知
if [[ -n "${SLACK_WEBHOOK_URL}" ]]; then
    curl -s -X POST "${SLACK_WEBHOOK_URL}" \
        -H 'Content-type: application/json' \
        --data "{\"text\": \"[ClickHouse Backup] ${BACKUP_TYPE} backup completed: ${backup_name} (${create_duration}s)\"}"
fi

8.4 恢复演练流程

每季度至少执行一次完整的灾难恢复演练:

#!/bin/bash
# disaster_recovery_drill.sh

set -euo pipefail

# 1. 从S3下载最新备份到测试环境
clickhouse-backup download $(clickhouse-backup list --remote --latest 1 | awk '{print $2}')

# 2. 在隔离的测试集群上执行恢复
clickhouse-backup restore $(clickhouse-backup list --remote --latest 1 | awk '{print $2}') \
    --restore-database=production_restore

# 3. 执行数据完整性校验
clickhouse-client --query "
    SELECT
        count() AS total_rows,
        uniqExact(id) AS unique_events,
        min(date) AS earliest_date,
        max(date) AS latest_date
    FROM production_restore.events
"

# 4. 生成验证报告
echo "DRILL COMPLETED: $(date)" >> /var/log/clickhouse-backup/drill.log

N. 总结与最佳实践

本文系统梳理了ClickHouse生态中的四大备份方案:原生BACKUP/RESTORE命令、FREEZE PARTITION快照、第三方clickhouse-backup工具以及逻辑导出方案。每种方案各有优劣,在实际生产环境中应组合使用。

核心最佳实践总结

  1. 版本优先:如果使用ClickHouse 22.4+,优先采用原生BACKUP/RESTORE命令,它是官方支持、功能最完善的备份机制。

  2. 分层备份策略:本地快照用于快速恢复(如误删除表),S3/云存储用于跨地域灾备。两者结合可以兼顾RTO和RPO两个指标。

  3. 增量优先:对于数据量大的表(如日志表每天数TB),增量备份可以将备份窗口从数小时缩短到数分钟。

  4. 自动化为王:所有备份操作必须自动化,禁止手动备份。人是备份链中最不可靠的环节。

  5. 验证即生命:再完善的备份策略,如果从不验证,都等于没有备份。建议将备份恢复验证纳入常规运维流程。

  6. 备份隔离:备份数据应与生产数据物理隔离。如果备份与生产数据在同一个存储控制器上,控制器故障可能导致两者同时丢失。

  7. 监控告警:备份任务必须纳入监控体系——备份失败、上传失败、存储空间不足等情况必须第一时间通知运维人员。

ClickHouse的备份虽然比传统数据库复杂,但通过合理的方案组合和自动化工具链,完全可以构建起一套可靠、高效、自动化的数据保护体系。下一篇文章我们将聚焦ClickHouse的监控体系,通过系统表与Prometheus/Grafana集成实现全方位的可观测性。


上一篇【第52篇】ClickHouse熔断机制_系统过载保护策略
下一篇【第54篇】ClickHouse服务监控_系统表与监控指标体系


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐