記憶域スペース ダイレクトでボリュームの割り当てを区切る

適用対象: Windows Server 2022、Windows Server 2019

Windows Server 2019 では記憶域スペース ダイレクトでボリュームの割り当てを手動で区切るオプションが導入されています。 そのようにすると、特定の条件下でフォールト トレランスを大幅に向上させることができますが、管理上の考慮事項と複雑さが増えます。 このトピックでは、そのしくみについて説明し、PowerShell での例を示します。

重要

これは Windows Server 2019 の新機能です。 Windows Server 2016 では使用できません。

前提条件

Green checkmark icon. 次の場合に、このオプションの使用を検討してください。

  • クラスターに 6 台以上のサーバーがある。
  • かつクラスターが 3 方向ミラーの回復性のみを使用している。

Red X icon. 次の場合はこのオプションを使用しないでください。

理解

レビュー: 通常の割り当て

通常の 3 方向ミラーリングでは、ボリュームは多数の小さな "スラブ" に分割され、それらが 3 回コピーされ、クラスター内のすべてのサーバーのすべてのドライブに均等に分散されます。 詳細については、この詳細なブログを参照してください。

Diagram showing the volume being divided into three stacks of slabs and distributed evenly across every server.

この既定の割り当てにより、並列読み取りと書き込みが最大限に実行され、パフォーマンスが向上します。また次のように、シンプルになります。つまり、すべてのサーバーが均等にビジー状態になり、すべてのドライブが均等にいっぱいになり、すべてのボリュームがいっしょにオンラインのままになるかオフラインになります。 各ボリュームは、この例に示すように、最大 2 つの同時障害に耐えることが保証されます。

ただし、この割り当てでは、ボリュームは 3 つの同時障害に耐えることはできません。 3 台のサーバーで同時に障害が発生した場合、または 3 台のサーバーのドライブで同時に障害が発生した場合、少なくとも一部のスラブが (非常に高い確率で) 障害が発生した 3 台のドライブまたはサーバーに割り当てられているため、ボリュームにアクセスできなくなります。

次の例では、サーバー 1、3、および 5 で同時に障害が発生します。 多くのスラブには残ったコピーがありますが、一部のスラブにはありません。

Diagram showing three of six servers highlighted in red, and the overall volume is red.

ボリュームはオフラインになり、サーバーが回復するまでアクセスできなくなります。

新機能: 区切られた割り当て

区切られた割り当てでは、使用するサーバーのサブセットを指定します (最小 4)。 ボリュームは、以前と同様に 3 回コピーされるスラブに分割されますが、すべてのサーバーに割り当てられるのではなく、指定したサーバーのサブセットにのみスラブが割り当てられます

たとえば、8 ノードのクラスター (ノード 1 - 8) を使用している場合は、ノード 1、2、3、4 のディスクにのみボリュームが割り当てられるように指定できます。

長所

この例の割り当てでは、ボリュームは同時に 3 つの障害に耐えることができます。 ノード 1、2、および 6 がダウンした場合、ボリュームの 3 つのデータ コピーを保持しているノードのうち 2 つだけがダウンし、ボリュームはオンラインのままになります。

存続確率は、サーバーの数とその他の要因によって異なります。詳細については、「分析」を参照してください。

短所

区切られた割り当てにより、管理上の考慮事項と複雑さが増加します。

  1. ベスト プラクティス」セクションで説明されているように、管理者は、サーバー間での記憶域使用率のバランスを取るとともに、高い存続確率を維持するように各ボリュームの割り当てを区切る必要があります。

  2. 区切られた割り当てでは、サーバーごとに 1 つの容量ドライブ (最大値はありません) に相当するものを予約します。 これは、通常の割り当てに関して公開されている推奨事項 (合計 4 つの容量ドライブ) を超えています。

  3. サーバーとそのドライブの削除に関するページで説明されているように、サーバーで障害が発生し、交換が必要になった場合、管理者は、新しいサーバーを追加して障害が発生したボリュームの割り当てを削除することで、影響を受けるボリュームを更新する必要があります。次の例を参照してください。

PowerShell での使用法

New-Volume コマンドレットを使用して記憶域スペース ダイレクトにボリュームを作成できます。

たとえば、通常の 3 方向ミラー ボリュームを作成するには、次のようにします。

New-Volume -FriendlyName "MyRegularVolume" -Size 100GB

