Feedforce Developer Blog

フィードフォース開発者ブログ

RDS for MySQL5.7 から Aurora へ移行しました

ソーシャルPLUS 開発チーム id:mizukmb です。ISUCON7 予選は惨敗でした。来年また頑張ります。

先日、ソーシャルPLUS の LINE メッセージ配信を提供するバックエンドの DB を RDS for MySQL5.7 から Amazon Aurora に移行しました。

今回は、RDS for MySQL 5.7 から MySQL 5.6 の Fork である Amazon Aurora への移行を行う際の注意点や実際の移行手順について説明します。

なぜ Aurora に移行したのか

Aurora 自体は様々な機能を持っていますが、今回は ストレージの自動スケーリング耐障害性 という主に運用目線で見たときの強みを評価し、採用しました。

aws.amazon.com

ソーシャルPLUSチームでは現在ソーシャルログイン機能と、 LINE メッセージ配信機能を提供する2つのサービスを開発、保守運用しています。 LINE メッセージ配信機能は新しめのサービスで比較的軌道修正がしやすい段階だったので、運用負荷を減らすことを目的として、思い切って Aurora に移行しました。

移行手順

前日までにできることと、当日やることの2つに作業を分けて行いました。

前提

RDS for MySQL5.7 から Aurora のリードレプリカを作成することはできません。従って、今回のようなケースでは 新規に Aurora を作成 し、手動によるデータ移行が必要になります。

サービスの要とも言える DB 内データを、手動で取り出してインポート… は失敗するのが怖いので、できるだけ避けたいところです。そこで今回は、 既存 DB を RDS へ最小限のダウンタイムで移行することができる AWS Database Migration Service (以下 DMS) を利用して、データ移行を行いました。

aws.amazon.com

また、今回はメンテナンス時間を設けての作業でしたので、作業当日はサービスの全機能が停止した状態が発生していました。ダウンタイム無しの移行も可能だと思いますが、タイミングがシビアすぎたので今回は安全に作業できることを優先させました。このあたりは後述します。

前日までにできること

できるだけのことをやって、当日の手順は少なくします。

1. MySQL5.7 特有の機能を把握する

今回の移行は、平たく言えば MySQL5.7 -> MySQL5.6 へのダウングレードなので、 MySQL5.7 にて追加された機能や変更されたデフォルトの設定などから影響する箇所を事前に把握する必要があります。

今回は以下の点について変更する必要がありました

  • JSON 型カラムの廃止
  • utf8mb4 使用時の最大カラムサイズ制限

JSON 型カラムについては、アプリの改修規模が大規模でなかった && サービスに大きな影響を与えるような部分ではなかったという理由から、該当箇所を TEXT 型に変更し、移行元の DB にマイグレーションしました。

最大カラムサイズ制限というのは、いわゆる max key length is 767 bytes: のようなエラーのことです。 こちらの Qiita 記事を参考にして、Aurora 側で設定する必要のあるパラメータグループや ENGINE=InnoDB ROW_FORMAT=DYNAMIC をデフォルトで指定するための initializer を用意したりしました。

