캐시... 에 대해

장석원·6일 전

4개로 정리 가능하다.
어디에 저장하나, 무엇을 저장하나, 언제까지 믿나, 어떻게 지우나.

계층

1. React 렌더 캐시

  • 컴포넌트 메모리에 계산 결과, 함수, 렌더 결과 저장. 제어는 useMemo, useCallback, React.memo

2. 클라이언트 데이터 캐시

  • JS 메모리(탭 단위)에 API 응답. TanStack Query, SWR

3. 브라우저 HTTP 캐시

  • memory / disk cache에 HTTP 응답 전체, 응답의 Cache-Control

4. Service Worker 캐시

  • Cache Storag에 개발자가 고른 요청, SW 코드(PWA)

5. CDN 캐시

  • 엣지 서버에 HTTP 응답, s-maxage, purge

6. 서버/프레임워크 캐시

  • Next.js 서버에 fetch 결과, 렌더된 HTML/RSC, revalidate, "use cache", 태그

7. DB 앞 캐시

  • Redis 등에 쿼리 결과, 백엔드 코드

흐름은 사용자 요청하면 1번부터 차례로 확인.
앞쪽에서 캐시가 맞으면 뒤로가지 않는다.
이전에는 2~6번이 비어 있어서 모든 요청이 Postgres 까지 가고있었다.

1. React 랜더 캐시

"같은 입력이면 다시 계산하지 않음"

  • useMemo(fn, deps)는 deps가 같으면 계산 결과를 재사용한다.
  • useCallback(fn, deps)는 함수 참조를 재사용한다. 이름이 달라도 useMemo(() -> fn, deps)와 같음
  • React.memo(Component)는 props가 얕은 비교로 같으면 렌더 자체를 건너뛴다.

컴포넌트 인스턴스 하나에 직전 값 1개만 기억한다.
컴포넌트가 언마운트되면 사라지고, 다른 컴포넌트와도 공유되지 않는다.
그리고 React.memo에 객체나 함수를 props로 넘기면서 useMemo/useCallback으로 감싸지 않으면, 매 렌더마다 참조가 바뀌어서 memo가 전혀 동작하지 않는다.

2. TanStack Query / SWR

가장 자주 사용하게 되는 캐시이다.

  • 이름부터 연결되어있는게. SWR 라이브러리 이름은 stale-while-revalidate 에서 왔다.
    HTTP 지시자와 똑같은 전략을 JS 메모리에서 구현한 것.

  • 캐시 키는 queryKey이다. ['catalog', 'plans'] 처럼 사용하는데, 같은 키를 쓰는 컴포넌트 모두 요청 한 번을 공유한다. 이것을 dedupe 라고 불림.

  • 반드시 구분해야 할 두 값이 있다. no-cache / no -store 함정 구조와 같음
    - staleTime 은 언제까지 신선하다고 믿을지. 기본 값이 0이라 쓸때마다 백그라운드에서 다시 가져온다. HTTP의 max-age 에 해당

    • gcTime(구 cacheTime)은 아무도 안 쓰는 데이터를 메모리에서 언제 지울지. 기본값은 5분이고, HTTP로 치면 "저장을 언제까지 하나"에 해당한다.
    • stale이 됐다고 지워지는 게 아니다. 화면에는 stale 데이터를 보여주면서 다시 가져온다.
  • 무효화는 queryClient.invalidateQueries({ queryKey: ['catalog'] })로 한다. 보통 mutation 성공 후 호출.

3. 브라우저 HTTP 캐시

정적 파일 전략 (cache busting)

"브라우저 캐시는 지울 수 없다".

index.html -> Cache-Control: no-cache

/_next/static/app.3f2a1b.js -> Cache-Control: public, max-age=31536000, immutable
  • JS/CSS/이미지 파일명에 내용 해시를 붙인다. 내용이 바뀌면 파일명도 바뀌니, 1년 캐시를 걸어도 안전하다. 옛 파일은 아무도 요청하지 않게 될 뿐이다.
  • HTML만 no-cache로 둬서 항상 쵯니 파일명을 가리키게 한다.
  • 지우는 대신 URL을 바꾸는 방식으로 무효화. Next.js 빌드가 이걸 자동으로 해준다. _next/static 응답 헤더를 DevTools에서 직접 보면 됨

4. Service Worker

오프라인 지원이나 PWA를 만들때 사용하는 캐시.
개발자가 fetch를 가로채 캐시 전략을 코드로 직접 짠다.
"HTTP 캐시와 별개인 캐시가 하나 더 있다."

5. CDN

s-maxage, stale-while-revalidate, purge 모두 여기 해당한다.
사용자끼리 공유되는 유일한 프론트 쪽 캐시. DB 부하를 줄이는 효과가 가장 크다.

6. Next.js캐시

App Router에는 캐시가 여러 겹 있다.

  • Request Memoization: 서버 - 요청 1번 동안 한 렌더 안에서 같은 fetch를 여러번 불러도 한번만 실행
  • Data Cache: 서버,영구 - fetch 결과를 요청 간에 저장, revalidate로 갱신
  • Full Route Cache: 서버 - 렌더된 HTML/RSC 결과 저장
  • Router Cache: 브라우저 메모리 - 방문한 라우트의 RSC 결과를 저장, 뒤로가기 시 즉시 표시

쿼리 하나의 일생..
fresh -> stale -> inactive -> 삭제

0개의 댓글