Caricamento in corso

[Microsoft] DFSR ConflictAndDeleted riempie il disco: cause e pulizia

Un file server in replica DFSR inizia a consumare spazio disco in modo anomalo: centinaia di gigabyte che spariscono giorno dopo giorno, senza che nessuno abbia caricato dati nuovi. Le share sono grandi come sempre grazie alle quote impostate, eppure il volume si avvicina pericolosamente alla saturazione. Questa guida ripercorre l’intera indagine, dalla scoperta del sintomo fino alla causa radice e alla pulizia completa, con tutti i comandi PowerShell usati, ed è pensata come riferimento pratico per chiunque gestisca un’infrastruttura DFS Replication e si trovi davanti allo stesso comportamento.

Lo scenario

L’ambiente è composto da due file server Windows in DFS Replication con configurazione active/passive:

  • FS01 — nodo attivo, quello su cui scrivono tutti gli utenti tramite il namespace DFS
  • FS02 — nodo passivo, riceve la replica ed è impostato come target offline nel namespace (vi si accede solo puntandolo direttamente per nome o IP, cosa che nel funzionamento normale non accade)

Entrambi hanno un volume dati E: da circa 1,5 TB, che ospita nove cartelle replicate corrispondenti alle share aziendali.

Il sintomo che fa scattare l’allarme: lo spazio libero del disco E: cala di centinaia di GB in pochi giorni, arrivando a poche decine di GB liberi su un volume da 1,5 TB. Un file server quasi pieno è già di per sé un rischio operativo serio, quando lo spazio si azzera, le scritture si bloccano e le share diventano inutilizzabili. Ma la cosa che non torna è che gli utenti non hanno caricato nulla di nuovo e le quote impostate impediscono che ciò si possa verificare: i dati reali sono stabili, eppure il disco si riempie.

Primo passo: capire cosa occupa realmente lo spazio

La domanda da porsi subito è: i dati degli utenti stanno crescendo davvero, oppure lo spazio se lo mangia qualcos’altro?

Il controllo dello spazio sui volumi:

Get-Volume | Where-Object DriveLetter | Sort-Object DriveLetter |
  Format-Table DriveLetter, FileSystemLabel,
    @{N='SizeGB';E={[math]::Round($_.Size/1GB,0)}},
    @{N='FreeGB';E={[math]::Round($_.SizeRemaining/1GB,0)}} -Auto

E soprattutto la quantità di dati realmente modificati per giorno, che raggruppa i file per data di ultima modifica e somma i GB:

Get-ChildItem -Path E:\ -Recurse -File -ErrorAction SilentlyContinue |
  Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-20) } |
  Group-Object { $_.LastWriteTime.Date } |
  ForEach-Object {
    [PSCustomObject]@{
      Giorno  = ($_.Name -as [datetime]).ToString('yyyy-MM-dd')
      NumFile = $_.Count
      TotGB   = [math]::Round((($_.Group | Measure-Object Length -Sum).Sum)/1GB,2)
    }
  } | Sort-Object Giorno | Format-Table -Auto

Nel caso reale il risultato è stato illuminante: i file effettivamente modificati dagli utenti erano appena 0,1–2 GB al giorno. Un abisso rispetto alle centinaia di GB di spazio che sparivano. Questo esclude che il problema siano i dati: qualcos’altro sta scrivendo massicciamente sul volume, in un’area che non sono le share visibili agli utenti.

Dove guardare: la cartella nascosta di DFSR

Su un file server in DFS Replication, l’area “di servizio” che può crescere a dismisura è la cartella nascosta System Volume Information\DFSR. Al suo interno vivono, per ogni cartella replicata, la staging area (l’area di transito per i file in replica) e la ConflictAndDeleted (dove finiscono i file in conflitto o cancellati).

Misura di Staging e ConflictAndDeleted per ogni replicated folder (richiede PowerShell come Amministratore per leggere System Volume Information):

