Windows Server 2016 : stopper les pauses GC et les recyclages IIS d’une API .NET (Déduplication, VSS, GC .NET)

Des recyclages IIS fréquents et des pauses GC peuvent ruiner l’expérience utilisateur sur une API .NET. Voici un plan d’action concret pour stabiliser Windows Server 2016 : piloter la déduplication, isoler VSS et affiner le GC .NET afin d’éliminer les pointes CPU/E/S et les redémarrages inopinés.

Sommaire

Contexte et symptômes

Votre API .NET s’exécute sur six hôtes Windows Server 2016 derrière un load‑balancer. Les processus IIS (w3wp.exe) atteignent un seuil mémoire d’environ 15 Go et sont recyclés, ce qui provoque des déconnexions et des indisponibilités ponctuelles. Les analyses montrent des cycles de Garbage Collection (GC) très fréquents, mais la source du GC n’est pas toujours claire : s’agit‑il du GC du CLR (.NET) ou du GC du service de déduplication/VSS (stockage) ?

Vue d’ensemble des mesures recommandées

AxeMesures proposéesEffet recherché
Service de déduplication• Laisser la GC « normale » hebdomadaire (paramètre par défaut).
• Lancer la Full GC uniquement à la demande :
Start-DedupJob <volume> -Type GarbageCollection -Full
• Bloquer l’exécution automatique de la Full GC :
DeepGCInterval = 0xffffffff dans :
HKLM\SYSTEM\CurrentControlSet\Services\ddpsvc\Settings
(ou HKLM\CLUSTER\Dedup en cluster).
Éviter les pics d’E/S et de CPU dus à la Full GC, qui déclenchent ralentissements et recyclages.
VSS (Volume Shadow Copy)Déplacer la zone de diff (« shadow storage ») sur un volume dédié via vssadmin.Limiter les suppressions de snapshots qui entraînent du ménage intensif et aggravent fragmentation/mémoire.

Diagnostic : identifier la source des pauses

Avant toute action, distinguez les trois familles d’événements :

  • GC .NET : cycles du CLR qui compactent/collectent les générations 0/1/2 et le LOH (Large Object Heap).
  • GC Déduplication : tâches de maintenance du service Data Deduplication (dont la Full GC ou Deep GC), très intensives en CPU/E/S.
  • Activité VSS : création/suppression de snapshots qui déclenchent du nettoyage sur le stockage.

Compteurs PerfMon à surveiller

CatégorieCompteurs clésInterprétation
.NET CLR Memory (instance w3wp)% Time in GC, Gen 2 Collections, Allocated Bytes/sec, # Bytes in all Heaps, LOH sizeSi % Time in GC et Gen 2 s’envolent, la pression mémoire applicative est réelle.
Process (w3wp) & MemoryPrivate Bytes, Working Set, Committed Bytes, Available MBytesCorréler la montée des Private Bytes au timing des recyclages IIS.
Data DeduplicationCompteurs Jobs, Chunk Store, Garbage Collection (file d’attente, débit)Un pic côté déduplication pendant les incidents oriente vers la Full GC du stockage.
VSSÉvénements de création/suppression de clichés, latence disqueSuppression massive de clichés = ménage et E/S soutenues.

Plan d’action priorisé

Bloquer et piloter la Full GC de déduplication

Sur des volumes applicatifs très sollicités, la Full GC du service de déduplication (ddpsvc) peut provoquer des tempêtes d’E/S et un CPU élevé. Le principe est de :

  1. Empêcher son déclenchement automatique.
  2. La lancer manuellement et nœud par nœud pendant une fenêtre de maintenance (avec drainage du trafic par le load‑balancer).

Désactivation de l’exécution automatique

Définissez la valeur DeepGCInterval à 0xffffffff (DWORD) pour bloquer la Full GC automatique :

reg add HKLM\SYSTEM\CurrentControlSet\Services\ddpsvc\Settings /v DeepGCInterval /t REG_DWORD /d 0xffffffff /f

En environnement cluster :

reg add HKLM\CLUSTER\Dedup /v DeepGCInterval /t REG_DWORD /d 0xffffffff /f

Bonnes pratiques : consigner le changement (Change Advisory), redémarrer le service ddpsvc si nécessaire et documenter le rollback (revenir à l’intervalle par défaut).

Lancer une Full GC manuelle et contrôlée

Sur un seul nœud à la fois, drainé du load‑balancer :

# Exemple : lancer une Full GC sur le volume E:
Start-DedupJob E: -Type GarbageCollection -Full

# Pour suivre la progression

Get-DedupJob 

Attendez la fin, effectuez une vérification métier basique, puis réadmettez le nœud dans le pool et passez au nœud suivant. Cette orchestration évite un impact global.

Isoler la zone de diff VSS sur un volume dédié

Placez le « shadow storage » sur un disque distinct (idéalement rapide) pour éviter que les suppressions de clichés ne perturbent les volumes applicatifs.

# Visualiser la configuration actuelle
vssadmin list shadowstorage

# Définir une zone dédiée (ex. : clichés du volume E: stockés sur V:)

