[ 공모전 ] useQuery + Promise.all vs useQueries

최문길·2024년 9월 1일
0

공모전

목록 보기
46/46

결과

Mode/요구사항 Type/설명 useQueries useQuery + Promise.all
Normal Performance 평균 (ms) 1613 ms 1212.2 ms
Cache 평균 (ms) 1.6 ms 2.3 ms
Secret Mode Performance 평균 (ms) 420.6 ms 282 ms
Cache 평균 (ms) 1.9 ms 2.1 ms
요구사항 API 병렬 호출
API 통신 성공
react-query 사용
렌더링 최적화 ✖️ / 2회 ✅ / 1회
로딩 및 에러 처리 ✖️ / 2번 ✅ / 1번
queryKey 수 50개 / 코드 복잡성, 확장성 복잡 25개 / 유지 및 코드의 단순화



서울시 공공 API를 병렬 처리할 API 모듈을

  • useQuery+Promise.all
  • useQueries

두 가지를 가지고 performance 측정을 진행해 보았다.

hook 만들기

필수 값들

interface ILocationType {
  api_query: T_LocationType | null;
}

const DYNAMIC_API_QURIES = [
  { query_key: "LOCALDATA_020301_" },
  { query_key: "LOCALDATA_020302_" },
]; 
  • T_Locationtype 에는 각 지역구별 별칭이 있다.
    eg)
    | "JG"
    | "YC"
    | "GJ"
    | "GD"
    | "JN"

  • DYNAMIC_API_QURIES 에 있는 query_key 는 동물병원/동물약국 queryString이다.

이 두가지를 합쳐서 특정 지역구의 동물병원/동물약국으로 요청을 보낸다.
eg ) LOCALDATA_020301_JG = 중구 동물병원 uri이다.

hook 만들기

const hook = (props:ILocationType) {
  const { api_query } = props;
  // useCallback,useMemo 하면 되는데 귀찮아서 이걸로 리렌더링 방지
  const [cachedQueryState, setCachedQueryState] = useState(null);
  // performance 측정할 ref
  const ref = useRef({ start: null, end: null });
  
  useEffect(() => {
    if (api_query !== cachedQueryState) {
      setCachedQueryState(api_query);
      ref.current.start = performance.now();
    }
  }, [cachedQueryState, api_query]);
}

props를 통해 들어온 api_query : "JG"|"GB" ... 를 통해서 useEffect안에서 performance를 측정하기 시작한다.

useQuery/useQueries 내부

// useQuery || useQueries 공통 내부
  	refetchOnWindowFocus: false,
    staleTime: Infinity,
	gcTime: Infinity,
    enabled: !!api_query && api_query === cachedQueryState, //  리렌더링 방지 
      
// useQuery : select
select: (data) => {
      const result = refineSeoulApiData(data);
      ref.current.end = performance.now();
      return result;
    },
// useQueries : combine
 combine(results) {
      const allSuccess = results.every((result) => result.status === "success");
      if (allSuccess) {
        const refinedResults = results.map((result) => result.data);
        const allResults = refineSeoulApiData(refinedResults);
        ref.current.end = performance.now();
        return allResults;
      }
    },

useQuery와 useQueries는 enabled 를 통해서 호출하도록 하였다.
그리고 staleTime과 gcTime을 무한으로 설정하였는데,
각 지역구의 데이터를 한 번씩 만 불러오면 호출을 안해도 되므로 Infinity 로 설정 했다.

selectcombine 안에서 데이터를 호출하면 데이터를 다시 한 번 더 가공까지 끝나야 하므로, 가공이 끝난 직후에 performance를 ref에 넣었다.

return {...,ref}

 return {
    // useQUery+Promise.all/ useQueries 데이터
    ref,
  };

ref를 내보내서 측정 하였다.

측정

측정은 총 6번씩 했으며, 3번은 일반 브라우저에서, 나머지 3번은 secret모드에서 크롬으로 측정 하였다.

측정한 파일은 여기를 보면 된다. useQuery+Promise.all vs useQueries.js

각 측정한 후 평균값을 소수점 한 자리까지 냈다.


type T_AREA = {
   location: string, // 광진구, 강북구 , 도봉구 ...
   api_query: string, // "GJ" | "GB" | "DB" ....
   cache: string, // "2.900000000372529 ms"
   performance: string, // "121.3999999994412 ms"
}[]

위에서 보면 performance는 위에서 설명한 ref의 값이고, cache 부분이 보이는데,

캐시는 호출이 끝난 이후, 데이터 호출 할 필요가 없으므로 cache 에 있는 값을 꺼내서 보여주는 performance 측정값이다.

측정결과

Mode Type Performance 평균 (ms) Cache 평균 (ms)
Normal useQueries 1613 ms 1.6 ms
useQuery + Promise.all 1212.2 ms 2.3 ms
Secret Mode useQueries 420.6 ms 1.9 ms
useQuery + Promise.all 282 ms 2.1 ms
  • 각각의 performance는 useQuery+Promise.all200~400ms정도 빠르다.
  • 각 API 호출 이 후 Cache된 값을 가져오는데 useQueries0.2~0.7 ms 정도 빠르다 .

useQuery+Promise.all 선택 이유들

내가 프로젝트에서 useQuery+Promise.all 을 선택한 이유는 단순히 빠르기 때문만이 아니다.

useQuery vs useQueries

Mode/요구사항 Type/설명 useQueries useQuery + Promise.all
Normal Performance 평균 (ms) 1613 ms 1212.2 ms
Cache 평균 (ms) 1.6 ms 2.3 ms
Secret Mode Performance 평균 (ms) 420.6 ms 282 ms
Cache 평균 (ms) 1.9 ms 2.1 ms
요구사항 API 병렬 호출
API 통신 성공
react-query 사용
렌더링 최적화 ✖️ / 2회 ✅ / 1회
로딩 및 에러 처리 ✖️ / 2번 ✅ / 1번

위와 같이 표로 정리 하였을 때 2가지가 제한이 되어있었다.

queryKey

보지못했던 것 설명 useQueries useQuery+Promise.all
queryKey 수 사용된 queryKey의 개수 50개 25개

queryKey를 50개로 세분화 하는 것은 각각의 각 호출에 따른 데이터를 독립적으로 관리 할 수 있고 먼저 끝난 데이터를 보여줄 수 있다는 점은 useQuery+Promise.all 보다 상대적으로 빠르다.

그렇지만, 프로젝트의 확장성과 유지보수측면에서 본다면, 각 쿼리키에 따른 데이터 관리의 복잡성 증가 및 개별적으로 querykey가 사용되어 처리 되기 때문에 메모리와 자원 소모가 상대적으로 클 우려가 있다.

무엇보다, 우리의 프로젝트에서는 두 개의 데이터를 하나의 데이터로 처리하여 보여주는 쪽으로 방향을 잡았기 때문에, 굳이 queryKey를 세분화 할 필요가 없다 판단 하였다.

참고

https://github.com/TanStack/query/discussions/2011
https://tkdodo.eu/blog/react-query-and-type-script#type-safety-with-the-enabled-option
https://github.com/TanStack/query/discussions/6305

0개의 댓글

관련 채용 정보