Je voulais vérifier un point : un disque OS éphémère (Ephemeral OS Disk) peut être placé sur Temp, NVMe ou Cache selon la série de votre VM. La documentation Microsoft indique que l’option de placement ne change pas la performance ni le coût de l’Ephemeral OS Disk, car la perf dépend du stockage local disponible sur le SKU.

Dans la vraie vie (et dans mes chiffres) : même si les trois restent “OS + éphémère”, le placement change le chemin I/O réel, et donc le comportement sous pression : la différence la plus visible n’est pas toujours l’IOPS moyen, mais la distribution de latence (p95/p99/p99.9) et la stabilité en charge soutenue.
Plusieurs articles ont déjà été écrits sur les performances de stockages Azure :
- Apprivoisez le stockage Azure
- Stockez vos données sur un service PaaS
- Que valent les disques Azure ?
- Coûts des stockages Azure
Et pour vous guider plus facilement dans cet article très (trop?) long, voici des liens rapides :
- FAQ
- Qu’est-ce qu’un stockage temporaire local ?
- Qu’est-ce qu’un disque OS éphémère ?
- Pourquoi utiliser un disque OS éphémère ?
- Pourquoi ne pas utiliser un disque OS éphémère ?
- Comment savoir si le SKU de ma VM dispose d’un stockage temporaire local ?
- Quid de la SLA d’une VM avec un disque OS éphémère ?
- Quid des performances d’une VM avec un disque OS éphémère ?
- Tests de performances
Mais avant d’aller directement dans les tests, prenons le temps de parcourir ensemble quelques notions concernant le stockage temporaire local attaché une machine virtuelle.
Qu’est-ce qu’un stockage temporaire local ?
Certaines tailles de machine virtuelle Azure incluent un stockage local temporaire éphémère, certaines des tailles les plus récentes utilisant des disques NVMe locaux temporaires.
Le stockage temporaire local utilise des disques supplémentaires approvisionnés directement en tant que stockage local sur un hôte de machine virtuelle Azure, plutôt que sur le stockage Azure distant. Ce type de stockage convient le mieux aux données qui n’ont pas besoin d’être conservées définitivement, telles que les caches, les mémoires tampons et les fichiers temporaires.

Le disque temporaire (souvent D: sous Windows) est un disque local éphémère classique : les données sont potentiellement perdues en si maintenance/redeploy/deallocate. Un disque OS éphémère peut être placé dessus (Temp placement, NVMe, ou sur le cache disk) selon le SKU de la machine virtuelle :
Étant donné que les données stockées sur ces disques ne sont pas sauvegardées, elles sont perdues lorsque la machine virtuelle est libérée ou supprimée. Le stockage éphémère est recréé au démarrage. Les disques éphémères locaux sont différents des disques de système d’exploitation éphémères.
Qu’est-ce qu’un disque OS éphémère ?
Un disque OS éphémère est un disque système stocké localement sur l’hôte Azure, et non sur un stockage distant Azure Storage. Le point le plus important est que celui-ci est non persistant : en cas de redéploiement, de recréation, le disque OS éphémère revient toujours à l’image de départ.

Les disques de système d’exploitation éphémères sont créés sur le stockage local de la machine virtuelle (VM) et ne sont pas enregistrés dans le Stockage Azure à distance.
Pourquoi utiliser un disque OS éphémère ?
Le premier avantage concerne le prix : On ne paye pas le volume de stockage du disque éphémère. Celui-ci est intégré au prix de la machine virtuelle. De plus, les performances sont bien meilleures que la plupart des disques :
Les disques OS éphémères conviennent parfaitement aux charges de travail sans état, dans lesquelles les applications tolèrent les pannes de machines virtuelles individuelles tout en restant sensibles aux délais de mise en service ou à la réimagerie d’instances spécifiques.
Comparé à un disque de système d’exploitation standard, un disque éphémère offre une latence plus faible pour les opérations de lecture/écriture et permet une réinitialisation plus rapide des machines virtuelles.
Pourquoi ne pas utiliser un disque OS éphémère ?
Comme annoncé plus haut, le disque OS éphémère ne doit pas contenir de la donnée critique. De plus, des fonctionnalités basiques ne sont pas disponibles :
- Arrêt / Démarrage de la VM
- Capture d’image VM
- Captures instantanées de disque
- Azure Disk Encryption
- Échanges de disques système d’exploitation
Sur le portail Azure, certains menus sont tout simplement grisés pour ce type de VM :

