Use a storage load generator to answer a narrow question, then confirm the result with the application. A large IOPS number is not automatically a faster database or file service. Block size, concurrency, cache behaviour, durability and the storage path determine what that number means.
This guide supplies read-only workload examples for a pre-existing disposable test file. It never targets a raw device and contains no write benchmark. No load was run for this article. Even read-only load consumes shared capacity, so first use requires an authorised isolated lab and a bounded test window.
1. Write the hypothesis and stop conditions
Define one question, such as whether the same VM experiences higher read latency after a specific storage-policy change. Record the application symptom, expected output, baseline configuration and candidate change.
Choose stop conditions with the platform owner: application timeouts, reduced storage protection, significant resync pressure or a predefined latency/headroom limit. Do not invent a universal limit; use the service's accepted operating range. Make the process owner and maximum duration explicit.
Record VM vCPU/RAM, controller, virtual disk, snapshot state, datastore/policy, host placement and relevant background work. Record guest patch level and the exact DiskSpd binary version and integrity identifier. Obtain the tool only through the official Microsoft project: https://github.com/microsoft/diskspd. A rolling wiki reference is not proof that every switch exists in an older binary.
2. Prepare the fixture before timing
Use a dedicated lab volume and a synthetic file prepared through the approved dataset process. Record file length, checksum, allocation and content characteristics. Confirm the backing storage has enough space and that the file is not sparse or unintentionally dominated by zero/compressible data when those properties matter.
Do not create or resize the fixture inside the timed command. Preparation can allocate storage and warm caches, so record its timing separately. Also avoid hashing the fixture immediately before a supposedly cold read: hashing reads the file and can change the cache state.
The fictional example path is D:\Bench\read-fixture.bin. Map that volume to its VMDK and datastore before testing. Never replace it with a production database file or a raw-device path. A file larger than guest RAM can reduce one cache's influence, but does not prove every host/array cache has been bypassed.
3. Understand the caching boundary
| Layer | Relevant observation | What it does not prove |
|---|---|---|
| Application | Repeated query/file behaviour | Every request reaches storage |
| Windows file cache | Buffered versus unbuffered file reads | Array or controller cache state |
| Hypervisor/storage stack | Virtual-disk and backend counters | Application transaction completion |
| Storage devices/array | Physical response and cache policy | That a synthetic profile matches the application |
DiskSpd's -Su requests unbuffered I/O. It is not a command to flush every cache. The -Sh shorthand also includes write-through behaviour and is not simply a universal cold-cache mode. Persistent storage caches can legitimately remain in the completion path. DiskSpd command reference: https://github.com/microsoft/diskspd/wiki/Command-line-and-parameters
Label the cache policy used in every result. Do not compare a buffered first run with an unbuffered second run and attribute the difference solely to a VMware setting. Microsoft also discusses cache and I/O considerations in its SMB performance guidance: https://learn.microsoft.com/en-us/windows-server/administration/performance-tuning/role/file-server/smb-file-server.
4. Run a bounded low-concurrency profile
Verify local help for the installed DiskSpd binary. Confirm the prepared fixture exists and the output filenames are unused. In an approved PowerShell lab session, the following profile requests random 8-KiB reads with one worker and one outstanding request:
.\diskspd.exe -b8K -d60 -W10 -C5 -o1 -t1 -r -w0 -L -Su -Rtext 'D:\Bench\read-fixture.bin' > '.\read-q1.txt'
if ($LASTEXITCODE -ne 0) { throw 'DiskSpd failed; preserve output and investigate' }
The measured phase is 60 seconds, preceded by 10 seconds of warm-up and followed by five seconds of cooldown. -w0 selects reads only; -L requests latency statistics. There is no file-creation flag. These options were checked against the public reference, not an installed binary in a lab.
Ensure the fixture and result directory are genuinely allocated for the test. Output redirection writes a local result file and can overwrite an existing file; select a unique run directory before executing the example. Stop the one identified test process if the agreed stop condition occurs, rather than terminating unrelated applications.
5. Change one workload dimension
After the first profile completes and shared-resource health is acceptable, an eight-outstanding-request comparison can use the same inputs:
.\diskspd.exe -b8K -d60 -W10 -C5 -o8 -t1 -r -w0 -L -Su -Rtext 'D:\Bench\read-fixture.bin' > '.\read-q8.txt'
if ($LASTEXITCODE -ne 0) { throw 'DiskSpd failed; preserve output and investigate' }
This changes concurrency, not storage configuration. Higher throughput at a higher queue depth may come with worse response time. It is a capacity curve observation, not proof that the application became faster.
For sequential-read questions, define a separate profile with the appropriate sequential option and transfer size after checking the command reference. Do not blend those results with the random profile. Keep thread count, target count and outstanding operations explicit; multiplying them can create much more load than the operator intended.
This article withholds write profiles. A write workload can overwrite target data and has additional persistence, endurance and preparation requirements. A qualified storage specialist should design and validate it independently on disposable storage.
6. Interpret the result before comparing percentages
Check that the result reports the intended target, block size, duration, threads, queue and cache policy. Reject a run that failed, targeted the wrong volume or overlapped an unplanned resync/backup unless that overlap was the test's purpose.
Record read throughput, IOPS, average latency, relevant percentiles and total operations. Confirm units before combining guest and backend charts. Microsoft's result interpretation guide: https://github.com/microsoft/diskspd/wiki/Analyzing-test-results explains latency histograms and why extreme percentiles need enough operations.
A useful arithmetic check is throughput bytes/second ≈ IOPS × bytes/operation for a uniform block size. At a hypothetical 10,000 operations/second and 8 KiB per operation, the corresponding payload rate is 81,920,000 bytes/second, about 78.1 MiB/second. This is an illustration, not a measured result or target.
At 10,000 measured reads, the highest 0.01 percent represents roughly one operation. Reporting a precise 99.99th percentile as a stable service characteristic from that sample would overstate the evidence. Keep sample counts and repeatability beside tail claims.
7. Compare baseline, candidate and baseline again
For a configuration experiment, use an A/B/A sequence: original configuration, one approved candidate, then original again. Run matched workload profiles and record host placement, competing demand and cache treatment each time.
Keep the low-queue and higher-queue results separate. Compare medians across repeated runs and include failures. If the final baseline differs substantially from the first, investigate drift before assigning the improvement to the candidate.
Then rerun the actual application operation with the same correctness criteria. A storage gain that does not reduce application time may mean the application is constrained elsewhere. That is useful evidence rather than a reason to manufacture a broader performance claim.
Acceptance and rollback
Accept the experiment only when inputs are comparable, results repeat, required storage protection remains healthy and the application behaves correctly. State exactly which workload benefited and which cache boundary was exercised.
Restore the one changed configuration through its supported reversal, verify health and retain both favourable and unfavourable results. Remove only the identified synthetic fixture after the evidence retention decision. Do not delete application data, caches or snapshots as benchmark cleanup.
Related topics: ESXi memory pressure, vSAN policies and Windows SMB tuning.
Learning notes
- Unbuffered I/O does not mean every storage cache is bypassed.
- Queue depth can raise throughput while making individual operations slower.
- A synthetic benchmark is evidence about its workload, not a universal speed rating.
Sources and review status
See the source ledger, author self-check and series index. Independent review and lab validation remain pending.