위계 데이터를 트리 테이블로 표현하고 있지만 전체 위계를 한눈에 펼쳐보고 싶다는 요구사항이 있었고, 이를 해결하기 위해 트리뷰 형태로 프로토타입을 만들었습니다. react-flow 라이브러리를 사용하여 수만개의 데이터를 트리뷰로 표현하고 그 과정에서 마주한 성능 문제를 해결한 경험을 정리합니다. 특히 3만개 이상 대량 위계 데이터를 다룰 때 성능 문제가 없는지 확인하는 것이 중요했습니다.

어떻게 만들었나?

처음에는 konvad3를 고려했지만, 코드 관리의 용이성을 생각해 추상화가 잘 되어있는 react-flow를 선택했습니다. react-flow는 노드와 엣지 개념이 있는 모든 종류의 다이어그램을 그리는 데 사용할 수 있는 강력한 라이브러리입니다.

작업 흐름은 다음과 같았습니다.

  1. 백엔드에서 받은 nested 형태의 위계 데이터에서 react-flow가 요구하는 노드와 엣지 정보를 추출합니다.
  2. 각 노드를 적절한 위치에 표시하기 위해 x, y 좌표를 계산합니다. 처음에는 공식 문서에서 권장하는 d3-hierarchydarge 라이브러리를 사용했지만, 3만 개 노드의 좌표를 계산하는 데 1초 이상이 소요되어 성능 문제가 있었습니다.

어떤 부분이 개선이 필요햇나?

초기 구현 버전은 3만개 데이터를 렌더링하는 데 심각한 성능 문제를 보였습니다. 가장 큰 병목 지점은 좌표 계산렌더링이었습니다. d3-hierarchy 라이브러리 대신, 더 간단하고 빠른 방법으로 직접 좌표를 계산하기로 했습니다.

  1. Post-order 순회: 가장 아래 자식 노드부터 시작하여, 각 노드의 너비(자식 노드들의 너비 합)를 계산하며 전체 높이 너비를 구합니다.

  1. Pre-order 순회: 최상위 부모 노드부터 시작하여, 1번에서 계산한 너비 비율에 따라 자식 노드들의 x, y 좌표를 순차적으로 계산합니다. 부모 노드 너비에 대해 자식 노드들의 너비 비율에 해당하는 x 좌표를 계산하고 y 좌표는 depth height를 곱하여 계산합니다.

위 방식으로 3만개 데이터 좌표 계산 시간을 100ms 이내로 단축할 수 있었습니다.

렌더링 최적화 방향

react-flowonlyRenderVisibleElements라는 렌더링 최적화 옵션을 제공하지만, 3만개 노드와 엣지 데이터를 한 번에 전달하면 초기 로딩에 5초 이상이 걸리는 문제가 있었습니다.

그래서 현재 화면에 보이는(viewport) 노드와 엣지만 react-flow에 전달하는 방식으로 변경했습니다.

  1. 전체 데이터 트리를 표현하는 데 필요한 총 영역의 크기를 계산합니다.
  2. react-flowonMove 이벤트에서 현재 화면의 확대/축소 및 이동 정보(transform)를 얻습니다.
  3. 1번과 2번 정보를 조합하여 현재 화면에 보여야 할 노드의 범위를 계산합니다.
    • svgRef.current.clientHeight, clientWidth 를 활용합니다
    • 화면 위치 이동 정보를 사용하여 현재 화면이 전체 영역에 몇번째 페이지 인지 확인합니다
  4. 계산된 범위에 해당하는 노드와 엣지만 react-flow 컴포넌트에 전달합니다.

이러한 최적화를 통해 3만개의 데이터도 부드럽게 탐색할 수 있는 트리뷰 프로토타입을 완성하였습니다.