Les fonctionnalités de sauvegarde et de reprise d’activité après sinistres sont elles aussi désactivés :

Comment savoir si le SKU de ma VM dispose d’un stockage temporaire local ?
La doc détaille trois placements possibles selon les VM :
- NVMe Disk Placement (GA sur des séries récentes v6+)
- Temp Disk / Resource Disk Placement
- Cache Disk Placement
La nature et le volume du stockage temporaire local dépend en effet de la famille et du SKU de votre machine virtuelle :

Attention, certaines machines virtuelles n’ont tout simplement pas de stockage temporaire local :

Quid de la SLA d’une VM avec un disque OS éphémère ?
Le base disk influence l’engagement contractuel et certaines phases de provisioning, mais il ne modifie pas le chemin I/O steady-state de l’OS.
Depuis peu, Azure permet de choisir le type de “base disk” associé à un disque OS éphémère : Standard HDD, Standard SSD ou Premium SSD.
Attention cependant, ce “base disk” ne correspond pas au support physique sur lequel l’OS s’exécute.
Dans le cas d’un disque OS éphémère, le système fonctionne toujours sur le stockage local de l’hôte (NVMe / Temp / Cache selon le placement). Le type de base disk désigne le type de disque managé logique utilisé par Azure lors du provisioning et pour l’engagement contractuel.
La prise en charge SSD est une nouvelle option qui permet aux clients de choisir le type de disque principal utilisé pour le disque d’OS éphémère. Auparavant, le disque de base ne pouvait être qu’un HDD standard. À présent, les clients peuvent choisir entre les trois types de disques : HDD Standard (Standard_LRS), SSD Standard (StandardSSD_LRS) ou SSD Premium (Premium_LRS). En utilisant SSD avec disque de système d’exploitation éphémère, les clients peuvent bénéficier des améliorations suivantes :
- Contrat SLA amélioré : les machines virtuelles créées avec SSD Premium fournissent un contrat SLA supérieur à celui des machines virtuelles créées avec hDD Standard. Les clients peuvent améliorer le contrat SLA pour leurs machines virtuelles éphémères en choisissant SSD Premium comme disque de base.
Concrètement :
- Le choix du base disk peut améliorer la SLA contractuelle de la VM.
- Il peut également influer sur certaines phases spécifiques (provisioning, re-imaging, lectures liées au backing managed disk).
- En revanche, il ne modifie pas les performances steady-state du stockage local sur lequel tourne réellement l’OS.
Autrement dit :
Choisir Premium SSD comme base disk améliore la SLA et certains scénarios liés au provisioning,
mais ne transforme pas un placement NVMe ou Temp en Premium SSD local.
| Pourcentage de disponibilité (SSD Premium, SSD Premium v2 et Ultra Disk) | Pourcentage de disponibilité (disque géré SSD standard) | Pourcentage de disponibilité (disque géré HDD standard) | Avoir Service |
| < 99,9 % | < 99,5 % | < 95 % | 10 % |
| < 99 % | < 95 % | < 92 % | 25 % |
| < 95 % | < 90 % | < 90 % | 100 % |
Quid des performances d’une VM avec un disque OS éphémère ?
Les disques temporaires locaux ne sont pas comptabilisés par rapport aux IOPS et aux limites de débit de la machine virtuelle. De plus, Microsoft nous indique que les performances du disque OS éphémère dépendent de la machine et non du type de stockage local :
Le disque OS éphémère exploite le stockage local intégré à la machine virtuelle. Étant donné que différentes machines virtuelles ont différents types de stockage local (disque de cache, disque temporaire et disque NVMe), l’option de placement définit l’emplacement où le disque de système d’exploitation éphémère est stocké. Le choix du placement n’influence ni les performances ni le coût du disque OS éphémère. Ses performances reposent sur le stockage local de la machine virtuelle. Selon le type de machine virtuelle, trois modes de placement sont proposés.
- Placement de disque NVMe (généralement disponible) : le type de placement de disque NVMe est désormais en disponibilité générale (GA) sur la dernière série de machines virtuelles v6 de la dernière génération, comme Dadsv6, Ddsv6, Dpdsv6, etc.
- Placement de disque temporaire (également appelé Placement de disque de ressources) : le type de placement de disque temporaire est disponible sur les machines virtuelles avec un disque temp comme Dadsv5, Ddsv5, etc.
- Emplacement du disque de cache : le type de placement du disque de cache est disponible sur les anciennes machines virtuelles qui avaient un disque de cache tel que Dsv2, Dsv3, etc.
La performance théorique dépend du SKU, pas du placement. En revanche, comme chaque placement repose sur un support physique différent (NVMe, temp disk, cache disk), le comportement réel sous charge peut varier sensiblement.
Mais cette seconde partie de la documentation Microsoft m’intrigue quand même :
Amélioration des performances : en choisissant SSD Premium comme disque de base, les clients peuvent améliorer les performances de lecture du disque de leurs machines virtuelles. Bien que la plupart des écritures se produisent sur le disque temporaire local, certaines lectures sont effectuées à partir de disques managés. Les disques SSD Premium fournissent 8 à 10 fois plus d’IOPS que hDD Standard.
J’ai créé plusieurs machines virtuelles avec le SKU Standard_D32ads_v7, dont voici les performances pour le stockage local :

