상세 컨텐츠

본문 제목

대용량 처리를 위한 Virtual Scrolling

IT 동향 및 소식

by 신시웨이 | 공식 블로그 2020. 11. 4. 13:56

본문

1990년대의 CS 모델 프로그램들이 2000년대 이후에는 Web 모델 프로그램들로 전환이 많이 이루어지고 있습니다.

이러한 추세에 맞추어 대량의 데이터를 처리하는 알고리즘 및 기법들도 새로운 환경에 변화되고 있습니다. 일반적으로 데이터베이스에 저장된 데이터를 조회하여 사용자 화면에 표출할 때 조회한 데이터를 그리드 페이지에 출력하여 페이징 처리하게 되는데 이때 대량의 데이터를 조회하여 출력할 경우 스크롤 이동 시 딜레이 현상이 발생하게 되는데 이러한 현상을 개선하고 최소화하기 위해 Virtual Scrolling 기법이 많이 활용되고 있습니다.

 

우리가 Web browser로 대량의 데이터를 다룰 때 화면에 표기되는 html element들을 오브젝트 개수만큼 생성하게 되면 클라이언트 자원을 많이 소모하게 되고, 이 때문에 브라우저 화면이 멈추거나 부드러운 조작을 할 수 없게 됩니다.

Virtual Scrolling은 화면의 보이는 영역만 element들을 생성하고 보이지 않는 영역은 빈 공간으로 두어, 스크롤링 시 내부의 연산을 통하여 다음 element를 추가하고, 기존 element를 제거하여 사용자에게 최적의 퍼포먼스를 제공하는 기법입니다.

Virtual Scrolling mechanism ©신시웨이 마케팅 

위의 Virtual Scrolling의 메커니즘을 보면 앞서 말했던 실제 보이는 영역과 가려져 있는 빈 영역이 존재하는 것을 볼 수 있고, 중간에 Padding row라는 영역이 있는 것을 볼 수 있습니다.

 

Padding row는 스크롤링시 다음 보여줄 데이터를 연산하기 위한 과정의 공백을 메우기 위한 여분의 row element로서, 부드러운 화면의 조작을 위해 존재하는 element로 사용자가 실제 스크롤링을 하면 이동한 만큼 다음 보일 row element를 계산하고 새로운 element가 추가되면 기존 있던 element가 제거되어 element의 개수를 유지하게 됩니다. Padding row의 최적화된 개수는 클라이언트마다 다를 수 있습니다.

 

Virtual Scrolling을 구현하기 위한 중요한 요소 중 하나는 row height입니다. 하나의 row element의 높이가 일정하지 않다면 전체 스크롤링 할 수 있는 높이를 구할 수 없게 되고, 전체 높이 값이 일정하지 않다면 스크롤링시 원하는 다음 화면을 기대할 수 없게 됩니다.

 

Virtual Scrolling을 구현 중 빠르게 이동되거나 어떤 지점으로 스크롤링이 바로 돼야 하는 상황을 대비하여 Skip paging이란 기법이 필요한데 Skip paging은 스크롤 중간의 연산 없이 바로 최종 화면의 element만 계산하여 보여주는 기법으로, 이 기법 없이 그리드를 구현하게 된다면 빠른 스크롤링이나 특정 지점 스크롤링시 지연이 발생 될 수 있습니다.

 

결과적으로 이러한 메커니즘들 때문에 사용자는 대량의 데이터를 부드럽게 조작해 볼 수 있으며, 자원 낭비로 인해 화면 멈춤 등의 이슈가 발생하지 않게 됩니다.

// Virtual Scrolling 예제
class PetraGrid {
  constructor(config) {
    // cofing set
    this.config = config;
    this.scrollEle = $('#' + config.scrollEle);
    this.gridEle = $('#' + config.gridEle);
    this.rowHeight = (config.rowHeight) ? config.rowHeight : 30;

    // 변수 초기화
    this.scrollHeight = 0; // 실제 높이
    this.viewHeight = 300; // view 높이
    this.bufferRowCount = 5; // 위 아래로 여분의 로우 갯수(성능 관련)
    this.prevScrollTop = 0; // 이전 스크롤 위치
    this.viewRowCount = 10; // 인덱스 로우 개수
    this.rows = {}; // 보여지는 tr 엘리먼트
    this.data = []; // 실제 데이터 list

    this.initialize();
  }

  initialize() {
    this.initViewConfig();
    this.initScrollConfig();

    this.scrollEle.scroll((e) => this.onScroll(e));
  }

  initViewConfig() {
    this.viewHeight = this.config.viewHeight;
    this.scrollEle
      .css({
        height: this.viewHeight + "px",
        width: (this.config.width) ? this.config.width + "px" : "100%"
      })

    this.viewRowCount = Math.ceil(this.viewHeight / this.config.rowHeight);
  }

  initScrollConfig() {
    let totalHeight = 0;
    totalHeight = this.config.rowHeight * this.data.length;

    this.gridEle
      .attr("tabindex", 0)
      .css({
        width: this.totalWidth + "px",
        height: totalHeight + "px"
      });

    this.scrollHeight = totalHeight;

  }

  onScroll(e) {
    var scrollTop = this.scrollEle.scrollTop();

    if (Math.abs(scrollTop - this.prevScrollTop) > this.viewHeight) {
      this.skipScroll(scrollTop);
    } else {
      this.stepScroll(scrollTop);
    }

    this.drawGrid(scrollTop);
  }

  stepScroll(scrollTop) {

    this.prevScrollTop = scrollTop;

  }

  skipScroll(scrollTop) {

    this.prevScrollTop = scrollTop;

    this.removeAllRows();
  }

  removeAllRows() {
    for (var i in this.rows) {
      this.rows[i].remove();
      delete this.rows[i];
    }
  }

  drawGrid(scrollTop) {
    var y = scrollTop;
    var buffer = this.rowHeight * this.bufferRowCount;
    var top = Math.max(0, Math.floor((y - buffer) / this.rowHeight));
    var bottom = Math.min(this.scrollHeight / this.rowHeight, Math.ceil((y + this.viewHeight + buffer) / this.rowHeight));


    for (var i in this.rows) {
      if (i < top || i > bottom) {
        this.rows[i].remove();
        delete this.rows[i];
      }
    }

    for (var i = top; i < bottom; i++) {
      if (!this.rows[i]) {
        this.rows[i] = this.renderRow(i);
      }
    }
  }

  renderRow(rowIdx) {
    var tr = $("<tr><td>" + rowIdx + "<td></tr>")
      .css({
        top: rowIdx * this.rowHeight,
        height: this.rowHeight + "px",
        "line-height": this.rowHeight + "px"
      })
      .appendTo(this.gridEle);
    return tr;
  }

  setData(data) {
    this.data = data;
    this.refresh();
    return this;
  }

  refresh() {
    console.log("### refresh");
    this.removeAllRows();
    this.initScrollConfig();
    this.scrollEle.trigger("scroll");
    this.scrollEle.scrollTop(0);
  }
}

//50만개의 연속된 숫자 배열 초기화
var data = [...Array(500000)].map((a, b) => b)

let grid = new PetraGrid({
  scrollEle: 'scrollEle',
  gridEle: 'gridEle',
  rowHeight: 30,
  viewHeight: 150
});

grid.setData(data);

 

 

현재 새롭게 출시를 준비 하는 Petra DataStudio(가칭)은 사용자 및 데이터베이스 정보를 중앙에서 집중적으로 관리하여 기업 내 DB 보안 및 작업의 편의성을 제공해주는 Web bases SQL Tool Virtual Scrolling을 적용하여 빠른 렌더링 속도를 자랑하며 작성 또는 사용된 SQL을 조직 사용자 간 공유할 수 있는 특징을 가지고 있습니다. 또한, 다양한 형태의 데이터 출력 리포트를 제공하며 데이터 암호화 지원으로 데이터에 대한 보안성을 강화시켰습니다.

 

Petra DataStudio는 현재 알파 버전에 대한 테스트를 진행하고 있으며, 고객 의견 수렴을 위해 On-Line Open Beta Service를 진행할 예정입니다.

 

글. 데이터스튜디오팀 | 원충의

편집. 프리세일즈·마케팅팀 | 박병민

 

'IT 동향 및 소식' 카테고리의 다른 글

Java Object to CSV  (0) 2020.11.26
새로운 웹 기능 WebAssembly  (0) 2020.11.05
대용량 처리를 위한 Virtual Scrolling  (0) 2020.11.04
REST 살펴보기  (0) 2020.10.07
쿠버네티스 기본 개념  (0) 2020.09.04
CORS (Cross-Origin Resource Sharing) 이슈  (0) 2020.08.31

관련글 더보기

댓글 영역