SPDK From First Principles

SPDK deep learning path

Chapter 4: NAND Flash And SSD Internals

SPDK programmers do not usually program NAND directly. They program bdevs,

Source: drafts/hardware/04-nand-ssd-internals.md

Chapter Goal

SPDK programmers do not usually program NAND directly. They program bdevs, NVMe namespaces, queue pairs, pollers, and DMA buffers. But SSD behavior leaks through every abstraction: latency cliffs, write amplification, endurance limits, trim behavior, power-loss protection, thermal throttling, and zoned constraints all originate below the block interface.

This chapter gives you the SSD mental model needed to understand why the same SPDK workload can be smooth on one drive and chaotic on another. It does not try to reverse-engineer a vendor's firmware. The goal is more useful: understand the physical constraints and the host-visible contracts well enough to design SPDK systems that do not accidentally fight the drive.

SPDK's own doc/ssd_internals.md opens with the same warning: its model is for software developers, not a strict description of every SSD. Keep that boundary in mind throughout this chapter. When this chapter uses SPDK's FTL library as a source anchor, it is using a readable software FTL to make hidden concepts concrete. It is not claiming that a hardware SSD implements the same data structures or recovery protocol.

Beginner Mental Model

Think of an SSD as a warehouse that cannot overwrite labels in place. When you update "box 100," the warehouse puts the new box somewhere else, updates an index card saying "box 100 is now on shelf 8," and later cleans up the old shelf.

host view:
  LBA 100 -> latest bytes

SSD internal view:
  old physical page A: stale copy of LBA 100
  new physical page B: current copy of LBA 100
  mapping table: LBA 100 -> physical page B

That mapping table is the flash translation layer, or FTL. It is the heart of a normal SSD. The host sees logical blocks. The SSD internally manages pages, erase blocks, dies, channels, bad blocks, wear, error correction, and background cleanup.

The most important beginner correction is this: an LBA is not a fixed physical place on NAND. It is a name in a mapping table. If a host overwrites an LBA, the device usually writes new NAND somewhere else and changes the mapping. That single fact explains most SSD surprises: why free space matters, why random overwrite workloads age badly, why trim can help later, why preconditioning changes benchmarks, and why ZNS changes the host contract.

SPDK's bdev layer deliberately sits above these details. The official SPDK bdev programming guide defines a bdev as a device that supports reads and writes in fixed-size blocks, commonly 512 or 4096 bytes, and notes that those devices may be software constructs or physical NVMe SSDs. That is the right abstraction for SPDK applications, but the abstraction does not erase the physics underneath.

NAND Geometry

The names vary by vendor and generation, but the useful hierarchy is:

SSD controller
  channels
    packages
      dies
        planes
          blocks / erase blocks
            pages
              sectors / codewords

The important constraints are:

block first.

  • Read is page-sized or near page-sized internally.
  • Program writes data to pages, often with restrictions on order and repetition.
  • Erase works on a much larger erase block.
  • A page generally cannot be overwritten in place without erasing the whole
  • Erase wears out the media.

The page is the convenient unit for reading and programming data, while the erase block is the unit that can be reset for reuse. This mismatch is the core economic problem of NAND. If the host rewrites one 4 KiB block, the device cannot simply flip the old bytes back and program the new bytes in place. It has to preserve any still-valid data sharing the same erase block, write replacement data somewhere clean, and eventually erase the whole block.

doc/ssd_internals.md:18 describes erase blocks as large implementation-specific units and explains the asymmetric write/erase behavior. It also points out that the device exposes fixed-size logical blocks, usually 512 B or 4 KiB, even though those blocks do not statically map to fixed NAND locations.

That distinction matters for SPDK because SPDK can remove a lot of host-side overhead. Once kernel scheduling, page cache behavior, and interrupt overhead are out of the way, the shape of the workload presented to the SSD becomes more visible. A clean queue path does not make random overwrites cheaper inside NAND.

