# How Files Are Read and Written Through the Inode-Keyed Cache with Buffered Writes in Cloudflare Computer

> Learn how Cloudflare Computer reads and writes files using its inode-keyed cache with buffered writes. Discover how pending writes are stored in memory and retrieved for efficient data access.

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: internals
- Published: 2026-08-13

---

**The inode-keyed cache stores pending writes in memory keyed by file inode, allowing reads to return uncommitted data from `buffer.buf` while deferring persistent storage updates until the buffer is released.**

In the Cloudflare Computer project, the virtual filesystem (VFS) uses an SQLite-backed storage layer where every file and directory is identified by a unique **inode**. When applications open files for writing, the system creates an **inode-keyed cache** entry that stages bytes in memory before flushing to disk. This buffer architecture ensures that reads immediately reflect recent writes without requiring expensive synchronous database operations.

## How the Inode-Keyed Cache Structures Buffered Data

The buffer implementation centers on a `dirty` flag that tracks whether uncommitted data exists for a specific inode. When `openWriteBufferSync` (or the async `openWriteBufferForCreateSync`) initializes a write session, it registers a buffer structure containing `buffer.buf` (the raw bytes), `buffer.size` (the logical file size), and metadata including the target path (`dirtyPath`) and permissions mode.

Each buffer is retrieved via `getWriteBuffer(db, inode)`, which checks the cache for existing entries before falling back to the persistent `vfs_chunks` table. This lookup pattern ensures that operations always access the most recent data—whether committed or pending.

## Reading from the Write Buffer

Read operations prioritize the **inode-keyed cache** over persistent storage when the `dirty` flag is set. In [`packages/dofs/src/fs/readFile.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/readFile.ts) (lines 74‑76), the `readRangeSync` function first queries the write buffer for the target inode. If a dirty buffer exists, the read returns data directly from `buffer.buf` limited to `buffer.size`, bypassing the SQLite `vfs_chunks` table entirely.

If no dirty buffer exists for the inode, the read falls back to the standard VFS tables. This dual-path approach guarantees consistency: callers always see the latest writes, including those not yet flushed to the database. The `statSync` and `readdirSync` implementations in [`packages/dofs/src/fs/stat.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/stat.ts) (lines 71‑72) apply the same logic, reporting `buffer.size` from the dirty buffer rather than the persisted node size stored in `vfs_nodes`.

## The Buffered Write Lifecycle

