web-dev-qa-db-ja.com

復元速度を向上させる

QAデータベースから最大3GBのバックアップファイルと毎日の差分(最大500MB)があります。データの複雑さの理由から、一部の開発者はこのデータベースから定期的なバックアップを使用することを好みます。そのため、毎晩自動的に複数のデータベースに復元するプロセスがあります(つまり、開発者ごとに1つのデータベース)。

解凍すると、データベースサイズは約14GBと報告されます。

私たちのサーバーの1つでは、これらのデータベースをSSDで実行しているため、復元時間はデータベースごとに許容できる3分程度です。ただし、別のサーバーでは、SSDを追加することはできません。2つのデータベースの場合、復元には約17分かかります。

これは特にうまく拡張できないと思います-さまざまな開発者のために、そのインスタンス上にさらにいくつかのデータベースが必要であり、復元時間は法外です。

多くの場合、バックアップを複数のファイルに分割すると速度を回復するのに役立ちますが、バックアッププロセス自体は制御できません。私はdbasと話をすることができますが、彼らが変更を加えたくない可能性が十分にあります。

復元自体は、次のPowerShellスクリプトによって管理されます。

[CmdletBinding()]
Param(   
    [Parameter(Mandatory=$True)]
    [string[]]$dbNames
)
$sourcePath = "C:\SQL\Backups\"
$baseDbFolder = "C:\SQL\Data\"
$dbServer = "MyServer"
$dbToRestore = "SourceDatabase"

[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")  | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlEnum") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfo") | Out-Null

if ( Get-Command "Get-PSSnapin" -errorAction SilentlyContinue -and Get-PSSnapin -Registered | where {$_.name -eq 'SqlServerCmdletSnapin100'} )
{
    if( !(Get-PSSnapin | where {$_.name -eq 'SqlServerCmdletSnapin100'}))
    { 
        Add-PSSnapin SqlServerCmdletSnapin100 | Out-Null
    } ;
}
else
{
    if (Get-Command "Get-PSSnapin" -errorAction SilentlyContinue)
    {
        if( !(Get-Module | where {$_.name -eq 'sqlps'}))
        { 
            Import-Module 'sqlps' -DisableNameChecking ;
        }
    } ;
}

$sourcePathFull = join-path (join-path $sourcePath (join-path Full $dbToRestore)) *.bak
$sourcePathDiff = join-path (join-path $sourcePath (join-path Diff $dbToRestore)) *.bak
$sourceFileFull = gci $sourcePathFull | sort LastWriteTime | select -last 1
$sourceFileDiff = gci $sourcePathDiff | sort LastWriteTime | select -last 1
if ((Get-Item $sourceFileFull).LastWriteTime -gt (Get-Item $sourceFileDiff).LastWriteTime)
{
    $sourceFileDiff = $null
}

