23년 1월쯤 회사에 급한 일이 하나 들어온다.
무슨 일이길래 이렇게 급하게 일이 생긴걸까? 확인해보니 투자자에게서
앱이 갑자기 멈추거나 재시작되는 현상이 발생한다고 들어왔다.
우리에게 돈을 주는 사람(자본주의니까...)이니 빠른 해결을 위해 현재 팀에 유일하게 FE를 메인으로 개발하는 나(이 당시에는 아직 팀 전체가 풀스택 개발을 했었다)는 이 사태를 빠르게 파악해본다.
우선 원인 자체는 간단했다. 웹앱이니 만큼 앱이 재시작 되는 원인, 멈추는 원인을 네이티브 개발자와 확인해보니 메모리 누수 이슈라고 한다. 클라이언트 에서 메모리 누수라는 거는 처음 들어보는 이슈라 머리가 멍해졌다.
그러면... 우리 앱이 그만큼 메모리를 많이 먹었다라는건데 어떻게 해결해야할까 하는 중에
백엔드를 진짜 잘하면서도 프론트도 어느정도 익히셨던 분이 우리가 평소에 많은 useCallback, useMemo를 쓰면 최적화가 된다고 하던데 그거를 써보면 어떤가요 했던 말이 생각났다.
근데 그게 진짜 지금 상황에 도움이 되는건가? 그리고 그게 그렇게 좋으면 왜 개발을 할 때 useCallback, useMemo를 안쓰고 또 그 정도로 좋으면 우리가 안쓰더라도 알아서 처리해줘야 하는게 아닌가?
useCallback, useMemo는 오래걸리는 계산이 들어간 변수값 or 함수를 Memoization해 성능을 높이는 방식이다.
이거는 리액트 개념만은 아니다. 컴퓨터 프로그램이 동일한 계산을 반복해야할 때 이전에 계산한 값을 메모리에 저장해 동일한 계산을 생략해 실행 속도를 빠르게 하는 기술이다.
Memoization은 즉 메모리를 활용하는 기술이다. 메모리에 여러번 계산할 것을 저장해 실행 속도를 빠르게 해 성능을 최적화 하는 기술.
세상에 공짜는 없다. useCallback과 useMemo도 그런 케이스가 될 수 있다.
메모리에 저장하기 때문에 메모리 사용량은 점점 증가하게 될 것이다.
지금 현 상황에서는 정말 최악의 수가 될 거라는 거다.
이렇기 때문에 비싼 계산을 하는(즉 많은 계산이 필요한) 곳에만 useCallback과 useMemo를 넣는다라는 것이다.
이 사실을 알고난 후 부터 나는 useCallback과 useMemo의 극 안티팬이 되었다.
근데 어찌됬든 필요하에 만든 것이고 쓰는 사람들도 있는데 어떨 때 사용할 것인가는 정해져야 할 것 같았다.
개인적인 의견이 되겠지만 본인은 사용처를 이렇게 정의했다.
일단 useCallback과 useMemo의 주제에서 다시 본제로 돌아와서 그러면 도대체 뭐가 문제였을까?를 꾸준히 조사했을 때 예상가는 이슈는 다음과 같았다. (90% 이상은 확신하나, 늘 100%는 존재하지 않기에...)
그 당시 우리 서비스에는 react-query가 존재하지 않았다. (도입을 하려했는데 당시 백엔드 개발자 분들이 많이 익숙치 않으실까봐 얘기조차 안꺼내고 있었다.)
그래서 모든 server state의 정보들도 redux에 저장하고 있었는데 이 용량이 어마무시하게 높았던 것이다.
구글링을 하다보니 구글 개발자 콘솔에서 redux를 추출하고 이게 어느정도의 용량인지 측정하는 함수가 있길래 긁어다가 써보니... 메인 페이지 첫 접근만해도 5mb가 넘어가는 기적을 보았다.
무슨 서버 API 1,2개 호출되서 저장되는 건데 도대체 왜 5mb가 넘나 경악스러운 나머지 이 부분은 백엔드 분들께 말씀드려 경량화 작업과 핫픽스를 나갔다. (알고보니 불필요한 정보들도 전부 긁어와서 생긴 문제였다.)
이 계기를 통해 redux로 모든 server-state를 계속 저장하고 있지 말고 react-query로 캐싱 관리를 하기로 마음먹게 된 계기가 되었다.
이건 좀 소름이 돋았던 거다. 특정 컴포넌트에 addEventListener로 스크롤 이벤트에 특정 액션이 걸려있는데 unmount시에 removeEventListener하는 것이 없었다. 이거 공통 컴포넌트인데... 경악스러운 나머지 또 핫픽스를 준비했다.
eventListener가 메모리 누수를 발생시키는 이유는 추후 다뤄보겠다.
우리 어플리케이션에는 지도가 존재하는데 k사 지도를 사용하고 있었다. 거기에 위치를 나타내는 핀이 여러개 찍혀있었는데 너무 많이 찍혀있는 상태에서 뒤로가기를 할 때 그 핀들을 초기화하지 않고 있었다. 메모리 누수 이슈의 큰 이유는 아닐수도 있었으나 이것도 문제라고 판단해 핀 데이터들을 unmount시 초기화 하는 작업을 했다.
이 외의 것들은 추후 시간날 때 더 작성해보겠다.
적으면서 보니 정말 기본도 안된 실수같이 보이기도 한다. (내가 생각해도 그러지 않나...는 생각은 든다)
하지만 이해는 할 수 있다. 우리는 백엔드 개발자들이 프론트도 작업했고, 백엔드를 오랫동안 하신분들이 갑자기 프론트를 하시니 어렵고 이런 부분에서 실수가 나오는 것도 어찌보면 당연한 수순이 아닌가 싶다.
당장 나도 백엔드할 때 모르는게 많았고 오히려 기술적인 부분에서 놓친 게 있을 수도 있으니
현 시점에서는 이런 부분들이 프론트 개발자가 많아지면서 많은 개선이 이루어져서 잘 발생하지 않는 문제이지만 어찌됬든 되게 많은 것을 배우게 된 계기가 되었다.
useCallback과 useMemo는 웬만해서는 쓰지 말자(개인적인 생각)