Enterprise Edition の場合は、一つの可用性グループに複数のデータベースを配置することができますので、他の DB との JOIN については、必要となる DB を特定の可用性グループにまとめればよいのですが、Standard Edition の場合は、機能の制約があるためそうはいきません。
Standard Editoin では、セカンダリレプリカを読み取り専用として使用することができませんので、以下のような配置になった場合、DB1 と DB3 のテーブルを JOIN するということができない状態となります。
そもそもとして、リスナーを経由しないアクセスを使用としているのが問題なのですが、実際の利用シーンではこのような配置になった状態で、クエリ的に JOIN をしたい要望というのは発生してしまうかと思います。
そこで、クラスターに以下のような仕組みを作成してみました。
今回は [AG-01] ~ [AG-03] の 3 つの可用性グループを作成しており、[AG-01] に [AGMonitor] というリソースを追加しています。
このリソースでは、この可用性グループがプライマリとなっているサーバーに、すべての可用性グループを移動させた状態にするというような処理を実装しています。
これにより、冒頭の図のような配置にはならず、下図のように特定のサーバーに可用性グループを寄せることができるようになります。 ![]()
実際の動作としては以下のようになります。
最初は他のサーバーがプライマリとなっているため、自身のサーバーはセカンダリの状態です。
今回は 「AG-01」に「AGMonitor」のリソースを設定していますので、この可用性グループがフェールオーバーし、プライマリレプリカとなることで、他の可用性グループも関連させてフェールオーバーし、片側のサーバーに可用性グループを寄せるようになっています。
これで片方のサーバーにすべての可用性グループがプライマリの状態となりますので、DB 間の JOIN を実行することが可能となります。
# 冒頭でも書きましたが、本来はリスナー経由でのアクセスを前提とする必要があります。
検証用のモックとして作ったものですが、実際の処理としては以下のような内容となっています。
$QueryTimeout = 30
$WaitTime = 5
Import-Module SQLPS -DisableNameChecking
$sql = @"
SELECT
ag.name,
ar.replica_server_name,
drs.is_primary_replica,
ags.primary_replica,
DB_NAME(drs.database_id) AS database_name,
drs.database_state_desc,
ags.secondary_recovery_health_desc,
ags.synchronization_health_desc,
drs.synchronization_state_desc,
drs.last_commit_time,
drs.last_redone_time,
drs.last_sent_time,
drs.last_received_time,
drs.last_hardened_time
FROM
sys.dm_hadr_availability_group_states ags
LEFT JOIN
sys.availability_groups ag
ON
ags.group_id = ag.group_id
LEFT JOIN
sys.dm_hadr_database_replica_states drs
ON
drs.group_id = ag.group_id
LEFT JOIN
sys.availability_replicas ar
ON
ar.replica_id = drs.replica_id
WHERE
replica_server_name = @@SERVERNAME
{0}
OPTION (RECOMPILE)
"@
While ($true){
# セカンダリとなっている AG を取得
$SecondaryAGs = Invoke-Sqlcmd -ServerInstance . -Database master -Query ($sql -f "AND is_primary_replica = 0") -QueryTimeout $QueryTimeout
foreach ($AG in $SecondaryAGs){
$AGState = Invoke-Sqlcmd -ServerInstance . -Database master -Query ($sql -f "AND ag.name = '$($AG.name)'") -QueryTimeout $QueryTimeout
# フェールオーバー対象の AG が同期済みの状態でない場合は、フェールオーバーを待機
while($AGState.synchronization_state_desc -ne "SYNCHRONIZED"){
Write-Host ("AG:[{0}] Sync Status:{1}.Sync Wait... " -f $AG.name, $agstate.synchronization_state_desc)
Start-Sleep -Seconds $WaitTime
$AGState = Invoke-Sqlcmd -ServerInstance . -Database master -Query ($sql -f "AND ag.name = '$($AG.name)'") -QueryTimeout $QueryTimeout
}
Write-Host ("AG:[{0}] Failover Start" -f $AG.name)
Invoke-Sqlcmd -ServerInstance . -Database master -Query "ALTER AVAILABILITY GROUP [$($AG.Name)] FAILOVER" -QueryTimeout 0
Write-Host ("AG:[{0}] Failover End" -f $AG.name)
}
Write-Host "Waiting..."
Start-Sleep -Seconds $WaitTime
}
実施している内容はシンプルで、クエリを実行して可用性グループの状態を取得し、同期が完了している場合、フェールオーバーを実施するという仕組みになっています。
現状は、フェールオーバーが成功する前提で処理を組んできますが、同期完了の状態にならない場合の待機など、実稼働に即した形での修正は必要になるかと思いますが、ベースとしては使えるのではないでしょうか。
複数の可用性グループを同時にフェールオーバーするというような処理はしていないため、グループ数が多いとそれなりに時間がかかるため、効率については見直す必要があるかとは思いますが。
# あとはフェールオーバー失敗時等のエラートラップの考慮も。
このスクリプトをクラスターリソースとして登録します。
登録には以下のようなスクリプトを使用するとよいかと。
これで、可用性グループのリソースに汎用アプリケーションとして PowerShell のスクリプトが登録され、可用性グループの状態を監視しながら、必要に応じてフェールオーバーを発生させることができます。
スクリプトに関しては可用性グループをホストする各サーバーに配置する必要があります。
# スクリプトを汎用アプリケーションに登録
$AGName = "AG-01"
Add-ClusterResource -Name "AGMonitor" -Group $AGName -ResourceType "Generic Application"
$Resource = Get-ClusterResource -Name "AGMonitor"
$Resource | Set-ClusterParameter -Multiple @{CommandLine = "powershell.exe -Command ""C:\Scripts\AGMonitor.ps1""";CurrentDirectory="C:\Scripts"}
$Resource | Set-ClusterResourceDependency -Dependency "([$AGName])"
$Resource | Start-ClusterResource
スクリプトについては、AGMonitor.ps1 でも公開していますので、処理の修正が必要な個所等ありましたら、プルリク等をいただければと思います。