この記事は クラウドワークス Advent Calendar 2024 シリーズ3 11日目の記事です。
こんにちは。クラウドワークス SRE チームの田中(@kangaechu)です。 クラウドワークス では2024年12月に クラウドワークスのマスタデータベースをMySQL から Aurora に移行しました。今回はその移行について書きます。
クラウドワークスは、2012年にリリースされた日本最大級のクラウドソーシングサービスです。 クラウドソーシングを通じて、企業とフリーランスのマッチングを行い、業務の効率化を支援するため、仕事の公開、応募、受注、進捗管理、報酬の支払いなど、さまざまな機能を提供しています。 クライアント数は100万社、ワーカー数は670万人と、多くの方に利用していただいています。
そんなクラウドワークス のデータベースはAWSのRDS MySQLで運用されていました。 これをAuroraに移行するのですが、もし移行に失敗した場合、多くのユーザに影響を与えてしまいます。 移行に失敗したらどうしようという不安を抱えていました。
そこで、クラウドワークスで使用したビビリのためのAurora移行の方法を共有します。 これを行うことにより、Aurora化が失敗した場合でもRDSに戻すことができます。
移行概要
- ① RDS for MySQL(Aurora移行前のマスタデータベース)
- ② Aurora(Aurora移行後のマスタデータベース)
- ③ Aurora Replica(DMSのソースDBとして使用)
- ④ RDS for MySQL(失敗時のリカバリ用)
AWSのドキュメントには移行の方式が複数記載されています。 今回はAurora リードレプリカを使用し、Auroraを昇格することで移行を行う方式を選択しました。
通常は①のRDSに対し、②のAuroraのリードレプリカを作成することで移行を行います。 そのため、①と②のデータベースのみ作成すれば最低限の移行環境は整います。
クラウドワークスでは、DMS (Database Migration Service)を使用したRedshiftへのニアリアルタイムレプリケーションを行なっています。 ③については、DMSのレプリケーションのソースデータベースとして使用するために作成します。
上記の記事内に詳細がありますが、DMSタスクの停止時にDMSがトランザクションを読み飛ばすバグがあるため、③のAuroraデータベースを挟む構成としています。 RDSを使用することもできるのですが、「AuroraのスナップショットからRDSを作成することができない」という制約により、今後データベースを再作成しようとしたときに困る可能性があるため、Auroraを使用しています。 ②のAurora→③のAuroraは拡張binlogを使用するレプリケーションができるため、②のAuroraデータベースの設定で拡張binlogを有効化しています。
(③→DMSへのレプリケーションは拡張binlogをサポートしていないため、コミュニティ MySQL binlogを使用します。そのため拡張binlogの設定はデフォルトのままにしています。)
AWS DMS doesn't support Aurora MySQL enhanced binary log. https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.MySQL.html
今回重要となるデータベースがこの④になります。 ④はAurora化失敗時のリカバリ用として、RDSを追加しています。 こちらはAurora化失敗時に①の代わりに使用するために作成しています。 AuroraからRDSのレプリケーションができるのかが気になる方もいらっしゃるかと思いますが、AWSサポートにレプリケーションができることは確認済みです。
移行手順の詳細
移行自体はシステムメンテナンスを行い、サービス停止後に②のAuroraデータベースを昇格するだけです。とても簡単ですね。 昇格後はWebサービスの設定を変更し、Auroraデータベースを参照するようにすれば移行完了です。 これにより、①のRDSデータベースはレプリケーションから切り離されます。
正常に動くことを確認したら、④のRDSデータベースを削除します。
移行失敗時のリカバリ
移行に失敗した場合は④のRDSデータベースをレプリケーションから切り離し、Webサービスの設定を変更して④のデータベースを参照するようにします。 また、DMSへのレプリケーションを継続するために④→②のレプリケーション設定を行います。
移行に向けた作業
0. GTIDの有効化
移行時にbinlogのポジションを指定してリカバリする可能性があるため、GTIDを有効化します。
1. ①にbinlogレプリケーション用のユーザを作成
①のデータベースにレプリケーション用のユーザを作成します。 ①自体はこのユーザを使用しませんが、後々必要になります。
CREATE USER 'replication'@'(CIDR)' IDENTIFIED BY '***'; GRANT REPLICATION CLIENT, REPLICATION SLAVE ON *.* TO 'replication'@'(CIDR)';
2. binlogの保持期限を変更
RDS for MySQLやAuroraではbinlogの保持期限を指定するためのプロシージャが用意されています。
binlogの保持期間のデフォルトはNULL(バイナリログを保持しない)です。 最大値はRDSで168(7日)、Auroraで2160(90日)です。
移行中は④のbinlogを保持する必要があるため、④の保持期間を移行失敗までの期間まで保持されるよう、長めに設定します。
-- 現在の状態を確認 CALL mysql.rds_show_configuration; -- binlogの保持期限を設定 CALL mysql.rds_set_configuration('binlog retention hours', 168);
binlogの保持期限を伸ばすと、RDS/Auroraのストレージ容量が増えるため、必要であれば事前に拡張しておきます。
3. ②のAuroraデータベースを作成
Auroraデータベースを作成します。 マネジメントコンソールからであれば、「アクション→リードレプリカの作成」を選択します。 Terraformであれば、aws_rds_clusterリソースに replication_source_identifierを指定して作成します。
resource "aws_rds_cluster" "no_2" { replication_source_identifier = "no-1" engine = "aurora-mysql" engine_version = "8.0.mysql_aurora.3.07.1" ...
4. ③のAuroraデータベースを作成
②のスナップショットを作成し、③のAuroraデータベースを作成します。 Terraformであれば、aws_rds_clusterリソースに snapshot_identifierを指定して作成します。
③のAuroraデータベースのパラメータグループにはbinlogを有効化する設定を追加します。
5. ②→③のレプリケーション設定
Aurora→Auroraのレプリケーション設定を行います。 マネジメントコンソールからは実行できないため、③のデータベースにログインし、MySQLのコマンドを使用します。
-- ③から②に対するレプリケーションを設定 mysql> CALL mysql.rds_set_external_source_with_auto_position('(②のホスト名)' , 3306, 'replication', '********', 0); Query OK, 0 rows affected, 6 warnings (0.17 sec) -- レプリケーションを開始 mysql> CALL mysql.rds_start_replication; +---------------------------+ | Message | +---------------------------+ | Replica running normally. | +---------------------------+ 1 row in set (2.26 sec) Query OK, 0 rows affected (2.26 sec) -- レプリケーションの状態を確認 mysql> SHOW REPLICA STATUS\G *************************** 1. row *************************** Replica_IO_State: Waiting for source to send event (省略) 1 row in set (0.01 sec)
設定後、①のデータベースで何らかの変更を発生させ。それが③に反映されることを確認します。
6. ①のスナップショットから④のRDSデータベースを作成
①のスナップショットから④のRDSデータベースを作成します。 Terraformであれば、aws_db_instanceリソースに snapshot_identifierを指定して作成します。
7. ③→④のレプリケーション設定
移行失敗時のリカバリ用に、③→④のレプリケーション設定を行います。 こちらもマネジメントコンソールからは実行できないため、④のデータベースにログインし、MySQLのコマンドを使用します。
-- ③から④に対するレプリケーションを設定 mysql> CALL mysql.rds_set_external_master_with_auto_position('(③のホスト名)' , 3306, 'replication', '********', 0); +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Message | +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | rds_set_external_master_with_auto_position is deprecated and will be removed in a future release. Use rds_set_external_source_with_auto_position instead. Refer to the documentation for more information on deprecated statements | +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.17 sec) Query OK, 0 rows affected, 6 warnings (0.17 sec) -- レプリケーションを開始 mysql> CALL mysql.rds_start_replication; +---------------------------+ | Message | +---------------------------+ | Replica running normally. | +---------------------------+ 1 row in set (2.26 sec) Query OK, 0 rows affected (2.26 sec) -- レプリケーションの状態を確認 mysql> SHOW REPLICA STATUS\G *************************** 1. row *************************** Replica_IO_State: Waiting for source to send event (省略) 1 row in set (0.01 sec)
移行失敗時のリカバリ
システムメンテナンスでサービスを停止し、DBへのリクエストがない状態で開始します。
1. ④のRDSデータベースをレプリケーションから切り離す
④のRDSデータベースを①相当として使用するため、レプリケーションから切り離します。 ④のデータベースにログインし、AWSが準備した mysql.rds_reset_external_master プロシージャを使用します。
mysql> CALL mysql.rds_reset_external_master; +----------------------+ | message | +----------------------+ | Slave has been reset | +----------------------+ 1 row in set (2.04 sec) Query OK, 0 rows affected, 1 warning (2.04 sec) mysql> SHOW REPLICA STATUS\G Empty set (0.01 sec)
2. Webサーバの接続先を④に変更
Webサーバの接続先を④に変更します。
3. ④→②のレプリケーション設定
このままではDMSのレプリケーションが途切れてしまうため、④→②のレプリケーション設定を行います。 ②のデータベースにログインし、AWSが準備した mysql.rds_set_external_source_with_auto_position プロシージャを使用します。
-- 現在の状態を確認 mysql> show replica status\G Empty set (0.00 sec) -- ④から②に対するレプリケーションを設定 mysql> CALL mysql.rds_set_external_source_with_auto_position('④のホスト名' , 3306, 'replication', '********', 0); Query OK, 0 rows affected (0.19 sec) -- レプリケーションを開始 mysql> CALL mysql.rds_start_replication; +------------------------------------------------------------------------------+ | Message | +------------------------------------------------------------------------------+ | Replica has encountered an error. Run SHOW REPLICA STATUS; to see the error. | +------------------------------------------------------------------------------+ 1 row in set (2.13 sec) Query OK, 0 rows affected (2.13 sec) -- レプリケーションの状態を確認 mysql> SHOW REPLICA STATUS\G *************************** 1. row *************************** Replica_IO_State: Connecting to source (省略)
移行時に注意すべきポイント
移行時にはまったポイントをいくつか紹介します。
レプリケーション開始時にERROR 1267 (HY000): Illegal mix of collations
が発生する
レプリケーション開始時にERROR 1267 (HY000): Illegal mix of collations (utf8mb3_general_ci,IMPLICIT) and (utf8mb3_unicode_ci,IMPLICIT) for operation '='
が発生する場合があります。
これは、MySQL8.0から文字コードのデフォルトがutf8mb4になりましたが、MySQLの古いバージョンからアップデートしたインスタンスを使い続けていた場合に発生する可能性があります。
AWSサポートに依頼をするとプロシージャを修正してくれます。
マルチAZ時のレプリケーションが遅延する
復旧用のデータベースとして④のRDSデータベースを作成しますが、復旧時の切り替えを早くするため先にマルチAZを有効化した状態で①→②→③→④のレプリケーションを行うと、レプリケーション遅延が発生しました。
シングル構成で作成し、復旧時にマルチAZに変更することでレプリケーション遅延を回避できました。 ただ、マルチAZに変更する際には20分程度かかるため、復旧に時間がかかる可能性があります。
mysql.rds_set_external_source_with_auto_positionに失敗する
移行時の構成ではGTIDを有効化しています。
その環境で④→②のレプリケーション時に mysql.rds_set_external_source_with_auto_position
を実行するとレプリケーションの開始に失敗するエラーが発生しました。
原因はbinlogの保持期間が短すぎるため、GTIDに対応するbinlogが削除されていたことでした。
また、更新していないRDSのGTIDが進んでいた理由はRDSのハートビートによるものでした。 検証した環境ではRDSのみ5分に1回ハートビートとして以下のようなクエリを定期的に発行されていました。 これにより、GTIDの位置がずれてしまい、レプリケーションが開始できない状態になりました。 参考までにハートビートはこのようなクエリでした。
INSERT INTO mysql.rds_heartbeat2(id, value) values (1,1704034800000) ON DUPLICATE KEY UPDATE value = 1704034800000;
これらのハートビートのレコードはなくても良いため、レプリケーション先のデータベースでそのGTIDをスキップすることで解決しました。
-- 現在のGTIDを確認 mysql> SELECT * FROM mysql.gtid_executed; +--------------------------------------+----------------+--------------+ | source_uuid | interval_start | interval_end | +--------------------------------------+----------------+--------------+ | 11111111-1111-1111-1111-111111111111 | 1 | 100 | +--------------------------------------+----------------+--------------+ 1 rows in set (0.00 sec) -- ④→②のレプリケーション設定 mysql> CALL mysql.rds_set_external_source_with_auto_position('④のホスト名' , 3306, 'replication', '********', 0, 0); Query OK, 0 rows affected, 7 warnings (0.05 sec) -- レプリケーションを開始(エラー) mysql> CALL mysql.rds_start_replication; ERROR 1644 (45000): Slave has encountered an error. Run SHOW SLAVE STATUSG; to see the error. mysql> SHOW REPLICA STATUS\G (省略) Last_IO_Error: Got fatal error 1236 from source when reading data from binary log: 'Cannot replicate because the source purged required binary logs. Replicate the missing transactions from elsewhere, or provision a new replica from backup. Consider increasing the source's binary log expiration period. The GTID set sent by the replica is '11111111-1111-1111-1111-111111111111:1-100, and the missing transactions are '44444444-4444-4444-4444-444444444444:1' (省略) 1 row in set (0.00 sec) -- ソース側のデータベースに44444444-4444-4444-4444-444444444444:1のGTIDがあるが、binlogが削除されているためレプリケーションが開始できない状態 -- ④のデータベースに44444444-4444-4444-4444-444444444444:1のGTIDを追加 mysql> SET GTID_NEXT='44444444-4444-4444-4444-444444444444:1'; Query OK, 0 rows affected (0.00 sec) mysql> BEGIN;COMMIT; Query OK, 0 rows affected (0.00 sec) Query OK, 0 rows affected (0.01 sec) -- GTIDが追加されたことを確認 mysql> SELECT * FROM mysql.gtid_executed; +--------------------------------------+----------------+--------------+ | source_uuid | interval_start | interval_end | +--------------------------------------+----------------+--------------+ | 11111111-1111-1111-1111-111111111111 | 1 | 100 | | 44444444-4444-4444-4444-444444444444 | 1 | 1 | +--------------------------------------+----------------+--------------+ 2 rows in set (0.00 sec) -- GTIDが追加されたことを確認(show master statusの方が反映が早い) mysql> show master status; +-----------------------------+----------+--------------+------------------+------------------------------------------------------------------------------------------------------------------------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +-----------------------------+----------+--------------+------------------+------------------------------------------------------------------------------------------------------------------------------------+ | mysql-bin-changelog.1481979 | 111 | | | 11111111-1111-1111-1111-111111111111:1-100, 44444444-4444-4444-4444-444444444444:1| +-----------------------------+----------+--------------+------------------+------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) -- GTIDの設定を戻す mysql> SET GTID_NEXT='AUTOMATIC'; Query OK, 0 rows affected (0.00 sec) -- レプリケーションの状況を確認 -- GTIDが追加されていれば自動でレプリケーションが開始される mysql> show replica status\G *************************** 1. row *************************** Replica_IO_State: Waiting for master to send event
まとめ
クラウドワークスでは、RDS for MySQLからAuroraへの移行を行いました。 移行自体はAuroraデータベースを昇格するだけで簡単ですが、移行失敗時のリカバリを考慮すると複雑になります。 ビビりながらも、上記の手順でリカバリの手順を使うことなく移行を終えることができました。
クラウドワークスではSREを含むエンジニアを募集しています。興味のある方は以下のリンクからご応募ください。 https://crowdworks.co.jp/careers/mid_career/