ボリュームを作成し、その割り当てを区切る

3 方向ミラー ボリュームを作成し、その割り当てを区切るには、次のようにします。

  1. まず、クラスター内のサーバーを変数 $Servers に割り当てます。

    $Servers = Get-StorageFaultDomain -Type StorageScaleUnit | Sort FriendlyName
    

    ヒント

    記憶域スペース ダイレクトでは、"ストレージ スケール ユニット" という用語は、直接接続されたドライブや、ドライブを含む直接接続された外部エンクロージャなどの、1 台のサーバーに接続されているすべての未加工のストレージを指します。 このコンテキストでは、"サーバー" と同じです。

  2. 新しい -StorageFaultDomainsToUse パラメーターを使うか、$Servers にインデックスを付けることによって、どのサーバーを使用するかを指定します。 たとえば、1 番目、2 番目、3 番目、および 4 番目のサーバー (インデックス 0、1、2、および 3) に対して割り当てを区切るには、次のようにします。

    New-Volume -FriendlyName "MyVolume" -Size 100GB -StorageFaultDomainsToUse $Servers[0,1,2,3]
    

区切られた割り当てを表示する

MyVolume がどのように割り当てられているかを確認するには、付録Get-VirtualDiskFootprintBySSU.ps1 スクリプトを使用します。

PS C:\> .\Get-VirtualDiskFootprintBySSU.ps1

VirtualDiskFriendlyName TotalFootprint Server1 Server2 Server3 Server4 Server5 Server6
----------------------- -------------- ------- ------- ------- ------- ------- -------
MyVolume                300 GB         100 GB  100 GB  100 GB  100 GB  0       0

Server1、Server2、Server3、および Server4 にのみ、MyVolume のスラブが含まれていることに注意してください。

区切られた割り当てを変更する

新しい Add-StorageFaultDomain コマンドレットと Remove-StorageFaultDomain コマンドレットを使用して、割り当ての区切り方を変更します。

たとえば、MyVolume を 1 台のサーバー分移動するには、次のようにします。

  1. 5 番目のサーバーが MyVolume のスラブを格納 "できる" ことを指定します。

    Get-VirtualDisk MyVolume | Add-StorageFaultDomain -StorageFaultDomains $Servers[4]
    
  2. 1 番目のサーバーが MyVolume のスラブを格納 "できない" ことを指定します。

    Get-VirtualDisk MyVolume | Remove-StorageFaultDomain -StorageFaultDomains $Servers[0]
    
  3. 変更を有効にするために、記憶域プールの負荷を再調整します。

    Get-StoragePool S2D* | Optimize-StoragePool
    

Get-StorageJob を使用して、再均衡化の進行状況を監視できます。

完了したら、Get-VirtualDiskFootprintBySSU.ps1 をもう一度実行して、MyVolume が移動したことを確認します。

PS C:\> .\Get-VirtualDiskFootprintBySSU.ps1

VirtualDiskFriendlyName TotalFootprint Server1 Server2 Server3 Server4 Server5 Server6
----------------------- -------------- ------- ------- ------- ------- ------- -------
MyVolume                300 GB         0       100 GB  100 GB  100 GB  100 GB  0

Server1 に MyVolume のスラブがなくなり、代わりに Server5 にあることに注意してください。

ベスト プラクティス

区切られたボリュームの割り当てを使用する場合のベスト プラクティスを次に示します。

4 台のサーバーを選択する

3 方向ミラー ボリュームをそれぞれ 4 台のサーバーだけに区切ります。

ストレージの均衡化

ボリュームのサイズを考慮して、各サーバーに割り当てられるストレージの量を調整します。

段階的にボリュームの割り当てを区切る

フォールト トレランスを最大化するには、各ボリュームの割り当てを一意にします。つまり、"すべて" のサーバーを別のボリュームと共有することはありません (重複は問題ありません)。

たとえば、8 ノード システムの場合、次のようにします。ボリューム 1: サーバー 1、2、3、4、ボリューム 2: サーバー 5、6、7、8、ボリューム 3: サーバー 3、4、5、6、ボリューム 4: サーバー 1、2、7、8

分析

このセクションでは、障害の数とクラスター サイズに応じて、ボリュームがオンラインでアクセス可能である算術的な確率 (または、オンラインでアクセス可能なストレージ全体の予想される割合) を導き出します。

注意