Pourtant, les deux différents type de disque OS éphémère créés indiquent exactement les mêmes performances sur le portail Azure :


En extrapolant naïvement à partir de la capacité totale locale (440 Go x4), j’aurais pu m’attendre qu’en faisant un produit en croix basé sur la taille totale des 4 disques locaux attachés à ma VM, je m’attendais à trouver les performances suivantes sur mon disque OS éphémère :
- IOPS max : 43296 IOPS
- Bande passante max : 323 Mo/sec
Passons maintenant à l’approche utilisée pour mes tests.
Protocole de test que j’ai déployé :
- Machines virtuelles Azure :
Pour éviter toute ambiguïté, les 4 VMs utilisent toutes un disque OS avec Windows Server 2022, mais sur des placements différents :
| Nom de VM | SKU | Type de disque OS |
|---|---|---|
perftemp | Standard_D32ads_v5 | Ephemeral OS Disk – Temp placement |
perfnvme-vm | Standard_D32ads_v7 | Ephemeral OS Disk – NVMe placement |
perfcache | Standard_D32ds_v4 | Ephemeral OS Disk – Cache placement |
perfssd | Standard_D32ads_v7 | OS Disk Premium SSD (référence) |
- Outils de mesure :
Plusieurs outils de mesures ont été utilisés afin de mieux comprendre les performances :
| Nom de l’outil | Mesures effectuées | URL de téléchargement |
|---|---|---|
| fio | IOPS (read/write), bande passante (MB/s), latence moyenne, percentiles (p95, p99), tests steady-state, profils personnalisés (4K, 64K, queue depth, etc.) | https://github.com/axboe/fio |
| CrystalDiskMark | IOPS séquentiel et aléatoire, débit (MB/s), tests QD1/QD32, 4K/8K/1M | https://crystalmark.info/en/software/crystaldiskmark/ |
| AS SSD Benchmark | IOPS 4K read/write, latence d’accès, score global (read/write/total), test copie (ISO, Program, Game), test incompressible | https://www.alex-is.de/PHP/fusion/downloads.php?cat_id=4 |
CrystalDiskMark – Smoke Test :
L’outil a été laissé dans sa configuration de base. Cela permet de provoquer :
- Meilleur profit des caches (OS, contrôleur, stockage local, cache host)
- Meilleur visu du burst (très bon pendant un court moment)
- Ne force pas steady-state

Comme attendu, cela donne des chiffres parfois “spectaculaires”, notamment en lecture. Et c’est exactement le biais évoqué plus haut qui en ressort :
- Tests courts
- Pas de warm-up réel
- Influence potentielle du cache
CrystalDiskMark confirme les tendances générales, mais ne permet pas de juger la stabilité sous charge soutenue.
AS SSD Benchmark – Latence & Incompressible :
AS SSD Benchmark va un peu plus loin que CrystalDiskMark et donne d’autres infos : Seq / 4K / 4K-64Thrd + “Acc.time”. Cela donne dans les résultats une meilleure visibilité des différences d’écriture, parfois très forts selon le disque.
AS SSD est connu pour être très sensible à :
- la façon dont le chemin I/O gère les écritures (flush, cache, barrières)
- la latence (et il la met en avant via “Acc.time”)
- et la façon dont le driver/stack de stockage réagit
AS SSD peut donc apparaître comme plus sévère que CrystalDiskMark sur les écritures quand il tombe sur un scénario où le stockage/driver applique davantage de contraintes (flush/ordering).

