freeeの開発情報ポータルサイト

RDS Proxyを用いたオンラインスイッチオーバーによるMySQLのアップグレードについて

おはこんばんちは、DBREの橋本です。

今回は、Amazon RDS Proxy(以降RDS Proxyとよぶ)を用いたRDS for MySQLインスタンスおよびAurora MySQLクラスタのオンラインスイッチオーバーの手法について、ある程度社内での運用が確立してきましたので解説いたします。

従来のアップデート手法

AWS上でRDS for MySQLインスタンスやAurora MySQLクラスタ(以降これらをデータベースとしてまとめてよぶ)を運用している場合、それらのエンジンバージョンの更新を行ったり、OSバージョンの更新に伴う再起動を実施する必要があります。これらの更新を行う場合、以下のような方法が考えられます。

  1. 対象のデータベースに直接更新を適用する
  2. スナップショットを作成し、更新済みのデータベースとして復元する
  3. 更新済みの空のデータベースを新規作成し、そちらにデータを移行し、スイッチオーバーによってクライアントからのトラフィックを新しいデータベースに向ける

このうち、freeeの運用では更新後に問題が発生した際の切り戻しを考慮し、3.の方法を採用することが多いです。
この方法について、大まかな流れを以下に説明します。なお、RDS for MySQLとAurora MySQLのどちらも同様の方法となります。

前提

各データベースは以下の設定となっている前提で進めます。

  • Binary Logが出力されていること。Auroraクラスタの場合は初期状態では出力されていないため、パラメータグループを編集してBinary Logを出力させておく
  • データベースを指すRoute 53レコードが存在しており、そのCNAMEはデータベースのエンドポイントを指定していること。クライアントはデータベースへの接続の際にRoute 53レコードから解決する
  • 切り戻しは考慮しているが、移行先のバージョンは下がらないものとする。たとえばAurora 2からAurora 3、またはMySQL 5.7からAurora 2のような移行パスを想定している

1. スナップショットを作成し、新データベースとして復元する

AWS APIのCreateDBClusterSnapshotまたはCreateDBSnapshotを用いてデータベースのスナップショットを作成します。データベースの復元はRestoreDBClusterFromSnapshotまたはRestoreDBInstanceFromDBSnapshotを用います。
新データベースとして復元後、必要に応じてエンジンバージョンの更新やPerformance Insightsの有効化・拡張モニタリングなどの設定を行います。

新データベースの作成の図解
スナップショットを取得し、そのスナップショットから新データベースを復元する

2. 現行データベースから新データベースへBinary Log Replicationを行う

RDSのストアドプロシージャを用いて、現行データベースから新データベースへBinary Log Replicationを行います。

新データベースの復元時には現行データベースのBinary Logが引き継がれているため、新データベースのBinary Logを新しい方からさかのぼり、その SHOW BINLOG EVENTS の結果のうち Server_id フィールドの値が異なった時点のログファイルのポジションがレプリケーションの始点となります。

SHOW BINARY LOGSとSHOW BINLOG EVENTSの図解
Binary LogのServer_idの差異からレプリケーション開始のポジションを得る。この図の場合はPos = 24950が始点となる

Binary Log Replication開始の図解
現行データベースから新データベースへBinary Log Replicationを行い、データを同期する

レプリケーション開始後、新データベース側でSHOW SLAVE STATUSまたはSHOW REPLICA STATUSを実行し、レプリケーションエラーがないことを確認します。

新データベース内に存在する、アプリケーションで用いるMySQLユーザについて、書き込み権限を持っている場合はREVOKEステートメントによってSELECT権限のみ残すことでReadonlyの状態にしておきます。

3. スイッチオーバーを実施する

レプリケーションラグが0であることを確認してから、スイッチオーバーを実施します。まず、現行データベースをReadonlyにして新規の書き込みを停止させてから、必要に応じてクライアントのコネクションを切断・再確立させます。次に、データベースを指すRoute 53レコードのCNAMEを新データベースのエンドポイントに変更します。新データベースに名前解決されたら、旧データベース(現行データベースだったもの)に存在するユーザの権限をReadonlyにし、新データベースのユーザの書き込み権限を戻します。権限を反映させるため、データベース内に存在するアプリケーション用のMySQLユーザのプロセスをすべてkillします。

