Filesystems · Flash and Block Layers

Discard and TRIM: Telling the Device Which Blocks Are Free

How discard requests travel from the filesystem to the device, why they are advisory, what a secure erase adds, and the cost of queuing them badly.

A filesystem knows when a block is no longer referenced by any file. The device does not. The mapping table described in the flash translation layer still marks those pages valid, because the only thing that invalidates a page is a later write to the same logical address. A deleted file’s pages therefore remain live data from the controller’s point of view, and the collector copies them when it reclaims their block. On a workload that creates and deletes files, this is a large fraction of the copy traffic, all of it avoidable if the host says which addresses are free.

The request and what it guarantees

Discard is a request that the data at a range of logical blocks is no longer needed. The block layer passes it to the device, which may invalidate the corresponding mapping entries, erase the blocks containing them, or do nothing at all. The only guarantee is that a later read of a discarded range returns unspecified data, and that the request does not affect any other range.

The word “advisory” is load-bearing. A device is permitted to ignore a discard entirely, and some do for ranges below a minimum size or when the request rate is high. A device is not permitted to fail a write to a discarded range; the range must be writable afterwards, with the write’s data replacing whatever the discard left. The host therefore cannot use discard as an allocation oracle. Issuing discard and then reading back to see what changed tells nothing reliable about the device’s internals, because a device that zeroes the range and one that leaves it intact are both conforming.

Three names appear for the same concept at different layers. In the ATA command set the command is TRIM, and the related capability for ranges with a dataset management hint is the Data Set Management command. In the SCSI command set it is UNMAP, part of thin provisioning. In NVMe it is the Dataset Management command with a deallocate attribute on a range list. The block layer’s name is discard, and Linux exposes it through a request operation and a queue limit rather than through a single device command. The filesystem’s name is the same discard, exposed as a mount option or as an explicit ioctl.

Where the request originates

Two paths generate discards. The first is the filesystem, at the moment it frees blocks: deleting a file, truncating it, or punching a hole. The second is a userspace scan, which walks the filesystem’s free-space structures and issues discard for the ranges it finds free. The first is continuous and tied to the allocation lifetime; the second is periodic and covers the whole device.

Continuous discard has a cost that is not obvious from the device’s perspective. A filesystem that frees a single block must issue a request for that block, and the device must process it. On a rotational device this is meaningless; on a flash device it invalidates one page. A workload of many small deletes produces many small discard requests, each carrying command overhead through the block layer, the host bus adapter, and the device’s queue. The overhead per request can exceed the value of the invalidation, which is why the usual configuration issues discard in batches rather than per free.

Batched discard takes two forms. A filesystem can accumulate freed extents and issue one request per batch, which is what a mount option with a granularity or a periodic flush of a discard list does. Or the host can issue discard only for ranges large enough to matter, filtering small extents. Both trade the timeliness of invalidation for lower command overhead.

/* Batch free extents into discard requests. discard_range() is the
 * device-specific call and is elided. */
#define DISCARD_MIN_BYTES (64 * 1024)

static int discard_extents(int fd, const struct extent *ext, size_t n)
{
    uint64_t start = 0, len = 0;

    for (size_t i = 0; i < n; i++) {
        if (ext[i].len < DISCARD_MIN_BYTES)
            continue;
        if (len && ext[i].start == start + len) {
            len += ext[i].len;              /* coalesce adjacent extents */
            continue;
        }
        if (len && discard_range(fd, start, len) < 0)
            return -1;
        start = ext[i].start;
        len = ext[i].len;
    }
    return len ? discard_range(fd, start, len) : 0;
}

The coalescing step matters more than it appears. Free space in a filesystem is fragmented, and adjacent free extents are common after a large delete but not after a long period of mixed allocation. Coalescing turns a list of small ranges into a smaller list of larger ones, and the device’s internal cost is per request, so the reduction is direct.

What discard does to the mapping and to amplification

