Aurora MySQL 5.6のサポート終了とゼロダウンタイムアップグレードへの挑戦

こんにちは、グローバル向けAWS版kintoneのバックエンドエンジニアをしている@ueokandeです。 8月になって暑い日々が続きますね。そして8月と言えば、Amazon Aurora MySQL-Compatible Edition version 1 with MySQL 5.6 compatibility(以下Aurora MySQL 5.6)のサポート終了までおよそ半年となりました。

グローバル向けAWS版kintoneでは、Aurora MySQL 5.6を採用しているバックエンドサービスがいくつかあり、チームで移行作業に取り組んできました。この移行作業は単なるアップグレードだけではなく、ダウンタイムなしでデータベースを移行するチャレンジにも取り組みました。この記事ではAmazon Auroraの移行作業の全貌と、移行戦略を紹介します。

Aurora MySQL 5.6のサポート終了

今年の2022年2月にAWSからAurora MySQL 5.6のサポート終了がアナウンスされました。

aws.amazon.com

グローバル向けAWS版kintoneでもAurora MySQL 5.6を利用していたバックエンドサービスが3つありました。

  • 定期処理サービス。データベースにはジョブの実行計画や最終実行時刻のチェックポイントなどが記録される。
  • テナント管理サービス。データベースにはお客様が利用するkintone環境の情報や、環境の構築状況が記録される。
  • ロードバランサー用サービス。データベースにはお客様が利用するFQDNやアクセス制限の設定が記録される。

いずれのサービスもお客様のリクエストを直接受理はしませんが、kintoneを支える大事なミドルウェアサービスです。これらのサービスが停止するとkintoneの定期処理が実行されなくなったり、kintoneが全断する可能性もあります。

各サービスはそれぞれAuroraクラスタを保有しており、アップグレードが必要なAuroraクラスタも3つです。これらのデータベースにはAurora Serverless 5.6を採用しており、Aurora MySQL 5.6のサポート終了対象の1つでした。

データベースアップグレードの選択肢

Auroraのアップグレードプラクティスはいくつかあります。古くからMySQLで実施されてきたものから、AWSが提供する機能までいくつかあります。

1. インプレースアップグレード

まず最初に思いつくのがAuroraクラスタのインプレースアップグレードで、最も手軽に実施できる手段です。ただし移行作業当時、Aurora ServerlessはMySQL 5.6から5.7へのインプレースアップグレードが未対応でした。

Aurora MySQL 5.6サポート終了の発表から4カ月後の2022年6月に、Amazon Auroraのインプレースアップグレードが発表されました。その時はすでにチームで移行作業をスタートしていたのと、ダウンタイムが発生するので、結局インプレースアップグレードは採用しませんでした。

aws.amazon.com

2. バックアップ・リストアによる移行

インプレースアップグレードができないデータベースでは、新・旧両方のクラスタを構築して、データを移行することでアップグレードできます。mysqldumpによるデータの書き出しや、Auroraではスナップショットからのリストアも利用できます。移行作業中はデータの不整合やデータ損失を防ぐため、サービスを停止する必要があります。

3. バイナリログレプリケーション

バイナリログ(binlog)を使ったレプリケーションは、MySQLで広く利用されているレプリケーション手法です。こちらの機能もAmazon Aurora Serverless (v1) で利用できません。また仮に利用できたとしても、チームメンバーもバイナリログレプリケーションの経験や知識があまりないので、自信をもってうまく移行できるとはえいないでしょう。

チームが選択した方法

インプレースアップグレードやバイナリログレプリケーションはAurora Serverless 5.6では利用できず、バックアップ・リストアでは大きなダウンタイムが発生します。我々のチームは別の手段として、移行処理をサービスの一機能として実装することにしました。この方法によるメリットはいくつかあります。

1つめのメリットは、データベースへのアクセスを制御しやすい点です。移行作業をサービスに実装することで、移行中のデータアクセスをブロックしたり、移行完了後に接続先クラスタを即座に切り替えられます。データによっては、事前にコピーできるものや移行不要なテーブル(例えばメタデータ用テーブルなど)もあり、移行対象を限定することで移行時間も最小にできます。

2つめのメリットは、移行処理をすべてコードで表現できることです。データベースにアクセスする処理はすでに既存の実装があります。今あるロジックを活用することで、論理的なデータ不整合を防ぐことができ、安全にデータを移行できます。それらのコードには単体テストがあり、移行処理も自動テストを記述できます。リハーサルや事前作業とは別の方法で、移行時の安全・安心や品質を担保できます。

もちろん他の移行作業より開発コストが発生します。しかしサービスの停止や複雑なオペレーションをするリスクよりも、それを上回るメリットがチームにありました。各サービスも我々が実装しており、移行に必要なサービスの知識も自分たちが持っています。チームメンバーのほとんどがプロダクト開発出身というのも理由の1つで、複雑なオペレーションではなくコードで問題解決するというカルチャーが、この方法を後押ししました。またこのAurora Serverlessのインプレースアップグレードでは実現できないMySQL 8.0への移行も可能になりました。

移行処理の設計と実装

移行処理の状態保存

