본문 바로가기

카테고리 없음

[프론트] API 중복 호출 최적화 하기 (with useMemo)

서론

카테고리 페이지에서 필터 옵션을 결정하는 API 호출이 필요 이상으로 반복되는 문제가 발견되었습니다. 이 글에서는 해당 문제의 원인 분석과 해결 과정을 상세히 다루고, API 호출 최적화의 중요성에 대해 설명하겠습니다.

 

 

 

API 호출 최적화의 중요성

불필요한 API 호출은 서비스의 반응 속도를 저하시키고 서버 비용을 증가시킵니다. 이는 곧 사용자 경험의 저하로 이어지므로, 개선이 필수적인 문제입니다.

중복 호출의 원인 분석

1.  strictMode 사용

먼저 API가 왜 중복되어서 호출되는지 파악해야 합니다.

해당 페이지에서는 filter-option api를 두 번 호출합니다.

 

1. 실제 상품 검색에 적용 되는 filter-option 

2. 실시간으로 유저가 필터를 걸었다 뺄 수 있는 실시간 filter-option 

 

즉 실제로 화면에 찍혀야 하는 것은 7개가 아닌 2개가 찍혀있어야 합니다.

 

우선 첫 번째로 확인한 것은 이 API 호출되는 부분입니다.

api는 useEffect 내에서 호출이 되고 있습니다.

 

useEffect(() => {
  const brandIds = transformFixedFiltersData(brandIdsCheckMap)
  const categoryIds = [
    ...transformFixedFiltersData(categoryIdsCheckMap),
    ...initialCategoryIds,
  ]
  const attributeFilters = transformAttributeFiltersData(attributeFiltersMap)

  getFilterOptions({ brandIds, attributeFilters, categoryIds })
}, [
  brandIdsCheckMap,
  attributeFiltersMap,
  categoryIdsCheckMap,
  keyword,
  petTypeFilter,
])

 

 

현재는 개발 환경에서 테스트 중이기 때문에 strictMode가 true로 되어 있습니다.

strictMode에서는 컴포넌트가 두 번 마운트 되기 때문에 7번처럼 보이는 것입니다. 

StrictMode는 왜 개발환경에서 두 번 마운팅 되는가?

StrictMode에서 컴포넌트가 두 번 마운트 되는 것은 의도적인 행동입니다.

리액트에서 StrictMode가 true면 마운트, 원마운트, 업데이트 사이클을 두 번 실행하여 해당 과정이 순수하게 동작하는지 검사를 해주는 역할을 해줍니다.

 

즉 개발환경에서 더 나은 코드 품질 및 버그를 사전에 예방하기 위해서 사용됩니다. 

 

strictMode를 false로 변경 후 확인 한 결과 API 호출 횟수가 7회 -> 4회로 줄어든 걸 확인할 수 있었습니다.

 

우선 첫 번째로 strictMode가 이유인걸 찾았지만 그럼에도 불구하고 계속 API 중복 호출이 일어나고 있습니다.

2.  useMemo 사용

해당 API는 아래 컴포넌트에서 호출하고 있습니다.

 

  const getCategoryIdParams =
    categoryId === ALL_CATEGORIES ? allSubCategoryIds : [categoryId]
...
<SearchFilter initialCategoryIds={getCategoryIdParams} />

 

만약에 해당 컴포넌트가 다른 상태값에 의해서 렌더링이 다시 되면 getCategoryIdParams는 다시 할당이 될 거고 다시 할당이 된다면 SearchFilter는 props가 바뀌었기 때문에 다시 렌더링 되게 됩니다. 

다시 렌더링 하게 된다면 api호출 코드는 다시 호출될 것입니다. 

 

즉 정리하면 아래와 같습니다. 

1. 렌더링의 의해서 getCategoryIdParams가 값이 재할당 된다.

2. getCategoryIdParams가 재할당 되었기 때문에 SearchFilter 컴포넌트는 다시 렌더링 된다.

3. SearchFilter가 다시 렌더링 되면서 해당 API 호출 코드를 다시 호출한다.

 

그러면 이 과정을 해결하는 과정은 useMemo를 통해서 값이 재할당이 이러어져야 하는 경우에만 이루어지도록 하면 매번 값이 할당되는 게 아니기 때문에 SearchFilter 컴포넌트는 다시 렌더링 되지 않을 것입니다. 

 

즉 개선한 코드는 아래와 같습니다. 

 

const getCategoryIdParams = useMemo(
  () => (categoryId === ALL_CATEGORIES ? allSubCategoryIds : [categoryId]),
  [categoryId, title],
)
...
 <SearchFilter initialCategoryIds={getCategoryIdParams} />

 

categoryId 혹은 title이 바뀌었을 경우에만 getCategoryIdParams에 값이 재 할당되기 때문에 기존처럼 리렌더링 되는 현상을 막을 수 있었습니다.

 

이로 인해서 api 호출 횟수를 1회 줄일 수 있었습니다.

3. 호출 중인 API  호출 막기 

그다음으로 생각한 건 useEffect내에 디펜던시 어레이에 걸려있는 값들이 매번 동시에 똑같이 업데이트 되는게 아니라 업데이트 되는 타이밍이 다를거라 생각했습니다. 

 

petFilterType 값을 가지고 api가 호출되고, 그 다음으로  keyword 값을 가지고 api가 연달아 호출된다고 판단했습니다.

즉 상태가 바뀌는 시점에 따라서 이미 호출한 api를 연달아 호출할 수 있다고 생각했습니다. 

 

각기 다른 상태를 모두 한 번에 업데이트하는 방법보다 이미 호출한 API가 있을 경우 호출하지 않도록 하는 게 이 문제를 빠르게 해결할 수 있다고 생각해서 호출 중인 API가 있을 시 return 시켜서 호출되지 않도록 했습니다.

 

프로젝트에서는 tanstack-quert를 사용하고 있기 때문에 isPending인 경우에 return 하도록 했습니다.

 

추가한 코드는 아래와 같습니다.

 

if (productFilterOptionsMutate.isPending) return

 

결과적으로 이를 통해 API 호출 횟수를 1회 줄일 수 있었습니다. 

 

호출 2회

결론 

성능 개선 효과

최적화 작업을 통해 불필요한 API 호출이 크게 줄어들었습니다. 이로 인해서 서버 부하를 줄일 수 있었습니다.

초기에 개발 속도를 우선시하면서 발생할 수 있는 문제를 후속 작업을 통해서 장기적으로 더 견고하고 안정적인 서비스를 만들 수 있었습니다.

 

평소에 이른 최적화를 하기 보다 필요한 순간에 최적화를 하자는 주의로 개발하는데 이번에는 필요한 순간에 useMemo를 적절하게 사용한 것 같아 좋았습니다.