Les tests faits via AS SSD nous apporte donc ici deux éléments intéressants :
- Mesure directe de latence d’accès
- Test incompressible (moins biaisé par cache/compression)
Dans notre cas, on peut donc en déduire que disque éphémère NVMe domine clairement en latence pure, que le disque éphémère temporaire reste très proche, que le disque éphémère cache montre une latence un peu plus élevée, et que le disque Premium SSD affiche la latence la plus importante.
Le score global reflète davantage l’expérience “ressentie” qu’un simple IOPS max.
fio – tests soutenus proches d’un workload :
Contrairement à CrystalDiskMark (smoke test court) et AS SSD (latence & incompressible), fio permet de :
- contrôler précisément le pattern I/O
- imposer une durée suffisante
- forcer le bypass du cache OS (–direct=1)
- introduire une phase de warm-up
- mesurer les percentiles élevés (p95, p99, p99.9)
Autrement dit, fio mesure le comportement soutenu proche d’un workload réel.
Je suis passé par Chocolatey pour installer fio sur mes 4 machines virtuelles Azure grâce au script PowerShell suivant :
Set-ExecutionPolicy Bypass -Scope Process -Force
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
choco --version
choco install fio -y
fio --version

J’ai ensuite lancé le script fio suivant sur chacun des machines virtuelles pour générer et centraliser mes résultats sur un Azure file share. Dans ce script, fio teste :
- Random 4K en montée de charge (sweep de queue depth QD1→64) en lecture/écriture
- Un “peak” comparable : random 4K QD32, 8 jobs (lecture + écriture).
- Un run plus long “steady-state” : random write 4K QD32, 8 jobs (soutenu).
- Le coût de la durabilité/synchronisation : random write 4K QD1, 1 job, fsync=1.
- Le débit séquentiel : read/write 1M, QD32, 4 jobs.
- Et il fait un warmup au début.
# =========================
# FIO -> Z:\fio-results\ (NO subfolders)
# VM name only in OUTPUT (filename + header), not in folder structure
# Tests:
# - Scaling (QD sweep) : randread4k / randwrite4k (for graphs, not for "max" headline)
# - Peak controlled : randread4k / randwrite4k @ QD32 NJ8 (reference "max comparable")
# - Steady-state : randwrite4k @ QD32 NJ8 (longer run)
# - Sync durability : randwrite4k QD1 NJ1 fsync=1
# - Throughput : seq read/write 1m @ QD32 NJ4
#
# Notes:
# - Uses --direct=1 (bypass OS cache)
# - Uses time_based + ramp_time (warm-up)
# - Emits a CSV summary you can aggregate across VMs
# =========================
param(
[string]$OutDir = "Z:\fio-results",
[string]$Target = "C:\fio_test.dat",
[string]$FileSize = "32G", # Bigger => fewer cache illusions
[int] $Runtime = 60,
[int] $RampTime = 10,
[string]$FioExe = "fio" # Or full path: C:\fio\fio.exe
)
$VmName = $env:COMPUTERNAME
function Ensure-Dir {
param([string]$Path)
try {
New-Item -ItemType Directory -Path $Path -Force | Out-Null
return $true
} catch {
return $false
}
}
# Prefer Z:\fio-results, fallback to C:\fio-results if Z: not available
if (-not (Ensure-Dir -Path $OutDir)) {
$OutDir = "C:\fio-results"
if (-not (Ensure-Dir -Path $OutDir)) {
Write-Host "ERROR: Unable to create output directory (Z:\fio-results or C:\fio-results)." -ForegroundColor Red
exit 1
}
}
# Ensure fio exists
try { & $FioExe --version | Out-Null } catch {
Write-Host "ERROR: fio not found. Put fio.exe in PATH or set -FioExe to its full path." -ForegroundColor Red
exit 1
}
function Run-FioTest {
param(
[Parameter(Mandatory=$true)][string]$TestName,
[Parameter(Mandatory=$true)][string]$TestType,
[Parameter(Mandatory=$true)][string[]]$Args
)
$ts = Get-Date -Format "yyyyMMdd-HHmmss"
$outFile = Join-Path $OutDir "$($VmName)_$($TestName)_$ts.txt"
@(
"VM: $VmName"
"Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
"TestType: $TestType"
"Test: $TestName"
"Command: $FioExe $($Args -join ' ')"
"----------------------------------------"
) | Out-File -FilePath $outFile -Encoding utf8
& $FioExe @Args 2>&1 | Out-File -FilePath $outFile -Append -Encoding utf8
return $outFile
}
function Parse-FioSummary {
param(
[Parameter(Mandatory=$true)][string]$FilePath,
[Parameter(Mandatory=$true)][string]$TestType
)
$content = Get-Content $FilePath -Raw
$readLine = [regex]::Match($content, '^\s*read:\s*IOPS=([0-9\.]+[kKmM]?),\s*BW=([0-9\.]+[A-Za-z\/]+)', 'Multiline')
$writeLine = [regex]::Match($content, '^\s*write:\s*IOPS=([0-9\.]+[kKmM]?),\s*BW=([0-9\.]+[A-Za-z\/]+)', 'Multiline')
# fio prints percentiles when --percentile_list is used
$p50 = [regex]::Match($content, '50\.00th=\[\s*([0-9]+)\]', 'Multiline')
$p95 = [regex]::Match($content, '95\.00th=\[\s*([0-9]+)\]', 'Multiline')
$p99 = [regex]::Match($content, '99\.00th=\[\s*([0-9]+)\]', 'Multiline')
$p999 = [regex]::Match($content, '99\.90th=\[\s*([0-9]+)\]', 'Multiline')
[PSCustomObject]@{
VM = $VmName
TestType = $TestType
File = Split-Path $FilePath -Leaf
ReadIOPS = if ($readLine.Success) { $readLine.Groups[1].Value } else { "" }
ReadBW = if ($readLine.Success) { $readLine.Groups[2].Value } else { "" }
WriteIOPS = if ($writeLine.Success) { $writeLine.Groups[1].Value } else { "" }
WriteBW = if ($writeLine.Success) { $writeLine.Groups[2].Value } else { "" }
P50_usec = if ($p50.Success) { $p50.Groups[1].Value } else { "" }
P95_usec = if ($p95.Success) { $p95.Groups[1].Value } else { "" }
P99_usec = if ($p99.Success) { $p99.Groups[1].Value } else { "" }
P999_usec = if ($p999.Success) { $p999.Groups[1].Value } else { "" }
}
}
# Common args (cache-light, more comparable)
$common = @(
"--filename=$Target",
"--size=$FileSize",
"--direct=1",
"--ioengine=windowsaio",
"--runtime=$Runtime",
"--ramp_time=$RampTime",
"--time_based",
"--group_reporting",
"--thread",
"--percentile_list=50:95:99:99.9"
)
$tests = @()
# ------------------------------------------------------------
# 0) Optional preconditioning (light warm-up) for consistency
# ------------------------------------------------------------
$tests += @{
Type="warmup"
Name="warmup_write1m_qd8_nj1_30s"
Args=@(
"--name=warmup",
"--filename=$Target",
"--size=$FileSize",
"--direct=1",
"--ioengine=windowsaio",
"--bs=1m",
"--iodepth=8",
"--numjobs=1",
"--rw=write",
"--runtime=30",
"--ramp_time=0",
"--time_based",
"--group_reporting",
"--thread"
)
}
# ------------------------------------------------------------
# 1) Scaling (QD sweep) - for graphs only
# Keep it longer than micro-runs to reduce burst artifacts
# ------------------------------------------------------------
foreach ($qd in @(1,2,4,8,16,32,64)) {
$tests += @{
Type="scaling"
Name="scaling_randread4k_qd$qd_nj8_90s"
Args=@(
"--name=rr_qd$qd",
"--filename=$Target",
"--size=$FileSize",
"--direct=1",
"--ioengine=windowsaio",
"--bs=4k",
"--iodepth=$qd",
"--numjobs=8",
"--rw=randread",
"--runtime=90",
"--ramp_time=15",
"--time_based",
"--group_reporting",
"--thread",
"--percentile_list=50:95:99:99.9"
)
}
$tests += @{
Type="scaling"
Name="scaling_randwrite4k_qd$qd_nj8_90s"
Args=@(
"--name=rw_qd$qd",
"--filename=$Target",
"--size=$FileSize",
"--direct=1",
"--ioengine=windowsaio",
"--bs=4k",
"--iodepth=$qd",
"--numjobs=8",
"--rw=randwrite",
"--runtime=90",
"--ramp_time=15",
"--time_based",
"--group_reporting",
"--thread",
"--percentile_list=50:95:99:99.9"
)
}
}
# ------------------------------------------------------------
# 2) Peak controlled (reference values, comparable across disks)
# ------------------------------------------------------------
$tests += @{
Type="peak"
Name="peak_randread4k_qd32_nj8_120s"
Args=@(
"--name=peak_rr",
"--filename=$Target",
"--size=$FileSize",
"--direct=1",
"--ioengine=windowsaio",
"--bs=4k",
"--iodepth=32",
"--numjobs=8",
"--rw=randread",
"--runtime=120",
"--ramp_time=20",
"--time_based",
"--group_reporting",
"--thread",
"--percentile_list=50:95:99:99.9"
)
}
$tests += @{
Type="peak"
Name="peak_randwrite4k_qd32_nj8_120s"
Args=@(
"--name=peak_rw",
"--filename=$Target",
"--size=$FileSize",
"--direct=1",
"--ioengine=windowsaio",
"--bs=4k",
"--iodepth=32",
"--numjobs=8",
"--rw=randwrite",
"--runtime=120",
"--ramp_time=20",
"--time_based",
"--group_reporting",
"--thread",
"--percentile_list=50:95:99:99.9"
)
}
# ------------------------------------------------------------
# 3) Steady-state style (longer, to see sustained behavior)
# ------------------------------------------------------------
$tests += @{
Type="steady"
Name="steady_randwrite4k_qd32_nj8_180s"
Args=@(
"--name=steady_rw",
"--filename=$Target",
"--size=$FileSize",
"--direct=1",
"--ioengine=windowsaio",
"--bs=4k",
"--iodepth=32",
"--numjobs=8",
"--rw=randwrite",
"--runtime=180",
"--ramp_time=30",
"--time_based",
"--group_reporting",
"--thread",
"--percentile_list=50:95:99:99.9"
)
}
# ------------------------------------------------------------
# 4) Sync durability penalty (OS-like durability semantics)
# ------------------------------------------------------------
$tests += @{
Type="sync"
Name="sync_randwrite4k_qd1_nj1_fsync1_60s"
Args= $common + @(
"--name=sync_rw",
"--bs=4k",
"--iodepth=1",
"--numjobs=1",
"--rw=randwrite",
"--fsync=1"
)
}
# ------------------------------------------------------------
# 5) Throughput (large blocks)
# ------------------------------------------------------------
$tests += @{
Type="throughput"
Name="seqread1m_qd32_nj4_60s"
Args= $common + @(
"--name=sr_1m",
"--bs=1m",
"--iodepth=32",
"--numjobs=4",
"--rw=read"
)
}
$tests += @{
Type="throughput"
Name="seqwrite1m_qd32_nj4_60s"
Args= $common + @(
"--name=sw_1m",
"--bs=1m",
"--iodepth=32",
"--numjobs=4",
"--rw=write"
)
}
# ------------------------------------------------------------
# Run + summary
# ------------------------------------------------------------
$results = @()
foreach ($t in $tests) {
Write-Host "Running $($t.Type) / $($t.Name) ..." -ForegroundColor Cyan
$file = Run-FioTest -TestName $t.Name -TestType $t.Type -Args $t.Args
$results += Parse-FioSummary -FilePath $file -TestType $t.Type
}
$ts = Get-Date -Format "yyyyMMdd-HHmmss"
$summaryCsv = Join-Path $OutDir "fio_summary_$($VmName)_$ts.csv"
$summaryTxt = Join-Path $OutDir "fio_summary_$($VmName)_$ts.txt"
$results | Export-Csv -NoTypeInformation -Path $summaryCsv -Encoding UTF8
$results | Sort-Object TestType, File | Format-Table -AutoSize | Out-String | Out-File -FilePath $summaryTxt -Encoding utf8
Write-Host "Done. Outputs in: $OutDir" -ForegroundColor Green
Write-Host "Summary CSV: $summaryCsv" -ForegroundColor Green
Write-Host "Summary TXT: $summaryTxt" -ForegroundColor Green

