[React-Query] Important Defaults

곽재훈·2024년 6월 8일
post-thumbnail

참조한 공식 문서

https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults

Important Defaults


1. React-Query에서 쿼리의 수명. 그리고 Cache.

Out of the box, TanStack Query is configured with aggressive but sane defaults. Sometimes these defaults can catch new users off guard or make learning/debugging difficult if they are unknown by the user. Keep them in mind as you continue to learn and use TanStack Query:

Query instances via useQuery or useInfiniteQuery by default consider cached data as stale.

쿼리를 사용할 때 동일한 요청을 반복적으로 한다면 똑같은 값을 계속 받아올 가능성이 있다. 그렇게 되면 동일한 데이터를 받기 위해 자원을 낭비하게 되므로 cache를 통해 데이터를 저장해놓고, 만약 다음 요청에서 받아올 데이터가 cache에 저장된 데이터와 같다면 새로 데이터를 받아오지 않고 기존에 저장해 둔 cache data를 불러온다.

그렇다면 언제 데이터를 새로 fetch하고 언제 저장된 cache data를 불러올까? 쿼리에서 cache data는 크게 다섯 가지 상태로 나뉘는데, 여기서는 freshstale 만 다루도록 하자. fresh 상태는 staleTime이 지나지 않은 데이터의 상태이다! 그래서 fresh 상태에서 동일한 요청을 서버로 보낼 경우, 기존에 저장된 cache data를 보여줄 것이다. staleTime이 초과되었을 경우 statestale로 바뀐다. 이 상태에서는 데이터를 요청하면 cache data를 오래된 데이터로 간주하여 서버로부터 새로운 데이터를 불러오는 작업을 수행한다.

그런데 query에서는 이 staleTime이 기본적으로 0인 것이다. 즉, 데이터를 받아오는 순간 그 데이터는 즉시 오래된 데이터로 간주되고 데이터를 요청할 때마다 매번 새로 데이터를 받아오도록 설정되어 있는 것이다.

To change this behavior, you can configure your queries both globally and per-query using the staleTime option. Specifying a longer staleTime means queries will not refetch their data as often

이런 디폴트값을 바꾸려면 query 별로 혹은 전역적으로 query의 staleTime option을 설정할 수 있다고 한다.


2. Query가 Refetch되는 조건들.

Stale queries are refetched automatically in the background when:

  • New instances of the query mount
  • The window is refocused
  • The network is reconnected
  • The query is optionally configured with a refetch interval

stale state인 query가 background애서 자동으로 refetch되는 상황은 네 가지가 있다.

  1. 새로운 query instance가 mount될 때.
  2. 윈도우에 다시 포커스가 들어올 때
  3. 인터넷이 재연결될 때
  4. 쿼리의 option으로 들어가는 refetch interval 시간이 지났을 경우

To change this functionality, you can use options like refetchOnMount, refetchOnWindowFocus, refetchOnReconnect and refetchInterval.

위와 같은 refetch 조건을 수정하고 싶으면 refetchOnMount, refetchOnWindowFocus, refetchOnReconnectm, refetchInterval 등을 사용할 수 있다.

즉, 위의 네 가지 상황에서 의도적으로 refetch 가 일어나는 걸 막고싶을 때 쓸 수 있는 옵션인 것 같다.


3. Query를 수거하는 garbage collector, gcTime.

Query results that have no more active instances of useQuery, useInfiniteQuery or query observers are labeled as "inactive" and remain in the cache in case they are used again at a later time.

By default, "inactive" queries are garbage collected after 5 minutes.

To change this, you can alter the default gcTime for queries to something other than 1000 60 5 milliseconds.

더 이상 활성 인스턴스가 없는 쿼리들은 inactive 상태로 바뀐다. 다만 없어지는 건 아니고 다시 재사용될 경우를 위해서 cache 에 남아있는다. 기본적으로, inactive 상태인 경우에는 5분 뒤에 garbage에 수집된다.

4. Exponential backoff delay. 점진적인 재요청

Queries that fail are silently retried 3 times, with exponential backoff delay before capturing and displaying an error to the UI.

To change this, you can alter the default retry and retryDelay options for queries to something other than 3 and the default exponential backoff function.

여기서 Exponential Backoff 는 점진적으로 시간이 늘어나는 retry 방식의 일종이다.
쿼리 요청이 실패할 경우, 에러를 캡쳐하거나 화면에 표시하기 전에 3번의 점진적인 재시도를 시도한다.
각각의 점진적인 요청은 첫 시도가 실패했을 경우 다음 시도가 400ms 뒤라면, 그 다음 시도는 800ms, 그 다음 시도는 1600ms 뒤에 이루어지는 형태이다. 이렇게 뒤로 갈수록 요청에 대한 delay가 길어지는 이유는 무엇일까? 우리가 가끔씩 핸드폰이나 노트북을 쓸 때, 와이파이나 데이터가 안 터지는 경우들이 있을 것이다. 그런 상황에서 문제가 0.1초만 발생했다가 사라지는 경우는 거의 없지 않은가? 대부분 10분 정도씩 애를 먹고는 한다. 그래서 재시도를 똑같이 400ms간격으로 실행할 경우, 의미없는 4번의 요청이 발생할 수 있고 이는 3번의 추가적인 재시도 요청때문에 서버에게 4배의 트래픽 부담을 지울 수 있기 때문이다. 그래서 상대적으로 부담을 덜 주기 위해서 Exponential backoff delay 라는 방식을 사용한다고 생각할 수 있을 것 같다.
이런 재요청이나 재요청 간격에 대한 delay를 변경하고 싶으면 retryretryDelay 옵션을 조정하면 된다고 한다. 근데 아마 바꿀 일은 거의 없지 않을까.

Query results by default are structurally shared to detect if data has actually changed and if not, the data reference remains unchanged to better help with value stabilization with regards to useMemo and useCallback. If this concept sounds foreign, then don't worry about it! 99.9% of the time you will not need to disable this and it makes your app more performant at zero cost to you.

Structural sharing only works with JSON-compatible values, any other value types will always be considered as changed. If you are seeing performance issues because of large responses for example, you can disable this feature with the config.structuralSharing flag. If you are dealing with non-JSON compatible values in your query responses and still want to detect if data has changed or not, you can provide your own custom function as config.structuralSharing to compute a value from the old and new responses, retaining references as required.

기본적으로는 쿼리는 데이터의 변경 여부를 확인하고 데이터 변경이 없으면 데이터 참조를 변경하지 않은 채로 유지한다고 한다. 즉 기본적으로 우리의 소중한 자원을 아껴주기 위해 노력하고 있다고 볼 수 있다.
다만 이러한 데이터의 구조적 공유는 JSON 호환 값에서만 작동한다고 한다. 즉, 다른 값의 유형은 실제로 데이터가 같더라도 항상 값이 변경된 것으로 간주해서 데이터를 다시 참조하는데 아무래도 이건 데이터를 fetch하는 부분과는 다른 부분인 것 같다.

  • 그럼 날아오는 데이터 중에서 JSON 호환 값이 아닌 건 주로 어떤 형태로 날아오는걸까…? 이미지? 파일?

후기

리액트 쿼리 공식 문서의 첫 부분을 읽었는데, cache에서 fresh state를 stale state로 변경하는 staleTime 외에는 당장 크게 만져볼 부분은 없을 것 같지만 그래도 기본적인 동작 원리에 대해서 천천히 고민해 볼 수 있는 시간이었다!

profile
개발하고 싶은 국문과 머시기

0개의 댓글