안녕하세요
[UX] 자바스크립트 메모리 누수를 경계하라 본문
https://ui.toast.com/posts/ko_20210611#3-%EB%B6%84%EB%A6%AC%EB%90%9C-dom-%EB%85%B8%EB%93%9C
페이지 4개를 하나씩 4번 총 16번의 페이지 이동을 한 Heap stack이다.
페이지 이동 시 절벽처럼 뚝 뚝 떨어지는 부분이 컴포넌트가 unmount될 때 heap에서 메모리를 제거하는 것이다.
(Heap 메모리가 제거되는 예시1~4)
이 heap 그래프가 페이지 전환할 때마다 가파른 우상향을 그린다면 이를 해결하여야 한다.
그렇지 않으면 애플리케이션이 느려지다가 어느 순간 멈춰버릴 것이다.
(우상향 예시)
1.나도 모르게 전역 변수나 전역에서 동작하는 함수가 생성되지 않았는지 확인
2.컴포넌트 제외 시 이벤트 제거 (useEffect로 unmount 직전에 return 하도록 한다.)
memory tab을 보면 얼마만큼의 메모리를 확보할 수 있는지 알 수 있다.
detached DOM이란 현재 페이지에는 존재하지 않지만, 계속 참조되고 있는 객체를 의미한다.
따라서 이들이 제거되었을 때, 그만큼의 메모리를 다시 확보할 수 있게 되는 것이다.
Shallow Size는 실제 해당 객체가 보유하고 있는 메모리의 크기이고,
Retained Size는 해당 객체가 제거될 때 확보될 수 있는 메모리의 크기이다.
이렇게 솟은 부분을 드래그하면 다음과 같은 결과를 얻을 수 있다.
(왼쪽: 이전 페이지)
(오른쪽: 현재 페이지)
Shallow Size는 해당 객체가 실제로 보유하고 있는 size라 했는데,
- 왼쪽 막대
Retained Size에 비해 전체적으로 적다.
가비지 컬렉터에 의해서 제거된 부분이 회색 막대이고 나머지 완전히 제거되지 않은 Shallow Size가 파란색 막대 만큼 남아있다.
- 오즉쪽 막대의 경우에는 Shallow Size가 Retained Size과 비교해도 큰 부분을 차지하고 있는 것을 볼 수 있다.
1. 과거의 나 vs 위의 글을 읽고 난 후의 나
"과거의 나"
의 코드 작성 방식은 중에서 바로 문제되는 코드가 떠올랐다.
hook을 이용하지 않는 객체를 component 밖에 두기
const filterData = ["강지", "칸나", "유니", "타비", "시로", "리제", "히나"];
const BarChart = ({ barData, date }) => {
const { data, keys, section, playedGames } = barData;
const copiedData = JSON.parse(JSON.stringify(data));
BarChart라는 컴포넌트 바깥에 filterData를 두었는데,
이는 전역변수로 선언이 되어 컴포넌트가 unmount 되어도 메모리에 남아있게 된다.
const BarChart = ({ barData, date }) => {
const { data, keys, section, playedGames } = barData;
const copiedData = JSON.parse(JSON.stringify(data));
const filterData = ["강지", "칸나", "유니", "타비", "시로", "리제", "히나"];
따라서 이와 같이 unmount된 컴포넌트가 메모리 누수를 발생시키지 않는 방식을 택하기로 했다.
"글을 읽고 난 후의 나"
하지만 그럼에도 이렇게 모든 코드를 컴포넌트 내부에 위치시키게 되면 컴포넌트가 너무 길어져 가독성이 떨어지게 된다.
예를 들어 이와 같은 객체(x30)의 경우에는 컴포넌트 내부에 위치시켰다가 하루종일 마우스 스크롤을 굴려야 한다.
const playlistInfoMap = {
PLfK50SdqLy5W0grEiSx7AzWmEXU1ziecp: {
singerName: "kanna",
regex: /\s*아이리\s*/g,
},
"PL6Ze55OL-JEbYCL6LQE5HmjUupB4V66OD": {
singerName: "yuni",
regex: /\s*아야츠노\s*/g,
},
PLfK50SdqLy5VK0aRt1d5NXPs3BYMHthsf: {
singerName: "mashiro",
regex: /\s*네네코\s*/g,
},
PLV66EiBlCCp1fpHjHJNHChu_p5stcN2Oy: {
singerName: "lize",
regex: /\s*아카네\s*/g,
},
"PLfK50SdqLy5V5-cQEpkUAFtSRldvRKd5F": {
singerName: "hina",
regex: /\s*시라유키\s*/g,
},
PLDZwo_AjTKR7urk5_bq3I0wdA2DkLbbTo: {
singerName: "tabi",
regex: /\s*아라하시\s*/g,
},
};
이런 경우에는 외부에 선언을 하기로 했다.
2. 외부 선언 vs 내부 선언 기준
외부선언을 할 때는
1. 재사용을 위한 모듈화
다른 파일에서도 이용을 하는 객체, 배열이나 함수 등의 경우에는 내부에 선언하면 export 자체를 할 수가 없다.
외부 선언이 강제된다.
2. 가독성 향상
정~~~말 긴 객체가 있다면 이는 파일로 구분하는 것이 낫다.
unmount 됐을 때 메모리... 그거 얼마나 아끼겠다고(아낄 수 있으면 아끼면 좋다) 무리하게 개발 비용을 증가시킬 필요는 없을 것이다.
3. 테스트 용이성
함수를 단위 테스트하려면 꺼내놓아야 한다.
외부 선언이 강제된다.
내부선언을 할 때는
1. 스코프 제한
함수가 컴포넌트 내부에 있으면 해당 컴포넌트 내에서만 사용되며 외부에서는 접근할 수 없다.
unmount 될 때 가비지 콜렉터에서 수거 대상이 된다.
2. 캡슐화
배열, 객체, 함수가 컴포넌트 내부에 있으면 해당 컴포넌트의 로직과 관련된 함수임을 명시적으로 알 수 있다.
참고
https://ui.toast.com/posts/ko_20210611
https://blog.eunsukim.me/posts/debugging-javascript-memory-leak-with-chrome-devtools
진짜 매일 다시 읽어봐도 좋을 글이다.
'유튜브컨텐츠탐색-StelLife' 카테고리의 다른 글
[UX] 동적 width의 image가 로드되기 전과 후 레이아웃 다른 문제 해결 - 스켈레톤 (0) | 2023.08.23 |
---|---|
[UX] Channels 페이지네이션 적용 (0) | 2023.08.16 |
[report:3] Live 상태 업데이트 실패 (0) | 2023.05.28 |
[report:2] 아이폰에서 다르게 보이는 문제 (0) | 2023.03.27 |
[report:1] 특정 채널 제외하기 (0) | 2023.03.27 |