Storage Write Amplification Tests: Separate Application Bytes from Erase Work on Raw Flash
Measure raw-flash amplification with separate ledgers for useful application payload, filesystem program requests, and erase operations by block. A single “bytes written” counter cannot explain whether a settings workload creates extra programming, repeatedly erases a small region, or merely repeats unchanged application requests.
This method uses littlefs v2.11.2 callbacks as one observation boundary. It targets application settings and file churn on raw storage, not managed-eMMC internal controller work or bootloader swap-mode wear. The arithmetic example is hypothetical; no device endurance or workload result is claimed.
1. Define the denominator before collecting traffic
Choose what counts as useful application bytes. One defensible metric is the serialized payload of each distinct, durably accepted product update. Another is every accepted save request, including an unchanged value. Both can be useful, but they answer different questions and must never be silently mixed.
Keep rejected requests, retries and no-change requests as separate counters. If the application serializes a 32-byte setting into a 64-byte envelope, record both semantic payload and serialized bytes. Otherwise an encoding change can appear as a filesystem regression or improvement simply because the denominator changed.
Use a transaction identifier to connect each product operation to its associated storage work. Where the filesystem shares a later commit or compaction across several transactions, attribute the work to the workload window rather than inventing an exact per-request split.
2. Instrument two storage boundaries when needed
The littlefs v2.11.2 configuration exposes read, program, erase and synchronization callbacks. Program requests include block, offset and size; erase requests identify a logical block. Its logical block size can be larger than the hardware erase unit, so a callback count is not automatically a physical erase-command count.
| Observation boundary | Record | Interpretation limit |
|---|---|---|
| Application. | Transaction, payload size, changed/unchanged state, final result and durability point. | Does not include filesystem metadata work. |
| littlefs callback. | Operation, logical block, offset, requested size, start/end and return code. | Driver expansion or retries may remain hidden. |
| Physical driver command. | Device/erase-unit identity, command, programmed length, completion and error state. | Only as accurate as the driver and hardware observations. |
Map logical blocks to their actual underlying erase units. If a driver divides one logical erase into several hardware commands, count those commands at the physical boundary and keep the logical count separately. Do not add the two levels together as independent erase work.

3. Preserve attempts, successes and uncertainty
Record attempted program bytes and successful-return program bytes separately. A failed operation can still have changed media before reporting an error. Label its physical effect unknown unless the driver/device contract or instrumentation establishes more.
For erases, maintain a counter per physical erase unit as well as the total. Include failures and retries in a separate event stream. A mean count can hide a frequently reused block, so retain the distribution, maximum and untouched blocks within the measured region.
Keep instrumentation outside the measured storage path. A verbose trace written back to the same flash can dominate the workload. Prefer bounded RAM counters, an external test channel or deferred export, and measure whether tracing materially changes timing or scheduling.
4. Compare workloads with identical durability semantics
Start each run from a recorded disposable image or a reproducible preparation sequence. Exclude production calibration and identity data. Record littlefs configuration, driver revision, erase/program geometry, file layout, free-space state and the initial block counters.
| Workload | Controlled comparison | Question |
|---|---|---|
| Repeated unchanged save requests. | Same payload and save boundary each time. | Which layer avoids redundant work, if any? |
| One changing settings object. | Fixed serialization size and durability requirement. | What program and erase work accompanies each accepted change? |
| Many small independently replaced files. | Same total useful payload as a larger object. | How does metadata shape affect the window totals? |
| Steady replacement/delete churn. | Same operations on fresh and aged store states. | Does compaction or allocation history change the distribution? |
| Supported low-space state. | Same admitted operations and error policy. | Does extra work or rejection appear near the product’s limit? |
Do not compare a synchronized save in one build with a buffered write in another and call the reduction a free optimization. The file write/sync contract makes that distinction explicit. If batching is evaluated, report its changed acknowledgement and loss window as a separate product decision.
The littlefs design describes metadata logs and compaction. This motivates testing both fresh and churned states; it does not supply a universal amplification factor. Hold file names, sizes, operation order and configuration constant when isolating one change.
5. Calculate named metrics, not one blended number
Define callback program amplification as successful-return program-request bytes divided by the selected application-byte denominator over the same completed workload window. If the denominator is zero, report the ratio as undefined and retain the absolute storage work.
Report erase work separately: completed physical erase commands, bytes covered by those erases, and counts per erase unit. Erased bytes and programmed bytes are different operations. Adding them and calling the result physical write amplification loses that distinction.
For a hypothetical window of 1,000 accepted 32-byte updates, useful payload is 32,000 bytes. If callback program requests total 256,000 successful-return bytes, the callback ratio is 8.0. This is illustrative arithmetic, not a measured littlefs result or the device’s internal amplification.
Suppose the same window contains 12 completed 4,096-byte physical erases: 49,152 erased bytes. Across eight units, counts of 3, 3, 2, 1, 1, 1, 1 and 0 have mean 1.5 and maximum 3. The distribution exposes concentration that the total alone cannot show.
6. Turn the trace into a bounded change decision
Investigate unexpected work by aligning compaction events, retries, file replacements and background activity with the transaction window. Separate setup, steady-state work and final synchronization. Include a settling boundary so one run does not defer work into the next run’s measurement.
Accept an optimization only when useful behavior and required durability remain intact. Recheck data correctness, storage-full handling, restart recovery and latency under the same workload. Lower programming traffic that loses accepted settings is not an improvement.
Release the event schema, reproducible workload, block map and before/after distributions. These explain how the application uses raw flash; projecting service life still requires the exact component’s endurance conditions and a separately justified operating model.