Servers Backup & DR

Windows Failover Clustering and Storage Spaces Direct: A Production Build

Hyper-converged infrastructure on Windows Server collapses compute and storage onto the same nodes using Storage Spaces Direct (S2D), with Failover Clustering providing the high-availability fabric. Done right, you get a software-defined storage pool that survives a node loss without a SAN in sight — every node contributes its local NVMe, SSD, and HDD to a shared pool, presented back to the cluster as Cluster Shared Volumes (CSV) that every node reads and writes concurrently. Done wrong, you build a cluster that loses quorum the first time a node reboots for patching, or a pool whose resync storm pins storage latency at 60 ms every time a node comes back. This is the production build I run: understand the machine first — the software storage bus, the cache binding, the resiliency math — validate before you commit, enable S2D in a controlled step, and prove it survives failure before it carries load.

The reference throughout is a four-node cluster, each node with dual 25 GbE RDMA NICs and a mix of NVMe cache and SSD/HDD capacity drives. Commands target Windows Server 2022 (S2D requires Datacenter edition); the mechanics apply to 2019 and 2025 too. What separates this from a “click Enable-ClusterS2D and hope” walkthrough is that S2D fails in specific, diagnosable ways — a cache drive that silently detaches, a mirror volume that can’t rebuild because reserve capacity was consumed, a RoCE fabric that degrades to lossy without a single disk going unhealthy — each a distinct mechanism you can see, confirm, and fix. The tables here enumerate every resiliency type, fault-domain level, drive state, and failure flow, so mid-incident you read the prose once and keep the tables open.

By the end you will stop treating S2D as a black box: why a three-way mirror survives two disk losses but a two-way mirror can’t survive a drive failure during a node reboot, how much of your raw capacity the mirror and reserve consume, what the cache actually caches (and what changes on an all-NVMe box), how the Software Storage Bus (SSB) and Storage Bus Layer (SBL) move every write across RDMA to its copies, and exactly what happens — command by command, state by state — when a drive dies at 3 a.m. or a node drops mid-patch.

What problem this solves

Traditional Windows high availability meant a SAN: a shared array on Fibre Channel or iSCSI, a disk witness on a shared LUN, and a storage team owning the array separately from the server team. It worked, but it concentrated risk and cost in one box, required specialist skills, and made a two-node branch cluster absurdly expensive. S2D removes the SAN entirely — storage is the local drives in each server, pooled in software and mirrored across nodes over the network, so a node can die and its data still lives on the survivors. That is the whole promise of hyper-converged infrastructure: commodity servers with local NVMe/SSD/HDD, SAN-grade resilience in software.

What breaks without understanding the machinery: teams enable S2D, drop VMs onto it, and hit failures that live in layers the SAN world never exposed. A two-way mirror looks fine until a drive fails while a node is rebooting for patches — now two fault domains are unavailable and the volume goes offline. A pool fills to 95% because nobody reserved rebuild capacity, and the next drive failure can’t rebuild — degraded with no path back to healthy. RoCE storage traffic degrades to lossy after a switch firmware update resets DCB, and “storage is slow” tickets chase the disk layer for weeks while every disk reports green. A node is rebooted for firmware without pausing S2D on it first, triggering a full resync of everything it held. None of these are bugs; they are the system behaving exactly as designed against an operator who didn’t know the design.

Who hits this: anyone running Hyper-V or SQL on Windows who wants HA without a SAN — branch offices (2-node clusters), mid-size datacentres (4–8 nodes replacing an aging array), and SQL/VDI shops needing low-latency storage with node fault tolerance. It bites hardest on teams migrating from SAN thinking, because the failure modes, the sizing math (usable ≠ raw), and the maintenance discipline (pause storage before every reboot) are all new. The fix is never “add more disks and hope” — it is knowing the resiliency math, reserving rebuild capacity, and driving maintenance and repair with the exact PowerShell that keeps the pool healthy.

To frame the whole field before the deep dive, here is every layer of an S2D cluster, what lives there, and the failure class it owns:

Layer What it is What lives here Failure class it owns
Failover Cluster HA membership + quorum Nodes, votes, witness, cluster DB Quorum loss on patch reboot; split-brain
Storage pool All eligible drives, pooled Physical disks, cache bindings, reserve Pool degraded; no rebuild capacity
Software Storage Bus (SSB) Virtual SAN fabric over SMB/RDMA East-west storage traffic, SBL cache “Slow storage” from RDMA/DCB drift
Virtual disks (Spaces) Resilient volumes carved from pool Mirror/parity extents, copies Volume offline when fault domains lost
ReFS + CSV File system + shared-volume layer C:\ClusterStorage\*, integrity streams CSV redirected I/O; ReFS corruption
RDMA / SET network Low-latency east-west transport RoCEv2/iWARP, PFC/DCB, SET vSwitch Retransmits, latency spikes, path loss
Clustered roles The workloads Hyper-V VMs, SOFS shares Failover flapping; cold-cache after move

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should already be comfortable with Failover Clustering fundamentals — that a cluster stays online only while it holds quorum (a majority of votes), that each node has one vote and a witness breaks ties, and that Cluster Shared Volumes let every node access the same volume concurrently. You should know how to run PowerShell remotely (Invoke-Command), read Get-* output, and understand basic storage concepts (RAID-like resilience, mirror vs parity, IOPS vs throughput). Familiarity with SMB, RDMA at a conceptual level, and Hyper-V helps but isn’t assumed.

This sits in the Windows Server storage and availability track. It is the hyper-converged sibling of the SAN-based cluster: where a classic cluster uses shared external storage and a disk witness, S2D pools local drives and uses a cloud or file-share witness. It pairs tightly with Patching Failover Clusters with Cluster-Aware Updating and Stretch Clusters via Storage Replica — CAU is how you patch this cluster without downtime, and Storage Replica is how you stretch it across two sites once the single-site build is bulletproof. If you run Hyper-V on it (the most common case), Hyper-V Live Migration and Replica for Zero-Downtime VM Mobility covers the compute-mobility half. For headless operation, Managing Windows Server Core Headless with Windows Admin Center and PowerShell is how you run these nodes on Server Core (recommended for HCI). Configuration drift across nodes — a killer for S2D, which demands identical firmware/drivers — is addressed in Configuration Management for Windows Server with PowerShell DSC and Ansible.

A quick map of who confirms what during an S2D incident, so you open the right tool first:

Concern Where the truth lives Confirming cmdlet / tool Failure classes it surfaces
Cluster membership / quorum Cluster DB, event log Get-ClusterNode, Get-ClusterQuorum Node down, quorum loss (event 1177)
Pool / drive health Storage subsystem Get-StoragePool, Get-PhysicalDisk Pool degraded, drive lost/retired
Volume resiliency / repair Virtual disk + storage jobs Get-VirtualDisk, Get-StorageJob Volume degraded, rebuild in flight
Cache bindings Physical disk usage Get-PhysicalDisk (Usage=Journal) Cache drive detached, no cache
RDMA fabric NIC + SMB + switch Get-NetAdapterRdma, Get-SmbMultichannelConnection Latency spikes, retransmits, path loss
CSV / ReFS Cluster shared volumes Get-ClusterSharedVolume, event logs Redirected I/O, ReFS corruption
Fault domains Storage fault domain topology Get-StorageFaultDomain Wrong tolerance unit; unbalanced pool

Core concepts

Six mental models make every later decision — and every incident — obvious.

S2D is a software-defined virtual SAN built from local drives. There is no shared array. Each node’s local drives are claimed into a single storage pool spanning all nodes. When you create a volume, S2D spreads its data — and its redundant copies — across drives in different nodes, so losing a node loses only one copy. The Software Storage Bus (SSB) stitches every node’s drives into that one pool: it presents remote drives to the local storage stack as if local, and moves I/O between nodes over SMB3 with RDMA. This is why the network is the storage backplane in S2D — a degraded RDMA fabric is a degraded storage array, even though every disk reads healthy.

Resiliency is copies across fault domains, and the fault domain is the node, not the drive. A two-way mirror keeps 2 copies of every slab; a three-way mirror keeps 3. Parity keeps data plus computed parity symbols (like RAID-5/6) for space efficiency at a write-performance cost. S2D places these copies to survive the loss of a whole node (the default fault domain), not merely a drive. That’s the value proposition and the source of the sizing math: a three-way mirror on a 4-node cluster survives any two nodes failing (non-concurrently) because the third copy is always on a survivor — but it costs 3× the raw capacity for the data you store.

The cache is a write-back / read cache in front of the capacity tier, bound automatically. With mixed media, S2D binds the fastest device class (NVMe, then SSD) as cache and the slower class as capacity. Every write lands in cache first (low latency), de-stages to capacity later, and hot reads come from cache. Cache drives show Usage = Journal; capacity drives show Usage = Auto-Select. The cache is not extra usable space — it’s a performance layer sacrificed from the fast drives. On an all-NVMe or single-media box there is no separate cache tier by default (nothing faster to cache with), which changes both sizing and performance.

ReFS on CSV is the mandatory file system, not optional plumbing. S2D volumes use CSVFS_ReFS — ReFS presented through the Cluster Shared Volume layer. ReFS provides integrity streams (checksums that let S2D detect and auto-repair bit rot from a good copy), block cloning (near-instant VHDX operations), and real-time tiering for mirror-accelerated-parity volumes. CSV makes the volume accessible from every node simultaneously — essential for live migration and Scale-Out File Server. NTFS on S2D throws away integrity streams and tiering; ReFS is required.