vssadmin add shadowstorage /for=E: /on=V: /maxsize=200GB

# Adapter la taille si nécessaire

vssadmin resize shadowstorage /for=E: /on=V: /maxsize=300GB 

Dimensionnez la zone de diff selon la fréquence des sauvegardes, la volumétrie et la fenêtre de rétention des clichés.

Optimiser le GC .NET et les recyclages IIS

L’objectif est de réduire la pression GC applicative tout en évitant les recyclages par seuil mémoire trop agressif.

Activer le GC serveur et la GC en arrière‑plan

Dans web.config ou app.config :

&lt;configuration&gt;
  &lt;runtime&gt;
    &lt;gcServer enabled="true"/&gt;
    &lt;gcConcurrent enabled="true"/&gt;
  &lt;/runtime&gt;
&lt;/configuration&gt;

Pourquoi : le GC serveur parallélise les collectes et exploite mieux les cœurs, avantageux sous charge.

Réduire l’impact des pauses pendant les pics

Autour d’événements critiques (pics d’appels, batchs synchrones), utilisez un mode de latence adapté :

using System;
using System.Runtime;
using (new SustainedLowLatencyScope())
{
    // Code critique (ex. : traitement temps réel)
    // ...
}

public sealed class SustainedLowLatencyScope : IDisposable
{
private readonly GCLatencyMode _previous = GCSettings.LatencyMode;
public SustainedLowLatencyScope()
=> GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency;
public void Dispose()
=> GCSettings.LatencyMode = _previous;
} 

Réservez ce mode aux fenêtres ciblées ; en continu, il peut augmenter la pression mémoire.

Gérer le Large Object Heap (LOH) avec parcimonie

Les allocations > 85 Ko vont au LOH. Si la fragmentation devient un problème, vous pouvez, à froid (fenêtre de maintenance), demander une compaction :

using System.Runtime;
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();

À éviter en pleine charge, car la compaction peut rallonger une pause.

Repenser les seuils de recyclage IIS

Paramètre App PoolRecommandationCommande/appcmd
Recyclage par mémoire privéeÉviter 15 Go arbitraires ; commencer par 0 (illimité) ou relever le seuil après observation.appcmd set apppool "ApiPool" /recycling.periodicRestart.privateMemory:0
Recyclage périodique (minutes)Désactiver le timer par défaut, planifier des heures creuses spécifiques.appcmd set apppool "ApiPool" /recycling.periodicRestart.time:00:00:00
Plages horaires cibléesUn recyclage contrôlé, 1 fois/jour max, hors charge, après vidange des sessions.appcmd set apppool "ApiPool" /+recycling.periodicRestart.schedule.[value='02:30:00']
Mode de démarrageAlwaysRunning + preloadEnabled pour réduire l’impact au redémarrage.appcmd set apppool "ApiPool" /startMode:AlwaysRunning
appcmd set app "Default Web Site/Api" /preloadEnabled:true

Astuce : testez l’élévation du seuil mémoire par paliers (ex. 22 Go → 28 Go) et corrélez au % Time in GC et au Gen 2.

Mettre à jour le système et le runtime

  • Appliquer les derniers Cumulative Updates Windows Server 2016.
  • Utiliser un runtime récent (.NET Framework 4.8 ou .NET 6/7 LTS) lorsque possible ; les optimisations GC y sont plus efficientes (compaction, background GC, LOH).

Désactiver la déduplication si le gain est marginal

Sur un volume applicatif avec peu de fichiers redondants, la déduplication apporte peu mais coûte en maintenance. Mesurez le taux de gain ; s’il est faible, envisagez la désactivation :

# Vérifier l’état
Get-DedupVolume -Volume E:

# Désactiver (après validation et sauvegarde)

Disable-DedupVolume -Volume E: 

Dans certains contextes, retirer la fonctionnalité FS‑Data‑Deduplication du rôle de fichiers peut simplifier l’exploitation (décision à cadrer selon vos exigences de stockage).

Méthodologie de déploiement sans interruption

  1. Pré‑production : reproduire la charge (JMeter, k6…), capturer latence p95/p99, % GC, CPU, E/S.
  2. Canari : appliquer le changement sur 1 nœud (drainé LB), valider fonctionnel, remettre en pool.
  3. Montée en charge : étendre à 2 → 3 → 6 nœuds en surveillant l’APM et les histogrammes de latence.

Observabilité et corrélations utiles

SignalOù regarderCe que cela indique
Spikes CPU + disques saturésPerfMon, logs déduplicationTâche GarbageCollection de dédup en cours.
% Time in GC > 20–30 % soutenu.NET CLR Memory (w3wp)Pression mémoire applicative, allocations massives/LOH.
Suppression en rafale de clichésVSS + monitoring sauvegardeZone de diff trop petite ou co‑localisée sur un volume chaud.
Recyclages corrélés aux picsLogs IIS, Event Viewer (App Pool)Seuil mémoire/recycle timer trop agressif.

