서버 측 렌더링 캐시: Cache-Aside와 CDN 적용
서버 측 렌더링에서 Cache-Aside 패턴과 CDN/Edge 캐싱 적용 방법을 개념, 구현 예제, 무효화 전략과 장애 대응 중심으로 정리한 실무 자료
목차
개요
서버 측 렌더링(SSR) 환경에서 캐시는 성능과 비용에 직접적인 영향을 준다. 여기서는 SSR 캐시 전략 Node.js 관점에서 Cache-Aside 패턴과 CDN(또는 Edge) 캐싱을 어떻게 결합할지 설명한다. 초보자도 이해하기 쉽도록 개념, 흐름, 코드 예제, 유의사항 순으로 정리한다.
기본 개념
Cache-Aside 패턴이란?
Cache-Aside는 애플리케이션이 먼저 캐시를 조회하고, 캐시에 없으면 원본(데이터베이스나 렌더러)에서 데이터를 읽어 캐시에 저장한 뒤 응답하는 방식이다. 읽기 중심 워크로드에서 일관성과 응답 속도 사이에 균형을 맞추기 용이하다.
CDN / Edge 캐싱 역할
CDN은 엣지 노드에 콘텐츠를 저장해 사용자에 가까운 위치에서 응답을 제공한다. SSR에서는 완전 HTML을 엣지에 저장하거나, 페이지 조각(fragment)을 캐시해서 네트워크 왕복을 줄인다. Edge 캐싱 Node 앱 설계는 적절한 캐시 제어 헤더와 무효화 전략이 핵심이다.
Cache-Aside 패턴 적용 흐름
- 클라이언트 요청 수신
- 서버(또는 미들웨어)가 먼저 내부 캐시(예: Redis)를 조회
- 캐시 미스 시 렌더러나 DB 호출로 콘텐츠 생성
- 생성된 콘텐츠를 캐시에 저장(유효기간 포함)
- 응답과 함께 CDN에 캐시될 수 있도록 적절한 헤더 설정
Node.js 구현 예제
설계 포인트
Cache-Aside 패턴 Node에서 구현할 때 고려할 항목:
- 캐시 키 설계: URL, 쿼리, 인증 상태 등으로 구분
- TTL: 페이지 특성에 맞는 만료시간 설정
- 동시성 제어: Cache stampede 방지(락 또는 요청 코alescing)
- 무효화 정책: 콘텐츠 변경 시 캐시를 즉시 비우는 방법
Express + Redis 예제
const express = require('express')
const redis = require('redis')
const fetchPage = require('./render') // 렌더링 함수
const app = express()
const client = redis.createClient({ url: process.env.REDIS_URL })
client.connect()
app.get('*', async (req, res) => {
const key = `page:${req.originalUrl}`
try {
const cached = await client.get(key)
if (cached) {
res.set('X-Cache', 'HIT')
res.set('Cache-Control', 'public, max-age=60')
return res.send(cached)
}
// 캐시 미스: 렌더링 수행
const html = await fetchPage(req)
// 캐시에 저장 (비동기 처리 고려)
await client.setEx(key, 60, html)
res.set('X-Cache', 'MISS')
res.set('Cache-Control', 'public, max-age=60')
return res.send(html)
} catch (err) {
// 장애 시도: 원본으로 응답
console.error(err)
const html = await fetchPage(req)
res.set('Cache-Control', 'no-store')
return res.send(html)
}
})
app.listen(3000)
위 예제는 기본적인 Cache-Aside 패턴 Node 방식이다. 동시성 문제나 대규모 트래픽에는 추가적인 보호 장치가 필요하다.
CDN / Edge 캐싱 설정
헤더 전략
CDN에 의존하려면 응답 헤더로 의도를 명확히 전달해야 한다. 일반적인 설정 예시는 다음과 같다.
// 서버에서 설정할 헤더 예시
res.set('Cache-Control', 'public, max-age=60, s-maxage=300')
res.set('Surrogate-Control', 'max-age=300')
// Vary, ETag 등을 통해 캐시 버전 관리 가능
여기서 s-maxage는 리버스 프록시나 CDN 전용 TTL로, Edge 캐싱 Node 앱 설계에서 자주 사용된다. Surrogate-Control는 일부 CDN에서 우선적으로 참조되는 헤더다.
무효화와 재검증
- 퍼즈(Purge): 콘텐츠가 변경되면 CDN API로 즉시 제거
- 캐시 키 버전: URL에 버전(query 또는 path) 첨가로 블루그린 방식 적용
- 조건부 요청: ETag/Last-Modified로 재검증
운영에서 주의할 점
일관성 vs 성능
짧은 TTL은 최신성 확보에 유리하지만 빈번한 원본 호출을 유발한다. 반면 긴 TTL은 성능을 높이지만 콘텐츠 지연 문제를 동반한다. 중요한 데이터는 캐시 제외나 짧은 TTL로 처리하고, 정적 콘텐츠는 긴 TTL을 사용한다.
장애 대응
- 캐시 장애 시 원본 폴백 경로 확보
- 캐시 스탬피드 방지: 요청 coalescing 또는 분산 락 적용
- 모니터링: 캐시 히트율, 레이턴시, 오류율 관찰
요약과 권장 사항
SSR 캐시 전략 Node.js 환경에서는 Cache-Aside 패턴 Node 적용으로 서버 내부 캐시를 운용하고, CDN/Edge 캐싱을 병행해 사용자 경험을 개선하는 것이 효과적이다. 핵심은 캐시 키 설계, TTL 정책, 무효화 전략, 그리고 장애 시 폴백 경로다. 실무에서는 작은 사례부터 시작해 모니터링을 통해 정책을 조율하는 방식이 안전하다.