๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

[Medium] ๐Ÿ“„ HTTP Caching

1. What is HTTP caching and why is it important?โ€‹

HTTP ์บ์‹œ๋ž€ ๋ฌด์—‡์ธ๊ฐ€? ์™œ ์ค‘์š”ํ•œ๊ฐ€?

HTTP ์บ์‹œ๋Š” ํด๋ผ์ด์–ธํŠธ(๋ธŒ๋ผ์šฐ์ €) ๋˜๋Š” ์ค‘๊ฐ„ ์„œ๋ฒ„์— HTTP ์‘๋‹ต์„ ์ž„์‹œ๋กœ ์ €์žฅํ•˜๋Š” ๊ธฐ์ˆ ๋กœ, ํ›„์† ์š”์ฒญ ์‹œ ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋ฒ„์— ๋‹ค์‹œ ์š”์ฒญํ•  ํ•„์š”๊ฐ€ ์—†๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

์บ์‹œ vs ์ž„์‹œ ์ €์žฅ: ๋ฌด์—‡์ด ๋‹ค๋ฅธ๊ฐ€?โ€‹

๊ธฐ์ˆ  ๋ฌธ์„œ์—์„œ ์ด ๋‘ ์šฉ์–ด๋Š” ์ข…์ข… ํ˜ผ์šฉ๋˜์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” ๋‹ค๋ฅธ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Cache(์บ์‹œ)โ€‹

์ •์˜: ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ์˜ ์‚ฌ๋ณธ์œผ๋กœ, "์žฌ์‚ฌ์šฉ"๊ณผ "์ ‘๊ทผ ์†๋„ ํ–ฅ์ƒ"์„ ๊ฐ•์กฐํ•ฉ๋‹ˆ๋‹ค.

ํŠน์ง•:

  • โœ… ์„ฑ๋Šฅ ํ–ฅ์ƒ์ด ๋ชฉ์ 
  • โœ… ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜๋ณต์ ์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • โœ… ๋ช…ํ™•ํ•œ ๋งŒ๋ฃŒ ์ •์ฑ…์ด ์žˆ์Œ
  • โœ… ์ผ๋ฐ˜์ ์œผ๋กœ ์›๋ณธ ๋ฐ์ดํ„ฐ์˜ ์‚ฌ๋ณธ

์˜ˆ์‹œ:

// HTTP Cache - API ์‘๋‹ต ์บ์‹œ
Cache-Control: max-age=3600 // 1์‹œ๊ฐ„ ์บ์‹œ

// Memory Cache - ๊ณ„์‚ฐ ๊ฒฐ๊ณผ ์บ์‹œ
const cache = new Map();
function fibonacci(n) {
if (cache.has(n)) return cache.get(n); // ์บ์‹œ ์žฌ์‚ฌ์šฉ
const result = /* ๊ณ„์‚ฐ */;
cache.set(n, result);
return result;
}

Temporary Storage(์ž„์‹œ ์ €์žฅ)โ€‹

์ •์˜: ์ž„์‹œ๋กœ ์ €์žฅ๋˜๋Š” ๋ฐ์ดํ„ฐ๋กœ, "์ผ์‹œ์„ฑ"๊ณผ "์‚ญ์ œ๋จ"์„ ๊ฐ•์กฐํ•ฉ๋‹ˆ๋‹ค.

ํŠน์ง•:

  • โœ… ์ž„์‹œ ๋ณด๊ด€์ด ๋ชฉ์ 
  • โœ… ๋ฐ˜๋“œ์‹œ ์žฌ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹˜
  • โœ… ์ƒ๋ช… ์ฃผ๊ธฐ๊ฐ€ ์ผ๋ฐ˜์ ์œผ๋กœ ์งง์Œ
  • โœ… ์ค‘๊ฐ„ ์ƒํƒœ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Œ

์˜ˆ์‹œ:

// sessionStorage - ์‚ฌ์šฉ์ž ์ž…๋ ฅ ์ž„์‹œ ์ €์žฅ
sessionStorage.setItem('formData', JSON.stringify(form)); // ํƒญ์„ ๋‹ซ์œผ๋ฉด ์‚ญ์ œ

// ํŒŒ์ผ ์—…๋กœ๋“œ ์ž„์‹œ ์ €์žฅ
const tempFile = await uploadToTemp(file); // ์ฒ˜๋ฆฌ ํ›„ ์‚ญ์ œ
await processFile(tempFile);
await deleteTempFile(tempFile);

๋น„๊ตํ‘œโ€‹

ํŠน์„ฑCache(์บ์‹œ)Temporary Storage(์ž„์‹œ ์ €์žฅ)
์ฃผ์š” ๋ชฉ์ ์„ฑ๋Šฅ ์ตœ์ ํ™”์ž„์‹œ ๋ณด๊ด€
์žฌ์‚ฌ์šฉ์˜ˆ, ์—ฌ๋Ÿฌ ๋ฒˆ ์ฝ๊ธฐ๋ฐ˜๋“œ์‹œ ๊ทธ๋ ‡์ง€๋Š” ์•Š์Œ
์ƒ๋ช… ์ฃผ๊ธฐ์ •์ฑ…์— ๋”ฐ๋ผ ๊ฒฐ์ •์ผ๋ฐ˜์ ์œผ๋กœ ์งง์Œ
๋Œ€ํ‘œ์  ์šฉ๋„HTTP Cache, Memory CachesessionStorage, ์ž„์‹œ ํŒŒ์ผ
์˜์–ด ๋Œ€์‘CacheTemp / Temporary / Buffer