Runbook d’incident : quoi faire pendant la crise

  1. Isoler : sortir 1 nœud du pool LB (drain), vérifier CPU/disque et % Time in GC.
  2. Confirmer la cause : si dédup en Full GC, laisser terminer sur ce nœud drainé ; si GC .NET, inspecter Allocated Bytes/sec et rechigner aux recyclages immédiats sauf fuite manifeste.
  3. Stabiliser : élever temporairement le seuil mémoire de l’App Pool ou le mettre à 0, désactiver les recyclages périodiques.
  4. Remédiation : déplacer le shadow storage VSS, bloquer la Full GC automatique, planifier une maintenance.
  5. Retour à la normale : réadmettre le nœud, supervision renforcée 24–48 h.

Exemples de scripts PowerShell prêts à l’emploi

Désactiver l’exécution automatique de la Full GC (déduplication)

$path = "HKLM:\SYSTEM\CurrentControlSet\Services\ddpsvc\Settings"
New-Item -Path $path -Force | Out-Null
New-ItemProperty -Path $path -Name "DeepGCInterval" -Value 0xffffffff -PropertyType DWord -Force | Out-Null
Restart-Service ddpsvc -Force
Write-Host "Full GC automatique désactivée."

Lancer une Full GC manuelle pendant une fenêtre

param(
  [Parameter(Mandatory=$true)][string]$Volume = "E:"
)
Write-Host "Démarrage Full GC sur $Volume"
Start-DedupJob $Volume -Type GarbageCollection -Full
do {
  $job = Get-DedupJob | Where-Object { $_.Type -eq "GarbageCollection" -and $_.Volume -eq $Volume }
  Start-Sleep -Seconds 15
  Write-Host ("Progression&nbsp;: {0}&nbsp;%" -f $job.Progress) 
} while ($job -and $job.Status -eq "Running")
Write-Host "Full GC terminée."

Déplacer la zone de diff VSS vers un volume dédié

param(
  [string]$For = "E:",
  [string]$On = "V:",
  [string]$Max = "200GB"
)
vssadmin add shadowstorage /for=$For /on=$On /maxsize=$Max
vssadmin list shadowstorage

Réglages IIS essentiels

$pool = "ApiPool"
& "$env:windir\system32\inetsrv\appcmd.exe" set apppool $pool /recycling.periodicRestart.privateMemory:0
& "$env:windir\system32\inetsrv\appcmd.exe" set apppool $pool /recycling.periodicRestart.time:00:00:00
& "$env:windir\system32\inetsrv\appcmd.exe" set apppool $pool /startMode:AlwaysRunning
# Exemple d'ajout d'une fenêtre de recycle contrôlée à 02h30
& "$env:windir\system32\inetsrv\appcmd.exe" set apppool $pool "/+recycling.periodicRestart.schedule.[value='02:30:00']"

Conseils d’architecture et d’exploitation

  • Orchestration LB : implémentez un drain par nœud (connexion persistante + santé) pour chaque maintenance dédup/VSS.
  • SLO de latence : suivez p95/p99 par route API, pas uniquement la moyenne.
  • Capacité mémoire : budgétez la mémoire par w3wp = Overhead CLR + caches + LOH + buffers réseau + marge de 20–30 %.
  • Allocations : limitez les allocations transitoires massives (éviter string concat, préférer StringBuilder, pooling d’objets, ArrayPool<T>).
  • Logs : enrichissez les journaux de recyclage (ID d’App Pool, raison, mémoire, trafic) pour corréler rapidement.

Checklist de validation avant production

  • PerfMon : profils .NET, Dédup, VSS établis et seuils d’alerte posés.
  • Full GC dédup automatique bloquée et procédure manuelle documentée.
  • Shadow storage déplacé et dimensionné, sauvegardes vérifiées.
  • GC serveur activé, recycles IIS cadrés, preload actif.
  • Tests de charge réalisés (≥ 1 h) avec captures de p95/p99 et % GC.
  • Déploiement progressif planifié (1 → 6 nœuds) et rollback clair.

FAQ express

Le GC .NET suffit‑il à expliquer les pics ?
Pas toujours. Sur des serveurs de fichiers dédupliqués, la Full GC de déduplication est un classique des pannes « fantômes ». D’où l’importance de séparer GC applicatif et maintenance stockage.

Dois‑je augmenter indéfiniment la RAM ou le seuil IIS ?
Non. Relevez le seuil pour éviter des recyclages inutiles, mais corrigez surtout la cause (dédup/VSS, allocations) ; sinon la latence restera erratique.

Faut‑il désactiver la déduplication partout ?
Non. Évaluez le gain réel. Sur des volumes applicatifs chauds, elle est souvent contre‑productive ; sur des volumes froids/archivage, elle reste pertinente.

Conclusion

En combinant la désactivation de la Full GC automatique de la déduplication, l’isolation du shadow storage VSS et un tuning ciblé du GC .NET (GC serveur, fenêtres de recyclage maîtrisées, modes de latence adaptés), vous réduisez drastiquement les pauses GC et la probabilité de recyclages IIS. Le résultat : une API .NET stable sous forte charge, moins d’incidents de disponibilité et une expérience utilisateur nettement plus fluide.

Sommaire