면접 이어하기 기능 구현 및 문제 해결

최창연·2025년 4월 29일
0

트러블 슈팅

목록 보기
6/6
post-thumbnail

이어하기 기능을 의논하게 된 계기

  • 면접을 보는 사용자들이 불가피하게 면접을 끝냈을 때, 면접을 새롭게 진행해야된다면 사용자의 이탈율이 매우 높을 것이라고 생각하게됨

이러한 이유로 이어가기 기능의 제공 범위를 의논함

  • 최초에는 이어가기 기능 -> interview의 상태(진행 중)에 맞춰서 제공
    • 모든 interview에 대한 이어하기 기능을 제공하고자 함
  • 그러나 해당 부분은 팀원들간의 의견이 상이함
    • 모든 이어가기 제공
      • 사용자로 하여금 어떤 면접이라도 진행할 수 있어야 함
      • 사용자에게 자유로운 서비스 흐름을 제공해야 함
    • 가장 최신의 면접에 대한 이어하기 기능 제공
      • 리소스적인 측면이나 흐름에 있어선 가장 최신의 면접에 대해서만 제공해야 함
      • 면접을 실제와 같이 진행하기 위해선 하나의 면접에 대해서 마무리 짓는 것이 좀 더 알맞는 방식임

우리의 서비스는 실제와 같은 면접 방식을 제공하는 것이 중요하기에 후자의 서비스 방식을 채택하게됨

이어가기 기능의 주요 흐름

  • 사용자가 면접을 진행
  • 면접 도중에 이탈(페이지 이동 발생)
    • 이탈하게 될 때 면접의 상태를 진행 중이라고 저장
    • 추후에 다시 면접 페이지로 접근 시 진행 중 면접 기록을 제공

기능 구현 접근 방식

사용자가 면접을 완료하지 않고 페이지를 나갈 때를 포착한다면, 그 시점에 상태를 저장하고 기록을 변경하도록 하면 되기 때문에 UseEffectCleanUp을 활용하여 Unmounted 시점에 데이터 처리로직을 구현하는 방식으로 이어 하기 기능을 제공하고자 했다.

문제 발생 내용

기능 구현 시, 질문 초기화, 페이지를 나갔음에도 재생되는 음성 등 매우 크리티컬한 문제들이 존재했다. 이 중 중요하게 생각하는 내용 2개를 설명 하려고 한다.

1. tanstack query Mutation 미반영

해당 페이지는 prefetch를 통해 면접 기록을 받아온다. 그렇다면 페이지 이탈 혹은 면접 완료 시 해당 쿼리키로 저장돼있는 데이터를 mutate를 통해서 변경해야 한다. 그렇기 때문에 클린업 함수에 Mutate를 실행하여 해당 로직이 동작하도록 구현했다.

그러나 면접 이탈 이후에 면접 페이지 진입 시 데이터의 변경이 일어나지 않고 있었다.

서버에게 면접 상태 변경 요청 이후 성공 시( onSuccess() ), 쿼리키의 무효화 및 삭제 요청을 보내 면접페이지에 반영되도록 설계했다.

실제로 다른 페이지 이동 이후 면접 페이지로 접근했을 땐 변경된 데이터를 확인할 수 있었지만, 진행 중 곧바로 선택 페이지로 접근했을 때는 데이터를 가져오지 못하는 것을 확인했다.

이 부분을 찾다보니 onSuccessonError의 시점을 mutationFn 이 실행된 직후라고 보장할 수 없다는 것을 확인했다.
해당 내용은 JS의 이벤트 루프와 비동기 처리를 위한 두 가지 큐를 확인할 수 있었다.

setTimeout, setInterval, DOM events 등을 관리하는 Task Queue 그리고 Promises, MutationObserver, queueMicrotask 등을 관리하는 Microtask Queue가 존재한다.

이 때 Microtask가 항상 Task보다 먼저 실행되는데
실제 흐름 순서는

  • unmounted() 클린업 함수 호출
  • 내부에서 patchInterviewHistoryMutate() 실행
  • mutation이므로 내부적으로 Promise가 만들어짐 실제 서버 요청은 백그라운드에서 비동기적으로 처리됨
  • onSuccess는 .then()에 등록된 Microtask로 마이크로태스크 큐에 들어감
  • unmounted()는 여전히 async이긴 해도, 내부 await이 끝날 때까지 기다리지 않고 이후 작업(컴포넌트 언마운트) 로 넘어감 -> 문제 발생 지점
  • 이벤트 루프가 돌아가서 마이크로태스크 큐에 있는 onSuccess()를 실행