Get-ChildItem 'E:\System Volume Information\DFSR\Private' -Recurse -Directory -EA SilentlyContinue |
  Where-Object { $_.Name -eq 'ConflictAndDeleted' -or $_.Name -eq 'Staging' } |
  ForEach-Object {
    $s = (Get-ChildItem $_.FullName -Recurse -File -EA SilentlyContinue | Measure-Object Length -Sum).Sum
    [PSCustomObject]@{ Tipo=$_.Name; GB=[math]::Round($s/1GB,2); Folder=$_.Parent.Name }
  } | Where-Object { $_.GB -gt 0.1 } | Sort-Object GB -Descending | Format-Table -Auto

Nel caso reale è emerso subito il colpevole: una ConflictAndDeleted da 353 GB su una singola cartella replicata. Le staging erano normali (pochi GB ciascuna). Tutto lo spazio mancante era finito lì.

Cos’è la ConflictAndDeleted e perché era esplosa

Quando DFSR rileva un conflitto, lo stesso file modificato su entrambi i membri prima che la replica converga, o processa una cancellazione, non elimina subito il file “perdente”: lo sposta, compresso, nella cartella ConflictAndDeleted come rete di sicurezza. In condizioni normali questa cartella ha una quota contenuta e si autopulisce. Se però qualcosa genera conflitti in massa in modo continuo, cresce senza sosta fino a occupare centinaia di GB.

Il conteggio degli eventi di conflitto lo conferma:

Get-WinEvent -LogName 'DFS Replication' -MaxEvents 200 |
  Where-Object { $_.Id -in 4412,4413,2104,2106,4202,4204 } |
  Group-Object Id | Sort-Object Count -Descending | Format-Table Count, Name -Auto

L’Event ID 4412 Ã¨ la firma del problema: “un file è stato modificato su più server, l’algoritmo di risoluzione conflitti ha scelto il vincitore, il perdente è stato spostato nella Conflict and Deleted folder”. Nel caso reale, quasi tutti gli ultimi eventi erano 4412.

Per vedere quali file entrano in conflitto (il messaggio contiene il percorso completo e i member ID coinvolti):

Get-WinEvent -LogName 'DFS Replication' -MaxEvents 10 -EA SilentlyContinue |
  Where-Object { $_.Id -eq 4412 } |
  Select-Object -First 5 | ForEach-Object { $_.Message; "----------------" }

La svolta: perché nascevano i conflitti

Il punto chiave è capire perché si generano conflitti “changed on multiple servers”. In una configurazione active/passive, sul nodo passivo non scrive nessuno, quindi conflitti di questo tipo non dovrebbero mai esistere. Eppure c’erano, a centinaia.

Il primo sospetto. qualcuno che scrive direttamente sul nodo passivo bypassando il namespace. è stato escluso verificando le sessioni SMB su FS02: pochissime connessioni, nessun file aperto nelle cartelle in conflitto. Non era un utente. È stato utile anche confermare come risolve il namespace, per verificare che i client non potessero atterrare sul nodo passivo:

Get-DfsnFolderTarget -Path '\\dominio\namespace\Uffici' |
  Format-Table TargetPath, State, ReferralPriorityClass -Auto

Il target del nodo passivo risultava correttamente Offline: nessun client poteva scrivervi tramite DFS.

La risposta era negli eventi di sistema del nodo passivo FS02:

(Get-CimInstance Win32_OperatingSystem).LastBootUpTime
Get-WinEvent -LogName System -FilterXPath "*[System[EventID=6008 or EventID=41 or EventID=6005]]" -MaxEvents 20 |
  Select-Object TimeCreated, Id, LevelDisplayName | Format-Table -Auto

Il log ha rivelato un Event ID 41 (Kernel-Power, Critical) seguito da 6008 (arresto imprevisto del sistema): FS02 aveva subito uno spegnimento anomalo (dirty shutdown) in una data precisa, la stessa in cui lo spazio disco aveva iniziato a scendere.