Why SSDs Remap Writes

Suppose a filesystem overwrites LBA 7:

before:
  LBA 7 -> physical page 111

write new data to LBA 7:
  controller chooses empty physical page 829
  controller writes data there
  controller changes map: LBA 7 -> physical page 829
  physical page 111 becomes invalid

This is out-of-place update. It turns random host overwrites into sequential-ish media programs. The price is garbage collection: eventually the device must reclaim blocks full of stale physical pages.

SPDK's FTL library exposes the same ideas in software. doc/ftl.md:11 defines L2P, the logical-to-physical map. lib/ftl/ftl_core.h shows that an SPDK FTL device owns bands, a free-band list, a current write target, and the L2P table:

/* Array of bands */
struct ftl_band                 *bands;

/* Number of operational bands */
uint64_t                        num_bands;

/* Next write band */
struct ftl_band                 *next_band;

/* Free band list */
TAILQ_HEAD(, ftl_band)          free_bands;

/* Closed bands list */
TAILQ_HEAD(, ftl_band)          shut_bands;

/* Number of free bands */
uint64_t                        num_free;

/* Logical -> physical table */
void                            *l2p;

This snippet is useful because it names the ownership problem. Something has to own the reusable regions (bands), know which regions are available (free_bands and num_free), choose where new writes go (next_band), and remember where the latest version of each logical block lives (l2p).

Hardware firmware uses vendor-specific structures, but it still needs answers to the same questions. Where can the next write land? Which old physical locations became stale? Which blocks can be erased without losing live data? How can the mapping be rebuilt after a crash or power loss?

After the function has resolved any write-after-write race, the SPDK FTL L2P update path shows why update ordering is not arbitrary:

if (current_addr != FTL_ADDR_INVALID) {
        /* For recovery from SHM case valid maps need to be set before l2p set and
         * invalidated after it */

        /* DO NOT CHANGE ORDER - START */
        ftl_nv_cache_set_addr(dev, lba, new_addr);
        ftl_l2p_set(dev, lba, new_addr);
        ftl_invalidate_addr(dev, current_addr);
        /* DO NOT CHANGE ORDER - END */
        return;
} else {
        uint64_t trim_seq_id = get_trim_seq_id(dev, lba);
        uint64_t new_seq_id = ftl_nv_cache_get_chunk_from_addr(dev, new_addr)->md->seq_id;

        /* Check if region hasn't been trimmed during IO */
        if (new_seq_id < trim_seq_id) {
                return;
        }
}

The code is handling real races in a log-structured design: two writes to the same LBA may be in flight, a trim may arrive while a write is still completing, and crash recovery still needs to pick the newest valid copy. That is why sequence information exists. An SSD cannot merely remember "some copy of LBA 7." It has to identify the current copy even when writes, metadata persistence, trim, and recovery overlap.

Pages, Erase Blocks, And Write Amplification

If the host writes 4 KiB, the SSD may have to write much more than 4 KiB internally. Write amplification is:

physical bytes written to NAND / logical bytes written by host

Write amplification comes from metadata, garbage collection, relocation, parity/internal RAID, write shaping, read-modify-write, and poor alignment. A drive with a write amplification of 3 writes three NAND bytes for every host byte. That matters for both performance and endurance.

A simplified garbage collection cycle:

erase block before GC:
  [valid][stale][stale][valid][stale][free? no][valid][stale]

GC:
  read valid pages
  write valid pages elsewhere
  erase whole block
  return block to free pool

erase block after GC:
  [empty][empty][empty][empty][empty][empty][empty][empty]

doc/ssd_internals.md:61 through doc/ssd_internals.md:71 gives the same high-level GC sequence. SPDK FTL models reusable regions as bands. doc/ftl.md:80 explains relocation: valid blocks are copied so a band can be reused.

The band metadata makes the log-structured model explicit:

enum ftl_band_state {
        FTL_BAND_STATE_FREE,
        FTL_BAND_STATE_PREP,
        FTL_BAND_STATE_OPENING,
        FTL_BAND_STATE_OPEN,
        FTL_BAND_STATE_FULL,
        FTL_BAND_STATE_CLOSING,
        FTL_BAND_STATE_CLOSED,
        FTL_BAND_STATE_MAX
};

An empty reusable region starts as FREE. It becomes OPEN while accepting writes, then FULL, CLOSING, and CLOSED as data and tail metadata are settled. That lifecycle is a software mirror of the device-level problem: allocate clean space, write sequentially, preserve enough metadata to recover, then eventually relocate live data and free the region again.

The metadata kept for a band explains what must survive beyond the raw user payload:

/* Current physical address of the write pointer */
ftl_addr                addr;

/* Offset from the band's start of the write pointer */
uint64_t                offset;

/* Band's state */
enum ftl_band_state     state;

/* Sequence ID when band was opened */
uint64_t                seq;

/* Sequence ID when band was closed */
uint64_t                close_seq_id;

/* Number of times band was fully written (ie. number of free -> closed state cycles) */
uint64_t                wr_cnt;

/* Durable format object id for P2L map, allocated on shared memory */
ftl_df_obj_id           df_p2l_map;

/* CRC32 checksum of the associated P2L map when band is in closed state */
uint32_t                p2l_map_checksum;

The write pointer records where the next program operation belongs. Sequence IDs record age, which recovery uses to resolve multiple copies of the same LBA. The write count is wear information. The P2L checksum exists because recovery cannot trust metadata blindly. If a mapping record is corrupted, a software FTL needs a way to detect that before rebuilding a bad L2P table.

The practical SPDK lesson is that write size and write pattern are not just throughput details. They influence how much relocation the lower layer must do. If a bdev stack turns a clean sequential stream into scattered small overwrites, the SSD may eventually pay for that transformation in background work.

Parallelism: Channels, Dies, Planes

SSDs are fast because they do many slow things in parallel. One NAND die is not magic. A controller spreads reads and writes across channels and dies, much like a storage-aware RAID engine. Sequential writes can fill parallel lanes efficiently. Random small writes may force more metadata work, read-modify-write, and garbage collection.

This is why queue depth helps until it does not. More outstanding work gives the controller scheduling freedom. The controller can reorder independent requests, fill channel-level parallelism, and hide individual NAND latencies. But too much outstanding work can increase tail latency, make flushes wait behind a backlog, or cause thermal and power throttling to show up sooner.

SPDK exposes queue control at higher layers. The bdev layer gives applications asynchronous requests on per-thread I/O channels, and the SPDK bdev programming guide emphasizes that requests are represented by spdk_bdev_io objects and submitted on associated I/O channels. The NVMe queue-machine chapter will explain how those bdev requests become NVMe submission queue entries.

The internal SSD lesson is simple: queue depth is a tool for exposing parallelism, not a guarantee of lower latency. If the drive is already doing emergency GC, more queue depth often gives it more work to delay.

Overprovisioning And Spare Area

An SSD usually has more physical NAND than it reports as logical capacity. That spare area is overprovisioning. It gives the controller room to:

  • keep free erase blocks available,
  • replace bad blocks,
  • absorb bursts,
  • lower write amplification,
  • spread wear,
  • recover from power failures or metadata updates.

SPDK's FTL configuration makes overprovisioning explicit:

/*
 * FTL configuration.
 *
 * NOTE: Do not change the layout of this structure. Only add new fields at the end.
 */
struct spdk_ftl_conf {
        /* Device's name */
        char                    *name;

        /* Device UUID (valid when restoring device from disk) */
        struct spdk_uuid        uuid;

