최근 tanstack사의 react-query가 버전 4를 정식 릴리즈 했다. 그리고 공식 이름을 tanstack-query로 변경하고 하위 패키지로 react-query를 제공한다. 이름을 변경한 이유는 react-query v4부터 react 뿐만 아니라 vue, solid, svelte도 지원하기 때문이 아닐까 추측해본다.
회사에서 진행하는 프로젝트에 활발하게 react-query를 도입 중이다. react-query에는 비슷하지만 다른, 헷갈리는 개념들이 몇 개 있다. isFetching과 isLoading, staleTime과 cacheTime이 그러하다. 이번 글은 staleTime과 cacheTime의 개념과 차이점을 정리한 내용이다.
staleTime
stale의 뜻은 "신선하지 않은"이다. 구체적인 사례를 들어 설명하겠다.
아이유 콘서트 티켓팅이 4시부터 시작되고 있다. 이번 기회에는 꼭 콘서트를 보고야 말겠다고 다짐한 현미는 운이 좋게 4시 0분 0초에 티켓팅 좌석 선택 페이지로 접근했다. 명당자리가 비어있는 것을 확인한 현미는 좌석을 선택하고 결제하기 버튼을 누른다. 결제 페이지로 이동하는 순간 "해당 좌석은 예매가 완료되었습니다."라는 문구를 보고 좌절한다.
위 사례에서 16:00:00에 좌석이 비어있다는 정보를 서버로부터 받아와서 현미에게 보여주었지만, 현미가 결제 페이지로 이동하는 순간인 16:00:04에는 그 정보가 신선하지 않은 상한 정보였다. 즉, 현미가 처음 서버로부터 가져왔던 빈 좌석 정보는 stale 상태가 되었다. 일반적으로 서버와 클라이언트의 데이터 주고받음은 양방향 통신이 아니기 때문에 위와 같은 사례는 무수히 경험해보았을 것이다.
그리고 이러한 상황을 피하고자 react-query는 서버로부터 응답받는 데이터를 서버 상태라 정의하고, 이를 정확한 데이터 즉, 신선한 데이터로 동기화해주는 작업을 추상도 높게 제공한다.
특히 특정 상황에 상한 데이터를 자동으로 신선한 데이터로 동기화를 하는 경우도 있다. 그 경우는 다음 세 가지이고, 충분히 데이터가 상했음을 의심할 수 있는 시점이라고 생각된다.
- (쿼리 훅이 포함된) 컴포넌트가 마운트 됐을 때
- 네트워크가 다시 연결됐을 때
- 브라우저가 focus 됐을 때
앞선 내용을 요약하자면 서버 상태는 신선한 상태 또는 상한 상태로 분류된다. 그리고 staleTime은 데이터의 신선한 상태가 유지되는 시간, 다시 말해 상하기까지의 시간이다. staleTime 타이머가 시작되는 시점은 서버 상태가 성공적으로 클라이언트에 도착한 시점이다. 그리고 신선한 상태를 유지하다가 지정한 staleTime이 지나면 stale 상태가 된다.
아무 값을 지정하지 않는다면 기본값인 0(suspense: true 옵션을 사용할 경우 1)으로 설정된다. 이 말은 기본적으로 데이터는 가져온 즉시 상한다는 말이다.
신선한 상태이지만, 데이터를 강제로 다시 가져오기 위한 방법은 invalidateQuries, refetch 두 가지밖에 없다.
staleTime은 어떻게 설정하냐에 따라 중복 요청이 발생할 수도 안 할 수도 있는 중요한 옵션이다. react-query의 중복 요청에 관련된 내용은 아래 글을 참고 바란다.
staleTime이 작동되는 메커니즘을 도식화하면 아래와 같다.
cacheTime
cache라는 단어는 stale보다 상대적으로 친숙할 것이다. 개인적인 견해로, 일반적으로 사용되는 cache의 의미와 미세한 차이가 있고, 그래서 생기는 혼동이 있다고 생각된다. (request의 발생 여부와 전혀 연관이 있지 않다.)
쿼리가 inactive 상태가 되면 cacheTime 동안 메모리에 존재하다가 가비지로 수집되어 제거된다. inactive 상태는 해당 쿼리 훅을 사용하는 모든 컴포넌트가 언마운트 되었다는 의미이다. cacheTime 내에 쿼리가 마운트 되면 react-query 캐시 저장소에 있던 값을 우선 반환한다. 그리고 이렇게 캐시 hit을 하더라도 재요청의 여부는 staleTime에 의해 결정된다.
데이터를 서버로부터 가져온 시점부터 타이머를 시작하는 staleTime과 다르게 cacheTime은 inactive 상태가 되면 타이머를 시작한다. 그리고 staleTime의 기본값은 0이지만, cacheTime의 가본 값은 300,000(5분)이다.
staleTime이 0이라면 각 쿼리를 마운트마다 요청이 발생하기 때문에 캐시 데이터는 그저 placeholder의 역할밖에 하지 않는다.
흐름
react-query의 흐름을 간단하게 나타내면 다음 그림과 같다.
상황 예시
다음은 react-query 도큐먼트에서 나온 캐싱 예시이다.
- 새로운 쿼리 인스턴스
useQuery(\['todos'\], fetchTodos)
가 마운트 된다.- 이 쿼리 + 변수 조합으로 만들어진 다른 쿼리가 없으므로 이 쿼리는 하드 로딩 상태를 표시하고 데이터를 가져오기 위한 네트워크 요청을 한다.
- 그런 다음
\['todos'\]
와fetchTodos
를 고유 식별자로 데이터를 캐시 한다. - 쿼리 훅은 설정한 staleTime(기본값 0, 즉시) 이후에 상한 쿼리가 된다.
useQuery(\['todos'\], fetchTodos)
의 두 번째 인스턴스가 다른 위치에 마운트 된다.- 이 데이터는 이 쿼리의 첫 번째 인스턴스의 캐시 저장소에 존재하므로 해당 데이터는 캐시 저장소에서 즉시 반환된다.
- 새 인스턴스가 화면에 나타나기 때문에 두 쿼리 모두에 대해(단 하나의 요청만) 백그라운드 refetch가 트리거 된다.
- 응답(fetch)이 성공하면 두 인스턴스 모두 새로운 데이터로 업데이트한다.(rerender)
useQuery(\['todos'\], fetchTodos)
쿼리의 두 인스턴스가 모두 마운트 해제되어 더 이상 사용되지 않는다. (inactive)- 이 쿼리의 active 인스턴스가 더 이상 없으므로 쿼리를 삭제하고 가비지 수집하기 위해 캐시 timeout이 cacheTime으로 설정된다. (기본값 5분).
- 캐시 timeout이 완료되기 전에 다른
useQuery(\['todos'\], fetchTodos)
가 마운트 된다. 쿼리는 백그라운드에서 fetchTodos 함수를 실행하여 쿼리를 신선한 값으로 동기화하는 동안 사용 가능한 캐시 된 값을 즉시 반환합니다. - 마지막 useQuery(['todos'], fetchTodos)` 인스턴스가 언마운트 된다.
- 5분 이내에
useQuery('todos', fetchTodos)
쿼리 인스턴스가 더 이상 나타나지 않는다.- 이 쿼리와 해당 데이터는 삭제되고 가비지 수집된다.
추천 및 주의
staleTime을 cacheTime보다 크게 설정하는 것은 의미가 없을 수 있다. staleTime = cacheTime
과 staleTime > cacheTime
은 정확하게 같게 동작할 확률이 크다. 그 이유는, 캐시 값이 만료되면 신선함, 상함에 상관없이 가져올 데이터가 없기 때문이다. 즉, 캐시 값이 만료되면 무조건 요청이 발생한다.
(자세한 설명은 다른 포스트에서 작성하겠습니다.)
결론
cacheTime은 특정 값의 만료와 관련이 있고, staleTime은 특정 쿼리의 만료와 관련이 있다.
참고
https://tkdodo.eu/blog/practical-react-query#the-defaults-explained
https://medium.com/doctolib/react-query-cachetime-vs-staletime-ec74defc483e
'Web > React' 카테고리의 다른 글
react-query props drilling 피하기 (1) | 2022.07.18 |
---|---|
react-query로 클라이언트 상태 관리 하기 (4) | 2022.04.03 |