Research notes / Filesystem I/O
FUSE writeback caching: reads behind writes and the fsync contract
/ Source review
Before enabling FUSE writeback caching, check who can change the backing data, whether the daemon can read partially overwritten pages, and what its synchronization callback actually does. Faster write completion and persistent storage are separate requirements.
On this page6 sections
A partial write can require a read
Linux v6.12 documents three paths. Direct I/O bypasses the FUSE page cache and readahead. Cached write-through forwards writes to the daemon immediately. Writeback-cache first dirties cached data, then sends it later. [1]
| Path | Write handling | Important condition |
|---|---|---|
| Direct I/O | Bypasses this page cache. | Does not describe the daemon's own buffering. |
| Write-through | Forwards each write as one or more requests. | No read requests generated for these writes. |
| Writeback-cache | Can defer and combine cached writes. | A partial-page update can require existing bytes first. |
Consequently, a daemon can receive READ for a file opened with O_WRONLY. This is a cache-maintenance request, not permission for the application to read through that descriptor. Writeback assumes changes pass through this kernel's FUSE path; the documentation therefore warns against treating it as a general network-filesystem setting. [1]
Engineering implication: test an overwrite inside an existing uncached page, not only a freshly created file. Establish how remote writers or direct changes to the backing store are coordinated before choosing a cache policy.
FLUSH, RELEASE and FSYNC have different jobs
In the libfuse 3.16.2 low-level contract, flush accompanies descriptor close and can occur more than once for a shared open file. Its name does not require pending data to become persistent. release ends the open-file lifetime; its returned error is not delivered to the closing application. fsync is the synchronization operation. [2]
For an incident trace, keep the application syscall, FUSE request and backing-store action separate. A log entry named “flush” is insufficient evidence of a media flush. See also stable writes and device flushes.
An unimplemented FSYNC can return success
Linux v6.12 first waits for file writeback, checks writeback errors and synchronizes inode metadata. It then issues FUSE_FSYNC unless the connection already records no_fsync. An ENOSYS reply sets that flag and is converted to success. Subsequent calls skip this daemon request, not the preceding writeback and error checks. [3]
The corresponding libfuse 3.16.2 low-level dispatcher sends ENOSYS when its fsync callback is absent. This is a source-level path through two pinned versions, not an experiment on every FUSE filesystem or a claim that every API wrapper has identical defaults. [4]
Hesela's conclusion: checking only the syscall's return value cannot establish what a particular daemon did to its backing store. Inspect the implementation and its persistence contract. A daemon may already persist each write; another may need a real backend synchronization operation. The protocol fallback alone distinguishes neither case.
This is separate from retrying fsync after a reported writeback failure: the case here is an unimplemented operation, not evidence that earlier damaged data was repaired.
What the FAST 2017 study can tell us
Vangoor, Tarasov and Zadok instrumented Stackfs over ext4, comparing basic and optimized FUSE configurations across 45 Filebench workloads on HDD and SSD. Their setup used Linux 4.1.13 and libfuse commit 386b1b. Work was fixed per run, with reported durations of 8-20 minutes; some SSD workloads used more work than HDD workloads. [5, sections 3-4]
The optimized configuration changed four things together: writeback caching, request size, threading and splicing. Its difference from the basic configuration therefore does not isolate writeback's causal effect. Some workloads remained close to native ext4 throughput; others lost roughly 83%. That is a workload-dependent historical range, not a slowdown constant for current FUSE implementations. [5, sections 4-5]
We reviewed the paper's methods and results, but did not rerun its benchmark or measure a production daemon. The useful transfer is its separation of workload and implementation costs, not a universal performance forecast.
Evaluate the contract before tuning
For a concrete deployment, use this review checklist:
- Record kernel, library, daemon and backend versions, plus negotiated cache settings.
- Trace cold partial overwrites and append behavior; compare daemon requests with application operations.
- Measure write-return latency and synchronization latency separately, with equal persistence requirements.
- Exercise backend write and sync errors in a disposable test environment; verify which failures reach the application.
- Validate recovery against known expected contents after the declared failure model. Process exit is not power loss.
These are proposed deployment checks, not tests performed for this article. The review adds documented request and callback distinctions to Hesela's latency measurement and durability guidance.
Corpus: FUSE, FUSE writeback-cache, and FUSE FLUSH. FUSE filesystems are unrelated to binary fuse filters.
Primary sources and scope
- Linux v6.12: FUSE I/O modes
- libfuse 3.16.2: low-level open, flush, release and fsync contracts
- Linux v6.12: fuse_fsync and connection-scoped no_fsync handling
- libfuse 3.16.2: do_fsync dispatch and missing low-level callback
- Vangoor, Tarasov and Zadok: To FUSE or Not to FUSE, FAST 2017, pp. 59-72, sections 3-5
Reviewed 18 October 2026. Kernel and libfuse behavior is version-scoped. This is a literature and implementation review, not a new benchmark, crash test or vulnerability report.