        /* Percentage of base device blocks not exposed to the user */
        uint64_t                overprovisioning;

That field mirrors what hardware SSDs do internally: not all physical blocks are surfaced to the host. A cloud system can create a similar effect by not filling a drive to 100 percent logical occupancy. Leaving logical space unused can help because the drive sees more blocks become deallocated or never written, which gives garbage collection more cheap victims.

Misconception to kill: "A 7.68 TB SSD contains exactly 7.68 TB of NAND." It almost certainly contains more raw NAND and exposes less after reserved area, metadata, bad block handling, parity, formatting, and vendor policy.

Overprovisioning does not make a pathological workload free. It changes the probability that GC can find mostly-stale erase blocks. When the spare pool gets small, the controller may need to move more live data before it can accept new writes. That is where write latency cliffs come from.

Wear Leveling And Endurance

Each erase block has a finite program/erase lifetime. Wear leveling tries to avoid killing a small subset of blocks while others stay fresh. Dynamic wear leveling spreads new writes. Static wear leveling occasionally moves cold data so old blocks are not permanently occupied by rarely changing data.

Endurance is usually specified as drive writes per day (DWPD) or total bytes written (TBW/PBW). Write amplification connects host workload to NAND wear:

NAND writes = host writes * write amplification

The important detail is that the host can consume endurance without seeing the extra writes. A random synchronous overwrite workload may write one logical unit at the bdev layer while causing metadata writes, relocated valid-page writes, and later erase cycles below it.

Cloud volume systems should treat endurance as a shared resource. A noisy tenant with random sync writes can consume more NAND lifetime than raw host bytes suggest. Rate limits that consider only submitted bytes miss the lower-layer cost. This is one reason production storage systems often track write shape, flush frequency, drive health, spare blocks, and tail latency together.

TRIM, UNMAP, And Deallocate

When the host deletes data, the SSD cannot infer that from ordinary overwrites or filesystem metadata. It needs an explicit hint. The command family is called TRIM in SATA, UNMAP in SCSI, and deallocate in NVMe Dataset Management.

When the SSD knows LBAs are no longer live, GC can skip their old physical pages. doc/ssd_internals.md:44 explains this from the device perspective. The SPDK bdev API exposes the same idea directly:

/**
 * Submit an unmap request to the block device. Unmap is sometimes also called trim or
 * deallocate. This notifies the device that the data in the blocks described is no
 * longer valid. Reading blocks that have been unmapped results in indeterminate data.
 *
 * \ingroup bdev_io_submit_functions
 *
 * \param desc Block device descriptor.
 * \param ch I/O channel. Obtained by calling spdk_bdev_get_io_channel().
 * \param offset The offset, in bytes, from the start of the block device.
 * \param nbytes The number of bytes to unmap. Must be a multiple of the block size.
 * \param cb Called when the request is complete.
 * \param cb_arg Argument passed to cb.

The phrase "indeterminate data" is doing real work. Unmap is not a promise that future reads will return zeros unless the specific device contract says so. It is primarily a liveness statement: the old contents no longer need to be preserved.

In SPDK FTL, trim has state in the device object:

/* Trim submission queue */
TAILQ_HEAD(, ftl_io)            trim_sq;

/* Trim valid map */
struct ftl_bitmap               *trim_map;
struct ftl_md                   *trim_map_md;
size_t                          trim_qd;
bool                            trim_in_progress;
struct ftl_md_io_entry_ctx      trim_md_io_entry_ctx;

That explicit state explains why unmap is not a magic zero-cost cleanup button. The system has to record what was trimmed, persist or mirror enough state for recovery, and coordinate with writes that may already be in flight. doc/ftl.md also notes that SPDK FTL currently constrains trims to 4 MiB alignment because metadata size and dirty-shutdown consistency matter.

Do not overpromise unmap. It is usually a hint or logical deallocation operation, not a secure erase guarantee. It may improve performance later, but issuing unmap in the foreground can still cost time now.

ECC, Read Disturb, Retention, And Bad Blocks

NAND stores charge. Charge leaks. Reads can disturb neighboring cells. Program operations are imperfect. SSDs use error-correcting codes, read retry, refresh, bad block maps, and media management to hide this from the host.

Symptoms that may bubble up:

retries.

