pile·
프론트엔드·뷰노뷰노·

Spatial index를 이용한 웹 어플리케이션 최적화

뷰노 SW개발팀이 병리 슬라이드 뷰어를 만들며 만난 렌더링 성능 문제와 해결 과정을 정리한 글이다. 병리 슬라이드는 스캐너로 디지털화하면 한 장이 수십억 픽셀(56064 * 80128, 100MB~10GB)에 이르고, 그 위에 딥러닝 모델이 찾아낸 Gland(분비선) 수백 개를 겹쳐 그려야 한다. 15,000개 점으로 이뤄진 400개 Gland를 HTML5 Canvas에 그리자 초기 로딩이 쓸 수 없는 수준으로 느려졌다. 네 가지 접근을 검토한 뒤 2차원 공간 인덱스를 도입해 Gland 62%, 점 78%를 줄였다.

핵심 포인트
  • 병리 뷰어 요구사항은 고해상도 슬라이드와 Gland 시각화, 확대·축소·이동, Gland 추가·수정·삭제 세 가지다
  • Gland를 이미지로 압축해 그리는 방법은 원본 좌표가 사라져 편집 요구사항과 충돌하므로 폐기했다
  • 보이는 영역만 그리기, 한 픽셀에 겹치는 점 제거, 확대 레벨별 그룹화 세 가지를 함께 적용했다
  • range query와 kNN query를 빈번히 호출해야 해서 R-tree 계열 spatial index를 미리 빌드했다
  • 그룹화는 넓이 기준 최소 힙에서 작은 Gland부터 꺼내 이웃 K개와 convex hull로 합치는 방식이다
  • 결과적으로 Gland 400개가 154개로, 점 15,917개가 3,428개로 줄고 호출당 70ms 미만이 됐다
상세 정리
  • 데이터 규모: 슬라이드 한 장이 마이크로미터 해상도로 스캔돼 수십억 픽셀이 되고 파일 크기는 100MB에서 10GB에 이른다
  • 렌더링 스택: UI는 React, 고해상도 이미지 표시는 OpenSeadragon을 쓰고 원본을 통째로 올리는 대신 확대 레벨과 viewport에 맞는 타일을 서버에서 받는다
  • 병목 위치: 이미지 자체가 아니라 AI가 찾은 Gland를 Canvas 위에 그리는 부분이 느렸다
  • 접근 1 폐기: heatmap처럼 이미지 포맷으로 압축하면 기존 Gland 정보가 날아가 사용자가 추가·수정·삭제를 할 수 없다
  • 접근 2: 현재 viewport에 들어오는 Gland만 그린다. 다만 축소 상태에서는 전부 보이므로 단독으로는 부족하다
  • 접근 3: Gland 좌표가 원본 이미지 좌표계를 따라 Canvas 한 픽셀에 여러 점이 겹친다. 중복 점을 제거해 15,917개를 3,428개로 78% 줄였다
  • 접근 4: 편집 가능한 확대 레벨 전까지는 이곳에 Gland가 있다는 것만 알면 되므로 인접 Gland를 묶어 400개를 154개로 62% 줄였다
  • 인덱스 선택 이유: 이진 트리·레드블랙 트리는 1차원 데이터용이라 x, y 2차원 조회에는 R-tree, Kd-tree, MVP-tree 같은 spatial index가 필요하다
  • 쿼리 두 종류: range query는 특정 범위 안의 데이터를, kNN query는 가장 가까운 k개를 찾는다. 접근 2는 range, 접근 4는 kNN에 대응한다
  • 알고리즘 흐름: getGlands는 range query로 화면 범위를 뽑고 확대 레벨로 점을 정리한 뒤 그룹화를 거치며, 확대·축소·이동 이벤트마다 호출된다
  • 그룹화 상세: 넓이가 작은 것이 우선인 힙에 Gland를 넣고 임계 넓이 80을 넘으면 결과에 담고, 아니면 kNN으로 이웃 5개를 찾아 convex hull로 합쳐 다시 힙에 넣는다
  • 반복 효과: 작은 Gland가 몰린 구역은 병합이 거듭되며 하나의 큰 덩어리로 수렴한다
  • 성능 결과: 매 이벤트마다 다시 계산하는데도 함수 실행이 70ms 미만이라 사용 불가였던 기존 방식과 달리 쓸 만한 수준이 됐다
  • 확장성 전망: 슬라이드가 클수록 줄어드는 비율이 커져 효율 이득도 함께 커진다고 본다
왜 읽나캔버스에 수만 개 도형을 그려야 하는 상황을 자료구조 선택으로 푸는 절차를 수치와 함께 따라갈 수 있다.
뷰노
뷰노 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    항공 프론트엔드 구축기 (7/10): 창구를 하나만 두었습니다

    여기어때 항공 서비스 프론트엔드가 웹과 앱 웹뷰 두 환경에서 동일한 함수 호출로 동작하는 앱 브릿지 추상화 레이어를 설계한 과정을 다룬다. iOS·안드로이드 규약 차이와 "웹에 존재하지 않는 브릿지를 어떻게 호출하나"라는 문제를 단일 추상화 층으로 해결한 구현 사례다.

    요약 이어보기
    #react#typescript#webview+2