Reserve capacity is the room a rebuild needs, and it is not optional. When a drive or node fails, S2D rebuilds the missing copies onto the remaining drives to restore full resiliency — but only if there is free space to rebuild into. Fill the pool and the next failure leaves you degraded with no path to healthy until you free space or add drives. Leave one capacity drive’s worth of free space per node (Microsoft’s guidance) as reserve — deducted from usable capacity up front, never “found” later.

The quorum model is the same clustering quorum — but the witness must never be a disk. S2D still needs cluster quorum, and a witness is still the tiebreaker for even-node clusters. But there is no shared disk in an HCI S2D cluster, so a disk witness is impossible — use a cloud witness (a tiny Azure blob) or a file-share witness (a share on an independent server, never a cluster node). Getting this wrong — no witness on a 2- or 4-node cluster — is the single most common self-inflicted outage: quorum is lost the first time a node reboots for patching.

The vocabulary in one table

Before the deep sections, pin down every moving part. The glossary at the end repeats these for lookup; this is the mental model side by side:

Term One-line definition Where it lives Why it matters
Storage pool All eligible drives across all nodes, pooled Cluster storage subsystem The source of all capacity; one pool per S2D cluster
Software Storage Bus (SSB) Virtual SAN fabric presenting remote drives locally Software layer over SMB/RDMA Moves every cross-node write; the storage backplane
Storage Bus Layer (SBL) cache The write-back/read cache the SSB manages On cache drives (Usage=Journal) Absorbs writes, serves hot reads; performance
Cache drive Fastest media class, bound as cache NVMe/SSD, Usage=Journal Not usable space; sacrificed for speed
Capacity drive Slower media class, holds the data SSD/HDD, Usage=Auto-Select Your usable-before-resiliency raw capacity
Virtual disk (Space) A resilient volume carved from the pool Cluster storage Resiliency (mirror/parity) is set here
CSVFS_ReFS ReFS presented via Cluster Shared Volume C:\ClusterStorage\* Integrity, block clone, tiering, shared access
Fault domain The unit of failure S2D tolerates (default: node) Storage fault-domain topology Determines whether you survive a node, chassis, rack
Resiliency Number/pattern of copies (2/3-way mirror, parity) Per virtual disk Fault tolerance vs usable capacity trade-off
Reserve capacity Free space kept for rebuilds Deducted from usable No reserve → no rebuild after a failure
Storage job An async pool operation (repair/rebalance) Get-StorageJob How you track a rebuild/resync
Storage maintenance mode Pauses S2D on a scale unit for safe reboot Per node/scale unit Prevents needless resync storms
Witness Quorum tiebreaker (cloud or file share on S2D) Azure blob / SMB share Even-node clusters need it; never disk on S2D

The S2D I/O path: SSB, SBL cache, and how a write becomes redundant

To operate S2D you must know what a single write does, because every performance and resiliency behaviour follows from it. Here is one 64 KB write to a three-way-mirror CSV, from the VM down to persistent media on three nodes:

Step What happens Layer Latency contributor
1 VM issues write to its VHDX on C:\ClusterStorage\vm-prod-01 Guest / Hyper-V
2 CSV routes the I/O; ReFS receives it with an integrity checksum CSVFS_ReFS Metadata, checksum compute
3 Spaces determines the 3 mirror copies live on drives in 3 different nodes Storage Spaces Slab/extent mapping
4 SSB sends the write to each target node over SMB3 + RDMA Software Storage Bus Network RTT (RDMA ≈ µs)
5 On each node the SBL cache (NVMe) acknowledges the write (write-back) SBL cache NVMe write latency
6 Once a quorum of copies is durable in cache, the write is acknowledged to the VM Spaces / SSB The number the VM “feels”
7 Later, cache de-stages the data to capacity drives (SSD/HDD) SBL cache → capacity Background; not on the write path

The consequences, which drive every later decision:

What binds to cache, by media mix

Cache behaviour is not uniform — it depends on what media types are present. Get this wrong in your hardware order and you either waste NVMe or get no write cache at all:

Deployment media Cache device Capacity device Caching behaviour Notes
NVMe + HDD NVMe HDD Read + write cache Biggest win; NVMe hides HDD latency both directions
SSD + HDD SSD HDD Read + write cache SSD fronts HDD; good bulk tier
NVMe + SSD NVMe SSD Write-only cache SSD reads are already fast; cache absorbs writes
NVMe + SSD + HDD NVMe SSD (perf) + HDD (cap) Write cache for SSD, read+write for HDD 3-tier; NVMe caches, SSD+HDD are capacity
All-NVMe (none by default) NVMe No cache tier Nothing faster to cache with; can force one for endurance
All-SSD (none by default) SSD No cache tier Same reasoning; optionally bind some SSDs as cache

For all-flash boxes where you still want a cache (usually to spare capacity-drive endurance by absorbing writes on higher-endurance cache drives), force the cache device model before enabling S2D:

# Force NVMe as cache in an all-NVMe box to spare capacity-drive endurance (optional)
Enable-ClusterStorageSpacesDirect -CimSession 'hci-clus01' `
    -CacheDeviceModel 'INTEL SSDPE2KX010T8' `
    -Confirm:$false

The cache-to-capacity ratio matters: the cache must hold the active working set. Microsoft’s guidance is a minimum of ~2 cache drives per node, and capacity drives should be a multiple of the cache drives so binding is even (e.g. 2 cache + 4 capacity, each cache drive fronting 2 capacity drives).

Resiliency: mirror, parity, and mirror-accelerated parity

Resiliency is set per volume — the single decision defining your fault tolerance and usable capacity. This is where SAN thinking fails people: usable capacity is a fraction of raw, and the fraction depends on the resiliency and node count. Study the full matrix:

Resiliency Min nodes Copies / scheme Survives Capacity efficiency Write performance Use for
Two-way mirror 2 2 copies 1 drive or 1 node 50% Highest (mirror) 2-node clusters only; latency-critical
Three-way mirror 3 3 copies 2 drives or 2 nodes (non-concurrent) 33% High (mirror) Most production VM/SQL workloads
Dual parity (erasure coding) 4 Data + 2 parity 2 drives or 2 nodes ~50–80% (scales up with nodes) Lower (parity write penalty) Archival, backups, large cold data
Mirror-accelerated parity 4 Mirror tier + parity tier 2 drives or 2 nodes Mixed (between mirror & parity) Mirror-speed writes, parity-efficient rest Mixed hot-write + cold-capacity
Nested two-way mirror (2-node) 2 4 copies (2×2) Drive and node concurrently 25% High 2-node clusters needing drive+node tolerance
Nested mirror-accelerated parity (2-node) 2 Nested mirror + nested parity Drive + node concurrently Mixed (better than nested mirror) Mirror-speed hot + efficient cold 2-node with capacity efficiency need

Why two-way mirror is dangerous in production

Two-way mirror keeps two copies. On a 3+ node cluster it can survive one node — but consider the real failure sequence that bites teams: you reboot node A for patching (one fault domain unavailable), and during that window a drive fails on node B. Slabs that had one copy on node A and one on the failed drive of node B now have zero available copies — the volume goes offline and VMs crash. Patch-window drive failures are exactly when clusters are most stressed, and two-way mirror has no margin for a fault during maintenance. Use it only on genuine 2-node clusters, and even then prefer nested resiliency if you must survive a drive failure while a node is down.

Three-way mirror: the production default

Three copies across three fault domains: any two nodes (or drives) can be lost non-concurrently and every slab still has a copy. This is the safe default for active VM/SQL workloads — mirror write performance (no parity penalty), and enough margin that a drive failure during a patch reboot still leaves one copy. The cost is capacity: 3× the raw for the data you keep, i.e. 33% efficiency.

Parity and mirror-accelerated parity

Dual parity (erasure coding) is space-efficient — like RAID-6 it stores data plus two parity symbols and tolerates two failures, and efficiency improves with more nodes. But it carries a write penalty (read-modify-write of parity on every write), so use it for cold, large, sequentially-written data (backups, archives, media), not busy VMs.

Mirror-accelerated parity (MAP) is the middle path, and the reason ReFS real-time tiering matters. A MAP volume has a mirror tier and a parity tier: writes land in the fast mirror tier (mirror-speed), and ReFS rotates cold data down to parity in the background. You get near-mirror write performance for hot data and near-parity efficiency for the cold majority. The trade-off: size the mirror tier for your write working set, or if it fills faster than ReFS can rotate, writes slow to parity speed. MAP shines for a hot write front over a large cold tail (file servers, backup targets with active ingest).

Computing usable capacity and reserve — worked

The arithmetic on the reference 4-node cluster, assuming each node has 8 × 4 TB capacity SSDs (cache NVMe is separate and doesn’t count): 8 × 4 TB × 4 nodes = 128 TB raw.

Resiliency Raw capacity Efficiency Usable before reserve Reserve (~1 cap drive/node) Usable after reserve
Two-way mirror 128 TB 50% 64 TB ~16 TB (4 × 4 TB) ~48 TB
Three-way mirror 128 TB 33% ~42 TB ~16 TB ~26 TB
Dual parity (4-node) 128 TB ~50% ~64 TB ~16 TB ~48 TB
MAP (20% mirror / 80% parity) 128 TB ~55–65% ~70–83 TB ~16 TB ~54–67 TB

