캐싱 이해하기

쏘뽀끼·2024년 8월 19일

react

목록 보기
12/25

컴퓨터에 있는 데이터를 가져오는 것에 비해 백엔드에서 데이터를 가져오는 일은 시간이 많이 걸린다.
그런데 만약 유저가 방금 전에 확인한 데이터를 또 보고, 또 보는 경우에는 이 똑같은 데이터를 매번 백엔드에서 받아 와야만 할까?
유저가 자주 보는 이 데이터를 어딘가에 저장해 두었다가 백엔드에 요청할 필요 없이 바로 보여주면 좋다.
바로 이럴 때 캐시(cache)라는 걸 사용한다.

캐시(Cache)

캐시란 데이터를 미리 복사해 놓는 임시장소를 말한다.
보통 저장 공간의 크기는 작지만, 데이터를 가져오는 속도는 아주 빠르다는 특징이 있다.
따라서 자주 사용하는 데이터를 캐시에 저장해 두면, 해당 데이터를 훨씬 빠르게 가져와서 사용할 수 있다.


웹 브라우저는 기본적으로 캐시를 사용해서 속도를 높이고 네트워크 비용을 아낀다.
사이트에 접속했을 때 받아 온 데이터를 캐시 형태로 저장해서, 사용자가 같은 사이트에 접속하면 서버에 매번 데이터를 다시 요청하는 게 아니라 저장해 놓은 데이터를 유저에게 보여준다.
그리고 이렇게 캐시를 사용하는 걸 '캐싱'이라고 한다.




React Query의 캐시

리액트 쿼리 역시 이러한 캐싱을 지원한다.
매번 데이터를 백엔드에서 받아 오는 게 아니라 캐시에 저장되어 있는 데이터를 유저에게 보여주기도 한다.
그러면 언제 데이터를 백엔드에서 다시 받아오는 refetch를 하고 언제 캐시에 저장되어 있는 데이터를 보여줄까?
이걸 이해하려면 우선 리액트 뭐리의 데이터 라이프 사이클을 알아야 한다.

function HomePage() {
  const result = useQuery({ queryKey: ['posts'], queryFn: getPosts });
  console.log(result);

  return <div>홈페이지</div>;
}

useQuery()를 이용해 포스트 데이터를 받아왔다.
HomePage라는 컴포넌트가 렌더링되면 useQuery()가 실행이 되고, 여기서 쿼리 함수로 설정한 getPosts()함수를 통해 벡엔드로부터 포스트 데이터를 받아옸었다.
그런데 useQuery()를 사용한다고 무조건 쿼리 함수가 실행되어 백엔드로부터 데이터를 받아오는 것은 아니다.

useQuery()에서 쿼리 함수 말고도 쿼리 키를 설정해 줬다.
리액트 쿼리 개발자 도구를 열어보면, 현재 캐시에 저장되어 있는 데이터들을 확인할 수 있다.


여기에 ['posts']라는 쿼리 키로 받아온 포스트 데이터가 캐시에 저장되어 있는 것을 볼 수 있다.
useQuery()의 동작을 좀 더 자세히 들여다 보면, useQuery()는 먼저 전달받은 쿼리 키로 캐시에 저장된 데이터가 있는지 확인한다.
만약 저장되어 있는 데이터가 없으면 쿼리 함수를 실해해 데이터를 백엔드로부터 받아오게 된다.
그런 다음에 쿼리 키,여기서는 ['posts']라는 키로 데이터를 캐시에 저장한다.

그럼 만약 useQeury()가 실행이 되었는데, 이미 ['posts']라는 쿼리 키로 저장된 데이터가 캐시에 있으면, 데이터의 상태에 따라 조금 다르게 동작한다.
리액트 쿼리는 백엔드에서 이제 막 데이터를 받아와 캐시에 저장된 데이터는 fresh, 즉 신선한 상태로 판단한다.
그러다가 stale time이라고 불리는 특정 시간이 지나면 데이터는 stale, 즉 신선하지 않은 상태가 된다. 마지막으로 컴포넌트가 언마운트되면(DOM 트리에서 제거되면) 해당 데이터가 쓰이지 않는 상태가 되어서 데이터는 inactive상태가 된다.

다시 돌아가서 이미 ['posts']라는 쿼리 키로 저장된 데이터가 있는 경우, useQuery는 캐시에 저장되어 있는 데이터를 리턴한다.
만약 데이터가 fresh상태라면 캐시에 저장된 데이터를 리턴하고 끝이지만, 데이터가 stale상태라면 백그라운드에서 refetch를 진행한다.
그리고 백엔드에ㅓㅅ 새로 받아 온 데이터로 기존의 ['posts']로 저장되어 있는 데이터를 갱신한다.

