当备份目标为 S3 时,如何使用“multipart”上传 50 GB 单库?

解读

面试官真正想确认的是:

  1. 是否理解 CouchDB 备份本质是顺序导出一份 .couch 文件,而非“SELECT *”式逻辑备份;
  2. 是否知道 50 GB 单文件直接 PUT 会触发 S3 单对象 5 GB 限制,必须改用 multipart;
  3. 能否把 CouchDB 的离线快照、并发分块、失败重传、成本优化、权限模型在中国区 S3(北京、宁夏)合规落地
  4. 能否给出可脚本化、可回滚、可验证的完整方案,而不是只背“aws s3 cp”参数。

知识点

  • CouchDB 文件级一致性:备份前必须暂停写入或利用 /_backup 端点(2.3+)做快照,否则 .couch 可能处于不一致状态。
  • S3 Multipart Upload 协议:每个分片 5 MB–5 GB,最多 10 000 片;Initiate → Upload Parts → Complete,支持断点续传。
  • 中国区 AK/SK 合规:必须使用国内官方 SDK(boto3、aws-cli 2.x)+ 北京/宁夏区域域名,避免海外 endpoint 被墙。
  • 成本与限速:跨区域流出流量费约 0.49 元/GB;若同区上传走内网,流量 0 元但需走 VPC Endpoint 避免 NAT 费用。
  • 完整性校验:每片在上传前计算 CRC32-CSHA-256,Complete 时带上 ChecksumAlgorithm 参数,S3 会二次校验。
  • 回滚策略:若 Complete 失败,24 h 内未调用 Abort,会按已上传分片占用空间计费;脚本必须捕获异常主动 Abort。
  • CouchDB 版本差异:1.x 需停写;2.x 可用 /_backup 触发快照;3.x 支持热备份,但快照仍推荐在低峰期执行。

答案

  1. 快照阶段
    a. 低峰期执行 curl -X POST http://localhost:5984/_backup -d '{"type":"file","name":"shop_50gb"}'
    b. 等待返回 "state":"completed",得到 /var/lib/couchdb/backup/shop_50gb.couch 文件,确认大小 50 GB。

  2. 分片规划
    采用 100 片 × 512 MB,单片 512 MB 既小于 5 GB 上限,又保证断点重传粒度可控;并发 8 线程,在中国区北京 region 内网走 VPC Endpoint,节省流量费。

  3. 上传脚本(Python 3 + boto3)

import boto3, os, hashlib, threading
from concurrent.futures import ThreadPoolExecutor

file_path = '/var/lib/couchdb/backup/shop_50gb.couch'
bucket = 'couchdb-backup-cn'
key = 'couchdb/shop_50gb.couch'
part_size = 512 * 1024 * 1024  # 512 MB

s3 = boto3.client('s3', region_name='cn-north-1',
                  endpoint_url='https://s3.cn-north-1.amazonaws.com.cn')

mpu = s3.create_multipart_upload(Bucket=bucket, Key=key,
                                 ChecksumAlgorithm='CRC32C')
upload_id = mpu['UploadId']

parts = []
lock = threading.Lock()

def upload_part(idx, start, end):
    with open(file_path, 'rb') as f:
        f.seek(start)
        data = f.read(end - start)
    crc = hashlib.crc32c(data).digest()
    resp = s3.upload_part(Bucket=bucket, Key=key, PartNumber=idx,
                          UploadId=upload_id, Body=data,
                          ChecksumCRC32C=crc.hex())
    with lock:
        parts.append({'ETag': resp['ETag'], 'PartNumber': idx,
                      'ChecksumCRC32C': resp['ChecksumCRC32C']})

size = os.path.getsize(file_path)
futures = []
with ThreadPoolExecutor(max_workers=8) as exe:
    for i in range(1, (size // part_size) + 2):
        start = (i-1) * part_size
        end = min(i * part_size, size)
        if start >= size:
            break
        futures.append(exe.submit(upload_part, i, start, end))
# 异常捕获省略,生产环境需 try/except 并调用 abort_multipart_upload

parts.sort(key=lambda x: x['PartNumber'])
s3.complete_multipart_upload(Bucket=bucket, Key=key,
                             UploadId=upload_id,
                             MultipartUpload={'Parts': parts})
print('backup done')
  1. 验证与清理
    使用 aws s3api head-object --bucket couchdb-backup-cn --key couchdb/shop_50gb.couch 确认 "Size": 53687091200
    本地计算 sha256sum shop_50gb.couch 并与下载后文件比对,哈希一致即成功;
    24 h 内若脚本异常退出,定时巡检任务会扫描未完成 UploadId 并调用 abort_multipart_upload 防止空计费。

拓展思考

  • 增量策略:CouchDB 2.3+ 支持 /_backup?incremental=true,但底层仍是全量快照;若数据量大,可改用 连续复制到另一集群 做逻辑增量,再对新集群做快照,降低 RPO。
  • 加密与合规:中国区金融客户需KMS 国密算法;可在 create_multipart_upload 时指定 ServerSideEncryption='aws:kms' 并传入 SSEKMSKeyId,保证数据落盘加密。
  • 成本再优化:512 MB 分片在 10 Gbps 内网链路下 8 并发即可跑满带宽;若备份窗口充裕,可调大到 1 GB 分片、4 并发,减少 API 调用次数(每万次 0.45 元),节省约 15 % 请求费用。
  • 多云容灾:同一脚本只需替换 endpoint 即可将分片并行上传到阿里云 OSS 或腾讯云 COS 的类似 Multipart 接口,实现双云对拷;注意国内云厂商的 PartSize 上限差异(OSS 最大 5 GB 同 S3,COS 最大 100 MB 需调整)。