Two rules the table encodes:

Set the reserve implicitly by not allocating the last drive-per-node of capacity. Confirm the pool has headroom:

# Pool free space must exceed at least one capacity drive per node (the reserve)
Get-StoragePool -IsPrimordial $false |
    Select-Object FriendlyName,
        @{n='Size(TB)';    e={[math]::Round($_.Size/1TB,1)}},
        @{n='Alloc(TB)';   e={[math]::Round($_.AllocatedSize/1TB,1)}},
        @{n='Free(TB)';    e={[math]::Round(($_.Size-$_.AllocatedSize)/1TB,1)}}

Fault domains: surviving the unit you actually care about

By default S2D uses the node (StorageScaleUnit) as the fault domain — copies are placed so any one node can fail without data loss. But you can tell S2D about larger physical boundaries — chassis and rack — so it survives the loss of a whole chassis or rack. This matters when nodes share a chassis (blade enclosures) or a rack (shared power/top-of-rack switch): if all copies of a slab sit in nodes in the same rack and that rack loses power, you lose data even though no single node failed.

The fault-domain hierarchy and what each buys you:

Fault domain level Tolerates loss of When you need it How S2D uses it
PhysicalDisk One drive Always (baseline) Copies never share a drive
StorageScaleUnit (node) One node Default; every HCI cluster Copies placed across nodes (default)
StorageChassis One chassis (multiple nodes) Blade/enclosure deployments Copies placed across chassis
StorageRack One rack (power/ToR switch) Multi-rack clusters Copies placed across racks
StorageSite One site Stretch clusters (with Storage Replica) Cross-site replication, not S2D placement

Inspect the fault-domain topology (before enabling S2D, so placement is right from the start):

# Show the fault-domain tree the cluster currently sees
Get-StorageFaultDomain -Type StorageScaleUnit |
    Select-Object FriendlyName, FaultDomainType, HealthStatus

# For rack/chassis awareness, assign nodes to racks/chassis BEFORE enabling S2D
Set-ClusterFaultDomain -Name 'hci-n1' -Parent 'Rack-A'
Set-ClusterFaultDomain -Name 'hci-n2' -Parent 'Rack-A'
Set-ClusterFaultDomain -Name 'hci-n3' -Parent 'Rack-B'
Set-ClusterFaultDomain -Name 'hci-n4' -Parent 'Rack-B'

# Create the rack fault domains first
New-ClusterFaultDomain -Name 'Rack-A' -Type Rack
New-ClusterFaultDomain -Name 'Rack-B' -Type Rack

Rack/chassis awareness changes copy placement, which changes the minimum node count for a given resiliency: a three-way mirror surviving a rack loss needs its three copies in three racks — with only two racks, that’s impossible. Design fault domains against the failure unit you’re insuring, and confirm the node/rack count supports the resiliency you want.

Networking: RDMA, DCB, SET, and SMB Multichannel

S2D moves all east-west storage traffic over SMB3, the performance-critical path. It must run over RDMA (Remote Direct Memory Access) for low latency and CPU offload — without it, the CPU cost of moving storage between nodes at scale is crippling. There are two RDMA flavours, and the choice has real operational consequences.

RoCE vs iWARP

Aspect RoCEv2 (RDMA over Converged Ethernet) iWARP (RDMA over TCP)
Transport UDP/IP over lossless Ethernet TCP/IP (handles loss natively)
Switch config required Yes — PFC + DCB end to end (lossless) No — works over standard switches
Sensitivity to misconfig High — one drifted switch → lossy → latency Low — TCP retransmits transparently
Routability Routable (RoCEv2) Routable
Typical performance Marginally higher at scale when tuned Very close; simpler to run
Operational burden Higher (DCB/PFC is a discipline) Lower — the pragmatic default
Best for Teams with strong network engineering + DCB tooling Everyone who wants RDMA without DCB pain

The pragmatic take from production: iWARP unless you have a reason not to. RoCE’s marginal edge is real only when PFC/DCB is configured and stays configured end to end — and the most common S2D storage incident is a RoCE fabric that silently degrades to lossy after a switch firmware update or config drift, producing “slow storage” that chases the disk layer for weeks. iWARP has no switch DCB dependency, so there’s nothing to drift. If you inherit a RoCE cluster, treat the switch DCB state as part of the cluster and monitor RDMA error counters.

DCB / PFC for RoCE (only if you run RoCE)

RoCEv2 needs lossless Ethernet: Priority Flow Control (PFC) to prevent drops on the storage priority, and DCB (Data Center Bridging) to reserve bandwidth — identical on every NIC and every switch port in the storage path.

# --- RoCE only: configure DCB/PFC on each node (must match the physical switches) ---
Invoke-Command -ComputerName $nodes -ScriptBlock {
    # SMB Direct traffic on priority 3; a class for it
    New-NetQosPolicy -Name 'SMB'    -NetDirectPortMatchCondition 445 -PriorityValue8021Action 3
    New-NetQosPolicy -Name 'Cluster' -Cluster -PriorityValue8021Action 7

    # Enable PFC on priority 3 (the SMB Direct priority); disable on others
    Enable-NetQosFlowControl  -Priority 3
    Disable-NetQosFlowControl -Priority 0,1,2,4,5,6,7

    # Reserve 50% bandwidth for SMB Direct
    New-NetQosTrafficClass -Name 'SMB' -Priority 3 -BandwidthPercentage 50 -Algorithm ETS

    # Apply DCB to the storage NICs and turn on hardware flow control
    Enable-NetAdapterQos -InterfaceAlias 'SMB1','SMB2'
}

The knobs that must match host-and-switch, and what happens if they don’t:

DCB / PFC knob What it does Must match Symptom if mismatched
PFC priority (e.g. 3) Which 802.1p priority is lossless Host + every switch port RoCE falls back to lossy → retransmits, latency spikes
Traffic class bandwidth (ETS) Reserved % for SMB Direct Host + switch Storage starved under congestion
DCBX (exchange) mode How host/switch negotiate DCB Host + switch (or set static) Silent DCB drift after firmware update
Jumbo frames (MTU 9014) Larger frames, less overhead Host + switch end to end Fragmentation or blackholed frames
VLAN tagging on storage vNICs Isolates storage traffic Host vNIC + switch trunk Traffic on wrong class / no PFC

SET switch and storage vNICs

Most builds are converged: the same dual NICs carry storage and tenant/management traffic behind a Switch Embedded Teaming (SET) virtual switch. SET is the only supported teaming for S2D (classic LBFO isn’t supported for RDMA storage). Build the SET switch, add host vNICs for storage, and pin each vNIC to a physical adapter so each RDMA path stays on its own NIC:

Invoke-Command -ComputerName $nodes -ScriptBlock {
    # Create the SET switch across both storage NICs (no host OS adapter yet)
    New-VMSwitch -Name 'ConvergedSwitch' `
        -NetAdapterName 'SMB1','SMB2' `
        -EnableEmbeddedTeaming $true `
        -AllowManagementOS $false

    # Two host vNICs for SMB storage traffic
    Add-VMNetworkAdapter -ManagementOS -Name 'SMB_1' -SwitchName 'ConvergedSwitch'
    Add-VMNetworkAdapter -ManagementOS -Name 'SMB_2' -SwitchName 'ConvergedSwitch'

    # Pin each storage vNIC to a separate physical NIC (affinity → predictable RDMA paths)
    Set-VMNetworkAdapterTeamMapping -ManagementOS `
        -VMNetworkAdapterName 'SMB_1' -PhysicalNetAdapterName 'SMB1'
    Set-VMNetworkAdapterTeamMapping -ManagementOS `
        -VMNetworkAdapterName 'SMB_2' -PhysicalNetAdapterName 'SMB2'

    # Put the storage vNICs on their VLANs and enable RDMA on them
    Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName 'SMB_1' -Access -VlanId 711
    Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName 'SMB_2' -Access -VlanId 712
    Enable-NetAdapterRdma -Name 'vEthernet (SMB_1)','vEthernet (SMB_2)'
}

Confirm RDMA is actually live — the step everyone skips

This is where most “S2D is slow” tickets are resolved. Enabling RDMA on the adapter is not the same as RDMA being used for storage traffic:

# 1. Adapters report RDMA-capable AND enabled
Get-NetAdapterRdma | Select-Object Name, Enabled, MaxQueuePairs

# 2. SMB sees RDMA-capable interfaces
Get-SmbClientNetworkInterface | Where-Object RdmaCapable |
    Select-Object FriendlyName, RdmaCapable, RssCapable

# 3. THE proof: SBL (the S2D storage instance) is actively using RDMA
Get-SmbMultichannelConnection -SmbInstance SBL |
    Select-Object ServerName, ClientRdma, ServerRdma, ClientInterfaceIndex

# 4. Under load, SMB Direct connection count should be non-zero
Get-SmbConnection -SmbInstance SBL | Measure-Object   # activity present

# 5. RoCE only: verify DCB/PFC actually applied
Get-NetAdapterQos | Where-Object Enabled | Select-Object Name, OperationalFlowControl
Get-NetQosFlowControl

The three SMB instances and what each is for:

SMB instance Purpose Cmdlet flag What “no RDMA here” means
Default Normal SMB file shares, client traffic (none / default) Client SMB not on RDMA (often fine)
CSV Cluster Shared Volume redirected I/O -SmbInstance CSV CSV redirection not accelerated
SBL S2D east-west storage bus traffic -SmbInstance SBL The storage backplane isn’t on RDMA — fix immediately

RoCEv2 requires PFC and DCB consistent on both NICs and switches; without it end to end, RoCE falls back to lossy — retransmits and latency spikes with every disk still green. iWARP needs no switch DCB, which is why many teams prefer it. Whichever you run, Get-SmbMultichannelConnection -SmbInstance SBL showing ClientRdma/ServerRdma = True proves the storage bus is accelerated.

Finally, tell live migration to ride SMB (hence RDMA) with sane concurrency:

Invoke-Command -ComputerName $nodes -ScriptBlock {
    Set-VMHost -VirtualMachineMigrationPerformanceOption SMB `
        -MaximumVirtualMachineMigrations 2 `
        -MaximumStorageMigrations 2
}

Quorum and the witness on S2D

S2D uses the same cluster quorum model as any failover cluster, with one hard rule: the witness is never a disk. A cluster stays online only while it holds quorum — a majority of votes; each node gets one vote, a witness gets one as a tiebreaker.

Nodes Votes without witness Survives (no witness) Witness recommendation
2 2 0 node failures (50% is not a majority) Mandatory — else any reboot loses quorum
3 3 1 node failure Recommended (dynamic witness may not vote)
4 4 1 node failure (2 lost = 50%) Mandatory for the 2-down edge
5 5 2 node failures Recommended
8 8 3 node failures Recommended

Dynamic quorum recalculates the working majority as nodes leave gracefully, so a cluster can survive to a last node standing if nodes drop sequentially. Dynamic witness toggles the witness vote on/off to keep the total odd — why a 4-node cluster gives the witness a vote and an odd cluster may not. Both are on by default; understand them when reading logs, because the vote a node holds changes over time.

Dynamic quorum protects against sequential failures, not simultaneous ones. If two of four nodes die at the exact same instant before the cluster recomputes votes, you still lose quorum. Witness placement is what saves you in that window.

Witness type selection on S2D:

Witness type What it is Use when On S2D?
Cloud witness A tiny blob in an Azure Storage account Default; branch/stretch; internet egress exists Yes — preferred
File-share witness (SMB) A share on a separate independent server Air-gapped / no internet egress Yes (never on a cluster node)
Disk witness A shared LUN Classic shared-storage clusters No — there is no shared disk

Validate, create, and enable S2D — the controlled sequence

Never enable S2D without a clean validation and a staged build. The sequence: install features → validate → create the cluster with no storage → configure the witness → enable S2D → verify cache → create volumes. One-shot builds hide which step failed.

Step 1 — Features on every node

$nodes = 'hci-n1','hci-n2','hci-n3','hci-n4'

Invoke-Command -ComputerName $nodes -ScriptBlock {
    Install-WindowsFeature -Name Failover-Clustering, FS-FileServer, Hyper-V, Data-Center-Bridging `
        -IncludeManagementTools -Restart:$false
}
# Reboot nodes that requested it (Hyper-V install typically does), then continue.

Step 2 — Validate with Test-Cluster (the report Microsoft support demands)

Test-Cluster with the Storage Spaces Direct suite exercises S2D readiness — identical drive firmware, unclaimed drives, consistent NIC drivers, network communication. Treat every Failed as a hard stop and review every Warning.

Test-Cluster -Node $nodes `
    -Include 'Storage Spaces Direct','Inventory','Network','System Configuration' `
    -ReportName 'C:\ClusterReports\precheck'
# Open the generated HTML report. Any 'Failed' = do not proceed.

The validation checks that matter most for S2D, and what a failure means:

Validation check What it verifies Why it matters for S2D Failure means
List All Disks / firmware Identical drive models + firmware across nodes S2D needs matched drives for predictable behaviour Firmware drift → unpredictable pool behaviour
Disks eligible for pooling All capacity drives unclaimed (no partitions) Leftover partitions block pooling Clear stale disks before clustering
Network communication Nodes reach each other on all networks SSB traffic needs the network to work Fix connectivity before enabling S2D
RDMA / NIC consistency Same NIC drivers/firmware, RDMA present Storage bus rides RDMA RDMA not present → S2D unsupported/slow
System configuration OS build, domain membership, roles S2D needs Datacenter, domain join, matched OS Edition/domain mismatch blocks build
Storage Spaces Direct suite End-to-end S2D readiness The definitive go/no-go Any failure = don’t enable S2D

If a drive shows a stale partition table from a prior build, clear it before clustering — destructive, triple-check the host list:

Invoke-Command -ComputerName $nodes -ScriptBlock {
    Get-Disk | Where-Object { $_.IsSystem -eq $false -and $_.IsBoot -eq $false } |
        Clear-Disk -RemoveData -RemoveOEM -Confirm:$false -ErrorAction SilentlyContinue
}

Step 3 — Create the cluster with -NoStorage

Create the cluster without claiming storage, so S2D can claim drives in a controlled later step. Supply a static management IP:

New-Cluster -Name 'hci-clus01' `
    -Node $nodes `
    -StaticAddress '10.20.0.50' `
    -NoStorage

Step 4 — Configure the witness (cloud or file share)

# Cloud witness (preferred): needs a storage account name + one access key
Set-ClusterQuorum -Cluster 'hci-clus01' `
    -CloudWitness `
    -AccountName 'sthciwitness01' `
    -AccessKey '<storage-account-key>' `
    -Endpoint 'core.windows.net'

# OR — air-gapped: a file-share witness on an INDEPENDENT server (never a node)
Set-ClusterQuorum -Cluster 'hci-clus01' `
    -FileShareWitness '\\fsw-srv01\hci-clus01-witness'

Confirm quorum and dynamic behaviour:

Get-ClusterQuorum -Cluster 'hci-clus01' | Format-List *
(Get-Cluster).DynamicQuorum   # expect 1
Get-ClusterNode | Select-Object Name, State, DynamicWeight, NodeWeight
# DynamicWeight = vote the node currently holds; NodeWeight = eligible to vote at all. Both 1 = healthy.

Step 5 — Enable Storage Spaces Direct

One cmdlet claims eligible drives, builds the pool, and (with mixed media) auto-binds the fastest device as cache:

Enable-ClusterStorageSpacesDirect -CimSession 'hci-clus01' -Confirm:$false

Verify the pool and the cache bindings:

Get-StoragePool -IsPrimordial $false |
    Select-Object FriendlyName, Size, AllocatedSize, HealthStatus

# Cache drives show Usage=Journal; capacity drives show Usage=Auto-Select
Get-PhysicalDisk |
    Select-Object FriendlyName, MediaType, Usage, Size, HealthStatus |
    Sort-Object Usage

Step 6 — Create volumes with the right resiliency and reserve

Create volumes as CSV ReFS so every node can access them concurrently. PhysicalDiskRedundancy 2 is what makes a mirror three-way (two copies can be lost):

# Three-way mirror volume for VMs (production default)
New-Volume -FriendlyName 'vm-prod-01' `
    -FileSystem CSVFS_ReFS `
    -StoragePoolFriendlyName 'S2D on hci-clus01' `
    -ResiliencySettingName 'Mirror' `
    -PhysicalDiskRedundancy 2 `
    -Size 4TB

# Mirror-accelerated parity volume for bulk/archive (mirror tier + parity tier)
New-Volume -FriendlyName 'archive-01' `
    -FileSystem CSVFS_ReFS `
    -StoragePoolFriendlyName 'S2D on hci-clus01' `
    -StorageTierFriendlyNames 'Performance','Capacity' `
    -StorageTierSizes 1TB, 9TB

# STOP allocating once free space ≈ one capacity drive per node remains (the reserve)

The New-Volume parameters that define resiliency, decoded:

Parameter Values Effect Gotcha
FileSystem CSVFS_ReFS (use this) / CSVFS_NTFS ReFS = integrity, tiering, block clone NTFS loses S2D’s key features
ResiliencySettingName Mirror / Parity Mirror (copies) vs parity (erasure code) Parity has a write penalty
PhysicalDiskRedundancy 1 (2-way) / 2 (3-way / dual parity) Number of failures tolerated 2 = three-way mirror or dual parity
StorageTierFriendlyNames Performance,Capacity Builds a MAP volume (mirror + parity tiers) Sizes the mirror vs parity split
StorageTierSizes e.g. 1TB,9TB Mirror-tier size, parity-tier size Under-size mirror → writes hit parity
Size e.g. 4TB Usable size of a single-tier volume Counts against reserve planning

Deploy clustered roles

With storage and networking in place, deploy workloads onto CSV paths.

Hyper-V VMs as clustered roles

New-VM -Name 'app-vm-01' `
    -MemoryStartupBytes 16GB `
    -Generation 2 `
    -Path 'C:\ClusterStorage\vm-prod-01' `
    -NewVHDPath 'C:\ClusterStorage\vm-prod-01\app-vm-01\disk0.vhdx' `
    -NewVHDSizeBytes 128GB

# Make it highly available (clustered)
Add-ClusterVirtualMachineRole -VirtualMachine 'app-vm-01' -Cluster 'hci-clus01'

Scale-Out File Server (SOFS) for app data

For SQL-over-SMB or Hyper-V-over-SMB, a Scale-Out File Server gives active-active share access from every node:

