Benchmarking 201

SSD Benchmarking 201, Part 2: Steady State and Tail Latency

When a preconditioned SSD is ready to measure under the SNIA test specification, and how to report its latency past the average with percentiles and QoS.

On this page 10 sections

Part 1 ended on a question its job files couldn’t answer: was the drive ready to be measured when the clock started? A flash SSD has no single speed. A fresh drive runs fast for a while and then settles to a level it holds for the rest of its working life, and only that settled level says how it behaves in service. To turn “settled” into a test with limits, SNIA wrote the Solid State Storage Performance Test Specification (PTS).

Steady state and tail latency: rounds that settle, a latency histogram, and the slow end an average hidesA three-panel cover. Left: a tracked result falling over the first rounds and then flat, with the last rounds inside a window box. Center: a latency histogram with one tall bucket and short buckets on either side. Right: a distribution's average marked near its peak and a long thin tail running to the right, the slow I/Os the average hides.GUIDE / BENCHMARKING 201, PART 2Steady first, then the tailROUNDS · BUCKETS · TAIL01 ROUNDS02 BUCKETS03 TAILSettle firstjudged on last roundsCount bucketspercentile estimatesaverageslowestRead the tailwhere averages hide

This part puts my 2016 rounds on a Micron 9100 MAX 2.4TB against that test and then looks at what an average hides. One step in my sweep averaged 0.153ms per read and logged a maximum of 3.3ms for its slowest one. Latency percentiles fill the space between those two numbers. Iometer only gives them as an estimate from its histogram, and fio reports them directly.

What the SNIA PTS counts as steady state

The introductory series’ part on a repeatable test method covers why a fresh drive needs filling before it’s tested. A PTS run goes further and starts every test from a known state. First comes a purge, by an ATA security erase, an NVMe format, or a vendor tool, which returns the drive as close as it can get to never written, and the report names the method. Workload-independent preconditioning follows, with 128KiB sequential writes over twice the user capacity. After that the test loop serves as its own workload-dependent preconditioning and repeats until the drive settles.

For the test of I/O operations per second (IOPS), that loop is 56 one-minute runs, every pairing of seven read and write mixes with eight random block sizes from 1,024KiB down to 0.5KiB. One pass through all 56 is a round. Rounds repeat until the drive reaches steady state or 25 have run. For enterprise drives the PTS recommends four threads at 32 outstanding I/Os each, with the drive’s volatile write cache disabled.

What the SNIA PTS counts as steady state

One PTS round runs 56 one-minute tests in a fixed order

Upper panel: a grid of seven rows and eight columns. Rows are the read share of each test, from 100% reads at the top through 95, 65, 50, 35 and 5% to 0% reads at the bottom. Columns are random block sizes from 1,024KiB on the left through 128, 64, 32, 16, 8 and 4KiB to 0.5KiB on the right. The first test of a round is 100% reads at 1,024KiB and the 56th is 0% reads at 0.5KiB, run row by row. Three cells are marked as the PTS 2.0 tracking results: 1,024KiB at 100% reads, 64KiB at 65% reads and 4KiB at 0% reads. Lower panel: nine rounds of one tracked result falling and then flattening, with the last five rounds inside a window box. Inside the window the range must stay within 20% of the average and a fitted line must change by no more than 10% of it.Random block size, KiB (inner loop, left to right)Reads1,024128643216840.5100%95%65%50%35%5%0%156Run order: across each row, then down to the next mixONE TRACKED RESULT, ROUND BY ROUNDRange within 20% of the averageIOPSRounds, first to lastPTS 2.0 tracked resultFive-round windowFitted line within 10%
Figure 2.1 · Read mix is the outer loop and block size the inner one; the window test is judged on the last five rounds. Source: SNIA PTS 2.0.2, sections 2.1.24 and 7.2; the round values in the lower panel are illustrative.

Flash Foundations Part 9 walks through the two-part definition on a real write log. Across a window of the last five rounds, the tracked result’s range must stay under 20% of its average, and a line fitted through those rounds must change by no more than 10% of it.

Which result gets tracked changed between versions. PTS Enterprise version 1.1 tracked one result, 4KiB random writes. Version 2.0 merged the enterprise and client specs and tracks three, adding 64KiB at 65% reads and 1,024KiB random reads, because newer, larger SSDs can be tuned for 4KiB random writes and settle on that result before the others do.

A report carries as much weight as the limits. It needs a convergence plot of every round from the first to the last, a plot of the window rounds against the limits, and a table of the window results. A drive that hasn’t settled by round 25 can keep running or stop there, and a report that stops has to say steady state wasn’t reached.

