先日、RDS の SQL Server で Multi-AZ の機能がサポートされました。
【AWS発表】Amazon RDS for SQL ServerでもMulti-AZ機能を利用可能に
Amazon RDS for Microsoft SQL Server
Multi-AZ 配置
構築方法などは、【速報】Amazon RDS for SQL ServerがMulti-AZをサポートしたのでやってみた が参考になります。
どのような仕組みで実装されているのかが気になったので試してみました。
Contents
■Mult-AZ の設定
Multi-AZ を使用した SQL Server の作成は簡単で、[Multi-AZ Deployment] を [Yes (Mirroring)] にして、RDS のインスタンスを作成するだけです。
この投稿を書いている時点では、[US East (N. Virginia)] [US West (Oregon)] [EU (Ireland)] でのみ使用することができます。
インスタンスの作成まで 1 時間程度かかりますので気長に待ちます。
作成が完了すると以下のような Multi-AZ が設定された SQL Server のインスタンスが作成されます。
■構成を確認してみる
それでは構成を確認してみたいと思います。
Multi-AZ を設定した SQL Server では接続のポイントは単一となるようです。
ポータルで確認をしたエンドポイントの情報をしてインスタンスに接続をすると、プリンシパルのサーバーに接続がされます。
RDS の Multi-AZ はデータベースミラーリングを使用して構築されています。
データベースミラーリングですが、Standard / Enterprise Edition で使用できる機能となります。
# BI Edition でも使用できますが今回は省略で。
ミラーリングには、
- 同期モード
- 非同期モード
があり、Multi-AZ では同期モードが使用されているようです。
# Standard では 同期モードしか使用できない というのもありますが。
また、ミラーリング用の監視サーバー (Witness) も使用されているようで、自動フェールオーバー可能な同期モードで構築が行われているようです。
それでは構成を sys.database_mirroring から確認してみたいと思います。
select @@servername AS server_name , DB_NAME(database_id) AS db_name , mirroring_state_desc , mirroring_role_desc , mirroring_safety_level_desc , mirroring_partner_name , mirroring_partner_instance , mirroring_witness_name from sys.database_mirroring
partner と、witness が設定されており、ミラーリングの保護レベルとしては FULL (同期モード) が使用されていることが確認できます。
ミラーリングポートとしては 1120 が使用されているようですね。
つぎに、ミラーリングで使用しているエンドポイントの設定を sys.database_mirroring_endpoints から確認してみます。
select name , connection_auth_desc , encryption_algorithm_desc from sys.database_mirroring_endpoints
ミラーリングのエンドポイントでは証明書が使用されているのが確認できます。
RDS for SQL Server では SQL Server のサービスはドメインアカウントではなく、ローカルアカウントを使用して起動しているようです。
そのため、エンドポイント間の接続にはユーザーアカウントではなく証明書を使用した接続を利用しているようです。
インスタンスのフェールオーバーですが [Instance Action] の [Reboot] から実行することができます。
Multi-AZ を設定しているインスタンスでは、[Reboot With Failover] を設定することができ、これによりプリンシパルとミラーを手動で切り替えることができます。
データベースミラーリングを設定しているサーバーに接続をしている場合、接続文字列にフェールオーバーパートナーを設定し、障害が発生した場合に接続先を自動的に切り替えるのが一般的かと思います。
SQL Server のデータベース ミラーリング
RDS for SQL Server の接続には、エンドポイントの情報のみが公開されており、プリンシパルとミラーを意識せずに接続をすることになり、フェールオーバーをすると自動的に接続先が切り替わります。
この仕組みですが、DNS を用いて実施しているようです。
現状の RDS のエンドポイントを NSLOOKUP した結果が、以下になります。
EC2 のインスタンスとして、[ec2-54-213-108-114.us-west-2.compute.amazonaws.com] が使用されていることが確認できます。
それでは、フェールオーバーをしてみたいと思います。
フェールオーバーをすることで、DNS の CNAME が [ec2-54-201-30-115.us-west-2.compute.amazonaws.com] に切り替わったことが確認できます。
フェールオーバーをすることで、今までミラーだったサーバーがプリンシパルに昇格し、そのタイミングで DNS の CNAME を切り替えて接続先を DNS ベースで透過的に切り替えているようです。
DNS のキャッシュや、既に接続しているコネクションが正常に切り替わっているかの検証はきちんと実施したほうが良さそうですね。
ec2 ~ の DNS 名がわかっている場合には、直接接続することもできてしまうようです。
# 管理を考慮すると、EC2 インスタンス名も把握しておいたほうが良さそうな気がするのですがどうなのでしょう。
以下は、直接接続をした場合の SSMS の表示内容ですが、各インスタンスがプリンシパルとミラーの関係になっていることが確認できます。
データベースミラーリングでは、master/model/msdb/tempdb といったシステムデータベースはミラーされません。
そこで、通常運用で使用する以下のオブジェクトがミラーに連携されるかを確認してみました。
ログイン | ミラーに連携される |
SQL Server Agent ジョブ | 連携されない |
RDS for SQL Server には、いくつかのトリガーが設定されています。
データベースとログインの作成に関してはトリガーで制御 (おそらく、ミラーへの情報同期の機構) されているようですが、SQL Server Agent ジョブについては同期が行われないようですので、定期的なジョブ実行をしたい場合には注意したほうが良さそうですね。
データベースについては作成するとミラーリング対象のデータベースとして設定が行われます。
ただし、設定がされるまではタイムラグがあるようです。
以下は作成直後の状態ですが、この状態ではミラーリングは行われていません。
RDS では構成の維持管理のために、 [RdsAdminService] というサービスがバックグラウンドで動作しているようなのですが、ミラーリングの設定はこのサービスが実行しているようです。
データベースを作成すると以下のようなクエリがこのサービスから実行されています。
BACKUP DATABASE [RDSTEST2] TO DISK = 'D:RDSDBDATABACKUPb5d954b4-aaa9-4537-996b-0b5f82635d4d.1402357569.database_backup' WITH FORMAT DECLARE @after_backup_epoch BIGINT SET @after_backup_epoch = CAST('1402357570' AS BIGINT) BEGIN TRANSACTION INSERT INTO [rdsadmin].[dbo].[createdb_activity]([family_guid], [database_name], [properly_created_time]) SELECT 'b5d954b4-aaa9-4537-996b-0b5f82635d4d', N'RDSTEST2', DATEADD(second, @after_backup_epoch, '1970-01-01 00:00:00') AS properly_created INSERT INTO [rdsadmin].[dbo].[done_initial_db_backups]([family_guid]) VALUES ('b5d954b4-aaa9-4537-996b-0b5f82635d4d') INSERT INTO rdsadmin.dbo.db_backups (family_guid, before_backup_epoch, after_backup_epoch, database_name, database_backup) SELECT 'b5d954b4-aaa9-4537-996b-0b5f82635d4d', '1402357569', '1402357570', N'RDSTEST2', bulkcolumn FROM OPENROWSET(BULK 'D:RDSDBDATABACKUPb5d954b4-aaa9-4537-996b-0b5f82635d4d.1402357569.database_backup', SINGLE_BLOB) AS new_backup; COMMIT TRANSACTION declare @p1 int set @p1=104 exec sp_prepexec @p1 output,N'@mirroring_login_0 nvarchar(45),@mirroring_login_1 nvarchar(45),@mirroring_login_2 nvarchar(45)',N'SELECT CHECKSUM_AGG( CONVERT(INT, HASHBYTES(''MD5'', CONVERT(varchar(11), BINARY_CHECKSUM( p.name, p.sid, p.default_database_name, p.default_language_name, p.is_disabled, p.is_policy_checked, p.is_expiration_checked, p.password_hash, perms.permission_name, perms.state_desc, grantors.name ) ) ) ) ) FROM sys.sql_logins p LEFT OUTER JOIN sys.server_permissions perms ON p.principal_id = perms.grantee_principal_id LEFT OUTER JOIN sys.server_principals grantors ON perms.grantor_principal_id = grantors.principal_id WHERE p.type = ''S'' AND p.name NOT IN (''##MS_PolicyEventProcessingLogin##'', ''##MS_PolicyTsqlExecutionLogin##'', @mirroring_login_0, @mirroring_login_1, @mirroring_login_2) UNION ALL SELECT CHECKSUM_AGG( CONVERT(INT, HASHBYTES(''MD5'', CONVERT(varchar(11), BINARY_CHECKSUM( p.name, role_principal.name ) ) ) ) ) FROM sys.sql_logins p left outer join sys.server_role_members roles on p.principal_id = roles.member_principal_id left outer join sys.server_principals role_principal on roles.role_principal_id = role_principal.principal_id where p.type = ''S'' and role_principal.name is not null',@mirroring_login_0=N'AMAZONAGHLRIKB_login',@mirroring_login_1=N'AMAZONACHOVKR1_login',@mirroring_login_2=N'AMAZONA24V00NU_login' select @p1 DECLARE @num_days_to_retain INT DECLARE @earliest_retain_datetime DATETIME DECLARE @earliest_retain_epoch BIGINT SET @num_days_to_retain = CAST('36' AS INT) IF @num_days_to_retain < 1 BEGIN RAISERROR('NumDaysToRetain is %d but must be at least 1', 16, 0, @num_days_to_retain) WITH LOG END SET @earliest_retain_datetime = DATEADD(day, 0 - @num_days_to_retain, SYSUTCDATETIME()) SET @earliest_retain_epoch = DATEDIFF(second, '1970-01-01 00:00:00', @earliest_retain_datetime) DELETE FROM rdsadmin.dbo.log_backup_manifest WHERE file_epoch < @earliest_retain_epoch DELETE FROM rdsadmin.dbo.createdb_activity WHERE properly_created_time < @earliest_retain_datetime DELETE FROM rdsadmin.dbo.db_backups WHERE family_guid != 'ccfa0f19-df9b-4f12-9ce2-8d6f3408add2' and after_backup_epoch < @earliest_retain_epoch exec msdb..sp_delete_backuphistory @earliest_retain_datetime DELETE FROM rdsadmin..db_mappings WHERE family_guid NOT IN (SELECT family_guid FROM sys.databases sysdb JOIN rdsadmin..db_mappings mappings ON sysdb.name = mappings.database_name) AND created_time < GETDATE() - 15; declare @request_action as varchar(50) = 'SET_WITNESS_SERVER'; IF @request_action = 'SET_PARTNER_SERVER' BEGIN ALTER DATABASE [RDSTEST2] SET PARTNER = 'TCP://rdswitness:1120'; IF (select mirroring_state from sys.database_mirroring where database_id = (select database_id from sys.databases where name = 'RDSTEST2')) IS NOT NULL BEGIN ALTER DATABASE [RDSTEST2] SET PARTNER TIMEOUT 30; END END ELSE IF @request_action = 'SET_PARTNER_FAILOVER' BEGIN ALTER DATABASE [RDSTEST2] SET PARTNER FAILOVER; END ELSE IF @request_action = 'SET_PARTNER_FORCE_SERVICE_ALLOW_DATA_LOSS' BEGIN ALTER DATABASE [RDSTEST2] SET PARTNER FORCE_SERVICE_ALLOW_DATA_LOSS; END ELSE IF @request_action = 'SET_PARTNER_OFF' BEGIN ALTER DATABASE [RDSTEST2] SET PARTNER OFF; END ELSE IF @request_action = 'SET_PARTNER_RESUME' BEGIN ALTER DATABASE [RDSTEST2] SET PARTNER RESUME; END ELSE IF @request_action = 'SET_PARTNER_SAFETY_FULL' BEGIN ALTER DATABASE [RDSTEST2] SET PARTNER SAFETY FULL; END ELSE IF @request_action = 'SET_PARTNER_SAFETY_OFF' BEGIN ALTER DATABASE [RDSTEST2] SET PARTNER SAFETY OFF; END ELSE IF @request_action = 'SET_PARTNER_SUSPEND' BEGIN ALTER DATABASE [RDSTEST2] SET PARTNER SUSPEND; END ELSE IF @request_action = 'SET_PARTNER_TIMEOUT' BEGIN ALTER DATABASE [RDSTEST2] SET PARTNER TIMEOUT 30; END ELSE IF @request_action = 'SET_WITNESS_SERVER' BEGIN ALTER DATABASE [RDSTEST2] SET WITNESS = 'TCP://rdswitness:1120'; END ELSE IF @request_action = 'SET_WITNESS_OFF' BEGIN ALTER DATABASE [RDSTEST2] SET WITNESS OFF; END ELSE BEGIN RAISERROR ('Error - unknown request action %s', 16, 1, @request_action); END
以下のような
- CREATE DATABASE されたタイミングでイベントをアクティビティとして登録
- バックアップを取得
- ミラーサーバーにリストア
- 作成したデータベースをミラーリング対象として設定
一連の処理を実行しているのかと思います。
ミラーリングを設定したデータベースは削除しようとすると以下のようなエラーとなります。
一度ミラーを以下のようなクエリで解除し、同期を外した状態にすることで削除が可能となります。
# PARTNER OFF にして、少し待っていると再度同期設定がされてしまったので、ALTER → DROP を一連の流れで実施する必要がありそうですね。API とかあるのでしょうか??
ALTER DATABASE RDSTEST2 SET PARTNER OFF DROP DATABASE RDSTEST2
DROP DATABASE もトリガーでキャッチされているようで、DROP 直後はミラーでもデータベースは残っていますが、少し待っていると削除が行われます。
ドキュメントをきちんと読めていないので手探りで検証してみましたが、なかなか面白い作りですね。
ざっくりとした構成は以下のようになるようです。
SQL Server の情報としては同期モードのミラーリングについての情報を参考にするとよさそうですね。