Двенадцать сборок

Перемещение двух терабайт между двумя нашими площадками без простоя

Pre-copy, стриминговый репликат и проксируемое переключение, с настройкой TCP, которая определяет, займёт ли передача на большое расстояние час или день.

Что здесь происходит

Два терабайта файлов и рабочая база данных Postgres переезжают с одного сайта на другой, и приложение всё это время отвечает на запросы. Метод — массовое предварительное копирование, пока источник продолжает обслуживать запросы, потоковая реплика, которая не отстаёт, и переключение, измеряемое секундами, при этом старый хост проксирует на новый, пока DNS не догонит.

Самое интересное — расстояние. Между AMS-01 и FRA-01 время кругового пути семь миллисекунд, и один поток заполнит порт. Запустите то же копирование на GRU-01, где время кругового пути около двухсот, и ненастроенная передача будет ползти ничтожной долей от того, что вы оплачиваете, пока обе машины почти простаивают. Этот разрыв целиком зависит от того, сколько данных одному соединению разрешено держать в полёте.

Перед началом

  • Исходный и целевой инстансы, туннель между ними и root на обоих.
  • DNS под вашим контролем, с TTL записи, сниженным до шестидесяти секунд минимум за день. Это шаг, который все пропускают, а потом ждут шесть часов.
  • Достаточно диска на целевом хосте для всего набора данных плюс реплика.

1. Выясните, на что способен канал

Произведение полосы на задержку решает всё. Умножьте время кругового пути на желаемую скорость — и получите объём данных, который должен быть в полёте без подтверждения в любой момент:

ping -c20 target.example.com | tail -1
ПутьRTTВ полёте при 1 Гбит/сОкно ядра по умолчанию
AMS-01 to FRA-017 мс0.9 МБДостаточно
AMS-01 to NYC-0176 мс9.5 МБНедостаточно
AMS-01 to SIN-01168 мс21 МБСовсем не то
AMS-01 to GRU-01196 мс24.5 МБСовсем не то

На обеих машинах:

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 вместо алгоритма управления перегрузкой по умолчанию важен на длинных путях при любых потерях, потому что алгоритмы, основанные на потерях, интерпретируют один потерянный пакет как перегрузку и вдвое уменьшают окно. При задержке более двухсот миллисекунд восстановление занимает секунды, и это повторяется многократно.

Измерьте, прежде чем доверить два терабайта предположению:

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

Восемь параллельных потоков должны приблизиться к скорости порта. Если один поток даёт сто мегабит, а восемь — восемьсот, значит, настройки окна не подействовали; один поток должен сам по себе достигать почти полной скорости после их применения.

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 тратит время на это вместо переноса байтов.

Для миллионов мелких файлов один поток — неправильная форма. Разбейте по каталогам верхнего уровня и запустите несколько:

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. База данных: репликация, а не копирование

Дамп и восстановление двухсот гигабайт 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;"

Recovery должен быть false на целевом хосте, и количество строк должно совпадать. Наконец, подтвердите, что трафик действительно переехал, а не тихо проксируется вечно:

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

Когда старый хост простаивает полное TTL плюс достаточный запас, снимите прокси и уничтожьте инстанс. Не уничтожайте его в тот же день; самая дешёвая страховка во всей процедуре — оставить исходную машину нетронутой и выключенной на неделю.

После

Два терабайта по хорошо настроенному длинному пути — это примерно час на десяти гигабитах и гораздо дольше на практике, поскольку источник также обслуживает запросы. Между соседними европейскими сайтами рассчитывайте на вторую половину дня; между континентами начинайте вечером и делайте переключение на следующий день. Страница локаций содержит времена кругового пути, которые мы измеряем, и именно эти цифры нужно подставить в таблицу в шаге первом.

Готовы, когда вы готовы

Выберите город. Выберите размер. Оплатите монетой.

Никаких форм о том, кто вы, никакого ожидания одобрения человеком, никаких звонков для проверки. Счёт оплачен — и учётные данные приходят на почту.