React-query refetchInterval 동적으로 할당하기

이진혁·2023년 5월 29일
post-thumbnail

시작하며

프로젝트 기능 요구 사항 중 실시간으로 데이터를 받아와 클라이언트에서 뿌려주는 작업이 있었다.

로봇과 관련된 데이터를 관리하는 프로젝트 특성상 로봇에서 발생하는 에러들을 빠르게 체크해야 했기 때문이다.

실시간으로 데이터를 받아오는 방법으로 WebSocket이나 SSE를 떠올렸고 SockJs를 통한 양방향 통신 경험이 있기 때문에 무난하게 구현할 수 있는 기능이라 생각했다.

하지만 인생은 내 뜻대로 되는 일이 없었으니,,

백 쪽에서 실시간 통신을 위한 기능 개발에 리소스를 투자할 여력이 없는 상황,,!

하지만 실시간으로 발생하는 에러들을 받아와 빠르게 체크하고 싶어하는 클라이언트의 요구,,!

꽤나 절망적이었다.

당시 회사 개발 팀원 중 한 분이 사내 프로젝트에서 개발했던 기능 중에 실시간으로 데이터를 받아와 뿌려주는 작업을 진행했다고 말씀해주셔서

엇 어떻게 진행한 거지? 역시 다른 방법이 있구나! 싶어 코드를 확인해보니

setInterval를 통해 서버에게 1초마다 data fetching을 하고 있었다,,

폴링도 보통 1분에 한 번씩 하는 것이 일반적이라고 들었는데..

그 많은 에러 로그들을..(3000개가 넘어감 많으면 10000개)

1초에 한 번씩..

무한 요청을 때리고 있었던 것이다..!

아무래도 회사의 핵심적인 비즈니스 모델이 아닌 팀원들이 에러를 체크하는 간단한 용도로만 사용되었기 때문에 많은 리소스를 투자하지 않았던 것으로 생각된다.

결국 실시간으로 데이터를 통신하기 위해 나에게 주어진 건 해당 데이터를 반환하는 작고 귀여운 API 한 줄.

해보자.

React-query 이용하기

내가 처한 상황을 따져보았을 때 이번 기능을 위해 가장 중요시 해야하는 건 바로

불필요한 통신 횟수를 최대한 줄이기.

나의 경우 비동기 과정을 효율적으로 처리하기 위해 react-query를 사용하고 있는데

이 react-query의 refetchInterval 속성을 이용하기로 결정했다.

setTimeout을 이용해 일정 주기를 설정해놓고 데이터를 패칭해오는 전체적인 로직을 refetchInterval로 간단히 설정 할 수 있다.

refetchInterval : 6000

useQuery 속성에 위와 같이 추가하면 1분 마다 새롭게 데이터를 패칭해온다.

하지만 refetch 해오는 주기를 고정적인 값으로 1초, 5초, 10초 등으로 설정해놓으면 불필요한 통신이 너무나 많이 발생하기 때문에 이 refetch하는 주기를 동적으로 할당하기로 결정했다.

먼저 서버에서 받아오는 에러 로그들을 분석한 결과, 첫 번째 에러와 두 번째 에러의 시간 차가 1분 미만이라면 다시 에러가 발생할 확률이 높았다.

반대로 두 에러의 시간 차가 5분 이상 차이가 났다면 다시 에러가 발생할 확률이 현저히 줄어들었다.

또한 이 에러들은 실제 매장에서 운용중인 로봇들에게서 실시간으로 발생하는 에러이기 때문에 매장이 문을 닫는 새벽 시간 때에는 에러가 거의 발생하지 않았다.

추가로 예외적인 케이스가 있었는데 두 에러의 시간 차가 짧은데도 불구하고 에러가 한동안 일어나지 않는 경우가 있었다.

따라서 나는 이 자료들을 토대로 내린 결론은

  1. 두 에러의 시간 차가 만약 짧다면 짧은 주기로 설정.
  2. 두 에러의 시간 차가 만약 길다면 보다 긴 주기로 설정.
  3. 에러가 거의 발생하지 않는 새벽 시간대에는 현재 시간을 계산하여 더욱 긴 주기로 설정.
  4. 두 에러의 시간차가 짧더라도 추가로 에러가 발생하지 않는 경우가 있기 때문에 현재 시간을 계산하고 만약 첫번째 에러가 일어난 지 5분 동안 아무런 에러가 발생하지 않았다면 짧은 시간으로 설정되어 있던 주기로 긴 주기로 교체.
  5. 비교적 실시간으로 데이터를 받아와야 하며 에러들 발생하는 주기 특성 상 새벽 시간을 제외하고 최대 주기는 30초로 제한

