SQL Server 2012で導入された可用性グループは、データベースの高可用性と障害復旧の両方についての考え方の根本的な変化を表しています。ここで可能になった優れた点の1つは、読み取り専用操作をセカンダリレプリカにオフロードすることです。これにより、プライマリ読み取り/書き込みインスタンスは、エンドユーザーのレポートなどの厄介なことに煩わされることはありません。これを設定するのは簡単ではありませんが、以前のソリューションよりもはるかに簡単で保守が容易です(ミラーリングとスナップショットの設定、およびそれに関連するすべての永続的な保守が好きな場合は、手を挙げてください)。
可用性グループについて聞くと、人々は非常に興奮します。次に現実が襲います。この機能には、SQLServerのEnterpriseEditionが必要です(とにかく、SQL Server 2014以降)。 Enterprise Editionは、特にコアが多数ある場合、特にCALベースのライセンスが廃止されたため(2008 R2から祖父になっていない限り、最初の20コアに制限されている場合)は高価です。また、Windows Serverフェールオーバークラスタリング(WSFC)も必要です。これは、ラップトップでテクノロジをデモンストレーションするためだけでなく、WindowsのEnterprise Edition、ドメインコントローラ、およびクラスタリングをサポートするための一連の構成も必要とします。また、ソフトウェアアシュアランスに関する新しい要件もあります。スタンバイインスタンスを準拠させたい場合は、追加のコストがかかります。
一部の顧客は価格を正当化できません。他の人はその価値を見ますが、単にそれを買う余裕はありません。では、これらのユーザーは何をするのでしょうか?
あなたの新しいヒーロー:ログ配送
丸太の発送は何年も前からありました。シンプルで機能します。ほとんどいつも。また、可用性グループによって提示されたライセンスコストと構成のハードルを回避するだけでなく、今週のSQLskills Insiderニュースレター(2014年10月13日)でPaul Randal(@PaulRandal)が話していた14バイトのペナルティを回避することもできます。
ただし、ログに送信されたコピーを読み取り可能なセカンダリとして使用する際の課題の1つは、新しいログを適用するために現在のすべてのユーザーを追い出さなければならないことです。クエリの実行から、またはデータが古くなっているためにユーザーがイライラする場合。これは、人々が自分自身を単一の読み取り可能なセカンダリに制限しているためです。
そのようにする必要はありません。ここには適切な解決策があると思います。たとえば、可用性グループをオンにするよりも前もって多くの作業が必要になる場合がありますが、それは確かに一部の人にとって魅力的なオプションです。
基本的に、いくつかのセカンダリを設定できます。ここでは、ラウンドロビンアプローチを使用して、ログシップを行い、そのうちの1つだけを「アクティブな」セカンダリにします。ログを送信するジョブは、現在アクティブなログを認識しているため、 WITH STANDBY
を使用して新しいログを「次の」サーバーに復元するだけです。 オプション。レポートアプリケーションは同じ情報を使用して、実行時にユーザーが実行する次のレポートの接続文字列を決定します。次のログバックアップの準備ができると、すべてが1つシフトし、新しい読み取り可能なセカンダリになるインスタンスが WITH STANDBY
を使用して復元されます。 。
モデルを複雑にしないために、読み取り可能なセカンダリとして機能する4つのインスタンスがあり、15分ごとにログバックアップを作成するとします。いつでも、スタンバイモードでアクティブなセカンダリが1つあり、データは15分以内で、スタンバイモードで新しいクエリを処理していないセカンダリが3つあります(ただし、古いクエリの結果が返される場合があります)。
これは、クエリが45分より長く続くと予想されない場合に最適に機能します。 (読み取り専用操作の性質、より長いクエリを実行している同時ユーザーの数、および全員を追い出すことによってユーザーを混乱させる可能性があるかどうかに応じて、これらのサイクルを調整する必要がある場合があります。)
また、同じユーザーが連続して実行するクエリで接続文字列を変更でき(アーキテクチャによっては同義語またはビューを使用できますが、これはアプリケーションに必要なロジックです)、次のような異なるデータが含まれている場合にも最適に機能します。その間に変更されました(ライブの絶えず変化するデータベースにクエリを実行しているかのように)。
これらすべての仮定を念頭に置いて、実装の最初の75分間の一連のイベントを以下に示します。
time | イベント | ビジュアル |
---|---|---|
12:00 (t0) |
| |
12:15 (t1) |
| |
12:30 (t2) |
| |
12:45 (t3) |
| |
13:00 (t4) |
|
それは簡単に思えるかもしれません。それをすべて処理するコードを書くのはもう少し大変です。大まかな概要:
- プライマリサーバー上(これを
BOSS
と呼びます )、データベースを作成します。さらに先に進むことを考える前に、トレースフラグ3226をオンにして、正常なバックアップメッセージがSQLServerのエラーログを散らかさないようにします。 -
BOSS
について 、セカンダリごとにリンクサーバーを追加します(PEON1
と呼びます) ->PEON4
。 - すべてのサーバーがアクセスできる場所で、データベース/ログのバックアップを保存するファイル共有を作成し、各インスタンスのサービスアカウントに読み取り/書き込みアクセス権があることを確認します。また、各セカンダリインスタンスには、スタンバイファイル用に指定された場所が必要です。
- 別のユーティリティデータベース(または必要に応じてMSDB)で、データベース、すべてのセカンダリ、およびログのバックアップと復元の履歴に関する構成情報を保持するテーブルを作成します。
- データベースをバックアップし、セカンダリに復元するストアドプロシージャを作成します
WITH NORECOVERY
、次に1つのログを適用しますWITH STANDBY
、および1つのインスタンスを現在のスタンバイセカンダリとしてマークします。これらの手順を使用して、問題が発生した場合にログ配布設定全体を再初期化することもできます。 - 15分ごとに実行されるジョブを作成して、上記のタスクを実行します。
- ログをバックアップする
- 適用されていないログバックアップを適用するセカンダリを決定する
- これらのログを適切な設定で復元します
- 新しい読み取り専用クエリに使用するセカンダリを呼び出し元のアプリケーションに通知するストアドプロシージャ(および/またはビュー?)を作成します。
- クリーンアップ手順を作成して、すべてのセカンダリに適用されたログのログバックアップ履歴をクリアします(おそらく、ファイル自体を移動またはパージするためにも)。
- 堅牢なエラー処理と通知でソリューションを強化します。
ステップ1-データベースを作成する
私のプライマリインスタンスは、。\ BOSS
という名前のStandardEditionです。 。その場合、1つのテーブルを使用して単純なデータベースを作成します。
USE [master]; GO CREATE DATABASE UserData; GO ALTER DATABASE UserData SET RECOVERY FULL; GO USE UserData; GO CREATE TABLE dbo.LastUpdate(EventTime DATETIME2); INSERT dbo.LastUpdate(EventTime) SELECT SYSDATETIME();
次に、そのタイムスタンプを1分ごとに更新するだけのSQLServerエージェントジョブを作成します。
UPDATE UserData.dbo.LastUpdate SET EventTime = SYSDATETIME();
これにより、初期データベースが作成され、アクティビティがシミュレートされ、読み取り可能な各セカンダリでログ配布タスクがどのようにローテーションされるかを検証できます。この演習のポイントは、テストログの配布にストレスをかけたり、パンチスルーできる量を証明したりすることではないことを明確に述べたいと思います。それはまったく別の演習です。
ステップ2–リンクサーバーを追加する
。\PEON1
という名前の4つのセカンダリExpressEditionインスタンスがあります 、。\ PEON2
、。\ PEON3
、および。\ PEON4
。そこで、このコードを4回実行し、 @s
を変更しました。 毎回:
USE [master]; GO DECLARE @s NVARCHAR(128) = N'.\PEON1', -- repeat for .\PEON2, .\PEON3, .\PEON4 @t NVARCHAR(128) = N'true'; EXEC [master].dbo.sp_addlinkedserver @server = @s, @srvproduct = N'SQL Server'; EXEC [master].dbo.sp_addlinkedsrvlogin @rmtsrvname = @s, @useself = @t; EXEC [master].dbo.sp_serveroption @server = @s, @optname = N'collation compatible', @optvalue = @t; EXEC [master].dbo.sp_serveroption @server = @s, @optname = N'data access', @optvalue = @t; EXEC [master].dbo.sp_serveroption @server = @s, @optname = N'rpc', @optvalue = @t; EXEC [master].dbo.sp_serveroption @server = @s, @optname = N'rpc out', @optvalue = @t;
ステップ3–ファイル共有を検証する
私の場合、5つのインスタンスすべてが同じサーバー上にあるため、インスタンスごとにフォルダーを作成しました: C:\ temp \ Peon1 \
、 C:\ temp \ Peon2 \
、 等々。セカンダリが異なるサーバー上にある場合、場所はそのサーバーからの相対位置である必要がありますが、プライマリからアクセスできる必要があります(したがって、通常はUNCパスが使用されます)。各インスタンスがその共有に書き込めることを検証する必要があります。また、各インスタンスがスタンバイファイルに指定された場所に書き込めることも検証する必要があります(スタンバイに同じフォルダーを使用しました)。これを検証するには、各インスタンスから指定された各場所に小さなデータベースをバックアップします。これが機能するまで続行しないでください。
ステップ4–テーブルを作成する
このデータをmsdb
に配置することにしました 、しかし、私は別のデータベースを作成することに賛成または反対の強い感情を持っていません。最初に必要なテーブルは、ログ配布するデータベースに関する情報を保持するテーブルです。
CREATE TABLE dbo.PMAG_Databases ( DatabaseName SYSNAME, LogBackupFrequency_Minutes SMALLINT NOT NULL DEFAULT (15), CONSTRAINT PK_DBS PRIMARY KEY(DatabaseName) ); GO INSERT dbo.PMAG_Databases(DatabaseName) SELECT N'UserData';
(命名スキームに興味がある場合、PMAGは「PoorMan'sAvailabilityGroups」の略です。)
必要なもう1つのテーブルは、個々のフォルダやログ配布シーケンスでの現在のステータスなど、セカンダリに関する情報を保持するためのテーブルです。
CREATE TABLE dbo.PMAG_Secondaries ( DatabaseName SYSNAME, ServerInstance SYSNAME, CommonFolder VARCHAR(512) NOT NULL, DataFolder VARCHAR(512) NOT NULL, LogFolder VARCHAR(512) NOT NULL, StandByLocation VARCHAR(512) NOT NULL, IsCurrentStandby BIT NOT NULL DEFAULT 0, CONSTRAINT PK_Sec PRIMARY KEY(DatabaseName, ServerInstance), CONSTRAINT FK_Sec_DBs FOREIGN KEY(DatabaseName) REFERENCES dbo.PMAG_Databases(DatabaseName) );
ソースサーバーからローカルにバックアップし、セカンダリをリモートで復元する場合、またはその逆の場合は、 CommonFolder
を分割できます。 2つの列に( BackupFolder
およびRestoreFolder
)、コードに関連する変更を加えます(それほど多くはありません)。
sys.servers
の情報に少なくとも部分的に基づいて、このテーブルにデータを入力できるため –データ/ログおよびその他のフォルダーがインスタンス名にちなんで名付けられているという事実を利用する:
INSERT dbo.PMAG_Secondaries ( DatabaseName, ServerInstance, CommonFolder, DataFolder, LogFolder, StandByLocation ) SELECT DatabaseName = N'UserData', ServerInstance = name, CommonFolder = 'C:\temp\Peon' + RIGHT(name, 1) + '\', DataFolder = 'C:\Program Files\Microsoft SQL Server\MSSQL12.PEON' + RIGHT(name, 1) + '\MSSQL\DATA\', LogFolder = 'C:\Program Files\Microsoft SQL Server\MSSQL12.PEON' + RIGHT(name, 1) + '\MSSQL\DATA\', StandByLocation = 'C:\temp\Peon' + RIGHT(name, 1) + '\' FROM sys.servers WHERE name LIKE N'.\PEON[1-4]';
多くの場合、複数のログファイルを順番に復元する必要があるため、(最後のバックアップだけでなく)個々のログバックアップを追跡するためのテーブルも必要です。この情報はmsdb.dbo.backupset
から取得できます 、しかし、場所などを取得するのははるかに複雑です。バックアップ履歴をクリーンアップする可能性のある他のジョブを制御できない可能性があります。
CREATE TABLE dbo.PMAG_LogBackupHistory ( DatabaseName SYSNAME, ServerInstance SYSNAME, BackupSetID INT NOT NULL, Location VARCHAR(2000) NOT NULL, BackupTime DATETIME NOT NULL DEFAULT SYSDATETIME(), CONSTRAINT PK_LBH PRIMARY KEY(DatabaseName, ServerInstance, BackupSetID), CONSTRAINT FK_LBH_DBs FOREIGN KEY(DatabaseName) REFERENCES dbo.PMAG_Databases(DatabaseName), CONSTRAINT FK_LBH_Sec FOREIGN KEY(DatabaseName, ServerInstance) REFERENCES dbo.PMAG_Secondaries(DatabaseName, ServerInstance) );
各セカンダリの行を保存し、すべてのバックアップの場所を保存するのは無駄だと思うかもしれませんが、これは将来を保証するためです。つまり、任意のセカンダリのCommonFolderを移動する場合に対処します。
そして最後に、ログの復元の履歴があるため、いつでも、どのログがどこに復元されたかを確認できます。復元ジョブでは、まだ復元されていないログのみを確実に復元できます。
CREATE TABLE dbo.PMAG_LogRestoreHistory ( DatabaseName SYSNAME, ServerInstance SYSNAME, BackupSetID INT, RestoreTime DATETIME, CONSTRAINT PK_LRH PRIMARY KEY(DatabaseName, ServerInstance, BackupSetID), CONSTRAINT FK_LRH_DBs FOREIGN KEY(DatabaseName) REFERENCES dbo.PMAG_Databases(DatabaseName), CONSTRAINT FK_LRH_Sec FOREIGN KEY(DatabaseName, ServerInstance) REFERENCES dbo.PMAG_Secondaries(DatabaseName, ServerInstance) );
ステップ5–セカンダリを初期化する
バックアップファイルを生成する(そして、さまざまなインスタンスが必要とする場所にミラーリングする)ストアドプロシージャが必要です。また、各セカンダリに1つのログを復元して、すべてをスタンバイ状態にします。この時点で、これらはすべて読み取り専用クエリで使用できるようになりますが、一度に「現在の」スタンバイになるのは1つだけです。これは、完全バックアップとトランザクションログバックアップの両方を処理するストアドプロシージャです。完全バックアップが要求されたとき、および @init
1に設定すると、ログ配布が自動的に再初期化されます。
CREATE PROCEDURE [dbo].[PMAG_Backup] @dbname SYSNAME, @type CHAR(3) = 'bak', -- or 'trn' @init BIT = 0 -- only used with 'bak' AS BEGIN SET NOCOUNT ON; -- generate a filename pattern DECLARE @now DATETIME = SYSDATETIME(); DECLARE @fn NVARCHAR(256) = @dbname + N'_' + CONVERT(CHAR(8), @now, 112) + RIGHT(REPLICATE('0',6) + CONVERT(VARCHAR(32), DATEDIFF(SECOND, CONVERT(DATE, @now), @now)), 6) + N'.' + @type; -- generate a backup command with MIRROR TO for each distinct CommonFolder DECLARE @sql NVARCHAR(MAX) = N'BACKUP' + CASE @type WHEN 'bak' THEN N' DATABASE ' ELSE N' LOG ' END + QUOTENAME(@dbname) + ' ' + STUFF( (SELECT DISTINCT CHAR(13) + CHAR(10) + N' MIRROR TO DISK = ''' + s.CommonFolder + @fn + '''' FROM dbo.PMAG_Secondaries AS s WHERE s.DatabaseName = @dbname FOR XML PATH(''), TYPE).value(N'.[1]',N'nvarchar(max)'),1,9,N'') + N' WITH NAME = N''' + @dbname + CASE @type WHEN 'bak' THEN N'_PMAGFull' ELSE N'_PMAGLog' END + ''', INIT, FORMAT' + CASE WHEN LEFT(CONVERT(NVARCHAR(128), SERVERPROPERTY(N'Edition')), 3) IN (N'Dev', N'Ent') THEN N', COMPRESSION;' ELSE N';' END; EXEC [master].sys.sp_executesql @sql; IF @type = 'bak' AND @init = 1 -- initialize log shipping BEGIN EXEC dbo.PMAG_InitializeSecondaries @dbname = @dbname, @fn = @fn; END IF @type = 'trn' BEGIN -- record the fact that we backed up a log INSERT dbo.PMAG_LogBackupHistory ( DatabaseName, ServerInstance, BackupSetID, Location ) SELECT DatabaseName = @dbname, ServerInstance = s.ServerInstance, BackupSetID = MAX(b.backup_set_id), Location = s.CommonFolder + @fn FROM msdb.dbo.backupset AS b CROSS JOIN dbo.PMAG_Secondaries AS s WHERE b.name = @dbname + N'_PMAGLog' AND s.DatabaseName = @dbname GROUP BY s.ServerInstance, s.CommonFolder + @fn; -- once we've backed up logs, -- restore them on the next secondary EXEC dbo.PMAG_RestoreLogs @dbname = @dbname; END END
これにより、別々に呼び出すことができる2つのプロシージャが呼び出されます(ただし、ほとんどの場合、呼び出されません)。まず、最初の実行時にセカンダリを初期化する手順:
ALTER PROCEDURE dbo.PMAG_InitializeSecondaries @dbname SYSNAME, @fn VARCHAR(512) AS BEGIN SET NOCOUNT ON; -- clear out existing history/settings (since this may be a re-init) DELETE dbo.PMAG_LogBackupHistory WHERE DatabaseName = @dbname; DELETE dbo.PMAG_LogRestoreHistory WHERE DatabaseName = @dbname; UPDATE dbo.PMAG_Secondaries SET IsCurrentStandby = 0 WHERE DatabaseName = @dbname; DECLARE @sql NVARCHAR(MAX) = N'', @files NVARCHAR(MAX) = N''; -- need to know the logical file names - may be more than two SET @sql = N'SELECT @files = (SELECT N'', MOVE N'''''' + name + '''''' TO N''''$'' + CASE [type] WHEN 0 THEN N''df'' WHEN 1 THEN N''lf'' END + ''$'''''' FROM ' + QUOTENAME(@dbname) + '.sys.database_files WHERE [type] IN (0,1) FOR XML PATH, TYPE).value(N''.[1]'',N''nvarchar(max)'');'; EXEC master.sys.sp_executesql @sql, N'@files NVARCHAR(MAX) OUTPUT', @files = @files OUTPUT; SET @sql = N''; -- restore - need physical paths of data/log files for WITH MOVE -- this can fail, obviously, if those path+names already exist for another db SELECT @sql += N'EXEC ' + QUOTENAME(ServerInstance) + N'.master.sys.sp_executesql N''RESTORE DATABASE ' + QUOTENAME(@dbname) + N' FROM DISK = N''''' + CommonFolder + @fn + N'''''' + N' WITH REPLACE, NORECOVERY' + REPLACE(REPLACE(REPLACE(@files, N'$df$', DataFolder + @dbname + N'.mdf'), N'$lf$', LogFolder + @dbname + N'.ldf'), N'''', N'''''') + N';'';' + CHAR(13) + CHAR(10) FROM dbo.PMAG_Secondaries WHERE DatabaseName = @dbname; EXEC [master].sys.sp_executesql @sql; -- backup a log for this database EXEC dbo.PMAG_Backup @dbname = @dbname, @type = 'trn'; -- restore logs EXEC dbo.PMAG_RestoreLogs @dbname = @dbname, @PrepareAll = 1; END
そして、ログを復元する手順:
CREATE PROCEDURE dbo.PMAG_RestoreLogs @dbname SYSNAME, @PrepareAll BIT = 0 AS BEGIN SET NOCOUNT ON; DECLARE @StandbyInstance SYSNAME, @CurrentInstance SYSNAME, @BackupSetID INT, @Location VARCHAR(512), @StandByLocation VARCHAR(512), @sql NVARCHAR(MAX), @rn INT; -- get the "next" standby instance SELECT @StandbyInstance = MIN(ServerInstance) FROM dbo.PMAG_Secondaries WHERE IsCurrentStandby = 0 AND ServerInstance > (SELECT ServerInstance FROM dbo.PMAG_Secondaries WHERE IsCurrentStandBy = 1); IF @StandbyInstance IS NULL -- either it was last or a re-init BEGIN SELECT @StandbyInstance = MIN(ServerInstance) FROM dbo.PMAG_Secondaries; END -- get that instance up and into STANDBY -- for each log in logbackuphistory not in logrestorehistory: -- restore, and insert it into logrestorehistory -- mark the last one as STANDBY -- if @prepareAll is true, mark all others as NORECOVERY -- in this case there should be only one, but just in case DECLARE c CURSOR LOCAL FAST_FORWARD FOR SELECT bh.BackupSetID, s.ServerInstance, bh.Location, s.StandbyLocation, rn = ROW_NUMBER() OVER (PARTITION BY s.ServerInstance ORDER BY bh.BackupSetID DESC) FROM dbo.PMAG_LogBackupHistory AS bh INNER JOIN dbo.PMAG_Secondaries AS s ON bh.DatabaseName = s.DatabaseName AND bh.ServerInstance = s.ServerInstance WHERE s.DatabaseName = @dbname AND s.ServerInstance = CASE @PrepareAll WHEN 1 THEN s.ServerInstance ELSE @StandbyInstance END AND NOT EXISTS ( SELECT 1 FROM dbo.PMAG_LogRestoreHistory AS rh WHERE DatabaseName = @dbname AND ServerInstance = s.ServerInstance AND BackupSetID = bh.BackupSetID ) ORDER BY CASE s.ServerInstance WHEN @StandbyInstance THEN 1 ELSE 2 END, bh.BackupSetID; OPEN c; FETCH c INTO @BackupSetID, @CurrentInstance, @Location, @StandbyLocation, @rn; WHILE @@FETCH_STATUS -1 BEGIN -- kick users out - set to single_user then back to multi SET @sql = N'EXEC ' + QUOTENAME(@CurrentInstance) + N'.[master].sys.sp_executesql ' + 'N''IF EXISTS (SELECT 1 FROM sys.databases WHERE name = N''''' + @dbname + ''''' AND [state] 1) BEGIN ALTER DATABASE ' + QUOTENAME(@dbname) + N' SET SINGLE_USER ' + N'WITH ROLLBACK IMMEDIATE; ALTER DATABASE ' + QUOTENAME(@dbname) + N' SET MULTI_USER; END;'';'; EXEC [master].sys.sp_executesql @sql; -- restore the log (in STANDBY if it's the last one): SET @sql = N'EXEC ' + QUOTENAME(@CurrentInstance) + N'.[master].sys.sp_executesql ' + N'N''RESTORE LOG ' + QUOTENAME(@dbname) + N' FROM DISK = N''''' + @Location + N''''' WITH ' + CASE WHEN @rn = 1 AND (@CurrentInstance = @StandbyInstance OR @PrepareAll = 1) THEN N'STANDBY = N''''' + @StandbyLocation + @dbname + N'.standby''''' ELSE N'NORECOVERY' END + N';'';'; EXEC [master].sys.sp_executesql @sql; -- record the fact that we've restored logs INSERT dbo.PMAG_LogRestoreHistory (DatabaseName, ServerInstance, BackupSetID, RestoreTime) SELECT @dbname, @CurrentInstance, @BackupSetID, SYSDATETIME(); -- mark the new standby IF @rn = 1 AND @CurrentInstance = @StandbyInstance -- this is the new STANDBY BEGIN UPDATE dbo.PMAG_Secondaries SET IsCurrentStandby = CASE ServerInstance WHEN @StandbyInstance THEN 1 ELSE 0 END WHERE DatabaseName = @dbname; END FETCH c INTO @BackupSetID, @CurrentInstance, @Location, @StandbyLocation, @rn; END CLOSE c; DEALLOCATE c; END
(コードが多く、不可解な動的SQLが多いことはわかっています。コメントは非常に寛大にしようとしました。問題が発生している部分がある場合は、お知らせください。)>
これで、システムを起動して実行するために必要なのは、2つのプロシージャ呼び出しを行うことだけです。
EXEC dbo.PMAG_Backup @dbname = N'UserData', @type = 'bak', @init = 1; EXEC dbo.PMAG_Backup @dbname = N'UserData', @type = 'trn';
これで、データベースのスタンバイコピーを含む各インスタンスが表示されます。
そして、どれが現在読み取り専用スタンバイとして機能する必要があるかを確認できます:
SELECT ServerInstance, IsCurrentStandby FROM dbo.PMAG_Secondaries WHERE DatabaseName = N'UserData';
ステップ6–ログをバックアップ/復元するジョブを作成する
このコマンドは、15分ごとにスケジュールするジョブに入れることができます:
EXEC dbo.PMAG_Backup @dbname = N'UserData', @type = 'trn';
これにより、アクティブなセカンダリが15分ごとにシフトし、そのデータは前のアクティブなセカンダリより15分新鮮になります。異なるスケジュールで複数のデータベースがある場合は、複数のジョブを作成するか、ジョブをより頻繁にスケジュールして dbo.PMAG_Databases
を確認できます。 個々のLogBackupFrequency_Minutes
のテーブル そのデータベースのバックアップ/復元を実行する必要があるかどうかを判断するための値。
ステップ7–どのスタンバイがアクティブであるかをアプリケーションに通知するためのビューと手順
CREATE VIEW dbo.PMAG_ActiveSecondaries AS SELECT DatabaseName, ServerInstance FROM dbo.PMAG_Secondaries WHERE IsCurrentStandby = 1; GO CREATE PROCEDURE dbo.PMAG_GetActiveSecondary @dbname SYSNAME AS BEGIN SET NOCOUNT ON; SELECT ServerInstance FROM dbo.PMAG_ActiveSecondaries WHERE DatabaseName = @dbname; END GO
私の場合、すべての UserData
全体でビューユニオンを手動で作成しました データベースを使用して、プライマリと各セカンダリのデータの最新性を比較できるようにします。
CREATE VIEW dbo.PMAG_CompareRecency_UserData AS WITH x(ServerInstance, EventTime) AS ( SELECT @@SERVERNAME, EventTime FROM UserData.dbo.LastUpdate UNION ALL SELECT N'.\PEON1', EventTime FROM [.\PEON1].UserData.dbo.LastUpdate UNION ALL SELECT N'.\PEON2', EventTime FROM [.\PEON2].UserData.dbo.LastUpdate UNION ALL SELECT N'.\PEON3', EventTime FROM [.\PEON3].UserData.dbo.LastUpdate UNION ALL SELECT N'.\PEON4', EventTime FROM [.\PEON4].UserData.dbo.LastUpdate ) SELECT x.ServerInstance, s.IsCurrentStandby, x.EventTime, Age_Minutes = DATEDIFF(MINUTE, x.EventTime, SYSDATETIME()), Age_Seconds = DATEDIFF(SECOND, x.EventTime, SYSDATETIME()) FROM x LEFT OUTER JOIN dbo.PMAG_Secondaries AS s ON s.ServerInstance = x.ServerInstance AND s.DatabaseName = N'UserData'; GO
週末のサンプル結果:
SELECT [Now] = SYSDATETIME(); SELECT ServerInstance, IsCurrentStandby, EventTime, Age_Minutes, Age_Seconds FROM dbo.PMAG_CompareRecency_UserData ORDER BY Age_Seconds DESC;
ステップ8–クリーンアップ手順
ログのバックアップと復元の履歴をクリーンアップするのは非常に簡単です。
CREATE PROCEDURE dbo.PMAG_CleanupHistory @dbname SYSNAME, @DaysOld INT = 7 AS BEGIN SET NOCOUNT ON; DECLARE @cutoff INT; -- this assumes that a log backup either -- succeeded or failed on all secondaries SELECT @cutoff = MAX(BackupSetID) FROM dbo.PMAG_LogBackupHistory AS bh WHERE DatabaseName = @dbname AND BackupTime < DATEADD(DAY, -@DaysOld, SYSDATETIME()) AND EXISTS ( SELECT 1 FROM dbo.PMAG_LogRestoreHistory AS rh WHERE BackupSetID = bh.BackupSetID AND DatabaseName = @dbname AND ServerInstance = bh.ServerInstance ); DELETE dbo.PMAG_LogRestoreHistory WHERE DatabaseName = @dbname AND BackupSetID <= @cutoff; DELETE dbo.PMAG_LogBackupHistory WHERE DatabaseName = @dbname AND BackupSetID <= @cutoff; END GO
これで、既存のジョブのステップとして追加することも、完全に個別に、または他のクリーンアップルーチンの一部としてスケジュールすることもできます。
ファイルシステムのクリーンアップは別の投稿のために残しておきます(おそらく、PowerShellやC#などの別のメカニズムです。これは通常、T-SQLで実行したい種類のことではありません)。
ステップ9–ソリューションを強化する
このソリューションをより完全にするために、ここでより良いエラー処理やその他の優れた機能がある可能性があるのは事実です。今のところ、これは読者の演習として残しておきますが、このソリューションの改善と改良について詳しく説明するフォローアップ投稿を確認する予定です。
変数と制限
私の場合、プライマリとしてStandard Editionを使用し、すべてのセカンダリにExpressEditionを使用したことに注意してください。予算規模をさらに進めて、Express Editionをプライマリとして使用することもできます。多くの人は、Express Editionはログ配布をサポートしていないと考えていますが、実際には、ManagementStudioのバージョンには存在しなかったウィザードにすぎません。 SQL Server 2012 Service Pack 1より前のExpress。ただし、ExpressEditionはSQLServer Agentをサポートしていないため、このシナリオでパブリッシャーにすることは困難です。ストアドプロシージャを呼び出すように独自のスケジューラーを構成する必要があります(C#さらに別のインスタンスでWindowsタスクスケジューラ、PowerShellジョブ、またはSQL Serverエージェントジョブによって実行されるコマンドラインアプリ)。どちらの側でもExpressを使用するには、データファイルが10 GBを超えないこと、およびクエリがそのエディションのメモリ、CPU、および機能の制限で正常に機能することを確認する必要があります。 Expressが理想的であることを示唆しているわけではありません。私は単に、非常に柔軟で読み取り可能なセカンダリを無料で(またはそれに非常に近い形で)持つことができることを示すために使用しました。
また、私のシナリオのこれらの個別のインスタンスはすべて同じVM上にありますが、そのように機能する必要はありません。インスタンスを複数のサーバーに分散させることができます。または、別の方法で、同じインスタンス上の異なる名前のデータベースの異なるコピーに復元することもできます。これらの構成では、上記でレイアウトしたものに最小限の変更を加える必要があります。また、復元するデータベースの数と頻度は完全にあなた次第です。ただし、実際的な上限はあります(ここで、[平均クエリ時間]>[セカンダリの数]x[ログバックアップ間隔]コード> 。
最後に、このアプローチには確かにいくつかの制限があります。網羅的ではないリスト:
- 自分のスケジュールで完全バックアップを引き続き作成できますが、ログバックアップは唯一のログバックアップメカニズムとして機能する必要があります。他の目的でログバックアップを保存する必要がある場合、ログチェーンに干渉するため、このソリューションとは別にログをバックアップすることはできません。代わりに、
MIRROR TO
を追加することを検討できます。 ログのコピーを他の場所で使用する必要がある場合は、既存のログバックアップスクリプトへの引数。 - While "Poor Man's Availability Groups" may seem like a clever name, it can also be a bit misleading. This solution certainly lacks many of the HA/DR features of Availability Groups, including failover, automatic page repair, and support in the UI, Extended Events and DMVs. This was only meant to provide the ability for non-Enterprise customers to have an infrastructure that supports multiple readable secondaries.
- I tested this on a very isolated VM system with no concurrency. This is not a complete solution and there are likely dozens of ways this code could be made tighter; as a first step, and to focus on the scaffolding and to show you what's possible, I did not build in bulletproof resiliency. You will need to test it at your scale and with your workload to discover your breaking points, and you will also potentially need to deal with transactions over linked servers (always fun) and automating the re-initialization in the event of a disaster.
The "Insurance Policy"
Log shipping also offers a distinct advantage over many other solutions, including Availability Groups, mirroring and replication:a delayed "insurance policy" as I like to call it. At my previous job, I did this with full backups, but you could easily use log shipping to accomplish the same thing:I simply delayed the restores to one of the secondary instances by 24 hours. This way, I was protected from any client "shooting themselves in the foot" going back to yesterday, and I could get to their data easily on the delayed copy, because it was 24 hours behind. (I implemented this the first time a customer ran a delete without a where clause, then called us in a panic, at which point we had to restore their database to a point in time before the delete – which was both tedious and time consuming.) You could easily adapt this solution to treat one of these instances not as a read-only secondary but rather as an insurance policy. More on that perhaps in another post.