[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 Cache | sessionStorage, ์์ ํ์ผ |
| ์์ด ๋์ | Cache | Temp / 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);
// โ ์๊ตฌ ์ ์ฅ์ด์ง๋ง ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํ ๊ฒ์ ์๋
์ ์ด ๋ ๊ฐ๋ ์ ๊ตฌ๋ถํ๋ ๊ฒ์ด ์ค์ํ๊ฐ?โ
-
์ค๊ณ ๊ฒฐ์ :
- ์ฑ๋ฅ ์ต์ ํ๊ฐ ํ์ํ๊ฐ? โ ์บ์ ์ฌ์ฉ
- ์์ ๋ณด๊ด์ด ํ์ํ๊ฐ? โ ์์ ์ ์ฅ ์ฌ์ฉ
-
๋ฆฌ์์ค ๊ด๋ฆฌ:
- ์บ์: ์ ์ค๋ฅ , ๋ง๋ฃ ์ ์ฑ ์ ์ฃผ๋ชฉ
- ์์ ์ ์ฅ: ์ ๋ฆฌ ์๊ธฐ, ์ฉ๋ ์ ํ์ ์ฃผ๋ชฉ
-
๋ฉด์ ๋ต๋ณ:
- "์ฑ๋ฅ์ ์ด๋ป๊ฒ ์ต์ ํํ๋๊ฐ" โ ์บ์ ์ ๋ต์ ๋ํด ์ด์ผ๊ธฐ
- "์์ ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋๊ฐ" โ ์์ ์ ์ฅ ๋ฐฉ๋ฒ์ ๋ํด ์ด์ผ๊ธฐ
์ด ๊ธ์์๋ ์ฃผ๋ก Cache(์บ์), ํนํ HTTP ์บ์ ๋ฉ์ปค๋์ฆ์ ๋ํด ์ค๋ช ํฉ๋๋ค.
์บ์์ ์ฅ์ โ
- ๋คํธ์ํฌ ์์ฒญ ๊ฐ์: ๋ก์ปฌ ์บ์์์ ์ง์ ์ฝ์ด HTTP ์์ฒญ์ ๋ณด๋ผ ํ์๊ฐ ์์
- ์๋ฒ ๋ถํ ๊ฐ์: ์๋ฒ๊ฐ ์ฒ๋ฆฌํด์ผ ํ๋ ์์ฒญ ์๋ฅผ ์ค์
- ํ์ด์ง ๋ก๋ฉ ์๋ ํฅ์: ๋ก์ปฌ ์บ์ ์ฝ๊ธฐ ์๋๊ฐ ๋คํธ์ํฌ ์์ฒญ๋ณด๋ค ํจ์ฌ ๋น ๋ฆ
- ๋์ญํญ ์ ์ฝ: ๋ฐ์ดํฐ ์ ์ก๋ ๊ฐ์
- ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์ : ํ์ด์ง ์๋ต์ด ๋นจ๋ผ์ง๊ณ ๋ ๋ถ๋๋ฝ๊ฒ ์ฌ์ฉ ๊ฐ๋ฅ
์บ์์ ์ข ๋ฅโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๋ธ๋ผ์ฐ์ ์บ์ ๊ณ์ธต โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ 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
ํ๋ฆ:
- ์ฒซ ๋ฒ์งธ ์์ฒญ: ์๋ฒ๊ฐ
Last-Modified๋ฅผ ๋ฐํ - ํ์ ์์ฒญ: ๋ธ๋ผ์ฐ์ ๊ฐ
If-Modified-Since๋ฅผ ํฌํจ - ๋ฆฌ์์ค ๋ฏธ๋ณ๊ฒฝ: ์๋ฒ๊ฐ
304 Not Modified๋ฅผ ๋ฐํ - ๋ฆฌ์์ค ๋ณ๊ฒฝ๋จ: ์๋ฒ๊ฐ
200 OK์ ์ ๋ฆฌ์์ค๋ฅผ ๋ฐํ
ETag / If-None-Matchโ
# ์๋ฒ ์๋ต(์ฒซ ๋ฒ์งธ ์์ฒญ)
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
# ๋ธ๋ผ์ฐ์ ์์ฒญ(ํ์ ์์ฒญ)
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
์ฅ์ :
Last-Modified๋ณด๋ค ๋ ์ ํ- ์๊ฐ์ ์์กดํ์ง ์๊ณ ์ฝํ ์ธ hash ์ฌ์ฉ
- ์ด ์ดํ์ ๋ณ๊ฒฝ๋ ๊ฐ์ง ๊ฐ๋ฅ
Last-Modified vs ETagโ
| ํน์ฑ | Last-Modified | ETag |
|---|---|---|
| ์ ํ๋ | ์ด ๋จ์ | ์ฝํ ์ธ 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์ ์ฅ์ :
- ๋ ์ ํ: ์ด ์ดํ์ ๋ณ๊ฒฝ๋ ๊ฐ์ง ๊ฐ๋ฅ
- ์ฝํ ์ธ ๊ธฐ๋ฐ: ์๊ฐ์ด ์๋ ์ฝํ ์ธ hash์ ๊ธฐ๋ฐ
- ์๊ฐ ๋ฌธ์ ๋ฐฉ์ง:
- ํ์ผ ๋ด์ฉ์ ๋ณ๊ฒฝ๋์ง ์์์ง๋ง ์๊ฐ์ด ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ(์ฌ๋ฐฐํฌ ๋ฑ)
- ์ฃผ๊ธฐ์ ์ผ๋ก ๋์ผํ ๋ด์ฉ์ผ๋ก ๋์์ค๋ ๋ฆฌ์์ค
- ๋ถ์ฐ ์์คํ : ์๋ก ๋ค๋ฅธ ์๋ฒ์ ์๊ฐ์ด ๋๊ธฐํ๋์ง ์์ ์ ์์
์์:
// ํ์ผ ๋ด์ฉ์ ๋ณ๊ฒฝ๋์ง ์์์ง๋ง 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 Cache | Disk 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)
โโ ์๋์ค โ ์
๋ฐ์ดํธ ๋น๋์ ๋ฐ๋ผ ๊ฒฐ์