このセクションは省略可能です。 数値演算が得意な場合は、読んでください。 そうでない場合でも心配しないでください。区切られた割り当てを正常に実装するために必要なのは、PowerShell の使用方法ベスト プラクティスだけです。

常に最大 2 つの障害に耐える

3 方向ミラー ボリュームはいずれも、割り当てに関係なく、同時に最大 2 つの障害に耐えることができます。 2 台のドライブで障害が発生した場合、または 2 台のサーバーで障害が発生した場合、またはそれぞれの 1 台で障害が発生した場合、通常の割り当てであっても、3 方向ミラー ボリュームはオンライン状態を維持し、アクセスできます。

クラスターの障害が半分を超えた場合は問題

逆に、クラスター内の半分以上のサーバーまたはドライブで一度に障害が発生した場合は、割り当てに関係なく、クォーラムが失われ、3 方向ミラー ボリュームがすべてオフラインになり、アクセスできなくなります。

その中間の場合

3 つ以上の障害が一度に発生しても、少なくとも半数のサーバーとドライブが引き続き稼働している場合、障害が発生しているサーバーによっては、区切られた割り当てを持つボリュームが引き続きオンラインでアクセス可能になることがあります。

よく寄せられる質問

一部のボリュームを区切り、他のボリュームを区切らないようにすることができますか?

はい。 割り当てを区切るかどうかは、ボリュームごとに選択できます。

区切られた割り当てによってドライブ置換の動作が変更されますか?

いいえ。通常の割り当てと同じです。

その他の参照情報

付録

このスクリプトは、ボリュームがどのように割り当てられているかを確認するのに役立ちます。

前述のとおりに使用するには、コピー/貼り付けを行って、Get-VirtualDiskFootprintBySSU.ps1 として保存します。

Function ConvertTo-PrettyCapacity {
    Param (
        [Parameter(
            Mandatory = $True,
            ValueFromPipeline = $True
            )
        ]
    [Int64]$Bytes,
    [Int64]$RoundTo = 0
    )
    If ($Bytes -Gt 0) {
        $Base = 1024
        $Labels = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
        $Order = [Math]::Floor( [Math]::Log($Bytes, $Base) )
        $Rounded = [Math]::Round($Bytes/( [Math]::Pow($Base, $Order) ), $RoundTo)
        [String]($Rounded) + " " + $Labels[$Order]
    }
    Else {
        "0"
    }
    Return
}