Add-ClusterScaleOutFileServerRole -Name 'sofs-app01' -Cluster 'hci-clus01'
New-SmbShare -Name 'sqldata' -Path 'C:\ClusterStorage\vm-prod-01\Shares\sqldata' `
    -FullAccess 'CONTOSO\SQLEngineAccounts' -ContinuouslyAvailable $true

Tune failover policy

The key knobs: how many failures are tolerated in a window before the cluster gives up, and whether a role fails back automatically. Leave failback disabled in most cases — it can move a VM back to a healthy-but-cold-cache node:

$grp = Get-ClusterGroup -Name 'app-vm-01'
$grp.FailoverThreshold = 2      # max failovers ...
$grp.FailoverPeriod    = 6      # ... within this many hours
$grp.AutoFailbackType  = 0      # 0 = no failback (prevents ping-pong / cold-cache moves)

The failover-policy knobs and how to reason about them:

Setting What it controls Default Recommended Why
FailoverThreshold Max failovers before giving up (per role) 2 Stops an endlessly-flapping role thrashing the cluster
FailoverPeriod Window (hours) for the threshold 6 6 Pairs with threshold
AutoFailbackType Auto-return to preferred owner 0 (no) 0 (no) Failback moves warm workload to a cold-cache node
Preferred owners Where a role should land none Set for placement Balances load; keeps affinity
PersistentState Restore role state on cluster start On On VMs come back after a full outage

Patching: Cluster-Aware Updating and storage maintenance mode

Patching is where the quorum design and storage discipline earn their keep. Cluster-Aware Updating (CAU) orchestrates a rolling update: drain one node, maintenance its storage, patch, reboot, return it, wait for resync, move to the next — the cluster stays online throughout. Enable self-updating mode to patch on a schedule:

Add-CauClusterRole -ClusterName 'hci-clus01' `
    -DaysOfWeek Sunday `
    -WeeksOfMonth 2 `
    -IntervalWeeks 4 `
    -MaxFailedNodes 1 `
    -MaxRetriesPerNode 2 `
    -EnableFirewallRules `
    -Force

Patch on demand:

Invoke-CauRun -ClusterName 'hci-clus01' `
    -CauPluginName 'Microsoft.WindowsUpdatePlugin' `
    -MaxFailedNodes 1 -MaxRetriesPerNode 2 -Force

For manual maintenance (firmware, hardware, driver swaps), you must pause S2D on the node first — the single most important S2D operational habit. Skipping it triggers a full resync of everything the node held the moment it goes down. The correct sequence:

# 1. Drain clustered roles off the node
Suspend-ClusterNode -Name 'hci-n2' -Drain

# 2. Put the node's STORAGE into maintenance mode (this is the step people skip)
Get-StorageFaultDomain -Type StorageScaleUnit |
    Where-Object FriendlyName -eq 'hci-n2' |
    Enable-StorageMaintenanceMode

# 3. ... do the work: firmware, driver, reboot ...

# 4. Take storage OUT of maintenance mode
Get-StorageFaultDomain -Type StorageScaleUnit |
    Where-Object FriendlyName -eq 'hci-n2' |
    Disable-StorageMaintenanceMode

# 5. Resume the node and let it re-absorb roles (no auto-failback)
Resume-ClusterNode -Name 'hci-n2' -Failback Immediate

# 6. WAIT for resync to finish before touching the next node
Get-StorageJob   # must be empty (or only 'Regeneration' completing) before proceeding

Why storage maintenance mode matters:

Without maintenance mode With maintenance mode
Node’s drives go “missing” abruptly on reboot S2D knows the drives are temporarily offline, not failed
S2D may start rebuilding missing copies elsewhere S2D holds off rebuild; data is expected back shortly
Node returns → full resync storm of everything it held Node returns → only the delta (writes during the window) resyncs
Storage latency spikes for the whole cluster Minimal, brief resync of just the changes
Longer time-to-healthy; higher risk in the window Fast return to healthy

Always confirm storage is fully healthy (Get-StorageJob empty) before touching the next node. Patching a second node while the first is still resyncing puts two fault domains in flux at once — the exact condition three-way mirror protects you from, squandered.

Health, faults, and storage jobs — reading the machine

S2D exposes its state through health faults, physical-disk states, virtual-disk states, and storage jobs — reading each is the difference between a five-minute triage and a two-hour guess.

Health faults

# The cluster's own health report — run this first when something's wrong
Get-StorageSubSystem -FriendlyName 'Clustered*' | Debug-StorageSubSystem
Get-StorageSubSystem -FriendlyName 'Clustered*' | Get-StorageHealthReport
Get-HealthFault   # active faults with descriptions + recommended actions

Physical disk states

PhysicalDisk state / property Meaning What to do
HealthStatus = Healthy Normal Nothing
HealthStatus = Warning Predictive failure or transient Investigate; plan replacement
HealthStatus = Unhealthy Failed or failing Replace; S2D rebuilds from copies
OperationalStatus = OK Fully operational Nothing
OperationalStatus = Lost Communication Drive not reachable Check cabling/slot; may be transient
OperationalStatus = Removing from Pool Being retired A retire operation is in progress
Usage = Journal Bound as cache Expected on cache drives
Usage = Auto-Select Capacity drive Expected on capacity drives
Usage = Retired Drive retired from pool Rebuild happened / in progress; replace

Virtual disk (volume) states

VirtualDisk state Meaning VMs affected? Action
HealthStatus = Healthy / OperationalStatus = OK Full resiliency No Nothing
OperationalStatus = Degraded A copy is missing (drive/node down) but data is available No (still serving) Let rebuild run; watch Get-StorageJob
OperationalStatus = InService Repair/rebuild in progress No Wait for completion
OperationalStatus = Detached Volume offline Yes — outage Too many fault domains lost; recover a node/drive
HealthStatus = Unhealthy Cannot maintain resiliency Possibly Investigate faults; add capacity

Storage jobs — how you watch a rebuild

# Every async pool operation (repair, regeneration, rebalance) with % complete
Get-StorageJob | Select-Object Name, JobState, PercentComplete, BytesProcessed, BytesTotal
Storage job name What it is When it runs
Repair / Regeneration Rebuilding missing copies after a drive/node loss After a failure or drive replacement
Rebalance Redistributing data evenly across drives/nodes After adding drives/nodes; periodic
Optimize Optimizing data placement (parity/MAP) Background / on demand
Retiring Draining a drive being retired During Set-PhysicalDisk -Usage Retired

Drive failure and node failure: the exact flows

This is what you’re really buying S2D for — and where knowing the flow command-by-command turns a 3 a.m. panic into a checklist.

A single drive fails

Step What S2D does What you see Your action
1 Detects the drive is unhealthy/lost Get-PhysicalDisk shows HealthStatus=Unhealthy Confirm which drive, which node
2 Marks affected virtual disks Degraded (still serving) Get-VirtualDisk shows Degraded No panic — data is available
3 Automatically starts a repair job to rebuild copies onto surviving drives (using the reserve) Get-StorageJob shows Repair/Regeneration Ensure reserve capacity exists
4 Restores full resiliency once repair completes Virtual disks return to Healthy/OK Now safe to replace the physical drive
5 You physically replace the drive; S2D auto-adds it and rebalances New drive Usage=Auto-Select; Rebalance job Confirm rebalance completes

Locate and replace a failed drive:

# Identify the failed drive and blink its slot LED so the tech pulls the right one
$bad = Get-PhysicalDisk | Where-Object HealthStatus -ne 'Healthy'
$bad | Select-Object FriendlyName, SerialNumber, MediaType, HealthStatus, OperationalStatus
$bad | Enable-PhysicalDiskIndication

# If S2D hasn't already retired it, retire it explicitly (drains its data)
$bad | Set-PhysicalDisk -Usage Retired

# After replacement the new drive auto-joins; confirm and rebalance
Get-PhysicalDisk | Where-Object Usage -eq 'Auto-Select' | Measure-Object
Get-StoragePool -IsPrimordial $false | Optimize-StoragePool
Get-StorageJob   # watch the rebalance complete

A node fails (unplanned)

Step What S2D / cluster does What you see Your action
1 Cluster detects the node down; VMs fail over to survivors Get-ClusterNode shows node Down; VMs restart elsewhere Confirm VMs are up on other nodes
2 Quorum recomputed via dynamic quorum + witness Get-ClusterQuorum; cluster stays online Confirm quorum held (no event 1177)
3 Virtual disks go Degraded (a copy is on the down node) Get-VirtualDisk shows Degraded Data still served from surviving copies
4 S2D waits before rebuilding (node may return soon) — configurable No immediate huge resync Decide: bring node back, or force rebuild
5a Node returns → only the delta resyncs Resume node; brief resync Resume-ClusterNode; watch Get-StorageJob
5b Node is dead for good → S2D rebuilds full copies onto survivors (needs capacity) Large repair job Ensure survivors have room; or add a node
# Confirm the cluster survived
Get-ClusterNode | Select-Object Name, State, StatusInformation
Get-ClusterQuorum | Format-List *
Get-VirtualDisk | Select-Object FriendlyName, HealthStatus, OperationalStatus

# Node coming back: resume it (delta resync only)
Resume-ClusterNode -Name 'hci-n3' -Failback Immediate
Get-StorageJob   # small resync

