11秒のダウンタイムでPostgreSQLデータベースの移行を完了
(gds.blog.gov.uk)- GOV.UK NotifyはPaaS終了に合わせて、400GBのPostgreSQL 11データベースを自前のAWSアカウント上のPostgreSQL 15 RDSへ移行し、ダウンタイムを約11秒まで短縮
- 対象DBでは先にテーブルだけを作成し、DMS full loadでデータをコピーした後、インデックスとキー制約を後から適用して大規模ロード時間を短縮
- 元DBは約13億行、85テーブル、185インデックス、120外部キーの規模で、平日には毎秒約1,000件の挿入・更新と同程度の読み取りが発生
- アプリケーション再デプロイに約5分かかる制約のため、同一認証情報とRoute53のDNS重み付け切り替えを事前に準備し、実際の切り替え時間を短縮
- DMSはPaaSおよびAWSのサポートを受けやすいため採用されたが、PostgreSQL間移行ではpglogicalのような代替の方がよりシンプルだった可能性もある
PaaS終了に合わせたNotifyデータベース移行
- GOV.UK NotifyはGOV.UK Platform as a Serviceでホスティングされており、PaaS終了に伴ってすべてのインフラを自前のAWSアカウントへ移す必要があった
- NotifyのデータベースはPaaSのAWSアカウント上にあるAWS RDS PostgreSQLで、通知送信データからサービスチームが使う数十万件のテンプレート内容まで保存している
- 既存データベースはsource database、新しいデータベースはtarget databaseとして区別して移行を進めた
- 主要な課題は新しいPostgreSQLデータベースを作ることではなく、すべてのデータを移し、アプリケーションの接続先を切り替える間のダウンタイムを最小化することだった
元データベースの規模とサービス上の制約
- 元データベースは約400GB規模のPostgreSQL 11だった
- 約13億行
- 85テーブル
- 185インデックス
- 120外部キー
- 通常の平日には毎秒約1,000件の挿入または更新が発生し、同程度の読み取りも発生する
- GOV.UK Notifyは洪水警報や旅券申請の進捗状況といった重要で即時性の高い通知を、毎日数百万件送信している
- すべての通知送信にデータベースアクセスが必要なため、サービス停止時間を短く保つ必要があった
DMSで構築した初期ロードと継続レプリケーション
- PaaSチームは、AWS Database Migration Serviceを用いたデータベース移行方法を提供していた
- DMSは元データベースから対象データベースへデータを移す役割を担い、元または対象のAWSアカウント上で実行できる
- DMSタスクは2段階に分かれる
- full load: 特定時点までに存在していたすべてのデータをテーブル単位でコピーする
- 継続レプリケーション: 元データベースの新しいトランザクションを対象データベースに再生し、2つのデータベースを同期させる
- アプリケーションが元データベースではなく対象データベースを使うように切り替える作業は、Notifyチームが直接担当した
対象データベースの準備とfull load
- DMSインスタンスは元のAWSアカウントに作成された
- PaaSチームがすでにアカウント内にDMSインスタンスを設定していたため、迅速に準備できた
- DMSインスタンスには元と対象のPostgreSQLデータベースへ接続できる認証情報が必要だった
- DMSインスタンスと対象データベースは異なるVPCにあったため、VPC peeringを設定し、PaaS VPCのDMSトラフィックがパブリックインターネットを経由せず自前のVPCへルーティングされるようにした
- 対象RDSインスタンスは自前のAWSアカウントに作成し、PostgreSQL 11のサポート終了が近づいていたため、新データベースはPostgreSQL 15で構成した
pg_dumpで元データベースのスキーマをダンプしてスキーマ再作成用SQLファイルを作り、最初はテーブル定義だけを対象データベースへ適用した- 外部キーはfull load時点では適用しなかった
- DMS full loadが外部キー制約の順序に従ってデータをコピーしないためである
- 主キーとインデックスもfull load前には作成しなかった
- 挿入のたびにインデックス更新が必要になり、数十億行を投入する際に全体時間が大きく伸びる可能性があるため
- 先にデータをすべてコピーしてからインデックスを追加する方が速かった
- full loadタスクは、開始ボタンを押した時点に存在していたデータをコピーする
- その後に発生した新規データや更新はfull loadに含まれない
- full load完了には約6時間かかった
- full load後に残りのスキーマファイルを適用してインデックスとキー制約を追加し、この作業には約3時間かかった
10日間レプリケーションを維持した後のトラフィック切り替え
- full load完了後、対象データベースはfull load開始時点の元データと一致していたが、その後も元では挿入・更新・削除が継続して発生していた
- DMSの継続レプリケーション、すなわちchange data captureタスクを開始し、full load開始以降の元データベースのtransaction logトランザクションを対象データベースへ送った
- レプリケーションプロセスが追いつくまでに数時間かかり、その後はDMSのレプリケーション遅延時間を監視して同期状態を確認した
- DMSレプリケーションは約10日間バックグラウンドで実行され、事前に利用者へ告知したトラフィック移行時点まで2つのデータベースを同期させた
- トラフィック切り替え手順は事前にPythonスクリプトとして作成した
- 元データベースへ向かうアプリケーショントラフィックを停止する
- レプリケーションが完全に追いついたことを確認する
- アプリケーションが対象データベースへ接続できるようにする
- 一部のアプリケーションが元DBを、残りが対象DBを使う状態は避ける必要があった
- 対象DBで生じた変更は元DBへ反映されないため、利用者に不整合なデータが見える可能性があるからである
- スクリプトは手作業よりも明示的で再現可能かつ高速に実行できるように作られ、事前テストとリハーサルで少なくとも40回使用された
- 目標ダウンタイムは5分未満で、移行時刻は深夜を避けつつ比較的静かな土曜の夕方に設定した
11秒のダウンタイムを実現したDNS切り替え
- 元データベースのトラフィック停止は、アプリケーション接続に対して
pg_terminate_backendを呼び出す方法で行い、1秒未満で完了した - アプリケーションが元DBへ再接続できないよう、PostgreSQLユーザーのパスワードも変更し、再接続時に認証エラーが発生するようにした
- DMSは対象データベースにレプリケーション状態テーブルを作成して毎分更新しており、移行スクリプトはこのテーブルを使って元と対象の間の遅延を確認した
- 追加の安全策として、アプリケーションが元DBへのアクセスを止めた後、スクリプトが元DBに単一レコードを書き込み、それが対象DBに到達するまで待機した
- アプリケーションのデータベース接続情報は
SQLALCHEMY_DATABASE_URI環境変数で提供されていた- 従来の形式は、ユーザー名、パスワード、RDSの場所を含む
postgresql://...@random-identifier.eu-west-1.rds.amazonaws.com:5432形式である - データベースの場所や認証情報を変えるにはアプリケーションの再デプロイが必要で、再デプロイには約5分かかった
- 従来の形式は、ユーザー名、パスワード、RDSの場所を含む
- 再デプロイによる追加ダウンタイムを避けるため、移行前に2つの準備を行った
- 元と対象のデータベースに同じユーザー名とパスワードを持つユーザーを作成した
- AWS Route53に
database.notifications.service.gov.ukのDNSレコードを作成し、TTLを1秒に設定した
- DNSレコードは当初、重みを元100%、対象0%に設定していた
- アプリケーションURIは、共通のユーザー名・パスワードと新しいドメイン名を使うよう事前に変更した
- 実際の切り替え時には、スクリプトがAWSのDNS重みを対象100%へ変更し、TTL 1秒の期限切れを待った
- 2023年11月4日土曜夕方の切り替え時点では、対象データベースと元データベースの間の遅延は数秒レベルだった
- 移行スクリプトの実行結果、アプリケーションは元DBへのアクセスを停止して新しい対象DBの利用を開始し、ダウンタイムは約11秒だった
DMS選定の評価とその後の作業
- DMSはGOV.UK PaaSでよくサポートされており、AWSのサポートも受けられたため採用された
- 今後PostgreSQL間のデータベース移行を再度行うなら、pglogicalのような代替ツールもさらに検討する予定である
- DMSは他のツールに比べて、複雑さと不慣れなレプリケーションプロセスを追加した可能性がある
- データベース移行後の次の段階はアプリケーション移行である
- GOV.UK NotifyアプリケーションはAWS Elastic Container Serviceへ移行予定で、その進捗は今後共有される予定である
1件のコメント
Hacker News のコメント
私たちも似たような移行を AWS RDS Blue-Green Deployments で行い、データベースは少し大きめでしたが、ダウンタイムは約20秒で、作業量もずっと少なく済みました。このスレッドでまだ触れられていないのが意外です。
基本的には、必要な変更を入れた新しい Blue/Green デプロイを立ち上げると、既存の blue 構成がトラフィックを処理し続ける間に、AWS が 論理レプリケーション で green デプロイを同期してくれます。
green には書き込みさえしなければ、修正やテスト、負荷テストも可能で、書き込みは引き続き live の blue に入り、その後 green にレプリケートされます。
準備ができたら switch コマンドを実行すると、AWS が同期確認、書き込みと接続の停止、レプリケーションの追いつき待ち、データベース名の変更、接続と書き込みの再開を処理します。
私たちの場合、ダウンタイムは20秒未満で、primary と複数のリードレプリカまで含めた構成全体が問題なく切り替わりました。AWS がデータベース URL も変更してくれるので設定を変える必要もなく、green が blue になり、従来の blue は old blue になるので、後で削除すればよいだけです。
強くおすすめしますが、アカウント間移動のようなケースでは使えない場合があるなど、制限はあります: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/blue-...
ドキュメント、特に制限事項は何度も読むべきです。開発環境で負荷をかけた状態でテストを実行し、ステージングでも再度試すのがよいでしょう。
あるいは、そのまま本番に YOLO で投入しても、おそらく大丈夫かもしれません。
ただし、RDS Blue/Green が任意の変更に使えるわけではないことを、苦労して学びました。私たちの場合、エンジンバージョンを上げることには使えても、下げることには使えないと分かりました。
MySQL 8.0 でストアドプロシージャの1つがごくまれに失敗したため、5.7 に戻す選択肢を検討しましたが、それは不可能でした。
トレードオフは、数秒間一部のリクエストが遅くなったことです。
DNS Groups とリトライの組み合わせは、この種の作業にはかなり使えるメカニズムです。
使用したツール: https://github.com/shayonj/pg_easy_replicate
入ってくる Postgres クエリを「一時停止」する方法はいくつもあり、たとえば pgbouncer を使って、失敗させずにレプリケーションが追いつくまで遅延させ、その後新しいデータベースで処理を続けさせることができます。
何か問題が起きてレプリケーションが追いつかない場合は、一時停止を解除し、それらのクエリを既存のデータベースで実行させればよいです。
そうすれば、11秒のダウンタイムは 0〜11秒の追加ページ読み込み時間に変わります。さらに重要なのは、これまでクエリ失敗を経験したことのない何千人ものデータベース利用者の中に、バグのあるエラー処理パスがあったり、たった1回のクエリ失敗でバッチ処理全体が壊れたりする場合でも、巻き添え被害 を大幅に減らせることです。
https://knock.app/blog/zero-downtime-postgres-upgrades と比較してみると興味深い。関連する議論は https://news.ycombinator.com/item?id=38616181 にあった
当時の議論の多くは「数分のダウンタイムを避けるために複雑にしすぎではないか」という結論に落ち着いていた。今回の事例はその証明のようにも見え、AWS Data Migration Service を使い、DNS レコードを切り替えて本番に移行したうえで 11秒のダウンタイムを受け入れればよい、という方向に見える
一部の特殊なデータ型はまったく処理できないこともある。移行後にはシーケンスも更新する必要があり、そうしないと主キー重複エラーが発生する可能性がある
適切な主キーがない場合、常に行全体を一度にコピーするわけではないため、問題が起きることもある
データベースが同じ AWS アカウント内にあり、4〜5分のダウンタイムを許容できるなら、グローバルデータベースやスナップショットを使った ハードウェアレベルのレプリケーションのほうが簡単である可能性が高い
最近、セルフホストしていた 3TB PostgreSQL データベースを 12 から 16 へ移行し、Ubuntu 18 から Ubuntu 22 へ切り替えた。同時に複数の拡張機能もアップグレードする必要があり、特に Timescale はすべての組み合わせを満たす互換バージョンがなかった
読み取り専用レプリカをアップグレードする方式で進めた。開始時点は PG12、Ubuntu 18、TS2.9 で、まず Ubuntu 22 上に PG12 と TS2.9 を維持した読み取り専用レプリカを作成した
その後メンテナンスモードに入り、すべてのサービスを停止し、読み取り専用レプリカを切り離したうえで、Ubuntu 22 上で PG12 を PG15 に上げつつ TS2.9 は維持した
次に PG15 と TS2.9 から TS2.13 へ上げ、最後に Ubuntu 22 上で PG15 を PG16 へ上げながら TS2.13 を維持した
最後にサービスを新しいデータベースサーバーへ再接続し、すべてのサービスを再開してからメンテナンスモードを終了した
すべてのデータベースアップグレード手順は Ansible で十分にテストし自動化していたが、テスト中には出なかった問題が1つ発生し、ダウンタイムは約30分に延びた。自分たちの用途では十分に許容できる範囲だった
論理レプリケーションを使っていれば、最後の瞬間の予期しない問題を減らせたかもしれず、次回のアップグレード周期ではこのアプローチを検討する予定
アップグレード時のダウンタイムを減らそうと論理レプリケーションも検討したが、データベーススキーマと DDL コマンドは複製されないため、Timescale が絡む場合は推奨されないように見えた
Timescale が内部で行う必要のある基盤スキーマ変更は、おおむねハイパーテーブルのチャンクサイズと流入する書き込みパターンの関数だろうから、計画したりタイミングを合わせたりすることはできるだろう。しかし、pg_upgrade が終わる間の短いメンテナンス枠を取る方法に比べると、潜在的な複雑さとリスクが大きすぎると判断した
こうした低ダウンタイム・無停止移行の敵は 長時間実行されるクエリだ
たとえば30分かかる単一の update クエリがある場合、そのクエリを kill してロールバックするか、30分の可用性喪失を受け入れる必要がある
私の知る限り、現在実行中のクエリを移行する方法はない
statement_timeout設定は味方だ極端に長いトランザクションがあるなら、それが実行されるタイミングを避けて切り替えられる可能性が高い。ランダム発生ではなく、スケジュールされたジョブのような結果であることを願う
トランザクションの時間制限とフェイルオーバー構成、たとえば既存の primary を失敗させる方式、そして pgbouncer のようなものを組み合わせれば、ダウンタイムの代わりに 遅くなる時間をかなり精密に制御できる
正直なところ、より心配なのはスタック全体と依存している外部キャッシュ DNS サーバーが DNS TTL を正しく守るかどうかだ
ただし通常のアプリでは十数秒のダウンタイムを避けることが致命的とは限らないので、自分にとってより単純な解決策を選ぶのが正しい
もちろん前者は実際には多く存在する。分単位や時間単位の処理をミリ秒単位に変える楽しさはかなり味わってきた
どのようなデータとどのような人たちがそうしたデータベース書き込みを扱っていて、上位レイヤーでキューにより細かく分割せずに、DB エンジンがそれほど長く処理することに依存しなければならないのか気になる
database.notifications.service.gov.ukに 1秒の TTL を持つ AWS Route53 DNS レコードを作成し、移行スクリプトが AWS の DNS ウェイトを対象データベースの場所へ 100% 送るように変更したあと、TTL の期限切れまで1秒待ったという部分が不思議それならアプリが次にデータベースへクエリしようとしたとき対象データベースにクエリすることになる、と言っているけど、これは彼らの ORM や Python のデフォルト動作が、クエリごとに DNS ルックアップをしてブロックされるという意味なのか?
解決済みアドレスをある程度の時間キャッシュすることもなく、コネクションプーリングや再利用もしないということ?
getaddrinfoやgethostnameがこの動作をするのだろう。Python はシステムレベルの呼び出しをほとんど再実装しないので、システム設定に依存する1秒 TTL が守られていたなら1秒間はキャッシュされたはずだが、DNS ルックアップライブラリ、とくにキャッシュ DNS サーバーが TTL を完全に守らないことも珍しくない。正直、それが観測されたダウンタイムの一部を説明している可能性もある
良いね。私たちはつい先ほど、RDS 上で約 2TB のデータ、8個のデータベースを含む Postgres クラスター3つを Postgres 14 から 16 に移行した。00:00 から 04:00 まで停止した
まず Cloudflare Workers で動く非常に軽量な代替サイト「メンテナンスモード」を有効にし、Terraform で DB を使うすべてのアプリを 0 にスケールダウンした
AWS の Web UI でアップグレードボタンを押して
pg_upgradeで 14→15 を実行し、完了を待ってから、もう一度押して 15→16 を進めたデータベースが接続を受け付け始めるまで待ったが、ready と表示される前でも接続は受け付けているようで、AWS は
pg_upgrade以外にも何かしているようだったその後
VACUUM ANALYZE; REINDEX DATABASE CONCURRENTLYを開始した。バージョン間の性能問題を避け、新バージョンの性能改善を活用する意図だったアプリを再び起動し始め、すべてのアプリでコンテナがいくつか立ち上がっているのを待ってからトラフィックを受け始め、メンテナンスサイトをオフにして寝た
REINDEX CONCURRENTLYは最大の DB でさらに18時間走り続けたが、何もブロックしなかった次回はダウンタイムを避けるために AWS Blue/Green デプロイを使う予定。今回は Blue/Green がサポートする 14 の最小マイナーバージョンである 14.9 ではなかったので使えなかった
自分でやるなら AWS の費用を払わず、論理レプリケーションとロードバランサーで Blue/Green を自前構成すると思う
pg_upgrade --hardlinksを使う自前のオンプレミス Postgres インスタンスで 2TB DB でも1分未満で処理したことがある
GOV.UK Notify は、GDS が英国の公共機関に提供するサービス群の一部。GOV.UK Pay と GOV.UK PaaS も含まれており、もともとは Government As A Platform として知られていた
DMS はひどい移行ツールだ。いくつもの移行問題とほぼ1か月格闘した末に諦めた
text と json 型を移行できず、AWS サポートも解決策を提示できなかった
AWS Blue/Green を初期テスト段階で使ってみたが、そのおかげでほぼ無停止のアップグレードが現実的になった
完全に壊れている
興味深いけれど、そもそも政府がなぜ AWS を使うのか分からない。これはプロダクトマーケットフィットを探るためにハックしているスタートアップでもなければ、マーケティング主導のトラフィック急増を予測できず対応している状況でもない
こうしたサービスが長期的に必要だと分かっていて、利用パターンもかなり予測できる
公共部門向けクラウドを作るか、合理的なオンプレミスのアプローチを採用できるはずだ。資金、調整、技術リーダーシップは必要だが、長期的には納税者に莫大なコスト削減をもたらすだろう
公共部門の IT は概してひどいものだが、その中で働く優秀なエンジニアもいることは分かっている
ハードディスクやドライバ、あるいはそのクラウド版であるストレージや Ansible のようなものを抱えて走り回る1時間は、顧客に必要なものを作るために使えない1時間だ
政府だからといって、なぜ違うのか?
政府が自動車を自前で作るとは期待せず、Volkswagen や Renault から買うと期待する。政府に輸送需要が明らかにあってもそうだ。なのに、なぜ IT インフラだけは自前で作れと言い張るのか分からない
それに、パンデミックのように完全に予想外のところから飛び出してくる出来事もある。パンデミック中に需要に合わせてスケールできたことは、公共部門が商用パブリッククラウドを使うべきだという重要な実証例の一つだった
Gov.UK の複数のイテレーションとクラウド間マイグレーションを扱った9月の発表 https://youtube.com/watch?v=mpY1lxkikqM&pp=ygUOUmljaGFyZCB0b... を見ることを勧める
少なくとも英国政府では、調達要件のため、数年ごとに利用量ベースの見積もりを市場に出し直さなければならない
例えば Oracle Cloud の価格が10分の1なら、そちらが契約を獲得する可能性が高く、その場合は契約期間中に Oracle へ移行しなければならず、その後さらに安い別のクラウドが出てくれば、また移る必要があるかもしれない
人生で見た中で最悪だった。VB.NET、Web Forms、古い SharePoint、Basic、さらにはアプリ全体が巨大なストアドプロシージャの塊というレガシーもたくさん扱ってきたにもかかわらず、そうだった
AWS、Azure、Google Cloud は少なくともエンドユーザー、つまり開発者を念頭に置いて作られている。一方、政府クラウドは、あらゆるところでコストを削ることを第一目標にした最低価格入札者が設計・構築したものだった
逆に、ドイツ政府系医療機関の社内データセンターを運用していた、本当に優秀なインフラ/運用担当者たちに会ったこともある。そこでの問題は技術でも人でもなく、インフラチームとエンジニアリングチームのあらゆるやり取りでボトルネックになろうとする管理職とプロセスが100%だった