use cache 공부 정리 | 캐시가 코드 안으로 들어온다는 것
Next.js use cache와 태그 기반 무효화를 GitBook 케이스로 공부한 기록.
Vercel 블로그에서 GitBook 발표 글을 읽었다. 3만 개의 문서 사이트를 단일 배포로 운영하면서 병합하면 즉시 콘텐츠가 갱신된다는 내용이었는데, 그 구조보다 캐시를 어떻게 다루는지가 더 궁금해졌다.
특히 Next.js의 use cache가 이 글에서 핵심으로 등장했고, 그 부분을 중심으로 공부해봤다.
1️⃣ 이게 뭐냐?
use cache는 Next.js에서 데이터 페칭 함수에 직접 붙이는 캐싱 지시어다. 함수 맨 위에 "use cache"라는 문자열을 적으면 그 함수의 결과가 캐시된다. "use client"나 "use server"처럼 React 디렉티브와 같은 방식이다.
기존에는 fetch 호출에 cache 옵션을 붙이거나, 라우트 파일에 revalidate 값을 따로 설정하는 식으로 캐시를 관리했다. 이 방식에서 아쉬운 점은 캐시 정책이 코드 여러 곳에 흩어지고, 어떤 함수가 어디서 캐시되는지를 한눈에 파악하기 어렵다는 것이었다.
use cache는 그 선언을 함수 바로 옆에 둔다. 같은 인자로 들어오는 요청은 API 호출이 중복 실행되지 않고 캐시된 결과가 반환된다. 여러 컴포넌트에서 같은 함수를 호출해도 실제 API 요청은 한 번만 나간다.
GitBook처럼 3만 개 사이트가 동시에 같은 API를 호출하는 환경에서 이게 중요하다. 함수 단위 캐싱으로 중복을 줄이는 건 이걸로 해결이 된다. 그런데 더 어려운 문제는 따로 있었다.
2️⃣ 내가 든 생각
캐시를 효율적으로 유지하려면 언제 무효화할지를 잘 결정해야 한다. 3만 개 사이트가 각자의 업데이트 주기로 돌아가는 멀티테넌트 환경에서, 캐시를 통째로 날리는 건 선택지가 아니다. 한 팀의 병합이 다른 사이트 캐시까지 건드리면 불필요한 비용이 너무 크다.
GitBook의 접근은 태그 기반 무효화였다. 콘텐츠 단위마다 태그를 붙여두고, 병합 이벤트가 발생하면 그 사이트에 연결된 태그만 재검증한다. 나머지는 그대로 유지된다.
👉🏻 이전에 캐시 무효화는 TTL, 즉 만료 시간 개념으로만 생각했는데, 이건 좀 다른 방향이었다. “시간이 지나면 자동으로 풀리는” 방식이 아니라, 변경이 일어났을 때 그 변경에 연결된 것만 날린다.
글에서 또 흥미로웠던 부분이 있었다. AI 크롤러 트래픽 비중이 점점 커지고 있다는 이야기였는데, AI 에이전트가 짧은 시간에 수백 개 페이지를 스캔하는 방식은 사람의 브라우징과 꽤 다른 패턴이다. 이런 트래픽에서는 캐시 적중률을 높여두지 않으면 서버 비용이 빠르게 커질 수 있다는 맥락이었다.
💡 이 구조에서 태그는 어떻게 콘텐츠에 자동으로 붙는 걸까?
문서에는 세부 구현이 나오지 않았는데, 아마 콘텐츠 ID 단위로 자동 생성되는 것 같긴 했다. 태그가 너무 많아지면 관리 비용도 생길 텐데, 그 부분이 어떻게 처리되는지 코드를 더 찾아봐야 할 것 같다.
3️⃣ 앞으로 어떻게 쓸까?
use cache를 직접 써볼 기회가 아직 없었다. 지금은 fetch 단에서 캐시 옵션을 붙이는 수준에 머물러 있다.
다음에 데이터 페칭 함수가 여러 개인 페이지를 만들게 되면 한번 써보고 싶다. 여러 컴포넌트에서 같은 함수를 호출하는 패턴에서 실제로 API 중복이 얼마나 줄어드는지 눈으로 확인해봐야 감이 잡힐 것 같다.
⭐️ 마지막으로, 캐시를 처음 제대로 살펴보면서
정리해보니 use cache가 캐싱 성능을 높이는 기법이라기보다, 캐시의 책임 위치를 바꾸는 일에 가까운 것 같다. 설정 파일이 아닌 함수가 직접 “나는 이렇게 캐시된다”고 선언하고, 무효화도 외부 스케줄이 아닌 병합 이벤트가 트리거한다. 이 두 가지가 같이 움직이는 방식이 꽤 깔끔하다는 인상이었다.
참고 원문: How GitBook serves 30,000 sites with sub-second content updates