# Node permanently dead: evict it, then S2D rebuilds full copies onto survivors' free space
Remove-ClusterNode -Name 'hci-n3' -Cluster 'hci-clus01' -Force
Get-StorageJob   # larger rebuild — ensure reserve/free space exists!

The reason three-way mirror is the production default is here: while a node is down (fault domain 1), a drive failure on a second node (fault domain 2) still leaves a third copy — on two-way mirror that same second failure is data loss. And the reason reserve capacity is mandatory is step 5b: a permanent node loss must rebuild full copies into free space, and without reserve the pool is stuck degraded.

Architecture at a glance

Picture the reference four-node cluster as three stacked planes that every I/O passes through. The compute plane is the four Hyper-V hosts running your VMs; each VM’s VHDX lives on a CSV path like C:\ClusterStorage\vm-prod-01, and because CSV presents the volume to every node at once, a VM can live-migrate between hosts without moving a byte of storage. Below it, the storage plane is a single pool spanning all four nodes: each node contributes local NVMe (bound as cache, Usage=Journal) and SSD/HDD (bound as capacity, Usage=Auto-Select), and from that pool you carve resilient virtual disks — a three-way mirror for busy VMs (three copies, one per node, survives two non-concurrent node losses) and a mirror-accelerated-parity volume for bulk data (mirror tier for hot writes, parity tier for the cold majority). Threading the two together is the network plane: the Software Storage Bus carrying east-west traffic over SMB3 + RDMA across dual 25 GbE NICs behind a SET switch. Off to the side sits the cloud (or file-share) witness — one vote that keeps quorum sane on the even node count, never a shared disk because there is none.

Follow one write to see the whole system move: a VM on node 1 issues a 64 KB write; ReFS stamps an integrity checksum; Spaces maps its three copies to drives on nodes 1, 2, 3; the SSB ships two copies over RDMA to the peers; each node’s SBL cache (NVMe) durably acknowledges; once a quorum of copies is safe in cache, the write is acknowledged — at NVMe-cache latency, not HDD latency — and de-stages to capacity later. Now overlay failure: pull a capacity drive and the affected virtual disk goes Degraded while a repair job rebuilds the missing copy into reserve space, VMs untouched; drop a whole node and dynamic quorum plus the witness keep the cluster online, VMs restart on survivors, and volumes stay Degraded (not Detached) because a third copy always survives. That’s the entire mental model — compute on top, one pooled storage layer beneath, an RDMA bus stitching them, a witness holding the tie — and every performance number and failure outcome in this article falls out of it.

Real-world scenario

Meridian Health Systems ran clinical VMs — an EHR database, PACS imaging file servers, and ~110 supporting VMs — on an aging Fibre Channel SAN nearing end of support, a steep renewal quote and a single blast radius. The infrastructure team of five moved to a four-node S2D cluster: each node 2 × 1.6 TB NVMe cache + 8 × 3.84 TB SSD capacity (~123 TB raw), dual 25 GbE NICs, Windows Server 2022 Datacenter. They chose three-way mirror for the EHR and general VMs and a mirror-accelerated-parity volume for PACS (a large, mostly-cold dataset with active ingest). The build went in clean — Test-Cluster passed, New-Cluster -NoStorage, a cloud witness, then Enable-ClusterStorageSpacesDirect — sized to ~26 TB usable after reserving one capacity drive per node. RDMA was iWARP, a deliberate call to avoid DCB risk on a small team.

Then the first patch cycle delivered the first self-inflicted lesson. An engineer rebooted hci-n2 for firmware without Enable-StorageMaintenanceMode. The drives went missing abruptly, S2D began treating them as failed, and when the node returned sixty seconds later it triggered a full resync of everything it held. Storage p99 write latency spiked from sub-millisecond to 35–50 ms for ~20 minutes; the EHR team logged a “slowness” ticket. Nothing was broken — every disk Healthy, quorum held, no data lost — but the resync storm was avoidable. The fix was procedural: a runbook that always drains the node, enables storage maintenance mode, does the work, disables it, resumes, and waits for Get-StorageJob to clear before the next node — plus Cluster-Aware Updating in self-updating mode so the sequence couldn’t be skipped.

The second lesson was capacity discipline. Six months in, PACS growth had quietly filled the pool to ~92%, eating the reserve. When a capacity SSD failed on hci-n4, the repair couldn’t fully rebuild — no free space to recreate the missing copies — and the pool sat Degraded with a stalled repair. They freed space by archiving cold imaging, expanded capacity, and added a hard alert at 80% utilization plus a synthetic check on Get-StorageJob duration. The cluster has since survived a genuine dead-node event (a failed mainboard on hci-n1) with zero VM data loss — VMs restarted on survivors, volumes went Degraded not Detached, and the delta resynced cleanly. The lesson on the wall: “S2D doesn’t fail like a SAN — it fails in resiliency math and maintenance discipline. Reserve the rebuild space, pause storage before every reboot, and never patch the next node until the last one is fully healthy.”

The incident timeline, because the order is the lesson:

Time Event Action taken Effect What it should have been
Week 0 S2D build Test-Cluster → NoStorage → cloud witness → Enable-S2D Clean cluster, iWARP RDMA live (correct)
Week 6 First patch Reboot hci-n2 without storage maintenance mode 20-min resync storm, 35–50 ms latency Drain + Enable-StorageMaintenanceMode first
Week 6 Post-mortem Wrote runbook; adopted CAU self-updating Skips became impossible (the fix)
Month 6 Pool at 92% Capacity SSD fails on hci-n4 Repair stalls — no reserve to rebuild into Never exceed ~80%; reserve 1 drive/node
Month 6 Recovery Free space, expand capacity Repair completes; healthy Alert at 80% earlier
Month 9 Dead node hci-n1 mainboard dies VMs restart on survivors, 0 data loss (three-way mirror worked as designed)

Advantages and disadvantages

S2D’s model — pooled local drives, resilience in software over an RDMA fabric — both earns HCI’s benefits and creates its distinctive failure modes. Weigh it honestly:

Advantages Disadvantages
No SAN — commodity servers, storage is local drives pooled in software; huge capex and skills saving The network is the storage backplane — a degraded RDMA/DCB fabric is degraded storage even with every disk green
Node fault tolerance — three-way mirror survives two node losses; a dead node loses no data Usable ≪ raw — 33% efficiency on three-way mirror; sizing math surprises SAN-trained teams
NVMe cache hides HDD/SSD latency — SSD-class write latency on cheap bulk capacity Reserve capacity is mandatory and invisible — fill the pool and the next failure can’t rebuild
CSV + live migration — every node sees every volume; VMs migrate with no storage move Maintenance discipline is unforgiving — skip storage maintenance mode and a reboot causes a resync storm
ReFS integrity + tiering — bit-rot auto-repair from a good copy; block clone; MAP tiering Datacenter edition + matched hardware — firmware/driver parity across nodes is a hard requirement
CAU rolling patches — the cluster patches itself with no downtime RoCE fragility — great when tuned, but DCB drift silently degrades storage; iWARP trades a little perf for sanity
Scales from 2 to 16 nodes — start with a branch pair, grow to a datacentre cluster One pool per cluster — you don’t get SAN-style multi-tenant array partitioning

The model is right when you want SAN-grade resilience without a SAN, run Hyper-V/SQL, and have the discipline (or automation) to reserve rebuild space and pause storage before reboots. It bites hardest on teams that treat it like a SAN — over-fill the pool, patch without maintenance mode, run RoCE without owning DCB, or pick two-way mirror to “save space.” Every disadvantage above is manageable — but only if you know it exists, which is the point of this article.

Hands-on lab

You can’t do a real four-node build without hardware, but you can build a working S2D cluster in nested virtualization on a single Hyper-V host (or an Azure VM that supports nested virt, e.g. Dv3/Ev3). This lab stands up a 2-node cluster with virtual data disks, enables S2D, creates a mirror volume, and injects a drive failure to watch the repair — the exact flow, at zero hardware cost. Run from an elevated PowerShell on the Hyper-V host.

Nested S2D is for learning the cmdlets and flows only — not supported for production, and the “RDMA” is emulated. The value is muscle memory on the exact commands.

Step 1 — Create two nested VMs with data disks (on the Hyper-V host).

