피그마 내부 이야기: 엄 (새 탭에서 열림)
Figma는 복잡한 협업 환경에서 수많은 댓글을 60fps의 부드러운 속도로 렌더링하기 위해 React의 기본 성능 한계를 극복한 과정을 공유합니다. 단순히 컴포넌트를 최적화하는 수준을 넘어, 브라우저의 레이아웃 계산 방식을 이해하고 불필요한 리렌더링을 원천 차단하는 아키텍처적 변화를 시도했습니다. 그 결과, 수천 개의 댓글이 있는 파일에서도 끊김 없는 사용자 경험을 제공할 수 있게 되었습니다.
대규모 댓글 목록의 성능 병목 현상
- 수천 개의 댓글이 존재할 때 React가 모든 요소를 DOM에 유지하면 메모리 사용량과 렌더링 시간이 기하급수적으로 증가하는 문제가 발생했습니다.
- 사용자가 스크롤할 때마다 발생하는 상태 업데이트가 전체 컴포넌트 트리를 재평가하게 만들어, 브라우저가 초당 60프레임을 유지하지 못하고 화면이 버벅이는 현상이 나타났습니다.
- 특히 각 댓글의 내용에 따라 높이가 가변적인 특성 때문에, 브라우저가 레이아웃을 다시 계산(Reflow)하는 과정에서 막대한 CPU 자원을 소모했습니다.
가상 리스트(Windowing)와 동적 높이 관리
- 화면에 현재 보이는 부분만 렌더링하는 가상화(Windowing) 기법을 적용하여 실제 DOM 노드 수를 수천 개에서 수십 개 수준으로 압축했습니다.
- 댓글마다 높이가 다른 문제를 해결하기 위해, 렌더링 전에 각 요소의 높이를 측정하고 이를 캐싱하는 메커니즘을 구현하여 스크롤 위치를 정확하게 계산했습니다.
- 사용자가 빠르게 스크롤할 때 빈 화면이 보이지 않도록 '오버스캔(Overscan)' 영역을 설정하여 위아래로 여분의 컴포넌트를 미리 렌더링했습니다.
React 상태 관리의 탈중앙화와 구독 모델
- React의 전형적인 단방향 데이터 흐름은 상위 컴포넌트의 상태 변경 시 하위 트리 전체를 리렌더링하므로, 대규모 목록에서는 부적합하다고 판단했습니다.
- 이를 해결하기 위해 각 댓글 컴포넌트가 중앙 스토어를 직접 구독(Subscription)하게 하여, 특정 댓글이 수정될 때 해당 컴포넌트만 정밀하게 업데이트되도록 설계했습니다.
- 이러한 '밀어내기(Push)' 방식의 업데이트를 통해 불필요한 VDOM 비교(Reconciliation) 과정을 생략하고 CPU 부하를 획기적으로 줄였습니다.
브라우저 렌더링 엔진 최적화
- CSS의
contain속성(예:contain: layout)을 활용하여 특정 댓글의 변화가 전체 페이지의 레이아웃에 영향을 주지 않도록 브라우저에게 명시적인 힌트를 제공했습니다. requestIdleCallbackAPI를 도입하여 사용자 상호작용에 즉각 필요하지 않은 비핵심 작업들은 브라우저의 유휴 시간에 처리되도록 스케줄링했습니다.- 마우스 오버 효과와 같은 고빈도 인터랙션은 React 상태를 거치지 않고 CSS 클래스 조작이나 직접적인 DOM 접근을 통해 처리하여 즉각적인 반응성을 확보했습니다.
대규모 웹 애플리케이션에서 극도로 매끄러운 성능을 달성하려면 React의 추상화 계층에만 의존하지 말고 브라우저의 실제 렌더링 메커니즘을 깊게 제어해야 합니다. 초기 개발 단계에서는 생산성을 위해 표준 React 패턴을 따르되, 성능 임계점에 도달한 복잡한 UI에서는 가상화, 상태 구독 모델, 레이아웃 격리 등의 로우레벨 최적화 기법을 도입하는 것을 권장합니다.