まずデータベースの移行が完了しているか否かの状態を記録するために、移行フラグを保存するテーブルを作成しておきます。通常リクエストがデータベースにアクセスするときは、同時に移行が完了しているかどうかをチェックします。移行がまだであれば旧データベースに読み書きし、移行が完了していれば新データベースに読み書きします。

移行フラグは旧データベースのテーブルに記録し、旧データベースのデータの読み書きと同一トランザクションで状態を確認します。状態の確認は通常リクエストでは共有ロック(SELECT … LOCK IN SHARE MODE)で取得し、データベース移行時は占有ロック(SELECT … FOR UPDATE)で状態の確認をします。占有ロックによるSELECTは共有ロックのSELECTをブロックできます。データベースの移行中は、通常リクエストのデータアクセスをブロックでき、移行中のデータを見ることはありません。移行フラグのテーブルと新しいデータベースはトランザクションが分離しますが、すでに移行が完了していれば新しいデータベースへの読み書きは問題ありません。

データベース移行の基本戦略
データベース移行の基本戦略

移行処理の実装

移行処理は以下のような実装になります。以下の処理を1つのトランザクション内で実行します。

  1. 移行フラグを占有ロック(SELECT … FOR UPDATE)で取得する。すでに移行済みなら何もしない。
  2. 旧データベースからデータをSELECTして、新しいデータベースにデータをINSERTする
  3. 移行フラグを完了状態にする

移行中に他のデータベースアクセスをブロックすることでデータ不整合を防げますが、データ量が多く移行に時間がかかれば、サービスのダウンタイムも長くなります。そこでデータベースのテーブル設計に着目し、ロック期間を短くすることでダウンタイムを小さくできます。実際に移行を進める中で実施した、ロック期間を短くする方法をいくつか紹介します。

移行の分割

データベースの設計によっては、すべてのテーブルが同一トランザクションで処理されるとは限りません。中にはテーブル間のトランザクションを分離しても、データの不整合が発生しない設計になっているものもあります。またデータベース内には通常稼働時にデータの更新がないテーブルもあります。そういった性質のデータは、一度の移行作業で全てのテーブルを移行する必要も無いはずです。

こういったデータベース設計では、移行作業をいくつかの単位に分割できます。移行する単位で移行フラグを管理し、移行処理をそれぞれ実行します。1つの移行処理の時間を抑えることができ、結果として通常リクエストをブロックする時間を最小に抑えられます。

データベースの分割移行
データベースの分割移行

差分更新

移行のロック期間はデータ量に依存します。事前にデータをコピーできれば、ロックの期間を短くできます。ただしロックをせずデータをコピーすると、コピー中に更新されたデータで不整合が生じます。そこでロック期間外で事前にデータをコピーしておき、更新があった差分のみロックして更新のみすることで、ロックの期間を最小に抑えることができます。

一般的なMySQLやAuroraクラスタは、データの読み込みよりも書き込みの方がレイテンシが大きくなる傾向があります。そのため全データをコピーするより、差分を検出して書き込み回数を減らした方が移行期間を短くできます。この戦略は特に、更新頻度が低いテーブルや、データが不変なテーブルで有効です。

具体的な移行作業は以下の通りです。ロックの戦略は通常移行時と変わらないので、移行中に不整合なデータが参照されることはありません。

  1. 対象のテーブルを事前にコピーする
  2. 移行状態を占有ロック(SELECT … FOR UPDATE)で取得して移行前なら移行を実行する。新・旧両方のデータを取得して、差分があるデータのみ更新する。
  3. 移行フラグを完了状態にする

データベースの差分更新
データベースの差分更新

既存のロック機構の利用

移行作業に関係なく、サービスの中でロックや排他処理をしている機能がいくつかあります。既存のロック機能を移行時のロックに利用することができます。例えばテナント管理サービスにはテナントごとの処理をシリアライズする仕組みが備わっていたり、定期処理サービスは各テナントごとに独立して処理を実行できるようになっています。サービスのより細かい機能に関係ことなので詳しい説明は割愛しますが、すでにあるロック機構を利用することで、適切なタイミングで移行を実行したり、移行処理を分割できました。

まとめ

チームではこの数カ月間、Aurora Serverless 5.6の移行作業に取り組んできました。ゼロダウンタイムというチームとしても大きなチャレンジでしたが、移行は無事終了しました。移行後はMySQL 8.0の実行計画の変更によるパフォーマンス劣化や、Aurora Serverless v2からProvisionedインスタンスに切り替えるなど、多少のトラブルは発生しましたが、移行自体は無事終了し、現在も安定して稼働しています。

今回サービスコードとして移行処理を実装したことでダウンタイムを無くすことができただけでなく、コードレビューや単体テストなどチームが得意なことで安心が担保できたのが大きかったです。また本文では触れませんでしたが、ログやメトリクスによって移行の進み具合も可視化できたり、データベーススキーマのリファクタができたのはよかったです。

僕たちのチームは引き続き、グローバル向けAWS版kintoneの安定運用を目指して、サービスの開発・運用や技術的なチャレンジに取り組んでいく予定です。チームではバックエンドエンジニアを募集しています!この記事を読んで少しでも興味が湧いた方はぜひ@ueokandeまでご連絡ください。 We are hiring!

cybozu.co.jp