R2 TEE() Caching Test

Test caching large files from R2 using body.tee() instead of clone().

← Back to Main Menu

The Problem

response.clone() buffers the entire body into memory.

Workers have a 128 MB memory limit.

For large files (like videos), clone() will fail with memory exceeded error.

The Solution

body.tee() creates two independent streams without full buffering.

const [stream1, stream2] = response.body.tee();
// stream1 → cache.put()
// stream2 → return to client

Caveat: If one stream is consumed faster than the other, data backs up in memory.

Test File: erfi-135kg.mp4

Get File Info Check Cache Clear Cache

Caching Methods

Method Description Expected Action
tee() Original: tee + waitUntil ? Testing Test
tee-await tee + TransformStream + waitUntil(Promise.all) ? Testing Test
tee-fixed tee + FixedLengthStream wrapper ? Testing Test
pipe-through tee + TransformStream pipes ? Testing Test
manual-pump Manual read/write to both destinations ? Testing Test
no-wait tee without waitUntil (fire & forget) ? Testing Test
readable-from Buffer chunks + ReadableStream.from() ✗ Memory Test
cache-after Buffer entire file, then cache ✗ Memory Test
clone() Baseline: response.clone() ✗ Memory Test
stream No caching, direct R2 stream ✓ Works Test

Quick Test All Methods

Test sequence for each method:

  1. Clear cache: curl 'https://cache-edge.erfianugrah.com/r2-tee/clear?key=erfi-135kg.mp4'
  2. Download via method: curl 'https://cache-edge.erfianugrah.com/r2-tee/METHOD?key=erfi-135kg.mp4' -o /dev/null
  3. Check cache: curl 'https://cache-edge.erfianugrah.com/r2-tee/check?key=erfi-135kg.mp4'

Replace METHOD with: tee, tee-await, tee-fixed, pipe-through, manual-pump, no-wait

Test Sequence

  1. Check file size - Verify it exceeds 128 MB
  2. Clear cache - Start fresh
  3. Test tee() - Should cache successfully (MISS first, then HIT)
  4. Check cache - Verify it's cached
  5. Test tee() again - Should be HIT

Headers to Watch

HeaderMeaning
X-Cache-StatusHIT = served from Cache API, MISS = fresh from R2
X-Cache-Methodtee or clone - which method was used
X-File-Size-MBFile size in MB
CF-Cache-StatusHIT when served from Cache API match()

Code Pattern

// From caching.mdx - handling large responses
async function fetchLargeWithCache(
  originUrl: string,
  ctx: ExecutionContext,
): Promise<Response> {
  const cache = caches.default;
  const cacheKey = new Request(originUrl, { method: "GET" });

  const cached = await cache.match(cacheKey);
  if (cached) return cached;

  const originResp = await fetch(originUrl);
  if (!originResp.ok || !originResp.body) return originResp;

  // tee() creates two streams from one - avoids buffering entire body
  const [stream1, stream2] = originResp.body.tee();

  const headers = new Headers(originResp.headers);
  headers.delete("Set-Cookie");
  if (!headers.has("Cache-Control")) {
    headers.set("Cache-Control", "public, max-age=3600");
  }

  const responseToCache = new Response(stream1, {
    status: originResp.status,
    headers,
  });
  ctx.waitUntil(cache.put(cacheKey, responseToCache));

  return new Response(stream2, { status: originResp.status, headers });
}