十二个构建

在我们的两个站点之间移动两 TB 数据而无需停机

预拷贝、流式复制和代理切换,以及决定长途传输需要一小时还是一天的 TCP 调优。

构建内容

将两 TB 的文件和一个实时 Postgres 数据库从一个站点迁移到另一个站点,应用在整个过程中持续响应请求。方法是批量预复制,源端持续服务,流式副本保持同步,切换在数秒内完成,旧主机在当前 DNS 生效前将流量代理到新主机。

距离是关键。AMS-01 和 FRA-01 之间往返时间 7 毫秒,单流即可占满端口。同样的复制到 GRU-01,往返接近 200 毫秒,未优化的传输只能达到你所付带宽的一小部分,而两台机器几乎空闲。这个差距完全取决于单个连接允许的在途数据量。

开始之前

  • 源和目标实例、两者之间的隧道,以及两端的 root 权限。
  • 可控制的 DNS,提前至少一天将记录的 TTL 降为 60 秒。此步常被跳过,之后只能等待六个小时。
  • 目标端有足够磁盘容纳整个数据集加上副本。

1. 评估链路能力

带宽延迟积决定一切。将往返时间乘以目标速率,得出任何时刻必须保持在途未确认的数据量:

ping -c20 target.example.com | tail -1
路径RTT1 Gbit/s 时在途默认内核窗口
AMS-01 到 FRA-017 ms0.9 MB足够
AMS-01 到 NYC-0176 ms9.5 MB不足
AMS-01 到 SIN-01168 ms21 MB远不够
AMS-01 到 GRU-01196 ms24.5 MB远不够

在两台机器上:

cat > /etc/sysctl.d/75-transfer.conf <<EOF
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 262144 134217728
net.ipv4.tcp_wmem = 4096 262144 134217728
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq
net.ipv4.tcp_mtu_probing = 1
EOF
sysctl --system

在长距离且存在丢包的情况下,BBR 而非默认的拥塞控制很重要,因为基于丢包的算法将单个数据包丢失视为拥塞并使窗口减半。在 200 毫秒以上的延迟下,恢复需要数秒,且这种情形会反复发生。

在投入两 TB 数据之前先进行测量:

# target
apt install -y iperf3 && iperf3 -s
# source
iperf3 -c target.example.com -P 8 -t 30

八个并行流应接近端口速率。如果单流达到 100 兆比特而八个达到 800 兆比特,说明窗口设置未生效;若生效,单流应能达到大部分端口速率。

2. 批量预复制

对实时源执行此操作,需要数小时,期间不会中断任何服务:

apt install -y rsync
rsync -aHAX --numeric-ids --partial --inplace --info=progress2 \
  -e "ssh -c [email protected] -o Compression=no" \
  /srv/data/ [email protected]:/srv/data/

选择加密算法并非迷信。在这些处理器上,硬件加速的 GCM 模式每核每秒移动数 GB,而某些客户端版本默认协商的加密算法明显较慢,将传输变成单核基准测试。关闭压缩,因为数据已是压缩过的,如果不是,CPU 会花时间在压缩而非传输字节上。

对于数以百万计的小文件,单流不合适。按顶层目录拆分并运行多个流:

ls /srv/data | xargs -P 8 -I{} rsync -aHAX --numeric-ids \
  -e "ssh -c [email protected] -o Compression=no" \
  /srv/data/{}/ [email protected]:/srv/data/{}/

八个并行复制使用八个专用核心是合适数量;十六个并不会快一倍,反而会让源机器对正在服务的负载感到不适。

3. 数据库:复制而非拷贝

转储并恢复 200 GB 的 Postgres 意味着长时间写入丢失窗口。改为流式处理。在源端:

sudo -u postgres psql -c "CREATE USER repl WITH REPLICATION PASSWORD '<a long password>';"
sudo -u postgres psql -c "SELECT pg_create_physical_replication_slot('target_site');"
echo "host replication repl 10.9.0.2/32 scram-sha-256" >> /etc/postgresql/17/main/pg_hba.conf
systemctl reload postgresql

在目标端:

systemctl stop postgresql
rm -rf /var/lib/postgresql/17/main/*
sudo -u postgres pg_basebackup -h 10.9.0.1 -U repl -D /var/lib/postgresql/17/main \
  -S target_site -X stream -R -P -c fast
systemctl start postgresql

-R 标志写入连接设置和 standby 信号文件,使目标以副本身份启动并开始跟随。从此它始终与源保持一两秒的延迟,这意味着切换时不再需要移动数据库。

4. 切换

从第三台机器启动探针并持续运行。它既是验证,也是对切换成本的记录:

while true; do curl -s -o /dev/null -w "%{http_code} %{time_total}\n" https://app.example.com/health; sleep 1; done | tee cutover.log

然后按以下顺序执行,不停顿:

# 1. final delta, source still live
rsync -aHAX --numeric-ids --delete -e "ssh -c [email protected]" /srv/data/ [email protected]:/srv/data/

# 2. stop writes on the source
systemctl stop app

# 3. one more delta, now tiny
rsync -aHAX --numeric-ids --delete -e "ssh -c [email protected]" /srv/data/ [email protected]:/srv/data/

# 4. promote the replica
ssh [email protected] "sudo -u postgres pg_ctl promote -D /var/lib/postgresql/17/main"

# 5. start the application on the target
ssh [email protected] "systemctl start app"

步骤二到步骤五只需数秒。现在停止旧主机继续提供过时内容(在 DNS 传播期间)——将其转换为新主机的代理:

server {
  listen 443 ssl;
  server_name app.example.com;
  ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
  location / {
    proxy_pass https://10.9.0.2;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
  }
}

这正是让整个过程不中断的关键。仍解析旧地址的用户通过隧道获得正确应答,而不是错误,更糟糕的是获得已在切换时停止更新的数据副本。现在更改 DNS 记录。

验证

首先查看收集的探针日志:

sort cutover.log | uniq -c | sort -rn | head

每一行应显示 200。切换期间有少量响应延迟是正常的;任何非 200 状态都意味着步骤五比步骤二启动晚了太多,前一节的代理将差距缩小到零。

然后证明数据一致而不只是存在:

rsync -aHAXn --delete --itemize-changes /srv/data/ [email protected]:/srv/data/ | head
du -s --bytes /srv/data
ssh [email protected] "du -s --bytes /srv/data"

如果试运行没有列出差异,说明两个目录树在内容、权限、所有者和扩展属性上一致。字节总数应完全匹配。

验证数据库:

ssh [email protected] "sudo -u postgres psql -c 'SELECT pg_is_in_recovery();'"
ssh [email protected] "sudo -u postgres psql app -c 'SELECT count(*) FROM orders;'"
sudo -u postgres psql app -c "SELECT count(*) FROM orders;"

目标端恢复状态应为 false,行数应一致。最后,确认流量确实转移,而非一直被悄然代理:

dig +short app.example.com
tail -f /var/log/nginx/access.log | wc -l   # on the old host, an hour later

当旧主机空闲超过一个完整 TTL 外加合理余量后,拆除代理并销毁实例。不要当天下午就销毁;整个过程中最便宜的保险是保留源机器完整并关闭一周。

之后

两 TB 数据经良好调优的长途链路,在 10 Gbit/s 下约一小时,实际更久,因为源端同时还在服务。欧洲临近站点间,可预期一个下午;跨洲则在晚间开始,次日进行切换。可以用位置页面中我们测量的往返时间作为第一步表格的数据。

随时恭候

选择城市,选择大小,用加密货币支付。

无需填写关于您的身份信息的表格,无需等待人工审批,无需电话验证。账单结清后,凭证将发送到您的邮箱。