멜론 팬스토어 팀이 Next.js App Router로 만든 신규 커머스 서비스의 오픈 전 부하 테스트에서 TPS가 예상보다 낮게 나오자 Full Route Cache를 적용해 서버 렌더링 CPU를 줄인 기록이다. 상품 수가 적고 트래픽이 특정 상품에 몰려 캐시 히트율에 유리하다는 판단이 출발점이다. 적용 후 평균 TPS가 34에서 220으로 올랐다.
핵심 포인트- 평균 TPS가 34에서 220으로 올랐고 다른 조건의 테스트에서도 5배에서 10배 개선이 나왔다.
- Full Route Cache는 서버 렌더링 결과 자체를 재사용하므로 CPU 절감 효과가 가장 크다.
- 적용 조건은 페이지가 Static 렌더링이 되도록 만드는 것이며 Dynamic Function이 하나라도 남아 있으면 캐시 단계가 건너뛰어진다.
- generateStaticParams에서 빈 배열을 반환하면 ISR로 동작해 동적 ID 경로에도 적용할 수 있다.
- Full Route Cache는 Data Cache가 HIT일 때만 동작하므로 페이지의 모든 fetch에 next.revalidate가 설정돼야 한다.
- searchParams로 다루던 태그 필터를 pathParams로 옮겨 static route 조건을 맞췄다.
상세 정리- 서비스 특성: 공연 티켓 구매자에게만 관련 상품을 파는 폐쇄형이라 상품 수가 적고 공연이 끝나면 사라진다. 오픈 시점에 트래픽이 몰리며 상품 목록·상세에 집중되고 재고 값을 빼면 데이터 변경이 거의 없다.
- 판단 근거: 상품 수가 적고 특정 상품으로 트래픽이 집중되는 조건은 캐시 히트율에 유리하므로 서버 렌더링 결과 캐싱이 가장 효과가 클 것으로 봤다.
- 캐싱 메커니즘 지도: Request Memoization은 한 요청 안에서 같은 fetch를 재사용하고, Data Cache는 응답값을 저장하며, Full Route Cache는 HTML과 RSC Payload를 저장하고, Router Cache는 클라이언트 네비게이션용이다.
- Request Memoization 주의: 서버에서 호출되는 GET에만 적용되고 POST나 DELETE, 클라이언트 호출에는 안 걸린다. 한 번의 렌더링 동안만 유효해 revalidate 개념 자체가 없다.
- Data Cache 주의: revalidate 시간이 지나도 첫 요청은 STALE 값을 그대로 반환하고 백그라운드에서 갱신하므로 의도와 다르게 동작할 수 있다. router.refresh로는 갱신되지 않고 revalidatePath를 써야 즉시 갱신된다.
- 대상 선정: 장바구니·결제 같은 개인화 페이지는 트래픽이 적고 동일 응답을 내려주면 안 되므로 제외하고, 비로그인도 조회 가능한 상품 목록·상세만 대상으로 잡았다.
- 사전 작업: cookies, headers, pathParams, searchParams가 여기저기 쓰이고 있어 변경 범위가 넓었고 안정적인 전환을 위해 단위를 쪼개고 테스트 코드를 보강했다.
- 디버깅 도구: 서버 컴포넌트에서 현재 시각을 찍고 옆에 revalidatePath를 호출하는 버튼을 붙여, 새로고침해도 값이 그대로면 캐시 히트로 판단했다.
- 다국어 경로: 지원 언어가 고정이므로 generateStaticParams에 그대로 넣어 static 페이지로 만들었다.
- 동적 경로: 상품 ID는 고정값이 아니므로 generateStaticParams가 빈 배열을 반환하게 해 ISR로 동작시켰다.
- 태그 필터: 상품 목록의 태그 선택을 쿼리스트링에서 경로 파라미터로 옮겨 searchParams 의존을 없앴다.
- 인증 전처리 이관: middleware에서 cookies·headers·searchParams로 하던 전처리를 클라이언트 AuthProvider로 옮겨 useEffect에서 쿠키와 쿼리스트링을 세팅하도록 했다.
- Header/Footer 처리: 개인화 데이터를 쓰고 있어 클라이언트 컴포넌트로 바꾸고 dynamic import에 ssr false를 줬으며, Layout Shift를 막으려 Placeholder 컴포넌트를 넣었다.
- 캐시 설정: layout에 revalidate를 1초로 두고 페이지 내 모든 fetch의 next.revalidate를 그 값 이상으로 맞췄다.
- 알려진 버그: next 14.0.2 이전 버전은 standalone 빌드 시 304 응답을 무한 캐싱하는 버그가 있어, 13대를 쓰던 팀은 메이저 업그레이드 대신 해당 부분만 몽키패치했다.
- 부수 조치: js와 css 정적 리소스를 로드 밸런서에서 캐싱하도록 함께 적용한 뒤 부하 테스트를 다시 돌렸다.
- 남은 아쉬움: App Router와 React Server Component가 아직 실험 단계인 기능과 버그를 안고 있어 프로덕션까지 가는 데 어려움이 많았고 디버깅 도구가 더 필요하다고 밝힌다.
왜 읽나App Router 서비스의 TPS가 안 나올 때 Full Route Cache를 실제로 켜기까지 걷어내야 하는 Dynamic Function 목록과 우회 방법이 구체적으로 나온다.