# How the Content-Addressed Blob Cache Works in @cloudflare/dofs: Implementation and Performance Deep Dive

> Discover how the content-addressed blob cache in cloudflare/dofs optimizes performance by storing file chunks with SHA-256 hashes, minimizing redundant SQLite queries for faster data access.

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: deep-dive
- Published: 2026-08-07

---

**The content-addressed blob cache in `@cloudflare/dofs` is a per-database, in-process LRU cache that stores raw file chunk bytes indexed by SHA-256 hash, eliminating redundant SQLite queries during sequential reads.**

The `@cloudflare/dofs` library implements a **distributed object file system** backed by SQLite, and its blob cache serves as a critical optimization layer between FUSE read operations and persistent storage. Understanding this cache mechanism is essential for developers building performant file system integrations with Cloudflare's edge infrastructure.

## Why the Blob Cache Exists

FUSE read operations request data in **128 KiB windows**, while `@cloudflare/dofs` persists data in **512 KiB chunks**. This mismatch creates a fundamental I/O inefficiency: without intermediate caching, a sequential read through a large file would query the same SQLite blob multiple times. The problem compounds with **deduplicated content**—a zero-filled 4 MiB file might reference a single blob hundreds of times, triggering identical database lookups for every read window.

The cache addresses this by keeping hot blob bytes in memory, reducing SQLite round-trips from one per read window to a single lookup per distinct hash.

## Core Architecture: Content-Addressed Storage

### SHA-256-Based Keying

Blobs are indexed by their **32-byte SHA-256 content hash**. In [`packages/dofs/src/fs/blobCache.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/blobCache.ts), this hash transforms into a **Latin-1 string** (`hashKey`) for efficient `Map` lookup:

- Binary hash → string conversion avoids hex-encoding overhead
- Content addressing guarantees **immutable, collision-resistant** cache entries
- Identical content automatically shares cache entries regardless of file location

```typescript
// Conceptual representation from blobCache.ts
function hashToKey(hash: Uint8Array): string {
  // Decodes binary hash as Latin-1 for cheap string keys
  return new TextDecoder('latin1').decode(hash);
}

```

### Per-Database Isolation

The cache uses a `WeakMap<Database, Map<string, Uint8Array>>` structure:

- Each SQLite database receives an **isolated cache instance**
- WeakMap allows garbage collection when database connections close
- Prevents **cross-test contamination** in test suites running multiple databases

## LRU Eviction and Memory Bounds

The cache enforces strict **memory discipline** through a bounded LRU policy:

| Parameter | Value | Purpose |
|-----------|-------|---------|
| `CHUNK_CACHE_MAX_ENTRIES` | 16 entries | Caps memory per database |
| Eviction strategy | Insertion-order LRU | O(1) deletion of oldest entry |
| Promotion on access | Delete + re-insert | Moves entries to "most recent" position |

When `getBlobBytes()` stores a new blob exceeding the limit, it evicts the **first key in insertion order**—the oldest, least-recently-used entry. Accessing an existing entry promotes it by removing and re-inserting, maintaining accurate recency tracking without complex data structures.

## Cache Lifecycle: Lookup, Miss, and Clear

### 1. Cache Lookup

The `getBlobBytes(db, hash)` function in [`packages/dofs/src/fs/blobCache.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/blobCache.ts) performs O(1) hash-key lookup:

```typescript
import { getBlobBytes } from "@cloudflare/dofs/src/fs/blobCache.js";

// Returns cached Uint8Array or undefined on miss
const bytes = getBlobBytes(db, contentHash);
// Caller must treat returned array as read-only

```

Cache hits return the **same `Uint8Array` instance**—callers must not mutate this buffer.

### 2. Cache Miss Handling

On miss, the function:

1. Queries `vfs_blob_bytes` table for the blob
2. Stores result in the per-database Map
3. Evicts excess entries if over `CHUNK_CACHE_MAX_ENTRIES`

```typescript
// Internal flow (simplified)
const cached = cache.get(db)?.get(hashKey);
if (cached) return cached;  // Hit path

// Miss path: fetch from SQLite, then cache
const bytes = db.query("SELECT bytes FROM vfs_blob_bytes WHERE hash = ?", [hash]);
cache.get(db)?.set(hashKey, bytes);
evictIfNeeded(cache.get(db)!);

```

### 3. Explicit Cache Clearing

`clearBlobCache(db)` deletes the entire cache for a database. Test suites rely on this for **deterministic isolation** between runs:

```typescript
import { clearBlobCache } from "@cloudflare/dofs/src/fs/blobCache.js";

// Guarantee clean state before performance-sensitive operations
clearBlobCache(db);
// ... run benchmark or test ...

```

## System Integration Points

### Read Path Integration

File read operations throughout `@cloudflare/dofs` leverage `getBlobBytes()`:

- **`readFile()`** in [`packages/dofs/src/fs/readFile.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/readFile.ts) — assembles full file contents
- **`readRangeSync()`** — random access reads
- **`Provider` implementation** in [`packages/dofs/src/provider.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/provider.ts) — higher-level API surface

Each call consults the cache, ensuring **sequential reads across the same 512 KiB chunk** hit memory after the first SQLite fetch.

### Write Path Consistency