Function Get-VirtualDiskFootprintByStorageFaultDomain {

    ################################################
    ### Step 1: Gather Configuration Information ###
    ################################################

    Write-Progress -Activity "Get-VirtualDiskFootprintByStorageFaultDomain" -CurrentOperation "Gathering configuration information..." -Status "Step 1/4" -PercentComplete 00

    $ErrorCannotGetCluster = "Cannot proceed because 'Get-Cluster' failed."
    $ErrorNotS2DEnabled = "Cannot proceed because the cluster is not running Storage Spaces Direct."
    $ErrorCannotGetClusterNode = "Cannot proceed because 'Get-ClusterNode' failed."
    $ErrorClusterNodeDown = "Cannot proceed because one or more cluster nodes is not Up."
    $ErrorCannotGetStoragePool = "Cannot proceed because 'Get-StoragePool' failed."
    $ErrorPhysicalDiskFaultDomainAwareness = "Cannot proceed because the storage pool is set to 'PhysicalDisk' fault domain awareness. This cmdlet only supports 'StorageScaleUnit', 'StorageChassis', or 'StorageRack' fault domain awareness."

    Try  {
        $GetCluster = Get-Cluster -ErrorAction Stop
    }
    Catch {
        throw $ErrorCannotGetCluster
    }

    If ($GetCluster.S2DEnabled -Ne 1) {
        throw $ErrorNotS2DEnabled
    }

    Try  {
        $GetClusterNode = Get-ClusterNode -ErrorAction Stop
    }
    Catch {
        throw $ErrorCannotGetClusterNode
    }

    If ($GetClusterNode | Where State -Ne Up) {
        throw $ErrorClusterNodeDown
    }

    Try {
        $GetStoragePool = Get-StoragePool -IsPrimordial $False -ErrorAction Stop
    }
    Catch {
        throw $ErrorCannotGetStoragePool
    }

    If ($GetStoragePool.FaultDomainAwarenessDefault -Eq "PhysicalDisk") {
        throw $ErrorPhysicalDiskFaultDomainAwareness
    }

    ###########################################################
    ### Step 2: Create SfdList[] and PhysicalDiskToSfdMap{} ###
    ###########################################################

    Write-Progress -Activity "Get-VirtualDiskFootprintByStorageFaultDomain" -CurrentOperation "Analyzing physical disk information..." -Status "Step 2/4" -PercentComplete 25

    $SfdList = Get-StorageFaultDomain -Type ($GetStoragePool.FaultDomainAwarenessDefault) | Sort FriendlyName # StorageScaleUnit, StorageChassis, or StorageRack

    $PhysicalDiskToSfdMap = @{} # Map of PhysicalDisk.UniqueId -> StorageFaultDomain.FriendlyName
    $SfdList | ForEach {
        $StorageFaultDomain = $_
        $_ | Get-StorageFaultDomain -Type PhysicalDisk | ForEach {
            $PhysicalDiskToSfdMap[$_.UniqueId] = $StorageFaultDomain.FriendlyName
        }
    }

    ##################################################################################################
    ### Step 3: Create VirtualDisk.FriendlyName -> { StorageFaultDomain.FriendlyName -> Size } Map ###
    ##################################################################################################

    Write-Progress -Activity "Get-VirtualDiskFootprintByStorageFaultDomain" -CurrentOperation "Analyzing virtual disk information..." -Status "Step 3/4" -PercentComplete 50

    $GetVirtualDisk = Get-VirtualDisk | Sort FriendlyName

    $VirtualDiskMap = @{}

    $GetVirtualDisk | ForEach {
        # Map of PhysicalDisk.UniqueId -> Size for THIS virtual disk
        $PhysicalDiskToSizeMap = @{}
        $_ | Get-PhysicalExtent | ForEach {
            $PhysicalDiskToSizeMap[$_.PhysicalDiskUniqueId] += $_.Size
        }
        # Map of StorageFaultDomain.FriendlyName -> Size for THIS virtual disk
        $SfdToSizeMap = @{}
        $PhysicalDiskToSizeMap.keys | ForEach {
            $SfdToSizeMap[$PhysicalDiskToSfdMap[$_]] += $PhysicalDiskToSizeMap[$_]
        }
        # Store
        $VirtualDiskMap[$_.FriendlyName] = $SfdToSizeMap
    }

    #########################
    ### Step 4: Write-Out ###
    #########################

    Write-Progress -Activity "Get-VirtualDiskFootprintByStorageFaultDomain" -CurrentOperation "Formatting output..." -Status "Step 4/4" -PercentComplete 75

    $Output = $GetVirtualDisk | ForEach {
        $Row = [PsCustomObject]@{}

        $VirtualDiskFriendlyName = $_.FriendlyName
        $Row | Add-Member -MemberType NoteProperty "VirtualDiskFriendlyName" $VirtualDiskFriendlyName

        $TotalFootprint = $_.FootprintOnPool | ConvertTo-PrettyCapacity
        $Row | Add-Member -MemberType NoteProperty "TotalFootprint" $TotalFootprint

        $SfdList | ForEach {
            $Size = $VirtualDiskMap[$VirtualDiskFriendlyName][$_.FriendlyName] | ConvertTo-PrettyCapacity
            $Row | Add-Member -MemberType NoteProperty $_.FriendlyName $Size
        }

        $Row
    }

    # Calculate width, in characters, required to Format-Table
    $RequiredWindowWidth = ("TotalFootprint").length + 1 + ("VirtualDiskFriendlyName").length + 1
    $SfdList | ForEach {
        $RequiredWindowWidth += $_.FriendlyName.Length + 1
    }

    $ActualWindowWidth = (Get-Host).UI.RawUI.WindowSize.Width

    If (!($ActualWindowWidth)) {
        # Cannot get window width, probably ISE, Format-List
        Write-Warning "Could not determine window width. For the best experience, use a Powershell window instead of ISE"
        $Output | Format-Table
    }
    ElseIf ($ActualWindowWidth -Lt $RequiredWindowWidth) {
        # Narrower window, Format-List
        Write-Warning "For the best experience, try making your PowerShell window at least $RequiredWindowWidth characters wide. Current width is $ActualWindowWidth characters."
        $Output | Format-List
    }
    Else {
        # Wider window, Format-Table
        $Output | Format-Table
    }
}

Get-VirtualDiskFootprintByStorageFaultDomain