์‹ค์ œ ์‚ฌ์šฉ์—์„œ์˜ ์ฐจ์ดโ€‹

// ===== Cache(์บ์‹œ)์˜ ์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค =====

// 1. HTTP ์บ์‹œ: API ์‘๋‹ต ์žฌ์‚ฌ์šฉ
fetch('/api/users') // ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ
.then((response) => response.json());

fetch('/api/users') // ๋‘ ๋ฒˆ์งธ๋Š” ์บ์‹œ์—์„œ ์ฝ๊ธฐ
.then((response) => response.json());

// 2. ๊ณ„์‚ฐ ๊ฒฐ๊ณผ ์บ์‹œ
const memoize = (fn) => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key); // ์žฌ์‚ฌ์šฉ
const result = fn(...args);
cache.set(key, result);
return result;
};
};

// ===== Temporary Storage(์ž„์‹œ ์ €์žฅ)์˜ ์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค =====

// 1. ํผ ๋ฐ์ดํ„ฐ ์ž„์‹œ ์ €์žฅ(์‹ค์ˆ˜๋กœ ๋‹ซ๋Š” ๊ฒƒ ๋ฐฉ์ง€)
window.addEventListener('beforeunload', () => {
sessionStorage.setItem('formDraft', JSON.stringify(formData));
});

// 2. ์—…๋กœ๋“œ ํŒŒ์ผ ์ž„์‹œ ์ €์žฅ
async function handleUpload(file) {
const tempPath = await uploadToTempStorage(file); // ์ž„์‹œ ์ €์žฅ
const processed = await processFile(tempPath);
await deleteTempFile(tempPath); // ์‚ฌ์šฉ ํ›„ ์‚ญ์ œ
return processed;
}

// 3. ์ค‘๊ฐ„ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ ์ž„์‹œ ์ €์žฅ
const tempResults = []; // ์ค‘๊ฐ„ ๊ฒฐ๊ณผ ์ž„์‹œ ์ €์žฅ
for (const item of items) {
tempResults.push(process(item));
}
const final = combine(tempResults); // ์‚ฌ์šฉ ํ›„ ๋” ์ด์ƒ ํ•„์š” ์—†์Œ

Web ๊ฐœ๋ฐœ์—์„œ์˜ ์‘์šฉโ€‹

// HTTP Cache(์บ์‹œ) - ์žฅ๊ธฐ ์ €์žฅ, ๋ฐ˜๋ณต ์‚ฌ์šฉ
Cache-Control: public, max-age=31536000, immutable
// โ†’ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด ํŒŒ์ผ์„ 1๋…„๊ฐ„ ์บ์‹œํ•˜๊ณ  ๋ฐ˜๋ณต ์‚ฌ์šฉ

// sessionStorage(์ž„์‹œ ์ €์žฅ) - ์ž„์‹œ ์ €์žฅ, ๋‹ซ์œผ๋ฉด ์‚ญ์ œ
sessionStorage.setItem('tempData', data);
// โ†’ ํ˜„์žฌ ํƒญ์—์„œ๋งŒ ์œ ํšจ, ๋‹ซ์œผ๋ฉด ์‚ญ์ œ

// localStorage(์žฅ๊ธฐ ์ €์žฅ) - ๋‘ ๊ฐ€์ง€์˜ ์ค‘๊ฐ„
localStorage.setItem('userPreferences', prefs);
// โ†’ ์˜๊ตฌ ์ €์žฅ์ด์ง€๋งŒ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ๊ฒƒ์€ ์•„๋‹˜

์™œ ์ด ๋‘ ๊ฐœ๋…์„ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•œ๊ฐ€?โ€‹

  1. ์„ค๊ณ„ ๊ฒฐ์ •:

    • ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•œ๊ฐ€? โ†’ ์บ์‹œ ์‚ฌ์šฉ
    • ์ž„์‹œ ๋ณด๊ด€์ด ํ•„์š”ํ•œ๊ฐ€? โ†’ ์ž„์‹œ ์ €์žฅ ์‚ฌ์šฉ
  2. ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ:

    • ์บ์‹œ: ์ ์ค‘๋ฅ , ๋งŒ๋ฃŒ ์ •์ฑ…์— ์ฃผ๋ชฉ
    • ์ž„์‹œ ์ €์žฅ: ์ •๋ฆฌ ์‹œ๊ธฐ, ์šฉ๋Ÿ‰ ์ œํ•œ์— ์ฃผ๋ชฉ
  3. ๋ฉด์ ‘ ๋‹ต๋ณ€:

    • "์„ฑ๋Šฅ์„ ์–ด๋–ป๊ฒŒ ์ตœ์ ํ™”ํ•˜๋Š”๊ฐ€" โ†’ ์บ์‹œ ์ „๋žต์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐ
    • "์ž„์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฐ€" โ†’ ์ž„์‹œ ์ €์žฅ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐ

์ด ๊ธ€์—์„œ๋Š” ์ฃผ๋กœ Cache(์บ์‹œ), ํŠนํžˆ HTTP ์บ์‹œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

