본문 바로가기

frond-end

가상스크롤 vue-virtual-scroller 활용한 대량 데이터 테이블 성능 최적화

반응형

혹시 지금 만들고 있는 서비스에 데이터가 엄청나게 많아서 화면이 버벅거리거나, 스크롤 할 때마다 렉 걸려서 짜증난 적 있으신가요?

저도 회사의 백오피스 화면인 실시간 주문 관제 시스템에서 예전부터 주문이 폭주하는 날이면 화면이 느려져서 관제팀에서 '이것 좀 어떻게 안 될까요?' 하고 계속 요청하셨던 고질적인 문제였어요. 다들 바쁘고, 백엔드 수정은 또 큰 작업이다 보니 쉽게 손대지 못하고 있었는데, 이번에 제가 프론트엔드 단에서 이 문제를 시원하게 해결하고 관제팀으로부터 '진짜 편해졌어요!', '속 시원하다!'는 칭찬까지 들었답니다! 😊 오늘은 그 해결 과정을 한번 풀어보려고 해요

 

.

.

.

 

이전에 있었던 문제점: 많은 주문 목록이 화면을 버겁게 만들었어요

 

우리 실시간 주문 관리 시스템에서 이런 문제들이 있었습니다:

1. 일단 너무 느려!  (DOM 과부하 & 스크롤 지옥)

  • 수백, 수천 건의 주문 데이터를 한번에 바로 불러오는 구조 -> 한꺼번에 화면에 그리니까 브라우저가 버거워 함 (DOM 렌더링)
  • 평소 주문 이입량은 300~500 건
  • 이번 5/8 어버이날엔 3,000건이 넘는 주문이 한 화면에 다 들어가려고 하니 브라우저가 아예 멈춰버리는 상황까지 발
    • 연말(크리스마스, 방어시즌 ... ), 5월 , 명절 에 주문량이 많음
  • 보이지도 않는 데이터까지 다 메모리에 올려놓고 있으니 리소스가 낭비

실시간으로 주문이 쏟아지는 걸 한눈에 봐야 하는 화면이 있어요. 평소에는 그럭저럭 잘 돌아가는데 ... 문제는 특별한날! 이번 어버이날에 주문량이 평소의 10배 이상 몰려서 화면에 보이지도 않는 저~ 아래 있는 데이터까지 테이블에 다 그려놓고 있으니 얼마나 비효율적이었겠어요. 

2. 스크롤이 너무 느려요

  • DOM 요소가 많아서 스크롤할 때마다 화면이 버벅거렸어요. 
  • 빠르게 스크롤 내리면 아예 프레임이 끊기면서 화면이 뚝뚝 끊기는 현상이 심했어요.
  • 이터가 많을수록 이 문제는 점점 심해져갔어요.

3. 처음 화면 뜨는 시간이 너무 길어요 (긴 초기 로딩 시간)

  • 모든 데이터를 한 번에 렌더링하니 초기 로딩 시간이 길었어요.
  • 문이 많은 날에는 페이지 로딩된 후에도 실제 데이터가 보이기까지 몇 초에서 몇십 초까지 기다려야 했어요. -> 관제 팀이 체감하기엔 5초 정도 걸리는 것 같았다고 해요 

5. 메모리 누수 위험도 있었어요

  • 계속 늘어나는 주문 데이터를 효율적으로 관리하지 못했어요.
  • 라우저가 사용하는 메모리가 계속 증가해서 오래 사용하면 성능이 떨어지고 크래시 위험까지 있었죠.

 

.

.

.

 

 

🤔 "그래서 뭘로 해결했는데?" - Vue Virtual Scroller

 

백엔드 API를 수정해서 페이징 처리를 하는 게 정석일 수도 있지만, 당장 급한 불을 꺼야 했고, 프론트엔드 단에서 빠르게 개선할 수 있는 방법을 찾고 싶었어요.

그래서 여러 가지 방법을 찾아보다가 "가상 스크롤 (Virtual Scrolling)" 이라는 개념을 알게 되었습니다.