  • A read that used to be fast becomes slow because the controller performs
  • A drive starts reporting media errors or health warnings.
  • Latency increases as the drive refreshes or relocates data.
  • SMART / NVMe health data shows spare depletion or temperature warnings.

Beginner trap: a successful read does not mean the media was easy to read. It only means the controller recovered the data within its error budget.

This matters in SPDK because an efficient polling path can expose drive-level latency variation with less host noise. If a workload shows rare long reads, the cause might be above SPDK, inside SPDK, in PCIe/NVMe transport behavior, or in the media management path. Do not assume "read-only workload" means "no internal drive work." Refresh, retry, and relocation can still occur.

Power-Loss Protection

Power-loss protection is not one feature. It can include capacitors, firmware protocols, non-volatile cache, metadata journaling, and conservative completion rules.

Without PLP, a device may complete a write when data is in volatile cache. With PLP, the same completion may be safe because the cache can be drained after power loss. Flush latency and sustained sync-write performance often differ dramatically between consumer and enterprise SSDs because of PLP.

SPDK exposes flush at the bdev layer. The spdk_bdev_flush documentation in include/spdk/bdev.h states that for devices with volatile caches, data is not guaranteed to be persistent until flush completes. That is a host-visible durability contract, but the physical truth still depends on the drive.

For SPDK users, the practical rule is: do not infer durability from performance. Read the device data, test power-fail behavior when possible, and preserve flush semantics through virtual bdev stacks. A virtual bdev that acknowledges flush too early can silently destroy the guarantee the application thinks it has.

Thermal And Power Throttling

SSDs are active computers. Controllers and NAND heat up. Firmware may reduce performance to stay inside thermal or power envelopes. This creates confusing behavior: a benchmark looks excellent for 60 seconds, then collapses; or reads stay stable while writes slow down.

Operational hints:

  • Check drive temperature and warning logs.
  • Run long enough benchmarks to hit steady state.
  • Compare cold-start, preconditioned, and sustained measurements.
  • Watch tail latency, not just average throughput.

Thermal throttling is especially easy to misread in SPDK tests because the host path may be stable while the device changes its own service rate. If CPU usage, reactor load, and queue submission behavior look unchanged while completion latency grows, check the drive before rewriting the application.

Zoned Namespaces As The FTL Leaking Upward

Zoned Namespaces (ZNS) expose some placement rules to the host. Instead of pretending every LBA can be overwritten freely, the device divides capacity into zones with write pointers. The host writes sequentially within zones and resets zones when data is no longer needed.

The official NVM Express ZNS page describes ZNS as a command set where an NVMe namespace is divided into zones that must be written sequentially. It also says ZNS is intended to reduce device-side write amplification, overprovisioning, and DRAM while improving tail latency, throughput, and capacity. The NVM Express ZNS specification PDF places "Theory of operation," zone states, write pointers, and host considerations at the center of the model.

SPDK exposes zoned bdev concepts in include/spdk/bdev_zone.h:

enum spdk_bdev_zone_type {
        SPDK_BDEV_ZONE_TYPE_CNV         = 0x1,
        SPDK_BDEV_ZONE_TYPE_SEQWR       = 0x2,
        SPDK_BDEV_ZONE_TYPE_SEQWP       = 0x3,
};

enum spdk_bdev_zone_state {
        SPDK_BDEV_ZONE_STATE_EMPTY      = 0x0,
        SPDK_BDEV_ZONE_STATE_IMP_OPEN   = 0x1,
        /* OPEN is an alias for IMP_OPEN. OPEN is kept for backwards compatibility. */
        SPDK_BDEV_ZONE_STATE_OPEN       = SPDK_BDEV_ZONE_STATE_IMP_OPEN,
        SPDK_BDEV_ZONE_STATE_FULL       = 0x2,
        SPDK_BDEV_ZONE_STATE_CLOSED     = 0x3,
        SPDK_BDEV_ZONE_STATE_READ_ONLY  = 0x4,
        SPDK_BDEV_ZONE_STATE_OFFLINE    = 0x5,
        SPDK_BDEV_ZONE_STATE_EXP_OPEN   = 0x6,
        SPDK_BDEV_ZONE_STATE_NOT_WP     = 0x7,
};

Those states are not cosmetic. They are the API surface for a different storage contract. A normal block device says, within alignment and capacity limits, "you may write this LBA." A sequential-write-required zone says, effectively, "you may write at the current write pointer, then the pointer advances."

SPDK carries the core zone information in a compact structure:

struct spdk_bdev_zone_info {
        uint64_t                        zone_id;
        uint64_t                        write_pointer;
        uint64_t                        capacity;
        enum spdk_bdev_zone_state       state;
        enum spdk_bdev_zone_type        type;
};

At the NVMe command-set level, SPDK's NVMe spec header mirrors the descriptor fields the device reports:

/** Zone Capacity (in number of LBAs) */
uint64_t zcap;

/** Zone Start LBA */
uint64_t zslba;

/** Write Pointer (LBA) */
uint64_t wp;

The mental model is: ZNS shifts some placement responsibility from opaque firmware to host software so the system can reduce write amplification and improve predictability. The host earns those benefits only if it actually writes sequentially, tracks zone state, limits open/active zones, handles reset, and routes data lifetimes intelligently.

Misconception to kill: "ZNS is just a faster normal SSD." It is a different contract. Host software must respect zone state and write-pointer rules.

Latency Cliffs

A latency cliff happens when a workload crosses an internal threshold:

