【Clickhouse从入门到精通】第53篇:ClickHouse数据备份方案全面解析
上一篇【第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版本开始,官方引入了原生的BACKUP和RESTORE命令,这是迄今为止最完善的原生备份机制。相比之前的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文件与源数据路径完全独立(通过硬链接),可以直接使用rsync或scp将整个备份目录传输到远程服务器,而不会影响生产服务器的正常运行:
# 将备份快照通过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工具以及逻辑导出方案。每种方案各有优劣,在实际生产环境中应组合使用。
核心最佳实践总结:
-
版本优先:如果使用ClickHouse 22.4+,优先采用原生BACKUP/RESTORE命令,它是官方支持、功能最完善的备份机制。
-
分层备份策略:本地快照用于快速恢复(如误删除表),S3/云存储用于跨地域灾备。两者结合可以兼顾RTO和RPO两个指标。
-
增量优先:对于数据量大的表(如日志表每天数TB),增量备份可以将备份窗口从数小时缩短到数分钟。
-
自动化为王:所有备份操作必须自动化,禁止手动备份。人是备份链中最不可靠的环节。
-
验证即生命:再完善的备份策略,如果从不验证,都等于没有备份。建议将备份恢复验证纳入常规运维流程。
-
备份隔离:备份数据应与生产数据物理隔离。如果备份与生产数据在同一个存储控制器上,控制器故障可能导致两者同时丢失。
-
监控告警:备份任务必须纳入监控体系——备份失败、上传失败、存储空间不足等情况必须第一时间通知运维人员。
ClickHouse的备份虽然比传统数据库复杂,但通过合理的方案组合和自动化工具链,完全可以构建起一套可靠、高效、自动化的数据保护体系。下一篇文章我们将聚焦ClickHouse的监控体系,通过系统表与Prometheus/Grafana集成实现全方位的可观测性。
上一篇【第52篇】ClickHouse熔断机制_系统过载保护策略
下一篇【第54篇】ClickHouse服务监控_系统表与监控指标体系
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)