์บ์‹œ์˜ ์žฅ์ โ€‹

  1. ๋„คํŠธ์›Œํฌ ์š”์ฒญ ๊ฐ์†Œ: ๋กœ์ปฌ ์บ์‹œ์—์„œ ์ง์ ‘ ์ฝ์–ด HTTP ์š”์ฒญ์„ ๋ณด๋‚ผ ํ•„์š”๊ฐ€ ์—†์Œ
  2. ์„œ๋ฒ„ ๋ถ€ํ•˜ ๊ฐ์†Œ: ์„œ๋ฒ„๊ฐ€ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์š”์ฒญ ์ˆ˜๋ฅผ ์ค„์ž„
  3. ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์†๋„ ํ–ฅ์ƒ: ๋กœ์ปฌ ์บ์‹œ ์ฝ๊ธฐ ์†๋„๊ฐ€ ๋„คํŠธ์›Œํฌ ์š”์ฒญ๋ณด๋‹ค ํ›จ์”ฌ ๋น ๋ฆ„
  4. ๋Œ€์—ญํญ ์ ˆ์•ฝ: ๋ฐ์ดํ„ฐ ์ „์†ก๋Ÿ‰ ๊ฐ์†Œ
  5. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ : ํŽ˜์ด์ง€ ์‘๋‹ต์ด ๋นจ๋ผ์ง€๊ณ  ๋” ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

์บ์‹œ์˜ ์ข…๋ฅ˜โ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๋ธŒ๋ผ์šฐ์ € ์บ์‹œ ๊ณ„์ธต โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1. Memory Cache (๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ) โ”‚
โ”‚ - ๊ฐ€์žฅ ๋น ๋ฆ„, ์šฉ๋Ÿ‰ ์ž‘์Œ โ”‚
โ”‚ - ํƒญ์„ ๋‹ซ์œผ๋ฉด ์‚ญ์ œ โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 2. Disk Cache (๋””์Šคํฌ ์บ์‹œ) โ”‚
โ”‚ - ๋” ๋А๋ฆผ, ์šฉ๋Ÿ‰ ํผ โ”‚
โ”‚ - ์˜๊ตฌ ์ €์žฅ โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 3. Service Worker Cache โ”‚
โ”‚ - ๊ฐœ๋ฐœ์ž๊ฐ€ ์™„์ „ํžˆ ์ œ์–ด โ”‚
โ”‚ - ์˜คํ”„๋ผ์ธ ์•ฑ ์ง€์› โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

2. What are the HTTP caching strategies?โ€‹

HTTP ์บ์‹œ ์ „๋žต์—๋Š” ์–ด๋–ค ๊ฒƒ๋“ค์ด ์žˆ๋Š”๊ฐ€?

์บ์‹œ ์ „๋žต ๋ถ„๋ฅ˜โ€‹

HTTP ์บ์‹œ ์ „๋žต
โ”œโ”€โ”€ ๊ฐ•๋ ฅํ•œ ์บ์‹œ (Strong Cache)
โ”‚ โ”œโ”€โ”€ Cache-Control
โ”‚ โ””โ”€โ”€ Expires
โ””โ”€โ”€ ํ˜‘์ƒ ์บ์‹œ (Negotiation Cache)
โ”œโ”€โ”€ Last-Modified / If-Modified-Since
โ””โ”€โ”€ ETag / If-None-Match

1. ๊ฐ•๋ ฅํ•œ ์บ์‹œ(Strong Cache / Fresh)โ€‹

ํŠน์ง•: ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋กœ์ปฌ ์บ์‹œ์—์„œ ์ง์ ‘ ์ฝ์œผ๋ฉฐ, ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Cache-Control(HTTP/1.1)โ€‹

Cache-Control: max-age=3600

์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋””๋ ‰ํ‹ฐ๋ธŒ:

// 1. max-age: ์บ์‹œ ์œ ํšจ ์‹œ๊ฐ„(์ดˆ)
Cache-Control: max-age=3600 // 1์‹œ๊ฐ„ ์บ์‹œ

// 2. no-cache: ์„œ๋ฒ„ ๊ฒ€์ฆ ํ•„์š”(ํ˜‘์ƒ ์บ์‹œ ์‚ฌ์šฉ)
Cache-Control: no-cache

// 3. no-store: ์ „ํ˜€ ์บ์‹œํ•˜์ง€ ์•Š์Œ
Cache-Control: no-store

// 4. public: ์–ด๋–ค ์บ์‹œ๋“  ์ €์žฅ ๊ฐ€๋Šฅ(๋ธŒ๋ผ์šฐ์ €, CDN)
Cache-Control: public, max-age=31536000

// 5. private: ๋ธŒ๋ผ์šฐ์ €๋งŒ ์บ์‹œ ๊ฐ€๋Šฅ
Cache-Control: private, max-age=3600

// 6. immutable: ๋ฆฌ์†Œ์Šค๊ฐ€ ์ ˆ๋Œ€ ๋ณ€ํ•˜์ง€ ์•Š์Œ(hash ํŒŒ์ผ๋ช…๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ)
Cache-Control: public, max-age=31536000, immutable

// 7. must-revalidate: ๋งŒ๋ฃŒ ํ›„ ๋ฐ˜๋“œ์‹œ ์„œ๋ฒ„ ๊ฒ€์ฆ ํ•„์š”
Cache-Control: max-age=3600, must-revalidate

Expires(HTTP/1.0, ๋” ์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Œ)โ€‹

Expires: Wed, 21 Oct 2025 07:28:00 GMT

๋ฌธ์ œ์ :

  • ์ ˆ๋Œ€ ์‹œ๊ฐ„์„ ์‚ฌ์šฉํ•˜๋ฉฐ, ํด๋ผ์ด์–ธํŠธ ์‹œ๊ฐ„์— ์˜์กด
  • ํด๋ผ์ด์–ธํŠธ ์‹œ๊ฐ„์ด ์ •ํ™•ํ•˜์ง€ ์•Š์œผ๋ฉด ์บ์‹œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•˜์ง€ ์•Š์Œ
  • Cache-Control๋กœ ๋Œ€์ฒด๋จ

