React Flow로 대규모 데이터 트리뷰 구현 및 최적화하기
위계 데이터를 트리 테이블로 표현하고 있지만 전체 위계를 한눈에 펼쳐보고 싶다는 요구사항이 있었고, 이를 해결하기 위해 트리뷰 형태로 프로토타입을 만들었습니다.
react-flow
라이브러리를 사용하여 수만개의 데이터를 트리뷰로 표현하고 그 과정에서 마주한 성능 문제를 해결한 경험을 정리합니다.
특히 3만개 이상 대량 위계 데이터를 다룰 때 성능 문제가 없는지 확인하는 것이 중요했습니다.
어떻게 만들었나?
처음에는 konva
나 d3
를 고려했지만, 코드 관리의 용이성을 생각해 추상화가 잘 되어있는 react-flow
를 선택했습니다.
react-flow
는 노드와 엣지 개념이 있는 모든 종류의 다이어그램을 그리는 데 사용할 수 있는 강력한 라이브러리입니다.
작업 흐름은 다음과 같았습니다.
- 백엔드에서 받은 nested 형태의 위계 데이터에서
react-flow
가 요구하는 노드와 엣지 정보를 추출합니다. - 각 노드를 적절한 위치에 표시하기 위해 x, y 좌표를 계산합니다. 처음에는 공식 문서에서 권장하는
d3-hierarchy
나darge
라이브러리를 사용했지만, 3만 개 노드의 좌표를 계산하는 데 1초 이상이 소요되어 성능 문제가 있었습니다.
어떤 부분이 개선이 필요햇나?
초기 구현 버전은 3만개 데이터를 렌더링하는 데 심각한 성능 문제를 보였습니다. 가장 큰 병목 지점은 좌표 계산과 렌더링이었습니다.
d3-hierarchy
라이브러리 대신, 더 간단하고 빠른 방법으로 직접 좌표를 계산하기로 했습니다.
- Post-order 순회: 가장 아래 자식 노드부터 시작하여, 각 노드의 너비(자식 노드들의 너비 합)를 계산하며 전체 높이 너비를 구합니다.
- Pre-order 순회: 최상위 부모 노드부터 시작하여, 1번에서 계산한 너비 비율에 따라 자식 노드들의 x, y 좌표를 순차적으로 계산합니다. 부모 노드 너비에 대해 자식 노드들의 너비 비율에 해당하는 x 좌표를 계산하고 y 좌표는 depth height를 곱하여 계산합니다.
위 방식으로 3만개 데이터 좌표 계산 시간을 100ms 이내로 단축할 수 있었습니다.
렌더링 최적화 방향
react-flow
는 onlyRenderVisibleElements
라는 렌더링 최적화 옵션을 제공하지만, 3만개 노드와 엣지 데이터를 한 번에 전달하면 초기 로딩에 5초 이상이 걸리는 문제가 있었습니다.
그래서 현재 화면에 보이는(viewport) 노드와 엣지만
react-flow
에 전달하는 방식으로 변경했습니다.
- 전체 데이터 트리를 표현하는 데 필요한 총 영역의 크기를 계산합니다.
react-flow
의onMove
이벤트에서 현재 화면의 확대/축소 및 이동 정보(transform)를 얻습니다.- 1번과 2번 정보를 조합하여 현재 화면에 보여야 할 노드의 범위를 계산합니다.
- svgRef.current.clientHeight, clientWidth 를 활용합니다
- 화면 위치 이동 정보를 사용하여 현재 화면이 전체 영역에 몇번째 페이지 인지 확인합니다
- 계산된 범위에 해당하는 노드와 엣지만
react-flow
컴포넌트에 전달합니다.
이러한 최적화를 통해 3만개의 데이터도 부드럽게 탐색할 수 있는 트리뷰 프로토타입을 완성하였습니다.