Recoil 메모리 누수 확인하기
이전 글에서 Recoil을 선택한 이유와 장점에 대해 설명했다.
마지막에는 Recoil의 메모리 누수 문제점에 대해 언급하며, 내 프로젝트에서도 이 문제가 발생하고 있는지 파악해보겠다고 했다.
결론적으로, 현재 진행 중인 외주 프로젝트에서는 메모리 누수가 발생하지 않았다.
[ 확실히 알게 된 건, atom만 쓴다면 메모리 누수 문제는 없다. ]
메모리 누수가 일어나고 있다고 가정하고, atom의 메모리 사용 상태를 지속적으로 점검했지만,
기존에 사용하던 atom들에서 문제가 발견되지 않았다. [ = 사용되지 않으면서도 메모리에 남아 있는 atom은 없었다. ]
상당히 뻘짓을 많이 했다. 메모리 누수가 일어난다고 해서, 없는 누수를 찾으려고 크롬 개발자 도구 메모리 탭 기능들을 몇일이나 봤다.
이해를 돕기 위해, 메모리 누수가 발생할 수 있는 상황을 코드로 살펴보자.
// ... 기존에 사용하던 atom들
// 테스트를 위해 추가한 selectorFamily
export const userNameQuery = selectorFamily({
key: 'UserName',
get: (userID: number) => async () => {
return userID+"test";
},
});
function UserInfo({userID}:any) {
const userName = useRecoilValue(userNameQuery(userID));
return <div>{userName}</div>;
}
function Root() {
const snapshot = useRecoilSnapshot();
useEffect(() => {
for (const node of snapshot.getNodes_UNSTABLE()) {
console.log(node.key, snapshot.getLoadable(node));
}
console.log("------------------------");
}, [snapshot]);
const [id, setID] = useState(Math.random());
useEffect(() => {
const intervalId = setInterval(() => {
setID(Math.random());
}, 5000);
return () => clearInterval(intervalId);
}, []);
return (
<>
<Suspense fallback={<div>Loading...</div>}>
<UserInfo userID={id} />
</Suspense>
</>
);
}
코드에서는 5초마다 새로운 랜덤 값을 userID로 전달하고, 이를 기반으로 userNameQuery selectorFamily가 새롭게 생성된다.
콘솔 로그를 보면, 새로운 값이 할당되면서 이전에 할당되었던 값도 여전히 메모리에 남아 있는 것을 확인할 수 있다.
하지만, isLogin과 같은 atom의 경우, 로그인 완료 이후 이전 상태 값인 false에서 바뀐 상태 값 true로 바뀐 것을 확인할 수 있다.
atom의 동작방식은 노드는 메모리에서 유지되고, 값만 바뀌기 때문이다.
확인을 위해 코드를 보자.
function Login() {
useTrackNodeChanges("isLogin");
const setIsLogin = useSetRecoilState(isLoginAtom);
// 로그인이 성공적으로 이루어지면 -> setIsLogin(true);
// ...
// ...
}
// 동일한 atom 노드인지 확인하는 함수
const useTrackNodeChanges = (atomKey: string) => {
const prevNodeRef = useRef(null);
const snapshot = useRecoilSnapshot();
const currentNode = Array.from(snapshot.getNodes_UNSTABLE())
.find(node => node.key === atomKey);
if (!currentNode) {
console.log(`Node with key ${atomKey} not found`);
return;
}
const currentValue = snapshot.getLoadable(currentNode).contents;
if (prevNodeRef.current) {
// 노드 동일성 체크
const isSameNode = prevNodeRef.current === currentNode;
console.log({
message: `Node for ${atomKey}:`,
isSameNode,
previousValue: prevNodeRef.current.value,
currentValue,
previousNodeReference: prevNodeRef.current,
currentNodeReference: currentNode
});
}
// 현재 노드를 이전 노드로 저장
prevNodeRef.current = currentNode;
prevNodeRef.current.value = currentValue; // 값도 저장
console.log(prevNodeRef.current);
console.log("--------------------------------------------------------");
};
로그인이 완료되어 isLogin의 값이 true로 바뀌어도, 같은 노드임을 확인할 수 있다.
앞으로, 새로운 Recoil에서 상태 관리에 사용되는 atom 이외의 키워드를 또 도입하게 된다면,
값이 변할 때 새로운 노드를 만들지만 기존의 노드를 삭제하지 않는지,
기존 노드를 유지하는지를 꼭 디버깅 해보는 것이 중요할 것 같다.
이 글이 도움되기를 바랍니다. 추가적으로 보완할 점이 있다면 언제든 알려주세요! 🙂
참조한 사이트
https://github.com/facebookexperimental/Recoil/issues/1840
https://recoiljs.org/ko/docs/api-reference/core/useRecoilSnapshot
https://velog.io/@saewoo1/useRef-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0%EC%9A%94-%EB%84%A4