  • free block pool becomes low,
  • background GC cannot keep up,
  • SLC cache fills,
  • thermal limit engages,
  • metadata cache misses increase,
  • queue depth hides then amplifies tail latency,
  • drive reaches a write cliff after preconditioning.

SPDK can make latency cliffs more visible because it removes scheduler and interrupt overhead. That is a benefit, but it also means the application must understand what the hardware is doing.

The SPDK FTL relocation structure shows one software version of a latency-cliff mechanism: when reusable space is scarce, a background mover consumes I/O resources to copy valid data before space can be reused.

struct ftl_reloc {
        /* Device associated with relocate */
        struct spdk_ftl_dev *dev;

        /* Indicates relocate is about to halt */
        bool halt;

        /* Band which are read to relocate */
        struct ftl_band *band;

        /* Bands already read, but waiting for finishing GC */
        TAILQ_HEAD(, ftl_band) band_done;
        size_t band_done_count;

        /* Flags indicating reloc is waiting for a new band */
        bool band_waiting;

        /* Maximum number of IOs per band */
        size_t max_qdepth;

The important idea is not the exact type names. The important idea is that garbage collection has state, queue depth, target regions, and work that competes with foreground I/O. When a drive enters on-demand cleanup, the host sees a cliff because writes now wait for invisible read-copy-erase work.

Source Anchors

fixed media locations.