La catena completa degli eventi:

  1. FS02 (nodo passivo) subisce un arresto anomalo, senza spegnimento pulito.
  2. Al riavvio, il database DFSR di FS02 non era stato chiuso correttamente ed entra in dirty shutdown recovery.
  3. In questa modalità DFSR non si fida più del proprio database e deve ri-confrontare tutti i file con il partner FS01.
  4. Durante la riconciliazione, moltissimi file risultano “cambiati su entrambi i server”, anche file vecchissimi. Ne nasce una valanga di conflitti (eventi 4412), il cui perdente finisce nella ConflictAndDeleted.
  5. La ConflictAndDeleted si gonfia senza sosta fino a centinaia di GB, consumando tutto lo spazio libero del volume.

Non era un attacco, non erano gli utenti, non era un processo applicativo. Era un dirty shutdown del nodo passivo che aveva mandato DFSR in una riconciliazione di massa.

La risoluzione, passo per passo

1. Verificare che la recovery sia conclusa

Prima di intervenire, controllare che DFSR abbia smesso di generare conflitti. Se l’ultimo evento 4412 risale a diverse ore prima e non ne nascono di nuovi, la riconciliazione è terminata:

Get-WinEvent -LogName 'DFS Replication' -MaxEvents 50 |
  Where-Object { $_.Id -eq 4412 } |
  Select-Object -First 5 TimeCreated | Format-Table -Auto

Per vedere l’andamento giorno per giorno (se il ritmo dei conflitti cala, la recovery si sta esaurendo):

Get-WinEvent -LogName 'DFS Replication' -MaxEvents 1000 -EA SilentlyContinue |
  Where-Object { $_.Id -eq 4412 } |
  Group-Object { $_.TimeCreated.Date } |
  Sort-Object Name |
  Format-Table @{N='Giorno';E={($_.Name -as [datetime]).ToString('dd/MM')}}, Count -Auto

2. Verificare che la replica sia allineata (no backlog)

Il backlog indica quanti file restano da replicare tra i due nodi. Deve essere zero in entrambi i versi:

dfsrdiag Backlog /RGName:"dominio\namespace\uffici" /RFName:"uffici" /SMem:FS01 /RMem:FS02
dfsrdiag Backlog /RGName:"dominio\namespace\uffici" /RFName:"uffici" /SMem:FS02 /RMem:FS01

Il risultato atteso è “No Backlog – member is in sync with partner”. Se invece esce un backlog elevato che non si smaltisce, la replica non converge e va valutato un authoritative sync (dichiarare il nodo con i dati buoni come autorità e ri-sincronizzare l’altro da zero).

3. Svuotare la ConflictAndDeleted

Questa è l’operazione che libera lo spazio. Non va fatta cancellando i file a mano, corromperebbe il database DFSR, ma con il metodo WMI ufficiale, che agisce solo sulla cartella di scarto e non tocca i dati reali:

foreach ($n in 'Uffici','Tecnico') {
  $rf = Get-WmiObject -Namespace "root\microsoftdfs" -Class DfsrReplicatedFolderInfo |
    Where-Object { $_.ReplicatedFolderName -eq $n }
  if ($rf) { "Cleanup $n -> $($rf.CleanupConflictDirectory().ReturnValue)" }
}

Un ReturnValue pari a 0 indica successo. La cancellazione fisica avviene in background: lo spazio libero risale gradualmente nei minuti successivi, comportamento del tutto normale, la misura della cartella si stabilizza prima che NTFS abbia finito di rilasciare i blocchi.

Va eseguita su entrambi i nodi. Per pulire tutte le cartelle replicate in un colpo, si estende il ciclo a tutti i nomi delle folder.

4. Verificare l’integrità dei dati

Rassicurazione fondamentale: la pulizia della ConflictAndDeleted non tocca i dati veri. Lo si conferma confrontando il contenuto delle share sui due nodi:

Get-ChildItem 'E:\Uffici' -Directory | Measure-Object | Select-Object Count
(Get-ChildItem 'E:\Uffici' -Recurse -File -EA SilentlyContinue | Measure-Object Length -Sum).Sum/1GB

Numero di cartelle e GB devono coincidere (a meno di pochi GB di file in transito) su entrambi i server.

Pulizia opzionale: le cartelle DFSR orfane

Un file server con una lunga storia di re-init e recovery può accumulare, dentro System Volume Information\DFSR\Private, cartelle appartenenti a repliche dismesse o a member ID non più esistenti. Occupano spazio ma non sono referenziate da DFSR attivo, quindi il cleanup WMI non le tocca. Nel caso reale erano decine, per diverse decine di GB.

Per identificarle si confrontano i GUID delle cartelle su disco con i GUID delle folder realmente attive. I GUID attivi vanno letti dal database DFSR (fonte autorevole, indipendente dalle dimensioni su disco: un filtro basato sulle dimensioni rischierebbe di nascondere folder attive momentaneamente vuote):

$vivi = Get-WmiObject -Namespace "root\microsoftdfs" -Class DfsrReplicatedFolderConfig |
  ForEach-Object { $_.ReplicatedFolderGuid.ToLower().Trim('{}').Substring(0,8) } | Sort-Object -Unique
$vivi

Poi si elencano le cartelle orfane (quelle il cui GUID non è tra gli attivi):

$attivi = $vivi
$base = 'E:\System Volume Information\DFSR\Private'
$orfane = Get-ChildItem $base -Directory -EA SilentlyContinue |
  Where-Object { $attivi -notcontains $_.Name.Substring(1,8).ToLower() }
"Orfane da rimuovere: $($orfane.Count)"
$orfane | ForEach-Object { $_.Name }

Verifica di sicurezza obbligatoria prima di cancellare: la lista dei GUID vivi nel database che non compaiono tra gli attivi deve essere vuota. Garantisce che la lista degli attivi è completa e che non si sta per rimuovere una folder in uso.

$vivi | Where-Object { $attivi -notcontains $_ }   # deve restituire nulla

Solo a quel punto si procede, con il servizio DFSR fermo:

Stop-Service DFSR -Force
Start-Sleep 5
foreach ($o in $orfane) {
  try { Remove-Item $o.FullName -Recurse -Force -EA Stop; "Rimossa: $($o.Name)" }
  catch { "ERRORE $($o.Name): $_" }
}
Start-Service DFSR

Alcune cartelle DFSR possono avere percorsi che superano il limite dei 260 caratteri di Windows, facendo fallire Remove-Item con un fuorviante “Could not find a part of the path”. In quel caso si svuotano con robocopy, sincronizzando sopra una cartella vuota, poi si rimuove il guscio:

$empty = 'E:\_empty_tmp'
New-Item -ItemType Directory -Path $empty -Force | Out-Null
robocopy $empty 'E:\System Volume Information\DFSR\Private\{GUID-orfano}' /MIR /R:1 /W:1 | Out-Null
Remove-Item 'E:\System Volume Information\DFSR\Private\{GUID-orfano}' -Recurse -Force
Remove-Item $empty -Recurse -Force

Questa pulizia è ottimizzazione, non emergenza: le cartelle orfane sono statiche e non contribuiscono alla crescita anomala dello spazio.

Verifiche finali prima di considerare chiuso

Dopo ogni stop/start di DFSR, il nodo partner logga temporaneamente errori di comunicazione (Event ID 5002 ed 5014): sono lo strascico normale del riavvio e si fermano non appena il servizio torna su. Da non confondere con un problema reale. La conferma che la connessione è stata ristabilita è l’Event ID 5004.

Controllo che non nascano errori nuovi dopo il riavvio:

Get-WinEvent -LogName 'DFS Replication' -MaxEvents 30 |
  Where-Object { $_.Id -in 5002,5014,5008 } |
  Select-Object TimeCreated, Id, LevelDisplayName |
  Sort-Object TimeCreated -Descending | Select-Object -First 5 | Format-Table -Auto

Controllo che il servizio sia ripartito con il database consistente — devono comparire solo eventi informativi di inizializzazione (1002, 1004, 1206, 1210, 4102, 4104), nessun 2212 (dirty shutdown DB) o 2214 (rebuild del DB):

Get-WinEvent -LogName 'DFS Replication' -MaxEvents 8 |
  Select-Object TimeCreated, Id, LevelDisplayName | Format-Table -Auto

La prova definitiva che la replica funziona end-to-end è un file di test. Sul nodo attivo:

"test $(Get-Date -Format HH:mm:ss)" | Out-File 'E:\Uffici\_test_replica.txt'

Dopo circa un minuto, sul nodo passivo:

Get-Content 'E:\Uffici\_test_replica.txt' -EA SilentlyContinue

Se il contenuto appare, la replica è viva e funzionante. Si cancella poi il file dal nodo attivo (la cancellazione si propagherà anche sul passivo).

Prevenzione: evitare la ricaduta

Il punto cruciale da comprendere è che DFSR non era difettoso: ha semplicemente reagito, come previsto, a un evento a monte. La causa radice era il crash del nodo passivo. Finché non si elimina l’innesco, il rischio di ricaduta resta. I presidi consigliati:

  • Indagare la causa del dirty shutdown. Sul lato hypervisor vanno esaminati gli eventi della macchina nella fascia oraria del crash, insieme alle attività di manutenzione e backup di quella sera: uno snapshot o un consolidation andato male è una delle cause più frequenti di arresto anomalo di una macchina virtuale.
  • Tenere il volume dati con margine di spazio libero. Un volume vicino alla saturazione trasforma un evento gestibile in un’emergenza: nel caso reale, il disco quasi pieno non aveva dove assorbire i 353 GB di ConflictAndDeleted, e questo ha reso ogni operazione più difficile. Mantenere almeno il 15–20% libero.
  • Monitorare il backlog e la ConflictAndDeleted. Un controllo automatico giornaliero, con alert al superamento di una soglia, permette di accorgersi di un’anomalia in poche ore invece che dopo giorni di crescita silenziosa dello spazio occupato.

Riferimento rapido: gli Event ID DFSR utili

  • 41 / 6008 (log System del nodo) — arresto anomalo / spegnimento imprevisto: l’innesco di tutto
  • 4412 — file in conflitto spostato nella ConflictAndDeleted: la firma del problema
  • 4413 — file rimossi dalla ConflictAndDeleted durante la pulizia
  • 2212 / 2214 — database DFSR in dirty shutdown / in ricostruzione: da NON vedere dopo un riavvio pulito
  • 5002 / 5014 — errori/avvisi di comunicazione col partner: normali come strascico di uno stop/start
  • 5004 — comunicazione col partner ristabilita
  • 1206 / 4104 — servizio e replicated folder inizializzati correttamente

In sintesi

Un file server che consuma spazio senza motivo apparente, con i dati utente fermi ma il disco che si riempie, ha portato, seguendo la discrepanza tra dati reali e spazio occupato, a scoprire una ConflictAndDeleted esplosa a 353 GB, generata da una riconciliazione di massa di DFSR dopo l’arresto anomalo del nodo passivo. La chiave della diagnosi è stata non fermarsi al sintomo, ma inseguire il numero che non tornava: pochi GB di dati nuovi a fronte di centinaia di GB spariti. Da lì la catena dirty shutdown → recovery DFSR → conflitti → ConflictAndDeleted è emersa con chiarezza, e la soluzione, svuotare la cartella di scarto, verificare l’integrità e la convergenza, ripulire gli orfani, è risultata pulita e a rischio zero sui dati.