시작해보자(비-장)

구현 코드

const refetchTime = () => {
    const present: number = new Date().getTime();
  
    const presentHour: number = new Date().getHours();
  
    const isNightTime: boolean = presentHour >= 2 && presentHour < 6;
  
    const firstErrorTime: number = new Date(recentErrors && recentErrors[0] && recentErrors[0].created_at).getTime();
  
    const secondErrorTime: number = new Date(recentErrors && recentErrors[1] && recentErrors[1].created_at).getTime();
  
    const gap: number = firstErrorTime - secondErrorTime;

    const timeMap: ITimeMap = {
      60000: 5000,
      300000: 10000,
      600000: 20000,
      1200000: 30000,
    };

    if (isNightTime) return 600000;

    if (present - firstErrorTime > 300000) return 30000;

    for (const timeRange in timeMap) {
      if (gap <= Number(timeRange)) {
        return timeMap[timeRange];
      }
    }

    return 30000;
  };

const fetchedData = useQuery(
    ['recentErrors'],
    async () => {
      const data = homeAPI.getRecentErrors();
      return data;
    },
    {
      onSuccess: (data: { error_notice: IErrorNotice[] }) => {
        setRecentErrors(data.error_notice);
      },
      refetchInterval: refetchTime(),
    },
  );

간단하게 설명하자면

  • present : ms 단위의 현재 시간.
  • presentHour : 현재 시.
  • inNighTime : 현재 시가 오전 2시 - 오전 6시 사이에 있는지 판별하는 boolean
  • firstErrorTime : ms 단위의 가장 최근에 발생한 에러 시간
  • secondErrorTime : ms 단위의 두 번째로 발생한 에러 시간
  • gap : ms 단위의 두 에러 시간 차

가장 먼저 체크하는 것은 현재 시간이 새벽인지 아닌지를 판별하고 만약 새벽 시간이라면 10분 주기로 refetch 할 수 있도록 설정해주었다.

그 다음으로는 첫 번째 에러가 발생하고 5분이 지났다면 30초 주기로 refetch 할 수 있도록 설정하였다.

이 조건이 모두 false라면 두 에러의 시간 차에 따라 범주에 맞게 refetch 주기를 할당해주었다.

이 과정에서 if문이나 switch case문을 고려해 보았으나 확장성을 고려한다면 객체 매핑을 이용하는 것이 가장 적합하다고 판단하여 timeMap이라는 객체를 생성하고 for-in 문을 통해 할당해주었다.

그리고 마지막으로 기본 default값으로 새벽 시간을 제외하고 최대 주기인 30초를 리턴해주었다.

구현 후 몇 가지 테스트를 진행해 보았는데 정상적으로 refetchTime이 조건에 알맞는 주기를 리턴하는 것을 확인했다! 짝짝

마치며

사실 아무리 refetch하는 최적의 주기를 찾아 동적으로 할당한다 해도 폴링을 통한 실시간 데이터 통신은 성능상 좋지 않다.

당연하게도 계속해서 요청을 날리면 서버의 부담이 증가하기 때문이다.

데이터의 양이 크면 클수록 더욱,,

WebSocket이나 SSE를 사용해 부담을 최소화 하는 것이 가장 적합하겠지만

방금 언급한 방법을 사용할 수 없는 현재 나의 상황에서 내가 할 수 있는 최선의 방법을 찾고 적용했다고 생각한다.

이번 기능을 구현하면서 특별한 기술적 챌린지는 없었지만,

에러 로그들을 분석하고 결론을 내고 range를 설정하며 refetch 주기를 할당하는 과정을 거치면서

문제를 정의하고 해결 방법을 도출할 수 있는 역량을 기를 수 있는 기회였었던 것 같다.

어떤 환경에서든 최선의 방법을 이끌어 낼 수 있는 개발자가 되고 싶다!

profile
개발 === 99%의 노력과 1%의 기도

1개의 댓글

comment-user-thumbnail
2024년 6월 25일

refetchInterval : 6000 -> 60000 으로 바꿔야 1분이 될 것 같아요

답글 달기