Synthèse des résultats :
Cela nous donne les synthèses suivantes :
| Disk | Peak 4K Read | Peak 4K Write | Steady 4K Write | Seq Read | Seq Write | Sync 4K Write |
|---|---|---|---|---|---|---|
| Temp (D32ads_v5) | 153k | 153k | 153k | 1951 MiB/s | 1950 MiB/s | 9.3k |
| Cache (D32ds_v4) | 98k | 93k | 94k | 1888 MiB/s | 1888 MiB/s | 7.9k |
| NVMe (D32ads_v7) | 146k | 60k | 60k | 1071 MiB/s | 536 MiB/s | 7.6k |
| Premium SSD | 10k | 4k | 3k | 152 MiB/s | 127 MiB/s | 507 |
| Disk | IOPS | P50 (µs) | P95 (µs) | P99 (µs) | P99.9 (µs) |
|---|---|---|---|---|---|
| Temp (D32ads_v5) | 153k | 212 | 381 | 523 | 903 |
| Cache (D32ds_v4) | 93k | 269 | 452 | 628 | 1104 |
| NVMe (D32ads_v7) | 60k | 261 | 437 | 601 | 1048 |
| Premium SSD | 4k | 402 | 761 | 1089 | 1827 |
Ce que peut en dire de ces tests :
Sur Temp, Cache et NVMe, on peut lire que Peak ≈ Steady. Cela signifie que :
- Pas de burst artificiel
- Pas d’effet cache court terme
- Pas de chute après 30 secondes
- Pas d’effondrement après warm-up
Le comportement observé est soutenu, et c’est extrêmement important, car beaucoup de benchmarks “rapides” montrent un pic initial qui s’effondre après 1 à 2 minutes.
Ici, ce n’est pas le cas.
- NVMe vs Temp : contre-intuitif
Intuitivement, on pourrait penser que NVMe est supérieur à Temp. Or, en écriture 4K soutenue :
- Temp : 153k IOPS
- NVMe : 60k IOPS
Ce n’est pas un artefact. De plus : Peak = Steady sur NVMe Cela indique un plafond structurel, pas un effet transitoire. La performance dépend donc du chemin I/O exposé par le SKU, pas uniquement de la nature “NVMe” du support. C’est un point fondamental.
- Premium SSD : changement de catégorie
Enfin, nous sommes dans un monde différent avec le premium ssd, En 4K write steady, le Premium SSD monte à 2 926 IOPS, avec un pique à 3500 IOPS. On n’est plus dans la même catégorie. Le disque managé respecte son cap IOPS contractuel :