$vms = 'lab-s2d-1','lab-s2d-2'
foreach ($v in $vms) {
    New-VM -Name $v -MemoryStartupBytes 4GB -Generation 2 -Path 'D:\LabVMs' `
        -NewVHDPath "D:\LabVMs\$v\os.vhdx" -NewVHDSizeBytes 60GB
    Set-VM -Name $v -ProcessorCount 4
    # Nested virtualization + MAC spoofing (needed inside the guest cluster)
    Set-VMProcessor -VMName $v -ExposeVirtualizationExtensions $true
    Get-VMNetworkAdapter -VMName $v | Set-VMNetworkAdapter -MacAddressSpoofing On
    # Four "capacity" data disks per node
    1..4 | ForEach-Object {
        $p = "D:\LabVMs\$v\data$_.vhdx"
        New-VHD -Path $p -SizeBytes 30GB -Dynamic | Out-Null
        Add-VMHardDiskDrive -VMName $v -Path $p
    }
}

Install Windows Server 2022 Datacenter in each VM and join them to a test domain (S2D requires domain membership). Expected: two VMs, each with four unformatted 30 GB data disks.

Step 2 — Inside the guests: install features (run against both nodes).

$nodes = 'lab-s2d-1','lab-s2d-2'
Invoke-Command -ComputerName $nodes -ScriptBlock {
    Install-WindowsFeature Failover-Clustering, FS-FileServer -IncludeManagementTools
}

Expected: Success = True, RestartNeeded = No for the clustering feature.

Step 3 — Validate and create the cluster with no storage.

Test-Cluster -Node $nodes -Include 'Storage Spaces Direct','Inventory','Network','System Configuration' `
    -ReportName 'C:\ClusterReports\lab'
New-Cluster -Name 'lab-clus' -Node $nodes -StaticAddress '10.99.0.50' -NoStorage

Expected: a validation report (review it), then a cluster object lab-clus with both nodes Up.

Step 4 — Configure a file-share witness (simplest for a lab).

# Create a share on a separate machine (your host or a third VM), then:
Set-ClusterQuorum -Cluster 'lab-clus' -FileShareWitness '\\host\lab-witness'
Get-ClusterQuorum | Format-List *

Expected: QuorumResource shows the file-share witness; (Get-Cluster).DynamicQuorum = 1.

Step 5 — Enable S2D and create a two-way mirror volume.

Enable-ClusterStorageSpacesDirect -CimSession 'lab-clus' -Confirm:$false
Get-StoragePool -IsPrimordial $false | Select-Object FriendlyName, Size, HealthStatus

# Two-node cluster → two-way mirror (PhysicalDiskRedundancy 1)
New-Volume -FriendlyName 'lab-vol' -FileSystem CSVFS_ReFS `
    -StoragePoolFriendlyName 'S2D on lab-clus' `
    -ResiliencySettingName 'Mirror' -PhysicalDiskRedundancy 1 -Size 20GB
Get-VirtualDisk | Select-Object FriendlyName, ResiliencySettingName, HealthStatus, OperationalStatus

Expected: a pool spanning both nodes (HealthStatus=Healthy), and a volume mounted at C:\ClusterStorage\lab-vol with HealthStatus=Healthy.

Step 6 — Inject a drive failure and watch the repair.

# On the Hyper-V host: yank a data disk from node 1 to simulate a drive failure
Get-VMHardDiskDrive -VMName 'lab-s2d-1' | Select-Object -First 1 | Remove-VMHardDiskDrive

# Inside the cluster: observe the effect
Get-PhysicalDisk | Where-Object HealthStatus -ne 'Healthy'      # the "failed" drive
Get-VirtualDisk  | Select-Object FriendlyName, OperationalStatus # expect Degraded (still serving)
Get-StorageJob                                                   # a repair may run if capacity allows

Expected: one physical disk unhealthy/lost, the virtual disk Degraded (not Detached — data still available), and the volume still mounted and readable. Validation: you’ve proven that a drive loss degrades but doesn’t down the volume, and you drove every core cmdlet (Enable-ClusterStorageSpacesDirect, New-Volume, Get-PhysicalDisk, Get-VirtualDisk, Get-StorageJob) on the exact flow you’d run in production.

Step 7 — Teardown.

# Inside: remove the cluster
Remove-Cluster -Cluster 'lab-clus' -Force -CleanupAD
# On the host: delete the lab VMs and their disks
$vms | ForEach-Object { Stop-VM $_ -TurnOff -Force -EA SilentlyContinue; Remove-VM $_ -Force }
Remove-Item 'D:\LabVMs' -Recurse -Force

The lab steps mapped to what each proves:

Step What you did What it proves Production analogue
3 Test-Cluster + New-Cluster -NoStorage The controlled build sequence Never skip validation; stage the build
4 File-share witness Even-node clusters need a witness Cloud witness in prod
5 Enable-ClusterStorageSpacesDirect + New-Volume Pool + resilient CSV ReFS volume creation The core S2D enablement
6 Remove a disk; observe Degraded Drive loss degrades, doesn’t down, the volume The 3 a.m. drive-failure reality

Common mistakes & troubleshooting

The failure modes that actually bite, as a scannable table first, then the detail for the ones that hurt most:

# Symptom Root cause Confirm (exact cmd) Fix
1 Cluster loses quorum on first patch reboot No witness on an even-node (2/4) cluster Get-ClusterQuorum shows no witness Set-ClusterQuorum -CloudWitness / -FileShareWitness
2 “S2D is slow”, latency spikes, disks all healthy RDMA/DCB fabric degraded (RoCE lossy or path down) Get-SmbMultichannelConnection -SmbInstance SBL (RDMA=False); Get-NetAdapterStatistics RDMA errors Fix DCB/PFC on drifted switch; or move to iWARP
3 Reboot causes a huge resync storm Skipped Enable-StorageMaintenanceMode before reboot Get-StorageJob full of Regeneration after reboot Always maintenance-mode storage before reboots
4 Volume Detached, VMs down Too many fault domains lost (2-way mirror + node down + drive) Get-VirtualDisk shows Detached Recover a node/drive; use three-way mirror
5 Drive failed but pool can’t rebuild — stuck Degraded No reserve capacity (pool too full) Get-StoragePool free < 1 cap drive/node Free space / add drives; reserve going forward
6 Cache drive shows as capacity or missing Cache device not detected / wrong media type Get-PhysicalDisk no Usage=Journal Check firmware; force -CacheDeviceModel
7 Enable-ClusterStorageSpacesDirect fails Drives not eligible (stale partitions) or validation not run Re-run Test-Cluster -Include 'Storage Spaces Direct' Clear-Disk stale drives; fix validation failures
8 Node won’t rejoin / storage not returning Node resumed before disabling maintenance mode Get-StorageFaultDomain shows node in maintenance Disable-StorageMaintenanceMode then resume
9 Pool utilization creeping to 90%+ No capacity monitoring; growth unchecked Get-StoragePool AllocatedSize/Size Alert at 80%; expand or archive
10 Live migration slow / not using RDMA Migration not set to SMB, or RDMA not live Get-VMHost migration option; SBL RDMA check Set-VMHost -VirtualMachineMigrationPerformanceOption SMB
11 Two-way mirror volume offline during patching 2-way mirror can’t tolerate a fault during maintenance Get-VirtualDisk Detached during a node reboot Rebuild as three-way mirror
12 Event 1135 / 1177 in the log Node removed from membership / cluster lost quorum Get-WinEvent .../FailoverClustering/Operational Fix networking/witness; 1177 = quorum design flaw
13 New drive added but capacity not usable Rebalance/optimize not run Get-PhysicalDisk new drive Auto-Select but pool not rebalanced Optimize-StoragePool
14 ReFS/CSV “redirected I/O” warnings A node lost direct storage access; I/O redirected over network Get-ClusterSharedVolume state Restore the node’s storage/network path

The expanded reasoning for the ones that cost the most time:

2. “S2D is slow” with every disk healthy — the RDMA/DCB trap. Root cause: The storage bus rides RDMA; if a RoCE fabric drifts to lossy (a switch firmware update reset DCB/PFC) or a storage path drops, latency spikes on the storage bus while every physical disk reads Healthy. Confirm: Get-SmbMultichannelConnection -SmbInstance SBL shows ClientRdma/ServerRdma = False or fewer connections than expected; Get-NetAdapterStatistics | Select Name, RdmaConnectionErrors climbs during spikes; Get-NetQosFlowControl / Get-NetAdapterQos show PFC not applied. Fix: Reapply DCB/PFC to the drifted switch and pin switch firmware in the change baseline; monitor RDMA error counters and PFC pause-frame asymmetry. If DCB discipline is a recurring problem on a small team, iWARP removes the switch dependency entirely.

3. A reboot causes a huge resync storm. Root cause: The node was rebooted without Enable-StorageMaintenanceMode, so S2D saw its drives vanish abruptly and, on return, resynced everything the node held instead of just the delta. Confirm: Get-StorageJob is full of Regeneration jobs immediately after a reboot; storage latency spikes cluster-wide. Fix: Always drain the node and enable storage maintenance mode before any reboot; disable it after; wait for Get-StorageJob to clear before the next node. Use CAU so this can’t be skipped.

5. A drive failed but the pool can’t rebuild — stuck Degraded. Root cause: No reserve capacity — the pool was too full for S2D to rebuild the missing copies into free space. Confirm: Get-StoragePool -IsPrimordial $false shows free space less than one capacity drive per node; the repair Get-StorageJob stalls. Fix: Free space (archive/delete) or add drives so the rebuild can complete; going forward, reserve one capacity drive’s worth of free space per node and alert at 80% utilization.

12. Events 1135 / 1177 in the FailoverClustering log. Root cause: 1135 = a node was removed from active membership (usually a network/communication problem); 1177 = the cluster lost quorum (the one you never want — a quorum design or witness failure); 1069/1146 = resource/role failures. Confirm: Get-WinEvent -LogName 'Microsoft-Windows-FailoverClustering/Operational' -MaxEvents 100 | Where LevelDisplayName -in 'Error','Warning'. Fix: For 1135, fix the storage/heartbeat network; for 1177, fix the quorum design (add/repair the witness) — 1177 almost always means the vote math was wrong for the failure that occurred.

Best practices

The alerts worth wiring before the next incident — leading indicators, not “cluster down”:

Alert on Signal Threshold (starting point) Why it’s leading
Pool utilization AllocatedSize/Size > 80% Predicts “no reserve to rebuild” before a drive fails
Rebuild duration Get-StorageJob running > N hours > 4–6 h A stalled/slow repair means the resiliency window is open
Virtual disk health OperationalStatus != OK any Degraded > 1 h Catches an open resiliency gap
Physical disk health HealthStatus != Healthy any A failing drive before it fully dies
RDMA errors RdmaConnectionErrors (NIC stats) any sustained rise Fabric degrading before latency spikes
Node state Get-ClusterNode State any not Up Membership loss before quorum is at risk
Quorum events Event 1135 / 1177 any 1177 1177 = you’re one failure from an outage

Security notes

Cost & sizing

The bill and sizing math differ from a SAN because you buy servers, not an array — and usable capacity is a fraction of raw that depends on the resiliency choice.

For the reference 4-node cluster: ~123 TB raw → ~26 TB usable on three-way mirror after reserve (mix in a MAP volume to reclaim efficiency for cold data). The dominant recurring cost is Datacenter licensing per core across all four nodes; hardware is capex amortized over the refresh cycle. The cost/sizing drivers:

Driver What you pay for / plan Rough figure What it buys Watch-out
Windows Server Datacenter Per physical core, all cores, per node Datacenter premium ×4 nodes S2D itself (Datacenter-only) Core-count licensing adds up on big sockets
Capacity drives Raw × efficiency − reserve = usable ~26 TB usable (3-way, this cluster) Your actual storage Usable ≪ raw; don’t size on raw
Cache (NVMe) 2+ per node, performance layer Not usable capacity Latency / working-set cache Size to working set; not “free space”
RDMA NICs + switches Dual 25 GbE + (DCB) switches Part of build capex The storage backplane RoCE switches cost more + DCB discipline
Reserve capacity ~1 capacity drive per node unallocated ~16 TB “lost” here Rebuild room after a failure Not optional; deducted up front
Growth Add nodes/drives incrementally Linear Capacity + compute + fault domains Rebalance after adding

Interview & exam questions

1. What is Storage Spaces Direct and how does it differ from a SAN-based cluster? S2D pools the local drives of every cluster node into a single pool and mirrors/erasure-codes data across nodes over RDMA — there is no shared SAN. A SAN-based cluster uses external shared storage and a disk witness; an S2D cluster has no shared disk, so it uses a cloud or file-share witness and provides node fault tolerance in software.

2. Explain two-way vs three-way mirror, and why two-way is risky in production. Two-way keeps 2 copies (50% efficiency) and survives one node/drive loss; three-way keeps 3 (33%) and survives two non-concurrent losses. Two-way has no margin for a fault during maintenance — a drive failing while a node reboots can drop slabs to zero copies and take the volume offline. Use three-way for production; two-way only on genuine 2-node clusters.

3. What is the cache tier, what binds to it, and what does it cache? With mixed media, S2D binds the fastest class (NVMe, then SSD) as cache (Usage=Journal) in front of the slower capacity class. On NVMe+HDD or SSD+HDD it’s a read+write cache; on NVMe+SSD it’s write-only; on all-NVMe/all-SSD there’s no cache tier by default. It absorbs writes and serves hot reads — a performance layer, not extra usable capacity.

4. Why enable storage maintenance mode before rebooting an S2D node? Without it, S2D sees the drives vanish abruptly and, on return, triggers a full resync of everything the node held — a cluster-wide latency storm. Enable-StorageMaintenanceMode marks the drives temporarily offline, so S2D holds off rebuilding and resyncs only the delta on return.

5. What is reserve capacity and what happens without it? Free pool space (guidance: one capacity drive per node) kept so S2D can rebuild missing copies after a failure. Without it, a failure leaves the pool degraded with no path to healthy — nowhere to recreate the copies. It’s deducted from usable capacity up front, not discovered later.

6. Why does an S2D cluster need a witness, and why can’t it be a disk? Even-node clusters (2 or 4) split votes 50/50 when a node is lost — not a majority — so they lose quorum without a witness tiebreaker. It can’t be a disk witness because there is no shared disk; use a cloud witness (Azure blob) or a file-share witness on an independent server.

7. RoCE vs iWARP — the operational trade-off? Both deliver RDMA. RoCEv2 needs lossless Ethernet (PFC + DCB) end to end; DCB drift silently degrades storage. iWARP runs over standard TCP with no switch DCB requirement — slightly less peak performance, far less operational risk. Pick iWARP unless you have strong network engineering and DCB tooling.

8. What is mirror-accelerated parity and when do you use it? A single ReFS volume with a mirror tier (fast writes) and a parity tier (efficient cold storage); ReFS rotates cold data down to parity. It gives near-mirror write performance for hot data and near-parity efficiency for the cold majority — ideal for an active write front over a large cold tail (file servers, backup targets). Size the mirror tier for the write working set or writes hit parity speed.

9. A four-node three-way-mirror cluster has a node down for patching, then a drive fails on a second node. What happens? The cluster survives with no data loss: three copies across three nodes means with one node down (copy 1 gone) and a drive failing elsewhere (copy 2 gone), a third copy survives. Volumes go Degraded, not Detached. On two-way mirror the same sequence is data loss — which is why three-way is the production default.

10. How do you validate and build an S2D cluster in the correct order? Install-WindowsFeature on all nodes → Test-Cluster -Include 'Storage Spaces Direct',... and fix every failure → New-Cluster -NoStorage → configure the witnessEnable-ClusterStorageSpacesDirect → verify cache → New-Volume -FileSystem CSVFS_ReFS ... with correct resiliency and reserve. Staging it (-NoStorage, then enabling S2D separately) makes each step verifiable.

11. What file system do S2D volumes use and why does it matter? ReFS on CSV (CSVFS_ReFS): integrity streams (auto bit-rot repair from a good copy), block cloning (fast VHDX operations), and real-time tiering for MAP; CSV makes the volume accessible from every node at once (live migration, SOFS). NTFS throws away integrity streams and tiering — ReFS is required.

12. Which cluster event IDs matter most on S2D? 1135 — node removed from membership (usually network/heartbeat). 1177 — cluster lost quorum (a quorum/witness design failure; the one you never want). 1069/1146 — resource/role failures. A 1177 almost always means the vote math was wrong for the failure that occurred — fix the witness/quorum design.

These map to Windows Server Hybrid Administrator (AZ-800/AZ-801)manage Windows Server storage, high availability, and failover clustering, including S2D, CSV, quorum, and CAU — and general Windows Server storage/HA competency. A compact cert-mapping for revision:

Question theme Primary exam Objective area
S2D architecture, pool, cache AZ-800 Manage Windows Server storage / HCI
Resiliency, fault domains, sizing AZ-800 Implement Storage Spaces Direct
Quorum, witness, dynamic quorum AZ-801 Implement failover clustering
CAU, maintenance mode, patching AZ-801 Manage failover clustering / updates
RDMA/SET networking AZ-800/AZ-801 Configure cluster networking
Stretch clusters (Storage Replica) AZ-801 Implement disaster recovery

Quick check

  1. Your four-node cluster has no witness. A single node reboots for a patch. What happens to the cluster, and what’s the fix?
  2. On an NVMe + HDD S2D deployment, which drives are cache and which are capacity, and does the cache serve reads, writes, or both?
  3. True or false: to fix an S2D pool that’s Degraded after a drive failure and can’t rebuild, you should scale out by adding another node’s worth of volumes.
  4. You need to reboot a node for firmware. List the correct sequence of S2D-specific steps before and after the reboot.
  5. Every disk in the pool reads Healthy, but storage latency is spiking under load and users complain. On a RoCE-based cluster, what do you check first?

Answers

  1. With no witness, a four-node cluster drops to exactly 50% of votes when one node is lost — not a majority — so the cluster loses quorum and goes offline. The fix is to configure a cloud witness or file-share witness (Set-ClusterQuorum -CloudWitness ...), which adds a tiebreaker vote so the survivors hold a majority. Never a disk witness on S2D.
  2. The NVMe drives are cache (Usage=Journal) and the HDDs are capacity (Usage=Auto-Select). On NVMe+HDD the cache serves both reads and writes — it absorbs writes (write-back) and serves hot reads, hiding HDD latency in both directions.
  3. False. Scaling out volumes does nothing for a stuck rebuild. The pool can’t rebuild because there’s no free space (reserve) to rebuild into — the fix is to free space or add drives/capacity so the repair can recreate the missing copies, and to reserve one capacity drive per node going forward.
  4. Suspend-ClusterNode -Drain <node> (move roles off) → Enable-StorageMaintenanceMode on that node’s StorageScaleUnit (so S2D holds off rebuilding) → do the firmware/reboot → Disable-StorageMaintenanceModeResume-ClusterNodewait for Get-StorageJob to clear before touching the next node.
  5. The RDMA/DCB fabric, not the disks. Check Get-SmbMultichannelConnection -SmbInstance SBL for ClientRdma/ServerRdma = True, and Get-NetAdapterStatistics | Select Name, RdmaConnectionErrors for rising errors during the spikes — a RoCE switch whose DCB/PFC drifted (e.g. after a firmware update) makes the storage bus lossy while every disk stays green. Reapply DCB/PFC (or move to iWARP).

Glossary

Next steps

You can now design, build, and operate a hyper-converged S2D cluster and reason about its failures. Build outward:

Failover ClusteringStorage Spaces DirectS2DWindows ServerHyper-Converged InfrastructureReFSRDMAPowerShell
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading