현재 상황

캔버스 데이터 저장 형식을 .svg에서 .json으로 변경하면서1 캔버스 렌더링 속도가 눈에 띄게 느려지는 문제가 발생했습니다. 제가 주로 담당하던 캔버스 저장/로딩 부분에서 조금이라도 속도를 올리고자 했습니다.

로딩 속도를 올리기 위해 떠올린 것은 아주 유명하고도 대표적인 방법인 ‘캐싱’이었습니다. 그래서 저는 차후 폴리싱 단계의 과업으로 미뤄두었던 IndexedDB를 사용하기로 했습니다.

기존 방식: Network + 브라우저 캐시

 기존의 방식은 서버 요청을 통해 데이터를 직접 받아오거나, 브라우저에서 캐시해 둔 데이터를 받아오는 방식입니다. 브라우저는 방문했던 웹 페이지의 이미지, 스크립트 등 리소스를 로컬 컴퓨터에 임시 저장(캐시)하고, 다시 방문할 때 서버에서 모두 다운로드하는 대신 이 캐시를 사용합니다.

수정 방식 1: + Indexed DB

웹 브라우저 저장소 기술 중 하나이며, 비슷한 기술로 localStorage 같은 것이 있습니다. localStorage 대신에 IndexedDB를 사용한 이유는 아주 많습니다.

LocalStorageIndexedDB
용량5-10MB<디스크의 50~80%
(브라우저별 상이)
처리 방식동기 + 메인 스레드비동기 + 별도 스레드
트랜잭션XO
사용쉬움복잡함

더 다양한 특징이 있겠지만, 주요 선택 이유는 위의 내용들입니다. 사용 난이도가 복잡한 것을 제외하고는 무조건 IndexedDB를 써야만하는 스펙이었어요.

가장 큰 문제는 용량입니다. 변환을 완료한 캔버스는 기본 설정상으로 약 1000개의 도트 객체를 가지게 됩니다. 그보다 더 많아질 수도 있고요. 1개의 도트 객체 데이터가 약 1KB의 용량을 차지합니다.

처리방식도 경우 동기 방식이면 큰일이 납니다. 자동저장을 구현하게 될 텐데, 메인 스레드에서 동기 방식으로 처리되면 자동 저장 시마다 캔버스 편집이 먹통이 될테니까요. 한 캔버스를 저장하는데 1MB의 용량이 든다면 localStorage에는 많아봤자 10개의 씬밖에 저장하지 못할 겁니다.

일반적으로 대용량 데이터, 문자열을 제외한 다양한 타입의 데이터를 저장할 때는 IndexedDB를 사용하는 것이 일반적이라는데 위의 특징들 때문인 듯 싶습니다.2

결과

IndexedDB를 사용했을 때, 캔버스 데이터 로딩 속도는 약 90% 단축 됩니다.

테스트 환경은 다음과 같습니다.

  • 컴퓨터: 제 노트북
  • 캔버스 객체 수: 5000개의 Circle 객체

결과는 다음과 같습니다

  • IndexedDB: 21.5-23.7ms (90% 향상)
  • Network: 153.6-362.3ms (기준)

수정 방식 2: + Memory Cache

이래도 사용자 불편감이 심해서 LRU방식의 메모리 캐시를 추가했습니다.

결과

IndexedDB에 캐시를 추가했을 때, 캔버스 데이터 로딩 속도는 전체적으로 약 99% 단축됩니다.

테스트 환경은 다음과 같습니다.

  • 컴퓨터: 제 노트북
  • 캔버스 객체 수: 5000개의 Circle 객체

결과는 다음과 같습니다

  • Memory Cache: 0.2-0.3ms (99.9% 향상)
  • IndexedDB: 21.5-23.7ms (90% 향상)
  • Network: 153.6-362.3ms (기준)

회고

로딩 속도는 많이 개선을 했지만, 그것이 엄청 유의미한 영향을 미치지는 못했습니다. 나중에서야 알게 되었지만, 데이터 로딩보다는 프론트 단에서 객체들을 캔버스에 띄우는 작업이 유의미하게 오래 걸리고 있었습니다.
이 경험을 통해 문제의 원인을 제대로 파악하는 것 문제 해결에 얼마나 중요한 지를 알게되었습니다.

Footnotes

  1. 레이어와 같은 캔버스 고급 기능을 추가하면서, 더이상 svg에는 캔버스 데이터를 다 저장할 수 없게 되었습니다. 이렇게 되면 다음 편집 때, 이로 인해 json으로 저장형식을 변경하게 되었습니다. ↩

  2. localStorage는 문자열만 가능하지만, IndexedDB는 모든 javascript 객체가 가능합니다. localStorage의 경우 모든 것을 문자열로 저장하기 때문에 JSON 직렬화가 필수적으로 진행되고, 이로 인해 데이터 손실이 가능하나, IndexedDB는 JavaScript 객체를 원본 그대로 저장할 수 있습니다. ↩