Ce test montre très clairement la différence entre un stockage managé distant avec limite provisionnée et stockage local intégré au SKU.
Si un stockage persistant est nécessaire, à vous maintenant de voir quel disque correspondra mieux à vos besoins :

- Pourquoi regarder p99/p99.9 et pas juste les IOPS ?
Microsoft documente les notions de limites cached/uncached et le fait qu’un workload peut être IO capped. Quand cela arrive :
- Les IOPS plafonnent
- La file d’attente sature
- La latence augmente
Ton plateau write NVMe en 4K random (QD sweep) a exactement la signature d’une limite plateforme. Deux disques peuvent faire 100k IOPS :
- L’un avec P99.9 à 10 ms
- L’autre avec P99.9 à 50+ ms
Ils n’auront pas du tout la même sensation côté OS. Le P99.9 capture les moments où :
- la queue est saturée
- un flush bloque
- un throttling intervient
C’est ce qui compte en production.
- Comment le cache d’Azure nous trompe ?
Microsoft indique qu’un disque avec host caching peut temporairement dépasser la limite disque. Cela explique pourquoi :
- CrystalDiskMark peut afficher des chiffres “incroyables”
- Un bench court peut mesurer le cache plutôt que le support réel
Si un benchmark va “trop vite”, c’est souvent un cache. De plus :
- Microsoft recommande un warm-up
- Les cached reads atteignent leurs meilleurs chiffres après stabilisation
Le cache modifie donc la mesure. En write, ce n’est pas magique :
Quand le caching est en Read/Write, l’écriture doit être validée dans le cache et sur le disque.
Elle compte dans les limites cached et uncached. Le cache ne supprime pas la limite soutenue.
- Pourquoi certains outils de mesure peuvent être trompeur lors de tests sur Azure ?
De ce fait, certains outils comme CrystalDiskMark, sont très bien pour un smoke test, mais :
- Les durées courtes,
- Les patterns,
- et l’absence de phase de warm-up
font qu’ils mesurent souvent le cache, pas le support réel. Un chiffre « trop beau » est souvent… un cache.
- La limite n’est pas le disque. C’est la VM :