スイッチオーバーの図解
両方のデータベースをReadonlyにし、CNAMEを新データベースのエンドポイントに変更する。このとき、トラフィックは両方のデータベースに混在する可能性がある

また、旧データベースから新データベースへのBinary Log Replicationを停止させるとともに新データベースから旧データベースへのBinary Log Replicationを開始し同期させることで、逆向きのスイッチオーバーによる切り戻しを行えるようにしておきます。

スイッチオーバーの図解
クライアントからのトラフィックがすべて新データベースに向いたら、新データベースから旧データベースへのレプリケーションを開始し、旧データベースはReadonlyにする

4. 旧データベースを削除

新データベースの動作に問題ないことを確認してから、新データベースから旧データベースへのBinary Log Replicationを停止し、旧データベースを削除します。

従来の手法における課題

上記に示した手順で運用してきましたが、運用上の課題が発生していました。

「3. スイッチオーバーを実施する」において、Route 53レコードのCNAMEの変更によるトラフィック切り替えの場合、CNAMEの変更の反映は非同期に行われるため、すべてのトラフィックが切り替わるまでに最大数分間かかることが予想されます。数分間継続するような書き込みエラーは許容しがたいため、深夜メンテナンスを設けてリクエストがない状態にしてからスイッチオーバーを実施していました。

一方、深夜メンテナンスの時間帯はほとんどすべてのサービスを利用できなくなるため、顧客影響も考えられます。メンテナンスを設けず可能な限りサービス稼働時間を下げないことがマジ価値であると考え、オンラインでスイッチオーバーを実施できないかどうか検討しました。

新手法の検討

いくつか案を考え検証しましたが、結論として管理の容易さからRDS Proxyを採用しました。RDS Proxyはクライアントアプリケーションとデータベース間に配置されるフルマネージドのプロキシで、MySQLユーザの認証情報やコネクションプールに関する設定をいくつか行うだけで導入できます。その他の案は以下の通りです。

  • ProxySQL: クライアント-データベース間のプロキシ。内部の設定を変えることでトラフィックを切り替える
  • Amazon RDS Blue/Green Deployments: 仕組みとしては従来の手法と同様で、各手順がフルマネージドとなっているものです。2023年7月現在では切り戻し手法を確立できないため、導入を見送りました

RDS Proxyの導入にあたっては、別途クエリレスポンスタイムの変化やSession Pinningなどの調査も行い、実用上問題ないと判断しました。RDS Proxyはスイッチオーバーを主目的として導入しましたが、一般的な用途としてコネクションプールの最適化やフェイルオーバーの際の可用性向上が挙げられ、これらは副次的な効果として得られている具合です。

RDS Proxyを用いたスイッチオーバーの手法

RDS Proxyを用いた場合のスイッチオーバーは、以下のような手順となります。

  1. あらかじめ現行データベースの前段にRDS Proxyを用意し、データベースを指すRoute 53レコードのCNAMEをProxy EndpointにすることでクライアントからのトラフィックをRDS Proxyに向ける
  2. スナップショットを作成し、新データベースとして復元する(従来手法と同じ)
  3. 現行データベースから新データベースへBinary Log Replicationを行う(従来手法と同じ)
  4. RDS Proxyのターゲットグループに設定されているデータベースを、現行データベースから新データベースに変更することでスイッチオーバーする
  5. 旧データベースを削除(従来手法と同じ)

