littlefs Garbage Collection Latency: Put Worst-Case Storage Work in the Scheduling Budget
A small application write can trigger work unrelated to the number of new payload bytes: finding free blocks, compacting metadata, completing consistency work, or waiting for flash operations. Budget the complete storage operation on an aged, occupied filesystem. A timing run immediately after formatting is a useful baseline, but a weak release test.
This procedure applies to littlefs v2.11.2 and a synchronous block-device integration. It produces a latency qualification matrix and a scheduling decision. It does not establish a universal worst-case execution time or prescribe a real-time operating system architecture. Controller buffering, execute-in-place restrictions, flash characteristics, and the product’s concurrency model remain integration-specific.
1. Define the operation whose deadline matters
Name the externally meaningful operation before choosing a timer. A settings request may include opening a file, locating its directory entry, modifying bytes, committing the update, and returning an acknowledgement. Measuring only the memory-copy portion hides the part that determines whether the caller can meet its deadline.
Record three intervals separately: waiting to acquire storage ownership, time inside filesystem operations, and time spent in each block-driver callback. Callback intervals are normally nested inside filesystem wall time; do not add them again when calculating total latency. Include any work performed after the original write call. Use a monotonic timestamp source whose own overhead you have characterized. Keep traces in RAM or on an independent interface so that logging does not create more filesystem traffic.
In v2.11.2, lfs_fs_gc() can move consistency work, selected metadata compaction, and allocator preparation into another execution path. It is optional, and a successful call may accomplish nothing. These documented actions make it a scheduling tool, not a promise that the next write will be cheap. See the versioned filesystem maintenance API.

2. Build reproducible filesystem histories
Keep the payload and application operation fixed while changing the filesystem history. Store the build identity, complete littlefs configuration, partition geometry, flash-driver revision, and a manifest of files for every test image. Preserve the original image when investigating a field failure; generate stress histories on authorized disposable test media instead.
| State | How to prepare the experiment | What it reveals |
|---|---|---|
| Fresh baseline | Use a separately authorized test volume with the minimum representative directory layout. | Basic driver and operation overhead. |
| Stable occupancy | Populate long-lived files to the intended operating level, then repeat the same update. | Behavior with persistent live data present. |
| High churn | Create, replace, and delete representative objects while keeping total live data roughly steady. | History-dependent allocation and metadata work. |
| Near the product’s capacity limit | Approach the documented application limit without consuming reserved test or recovery regions. | Latency and error behavior when free space is scarce. |
| Recovered boot | On a test clone, interrupt selected operations, reboot, then time the first update. | Work shifted into recovery or the first modifying operation. |
Do not define “aged” as an arbitrary number of writes. Define a history that exercises the actual product: repeated updates to one directory, rotation of log files, replacement of large objects, or many small inline values. The littlefs v2.11.2 design discussion distinguishes amortized metadata costs from individual expensive updates. Consequently, an average throughput measurement cannot establish a per-request deadline.
3. Capture the tail and explain it
For each operation retain its elapsed time, outcome, callback counts, total bytes read and programmed, erased-block count, and relevant storage-lock wait. Also record the image state before the event. A maximum without a reproducible history is difficult to investigate; a histogram without failed operations is misleading.
- A spike accompanied by many reads but few writes suggests investigation of traversal or allocation scanning.
- A spike accompanied by erase and program activity points toward maintenance or allocation requiring new physical space.
- A long application delay with short filesystem time suggests lock contention or scheduling interference.
- A short successful call followed by expensive commit work means the original measurement boundary was incomplete.
- A timeout or space error is an outcome to qualify, not a sample to remove from the latency dataset.
These are diagnostic hypotheses. Confirm them with traces or narrowly instrumented source paths; callback counts alone cannot identify every internal operation. Avoid verbose tracing inside a flash critical section until its timing effect is understood.
4. Evaluate maintenance without hiding its cost
Run paired experiments from equivalent starting images: normal operation, and an application-controlled idle interval that invokes maintenance before the same workload. Record maintenance duration and the following operations together. Moving a long stall earlier may help a user-facing deadline while leaving total work unchanged.
The v2.11.2 configuration exposes compact_thresh specifically for compaction during lfs_fs_gc(); it does not change the normal full-metadata compaction trigger. The header also documents metadata_max as a way to limit metadata-pair space on large blocks. Both are version-specific controls requiring renewed capacity and latency testing, rather than unexplained tuning constants. See the configuration field definitions.
Change one factor per experiment. A larger cache, different directory layout, maintenance interval, or lower occupancy limit can affect RAM use, traffic, and available capacity. Preserve the original test matrix when comparing the result so that an apparent improvement is not merely a less demanding history.
5. Convert observations into a scheduling decision
For a soft deadline, document the observed maximum, the tested operating envelope, the allowed overrun behavior, and the engineering margin. Label the maximum as observed, not proven. For a hard deadline, require a defensible bound across the permitted states and hardware conditions. If that bound is unavailable or too large, remove synchronous filesystem work from the deadline-critical execution path and define what happens while persistence is pending.
Idle maintenance itself needs ownership and a budget. It must not race an unsynchronized filesystem caller, monopolize a shared bus, or be assumed interruptible at arbitrary points. Review the integration’s lock behavior and flash execution restrictions before enabling it.
Hypothetical release decision
Consider a controller that accepts a configuration change while continuing a periodic control task. The team initially times updates on an empty volume. A later test plan adds long-lived calibration files, rotating diagnostic records, and interrupted-update recovery images. No numerical results are assumed here. If expensive operations coincide with metadata compaction, the team can test idle maintenance and a lower occupancy limit. If the control deadline still cannot be bounded, storage completion becomes an explicitly asynchronous state, and the interface reports success only according to its separate persistence contract.
The release artifact is a reproducible image set, timing traces with error outcomes, and a decision for each workload state. Re-run that qualification when changing littlefs, flash hardware, geometry, driver behavior, or file layout. A small payload is never an adequate reason to omit the storage tail from the schedule.