| VM | vCPU | Peak 4K Read | Peak 4K Write | Steady 4K Write | P99 Write (µs) | P99.9 Write (µs) |
|---|---|---|---|---|---|---|
| D32ads_v7 | 32 | ~146k | ~60k | ~60k | ~601 | ~1048 |
| D64ads_v7 | 64 | ~291k | ~120k | ~120k | ~580 | ~1010 |
| VM | Seq Read | Seq Write |
|---|---|---|
| D32ads_v7 | ~1071 MiB/s | ~536 MiB/s |
| D64ads_v7 | ~2144 MiB/s | ~1067 MiB/s |
Ces nouveaux tests montrent un comportement très clair :
- NVMe en D32 plafonne à ~60k IOPS write
- Le même NVMe en D64 monte à ~120k IOPS
- La latence reste comparable
Le support physique n’a pas changé. Le workload n’a pas changé. Le placement n’a pas changé.Ce qui a changé : le SKU, cela démontre que Le plafond de performance est imposé par la capacité I/O exposée par la VM, pas par le média NVMe lui-même.
Autrement dit, le NVMe ne “donne” pas 60k IOPS, la VM D32 expose 60k IOPS.
Mais attention, les chiffres donnés dans la documentation Microsoft décrivent le potentiel maximal du stockage local temporaire de la VM (souvent agrégé sur plusieurs disques). Un disque OS éphémère ‘NVMe placement’ n’est pas automatiquement équivalent à ce chemin I/O, et un test ‘fichier’ ajoute un overhead. On compare donc des plafonds de nature différente.