The write path (`upsertChunkBlob` in [`packages/dofs/src/fs/writeFile.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/writeFile.ts)) maintains cache validity through an important invariant: **existing (hash, bytes) pairs are never overwritten**. Content addressing guarantees that if a hash exists, its bytes are immutable.

Cache invalidation occurs only during **repair operations** (`stageBlob`), which explicitly clear the cache to prevent serving stale data:

```typescript
// From writeFile.ts implementation pattern
// Normal write: new hashes, no cache impact
await upsertChunkBlob(db, hash, bytes);

// Repair operation: must clear cache
stageBlob(db, hash, newBytes);
clearBlobCache(db);  // Prevent stale hits

```

## Performance Impact

The blob cache delivers **measurable latency reductions** for access patterns common in edge file systems:

| Scenario | Without Cache | With Cache |
|----------|---------------|------------|
| Sequential 4 MiB zero-filled file | 32 SQLite queries | 1 SQLite query + 31 memory hits |
| Random access within same chunk | 1 query per access | 1 query total |
| Large deduplicated files | Linear query growth | Sublinear based on unique chunks |

By bounding cache size to 16 entries (approximately 8 MiB worst-case), the implementation balances **hit rate against memory pressure** for multi-tenant edge environments.

## Working Example: Cache Behavior in Practice

```typescript
import { withDB } from "@cloudflare/dofs/src/fs/with-db.js";
import { readFile, readRangeSync } from "@cloudflare/dofs/src/fs/readFile.js";
import { writeFileSync } from "@cloudflare/dofs/src/fs/writeFile.js";
import { clearBlobCache } from "@cloudflare/dofs/src/fs/blobCache.js";

await withDB(async (db) => {
  // Create 4 MiB file deduplicated to single 512 KiB blob (all zeros)
  const payload = new Uint8Array(4 * 1024 * 1024).fill(0);
  await writeFileSync(db, "/big.bin", payload, {}, () => Date.now());

  // Reset cache for clean observation
  clearBlobCache(db);

  // Sequential 128 KiB reads: first fetches from SQLite, rest hit cache
  const window = 128 * 1024;
  for (let offset = 0; offset < payload.byteLength; offset += window) {
    const chunk = readRangeSync(db, "/big.bin", offset, window);
    // Iterations 2-32 return cached Uint8Array without database query
  }

  // Full file read also benefits from populated cache
  const contents = await readFile(db, "/big.bin");
});

```

## Key Implementation Files

Understanding the cache requires examining these source files in the `cloudflare/computer` repository:

| File | Role |
|------|------|
| [`packages/dofs/src/fs/blobCache.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/blobCache.ts) | LRU cache implementation, `getBlobBytes()`, `clearBlobCache()` |
| [`packages/dofs/src/fs/blobCache.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/blobCache.test.ts) | Validation of reuse, eviction, and LRU ordering |
| [`packages/dofs/src/fs/readFile.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/readFile.ts) | Read paths demonstrating cache integration |
| [`packages/dofs/src/provider.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/provider.ts) | Higher-level API using cached blob access |
| [`packages/dofs/src/fs/writeFile.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/writeFile.ts) | Write path and cache invalidation during repair |

## Summary

- The **content-addressed blob cache** eliminates redundant SQLite queries by storing 512 KiB file chunks in memory, indexed by SHA-256 hash.
- **Per-database isolation** via `WeakMap` prevents cross-contamination, while a 16-entry LRU bound controls memory consumption.
- **Cache hits return read-only `Uint8Array` instances**—mutating these corrupts cache state for subsequent callers.
- **Write operations preserve cache validity** through content-addressing immutability; only explicit repair operations require cache clearing.
- Integration spans [`readFile.ts`](https://github.com/cloudflare/computer/blob/main/readFile.ts), [`provider.ts`](https://github.com/cloudflare/computer/blob/main/provider.ts), and internal read paths, making the cache transparent to API consumers.

## Frequently Asked Questions

### How does the content-addressed blob cache handle hash collisions?

SHA-256 provides 256-bit collision resistance, making accidental collisions computationally infeasible. The cache treats hash equality as content equality—if two distinct inputs produced the same hash, they would incorrectly share cache entries. In practice, this matches `@cloudflare/dofs`'s overall content-addressed storage design where hash collisions would corrupt data regardless of caching.

### Why does the cache use Latin-1 string encoding for hash keys instead of hex or base64?

Latin-1 (ISO-8859-1) provides a **1:1 byte-to-character mapping** with zero encoding overhead. A 32-byte hash becomes a 32-character string, whereas hex encoding would double to 64 characters and base64 to approximately 44. This reduces memory per key and Map lookup overhead while remaining compatible with JavaScript string internals.

### What happens when `CHUNK_CACHE_MAX_ENTRIES` is exceeded?

The cache evicts the **least-recently-used entry** by deleting the first key in the Map's insertion order. JavaScript Maps maintain insertion order, making this O(1) without auxiliary data structures. The newly inserted entry becomes the "most recent," and subsequent accesses promote entries by delete/re-insert operations.

### Can the blob cache be disabled or tuned for different workloads?

The current implementation in `@cloudflare/dofs` hardcodes `CHUNK_CACHE_MAX_ENTRIES` at 16. Workload-specific tuning would require modifying [`packages/dofs/src/fs/blobCache.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/fs/blobCache.ts). The cache design assumes edge deployment constraints—small working sets with high temporal locality—rather than large-file streaming scenarios that might benefit from larger bounds.