๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Cache-Control์€ HTTP ์‘๋‹ต ํ—ค๋”๋กœ, ๋ธŒ๋ผ์šฐ์ €์™€ CDN์ด ์‘๋‹ต์„ ์–ผ๋งˆ๋‚˜ ์˜ค๋ž˜ ์บ์‹ฑํ• ์ง€ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. Sonamu์—์„œ๋Š” API์™€ SSR ์‘๋‹ต์— Cache-Control ํ—ค๋”๋ฅผ ์‰ฝ๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

HTTP ์บ์‹ฑ์ด๋ž€?

HTTP ์บ์‹ฑ์€ ๊ฐ™์€ ์š”์ฒญ์— ๋Œ€ํ•œ ์‘๋‹ต์„ ์žฌ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค.

์บ์‹ฑ ์—†์ด

๋ฌธ์ œ: ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋งค๋ฒˆ DB์—์„œ ์กฐํšŒ (๋А๋ฆผ, ์„œ๋ฒ„ ๋ถ€ํ•˜)

์บ์‹ฑ ์‚ฌ์šฉ

์žฅ์ : ์„œ๋ฒ„ ์š”์ฒญ ์—†์ด ์ฆ‰์‹œ ์‘๋‹ต (๋น ๋ฆ„, ์„œ๋ฒ„ ๋ถ€ํ•˜ ๊ฐ์†Œ)

Cache-Control ํ—ค๋”

๊ธฐ๋ณธ ๊ตฌ์กฐ

Cache-Control: public, max-age=3600
์˜๋ฏธ:
  • public: ๋ชจ๋“  ์บ์‹œ(๋ธŒ๋ผ์šฐ์ €, CDN)์—์„œ ์ €์žฅ ๊ฐ€๋Šฅ
  • max-age=3600: 3600์ดˆ(1์‹œ๊ฐ„) ๋™์•ˆ ์œ ํšจ

์ฃผ์š” ๋””๋ ‰ํ‹ฐ๋ธŒ

public vs private
Cache-Control: public, max-age=3600
  • public: ๋ชจ๋“  ์บ์‹œ์—์„œ ์ €์žฅ ๊ฐ€๋Šฅ (๋ธŒ๋ผ์šฐ์ €, CDN, ํ”„๋ก์‹œ)
  • ์šฉ๋„: ๋ชจ๋“  ์‚ฌ์šฉ์ž์—๊ฒŒ ๋™์ผํ•œ ์‘๋‹ต (์ƒํ’ˆ ๋ชฉ๋ก, ๊ณต์ง€์‚ฌํ•ญ)
Cache-Control: private, max-age=3600
  • private: ๋ธŒ๋ผ์šฐ์ €์—์„œ๋งŒ ์ €์žฅ ๊ฐ€๋Šฅ (CDN/ํ”„๋ก์‹œ๋Š” ๋ถˆ๊ฐ€)
  • ์šฉ๋„: ์‚ฌ์šฉ์ž๋ณ„๋กœ ๋‹ค๋ฅธ ์‘๋‹ต (๋งˆ์ดํŽ˜์ด์ง€, ์žฅ๋ฐ”๊ตฌ๋‹ˆ)

Stale-While-Revalidate

๋งŒ๋ฃŒ๋œ ์บ์‹œ(Stale)๋ฅผ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•˜๋ฉด์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ฐฑ์‹ ํ•˜๋Š” ์ „๋žต์ž…๋‹ˆ๋‹ค.
Cache-Control: public, max-age=60, stale-while-revalidate=300

์ž‘๋™ ๋ฐฉ์‹

์žฅ์ :
  • ์‚ฌ์šฉ์ž๋Š” ํ•ญ์ƒ ๋น ๋ฅธ ์‘๋‹ต (Stale์ด๋ผ๋„ ์ฆ‰์‹œ ๋ฐ˜ํ™˜)
  • ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๊ฐฑ์‹ ์œผ๋กœ ๋‹ค์Œ ์‚ฌ์šฉ์ž๋Š” ์‹ ์„ ํ•œ ๋ฐ์ดํ„ฐ
CloudFront ์ง€์›: AWS CloudFront๋Š” stale-while-revalidate๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

Stale-If-Error

์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ Stale ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
Cache-Control: public, max-age=60, stale-if-error=86400
์ž‘๋™ ๋ฐฉ์‹:
  1. ์ •์ƒ ์ƒํ™ฉ: 60์ดˆ๋งˆ๋‹ค ๊ฐฑ์‹ 
  2. ์„œ๋ฒ„ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ์ตœ๋Œ€ 86400์ดˆ(1์ผ) ๋™์•ˆ Stale ์บ์‹œ ์‚ฌ์šฉ
  3. ์„œ๋ฒ„ ๋ณต๊ตฌ: ๋‹ค์‹œ ์ •์ƒ ๊ฐฑ์‹ 