- Pourquoi les tests “fsync=1” ne prouvent pas la durabilité ?
fsync=1 mesure le coût d’un flush côté OS, mais ne prouve pas la persistance réelle sur le média ou la résistance à un crash hôte. fio indique qu’en non-buffered I/O, il peut ne pas sync comme attendu :
- fsync=1 force un flush côté OS
- mais ça ne valide pas une persistance réelle côté hyperviseur / host
- ce n’est pas un test de crash-consistency
Donc si tu veux un “durability test”, il faut une variante (ex : buffered ou job dédié) et mesurer la phase sync séparément.
Conclusion
Si je résume :
- Les trois disques OS éphémères surpassent le Premium SSD managé
- NVMe offre une latence très stable et évolue fortement avec le SKU
- Temp placement reste le plus performant en écriture 4K soutenue dans ce test précis
- Cache dépend davantage du comportement de file d’attente
Mais surtout, Microsoft a raison : la performance dépend du stockage local. Mais ce que la documentation ne met pas en avant, c’est que le stockage local n’est pas homogène selon le placement. Et c’est là que tout se joue :
- Les différences ne sont pas toujours visibles sur l’IOPS moyen. Elles apparaissent dans les percentiles élevés (p99/p99.9)
- C’est exactement ce que Microsoft ne détaille pas explicitement : la performance dépend du stockage local… mais le stockage local n’a pas la même nature physique selon le placement
D’un point de vue technique :
- Le placement ne change pas le coût
- Le placement ne change pas la “promesse marketing”
- Mais le placement change le chemin I/O réel
Et donc, en fonction de la charge de travail :
- Pour une charge de travail sensible à la latence (SQL temporaire, build intensif, traitement parallèle), le NVMe placement est clairement le plus intéressant.
- Pour des workloads stateless classiques, Temp reste un excellent compromis.
- Le Cache placement, sur des générations plus anciennes, reste viable mais moins moderne.
- Enfin, un Premium SSD managé reste plus simple opérationnellement, mais il est largement dépassé en performance pure par le stockage local éphémère.