2. ํ˜‘์ƒ ์บ์‹œ(Negotiation Cache / Validation)โ€‹

ํŠน์ง•: ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด ๋ฆฌ์†Œ์Šค๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

Last-Modified / If-Modified-Sinceโ€‹

# ์„œ๋ฒ„ ์‘๋‹ต(์ฒซ ๋ฒˆ์งธ ์š”์ฒญ)
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

# ๋ธŒ๋ผ์šฐ์ € ์š”์ฒญ(ํ›„์† ์š”์ฒญ)
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT

ํ๋ฆ„:

  1. ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ: ์„œ๋ฒ„๊ฐ€ Last-Modified๋ฅผ ๋ฐ˜ํ™˜
  2. ํ›„์† ์š”์ฒญ: ๋ธŒ๋ผ์šฐ์ €๊ฐ€ If-Modified-Since๋ฅผ ํฌํ•จ
  3. ๋ฆฌ์†Œ์Šค ๋ฏธ๋ณ€๊ฒฝ: ์„œ๋ฒ„๊ฐ€ 304 Not Modified๋ฅผ ๋ฐ˜ํ™˜
  4. ๋ฆฌ์†Œ์Šค ๋ณ€๊ฒฝ๋จ: ์„œ๋ฒ„๊ฐ€ 200 OK์™€ ์ƒˆ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐ˜ํ™˜

ETag / If-None-Matchโ€‹

# ์„œ๋ฒ„ ์‘๋‹ต(์ฒซ ๋ฒˆ์งธ ์š”์ฒญ)
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

# ๋ธŒ๋ผ์šฐ์ € ์š”์ฒญ(ํ›„์† ์š”์ฒญ)
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

์žฅ์ :

  • Last-Modified๋ณด๋‹ค ๋” ์ •ํ™•
  • ์‹œ๊ฐ„์— ์˜์กดํ•˜์ง€ ์•Š๊ณ  ์ฝ˜ํ…์ธ  hash ์‚ฌ์šฉ
  • ์ดˆ ์ดํ•˜์˜ ๋ณ€๊ฒฝ๋„ ๊ฐ์ง€ ๊ฐ€๋Šฅ

Last-Modified vs ETagโ€‹

ํŠน์„ฑLast-ModifiedETag
์ •ํ™•๋„์ดˆ ๋‹จ์œ„์ฝ˜ํ…์ธ  hash, ๋” ์ •ํ™•
์„ฑ๋Šฅ๋” ๋น ๋ฆ„hash ๊ณ„์‚ฐ ํ•„์š”, ๋” ๋А๋ฆผ
์ ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค์ผ๋ฐ˜ ์ •์  ๋ฆฌ์†Œ์Šค์ •๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค
์šฐ์„ ์ˆœ์œ„๋‚ฎ์Œ๋†’์Œ(ETag ์šฐ์„ )

3. How does browser caching work?โ€‹

๋ธŒ๋ผ์šฐ์ € ์บ์‹œ์˜ ๋™์ž‘ ํ๋ฆ„์€ ๋ฌด์—‡์ธ๊ฐ€?

์ „์ฒด ์บ์‹œ ํ๋ฆ„โ€‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๋ธŒ๋ผ์šฐ์ € ๋ฆฌ์†Œ์Šค ์š”์ฒญ ํ๋ฆ„ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ†“
1. Memory Cache ํ™•์ธ
โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ์บ์‹œ ๋ฐœ๊ฒฌ? โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Yes โ”‚ No
โ†“
2. Disk Cache ํ™•์ธ
โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ์บ์‹œ ๋ฐœ๊ฒฌ? โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Yes โ”‚ No
โ†“
3. Service Worker ํ™•์ธ
โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ์บ์‹œ ๋ฐœ๊ฒฌ? โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Yes โ”‚ No
โ†“
4. ์บ์‹œ ๋งŒ๋ฃŒ ์—ฌ๋ถ€ ํ™•์ธ
โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๋งŒ๋ฃŒ๋จ? โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Yes โ”‚ No
โ†“
5. ํ˜‘์ƒ ์บ์‹œ๋กœ ๊ฒ€์ฆ
โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๋ฆฌ์†Œ์Šค ๋ณ€๊ฒฝ? โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Yes โ”‚ No (304)
โ†“
6. ์„œ๋ฒ„์— ์ƒˆ ๋ฆฌ์†Œ์Šค ์š”์ฒญ
โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ์ƒˆ ๋ฆฌ์†Œ์Šค ๋ฐ˜ํ™˜ โ”‚
โ”‚ (200 OK) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

์‹ค์ œ ์˜ˆ์‹œโ€‹

// ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ
GET /api/data.json
Response:
200 OK
Cache-Control: max-age=3600
ETag: "abc123"

{ data: "..." }

// ========== 1์‹œ๊ฐ„ ์ด๋‚ด ์žฌ์š”์ฒญ ==========
// ๊ฐ•๋ ฅํ•œ ์บ์‹œ: ๋กœ์ปฌ์—์„œ ์ง์ ‘ ์ฝ๊ธฐ, ์š”์ฒญ ๋ณด๋‚ด์ง€ ์•Š์Œ
// Status: 200 OK (from disk cache)

// ========== 1์‹œ๊ฐ„ ํ›„ ์žฌ์š”์ฒญ ==========
// ํ˜‘์ƒ ์บ์‹œ: ๊ฒ€์ฆ ์š”์ฒญ ์ „์†ก
GET /api/data.json
If-None-Match: "abc123"

