SSD Bottleneck Analysis
A working page for modeling SSD performance as a series of capacity-constrained stages. Fill in device-specific numbers as they're gathered.
Method
Model the SSD data path as a chain of stages, each with its own maximum throughput (a "capacity," in the same sense as an arc in a transportation/network-flow problem). Data has to pass through every stage on the way from host to media, so the achievable end-to-end performance is bounded by the slowest stage:
SSD_Throughput(workload) = min( Stage_1, Stage_2, ..., Stage_N )
This is the same logic as a max-flow / min-cut problem: within a stage, parallel resources (lanes, channels, dies, planes) add capacity, but across stages the pipeline can only move data as fast as its narrowest link. The "bottleneck stage" is the equivalent of the minimum-capacity cut.
Two practical wrinkles versus a textbook transportation problem:
- Units must match before taking the min. Some stages are naturally bandwidth-bound (GB/s) and others are naturally operation-bound (IOPS). Convert using the workload's block size:
BW = IOPS x block_sizeandIOPS = BW / block_size. Which stages dominate depends on whether the workload is large-block/sequential (bandwidth-bound stages tend to win) or small-block/random (IOPS-bound stages tend to win). - The bottleneck can be time-varying. SLC write cache means the true minimum is different in "burst" mode (writing into cache) versus "sustained" mode (cache exhausted, writing/folding into native TLC/QLC). Compute both regimes separately.
Candidate Bottleneck Stages
1. Host Interface (PCIe link)
BW_host = N_lanes x LineRate_GT/s x EncodingEfficiency / 8 [GB/s]
N_lanes: e.g., x4LineRate_GT/s: Gen3 = 8, Gen4 = 16, Gen5 = 32EncodingEfficiency: 128b/130b ≈ 0.9846 (Gen3+); 8b/10b = 0.8 (Gen1/2)- Subtract ~5-10% for NVMe/TLP framing overhead to get a realistic usable number.
- Example: Gen4 x4 → 4 x 16 x 0.9846 / 8 ≈ 7.88 GB/s raw.
2. Controller Compute / FTL / Command Processing
IOPS_controller = N_cores x Clock_freq / CyclesPerCommand
or, if only a vendor spec is available, treat the published max IOPS number as this stage's capacity directly. Covers L2P (logical-to-physical) address translation, queue/doorbell handling, and command parsing. This is usually the limiter for small-block random IOPS on cheap/DRAM-less controllers.
3. DRAM Bandwidth (mapping table / cache)
BW_DRAM = DataRate_MTs x BusWidth_bits / 8 x N_channels [GB/s]
IOPS_DRAM = BW_DRAM / Bytes_DRAM_access_per_IO
- Each random IO needs L2P table read/update (a few bytes per 4KB mapped, more if page-level mapping) plus any data caching.
- DRAM-less (HMB-based) designs borrow host memory instead — same math, but
BW_DRAMis then constrained by the PCIe link too.
4. ECC / LDPC Encode-Decode Engine
BW_ECC = CodewordSize x DecodeRate_codewords_per_sec x N_engines [GB/s]
- Must exceed the raw NAND throughput at the required BER (bit error rate).
- Degrades over drive life: more read-retry / decode iterations near end-of-life reduces effective
DecodeRate.
5. NAND Flash Interface (channel bus / Toggle / ONFI)
BW_channel_total = InterfaceClock_MTs x BusWidth_bytes x N_channels [GB/s]
- Example: Toggle NAND at 800 MT/s, 1-byte bus, 8 channels → 0.8 x 1 x 8 = 6.4 GB/s aggregate raw.
- This is a hard ceiling independent of how fast the NAND array itself can program/read.
6. NAND Media / Array (program, read, erase)
BW_media = (PageSize x Planes x Dies_per_channel x N_channels) / t_op [GB/s]
IOPS_media = (Planes x Dies_per_channel x N_channels) / t_op
t_op=t_PROG(write),t_R(read), or amortizedt_BERS / pages_per_block(erase).- This is the fundamental physical-array limit, parallelized across planes/dies/channels.
- TLC/QLC
t_PROGis much slower than SLCt_PROG— see SLC cache section below.
7. SLC (pseudo-SLC) Write Cache — Burst vs. Sustained
Burst_BW = BW_media computed in SLC mode (1 bit/cell; fastest t_PROG)
Sustained_BW = BW_media computed in native mode (TLC/QLC) minus folding overhead
Time_to_exhaust = SLC_Cache_Size / (Host_Write_Rate) [if no concurrent folding]
- Once the SLC cache fills, the drive must fold (relocate) data from SLC to native TLC/QLC in the background, competing with host writes for NAND interface + media bandwidth.
- A simple sustained-state approximation:
Sustained_BW ≈ BW_media(native) / (1 + FoldingOverheadFraction)
8. Sustained Random Write / Garbage-Collection-Bound Throughput
BW_host_sustainable = BW_media / WAF
WAF(Write Amplification Factor) = total NAND bytes written / host bytes written. GC relocates valid data, consuming NAND media + channel bandwidth that would otherwise serve host writes.- This is the stage most sensitive to workload (random vs. sequential, fill level, over-provisioning) — WAF can range from ~1.05x (sequential/FDP-managed) to 3-5x (worst-case random on a nearly-full drive).
9. Queue Depth / Concurrency Ceiling (Little's Law)
IOPS_QD = QD / Latency_per_op
- Even if every downstream stage has spare capacity, the host can't exceed this if it doesn't issue enough outstanding commands to keep all channels/dies busy.
- Useful as a sanity check:
Required_QD_to_saturate = IOPS_media_stage x Latency_per_op.
10. Power / Thermal Throttling
Sustained_BW_thermal = Peak_BW x Duty_Cycle_allowed(T, PowerBudget)
- No clean closed form — usually an empirical throttling curve from vendor data (clock/parallelism reduced above a temperature or power threshold).
- Matters most for sustained sequential workloads and small form factors (M.2 without heatsink).
Aggregation
Convert every stage to the same unit for the workload of interest (GB/s for large-block/sequential, IOPS for small-block/random — or convert one to the other via block size), then:
SSD_Throughput(workload) = min(
BW_host,
IOPS_controller (x block_size if comparing to BW),
BW_DRAM / IOPS_DRAM,
BW_ECC,
BW_channel_total,
BW_media (or BW_media / WAF for sustained random writes),
Burst_BW or Sustained_BW (SLC cache, depending on time regime),
IOPS_QD,
Sustained_BW_thermal
)
Compute this separately for each regime of interest — e.g., burst sequential write, sustained sequential write, random read QD1, random read QD32, sustained random write — since the binding stage typically differs across them.
Worked Example (template — fill in with real spec values)
| Stage | Formula | Inputs needed | Computed (GB/s) | Computed (IOPS @ 4K) | Notes |
|---|---|---|---|---|---|
| Host interface (PCIe) | §1 | lanes, gen | |||
| Controller compute | §2 | vendor spec or cores/clock | |||
| DRAM bandwidth | §3 | DRAM type, width, channels | |||
| ECC engine | §4 | codeword size, decode rate | |||
| NAND interface | §5 | interface speed, channels | |||
| NAND media (native) | §6 | t_PROG/t_R, planes, dies | |||
| SLC cache (burst) | §7 | SLC t_PROG, cache size | |||
| Sustained write (WAF-bound) | §8 | WAF estimate | |||
| Queue depth ceiling | §9 | QD, latency | |||
| Thermal | §10 | throttle curve | |||
| Bottleneck (min) |
TODO — Spec Values to Gather Per Drive
- [ ] PCIe generation and lane count
- [ ] Controller model and rated max IOPS (vendor spec)
- [ ] DRAM type/size/width (or DRAM-less / HMB)
- [ ] NAND type (SLC/TLC/QLC), channel count, interface speed (Toggle/ONFI, MT/s)
- [ ] t_PROG, t_R, t_BERS (from NAND datasheet or measured)
- [ ] Planes per die, dies per channel
- [ ] SLC (pseudo-SLC) cache size and mode (static/dynamic)
- [ ] Expected/measured WAF for target workload
- [ ] Thermal throttle temperature and duty-cycle behavior
Open Questions / Next Steps
- Decide which workload profiles to model first (seq read/write, rand read/write at a few QDs).
- Pick a reference drive (or several) to populate the worked-example table with real numbers.
- Cross-check computed bottleneck against published/measured benchmark numbers to validate the model.