function GetCreateDbSql ([string]$baseDbFolder, [string]$dbName)
{
return @"
USE [master]
GO
IF db_id('$dbName') IS NOT NULL
    SET NOEXEC ON
GO
CREATE DATABASE [$dbName] ON  PRIMARY 
( NAME = N'AFO_PRIMARY', FILENAME = N'$baseDbFolder$dbName.mdf' , SIZE = 16240KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'AFO_log', FILENAME = N'$baseDbFolder$dbName.ldf' , SIZE = 1240KB , MAXSIZE = 2GB , FILEGROWTH = 10%)
GO
EXEC dbo.sp_dbcmptlevel @dbname=N'$dbName', @new_cmptlevel=80
GO
IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
begin
EXEC [$dbName].[dbo].[sp_fulltext_database] @action = 'disable'
end
GO
ALTER DATABASE [$dbName] SET ANSI_NULL_DEFAULT OFF
GO
ALTER DATABASE [$dbName] SET ANSI_NULLS OFF
GO
ALTER DATABASE [$dbName] SET ANSI_PADDING OFF
GO
ALTER DATABASE [$dbName] SET ANSI_WARNINGS OFF
GO
ALTER DATABASE [$dbName] SET ARITHABORT OFF
GO
ALTER DATABASE [$dbName] SET AUTO_CLOSE OFF
GO
ALTER DATABASE [$dbName] SET AUTO_CREATE_STATISTICS ON
GO
ALTER DATABASE [$dbName] SET AUTO_SHRINK OFF
GO
ALTER DATABASE [$dbName] SET AUTO_UPDATE_STATISTICS ON
GO
ALTER DATABASE [$dbName] SET CURSOR_CLOSE_ON_COMMIT OFF
GO
ALTER DATABASE [$dbName] SET CURSOR_DEFAULT  GLOBAL
GO
ALTER DATABASE [$dbName] SET CONCAT_NULL_YIELDS_NULL OFF
GO
ALTER DATABASE [$dbName] SET NUMERIC_ROUNDABORT OFF
GO
ALTER DATABASE [$dbName] SET QUOTED_IDENTIFIER OFF
GO
ALTER DATABASE [$dbName] SET RECURSIVE_TRIGGERS OFF
GO
ALTER DATABASE [$dbName] SET  DISABLE_BROKER
GO
ALTER DATABASE [$dbName] SET AUTO_UPDATE_STATISTICS_ASYNC OFF
GO
ALTER DATABASE [$dbName] SET DATE_CORRELATION_OPTIMIZATION OFF
GO
ALTER DATABASE [$dbName] SET TRUSTWORTHY OFF
GO
ALTER DATABASE [$dbName] SET ALLOW_SNAPSHOT_ISOLATION ON
GO
ALTER DATABASE [$dbName] SET PARAMETERIZATION SIMPLE
GO
ALTER DATABASE [$dbName] SET READ_COMMITTED_SNAPSHOT ON
GO
ALTER DATABASE [$dbName] SET  READ_WRITE
GO
ALTER DATABASE [$dbName] SET RECOVERY SIMPLE
GO
ALTER DATABASE [$dbName] SET  MULTI_USER
GO
ALTER DATABASE [$dbName] SET PAGE_VERIFY CHECKSUM
GO
ALTER DATABASE [$dbName] SET DB_CHAINING OFF
GO
ALTER DATABASE [$dbName] SET RECOVERY SIMPLE
GO
SET NOEXEC OFF
"@
}
function GetRestoreDbSql ([string]$fullBackupFile, [string]$diffBackupFile, [string] $baseDbFolder, [string]$dbName)
{
    $singleUser = @"
USE [master]

Alter Database [$dbName]
  SET SINGLE_USER With ROLLBACK IMMEDIATE
"@
    if ([string]::IsNullOrEmpty($diffBackupFile)) {
    $restoreFull = @"

RESTORE DATABASE [$dbName] FROM  DISK = N'$fullBackupFile' WITH  FILE = 1, RECOVERY, REPLACE,  STATS = 10
    , MOVE 'AFO_PRIMARY' TO '$baseDbFolder$dbName.mdf'
    , MOVE 'AFO_log' TO '$baseDbFolder$dbName.ldf'
"@
    } else {
    $restoreFull = @"

RESTORE DATABASE [$dbName] FROM  DISK = N'$fullBackupFile' WITH  FILE = 1, NORECOVERY, REPLACE,  STATS = 10
    , MOVE 'AFO_PRIMARY' TO '$baseDbFolder$dbName.mdf'
    , MOVE 'AFO_log' TO '$baseDbFolder$dbName.ldf'
"@
    }
    $restoreDiff = if ([string]::IsNullOrEmpty($diffBackupFile)) { "" } else { @"

RESTORE DATABASE [$dbName] FROM  DISK = N'$diffBackupFile' WITH  FILE = 1, STATS = 10, RECOVERY
"@ }    
    return "$singleUser$restoreFull$restoreDiff"
}
function GetTempSqlFilename ([string]$filename)
{
    return "$env:TEMP\$filename"
}
function WriteTempSqlFile ([string]$sql, [string]$filename)
{
    $stream = [System.IO.StreamWriter] $filename
    $stream.Write($sql)
    $stream.Close()
}

foreach ($dbName in $dbNames) {
    $createDbSqlFilename = GetTempSqlFilename "$dbName.create.sql"
    $restoreDbSqlFilename = GetTempSqlFilename "$dbName.restore.sql"
    $createDbSql = GetCreateDbSql $baseDbFolder $dbName
    $fileDiff = if ($fullOnly -eq $TRUE) { $null } else { $sourceFileDiff }
    $restoreDbSql = GetRestoreDbSql $sourceFileFull $fileDiff $baseDbFolder $dbName
    WriteTempSqlFile $createDbSql $createDbSqlFilename
    #Write-Host $createDbSqlFilename
    Invoke-Sqlcmd -InputFile $createDbSqlFilename -ServerInstance $dbServer -QueryTimeout 600
    WriteTempSqlFile $restoreDbSql $restoreDbSqlFilename
    #Write-Host $restoreDbSqlFilename
    Invoke-Sqlcmd -InputFile $restoreDbSqlFilename -ServerInstance $dbServer -QueryTimeout 600
}

私の制御(つまり、サーバー自体と復元スクリプト)の制限内で、復元のパフォーマンスを改善する方法はありますか?

3

スティーブがコメントで言ったように、ファイルの即時初期化がオンになっていることを確認してください。 Stingsの回答では、複数のファイルと圧縮について説明しましたが、残念ながら、これらはBACKUP側にあります。

ただし、 [〜#〜] restore [〜#〜] コマンドのBLOCKSIZEBUFFERCOUNTMAXTRANSFERSIZEオプションを使用して、restoreコマンドを変更することもできます。

残念ながら、RESTOREのBOLはそれらについてあまり言及していないため、 BACKUP DATABASE コマンドのBOLを確認する必要があります。

  • [〜#〜] blocksize [〜#〜]物理ブロックサイズをバイト単位で指定します。サポートされるサイズは、512、1024、2048、4096、8192、16384、32768、および65536(64 KB)バイトです。テープデバイスのデフォルトは65536で、それ以外の場合は512です。
  • [〜#〜] buffercount [〜#〜]バックアップ操作に使用するI/Oバッファーの総数を指定します。任意の正の整数を指定できます。ただし、Sqlservr.exeプロセスの仮想アドレス空間が不十分なため、多数のバッファが原因で「メモリ不足」エラーが発生する可能性があります。バッファーが使用する合計スペースは、buffercount * maxtransfersizeによって決定されます。

  • [〜#〜] maxtransfersize [〜#〜]SQL Serverとバックアップメディア間で使用される最大転送単位をバイト単位で指定します。可能な値は65536バイト(64 KB)の倍数で、最大4194304バイト(4 MB)です。

この 投稿には、MAXTRANSFERSIZEBUFFERCOUNTの類似点と警告がありました

これは使用するのが難しいオプションです。多くのバッファに指定すると、「メモリ不足」エラーが発生する可能性があります。このオプションは常に注意して使用してください。リカバリプロセスで使用される合計メモリは、MaxTransferSize x BufferCount =復元に必要なメモリです。サーバーに必要なメモリ容量がない場合、エラーが発生します。

SQL Serverの復元プロセスは、バケットを使用して火を消すと考えることができます。

BufferCountは火を消すために使用するバケットの数を設定し、MaxTransferSizeはこれらのバケットをどの程度いっぱいにするかを設定します。多くのバケットを使用し、半分だけを充填することは想像できるので、数個のバケットのみを使用するのと同じように、火を消すのにかかる時間に影響を与える可能性があります。持ち上げるには重すぎます。ストレージとメモリ構成に一致するように値を最適化する必要があるため、MaxTransferSizeおよびBufferCountオプションの設定は、環境ごとに異なる場合があります。最適化された復元時間になるまで、さまざまな値を試してください。

彼が最後に言ったように、これらの使用法はサーバーごとに異なるので、最適な復元速度を得るには、それらをいくらか試してみる必要があります。

そして、ここに がかなり詳細にそれらを使用することを引き継ぐ素晴らしい質問がありますBACKUPSについて話しているが、ここでもオプションは同じである。

編集私はこれを一日中探していました。 Nic Cainには、さまざまな設定でバックアップを何度も試行して、最良の結果を見つけるのに役立つスクリプトがあります。 ここにリンクがあります。 このスクリプトを簡単に変更して、代わりにRESTOREを停止できます。

3
Kenneth Fisher

バックアップ/復元時間を短縮するには、複数のT-SQLディスクと圧縮を試してください

バックアップと復元のT-SQLコマンドに複数のディスクを追加することで、バックアップと復元の両方の速度が大幅に向上することに気付きました。 PSスクリプトを変更する前に、この基本的なテストを試して結果を測定します。圧縮も使用してみてください。 DBAと一緒になって、違いを示してください。私自身DBAと言えば、これらの変更は簡単に実装できます。これらの小さな変更を行うために指を離すことができない場合、彼らは実際に何をしているのだろうと思います。

オプションA:

ソースシステムを使用して、複数のファイルディスクと圧縮を使用してデータベースをターゲットQAシステムにバックアップし、I/Oを最小化してスレッドを最大化します。

On Source SQL Server:

BACKUP DATABASE [MyDatabase] 
to  **DISK** = N'\\\QASystem\ShareLocation\FullBackupFile1.bak'
, **DISK** = N'\\\QASystem\ShareLocation\FullBackupFile2.bak' 
WITH INIT, COMPRESSION

ターゲットSQLサーバー:

RESTORE DATABASE [MyDatabase] 
FROM **DISK** = N'X:\LocalDirectory\FullBackupFile1.bak'
, **DISK** = N'X:\LocalDirectory\FullBackupFile2.bak' WITH  FILE = 1
, RECOVERY
, REPLACE
, STATS = 10
, MOVE 'AFO_PRIMARY' TO 'MyDatabase.mdf'
, MOVE 'AFO_log' TO 'MyDatabase.ldf'

オプションB:

ネットワークのスループットが本当に高い場合(10 GBの場合)、ネットワーク経由でファイルを低速のディスクにバックアップするのではなく、SSDディスクをファイルの復元場所として使用する必要があるかもしれません。

On Source SQL Server:

BACKUP DATABASE [MyDatabase] 
to **DISK** = N'Z:\SSDLocalDirectory\FullBackupFile1.bak'
, **DISK** = N'Z:\SSDLocalDirectory\FullBackupFile2.bak' 
WITH INIT, COMPRESSION

ターゲットSQLサーバー:

RESTORE DATABASE [MyDatabase] 
FROM **DISK** = N'\\\SSDSystem\ShareLocation\FullBackupFile1.bak'
, **DISK** = N'\\\SSDSystem\ShareLocation\FullBackupFile2.bak' WITH  FILE = 1
, RECOVERY
, REPLACE
, STATS = 10      
, MOVE 'AFO_PRIMARY' TO 'MyDatabase.mdf' 
, MOVE 'AFO_log' TO 'MyDatabase.ldf'

注:パフォーマンスの向上に気づいた場合は、ディスクファイルを追加して、さらに向上するかどうかを確認してください。

オプションC:

これは私が選択するものではないと思いますが、具体的にはあなたの質問にも答えて、頑固なDBAをバイパスする可能性があります。 SQL Server 2008 R2以降では、ファイル共有ストレージに接続されるデータベースを作成できます。理論的には、SSDディスクを共有し、実際のデータベース共有でデータベースを稼働させることができます。バックアップの方がはるかに高速だと思いますが、データベースへのアクセス量によってはネットワークが飽和する可能性があります。この方法を使用して、デタッチ/再アタッチもオプションになります。

https://support.Microsoft.com/en-us/kb/304261

1
Sting

上記のすべての回答は、oneデータベース復元の速度を向上させることに関するものです。しかし、あなたの質問から、リストアのスケーラビリティ、つまり複数のdbリストアについても懸念していることは承知しています。以前に同様の問題に実際に遭遇しましたが、私の解決策は、PowerShellワークフローを利用してparallel復元を行うことです。

リンクはこちらで確認できます

PowerShellで複数のSQL Serverデータベースの復元を自動化して環境を更新する

0
jyao