// ๋ฆฌ์†Œ์Šค ๋ฏธ๋ณ€๊ฒฝ
Response:
304 Not Modified
(body๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๊ณ  ๋กœ์ปฌ ์บ์‹œ ์‚ฌ์šฉ)

// ๋ฆฌ์†Œ์Šค ๋ณ€๊ฒฝ๋จ
Response:
200 OK
ETag: "def456"

{ data: "new data" }

4. What are the common caching strategies?โ€‹

์ผ๋ฐ˜์ ์ธ ์บ์‹œ ์ „๋žต์—๋Š” ์–ด๋–ค ๊ฒƒ๋“ค์ด ์žˆ๋Š”๊ฐ€?

1. ์˜๊ตฌ ์บ์‹œ ์ „๋žต(์ •์  ๋ฆฌ์†Œ์Šค์— ์ ์šฉ)โ€‹

// HTML: ์บ์‹œํ•˜์ง€ ์•Š์Œ, ๋งค๋ฒˆ ํ™•์ธ
Cache-Control: no-cache

// CSS/JS(hash ํฌํ•จ): ์˜๊ตฌ ์บ์‹œ
Cache-Control: public, max-age=31536000, immutable
// ํŒŒ์ผ๋ช…: main.abc123.js

์›๋ฆฌ:

  • HTML์€ ์บ์‹œํ•˜์ง€ ์•Š์•„ ์‚ฌ์šฉ์ž๊ฐ€ ์ตœ์‹  ๋ฒ„์ „์„ ๋ฐ›๋„๋ก ๋ณด์žฅ
  • CSS/JS๋Š” hash ํŒŒ์ผ๋ช…์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‚ด์šฉ์ด ๋ฐ”๋€Œ๋ฉด ํŒŒ์ผ๋ช…๋„ ๋ณ€๊ฒฝ
  • ์ด์ „ ๋ฒ„์ „์€ ์‚ฌ์šฉ๋˜์ง€ ์•Š๊ณ  ์ƒˆ ๋ฒ„์ „์ด ๋‹ค์‹œ ๋‹ค์šด๋กœ๋“œ๋จ

2. ์ž์ฃผ ์—…๋ฐ์ดํŠธ๋˜๋Š” ๋ฆฌ์†Œ์Šค ์ „๋žตโ€‹

// API ๋ฐ์ดํ„ฐ: ์งง์€ ์‹œ๊ฐ„ ์บ์‹œ + ํ˜‘์ƒ ์บ์‹œ
Cache-Control: max-age=60, must-revalidate
ETag: "abc123"

3. ์ด๋ฏธ์ง€ ๋ฆฌ์†Œ์Šค ์ „๋žตโ€‹

// ์‚ฌ์šฉ์ž ์•„๋ฐ”ํƒ€: ์ค‘๊ธฐ ์บ์‹œ
Cache-Control: public, max-age=86400 // 1์ผ

// ๋กœ๊ณ , ์•„์ด์ฝ˜: ์žฅ๊ธฐ ์บ์‹œ
Cache-Control: public, max-age=2592000 // 30์ผ

// ๋™์  ์ด๋ฏธ์ง€: ํ˜‘์ƒ ์บ์‹œ
Cache-Control: no-cache
ETag: "image-hash"

4. ๋ฆฌ์†Œ์Šค ์œ ํ˜•๋ณ„ ์บ์‹œ ๊ถŒ์žฅ ์‚ฌํ•ญโ€‹

const cachingStrategies = {
// HTML ํŒŒ์ผ
html: 'Cache-Control: no-cache',

// hash ํฌํ•จ ์ •์  ๋ฆฌ์†Œ์Šค
staticWithHash: 'Cache-Control: public, max-age=31536000, immutable',

// ์ž์ฃผ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š๋Š” ์ •์  ๋ฆฌ์†Œ์Šค
staticAssets: 'Cache-Control: public, max-age=2592000',

// API ๋ฐ์ดํ„ฐ
apiData: 'Cache-Control: private, max-age=60',

// ์‚ฌ์šฉ์ž๋ณ„ ๋ฐ์ดํ„ฐ
userData: 'Cache-Control: private, no-cache',

// ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ
sensitive: 'Cache-Control: no-store',
};

5. Service Worker cachingโ€‹

Service Worker ์บ์‹œ

Service Worker๋Š” ๊ฐ€์žฅ ์œ ์—ฐํ•œ ์บ์‹œ ์ œ์–ด๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ๊ฐœ๋ฐœ์ž๊ฐ€ ์บ์‹œ ๋กœ์ง์„ ์™„์ „ํžˆ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•โ€‹

// Service Worker ๋“ฑ๋ก
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// sw.js - Service Worker ํŒŒ์ผ
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js',
'/images/logo.png',
];

// ์„ค์น˜ ์ด๋ฒคํŠธ: ์ •์  ๋ฆฌ์†Œ์Šค ์บ์‹œ
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache);
})
);
});

// ์š”์ฒญ ์ธํ„ฐ์…‰ํŠธ: ์บ์‹œ ์ „๋žต ์‚ฌ์šฉ
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// ์บ์‹œ ์šฐ์„  ์ „๋žต
return response || fetch(event.request);
})
);
});

// ์—…๋ฐ์ดํŠธ ์ด๋ฒคํŠธ: ์˜ค๋ž˜๋œ ์บ์‹œ ์ •๋ฆฌ
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});

์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์บ์‹œ ์ „๋žตโ€‹

1. Cache First(์บ์‹œ ์šฐ์„ )โ€‹

// ์ ์šฉ: ์ •์  ๋ฆฌ์†Œ์Šค
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});