My 2016 rounds against the rule

My SNIA-style batch file ran one sequential fill and then my 56-spec config 25 times back to back, on my test system with Iometer 1.1.0. It’s the same 2h, 128KiB sequential fill from Part 1, run at a queue depth (QD) of 256, and the batch sends its results to a throwaway file. Each round on the 9100 MAX took about 56 minutes. Round 22 started at 20:38:45 on June 20, 2016, and round 23 at 21:35:02, and my workbook keeps rounds 22 to 25.

My 2016 rounds against the rule

No tracked result strayed more than 0.22% from its four-round mean

A line chart. The horizontal axis is the SNIA-style round, with rounds 1 to 21 drawn as a shaded block marked not kept, then rounds 22, 23, 24 and 25. The vertical axis is each round's IOPS as a percentage above or below that result's own four-round mean, on a scale from minus one to plus one. Three lines stay close to zero: 4KiB random writes at plus 0.18, minus 0.22, plus 0.11 and minus 0.07%; 64KiB at 65% reads at plus 0.03, plus 0.03, plus 0.05 and minus 0.11%; 1MiB random reads at plus 0.2, minus 0.04, minus 0.1 and minus 0.06%. The PTS limit of a 20% range is far outside the plotted scale.Round IOPS against its result's four-round mean, %−1−0.500.51Rounds 1 to 21not kept22232425SNIA-style round, kept rounds boxed4KiB, 0% reads64KiB, 65% reads1MiB, 100% reads
Figure 2.2 · The PTS allows a 20% range, far off this scale; rounds 1 to 21 were not kept, so the five-round window and the convergence plot cannot be drawn. Source: my enterprise test runs, Micron 9100 MAX 2.4TB, SNIA-style QD256 rounds 22 to 25, June 2016, recomputed from the raw rows.

All 56 specs pass both limits by a wide margin across those four rounds. Version 1.1’s tracked result, 4KiB random writes, ran from 336,992 to 338,321 IOPS around a 337,722 average. That’s a range of 0.4%, and a line fitted through the four rounds moves 0.1%. Both results that version 2.0 added are as flat, with a 0.2% range for 64KiB at 65% reads and 0.3% for 1MiB random reads. My widest range of any spec was 0.5%, at 1MiB with 50% reads, against the PTS limit of under 20%.

How my method differs from the spec

My window differs from the PTS in ways the kept files show. It is four rounds where the spec asks for five, and rounds 1 to 21 aren’t in the workbook, so there is no convergence plot of when the drive settled. The four rounds I kept pass both limits. My batch file starts at the fill with no purge step listed, and the results files don’t record a purge method.

My config departs from the spec twice more. It runs 16 workers at 16 outstanding I/Os, QD256 in total, against the recommended four at 32, and the PTS leaves that choice to the operator as long as the report states it. The PTS asks that its loop order be kept, and mine differs. My config runs block size as the outer loop from 512B up, with read shares from 0% up inside it, while the PTS nests them the other way. Its read mix sits outside, starting at 100% reads, with block size inside from 1,024KiB down. Each one-minute result in my order follows a different neighbor, so a cell in my table isn’t strictly comparable with the same cell in a PTS report.

A steady result belongs to its loop

My 6h 4KiB random write precondition averaged 311,527 IOPS and wrote roughly 11.5 times the drive’s capacity. At QD256 in the write sweep that followed, the drive ran 311,479 IOPS, within 0.02% of that average. Inside the SNIA-style rounds, 4KiB random writes at the same 16 workers with 16 outstanding I/Os each averaged 337,722 IOPS, 8.4% higher. Each 60s step of writes in those rounds follows a minute of 512B reads, while the sweep’s 300s step followed hours of nothing but writes, and the precondition itself ran 8 workers at 32. Loop order, step length and what ran before all differ between the two, and my data can’t separate them. On this drive the same 4KiB write result differed by 8.4% between two loops, so a figure quoted without its loop can’t be set beside another lab’s.

Ending a precondition on a measurement in fio

My random preconditions ran on the clock. Iometer keeps one row for the whole 6h in its results file, so it shows that the drive ended steady and not when it got there. fio can watch the drive as it runs and stop the job when the numbers settle.

Its steadystate option, ss for short, takes a criterion and a limit. At revision 3.42, fio’s documentation lists six criteria. Two of them, iops and bw, stop the job when every sample in the window sits within the limit of the window’s mean. Their slope versions stop it when a least-squares slope falls under the limit, and a latency pair does the same for completion latency. As its own example, the documentation gives iops_slope:0.1%, which ends a job once the slope falls below 0.1% of the mean IOPS.