composable 함수 기반의 VueUse 의 `useVirtualList` 를 사용해도 되지만, 현재 서비스는 vue 2.7 버전이어서 `vue-virtual-scroller` 라이브러리를  사용했습니다 ! 

 

Vue Virtual Scroller 적용하고 확 달라진 점들

 

[  가상 스크롤 적용 전 ]

 

현재 데이터는 600개 정도 되는데요.
보시는 바와 같이, 수백에서 수천 건에 달하는 모든 주문 데이터를 한번에 DOM(Document Object Model)에 렌더링하고 있습니다. 이로 인해 브라우저는 매우 많은 수의 테이블 행(<tr> 또는 <div>) 요소를 메모리에 로드하고 관리해야 합니다.
결과적으로 스크롤 시 각 행의 위치 계산 및 리페인트(repaint)/리플로우(reflow)에 과도한 연산이 발생하여, 영상에서 확인되듯이 스크롤 지연(lag), 버벅거림(stuttering), 그리고 심한 경우 브라우저 응답 없음 현상까지 나타납니다.
특히 데이터양이 많을수록 심화되어 사용자 경험을 크게 저하시키는 주요 원인이었습니다.

 

 

 

[ 가상 스크롤 적용 후 ]

 

 

`vue-virtual-scroller` 를 적용하여 가상 스크롤 최적화를 구현한 후의 성능을 보여줍니다.
이전과 달리, 현재 화면의 뷰포트(viewport) 내에 실제로 보이는 테이블 행(row)들과 약간의 버퍼(buffer) 영역에 해당하는 행들만 실제 DOM에 렌더링됩니다.
사용자가 스크롤하면 화면 밖으로 벗어나는 행의 DOM 요소는 제거되거나 재활용(recycle)되고, 새로 화면 안으로 들어오는 행에 해당하는 데이터로 내용이 교체되어 DOM에 추가됩니다.
즉, 전체 데이터가 아무리 많더라도 실제 DOM 요소의 수는 화면 크기에 비례하는 일정하고 적은 수(예: 20-50개)로 유지됩니다.
이 메커니즘 덕분에 브라우저의 렌더링 부하가 획기적으로 줄어들어, 영상에서처럼 데이터 양과 관계없이 매우 부드럽고 즉각적인 스크롤 반응성을 확보할 수 있었습니다. 메모리 사용량 또한 크게 감소하여 애플리케이션의 전반적인 안정성이 향상되었습니다.

 

 

 

 

 

Vue Virtual Scroller 적용 방법: 핵심 코드 및 설명

<div class="tbody height">
  <RecycleScroller
    class="scroller"
    :items="filterOrders"  
    :item-size="40"      
    key-field="_id"       
    :buffer="200"         
    v-slot="{ item: result }" 
  >
    <!-- 아래는 각 주문 행(row)을 렌더링하는 실제 컴포넌트/엘리먼트 내용 -->
    <div
      class="trow"
      ...
      }"
    >
      <!-- ... (각 컬럼 tcol 내용 생략) ... -->
    </div>
  </RecycleScroller>
  
  
  <script>
import { RecycleScroller } from 'vue-virtual-scroller';
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';

 

▪︎ `vue-virtual-scroller` 라이브러리에서 제공하는 `<RecycleScroller>` 컴포넌트 입니다. 이 컴포넌트가 가상 스크롤링 로직을 처리합니다.

   ▪︎ 화면 밖으로 나가는 DOM 요소를 버리는 대신 재활용(recycle)하여 성능을 더욱 최적화 하는 버전입니다 (라이브러리에는 DynamicScroller 도 있음.)

 

▪︎  `:item-size="40"`

   ▪︎ 각 항목(여기서는 테이블의 한 행)의 고정 높이(픽셀 단위)를 지정합니다. 가상 스크롤러는 이 값을 사용하여 현재 화면에 몇 개의 항목이 들어갈 수 있는지, 그리고 전체 스크롤 영역의 높이를 계산합니다. 모든 항목의 높이가 동일하고 예측 가능할 때 성능이 가장 좋습니다. (만약, 높이가 가변적이라면 `DynamicScroller` 와 다른 설정을 사용해야 합니다.)

 