主な変更点はRDS Proxyのターゲットグループの変更のみとなります。このステップを詳細に説明すると、以下のようになります。

  1. レプリケーションラグが0であることを確認する
  2. DeregisterDBProxyTargetsおよびRegisterDBProxyTargets APIを用いて、RDS Proxyのターゲットグループのデータベースを変更する。AWS APIの都合上、ターゲットグループを編集後、ターゲットグループの適用およびRDS Proxy〜データベース間の接続の切り替えは非同期に実行される
  3. RDS ProxyにSHOW GLOBAL VARIABLES WHERE variable_name = 'hostname'を定期的に問い合わせ、hostname(データベースのホスト名)の値が変わったことを確認する。これにより、スイッチオーバーによってクライアントからのトラフィックが新データベースに向いたとみなす
  4. 新データベースから旧データベースへのBinary Log Replicationを開始する。この時点ではどちらのデータベースもReadonlyとなっており、データベース間の整合性は保たれているとみなす
  5. 新データベースのユーザの書き込み権限を戻し、旧データベースのユーザをReadonlyにする。このとき、権限を反映させるため、データベース内に存在するアプリケーション用のMySQLユーザおよびrdsproxyadminユーザのプロセスをすべてkillする

RDS Proxyを用いたスイッチオーバーにおけるトラフィック切り替えの図解
RDS Proxyを用いたスイッチオーバーにおけるトラフィック切り替えの図解

以降では、スイッチオーバーの際の懸念をいくつか洗い出して検証した内容を紹介します。

スイッチオーバーの際のデータベース間でのクエリ結果の混在の有無について

ターゲットグループを変更した際に、現行データベースと新データベースとで接続が混在し、同一クエリで異なるレスポンスを得られることがないかが懸念となったので、その検証を行いました。これはたとえばSHOW GLOBAL VARIABLES WHERE variable_name = 'hostname'を高頻度に実行し、ターゲットグループの変更の際のクエリ結果が混在しないかどうかを確認できればよいです。

WriterとReaderそれぞれを持つAuroraクラスタをターゲットグループに指定し、Read/Write Proxy EndpointとRead-only Proxy Endpointの両方についてクエリした場合の結果を確認しました。結果として、ターゲットグループ変更後、Read/Writeの場合とRead-onlyは同じタイミングでデータベースへの接続が切り替わることを確認しました。

なお、データベースが切り替わる際に実行されていたトランザクションは中断されるため、時間あたりのクエリ数が少ない時間帯にスイッチオーバーを実行するとよいです。

スイッチオーバーの際のクエリエラーについて

データベースのユーザがReadonlyであることと、権限の反映のためにプロセスをkillしているため、書き込みエラーの発生やトランザクションの中断が発生しうることがあります。トラフィックが新データベースに向いてから新データベースでの書き込み権限の反映完了まで、経験的に10秒程度かかることがわかっています。日次バッチによる書き込みやkillされると困るトランザクションがある場合、またはサービス稼働時間のような定量的な目標値がある場合は事前に調整しておく必要があります。

実例

今回紹介した手法はコードを実装し、ステップごとにコマンドを実行することで自動化しています。また、以前に紹介したAWS Step FunctionsとAWS Fargateによるタスク実行環境を今回のスイッチオーバーの仕組みに導入し、作業者がデータベースにアクセスしたり作業環境を意識することなく実行可能となっています。現在はDBREがスイッチオーバーの作業を請け負っていますが、タスク実行権限だけあればよいため、将来的にデータベースの利用者である開発メンバに作業を移譲することを目指しています。

developers.freee.co.jp

今回のオンラインスイッチオーバーの仕組みによって、MySQLやAuroraのパッチバージョンの更新や、Aurora 2からAurora 3へのアップグレードを夜間や早朝にオンラインで実施しました。別件にて定期メンテナンスを行う場合もありますが、データベースのアップグレード作業をメンテナンス外で行うことにより、時間帯の限られたメンテナンスにおいて作業を減らすことにも一役買っています。

まとめ

RDS Proxyがデータベースのスイッチオーバーに利用可能であることと、freeeにおける運用事例を紹介しました。RDBMSを中心としたデータベースの運用改善に関心がある方は、カジュアル面談や採用を受け付けていますので、ご気軽に連絡ください。

jobs.freee.co.jp