Three more options shape the window:

  • ss_dur is the length of a rolling window, and its default of 0 turns detection off, so a job with ss alone never stops early.
  • ss_interval sets how often a sample is taken inside the window, every second by default, which the documentation warns may not converge on slower devices.
  • ss_ramp waits a set time before collection starts, so the early drop from fresh-drive speed stays out of the window.

Any job using these also needs time_based and a runtime, and that runtime ends the job if the drive never settles.

Ending a precondition on a measurement in fio

fio stops a job once a rolling window of samples settles

Upper panel: one fio job on a time line. The first stretch is ss_ramp, where no samples count. After it, a sample is taken every ss_interval, drawn as ticks, while the IOPS line falls and then flattens. A bracket marks the rolling ss_dur window over the latest samples; when every sample in it meets the criterion, the job stops. A dashed line at the right end marks runtime, the upper limit if the drive never settles. Lower panel: ten samples in one window around their mean. A band from 10% under to 10% over the mean marks the iops:10% criterion. One sample sits 14% over the mean, outside the band, and the lowest sits 5% under it, so the window's range is 19% of the mean: under the PTS limit of 20%, so it passes the PTS range test and fails iops:10%.01 ONE FIO JOB IN TIMEss_rampnot countedone sample every ss_intervalrolling ss_dur windowcriterion met,job stopsruntime cap02 ONE WINDOW AGAINST TWO TESTSmean+10%−10%+14%−5%range19%Inside iops:10%Outside itiops:10% band
Figure 2.3 · Option names from the fio documentation, revision 3.42; the samples are drawn to show the shape, and the 14% over and 5% under window, a 19% range, passes the PTS range but not iops:10%. Illustrative values.

On range, fio comes close to the PTS. Every sample within 10% of the mean keeps the window’s range to 20% of the mean at most, the PTS limit itself, and the two tests split on a lopsided window. Say a window’s samples run from 14% over the mean to 5% under it. Its range of 19% passes the PTS, and iops:10% keeps the job running. fio also checks one criterion per job over seconds of one workload, while the PTS checks two criteria over rounds of 56. Matching the spec still takes a script that runs the 56 specs as jobs, records each round, and applies both limits. For a precondition, ss with ss_dur and a runtime cap gives a job that ends on a measurement, and --debug=steadystate prints the calculations behind the stop.

Latency past the average

Iometer reports two latency numbers for each run, the average and the maximum response time. Neither one says what most I/Os saw.

Average and maximum response time

With one worker and one I/O in flight, a QD of 1, my 9100 MAX averaged 0.153ms per 4KiB random read over a 300s step. Across 1.95 million reads its maximum was 3.3ms, about 21.6 times the average. Most of the rest sat in one bucket of the histogram, with 99.79% finishing between 100 and 200µs and 70 taking 2 to 5ms.

A maximum is a single I/O, so it moves from run to run. Over my four SNIA-style rounds at QD256, the 4KiB random read result held within 0.2% while its maximum ran from 5.5 to 6.3ms, 13% apart. A maximum from one run says that a slow I/O happened and nothing about how often.

At the other extreme from my rounds sits the PTS latency test. It runs one thread with one outstanding I/O, so the host’s handling of a queue drops out, across 8KiB, 4KiB, and 0.5KiB at 100%, 65%, and 0% reads, recording average and maximum response time each round. Version 2.0 added a 20-minute 4KiB random write run that reports a response time histogram as its quality of service (QoS) report, along with the five nines response time that 99.999% of I/Os beat. Some enterprise data sheets publish QoS figures measured at PTS steady state, and the percentile section of Flash Foundations Part 9 reads one from Micron. My QD1 numbers come from a 300s sweep step after a fixed precondition, with the same single I/O in flight and none of the rounds.

Percentiles from Iometer’s histogram

Flash Foundations Part 9 defines p50 through p99.99 and why the tail matters. Iometer sorts every completed I/O into one of 21 buckets, and that’s enough for a rough percentile. Count the I/Os in each bucket from the fastest up and keep a running total. Wherever the total passes 99% of all I/Os, that bucket holds the 99th percentile. This method gives a range between two bucket edges and never a single value, so every percentile below is an estimate.

Part 1’s QD256 random read step, 758,048 IOPS at a 0.337ms average, logged 227 million reads. Of those, 72.0% landed in the 200 to 500µs bucket, 14.6% in 100 to 200µs, 12.1% in 0.5 to 1ms, and 1.2% in 1 to 2ms. Only 59,813, or 0.026%, took 2 to 5ms, and none took longer, with the slowest at 4.77ms. Counting up, the running total passes 50% in the 200 to 500µs bucket, 99% and 99.9% inside 1 to 2ms, and 99.99% and 99.999% inside 2 to 5ms.

