Skip to content

실시간 차트 페이지 만들다가 메모리 터져서 브라우저 죽은 이야기


진단하기

주식 실시간 차트 페이지 만들었는데, QA팀에서 "30분만 켜놓으면 브라우저 죽어요"라고 제보가 들어왔어요. 직접 켜놓고 밥 먹고 왔더니 진짜 탭이 먹통이 되어 있더라구요. Chrome DevTools 열어보니 메모리가 1GB 넘어가면서 계속 올라가고 있었어요. 메모리 누수구나 싶었어요!

재현하기

Memory Profiler로 스냅샷 찍어보니 5분만 지나도 detached DOM nodes랑 event listeners가 늘어나고 있었어요

tsx
useEffect(() => {
  const ws = new WebSocket("wss://api.example.com/realtime");

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    setChartData((prev) => [...prev, data]); // 데이터 계속 쌓임...
  };

  ws.connect();
  // return 빼먹음...
}, []);

useEffect(() => {
  const interval = setInterval(() => {
    fetchMarketData().then(setMarketData);
  }, 5000);
  // `clearInterval` 어디갔어...
}, []);

웹소켓이랑 인터벌 정리 안하고 냅뒀더니 컴포넌트 언마운트되도 계속 돌고 있었던거예요. 게다가 차트 데이터는 계속 쌓이기만 하궁

수정하기

cleanup 추가하고 데이터도 100개로 제한했어요.

tsx
useEffect(() => {
  const ws = new WebSocket("wss://api.example.com/realtime");

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    setChartData((prev) => {
      const updated = [...prev, data];
      return updated.slice(-100); // 최근 100개만
    });
  };

  ws.connect();

  return () => {
    ws.close(); // 이거 추가
  };
}, []);

useEffect(() => {
  const interval = setInterval(() => {
    fetchMarketData().then(setMarketData);
  }, 5000);

  return () => {
    clearInterval(interval); // 이것도 추가
  };
}, []);

AbortController로 API 요청도 취소되게 처리하고, React.memo로 쓸데없는 리렌더링도 막았어요.

재발방지하기

useEffect에서의 "끄기 행동"은 중요하다

setInterval 했으면 clearIntervaladdEventListener 했으면 removeEventListener 꼭 WebSocket 열었으면 close() 꼭 API 호출했으면 AbortController 써서 취소 처리 ESLint에 react-hooks/exhaustive-deps 켜놓으면 빼먹으면 잔소리해줘서 좋아요.

실시간 데이터 다루는 페이지는 꼭 30분은 켜놓고 테스트해보세요. 안 그러면 또 QA팀한테 혼나요!