캐러셀 로딩 시 프론트엔드 메모리 사용량 30배 개선

2020년 2분기, 저희는 체험 페이지에 대한 기술적 리팩토링을 진행하여 React로 재작성하고 REST API를 활용했습니다. 하지만 구현 과정에서 흥미로운 문제를 발견했습니다.
체험 페이지에는 약 20개의 캐러셀이 있으며, 각 캐러셀에는 최소 60개의 타일이 있습니다. 따라서 이 페이지는 제목, 사용자 평점, 동시 접속자 수, 썸네일 등 관련 정보를 포함해 1,200개 이상의 체험을 렌더링하게 됩니다. 각 캐러셀에서 오른쪽 스크롤 버튼을 클릭하면, 더 많은 체험이 있을 경우 다음 배치의 체험을 로드하기 위한 요청이 전송됩니다. 그러면 요청으로 인해 반환된 새로운 데이터가 캐러셀에 추가되는데, 이는 DOM에 더 많은 노드가 추가되고 브라우저에 더 많은 썸네일이 렌더링된다는 것을 의미합니다. 사용자가 스크롤하여 더 많은 데이터를 요청할수록 브라우저 내 메모리 사용량이 증가하게 되며, 이에 따라 사용자 경험(UX)이 느려지고 지연 현상이 발생하기 시작합니다.

메모리 프로파일
Chrome 개발자 도구를 사용하여 이 문제를 프로파일링할 수 있습니다. 첫 번째 스냅샷은 페이지가 처음 로드될 때 캡처되었으며, 두 번째 스냅샷은 사용자가 한 캐러셀에서 다음 스크롤 버튼을 12번 클릭한 후에 캡처되었습니다. 100개 이상의 문자열 노드와 썸네일 렌더링 증가로 인해 메모리 사용량이 약 9.7MB 증가한 것을 확인할 수 있습니다.

해결 방안
캐러셀이 스크롤될 때 사용자는 캐러셀 창에 표시된 항목만 볼 수 있습니다. 예를 들어, 1920×1080 해상도의 노트북에서 전체 화면 브라우저를 사용할 경우, 한 번에 약 9개 정도의 항목만 표시됩니다. 따라서 보이지 않는 노드와 썸네일을 모두 렌더링하거나, 요청을 통해 반환된 더 많은 데이터를 DOM으로 가져올 필요가 없습니다.
따라서 다음과 같은 방식을 제안합니다. API 요청을 통해 원시 데이터를 처음 가져온 후, DOM에 렌더링할 준비를 위해 동일한 길이의 배열을 생성합니다. 이 렌더링 배열 내에는 화면을 채우고 다음 항목으로 스크롤하는 데 필요한 데이터만 채웁니다. 배열의 나머지 부분은 화면이 해당 위치 근처로 스크롤되었을 때만 채워집니다. 렌더링 데이터의 범위를 기록하기 위해 두 개의 인덱스, 즉 renderingStartIndex와 renderingEndIndex를 정의해 봅시다. 목록에서 어떤 시작 위치가 보이는지 알려주기 위해 커서를 설정합니다. 스크롤 시에는 커서를 지속적으로 첫 번째 보이는 항목의 인덱스로 업데이트해야 합니다. 스크롤 애니메이션이 끝나기 전에, 다음 커서가 렌더링 데이터 배열 내에 있는지 확인해야 합니다. 캐러셀 창 크기와 카드 크기에 따라 디스플레이 창에 몇 개의 항목이 표시될지 쉽게 파악할 수 있습니다. 그런 다음 현재 창과 다음 창의 항목이 렌더링 데이터 배열의 범위 내에 있는지 확인해야 합니다. 그렇지 않은 경우, 원시 데이터에서 항목을 가져와 빈 목록을 채우는 동시에 커서 위치 이전의 데이터를 지웁니다. 이는 사용자가 오른쪽이나 왼쪽으로 스크롤할 때 적용됩니다. 사용자가 오른쪽으로 스크롤할 때는 백그라운드에서 더 많은 데이터를 가져와, 새로운 데이터를 수신할 때마다 원시 데이터로 채워 넣습니다. 단, 원시 데이터는 페이지 렌더링에 반영되지 않습니다.


메모리 프로파일
Chrome 개발자 도구를 다시 살펴보겠습니다. 이전과 마찬가지로 첫 번째 스냅샷은 초기화 시점에, 두 번째 스냅샷은 오른쪽으로 12번 스크롤한 후에 캡처되었습니다. 이제 100개 이상의 항목을 로드해도 메모리 사용량이 0.3MB만 증가하는 반면, 이전에는 9.7MB가 증가했습니다.

다음 단계는?
대용량 데이터 창을 처리할 수 있도록 캐러셀 컴포넌트를 개선하는 것 외에도, 기능이나 페이지를 리팩토링할 때는 성능 정보를 파악하기 위해 벤치마크를 더 추가해야 합니다.
현대 웹 애플리케이션 스택이 프론트엔드 중심으로 이동하고 있는 만큼, 프론트엔드 텔레메트리 데이터를 수집하고 개선하는 것은 기존의 백엔드 텔레메트리만큼이나 중요합니다.
Ying Jiang은 Roblox의 수석 프론트엔드 소프트웨어 엔지니어입니다. Roblox의 첫 번째 프론트엔드 엔지니어로서, 그녀는 Roblox에 최신 프론트엔드 기술 스택을 도입하고 개발 및 배포 파이프라인을 개선하는 업무를 담당하고 있습니다. 또한 채팅, 알림 스트림, 음성 등 실시간 기능 개발에도 참여해 왔습니다.
로블록스(Roblox Corporation)나 이 블로그는 어떠한 기업이나 서비스도 추천하거나 지지하지 않습니다. 또한, 이 블로그에 포함된 정보의 정확성, 신뢰성 또는 완전성에 대해 어떠한 보증이나 약속도 하지 않습니다.


