Skip to content

트러블슈팅 : 사용자의 마지막 액션만 실행하여 API호출 횟수 줄이기

Yein Jo edited this page Aug 17, 2022 · 1 revision

Trouble-Shooting

사용자의 마지막 액션만 실행하기

Map API 에서 사용자 드래그 이벤트에 따른 잦은 데이터 호출 횟수를 평균 10회에서 1회로 줄여 클라이언트 성능 개선

1️⃣ 도입 이유

  1. 위치 기반 서비스로 사용자의 지도 범위를 파악하고 위 경도를 담아 서버로 데이터를 요청하는 방식
  2. 따라서 사용자가 지도를 움직이면 다시 지도의 범위를 파악해 그에 맞는 데이터로 패칭해야함
  3. 하지만 이를 사용자의 모든 액션마다 요청을 보내면 과도한 데이터 요청으로 클라이언트 성능 저하가 우려

따라서 사용자의 드래그 도중 적절한 이벤트 시점을 찾아야 함

2️⃣ 문제 상황

디바운스_미적용2_AdobeExpress (1)
사용자가 지도를 드래그 마다 데이터를 계속해서 호출하고, 그에 따른 클라이언트 리렌더링도 계속해서 일어나고 있음

이런 상황을 위해 맵 API측에서 사용자의 지도 움직임에 따른 타일 전체 리렌더링이 완료될 때를 트리거로 하는tilesloaded액션을 제공함. 이는 사용자의 유휴 상태를 알 수 있는 좋은 이벤트이나 그럼에도 불구하고 아래의 상황이 아쉬웠음

  1. 맵이 빠르게 로딩되는 환경이라면, 사용자가 드래그를 여러 번 하는 짧은 순간에도 트리거에 걸리기 때문에 잦은 서버 데이터 요청으로 성능 저하가 우려됨.
  2. 유저가 찾고자 하는 지역으로 완전히 이동하기 전에도 리스트가 갱신 됨. 이는 데이터 상태를 알려주는 Loading과 데이터 없음 메세지, 그리고 업장 리스트 들을 계속해서 노출 하기 때문에 리스트의 상태가 난잡해보이고 UX적으로도 옳지 않음.
  3. 이후에 배슐랭 맵을 고도화 할 경우 맵API 호출 횟수에 제한이 걸려있기 때문에 횟수가 낭비되는 상황을 최대한 줄여야 함.

3️⃣ 해결 방안

위의 이유로 tilesloaded 이벤트에만 의존하여 요청하기 보단 사용자의 액션에 쓰로틀링을 걸거나 혹은 가장 마지막 액션만을 구분하여 API 요청을 보내야함 이 두가지 방법은 아래와 같이 정리해 볼 수 있음

방법 1. 쓰로틀링 : 일정 시간 단위마다 주기적으로 감시하여 API요청 보내기 방법 2. 디바운싱 : 사용자의 액션이 일정 시간 동안 발생하지 않으면 마지막 요청만 실행하기

❓ 의견 조율

방법 1은 문제 상황 2번인 유저가 찾고자 하는 지역으로 완전히 이동하기 전에도 API를 요청할 수 있기 때문에 해결 방안으로 옳지 않음

방법 2. 디바운스 시간을 짧게 가져가면 그냥 tilesloaded 만 쓰는 것과 유의미한 차이를 보이기 어려울 수 있고, 길게 가져간다면 데이터 로딩 시간의 지연으로 느껴져 오히려 사용자 경험을 해칠 수 있음.

✅ 의견 결정

방법 2인 디바운스 방식으로 결정. 디바운스 시간을 짧게 가져간다 해도 tilesloaded을 그대로 사용하는 것보다는 적게 요청되는 것이 확인됨. 또한 로딩 지연 시간에 관한 부분은 이후 skeleton UI를 적용 시켜 사용자 경험을 보완하기로 함.

💖 결과

결과 : debounce에 0.7초의 delay를 주어 통상적인 속도로 일정 거리를 드래그 할 경우 tilesloaded 만을 이용하는 것보다 API요청이 10회에서 1.5회로 눈에 띄게 줄어든 것을 확인함. 업장 목록도 안정적인 상태로 데이터를 받아오기 때문에 화면적적으로도 개선된 모습을 보임

** 결과 디바운스 코드 Hooks **

```jsx
import { useEffect, useState } from 'react';

function UseDebounce<T>(value: T, delay: number) {
  const [debounceVal, setDebounceVal] = useState(value);
  const [debounceState, setDebounceState] = useState(false);
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebounceVal(value);
      setDebounceState(false);
    }, delay);
    return () => {
      clearTimeout(handler);
      setDebounceState(true);
    };
  }, [value, delay]);
  return {
    debounceVal,
    debounceState,
  };
}

export default UseDebounce;
```