Latency past the average

Most reads took 200 to 500µs; the 99.99th percentile fell in 2 to 5ms

A vertical bar chart of the share of 227 million 4KiB random reads at QD256 in each Iometer response time bucket: 14.6% in 100 to 200µs, 72.0% in 200 to 500µs, 12.1% in 0.5 to 1ms, 1.2% in 1 to 2ms and 0.026% in 2 to 5ms. Under each bucket are the percentiles whose running total passes inside it: the 50th in 200 to 500µs, the 99th and 99.9th in 1 to 2ms, and the 99.99th and 99.999th in 2 to 5ms. Two percentiles in one bucket cannot be told apart.Share of 227 million reads, %02040608014.6%100–200µs72.0%200–500µsp5012.1%0.5–1ms1.2%1–2msp99p99.90.026%2–5msp99.99p99.999Response time bucket and the percentiles inside it4KiB random reads, QD256
Figure 2.4 · Each bucket is labeled with the percentiles it holds, an estimate that can name the bucket but never a value inside it; no read took longer than 5ms. Source: my enterprise test runs, Micron 9100 MAX 2.4TB, 4KiB random reads at QD256 (16 workers × 16), one 300s step, June 2016, recomputed from the raw Iometer row.

Bucket width sets one limit. Two percentiles that share a bucket can’t be told apart, and the 2 to 5ms bucket is 3ms wide, about nine times this step’s average response time. I/O count sets the other. Nine nines needs at least a billion I/Os under the PTS, which recommends running 1.5 times the minimum. By that rule this step’s reads support up to eight nines and the QD1 step’s 1.95 million support six.

Mixed workloads hide the slow side

At 4KiB and 65% reads in the SNIA-style rounds, my 9100 MAX averaged 0.557ms across all I/Os. Iometer splits that by direction, and reads averaged 0.818ms while writes averaged 0.071ms. Reads alone, at 100% reads in the same rounds, averaged 0.347ms, so mixing in 35% writes made each read take about 2.4 times as long. Reads in a mix can wait behind program and erase operations on the same die, and Flash Foundations Part 9 links that mechanism to its source.

Its histogram doesn’t split by direction, so any percentile read from it blends two populations. During the mixed run, 29.4% of all I/Os finished in under 100µs, and at 100% reads about 0.0005% did. Those fast completions are mostly writes. A 99th percentile read off the blended histogram likely sits lower than the reads alone would put it. fio prints its statistics for each data direction, so a randrw job reports read and write percentiles separately.

Inside a steady average

Every number on this page is an average over minutes or hours, and steady state only says those averages stopped moving from round to round. A four-round mean can’t show a dip of a few seconds inside a minute. My consistency runs for the 9100 MAX logged IOPS interval by interval at each queue depth from QD1 to QD256, which a single results row can’t show. One average also hides the split between reads and writes inside each minute. The 4KiB mix’s 0.557ms sits between reads at 0.818ms and writes at 0.071ms, and neither direction ran at the average.

Inside a steady average

In a 65% read mix, reads waited about 2.4 times as long as alone

A vertical bar chart of average response time at QD256 with 4KiB random I/O. In the 65% read mix, all I/Os averaged 0.557ms, reads 0.818ms and writes 0.071ms. With 100% reads in the same rounds, reads averaged 0.347ms, so reads in the mix took about 2.4 times as long as reads alone.Average response time, ms00.250.50.7510.557msAll I/Osin the mix0.818msReadsin the mix0.071msWritesin the mix0.347msReadsaloneWhich I/Os the average covers4KiB, 65% reads4KiB, 100% reads
Figure 2.5 · The mix average of 0.557ms hides reads at 0.818ms and writes at 0.071ms. Source: my enterprise test runs, Micron 9100 MAX 2.4TB, SNIA-style QD256 rounds 22 to 25, June 2016, mean of the four rounds from the raw rows.

Of the numbers in these results, the average at QD256 says least about what an application waits on. A read average inside a mix and the share of I/Os past 1ms say more, and both come from the same runs. Part 3 of this series turns to the drives themselves, with my results from these runs and the limits this part found in them.

KEEP GOING / NEXT CHAPTERContinue to Part 3

Sources

Primary is the original paper, standard or datasheet. Secondary is a review, a vendor summary or a news report. Measured is my own test data.

Show all 3 sources, 2 primary