💭 문제 상황
1라운드가 끝난 라운드 통계 화면에서 2라운드 게임을 시작할 때, 게임 화면과 라운드 통계 화면이 서로 라우팅시키며 무한 루프가 돈다.
🔍 현재 구현 방식
게임 화면 → 라운드 통계 화면 : 게임 화면에서 isFinished
가 true일 경우 라우팅
라운드 통계 화면 → 다음 게임 화면 : 라운드 통계 화면에서 isRoundFinished
가 true일 경우 라우팅
게임 화면 → 라운드 통계 화면 → 게임 화면 → 라운드 통계 화면 … 무한 루프
❌ 문제점
라운드 통계 화면에서 다음 게임 화면으로 넘어가는 시점에 isFinished와 isRoundFinished가 모두 true
🤔 고민한 내용
- 라운드 통계 화면에서 다음 버튼을 누르면 API를 호출하면서 isRoundFinished가 true가 되는 건 맞다.
- 그런데 다음 라운드가 진행되었는데 isFinished는 왜 true로 오는가? 서버에서 보낸 isFinished를 그대로 사용하고, 그 값을 true로 보내서 그런거면 서버 로직 문제인가?
- 서버에서 currentRound 값을 증가시키고 isRoundFinished를 true로 바꾸기 때문에 문제 없다.
- 로그 모니터링을 통해 로그를 확인해보니 이전에 진행한 contentId로 요청을 보내고 있었다.
- content Id로 해당 밸런스 게임이 끝났는지 isFinished 값을 넘겨주는데, 이전 라운드의 content Id를 요청하니 isFinished가 true로 넘어오는 것이다.
- 이전 라운드의 content Id 를 요청하면 서버에서 isFinished를 true로 주는게 맞는가?
- 아니다. 400 에러를 던진다. → 캐싱된 데이터가 문제
- 400 에러를 던지는데 같은 id로 왜 다시 요청하는가?
- 같은 id 다시 요청하는 이유는 페이지가 라우팅되면서 컴포넌트가 마운트되어 다시 API 호출 함수가 호출된다.
- 왜 이전 라운드의 content Id를 요청하는가?
- 이전에 요청했던 balanceContent API에서 준 content Id를 쿼리가 캐싱해서 갖고 있고, 그 값으로 vote-finished API를 호출하니 isFinished가 true로 넘어온다.
- isFinished가 true로 넘어오니 게임 화면에서 라운드 통계 화면으로 라우팅시키고, 해당 content Id에 해당하는 라운드 통계화면에서도 isRoundFinished가 true니까 다시 게임화면으로 라우팅시킨다.
- 캐싱을 아예 제거해보자!
- 멀리 떨어진 컴포넌트에서 사용하는 데이터가 같아 공통 부모로 끌어올리기보단 query의 staleTime을 지정해 캐싱 데이터를 가져오는 게 적절하다고 판단하였었다.
- 하지만 balanceContent API 에서 가져오는 content Id가 문제가 되는 것 같아 일단 staleTime을 제거하였다.
- 그전에 무한루프가 돌던 라우팅이 4~5번 정도 라우팅이 반복되고 다음 라운드가 진행되었다.
- balanceContent API 가 캐싱되어 새로운 content Id를 불러오지 않았다.
- 왜 캐싱을 제거했는데 라우팅이 진행되는가?
- 쿼리는 이미 불러온 데이터가 있다면 새로운 데이터가 불러와지기 전까지 이미 불러온 데이터를 반환한다.
- 새로운 데이터가 불러와지기 전까지 이전 데이터를 사용하는데, 해당 query key로 isFinished가 true를 반환한 데이터가 있기 때문에 그대로 불러와지는 것이다.
- 왜 invalidateQuries를 해도 안됐을까?
- invalidateQuries를 사용하면 지정한 쿼리키에 해당하는 캐시 데이터를 제거하고, 다시 fetch 요청하는 것으로 이해했다.
- 그래서 isFinished를 다시 받아오기 위해 invalidate를 적용하였는데 제대로 동작하지 않았다.
- 그 이유를 다시 생각해보니 invalidate하는 시점에 다시 fetch를 해올텐데, 새로운 content Id로 fetch 해오지 않기 때문에 똑같이 true 값을 가져온다.
- 왜 gcTime을 0으로 설정해도 캐싱 데이터가 삭제되지 않고 그대로 true를 반환할까?
- 캐싱된 값을 사용하지 않고 아예 새로운 값을 가져오도록 gcTime: 0과 removeCache를 사용해봐도 캐싱된 데이터가 제거되지 않았다.
- 코드레벨까지 뜯어본 블로그에 따르면 gcTime만큼 캐시가 유지되는 것이 아니라고 한다.
- 컴포넌트에서 쿼리에서 반환한 데이터를 사용하고 있다면 참조값이 끊어지지 않아 이전에 불러온 데이터를 그대로 사용한다.
✅ 해결 방법
balanceContent API 를 호출하여 반환받은 content Id 값이 이전 라운드 값이라서 문제였다.
balanceContent 쿼리의 status 값을 이용해서 라우팅 되는 조건을 수정하였다.
isFetching : true && isFetched : true
인 경우도 존재 → 이미 불러온 값이 있으면서 새로운 데이터를 불러오는 중일 때 (핵심 문제 상황)
현재 fetching 하고 있지 않은 상태에서 isFinished가 true일 경우 라우팅한다.
const { isFinished } = useVoteFinishedQuery();
useEffect(() => {
if(isFinished) {
navigate(...)
}
}, [isFinished, ...]);
const { balanceContent, isFetching } = useBalanceContentQuery();
const { isFinished } = useVoteFinishedQuery({
contentId,
enabled: !!contentId && !isFetching
});
useEffect(() => {
if(isFinished && !isFetching) {
navigate(...)
}
}, [isFinished, ...]);