A discard that the device honors invalidates the mapping entries for the range. Those pages become invalid immediately, without being rewritten, and the blocks containing them become candidates for collection with a lower valid count. The effect on the amplification formula from garbage collection is a reduction in the valid fraction u/n of the victim blocks, which lowers the copy cost per reclaimed block.

For a workload that deletes as much as it writes, the effect is the difference between a drive that copies deleted data forever and one that does not. Without discard, a filesystem at 50 percent utilization still presents the device with a logical address space that is 100 percent mapped, because every freed block’s last-written contents are still valid to the controller. The device’s free-page pool is then determined only by how much logical capacity is unwritten, not by how much the filesystem considers free. With discard, the two coincide, and the collector can reclaim blocks whose contents the filesystem has abandoned.

The same effect appears in thin-provisioned storage, where the layer below the block device allocates physical space only for written or mapped ranges. Discard in that context is the signal that a range can be deallocated, and the space saving is real capacity rather than just device efficiency. This is why discard is often described as a thin-provisioning primitive that flash happens to benefit from, rather than a flash primitive.

Discard and confidentiality

A discarded range returns unspecified data, and unspecified includes the previous contents. On a device that simply invalidates mapping entries, the physical pages still hold the old bytes until the block is erased, and a read of the discarded range, if the device chooses to satisfy it from a stale mapping or if the range is later reallocated, can return them. Filesystem-level discard is therefore not a sanitization mechanism.

A secure erase is a different operation with a stronger contract: the device is instructed to erase all user data, including data in over-provisioned blocks and in the mapping table’s remnants. The ATA command set defines a security erase and an enhanced security erase, and NVMe defines a sanitize operation with crypto-erase and block-erase variants. Crypto-erase destroys the media encryption key, which renders the ciphertext in every block unreadable regardless of whether the block is erased, and is the faster option on a self-encrypting drive. These operations apply to the whole device or namespace, not to a range, and they are not a substitute for per-range discard in normal operation.

The distinction has a practical consequence for anyone discarding before handing a device on. A filesystem-level discard leaves the bytes in place. Only the device-level erase or sanitize operation, or overwriting the range with new data, removes them from the medium.

Cost, latency, and when not to discard

A discard request occupies a slot in the device’s queue and, on some devices, blocks the queue while the controller processes the range list. Large discards covering many blocks can take longer than the equivalent write, because the controller may erase the affected blocks immediately rather than deferring the work. A filesystem that issues discard synchronously on every free operation adds that latency to the free path, and the free path is often on the critical path of a rename, an unlink, or a truncate.

The common configuration therefore defers discard to a periodic pass, which is what fstrim does: it walks the free-space structures and issues requests for the free ranges, typically on a timer rather than on every free. The trade is that space freed between passes is not signalled until the next one, so the device’s view lags the filesystem’s by up to the interval. For a device with enough over-provisioning to absorb that lag, the difference is small; for one without, the lag is the period during which amplification is higher than necessary.

Discard also interacts with the write cache and with ordering. A discard is not ordered against writes to other ranges, and a device may process a discard concurrently with writes. A range that is discarded and then written is fine, because the write supersedes the discard; a range that is written and then discarded loses the write, which is the intended meaning. The block layer’s queue must therefore not reorder a discard ahead of an earlier write to an overlapping range, and the device’s internal processing must respect that per-range order. The general ordering rules are the subject of flush, FUA, and write cache ordering.

Finally, discard is not free on the host side either. The filesystem must find the free extents, which for a large filesystem means walking the free-space bitmap or extent tree, and that walk touches metadata that may not be in cache. A periodic full-device discard on a busy system is a metadata read of the whole allocation structure, and on a multi-terabyte filesystem that is a substantial amount of I/O whose only purpose is to inform the device. Limiting discard to recently freed ranges, or to filesystems where the free-space structures are cheap to walk, is a reasonable response, and it is why the feature is configurable rather than unconditional.

References

  1. ATA/ATAPI Command Set - 4 (ACS-4) — T13 / INCITS
  2. NVM Express Base Specification — NVM Express, Inc.
  3. util-linux fstrim manual page — util-linux project