▪︎ `:buffer="200":`

   ▪︎.뷰포트(실제 보이는 화면 영역)의 위아래로 추가로 렌더링할 픽셀 크기를 지정합니다. 사용자가 빠르게 스크롤할 때 빈 화면이 잠깐 보이는 현상을 방지하기 위해 미리 약간의 항목을 더 렌더링해두는 역할을 합니다. 값이 클수록 스크롤은 부드러워지지만, 한 번에 렌더링하는 DOM 요소 수가 늘어나므로 적절한 값 설정이 필요합니다.

 

▪︎ `v-slot="{ item: result }" :`

   ▪︎ 슬롯(slot)을 사용하여 `<RecycleScroller>` 가 계산한 현재 화면에 표시해야 할 각 항목 데이터를 받아옵니다.

여기서는 `v-slot`을 통해 전달된 객체에서 item 속성을 result 라는 변수명으로 받았습니다.

슬롯 내부에서는 이 result 변수(개별 주문 데이터)를 사용하여 실제 테이블 행(trow) 과 그 안의 셀(tcol)들을 렌더링합니다. 이 부분이 실제로 화면에 그려지는 부분입니다.

 

▪︎ 스크롤러를 감싸는 부모 요소(.tbody.height, .scroller)에 height 또는 max-height 스타일이 지정되어 있어야 스크롤이 가능합니다.

 

 

`<RecycleScroller>`는 `:items`로 전체 데이터 목록을 받고, `:item-size` 정보를 이용해 현재 스크롤 위치에서 화면에 보여야 할 항목들이 무엇인지 계산합니다. 그리고 `v-slot`을 통해 계산된 항목들만 넘겨주어 실제 DOM 렌더링을 최소화합니다. 스크롤이 발생하면 이 과정을 반복하며 DOM 요소를 효율적으로 업데이트(재활용)하는 것입니다.

 

 

 

 

.

.

.

 

 

결론은!

Vue Virtual Scroller의 도입은 대량의 주문 데이터를 효율적으로 표시해야 하는 실시간 관제 시스템의 핵심적인 성능 병목 현상을 해결하고 사용자 경험을 개선했습니다.

이전에는 주문량이 폭증하는 어버이날, 연말 등 특수 시즌만 되면 시스템이 버벅거려 관제팀의 업무 효율이 떨어지고 스트레스가 극심했지만, 이제는 수천 건의 데이터가 몰아쳐도 빠르게 화면의 데이터를 확인할 수 있게 되었습니다 ! 

사용자 경험 측면에서는 이전의 답답했던 끊김 현상이 사라지고 부드러운 스크롤링과 즉각적인 반응성을 제공하게 되면서 관제팀 분들이 데이터 탐색과 상황 판단에 훨씬 더 집중 할 수 있게 되었습니다. 심지어 개발팀에 "아니, 이렇게 금방 되는 걸 왜 이제서야 해줬어요?" 하시면서 너무 만족해하셨어요. 그만큼 개선 효과가 확실 했다는 거겠죠!? 🤭

 

기술적으로 제일 만족했던 건, 데이터가 늘어날수록 성능이 나락가던 그 연결고리를 끊어냈다는 거예요. 가상 스크롤링을 쓰니까 데이터 양이랑 앱 성능이 따로 놀게 됐고 앞으로 데이터가 더 늘어나도 '아 또 느려지면 어떡하지?' 하는 걱정을 안해도 되는, 미래에도 잘 돌아갈 튼튼한 구조를 만들었다는 확신이 들었습니다. 

 

특히 이번 일로 확실히 느낀 건, 백엔드 API는 하나도 안 건드리고 론트엔드 렌더링 최적화만으로도 사용자 경험 완전히 바꿀 수 있다는 점이었어요. 물론 근본적인 구조 개선도 중요하지만, 때로는 프론트엔드에서 최적화하는 게 제일 빠르고 효과적인 해결책이 될 수 있다는겁니다. 

앞으로도 계속해서 개선점을 찾고 적용을 해보면서 공부해 나가야겠습니다!

728x90
반응형