React는 useEffect의 cleanup 함수가 비동기임에도 Promise를 기다리지 않는 이유는

  • useEffect의 cleanup 함수는 동기적으로 동작
    • React는 컴포넌트를 언마운트할 때 빠르게 정리(clean up)해야 함
      만약 cleanup이 비동기적이면, 다음 렌더에 영향을 줄 수 있기 때문이다.
  • React는 cleanup 함수가 "지연될 수 있다"고 생각하지 않음
    • 비동기 로직을 cleanup에 넣으면, 버그와 메모리 누수, race condition이 발생할 수 있다.

해당 이유로 onSuccess의 동작이 완료되지 않은 채로 면접 선택 페이지로 이동하게 된 것이다.

문제 해결 방법

UseMutate에는 비동기적인 처리를 위한 mutateAsync 함수가 존재하는데 해당 로직을 이용하여 unmount에서 직접 await를 명시하여 쿼리키 데이터에 대한 무효화 처리를 진행했다.

2. 뒤로 가기 시 면접 데이터 미반영

앞에서 말한 내용처럼 면접 도중에 페이지를 나가게 됐을 때 MutateAsync를 통해 비동기 처리와 쿼리키 무효화를 진행했다.

그러나 뒤로 가기면접 진행 페이지로 접근 시 이전까지 진행된 면접 데이터를 가져오지 못하는 것을 확인했다.

이에 대해서 처음엔 사용자의 뒤로 가기를 통한 면접 진행 페이지 접근을 막고자 했다. 실제로 우리가 설계한 user flow에서는 면접 진행 페이지는 무조건 면접관 선택 페이지를 거쳐야 들어갈 수 있었다.

그래서... 진행한 해결 과정..

  1. 사용자의 router queue를 확인하여 Navigation TypePOP인 것을 확인하고자 했다. 그러나 Next 환경에서는 사용할 수 없는 방법이였다.

  2. 그래서 이번에는 면접 진행 페이지에 들어올 때마다 onPopstate 이벤트 리스너를 추가하여 뒤로 가기에서 접근을 막도록 구현했다.

그러나 해당 방식은 면접 진행 페이지에 계속해서 접근하고 뒤로 가기 동작이 없다면 심각한 메모리 누수가 생길 수 있다. (이벤트 리스너가 계속해서 추가되고 제거되지 않기 때문에)

그래서 다시 한 번 문제가 발생하는 원인을 생각했다.

언마운트 시 서버로 전송한 데이터를 면접 선택 페이지에서 다시 fetch해야 하는데 그 과정없이 면접을 진행하려고 하니 정상 데이터로 진행하지 못한 것이다.
결과적으로 서버로 데이터를 받아오는 과정이 생긴다면 뒤로 가기에도 이어할 수 있게 되는 것이다.

그래서 데이터 fetching 함수를 새롭게 구현해야하나 생각하던 도중에 페이지 이동 시 리렌더링이 일어나면 데이터를 fetching 하는 과정도 다시 진행된다라는 점을 활용하여 아주 간단하게 문제를 해결했다.

해당 문제점들이 중요한 이유

  • 중요성: 비동기 데이터 처리뿐만 아니라, 그 결과가 페이지에 어떻게 렌더링되고 페인팅되는지가 설계한 사용자 플로우대로 작동하는지 확인하는 것이 매우 중요합니다. 서버에 요청을 보내는 로직이 정상적으로 실행되더라도, React의 cleanup과 router 동작 방식에 따라 화면이 갱신되지 않으면 사용자는 데이터가 반영되지 않았다고 느끼게 되어 서비스 신뢰도가 떨어질 수 있습니다. 이 과정에서 async-await 처리router.refresh() 호출 시점을 명확히 설계함으로써, 데이터 요청부터 화면 표시까지 일관된 흐름을 보장할 수 있음을 확인하였습니다.
profile
사용자와 소통하는 프론트엔드 개발자

0개의 댓글