# 変更する RDS パラメータグループ
# 便宜上 my.cnf 形式で書いてます
[mysqld]
innodb_file_format = Barracuda
innodb_file_per_table = 1
innodb_large_prefix
# config/initializers/ar_innodb_row_format.rb
# create_table メソッド実行時に、デフォルトで `ROW_FORMAT=DYNAMIC` を指定する
# ref: https://qiita.com/kamipo/items/101aaf8159cf1470d823
ActiveSupport.on_load :active_record do
  module ActiveRecord::ConnectionAdapters
    module RowFormat
      def create_table(table_name, options={}, &block)
        table_options = options.merge(options: 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC')
        super(table_name, table_options, &block)
      end
    end
    AbstractMysqlAdapter.prepend(RowFormat)
  end
end

qiita.com

さらに、MySQL5.7 では ROW_FORMAT のデフォルト値は DYNAMIC ではありますが、 MySQL5.6 つまり Aurora では違いますので、既存テーブルに明示的に反映させるために migration ファイルも用意し、 db:migrate しておきます。

# db/migrate/2017xxxxxxxx_add_row_format_option.rb 
# frozen_string_literal: true

class AddRowFormatOption < ActiveRecord::Migration[5.1]
  def up
    %w[
    # ここに全テーブル名を記述する
    foo
    bar
    # (snip)
    ].each do |table|
      execute "ALTER TABLE #{table} ENGINE=InnoDB ROW_FORMAT=DYNAMIC"
    end
  end
end

2. Aurora を作成する

予め移行先の Aurora は作成しておきます。安全に作業したかったので、 Terraform でコード化して準備しておきました。事前にレビューもしてもらえるので便利です。

以下のようなリソースを追加しました。

  • aws_security_group
  • aws_db_parameter_group
  • aws_rds_cluster_parameter_group
  • aws_rds_cluster
  • aws_rds_cluster_instance

作成した Aurora の確認方法は以下の通り。確認できたら監視に追加するのを忘れずに。

mysql> select AURORA_VERSION();
+------------------+
| AURORA_VERSION() |
+------------------+
| 1.14.1           |
+------------------+
1 row in set (0.00 sec)

3. DMS タスクの作成

DMS タスクも Terraform にてコード化することができます。が、今回限りの使用なので AWS のコンソール画面から作成しました。より安全に行うのであれば、コード化した方が良いと思います。ここは反省すべき点です。

以下のリソースを作成しました。

  • タスク
  • レプリケーションインスタンス
  • エンドポイント

今回は同 VPC 内によるデータ移行なので、エンドポイントの SSL モードを none にしていました。オンプレ -> AWS のようなインターネットを介する場合は設定しましょう。

また、外部キーが設定されたテーブルのデータ移行が失敗することがあります。エンドポイントのターゲットの 追加の接続属性initstmt=SET FOREIGN_KEY_CHECKS=0 を追加してください。

ロード中は、外部キーを無効にすることを検討してください。ロード中に MySQL 互換データベースで外部キーチェックを無効にするには、ターゲット MySQL、Aurora、MariaDB エンドポイント接続情報の [Extra Connection Attributes] セクションで [Advanced] に次のコマンドを追加します。

initstmt=SET FOREIGN_KEY_CHECKS=0

docs.aws.amazon.com

作業時点(2017年10月中旬頃)では、 DMS のコンソール画面の挙動が不安定でフォームに入力した文字が消えてしまったりして焦りました。注意してください。

4. 移行元 DB からテーブル定義をダンプし、 Aurora へインポートする

DMS では、データや主キーは移行されますが、 null 制約 や AUTO_INCREMENT 属性といった情報は移行されません。

この部分は mysqldump コマンドを使ってテーブル定義のみをダンプして、 Aurora にインポートしました。 mysqldump コマンドのオプションで --no-data を渡すと定義情報だけダンプできます。

# 移行元 DB からテーブル定義のみをダンプする
$ mysqldump -h <移行元 DB のエンドポイント> -u <ユーザ名> -p --no-data <テーブル名> > /tmp/rds-backup-no-data-`date +%Y%m%d%H%M%S`.sql

# 先ほどダンプしたテーブル定義情報を Aurora にインポートする
$ mysql -h <Aurora のエンドポイント> -u <ユーザ名> -p <テーブル名> < /tmp/rds-backup-no-data-2017xxxxxxxx.sql

こちらはクラスメソッドさんのブログを参考にしました。

dev.classmethod.jp

5. mysql-devel 5.6 をインストールしたアプリケーションサーバを用意する

意外な落とし穴(少なくとも私は気づくのが遅れた)として、 アプリケーションサーバの mysql-devel のバージョンを 5.7 から 5.6 にダウンさせる必要があります。また、 mysql2 Gem をインストールし直す必要があるため、すでに本番稼働しているサーバとは別に新規でサーバを構築しました。

アプリケーションサーバの構築は Blue-Green Deployment を採用していたので、ここも予め用意だけしておいて、当日の作業では LB の配下に加えるだけという状態にしておきました(一時的に Blue と Green が同時に存在する状態となってしまいますので、開発メンバーに周知しておきます)。

この時点でのインフラの状態です。

f:id:mizukmb:20171024114325p:plain

当日やること

ここからメンテナンス時間に突入します。時間が決められているので焦らず手順に沿って作業を進めます。

1. 現環境の各種監視アラートをストップする

アプリケーションサーバや DB の監視アラートをストップします。ソーシャルPLUS では監視ツールに Mackerel を採用していますので、各ホストのステータスを maintenance に変更しました。

2. 移行元 DB の RDS パラメータグループ read_only1 にして、読み取り専用にする

データ移行の直前で、 移行元 DB を読み取り専用にしておきます。書き込み系の一部機能は使えなくなります。

3. 移行元 DB のスナップショットを手動で作成する

何かあった時の最後の手札です。

このスナップショットは DB がクラッシュした時の命綱です。自動バックアップで作られるスナップショットは RDS インスタンスを Delete すると削除されるので、要注意です。

tech.feedforce.jp

4. DMS タスク実行(データ移行)

AWS コンソール画面から DMS タスクを実行して、データを移行します。ちなみに、継続的なレプリケーションも可能ですので、移行するデータが大量だったりする場合はこちらも検討してみてください。今回は一度の DMS タスク実行でデータ移行を行いました。

この時点で移行先 DB である Aurora へのデータ移行が完了し、残すは LB の向き先を変更するのみとなりました

5. LB の向き先を新環境のアプリケーションサーバに変更する

ここで瞬間的にサービスが完全停止する可能性があります。この作業が完了すると、移行先 DB である Aurora へのアクセスが走り、サービスが再開されます。

最終的にはこうなります

f:id:mizukmb:20171024115417p:plain

あとは動作確認して完了です。

所感

初の DB 移行作業だったので、様々な不安を抱えての実施でした。およそ一ヶ月間、手順書や各種対応 PR など事前にできることを徹底的にやってしまい、本番に望みました。結果として作業中に大きなトラブルは起きず、計画通りに作業が完了し安心しました。

しかし、同時に反省すべき点や課題が見えてきました。

今回の作業は、調査〜実施までほとんど一人でやってしまいました。もちろん、手順書を書き上げたタイミングなど適宜チームメンバーからレビューやアドバイスを頂いてたのですが、私の経験不足もあり、作業日時を延期するなど計画がずれ込むことがありました。ここはもっと周りを積極的に巻き込むべきだったと反省しています。

また、移行の計画を建てた段階で、 『本番のデータ量で RDS スナップショットを取得するための時間はどのくらいかかるか?』、『DMS タスク実行によるデータ移行はどのくらいの時間を要するか』など所要時間の把握が曖昧になっていました。今回は大きな問題にはなりませんでしたが、本番データをダンプしてステージ環境で予行演習を行うなどの対策はできるので、次回は忘れずに行おうと思います。

とにかく初めてのことだったので、安全・慎重に作業を進めることを意識していました。今後はダウンタイム無しで移行できることを考え、ユーザが安心して利用できるようなサービスの基盤を構築したいです。