์šฉ๋„: ์„œ๋ฒ„ ์žฅ์•  ์‹œ์—๋„ ์„œ๋น„์Šค ์œ ์ง€

Vary ํ—ค๋”

๊ฐ™์€ URL์ด๋ผ๋„ ์š”์ฒญ ํ—ค๋”์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
Cache-Control: public, max-age=3600
Vary: Accept-Language
์˜๋ฏธ: Accept-Language ํ—ค๋” ๊ฐ’์— ๋”ฐ๋ผ ์บ์‹œ ๋ถ„๋ฆฌ ์˜ˆ์‹œ:
GET /api/products (Accept-Language: ko) โ†’ ํ•œ๊ตญ์–ด ์‘๋‹ต ์บ์‹ฑ
GET /api/products (Accept-Language: en) โ†’ ์˜์–ด ์‘๋‹ต ์บ์‹ฑ
์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” Vary:
  • Accept-Language: ๋‹ค๊ตญ์–ด ์ง€์›
  • Accept-Encoding: ์••์ถ• ๋ฐฉ์‹ (gzip, br)
  • Authorization: ์ธ์ฆ ์—ฌ๋ถ€

Sonamu vs BentoCache

Sonamu์—๋Š” ๋‘ ๊ฐ€์ง€ ์บ์‹ฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ์žˆ์Šต๋‹ˆ๋‹ค:
๋ธŒ๋ผ์šฐ์ €/CDN ์บ์‹ฑ
@api({
  httpMethod: 'GET',
  cacheControl: { maxAge: 3600 }
})
async getProducts() {
  return this.findMany({});
}
์œ„์น˜: ๋ธŒ๋ผ์šฐ์ €, CDN, ํ”„๋ก์‹œ ์ œ์–ด: HTTP ํ—ค๋”๋กœ ์ œ์–ด ๋Œ€์ƒ: ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฐ›๋Š” ์ตœ์ข… ์‘๋‹ต ํŠน์ง•:
  • ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ ๊ฐ์†Œ
  • ํด๋ผ์ด์–ธํŠธ ์ œ์–ด (๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์บ์‹œ ๊ด€๋ฆฌ)
  • ๋ฌดํšจํ™” ์–ด๋ ค์›€

์กฐํ•ฉ ์‚ฌ์šฉ

๋‘ ๊ฐ€์ง€๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์ตœ๋Œ€ ์„ฑ๋Šฅ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
@cache({ ttl: "10m", tags: ["product"] })  // ์„œ๋ฒ„ ์บ์‹œ
@api({
  httpMethod: 'GET',
  cacheControl: { maxAge: 60 }  // HTTP ์บ์‹œ
})
async getProducts() {
  return this.findMany({});
}
ํšจ๊ณผ:
  1. ์ฒซ ์š”์ฒญ: DB ์กฐํšŒ (๋А๋ฆผ) โ†’ ์„œ๋ฒ„ ์บ์‹œ + HTTP ์บ์‹œ
  2. 60์ดˆ ์ด๋‚ด: ๋ธŒ๋ผ์šฐ์ € ์บ์‹œ์—์„œ ๋ฐ˜ํ™˜ (๋งค์šฐ ๋น ๋ฆ„, ์„œ๋ฒ„ ์š”์ฒญ ์—†์Œ)
  3. 60์ดˆ~10๋ถ„: ์„œ๋ฒ„ ์š”์ฒญํ•˜์ง€๋งŒ ์„œ๋ฒ„ ์บ์‹œ ์‚ฌ์šฉ (๋น ๋ฆ„, DB ์กฐํšŒ ์—†์Œ)
  4. 10๋ถ„ ์ดํ›„: DB ์กฐํšŒ (๋А๋ฆผ) โ†’ ๋‹ค์‹œ ์บ์‹ฑ

์บ์‹ฑ ๋ ˆ์ด์–ด

๊ฐ ๋ ˆ์ด์–ด์˜ ์—ญํ• :
  1. ๋ธŒ๋ผ์šฐ์ € ์บ์‹œ: ์‚ฌ์šฉ์ž๋ณ„ ์บ์‹œ, ๊ฐ€์žฅ ๋น ๋ฆ„
  2. CDN ์บ์‹œ: ์ง€์—ญ๋ณ„ ์บ์‹œ, ๋„คํŠธ์›Œํฌ ์ง€์—ฐ ๊ฐ์†Œ
  3. ์„œ๋ฒ„ ์บ์‹œ: DB ๋ถ€ํ•˜ ๊ฐ์†Œ, ์ฆ‰์‹œ ๋ฌดํšจํ™” ๊ฐ€๋Šฅ
  4. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: ์›๋ณธ ๋ฐ์ดํ„ฐ

