Next.js 서버의 문제점
JavaScript는 싱글 스레드이기에 한번에 하나의 일을 처리할 수 있다.
요청 1 → 처리 중... (다른 요청들 대기)
요청 2 → 대기 중...
요청 3 → 대기 중...
요청 4 → 대기 중...
Node.js는 기본적으로 단일 스레드에서 실행하기 때문에 서버 사이드 렌더링(SSR) 시 요청이 순차적으로 처리하게 된다. 이때 동시 요청이 많으면 병목 현상 발생하여 하나의 요청 처리가 오래 걸리면 다른 요청들이 모두 대기해야 한다.
이는 응답 시간 지연과 서버 과부하로 사용자 경험 저하되는 큰 문제가 되고 운영서버에서 발생할 경우 큰 이슈이다. next.js가 생각보다 적은 양의 트래픽으로도 무너질 수 있다는 것을 주의해야한다.
우리는 Next.js가 최신 기술로 인식되어 쉽게 사용하지만 SSR이 관리하는 방향에 대해서 고민이 필요하고, 프레임워크를 고를때 이를 간과해서는 안된다 .
2. 캐싱 전략 해결책
캐싱이란?
이전에 계산한 결과를 저장해두고, 같은 요청이 오면 저장된 결과를 바로 반환하는 기술 → 모든 사용자가 같은 캐시된 페이지를 받는다.
캐싱의 효과
캐싱 없음:
요청 → 데이터베이스 조회 → 계산 → 렌더링 → 응답 (느림)
캐싱 있음:
요청 → 캐시 확인 → 저장된 결과 반환 (빠름)
3. Next.js 캐싱 방법들
방법 1: fetch를 이용한 데이터 캐싱
// 무조건 적으로 캐싱
const response = await fetch('https://api.example.com/posts', {
next: { revalidate: 'force-cache' }
});
// 60초 동안 캐싱
const response = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 }
});
동작 과정:
- 1.첫 번째 요청: API 호출 → 결과 캐싱
- 2.60초 내 요청: 캐시된 데이터 즉시 반환
- 3.60초 후 요청: 캐시 갱신
방법 2: 페이지 레벨 캐싱 (ISR)
// next app router
// 1시간 동안 페이지 전체 캐싱
export const revalidate = 3600;
export default async function Page() {
const posts = await fetch('https://api.example.com/posts');
return <div>포스트 목록</div>;
}
ISR (Incremental Static Regeneration) 동작:
- 1.첫 방문자: 페이지 생성 → 캐싱
- 2.1시간 내 방문자들: 캐시된 페이지 즉시 제공
- 3.1시간 후 첫 방문자: 기존 페이지 제공 + 백그라운드에서 새 페이지 생성
- 4.새 페이지 준비 완료: 이후 방문자들에게 새 페이지 제공
방법 3: 태그 기반 캐싱
// 데이터에 태그 붙이기
const posts = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] }
});
// 필요할 때 특정 태그만 캐시 무효화
import { revalidateTag } from 'next/cache';
export async function createPost() {
// 새 포스트 생성 로직
revalidateTag('posts'); // 'posts' 태그 캐시만 새로고침
}
Next.js ISR 캐시는 어디에 저장될까?
Next.js의 ISR(Incremental Static Regeneration)을 사용할 때 캐시가 정확히 어디에 저장되는지, 그리고 서버가 여러 개일 때 어떤 문제가 발생할 수 있는지 알아보겠습니다.
캐시는 "파일 시스템"에 저장된다
ISR을 사용하면 캐시는 서버가 실행되는 환경의 파일 시스템에 저장됩니다. 메모리가 아닌 실제 파일로 생성되죠.
확인하는 방법
ISR 코드로 빌드 후 다음 경로를 확인해보세요:
.next/server/app/${pagename}.html
이 파일을 열어보면 ISR로 생성된 HTML이 미리 그려져 있는 것을 확인할 수 있습니다. 서버 실행 후 캐시가 갱신되면, 이 파일의 내용도 변경된 데이터로 업데이트됩니다.
멀티 Pod 환경에서의 문제점
여러 개의 서버 인스턴스(Pod)가 실행되는 환경에서는 각각의 서버가 독립적인 파일 시스템을 가집니다.
시나리오 예시
2개의 Pod가 존재하는 경우:
Pod A → server A running → .next/server/app/${pagename}.html
Pod B → server B running → .next/server/app/${pagename}.html
각 Pod마다 별도의 캐시 파일이 생성됩니다. n개의 Pod가 있다면 캐시도 n개 존재하게 되죠.
문제 상황: 지속적인 Cache Miss
다음과 같은 조건에서 문제가 발생할 수 있습니다:
revalidate: 60
설정- 사용자가 30초마다 방문
- 트래픽이 로드밸런싱으로 순차 분산
타임라인:
0~30초 → 사용자 A 방문 → Pod A cache miss (Pod A에서 캐시 생성)
31~60초 → 사용자 B 방문 → Pod B cache miss (Pod B에서 캐시 생성)
61~120초 → 사용자 C 방문 → Pod A cache miss (Pod A에서 다시 캐시 생성)
...
결과적으로 계속해서 cache miss가 발생하는 상황이 벌어집니다.
해결방안: Redis를 활용한 통합 캐시 관리
이런 문제를 해결하기 위해 Redis 캐싱 서버를 도입할 수 있습니다.
Redis 도입 후의 플로우
0~30초 → 사용자 A 방문 → Pod A → Redis cache miss (캐시 생성)
31~60초 → 사용자 B 방문 → Pod B → Redis cache hit ✅
61초 후 → 사용자 C 방문 → Pod A → Redis cache hit ✅
이제 60초마다 한 번씩만 캐시가 생성되어 원하는 대로 동작합니다.
정리
- Next.js ISR 캐시는 파일 시스템에 저장됩니다
- 멀티 Pod 환경에서는 각 Pod마다 독립적인 캐시가 생성됩니다
- 로드밸런싱 환경에서는 지속적인 cache miss 문제가 발생할 수 있습니다
- Redis 같은 외부 캐시 저장소를 사용하여 문제를 해결할 수 있습니다