데이터가 stale상태라면 리액트 쿼리는 기본적으로 다음 네 가지 상황에서 refetch를 진행하게 된다. 새로운 쿼리 인스턴스가 마운트 되거나, 브라우저 창에 다시 포커스가 가거나, 네트워크가 다시 연결되거나, 혹은 미리 설정해 둔 refetch interval 시간이 지났을 때 refetch를 하게 된다.
이를 변경하고 싶다면 각각 refetchOnMount, refetchOnWindowFocus, refetchReconnect, refetchInterval옵션을 변경하면 된다.




stale Time

stale time은 얼마만큼의 시간일 걸까?
사실 리액트 쿼리에서는 디폴트 값으로 stale time이 0으로 설정되어 있다.
그렇기 때문에 사실상 모든 데이터는 백엔드에서 막 받아왔어도 바로 stale상태가 된다.
따라서 매번 데이터가 필요할 때마다 refetch를 하게 된다.
구현하려는 사이트의 특성에 따라 매번 refetch를 할 필요가 없는 상황에서는 stale time 값을 적절히 변경해 주면좋다.




Garbage Collection Time

그럼 이렇게 캐시에 저장된 데이터는 영영 캐시에 남아있는 걸까?
그렇지 않다.
캐시는 한정된 공간이기 때문에 필요없는 데이터는 삭제해서 다른 데이터가 사용할 수 있는 공간을 마련해둬야 한다.
리액트 쿼리는 필요 없는 데이터를 삭제하는 것도 알아서 해준다.
쿼리 컴포넌트가 언마운트 되어 해당 데이터가 쓰이지 않는 상황이 되면 데이터는 inactive상태가 된다고 했다.
inactive상태의 데이터는 가비지 컬렉션 타임이 지나면 캐시에서 삭제가 된다.
가비지 컬렉션 타임은 기본적으로 5분으로 설정되어 있는데, 이 역시 값을 변경할 수 있다.




라이프 사이클 살펴보기

먼저 useQuery()가 실행되는 컴포넌트가 마운트되면 useQuery()를 통해 쿼리 함수가 실행되고 데이터를 받아온다.
받아 온 데이터는 useQuery()에서 지정해 줬던 쿼리 키를 이용해 캐싱, 즉 캐시에 저장이 된다.
이렇게 캐시에 저장된 데이터는 fresh상태에서 staleTime이 지나면 stale상태로 변경된다. 유저가 데이터를 요청하게 되면 캐시된 데이터를 먼저 보여주게 되는데, 이때 데이터가 fresh상태면 추가적인 refetch를 진행하지 않고, stale상태면 백그라운드에서 (자체적으로 알아서) refetch를 진행한다.
refetch가 끝나면 새로운 데이터로 유저에게 보여준다.
컴포넌트가 언마운트되어서 데이터가 inactvie상태가 되면 gcTime(가비지 컬렉션 타임)동안 캐시에 저장되어 잇다가 그 이후에 가비지 콜렉터에 의해 삭제가 되면서 여정이 마무리된다.




라이프 사이클 시간 설정하기

리액트 쿼리에서는 기본적으로 staleTime은 0, gcTime은 5분이다.
staleTime이 0 이므로 기본적으로는 매번 서버에서 데이터를 다시 받아오게 된다.
그래서 별도의 옵션 값 조정 없이 받아온 데이터를 리액트 쿼리 개발자 도구로 살펴보면 처음 데이터를 받아 오자마자 stale 상태가 되는 것을 알 수 있다.
이를 변경하려면 useQuery()에서 staleTime 옵션값을 변경해 주면 된다.
아래 코드와 같이 staleTime값을 1분으로 변경하면 처음 데이터를 받아 와도 1분간은 fresh상태로 유지되는 것을 확인할 수 있다.
그러다가 1분이 지나면 stale한 상태로 변하게 된다.
마찬가지로 가비지 컬렉션 타임음 gcTime옵션 값을 활용해서 변경할 수 있다.

function HomePage() {
  const result = useQuery({
    queryKey: ['posts'],
    queryFn: getPosts,
    staleTime: 60 * 1000,
        gcTime: 60 * 1000 * 10,
  });
  console.log(result);

  return <div>홈페이지</div>;
}

참고로 staleTimegcTime은 밀리초(ms)가 기준이기 때문에, 1000이 곧 1초를 의미한다.
1분은 60초이니 60을 곱해주었다.
여기서 gcTime은 10을 추가로 곱해서 10분으로 설정해보았다.

0개의 댓글