The complete lifecycle of a buffered write spans five distinct phases, each implemented in [`packages/dofs/src/fs/writeFile.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/writeFile.ts):

1. **Opening the Buffer** – `openWriteBufferSync` creates a new buffer structure keyed to the file's inode and registers it in the cache.

2. **Appending Data** – `writeRangeSync` writes incoming bytes into `buffer.buf` at the specified offset. The function invokes `ensureBufferCapacity` to expand the underlying array when necessary, then marks the buffer as `dirty` and updates `buffer.size` to reflect the new logical end-of-file.

3. **Metadata Reporting** – While the buffer remains dirty, calls to `statSync` return the buffered size rather than querying the `vfs_nodes` table, ensuring that file explorers and applications see the correct pending size.

4. **Pre-Flush Reading** – Any `readRangeSync` calls during this window serve data directly from `buffer.buf`, providing immediate read-after-write consistency without database round-trips.

5. **Committing Changes** – When the file handle closes or `fsync` triggers, `releaseWriteBufferSync` executes the materialization sequence (lines 803‑830). This transaction inserts the buffered bytes into `vfs_chunks`, updates the inode's size in `vfs_nodes`, and clears the `dirty` flag, atomically promoting the cached data to persistent storage.

## FUSE Integration and Path Semantics

When the VFS is exposed through the FUSE shim in [`packages/computerd/src/fuse/driver.ts`](https://github.com/cloudflare/computer/blob/main/packages/computerd/src/fuse/driver.ts), the driver respects the **inode-keyed cache** during path-based operations. The `getattr` implementation (lines 754‑760) explicitly checks for a dirty buffer via the inode-to-buffer mapping, reporting the buffered size to the operating system before the data commits to SQLite.

This integration preserves write semantics across file operations. The driver's rename test in [`packages/computerd/src/fuse/driver.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/computerd/src/fuse/driver.test.ts) (line 339) verifies that buffered bytes move with the file when its path changes, ensuring that hard links share the same staged data. The test suite in [`packages/dofs/src/fs/writeBuffer.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/writeBuffer.test.ts) (lines 71‑83) confirms that multiple paths referencing the same inode access identical buffered content.

## Code Example: Buffered Write and Read

```typescript
import { openDatabase, getWriteBuffer } from '@cloudflare/computer';

// Initialize database connection
const db = await openDatabase();

// Create a buffered write session for a new file
p.openWriteBufferForCreateSync("/example.txt", { mode: 0o644 });

// Write data that exists only in the inode-keyed cache
p.writeRangeSync("/example.txt", Buffer.from("hello world"), 0);

// Read returns data from the dirty buffer, not SQLite
const data = p.readRangeSync("/example.txt", 0, 11);
console.log(new TextDecoder().decode(data)); // → "hello world"

// Commit to vfs_chunks and update vfs_nodes
p.releaseWriteBufferSync("/example.txt");

// Now stat returns the persisted size
console.log(p.statSync("/example.txt").size); // 11

```

## Summary

- The **inode-keyed cache** maintains an in-memory `buffer.buf` for each dirty inode, enabling fast read-after-write performance without SQLite transactions.
- **Read operations** check `getWriteBuffer(db, inode)` first; if the `dirty` flag is set, data is served directly from the buffer rather than `vfs_chunks`.
- **Write operations** stage bytes via `writeRangeSync`, which expands capacity via `ensureBufferCapacity` and updates `buffer.size` for accurate stat reporting.
- **Persistence** occurs only during `releaseWriteBufferSync`, which atomically writes to `vfs_chunks`, updates `vfs_nodes`, and clears the dirty flag (lines 803‑830 in [`writeFile.ts`](https://github.com/cloudflare/computer/blob/main/writeFile.ts)).
- The **FUSE driver** respects buffered writes in `getattr` (lines 754‑760) and preserves buffer associations across renames and hard links.

## Frequently Asked Questions

### What happens when reading a file that has pending buffered writes?

The read operation queries `getWriteBuffer(db, inode)` to locate any dirty buffer associated with the file's inode. If found, `readRangeSync` returns bytes directly from `buffer.buf` up to `buffer.size`, ensuring the reader sees the most recent uncommitted data. Only when no dirty buffer exists does the system fall back to the persisted `vfs_chunks` table.

### How does the system report file size while writes are buffered?

Calls to `statSync` and the FUSE driver's `getattr` check the inode-keyed cache before querying the `vfs_nodes` table. When a dirty buffer exists, these functions report `buffer.size` rather than the persisted size, allowing applications to see the logical end-of-file including pending writes.

### When are buffered bytes actually written to the database?

Buffered data commits to persistent storage only when `releaseWriteBufferSync` is invoked—typically when the file descriptor closes or an explicit `fsync` occurs. This function executes a transaction that inserts chunks into `vfs_chunks`, updates the inode size in `vfs_nodes`, and clears the `dirty` flag, as implemented in [`packages/dofs/src/fs/writeFile.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/writeFile.ts) at lines 803‑830.

### Do hard links share the same write buffer?

Yes. Because the cache is keyed by **inode** rather than path, multiple directory entries referencing the same inode share a single buffer structure. The test suite in [`packages/dofs/src/fs/writeBuffer.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/writeBuffer.test.ts) (lines 71‑83) verifies that writes through one hard link are immediately visible when reading through another link to the same inode.