12のビルド

2テラバイトをダウンタイムなしで2つのサイト間で移転する

プレコピー、ストリーミングレプリカ、プロキシによるカットオーバー。長距離転送が1時間で済むか1日かかるかを左右するTCPチューニング。

これが作るもの

2テラバイトのファイルと稼働中のPostgresデータベースを、あるサイトから別のサイトへ移動します。アプリケーションはその間ずっとリクエストに応答し続けます。方法は、ソースが提供を続けている間にバルク事前コピーを行い、ストリーミングレプリカが追従し続け、DNSが追いつくまで旧ホストが新ホストへのプロキシとして機能する、秒単位で測定されるカットオーバーです。

距離が興味深い部分です。AMS-01とFRA-01の間では往復7ミリ秒で、単一ストリームでポートを満たせます。同じコピーをGRU-01まで実行すると、往復は200に近くなり、調整されていない転送は、支払っている帯域のほんの一部の速度で這うように進み、両方のマシンはほとんどアイドル状態です。このギャップは、単一の接続が転送中に持つことができるデータ量に完全に起因します。

始める前に

  • ソースとターゲットのインスタンス、それらの間のトンネル、両方でのrootアクセス。
  • 管理下にあるDNS。レコードのTTLは少なくとも1日前に60秒に下げておきます。これは人々がスキップして、その後6時間待つステップです。
  • データセット全体とレプリカを収容するのに十分なディスクがターゲットにあること。

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ミリ秒以上では、そこからの回復に数秒かかり、それが繰り返し発生します。

2テラバイトを仮定にコミットする前に測定します:

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

8つの並列ストリームでポート速度に近づくはずです。1つのストリームが100メガビット、8つが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モードはコアあたり毎秒数ギガバイトを移動しますが、一部のクライアントバージョンのデフォルトのネゴシエート暗号は著しく遅く、転送をシングルコアベンチマークに変えます。圧縮はオフです。データがすでに圧縮されているためであり、そうでない場合、CPUはバイトを移動する代わりに圧縮に時間を費やします。

数百万の小さなファイルの場合、1つのストリームは間違った形状です。トップレベルディレクトリで分割し、いくつか実行します:

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/{}/

8つの専用コアでの8つの並列コピーが適切な数です。16は2倍の速度にはならず、ソースマシンをまだ提供しているものにとって不快にします。

3. データベース、コピーではなくレプリケート

200ギガバイトの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フラグは接続設定とスタンバイシグナルファイルを書き込むため、ターゲットはレプリカとして起動し、追従を開始します。ここから、ソースから1〜2秒以内に永続的に保たれ、カットオーバーがデータベースの移動をまったく伴わないことを意味します。

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"

ステップ2から5までは数秒かかります。次に、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以外の応答は、ステップ5がステップ2の後にあまりにも広いマージンで開始されたことを意味し、前のセクションのプロキシがそのギャップをゼロに縮小するものです。

次に、データが単に存在するだけでなく同一であることを証明します:

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"

何も列挙しないドライランは、2つのツリーがコンテンツ、パーミッション、所有権、拡張属性で一致することを意味します。バイト数は正確に一致するはずです。

データベース次に:

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プラス快適なマージンの間アイドル状態になったら、プロキシを停止し、インスタンスを破棄します。同じ午後に破棄しないでください。この手順全体で最も安い保険は、ソースマシンをそのままにして1週間電源を切っておくことです。

その後

よく調整された長距離パスでの2テラバイトは、10ギガビットで約1時間ですが、ソースも提供しているため実際にはもう少しかかります。近隣のヨーロッパのサイト間では、午後を計画します。大陸間では、夕方に開始し、翌日にカットオーバーを行います。ロケーションページには、測定した往復時間がリストされており、ステップ1の表に入れる数値です。

準備はできている

都市を選べ。サイズを選べ。コインで支払え。

あなたが誰かに関するフォームも、承認する人間を待つ必要も、確認のための電話もありません。請求が完了すれば、認証情報があなたの受信トレイに届きます。