  • doc/ssd_internals.md:18: erase blocks and asymmetric erase/program behavior.
  • doc/ssd_internals.md:27: logical blocks as firmware constructs rather than
  • doc/ssd_internals.md:61: garbage collection sequence.
  • doc/ftl.md:11: L2P map.
  • doc/ftl.md:25: bands and sequential writing.
  • doc/ftl.md:80: relocation/garbage collection.
  • doc/ftl.md:109: FTL metadata.
  • doc/ftl.md:149: FTL trim alignment constraints.
  • include/spdk/bdev.h:1886: bdev unmap/trim/deallocate contract.
  • include/spdk/bdev.h:1936: flush and volatile cache persistence contract.
  • include/spdk/bdev_zone.h:28: zoned bdev zone types.
  • include/spdk/bdev_zone.h:42: zoned bdev zone states.
  • include/spdk/bdev_zone.h:55: spdk_bdev_zone_info.
  • include/spdk/ftl.h:82: FTL configuration.
  • include/spdk/ftl.h:89: overprovisioning configuration.
  • include/spdk/nvme_spec.h:4625: NVMe ZNS zone type.
  • include/spdk/nvme_spec.h:4629: NVMe ZNS zone states.
  • include/spdk/nvme_spec.h:4639: NVMe ZNS zone descriptor.
  • include/spdk/nvme_spec.h:4675: zone capacity, start LBA, and write pointer.
  • lib/ftl/ftl_core.h:103: array of bands.
  • lib/ftl/ftl_core.h:121: L2P table.
  • lib/ftl/ftl_core.h:130: valid map.
  • lib/ftl/ftl_core.h:145: relocation manager.
  • lib/ftl/ftl_core.h:148: core thread ownership.
  • lib/ftl/ftl_core.h:168: trim submission queue.
  • lib/ftl/ftl_core.h:171: trim valid map.
  • lib/ftl/ftl_internal.h:76: P2L mapping explanation.
  • lib/ftl/ftl_band.h:34: band states.
  • lib/ftl/ftl_band.h:54: band write pointer metadata.
  • lib/ftl/ftl_band.h:75: band sequence IDs.
  • lib/ftl/ftl_band.h:87: P2L checksum.
  • lib/ftl/ftl_l2p.c:148: L2P update path for cache/user writes.
  • lib/ftl/ftl_l2p.c:213: L2P update path for base-device relocation writes.
  • lib/ftl/ftl_reloc.c:44: relocation state object.

Operational Lab

Use a paper model with four erase blocks, each containing four pages. The host writes LBAs in this order:

0, 1, 2, 3, 0, 1, 4, 5, 0, 6

Rules:

move its valid pages elsewhere, then erase it.

  • A page can be programmed once.
  • Updating an LBA writes a new physical page.
  • The old physical page for that LBA becomes stale.
  • When no empty page exists, choose the erase block with the fewest valid pages,

Tasks:

  1. Draw the mapping after each write.
  2. Count stale pages after the tenth write.
  3. Pick a GC victim.
  4. Count how many extra page writes GC creates.
  5. Compute write amplification for this tiny example.

Then repeat the exercise with two extra spare pages that are not exposed to the host. The host-visible capacity is the same, but the device has more internal room. Notice how the victim choice and write amplification change. That is the smallest useful model of overprovisioning.

This exercise is intentionally small. Real SSDs have far more levels, but the mapping pressure is the same.

Source Reading Exercise

Read doc/ftl.md:25 through doc/ftl.md:52. Then open lib/ftl/ftl_band.h:34 through lib/ftl/ftl_band.h:94.

Answer:

  • Which states represent an empty reusable region?
  • Which states represent a region accepting writes?
  • Where is the write pointer stored?
  • Why does a band need a close sequence ID?
  • Why does the P2L map need a checksum?

Then read include/spdk/bdev.h:1886 through include/spdk/bdev.h:1939 and include/spdk/bdev_zone.h:28 through include/spdk/bdev_zone.h:61.

Answer:

  • What does SPDK promise after unmap?
  • What does it not promise after unmap?
  • Which fields tell a zoned application where it can write next?
  • Why does a zoned bdev need both capacity and write_pointer?

Self-Check

  1. Why is overwrite-in-place a bad model for NAND SSDs?
  2. What is write amplification?
  3. Why does overprovisioning improve random write behavior?
  4. Why can an unmap improve future garbage collection?
  5. Why can a read be slow even when no host write is active?
  6. How does ZNS change the host/device contract?
  7. Why is preconditioning necessary for serious SSD benchmarks?
  8. Why can a flush be fast on one SSD and expensive on another?
  9. Why can a higher queue depth improve throughput but worsen tail latency?

References