ข้ามไปยังเนื้อหา

Stale-While-Revalidate

Stale-while-revalidate (SWR) คือสิ่งที่ดีที่สุดของทั้งสองโลก: service worker คืน response จาก cache ทันที — ผู้ใช้จึงไม่ต้องรอ network เลย — และในขณะเดียวกันก็เริ่ม fetch จาก network ในเบื้องหลังเพื่ออัปเดต cache สำหรับครั้งถัดไป

ใน request ครั้งแรกสุดสำหรับ resource หนึ่ง ยังไม่มีอะไรใน cache SW จึงรอ response จาก network แคชไว้ แล้วคืนกลับไป ตั้งแต่ request ครั้งที่สองเป็นต้นไป สำเนาใน cache (ซึ่งอาจเก่า) จะถูกคืนกลับทันทีขณะที่สำเนาสด ๆ ถูก fetch อยู่เงียบ ๆ ในเบื้องหลัง ผู้ใช้เห็นเนื้อหาทันทีและจะได้เวอร์ชันล่าสุดในการเข้าชมครั้งถัดไป

รูปแบบนี้เข้ากันได้ดีโดยธรรมชาติกับ resource ที่อัปเดตเป็นครั้งคราวแต่เวลาโหลดที่ทันทีสำคัญกว่าความสดใหม่ระดับเป๊ะทุกพิกเซล: avatar ของผู้ใช้, ฟีดข่าว, config JSON, ไฟล์ CSS, และ web font ล้วนเป็นตัวเลือกที่ดี ไม่เหมาะกับข้อมูลแบบเรียลไทม์ — ราคาหุ้น, สกอร์สด, หรืออะไรก็ตามที่การช้าไปหนึ่งเวอร์ชันยอมรับไม่ได้

  1. service worker รับ fetch event เข้ามา
  2. เปิด cache และตรวจหา response ที่มีอยู่
  3. เริ่ม fetch จาก network โดยไม่สนใจ ว่า cache จะมี response หรือไม่ (การ revalidate ในเบื้องหลัง)
  4. หากมี response ใน cache จะถูกคืนกลับไปยังหน้าเพจทันที; หากไม่มี response ใน cache handler จะรอ network แล้วคืนค่านั้นแทน
  5. เมื่อ response จาก network มาถึง รายการใน cache จะถูกอัปเดตเงียบ ๆ เพื่อให้ request ครั้งถัดไปได้สำเนาที่สดใหม่ที่สุด
const CACHE = 'swr-v1';
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open(CACHE).then((cache) =>
cache.match(event.request).then((cached) => {
const networkFetch = fetch(event.request).then((response) => {
cache.put(event.request, response.clone());
return response;
});
// Serve cache instantly; fall back to network on miss
return cached || networkFetch;
})
)
);
});
sequenceDiagram
  participant P as Page
  participant SW as Service Worker
  participant C as Cache Storage
  participant N as Network

  P->>SW: fetch(request)
  SW->>C: cache.match(request)
  SW->>N: fetch(request) [background]
  C-->>SW: cached Response (may be stale)
  SW-->>P: Response (instant)
  N-->>SW: fresh Response
  SW->>C: cache.put(request, fresh.clone())
  Note over SW,C: Cache updated for next request
flow แบบ stale-while-revalidate

ยังมีรูปแบบนี้ในเวอร์ชันฝั่งเพจอีกด้วย หน้าเพจยิงทั้งการค้นหาใน cache และการ fetch จาก network พร้อมกัน render ผลลัพธ์จาก cache ทันที แล้วจึงอัปเดต UI เมื่อ response จาก network มาถึง — ไม่ต้องใช้ service worker เลย

async function fetchWithCacheThenNetwork(url) {
const cache = await caches.open('swr-v1');
const cached = await cache.match(url);
// Start the network request immediately, don't await yet
const networkPromise = fetch(url).then((response) => {
cache.put(url, response.clone());
return response.json();
});
// Render the stale cached version right away if available
if (cached) {
const staleData = await cached.json();
renderUI(staleData); // show immediately
}
// Update the UI when the fresh data lands
const freshData = await networkPromise;
renderUI(freshData);
}

วิธีนี้มีประโยชน์ใน framework ที่ component สามารถ re-render เมื่อข้อมูลสด ๆ มาถึง ให้ประสบการณ์ที่ค่อย ๆ ดีขึ้นโดยไม่มี loading spinner ที่มองเห็นได้เลย

ตัวเลือกBenefitCost
Stale-while-revalidateตอบกลับเร็วจาก cache ทันทีพร้อมอัปเดตเบื้องหลัง ได้ทั้งความเร็วและความสด (รอบถัดไป)ผู้ใช้เห็นข้อมูลเก่าไปก่อน 1 รอบเสมอ ไม่เหมาะกับข้อมูลที่ต้อง real-time
Cache-first ล้วนๆง่ายกว่า ไม่ต้องจัดการ background fetchไม่มีกลไก auto-refresh ข้อมูลจะเก่าค้างจนกว่าจะ invalidate cache เอง
  • ไม่ await การ update cache เบื้องหลัง (fire-and-forget ผิดจังหวะ) ทำให้ browser อาจ terminate service worker ก่อน fetch เสร็จ ต้องใช้ event.waitUntil()
  • ใช้ SWR กับข้อมูลที่ต้องถูกต้อง 100% เสมอ (เช่นยอดเงินคงเหลือ) ทำให้ผู้ใช้เห็นตัวเลขเก่าโดยไม่รู้ตัว
  • ไม่ trim cache entry เก่าออก ทำให้ storage โตเรื่อยๆ เพราะทุก revalidate จะเขียนทับแต่ไม่เคย cleanup entry ที่ไม่ใช้แล้ว

💡 ตัวอย่างจากของจริง

HTTP Cache-Control: stale-while-revalidate — เป็น pattern มาตรฐานที่ CDN อย่าง Cloudflare และ Fastly ใช้ก่อนที่ service worker จะยืมแนวคิดนี้มาใช้ฝั่ง client

Workbox StaleWhileRevalidate strategy — ใช้กันแพร่หลายในเว็บข่าวและ e-commerce ที่ต้องการทั้งความเร็วของหน้า listing และข้อมูลที่ไม่เก่าเกินไป

ใน stale-while-revalidate cache ถูกอัปเดตเมื่อใด?
asset ประเภทใดที่ stale-while-revalidate เหมาะน้อยที่สุด?
fetch handler แบบ SWR คืนค่าอะไรใน request ครั้งแรกสุดสำหรับ resource หนึ่ง?
SWR ถูกอธิบายว่า 'เสิร์ฟของเก่า แล้ว revalidate ในเบื้องหลัง' คำว่า revalidate ในที่นี้หมายความว่าอย่างไร?