์‹ค์ „ ์˜ˆ์ œ

1. ๊ณต๊ฐœ API (๋ชจ๋“  ์‚ฌ์šฉ์ž ๋™์ผ)

@api({
  httpMethod: 'GET',
  cacheControl: {
    visibility: 'public',
    maxAge: 300,  // 5๋ถ„
  }
})
async getProducts() {
  return this.findMany({});
}

2. ๊ฐœ์ธํ™” API (์‚ฌ์šฉ์ž๋ณ„ ๋‹ค๋ฆ„)

@api({
  httpMethod: 'GET',
  cacheControl: {
    visibility: 'private',
    maxAge: 60,  // 1๋ถ„
  }
})
async getMyOrders(ctx: Context) {
  return this.findMany({ 
    where: [['user_id', ctx.user.id]] 
  });
}

3. SSR ํŽ˜์ด์ง€

registerSSR({
  path: '/products',
  cacheControl: {
    visibility: 'public',
    maxAge: 10,
    staleWhileRevalidate: 30,  // ๋งŒ๋ฃŒ ํ›„ 30์ดˆ๊ฐ„ Stale ์‚ฌ์šฉ
  }
});

4. ์ •์  ํŒŒ์ผ (ํ•ด์‹œ ํฌํ•จ)

// ํŒŒ์ผ๋ช…: bundle-abc123.js
{
  cacheControl: {
    visibility: 'public',
    maxAge: 31536000,  // 1๋…„
    immutable: true,  // ์ ˆ๋Œ€ ๋ณ€๊ฒฝ ์•ˆ๋จ
  }
}

5. ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ

@api({
  httpMethod: 'POST',  // Mutation
  cacheControl: {
    noStore: true,  // ์บ์‹œ ์ €์žฅ ๊ธˆ์ง€
  }
})
async createPayment(data: PaymentSave) {
  return this.saveOne(data);
}

์ฃผ์˜์‚ฌํ•ญ

Cache-Control ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ:
  1. Mutation ์š”์ฒญ์€ ์บ์‹ฑ ๊ธˆ์ง€: POST, PUT, DELETE ์š”์ฒญ์€ no-store ์‚ฌ์šฉ
    // โŒ ์ž˜๋ชป๋œ ์˜ˆ
    @api({
      httpMethod: 'POST',
      cacheControl: { maxAge: 60 }  // Mutation์ธ๋ฐ ์บ์‹ฑ
    })
    
    // โœ… ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ
    @api({
      httpMethod: 'POST',
      cacheControl: { noStore: true }
    })
    
  2. ๊ฐœ์ธ ์ •๋ณด๋Š” private ๋˜๋Š” no-store: ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋…ธ์ถœ๋˜๋ฉด ์•ˆ ๋˜๋Š” ๋ฐ์ดํ„ฐ
    // โŒ ์œ„ํ—˜: public์ด๋ฉด CDN์— ์บ์‹ฑ๋  ์ˆ˜ ์žˆ์Œ
    @api({
      cacheControl: { visibility: 'public', maxAge: 60 }
    })
    async getMyProfile() { ... }
    
    // โœ… ์•ˆ์ „: private ๋˜๋Š” no-store
    @api({
      cacheControl: { visibility: 'private', maxAge: 60 }
    })
    async getMyProfile() { ... }
    
  3. ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ๋ฐ์ดํ„ฐ๋Š” ์งง์€ TTL: ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ ๋ฐฉ์ง€
    // โŒ ์žฌ๊ณ ๊ฐ€ ์ž์ฃผ ๋ณ€ํ•˜๋Š”๋ฐ 1์‹œ๊ฐ„ ์บ์‹ฑ
    @api({
      cacheControl: { maxAge: 3600 }
    })
    async getStock() { ... }
    
    // โœ… ์งง์€ TTL ์‚ฌ์šฉ
    @api({
      cacheControl: { maxAge: 10 }
    })
    async getStock() { ... }
    
  4. CDN ์‚ฌ์šฉ ์‹œ s-maxage ๊ณ ๋ ค: CDN์—์„œ ๋” ์˜ค๋ž˜ ์บ์‹ฑ ๊ฐ€๋Šฅ
    @api({
      cacheControl: {
        maxAge: 60,      // ๋ธŒ๋ผ์šฐ์ €: 1๋ถ„
        sMaxAge: 300,    // CDN: 5๋ถ„
      }
    })
    async getData() { ... }
    

๋‹ค์Œ ๋‹จ๊ณ„