Skip to content
iik89
Go back

ISR과 SWR 패턴: 시간 기반 캐시 재검증 아키텍처

1. 핵심 용어 정의

본격적인 구현에 앞서 이번 아키텍처에서 사용되는 핵심 개념을 정확히 정의한다.

시간 기반 재검증 (Time-based Revalidation)

캐시에 저장된 데이터를 즉시 반환하여 서버 부하를 최소화하되, 지정된 주기마다 백그라운드에서 원본 서버에 접근하여 캐시를 최신 데이터로 교체하는 전략이다. 성능과 신선도를 동시에 확보하는 균형점이다.

TTL (Time To Live)

캐시에 저장된 데이터의 유효 기간이다. TTL이 만료되면 해당 캐시는 Stale(만료) 상태로 전환되며, 다음 요청 시점에 백그라운드 갱신이 트리거된다.

ISR (Incremental Static Regeneration) — 점진적 정적 재생성

전체 사이트를 처음부터 다시 빌드하지 않고, TTL이 만료된 특정 페이지만 백그라운드에서 점진적으로 재생성하는 아키텍처다. 수만 개의 페이지를 보유한 대규모 서비스에서 전체 재빌드 비용을 획기적으로 줄인다.

SWR (Stale-While-Revalidate) 패턴

TTL이 만료된 시점에 사용자를 로딩 상태로 대기시키는 대신, 만료된 캐시를 즉시 반환하는 동시에 백그라운드에서 최신 데이터로 재검증 및 교체하는 패턴이다. 사용자 관점에서는 응답 지연이 존재하지 않으며, 다음 요청부터 갱신된 데이터를 제공받는다.


2. 프로젝트 구조

기존 렌더링 전략 테스트베드에 ISR 실험군을 추가한다.

src/
└── app/
    ├── globals.css
    ├── layout.tsx
    ├── page.tsx                        ← [수정] ISR 링크 추가
    └── product/
        ├── dynamic/page.tsx            ← [유지] no-store 동적 렌더링
        ├── cached/page.tsx             ← [유지] force-cache 영구 결빙
        └── isr/
            └── page.tsx                ← [신규] TTL 60초 SWR 컴포넌트

3. ISR 구현 — TTL 부여

코드

// src/app/product/isr/page.tsx

export default async function ISRProductDetail() {
  // 아키텍트의 통제: 영구 결빙 대신 60초의 TTL을 부여한다.
  // 60초 동안은 캐시를 반환하고, 만료 시점에 백그라운드에서 조용히 갱신한다.
  const res = await fetch(
    'https://timeapi.io/api/Time/current/zone?timeZone=Asia/Seoul',
    { next: { revalidate: 60 } } // 핵심: Next.js 전용 캐시 수명 설정
  );
  const data = await res.json();

  return (
    <div className="p-10 max-w-xl mx-auto mt-10 bg-white rounded-xl shadow-lg border border-purple-200">
      <div className="bg-purple-50 text-purple-600 font-bold px-3 py-1 rounded-full w-max mb-4 text-sm">
        ISR — 시간 기반 재검증 (TTL: 60s)
      </div>
      <h1 className="text-3xl font-black text-gray-900 mb-4">
        항공편 운항 정보
      </h1>
      <p className="text-gray-600 mb-4">
        평시에는 캐시를 즉시 반환하고, 60초 주기로 백그라운드에서 데이터를 교체한다.
      </p>
      <div className="bg-gray-900 text-purple-400 font-mono p-4 rounded-lg text-lg relative overflow-hidden">
        <div className="absolute top-0 right-0 bg-purple-600 text-white text-xs font-bold px-2 py-1 rounded-bl-lg">
          TTL: 60s
        </div>
        마지막 갱신 시각:<br />
        <span className="text-white">{data.dateTime}</span>
      </div>
    </div>
  );
}

next: { revalidate } 옵션 상세

next 속성은 표준 JavaScript fetch API에 존재하지 않는다. Next.js 프레임워크가 자체 서버 캐싱 엔진과 통신하기 위해 독자적으로 확장한 전용 객체다. 이 옵션을 통해 Data Cache 계층에 TTL을 부여하고, ISR 메커니즘을 활성화한다.


4. SWR 메커니즘 — 시간 흐름에 따른 동작 해부

next: { revalidate: 60 } 설정이 부여된 페이지는 다음 순서로 동작한다.

1) 최초 요청 (0초)

캐시 창고가 비어 있는 상태다. 프레임워크는 즉시 원본 서버에 요청을 보내 데이터를 가져오고, 그 결과를 캐시에 저장하면서 60초 타이머를 시작한다. 이 첫 번째 요청자는 실제 서버 통신 시간만큼 대기한다.

2) TTL 유효 구간 (1초 ~ 59초)

타이머가 만료되지 않은 상태다. 원본 서버를 전혀 조회하지 않고, 저장된 캐시를 즉시 반환한다. 수만 건의 요청이 동시에 유입되어도 서버 부하는 0에 수렴한다.

3) TTL 만료 시점 (60초 경과)

캐시가 Stale(만료) 상태로 전환된다. 이 시점에 요청이 유입되면 프레임워크는 다음 두 가지를 동시에 수행한다.

이것이 SWR(Stale-While-Revalidate) 패턴의 핵심이다. 사용자에게 지연을 노출하지 않으면서 캐시를 점진적으로 갱신한다.

동작 흐름 요약

최초 요청          TTL 유효 구간           TTL 만료 시점
    │                   │                      │
    ▼                   ▼                      ▼
원본 서버 조회     캐시 즉시 반환         캐시 즉시 반환 (Stale)
캐시 저장          서버 부하 = 0          + 백그라운드 갱신 트리거
타이머 시작                               타이머 리셋

5. 프로덕션 검증

ISR 타이머는 개발 환경(npm run dev)에서 정상적으로 동작하지 않는다. 반드시 프로덕션 환경에서 검증해야 한다.

npm run build
npm start

테스트 절차

1단계http://localhost:3000/product/isr에 접속하여 화면에 표시된 시각을 기록한다.

2단계 — 60초가 경과하기 전까지 새로고침을 반복한다.

3단계 — 60초 경과 후 새로고침을 1회 수행한다.

4단계 — 즉시 새로고침을 1회 더 수행한다.


6. 렌더링 전략 종합 비교

구분no-storeforce-cacherevalidate: N
렌더링 방식SSR (완전 동적)SSG (영구 결빙)ISR (주기적 재생성)
서버 부하요청마다 발생최초 1회TTL 만료 시에만 발생
데이터 신선도100% 실시간빌드 시점으로 고정TTL 주기 내 최신
사용자 대기매 요청마다 발생없음최초 1회만 발생
적합한 데이터실시간 재고/호가약관/고정 공지상품 목록/뉴스 피드

ISR은 성능과 신선도의 절충점이다. 데이터 갱신 주기가 수십 초에서 수 시간 단위로 허용되는 콘텐츠에 적용하면, no-store의 서버 부하와 force-cache의 데이터 경직성 문제를 동시에 해결할 수 있다.


Share this post on:

Previous Post
마이크로 캐싱과 컴포넌트 단위 핀셋 통제: 하이브리드 렌더링 아키텍처
Next Post
Next.js 15 캐시 아키텍처: 두 계층의 충돌과 렌더링 제어 전략