SREグループの古越です。
SREグループではAurora PostgreSQLのインプレースアップグレードで少し痛い目を見た過去があり、Aurora PostgreSQL 10.x EOS(2023/01/31)を切っ掛けとしてインプレースに変わる安全なアップグレード方法を模索していました。 2022/9頃から検証を始め、論理レプリケーションを使ったBlue/Greenデプロイ方式のアップグレード手順を確立することが出来ました。背景と具体的な手順について紹介していきます。
PostgreSQLは1年に1つ新しいメジャーバージョンがリリースされ、メジャーバージョンごとに初期リリースから5年でEOL(End of Life)を迎えます。
Aurora PostgreSQLは本体のPostgreSQLよりも2ヶ月程度遅れる形でEOS(End of Support)がやってきます。ユーザ目線で見ると、利用開始から5年以内に最低1回はアップグレードのアクションが必要になっています。
PostgreSQL EOLなど最新情報はこちらから確認出来ます。
https://www.postgresql.org/support/versioning/
一般的にPostgreSQLのアップグレード方式として以下の2種類が挙げられます
インプレースアップグレードは手軽に行うことが出来ますが、長めのダウンタイムが発生します。プロダクション環境に対する適用は、作業中やアップグレード後の影響を加味する必要が有ります。
Blue/Green方式での切り替えは管理者が頑張る事で実行可能ですが、考慮ポイントが多いためインプレースほど簡単に行えないことがデメリットとしてあります。
インプレースアップグレードはマネジメントコンソールからポチポチで実行できる簡単さが良いところですが、プロダクション環境に適用するには注意点がいくつかあります。
主な注意点
マイナーバージョンアップとは基礎から異なるので注意が必要です
AWSのナレッジセンターに、よくあるトラブルとトラブルシュート方法が記載されているため、懸念が有れば以下をチェックして頂くと良いかと思います。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/rds-postgresql-version-upgrade-issues/
パフォーマンス劣化要因
注意点としてアップグレード直後にパフォーマンス劣化する点を挙げていますが、劣化する主な原因は2つ考えられます
アップグレード後は統計情報が失われるため、 ANALYZE; による統計情報再構築が推奨されています。また、バッファキャッシュ対策有無で初回のクエリ応答速度が変わるため可能ならば対処しておきたいポイントです。
インプレースアップグレードを安全行う方法
上述の注意点があるため、安全を考えた場合
という形で段取りすれば安全にアップグレードできるかと思います
インプレースアップグレードでも良いのですが過去に苦い経験があり、なるべくダウンタイムが少ないBlue/Green方式を模索していました。
インプレースアップグレードの苦い経験
過去にAurora PostgreSQL 9.xのEOSを迎えるサービスがあり、10.xバージョンに引き上げる作業をSREグループで行っていました。
300GB程度のDBをメンテナンスに入れず、オンラインでインプレースアップグレードした結果
というユーザ影響が出てしまいました。
マイクロサービスの恩恵で1DB単位の影響が小さくなっている
もう一つの背景としてはマイクロサービスの採用があります。MoTではAurora PostgreSQLを使っているマイクロサービスが多くあります。基本的にサービス毎にRDSクラスターを分離してるため、アップグレード頻度は増えてしまっています。
ただメリットもあります。モノリシックなサービスよりはDBが分散してるため、インフラ部分の瞬間的なトラブルによるユーザ影響は小さくなります。サービスの特性にもよりますが、1サービスでダウンタイム数分または数秒レベルに最小化できるなら、オンラインでDBアップグレードを行ってもユーザーが体感出来ないレベルまで影響を落とすことが可能と考えられます。
そういった経緯や背景があり
というモチベーションになり、DB UPgradeに合わせてBlue/Green方式を標準化しようと取り組む事になりました。2022/9頃から事前検証とアップグレード作業を積み重ねて今に至ります。
本題から逸れますが、2022/11頃にAmazon RDS Blue/Green Deploymentがβ版として発表されています。Aurora MySQLユーザはメジャーアップグレードする方法を複数個から選択肢できるようになっています。
実際に試したところ
という点がわかっています。
意外にダウンタイムが長い印象はありますが、お手軽に実行出来る点が素晴らしいと思います。PostgreSQL版が提供されてないことが残念で、将来使えることを期待しながら本記事を書いております。
仮にPostgreSQL版がリリースされても、読み込みダウンタイムを最小化したい場合は本記事のようなアプローチが参考になるかもしれません。
前置きがだいぶ長くなりましたが、今回紹介するBlue/Green方式の手順について概要から説明致します。
構成踏まえた大まかなイメージとしては以下となります。
Blue/Green 概要図
実環境と微細は異なりますが、AWS上でよくある構成を基にイメージして頂くための簡易な図です。自分がやったときはEC2踏み台サーバは無く、EKS上で作業用podを起動して作業しています。
大まかにはRDS Blue/Green Deploymentと似た要領で、準備と切り替えの2ステップになります。
事前準備
(事前準備が終わった後、新しくDDLを発行しないよう関係者と調整する)
切り替え
影響は以下の通りです
設定中のパラメータ次第ですが、レプリケーション準備に再起動が必要なケースがあるので、準備段階にダウンタイムが発生するケースが有ります。
切り替え作業は稼働している2つのDBクラスターを切り替える作業になるため、TCP的なダウンタイムは発生しません。しかし、データの整合性を担保するために書き込みクエリを一瞬停止する必要が有り、書き込みエラーが発生するタイミングが有ります。
書き込み停止~切り替えの期間はスムーズにすすめることができれば数分で完了します。
MySQLの場合はバイナリログレプリケーションが知られているかと思います。MySQLバイナリログレプリケーションは1つ先のメジャーバージョンに対してレプリケーションを貼ることができるため、メジャーバージョンアップに利用できる特徴があります。
対してPostgreSQL公式のレプリケーション技術は2種類存在します
ストリーミングレプリケーションは一般的にリードレプリカを作成するケースで利用されていてWAL(Write Ahead Log)をディスクのブロックレベルでレプリケーションしている点が特徴です。シンプルですがメジャーバージョンを跨ぐレプリケーションが出来ないため、アップグレードには使うことが出来ません。
論理レプリケーションはWALをデコード(論理デコード)したデータを基礎として、デコードしたデータを転送先でリプレイする事で実現されてます。debeziumのような論理デコードを使ったCDC(Change Data Capture)に近い方法で実現される方式でして、一つ以上先の異なるメジャーバージョン間でもレプリケーションが可能になっています。
また、共通して言える事ですがPostgreSQLの場合はWALにすべての書き込み履歴が残っているため、mysqldump & ポジション一致のような作業は必要ありません。レプリケーションを設定した段階で初めから同期されます。
私自身あまり詳しくないところからはじめましたが、レプリケーション技術については以下スライドが非常によく纏まっております。
https://www.slideshare.net/masahikosawada98/postgresql-86891271
論理レプリケーションの設定自体は簡単です。
これでレプリケーションを組むことが出来ます。
メジャーバージョンを跨ぐレプリケーションが可能だったり、特定のテーブルに限定するなど調整が可能な所が論理レプリケーションの良い点です。ただ細かい制約があるため注意が必要です。
制約
注意すべき点を抜粋すると以下になります
詳しくは公式ドキュメントに記載されています。
https://www.postgresql.org/docs/current/logical-replication-restrictions.html
制約の回避策など
DDLとシーケンスがレプリケーションされない点については、アップグレード用途ならpg_dumpによる手動同期策があるのであまり問題になりません。
そしてAurora PostgreSQL限定の制限になりますが、Auroraのマスターユーザは一部権限が制限されているためpg_dumpで他のユーザ(role)のパスワードを出力することが出来ません。パスワードは別の形で再設定するなど、ひと手間必要になります。
ラージオブジェクトがレプリケーションされない点も注意が必要です。ただ、多くのケースでそこまで重用してないかと思います。利用している場合は書き込み停止時に pg_dump & restoreしてデータ移行するなど回避策を検討すると良さそうです。
具体的なコマンドを交えながら紹介していきます。
論理レプリケーションするためには再起動が必要なパラメータを事前に入れる必要が有ります。
Auroraの場合はパラメータ名が少しややこしいですが
で論理デコードが有効化されます。
このパラメータのために再起動を調整する必要があるため、将来のアップグレードに備えてクラスター作成時に有効化しておくと良いと思います。
詳細な手順は割愛しますが、snapshotからの復元等は不要で空のクラスターで構いません。
PostgreSQLはWALにすべての書き込み記録が残っているため、空状態からのフルロード、レプリケーション維持が容易に出来るようになっています。
MySQLの場合はバイナリログが時間経過で消えてしまうため、ログ記録されてないデータをdump & restoreしたり、ログの何処から読み取るかといったポジション指定作業が必要でした。作業自体はMySQLより簡単になると思います。
論理レプリケーションはMySQLレプリケーション同様にレプリケーション先が主体的に動いてデータを取得しに行きます。
PUBLICATION, SUBSCRIPTIONという設定が有り主体が分かりにくいのですが、PUBLICATION(Blue) <-port:5432- SUBSCRIPTION(Green) の経路で通信しています。
BlueクラスターのSecurityGroupを修正しGreenクラスターからのinbound 5432を許可してください。
踏み台サーバに相当するものがない場合、Blue, Green両方のクラスターにpsqlコマンドでログイン出来る環境を事前に作っておいてください。
作業用にEC2インスタンスを作成し、postgresをインストールするのが手っ取り早いかと思います。
今回はBlue, Greenと双方のクラスターに対して入力が必要なポイントが多数あります。
細かいコマンド入力を省くため、psql, pg_dumpで共用出来る環境変数を使っていきます。
環境変数に以下の値をセットして後続作業を進めます。
# psql,pg_dump共用環境変数
export PGPORT=5432
export PGDATABASE=main
export PGUSER=postgres # Master User
export PGPASSWORD=postgres
# RDS Endpoint
export BLUE_PGHOST=<blueクラスター クラスターエンドポイント>
export GREEN_PGHOST=<greenクラスター クラスターエンドポイント>
ちなみに、パスワードを PGPASSWORD に記録して良いかは環境によって違いますので注意してください。いくつかのOSではroot以外のユーザがpsコマンド経由で環境変数を見ることができるため、セキュリティリスクが有るようです。
仮に別組織と共用してる踏み台サーバが有る場合、パスワードファイルを使う形が良さそうです。適宜使い分けていただければと思います。
pg_dump —schema-only を使うとDDLを読み取ることが出来るため、同期作業に利用します。
dump結果はそのまま実行できるSQLになっているため、Blue側のpg_dumpの出力にパイプを通しGreenクラスターに横流しすると楽に同期出来ます。
pg_dump -h "${BLUE_PGHOST}" --schema-only | psql -h "${GREEN_PGHOST}"
DDLの同期が終わったら以下のようにBlueクラスターに PUBLICATIONを作成します。
名前は任意で良いのですが、 今回は engine_upgrade という名前で作成します。
psql -h "${BLUE_PGHOST}" -c "CREATE PUBLICATION engine_upgrade FOR ALL TABLES;"
続いてGreenクラスターにもSUBSCRIPTIONを設定します。こちらも任意の名前で良いです。
上記で設定したengine_upgrade というPUBLICATIONを見に行くように設定します。
psql -h "${GREEN_PGHOST}" -c "CREATE SUBSCRIPTION engine_upgrade CONNECTION 'host=${BLUE_PGHOST} port=${PGPORT} dbname=${PGDATABASE} user=${PGUSER} password=${PGPASSWORD}' PUBLICATION engine_upgrade;"
問題なければ、このSUBSCRIPTIONを設定した直後からレプリケーションが開始します。
今回はアップグレード用の一時的なレプリケーションを目的としているのでRDSのマスターユーザをそのまま使う手順としています。セキュリティが気になる場合はレプリケーション用ユーザを別途発行する形が良いと思います。
レプリケーション開始後は以下のコマンドを見て状況を確認出来ます
PUBLICATION
psql -h "${BLUE_PGHOST}" -x -c "\\dRp"
psql -h "${BLUE_PGHOST}" -x -c "SELECT * FROM pg_stat_replication;"
pg_stat_replicationは送る側の統計情報を見れますが、レプリケーションがうまくいっているか判断するには十分な情報が有りません。軽く目を通して問題無さそうか見る程度で良いです。
SUBSCRIPTION
psql -h "${GREEN_PGHOST}" -x -c "\\dRs"
psql -h "${GREEN_PGHOST}" -x -c "SELECT * FROM pg_stat_subscription;"
後述しますが、SUBSCRIPTION側の情報には幾つか重要な情報が有ります。
なおPostgreSQL 15からは pg_stat_subscription_stats という良いビューが存在しますが、PostgreSQL 10.x~14.xには存在しないため今回は省略します。
DBサイズの確認
psql -h "${BLUE_PGHOST}" -x -c "SELECT pg_size_pretty(pg_database_size(current_database()));"
psql -h "${GREEN_PGHOST}" -x -c "SELECT pg_size_pretty(pg_database_size(current_database()));"
レプリケーションが始まるとGreen側のpg_size_prettyが大きく変動していきます。
Blue側とGreen側のサイズを比較して、レプリケーション進捗を確認出来るので、両方同時に出力して様子を観察すると良いでしょう。
実際にやってて気づいた所ですがDBエンジンの更新に伴ってデータの圧縮効率は変わっているようです。PostgreSQL 10.x, 14.x それぞれレコード数が同じでもGreen側が概ね 10%程度小さくなる傾向が有りました。
確認方法は以上の通りですが、レプリケーションを貼った後に以下観点で問題無いこと確認すると大変良いと思います。
初期ロード中の確認ポイント
Green側のpg_stat_subscriptionを繰り返し実行し、以下をチェックする
その後の確認ポイント
すべて問題なければレプリケーションは完了です。
補足: 初回ロードからレプリケーションラグ解消までの所要時間
一概には言えませんが参考情報として、以下の条件では約5,6時間かかりました。
レプリケーション速度はデータ構造やインスタンスタイプによって異なると思いますが、データ量が多い場合は早めにレプリケーションを貼るように日程調整が必要そうです。この点はご注意ください。
レプリケーションが追いついた後、切り替わり直後のパフォーマンス劣化を防ぐために統計情報の更新とバッファの暖機を行います。
ANALYZEは1クエリ実行すれば完了します。
psql -h "${GREEN_PGHOST}" -c "ANALYZE;"
注意点ですがDBサイズ次第で時間がかかることを加味して、事前に実行することをオススメします。400GBのクラスターの場合は15分程度かかりました。
pg_buffercacheとpg_prewarmという拡張を使う事で bufferを暖機することが出来るので使い方を説明してきます。
まずはBlue側でpg_buffercacheを有効化し、よくbufferを利用しているテーブルを表示して調査します。
psql -h "${BLUE_PGHOST}"
-- pg_buffercache 有効化
CREATE EXTENSION pg_buffercache;
-- バッファページ数集計 (pg_* 系は除外)
SELECT c.relname, COUNT(*) AS buffers
FROM
pg_buffercache b
INNER JOIN
pg_class c
ON b.relfilenode = pg_relation_filenode(c.oid)
AND b.reldatabase IN(0,(
SELECT oid
FROM pg_database
WHERE datname = current_database()
))
WHERE c.relname NOT LIKE 'pg_%'
GROUP BY c.relname
ORDER BY 2 DESC
LIMIT 20;
上記SQLで TOP 20のテーブルをざっと確認します。
次にGreen側でpg_prewarmを使ってバッファを温めます。
psql -h ${GREEN_PGHOST}
-- pg_prewarm 有効化
CREATE EXTENSION pg_prewarm;
-- buffercacheの集計クエリを元に pg_prewarmでテーブルを指定して暖機する
-- 注意: bufferが蓄積した時は古いbufferから消えるため、利用頻度の低いテーブルから順に実行する
SELECT pg_prewarm('table5');
SELECT pg_prewarm('table4');
SELECT pg_prewarm('table3');
SELECT pg_prewarm('table2');
SELECT pg_prewarm('table1');
bufferは共有メモリですのでbufferに載せられるデータは限界が有ります。
bufferが十分に蓄積したときは古いbufferから消えていくため、利用頻度の低いテーブルから順に実行する事で、よく使うテーブルを優先的に暖機出来ます。
厳密にはもっと最適な暖機方法が有るかと思いますが、このアプローチでも一定の効果が見込めると思います。暖機した後、Green側にもbuffercacheの調査クエリを投げ Blue側と似たような状態か確認すると良いでしょう。
切り替え前の準備は以上です。
以後の作業はエンドユーザー影響が発生するため、なるべく利用者の少ないタイミングで実施するかメンテナンスを調整して実施する事をオススメします。
データベース全体に対して default_transaction_read_only = onを指定する事で書き込みを停止出来ます。
しかし、このパラメータが効くのは新規コネクションからです。コネクションプーリングしているようなケースの既存コネクションには影響しません。
100%書き込みを停止させるために、既存のコネクションを一度遮断すると良いと思います。
# read_onlyモード切り替え
psql -h "${BLUE_PGHOST}" -c "ALTER DATABASE ${PGDATABASE} set default_transaction_read_only = on;"
# rdsadminユーザ、replicationプロセスを除外したpidを遮断する
psql -h "${BLUE_PGHOST}" -x -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = current_database() AND pid <> pg_backend_pid() AND usename <> 'rdsadmin' AND application_name <> 'engine_upgrade';"
default_transaction_read_onlyはパラメータグループにも項目があります。ただ、こちらで検証したときはパラメータグループを変更しても書き込みを止めることが出来ませんでした。
ALTER DATABASEクエリで実行する方法が高速で確実かと思います。
書き込み停止の確認方法
書き込みが本当に停止しているか確認するにはpg_stat_activityを見るだけでは判断出来ません。pg_stat_databaseビューをwatchコマンドなどで目視して書き込み停止を確認する事をオススメします。
watch -n 0.5 "psql -x -c 'SELECT datname,xact_commit,xact_rollback,tup_inserted,tup_updated,tup_deleted,conflicts FROM pg_stat_database WHERE datname = \'${PGDATABASE}\';'"
対象データベースの tup_inserted, tup_updated, tup_deletedのカウンターが止まっていれば書き込みは停止しています。
補足: 書き込み停止の必要性
書き込みの静止点を必ず確保し、十分確認することを強くオススメします。
ダウンタイムを少なくしようと焦ると、書き込み停止確認を怠ってしまう事があります。必ず確認してください。30秒から1分程度様子を見る形でも良いと思います。
万が一パラメータにミスが有ってBlue側に書き込みが継続していた場合、Greenに切り替えた後にデータが欠落してしまいます。データ欠落を実際にやらかした事があるのですが、欠落したレコードの洗い出しと復元を迅速に行う事が求められ、レプリケーション作業より難易度の高いオペレーションが必要になりました。
深刻な被害を出さないために、小さなエラーを許容するというマインドでよいかと思います。
書き込みが停止している場合はレプリケーションする必要も無いため、ここで停止します。
psql -h "${GREEN_PGHOST}" -c "ALTER SUBSCRIPTION engine_upgrade DISABLE;"
sequenceはDDL同様にpg_dumpで簡単に同期出来ます。
pg_dump -h "${BLUE_PGHOST}" --data-only -t '*.*_seq' | psql -h "${GREEN_PGHOST}"
このsequence同期作業は論理レプリケーションの挙動に影響ありません。
書き込みを停止する前に事前実行し、問題なく同期されるか確認すると良いと思います。
アプリケーションによって異なりますが、幾つかの方法で実現可能です。
デプロイや再起動時にダウンタイムが発生しない作りが前提になりますが、以下いずれかで実現できるかと思います。
MoTの多くのマイクロサービスではkubernetes secretsに格納したエンドポイントをpodの環境変数に設定しているため、方法1のパラメータ更新で切り替えています。
方法4のCluster Identifier切り替え方式はMySQL版 RDS Blue/Green Deploymentが採用している方法と同じです。アプリケーション側の対応が不要になりますが、他方法と比べるとTCP的なダウンタイムが発生する点がデメリットとしてあります。
アプリケーションに手を入れることが出来ないときの最終手段として使えば良さそうです。
アプリケーション自体を動作確認するのが手っ取り早いですが、DBも含めて全体的に確認しておきましょう。
チェックするポイントとしては以下の通りです
論理レプリケーションの解除
psql -h "${GREEN_PGHOST}" -c "DROP SUBSCRIPTION engine_upgrade;"
SUBSCRIPTION設定を残したままPUBLICATION側のクラスターを削除してしまうと、DROP SUBSCRIPTIONがエラーになり、ひと手間必要になります。
先に解除しておきましょう。
Blue側クラスターの撤去
Greenに切り替わった後、半日程度経過を見てから撤去すると良いかと思います。
snapshotを保持して削除しましょう。
長くなりましたがBlue/Geen方式に関してのまとめになります。
MoT SREグループでは今回紹介した手順を元にして、5つのマイクロサービスのAurora PostgreSQLをアップグレードしています。来年頭にAurora PostgreSQL 11.x EOSが予告されているため今年後半に再び同じアプローチで実施する予定です。
PostgreSQLはMySQLと比較すると互換性の無い変更点が少ないため、1バージョンずつアップグードせずにEOL, EOSのタイミングで一気に最新にアップグレードする戦略も可能です。今年中にはPostgreSQL 11.x → 15.xのアップグレードを段取りしていきたいと考えています。
ブログ中では割愛していますがアップグレード後にPostgreSQL ExtentionやSQLクエリが問題なく応答されるかは別途事前検証されることをおすすめします。
手動Blue/Green方式は手間と習熟が必要です。この記事が不要になるAurora PostgreSQL版 Blue/Green Deploymentがリリースされることを待ち望んでいます。
興味のある方は 採用ページ も見ていただけると嬉しいです。
Twitter @goinc_techtalk のフォローもよろしくお願いします!