2. Network First(๋„คํŠธ์›Œํฌ ์šฐ์„ )โ€‹

// ์ ์šฉ: API ์š”์ฒญ
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.then((response) => {
// ์บ์‹œ ์—…๋ฐ์ดํŠธ
const responseClone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
// ๋„คํŠธ์›Œํฌ ์‹คํŒจ, ์บ์‹œ ์‚ฌ์šฉ
return caches.match(event.request);
})
);
});

3. Stale While Revalidate(๋งŒ๋ฃŒ ํ›„ ์žฌ๊ฒ€์ฆ)โ€‹

// ์ ์šฉ: ๋น ๋ฅธ ์‘๋‹ต์ด ํ•„์š”ํ•˜์ง€๋งŒ ์—…๋ฐ์ดํŠธ๋„ ์œ ์ง€ํ•ด์•ผ ํ•˜๋Š” ๋ฆฌ์†Œ์Šค
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
const fetchPromise = fetch(event.request).then((networkResponse) => {
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
});

// ์บ์‹œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์—…๋ฐ์ดํŠธ
return cachedResponse || fetchPromise;
})
);
});

6. How to implement cache busting?โ€‹

Cache Busting์„ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”๊ฐ€?

Cache Busting์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ตœ์‹  ๋ฆฌ์†Œ์Šค๋ฅผ ๊ฐ€์ ธ์˜ค๋„๋ก ๋ณด์žฅํ•˜๋Š” ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค.

๋ฐฉ๋ฒ• 1: ํŒŒ์ผ๋ช… Hash(๊ถŒ์žฅ)โ€‹

// Webpack/Vite ๋“ฑ ๋ฒˆ๋“ค ๋„๊ตฌ ์‚ฌ์šฉ
// ์ถœ๋ ฅ: main.abc123.js

// webpack.config.js
module.exports = {
output: {
filename: '[name].[contenthash].js',
},
};
<!-- ์ฐธ์กฐ ์ž๋™ ์—…๋ฐ์ดํŠธ -->
<script src="/js/main.abc123.js"></script>

์žฅ์ :

  • โœ… ํŒŒ์ผ๋ช…์ด ๋ณ€๊ฒฝ๋˜์–ด ์ƒˆ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๊ฐ•์ œ
  • โœ… ์ด์ „ ๋ฒ„์ „์€ ์บ์‹œ์— ์œ ์ง€, ๋‚ญ๋น„ ์—†์Œ
  • โœ… ๋ชจ๋ฒ” ์‚ฌ๋ก€

๋ฐฉ๋ฒ• 2: Query String ๋ฒ„์ „ ๋ฒˆํ˜ธโ€‹

<!-- ๋ฒ„์ „ ๋ฒˆํ˜ธ ์ˆ˜๋™ ์—…๋ฐ์ดํŠธ -->
<script src="/js/main.js?v=1.2.3"></script>
<link rel="stylesheet" href="/css/style.css?v=1.2.3" />

๋‹จ์ :

  • โŒ ์ผ๋ถ€ CDN์€ query string์ด ์žˆ๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ์บ์‹œํ•˜์ง€ ์•Š์Œ
  • โŒ ๋ฒ„์ „ ๋ฒˆํ˜ธ๋ฅผ ์ˆ˜๋™์œผ๋กœ ๊ด€๋ฆฌํ•ด์•ผ ํ•จ

๋ฐฉ๋ฒ• 3: ํƒ€์ž„์Šคํƒฌํ”„โ€‹

// ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉ
const timestamp = Date.now();
const script = document.createElement('script');
script.src = `/js/main.js?t=${timestamp}`;
document.body.appendChild(script);

์šฉ๋„:

  • ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์บ์‹œ ๋ฐฉ์ง€
  • ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—๋Š” ๋ถ€์ ํ•ฉ(๋งค๋ฒˆ ์ƒˆ ์š”์ฒญ)

7. Common caching interview questionsโ€‹

์ž์ฃผ ๋‚˜์˜ค๋Š” ์บ์‹œ ๋ฉด์ ‘ ์งˆ๋ฌธ

์งˆ๋ฌธ 1: HTML์ด ์บ์‹œ๋˜์ง€ ์•Š๋„๋ก ํ•˜๋ ค๋ฉด?โ€‹

ํด๋ฆญํ•˜์—ฌ ๋‹ต๋ณ€ ๋ณด๊ธฐ
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0

๋˜๋Š” meta ํƒœ๊ทธ ์‚ฌ์šฉ:

<meta
http-equiv="Cache-Control"
content="no-cache, no-store, must-revalidate"
/>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />

์งˆ๋ฌธ 2: Last-Modified๋งŒ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์™œ ETag๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”๊ฐ€?โ€‹

ํด๋ฆญํ•˜์—ฌ ๋‹ต๋ณ€ ๋ณด๊ธฐ

ETag์˜ ์žฅ์ :

  1. ๋” ์ •ํ™•: ์ดˆ ์ดํ•˜์˜ ๋ณ€๊ฒฝ๋„ ๊ฐ์ง€ ๊ฐ€๋Šฅ
  2. ์ฝ˜ํ…์ธ  ๊ธฐ๋ฐ˜: ์‹œ๊ฐ„์ด ์•„๋‹Œ ์ฝ˜ํ…์ธ  hash์— ๊ธฐ๋ฐ˜
  3. ์‹œ๊ฐ„ ๋ฌธ์ œ ๋ฐฉ์ง€:
    • ํŒŒ์ผ ๋‚ด์šฉ์€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์ง€๋งŒ ์‹œ๊ฐ„์ด ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ(์žฌ๋ฐฐํฌ ๋“ฑ)
    • ์ฃผ๊ธฐ์ ์œผ๋กœ ๋™์ผํ•œ ๋‚ด์šฉ์œผ๋กœ ๋Œ์•„์˜ค๋Š” ๋ฆฌ์†Œ์Šค
  4. ๋ถ„์‚ฐ ์‹œ์Šคํ…œ: ์„œ๋กœ ๋‹ค๋ฅธ ์„œ๋ฒ„์˜ ์‹œ๊ฐ„์ด ๋™๊ธฐํ™”๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ

์˜ˆ์‹œ:

// ํŒŒ์ผ ๋‚ด์šฉ์€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์ง€๋งŒ Last-Modified๊ฐ€ ๋ณ€๊ฒฝ๋จ
// 2024-01-01 12:00 - ๋ฒ„์ „ A ๋ฐฐํฌ(๋‚ด์šฉ: abc)
// 2024-01-02 12:00 - ๋ฒ„์ „ A ์žฌ๋ฐฐํฌ(๋‚ด์šฉ: abc)
// Last-Modified๋Š” ๋ณ€๊ฒฝ๋˜์—ˆ์ง€๋งŒ ๋‚ด์šฉ์€ ๋™์ผ!

// ETag์—๋Š” ์ด ๋ฌธ์ œ๊ฐ€ ์—†์Œ
ETag: 'hash-of-abc'; // ํ•ญ์ƒ ๋™์ผ

์งˆ๋ฌธ 3: from disk cache์™€ from memory cache์˜ ์ฐจ์ด๋Š”?โ€‹

ํด๋ฆญํ•˜์—ฌ ๋‹ต๋ณ€ ๋ณด๊ธฐ
ํŠน์„ฑMemory CacheDisk Cache
์ €์žฅ ์œ„์น˜๋ฉ”๋ชจ๋ฆฌ(RAM)ํ•˜๋“œ ๋””์Šคํฌ
์†๋„๋งค์šฐ ๋น ๋ฆ„์ƒ๋Œ€์ ์œผ๋กœ ๋А๋ฆผ
์šฉ๋Ÿ‰์ž‘์Œ(MB ์ˆ˜์ค€)ํผ(GB ์ˆ˜์ค€)
์ง€์†์„ฑํƒญ์„ ๋‹ซ์œผ๋ฉด ์‚ญ์ œ์˜๊ตฌ ์ €์žฅ
์šฐ์„ ์ˆœ์œ„๋†’์Œ(์šฐ์„  ์‚ฌ์šฉ)๋‚ฎ์Œ

๋กœ๋”ฉ ์šฐ์„ ์ˆœ์œ„:

1. Memory Cache(๊ฐ€์žฅ ๋น ๋ฆ„)
2. Service Worker Cache
3. Disk Cache
4. HTTP Cache
5. ๋„คํŠธ์›Œํฌ ์š”์ฒญ(๊ฐ€์žฅ ๋А๋ฆผ)

ํŠธ๋ฆฌ๊ฑฐ ์กฐ๊ฑด:

  • Memory Cache: ๋ฐฉ๊ธˆ ์ ‘๊ทผํ•œ ๋ฆฌ์†Œ์Šค(ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ๋“ฑ)
  • Disk Cache: ์ด์ „์— ์ ‘๊ทผํ•œ ๋ฆฌ์†Œ์Šค ๋˜๋Š” ํŒŒ์ผ ํฌ๊ธฐ๊ฐ€ ํฐ ๋ฆฌ์†Œ์Šค

์งˆ๋ฌธ 4: ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฆฌ์†Œ์Šค๋ฅผ ๊ฐ•์ œ๋กœ ๋‹ค์‹œ ๋กœ๋“œํ•˜๋ ค๋ฉด?โ€‹

ํด๋ฆญํ•˜์—ฌ ๋‹ต๋ณ€ ๋ณด๊ธฐ

๊ฐœ๋ฐœ ๋‹จ๊ณ„:

// 1. Hard Reload(Ctrl/Cmd + Shift + R)
// 2. ์บ์‹œ ์‚ญ์ œ ํ›„ ๋‹ค์‹œ ๋กœ๋“œ

// 3. ์ฝ”๋“œ์— ํƒ€์ž„์Šคํƒฌํ”„ ์ถ”๊ฐ€
const script = document.createElement('script');
script.src = `/js/main.js?t=${Date.now()}`;

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ:

// 1. ํŒŒ์ผ๋ช… hash ์‚ฌ์šฉ(๋ชจ๋ฒ” ์‚ฌ๋ก€)
main.abc123.js // Webpack/Vite ์ž๋™ ์ƒ์„ฑ

// 2. ๋ฒ„์ „ ๋ฒˆํ˜ธ ์—…๋ฐ์ดํŠธ
<script src="/js/main.js?v=2.0.0"></script>

// 3. Cache-Control ์„ค์ •
Cache-Control: no-cache // ๊ฐ•์ œ ๊ฒ€์ฆ
Cache-Control: no-store // ์ „ํ˜€ ์บ์‹œํ•˜์ง€ ์•Š์Œ

์งˆ๋ฌธ 5: PWA ์˜คํ”„๋ผ์ธ ์บ์‹œ๋Š” ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”๊ฐ€?โ€‹

ํด๋ฆญํ•˜์—ฌ ๋‹ต๋ณ€ ๋ณด๊ธฐ
// sw.js - Service Worker
const CACHE_NAME = 'pwa-v1';
const OFFLINE_URL = '/offline.html';

// ์„ค์น˜ ์‹œ ์˜คํ”„๋ผ์ธ ํŽ˜์ด์ง€ ์บ์‹œ
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll([
OFFLINE_URL,
'/styles/offline.css',
'/images/offline-icon.png',
]);
})
);
});

// ์š”์ฒญ ์ธํ„ฐ์…‰ํŠธ
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(() => {
// ๋„คํŠธ์›Œํฌ ์‹คํŒจ, ์˜คํ”„๋ผ์ธ ํŽ˜์ด์ง€ ํ‘œ์‹œ
return caches.match(OFFLINE_URL);
})
);
}
});

์™„์ „ํ•œ PWA ์บ์‹œ ์ „๋žต:

// 1. ์ •์  ๋ฆฌ์†Œ์Šค ์บ์‹œ
caches.addAll(['/css/', '/js/', '/images/']);

// 2. API ์š”์ฒญ: Network First
// 3. ์ด๋ฏธ์ง€: Cache First
// 4. HTML: Network First, ์‹คํŒจ ์‹œ ์˜คํ”„๋ผ์ธ ํŽ˜์ด์ง€ ํ‘œ์‹œ

8. Best practicesโ€‹

๋ชจ๋ฒ” ์‚ฌ๋ก€

โœ… ๊ถŒ์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•โ€‹

// 1. HTML - ์บ์‹œํ•˜์ง€ ์•Š์Œ, ์‚ฌ์šฉ์ž๊ฐ€ ์ตœ์‹  ๋ฒ„์ „์„ ๋ฐ›๋„๋ก ๋ณด์žฅ
// Response Headers:
Cache-Control: no-cache

// 2. CSS/JS(hash ํฌํ•จ) - ์˜๊ตฌ ์บ์‹œ
// ํŒŒ์ผ๋ช…: main.abc123.js
Cache-Control: public, max-age=31536000, immutable

// 3. ์ด๋ฏธ์ง€ - ์žฅ๊ธฐ ์บ์‹œ
Cache-Control: public, max-age=2592000 // 30์ผ

// 4. API ๋ฐ์ดํ„ฐ - ๋‹จ๊ธฐ ์บ์‹œ + ํ˜‘์ƒ ์บ์‹œ
Cache-Control: private, max-age=60
ETag: "api-response-hash"

// 5. Service Worker๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜คํ”„๋ผ์ธ ์ง€์› ๊ตฌํ˜„

โŒ ํ”ผํ•ด์•ผ ํ•  ๋ฐฉ๋ฒ•โ€‹

// โŒ ๋‚˜์œ ์˜ˆ: HTML์— ์žฅ๊ธฐ ์บ์‹œ ์„ค์ •
Cache-Control: max-age=31536000 // ์‚ฌ์šฉ์ž๊ฐ€ ์ด์ „ ๋ฒ„์ „์„ ๋ณผ ์ˆ˜ ์žˆ์Œ

// โŒ ๋‚˜์œ ์˜ˆ: Cache-Control ๋Œ€์‹  Expires ์‚ฌ์šฉ
Expires: Wed, 21 Oct 2025 07:28:00 GMT // HTTP/1.0, ๋” ์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Œ

// โŒ ๋‚˜์œ ์˜ˆ: ์บ์‹œ๋ฅผ ์ „ํ˜€ ์„ค์ •ํ•˜์ง€ ์•Š์Œ
// ์บ์‹œ ํ—ค๋”๊ฐ€ ์—†์œผ๋ฉด ๋ธŒ๋ผ์šฐ์ € ๋™์ž‘์ด ๋ถˆํ™•์‹ค

// โŒ ๋‚˜์œ ์˜ˆ: ๋ชจ๋“  ๋ฆฌ์†Œ์Šค์— ๋™์ผํ•œ ์ „๋žต ์‚ฌ์šฉ
Cache-Control: max-age=3600 // ๋ฆฌ์†Œ์Šค ์œ ํ˜•์— ๋”ฐ๋ผ ์กฐ์ •ํ•ด์•ผ ํ•จ

์บ์‹œ ์ „๋žต ๊ฒฐ์ • ํŠธ๋ฆฌโ€‹

์ •์  ๋ฆฌ์†Œ์Šค์ธ๊ฐ€?
โ”œโ”€ ์˜ˆ โ†’ ํŒŒ์ผ๋ช…์— hash๊ฐ€ ์žˆ๋Š”๊ฐ€?
โ”‚ โ”œโ”€ ์˜ˆ โ†’ ์˜๊ตฌ ์บ์‹œ(max-age=31536000, immutable)
โ”‚ โ””โ”€ ์•„๋‹ˆ์˜ค โ†’ ์ค‘์žฅ๊ธฐ ์บ์‹œ(max-age=2592000)
โ””โ”€ ์•„๋‹ˆ์˜ค โ†’ HTML์ธ๊ฐ€?
โ”œโ”€ ์˜ˆ โ†’ ์บ์‹œํ•˜์ง€ ์•Š์Œ(no-cache)
โ””โ”€ ์•„๋‹ˆ์˜ค โ†’ API์ธ๊ฐ€?
โ”œโ”€ ์˜ˆ โ†’ ๋‹จ๊ธฐ ์บ์‹œ + ํ˜‘์ƒ(max-age=60, ETag)
โ””โ”€ ์•„๋‹ˆ์˜ค โ†’ ์—…๋ฐ์ดํŠธ ๋นˆ๋„์— ๋”ฐ๋ผ ๊ฒฐ์ •

Referenceโ€‹