Indexed DB 적용기




안녕하세요. 드림어스컴퍼니 FE개발팀에서 FLO 웹 서비스 개발을 담당하고 있는 Ralph입니다.

이번 글에서는 FLO 웹 서비스에 새롭게 적용된 Indexed DB에 대한 소개와 적용 과정 및 결과를 공유해 보려고 합니다.




문제의 시작

사건 현장 캡쳐 이미지

화창한 어느 날, FLO 웹 서비스 상에서 지금껏 본 적 없는 에러와 마주하게 됩니다.

본 적 없는 에러이다 보니 자연스레 구글링 하였고, 그 결과는



“브라우저 스토리지 용량 초과"



디버깅 해본 결과, 재생 목록에 곡을 담는데 Local Storage 의 용량이 초과하여 데이터를 추가하는데

실패하고 있었습니다. 직감적으로 이건 작은 일이 아닌 큰 일이라는 걸 느꼈고, 별도의 검토 일정을 잡고 소스 코드를 분석해보기로 결정하였습니다.




Legacy 살펴보기


FLO 웹 서비스에서는 다양한 데이터들을 Local Storage 로 저장 관리하고 있었습니다.

그 중 가장 사이즈가 크고 중요도가 높은 사용자 플레이 리스트는 아래와 같이 관리되고 있었습니다.


브라우저 내 Local Storage 모습

그림과 같이 Local Storage 에서 사용자 플레이 리스트는 사용자 캐릭터 번호 별 객체 배열 형태를 띄고 있으며, 서버로부터 받은 데이터 배열을 stringify 하여 Key-Value 형태로 저장 관리하고 있습니다.


재생 목록의 최대 길이는 3,000곡으로 제한하고 있으며, 이는 캐릭터 별 3,000 곡을 의미하는 것으로

클라이언트는 캐릭터 3개에 해당하는 총 9,000곡에 대한 정보를 저장 관리하게 됩니다.

이와 같은 Local Storage 기반의 단순한 구조로 대량의 데이터를 저장 관리하는 기존 코드로부터 개선 및 고려해야할 사항들을 아래와 같이 정리하였습니다.


  • 증가하는 개인화 데이터를 위한 저장소 크기 확장

  • 캐릭터 별 서비스 시나리오를 위한 캐릭터 별 저장소 구축

  • Local Storage의 Syncronous API 성능 지연 개선

  • 데이터 get/set 시 String 컨버팅 로직 제거

  • 기존의 Key-Value 기반 데이터 로직과의 연동 편의




Indexed DB 살펴보기


인덱스가 포함된 JSON 객체를 위한 Transactional Local Database 이자 , Web Storage 의 한계를

극복하고 대량의 데이터 처리 및 Progressive Web App 을 위해 고안된 브라우저 표준 인터페이스 중

하나인 Indexed DB 의 주요 특징은 아래와 같습니다 .

  • Large Scale NoSQL Storage System 으로서 기본 데이터 쿼리와 트랜잭션 지원

  • MB 단위부터 GB 까지 디바이스의 스토리지 크기에 따라 대용 량 저장 관리

  • same origin policy 를 따르며 , 온라인 오프라인 환경 모두에서 쿼리 지원

  • Transaction Model 을 사용하여 내부 데이터 변경 연산은 트랜잭션을 통해서 일어남

  • Asyncronous API 제공하여 DB 내부 명령 처리에 대한 지연 없음

  • Index Table System 지향하며 , Index 설정으로 효율적인 데이터 검색 지원

  • 하나 이상의 DB 와 테이블을 가질 수 있음

  • Key Value 한 쌍을 저장하며 , File/Blob 포함 다양한 자바스크립트 데이터 타입 지원


실제 구현에 앞서, Web Storage 에 익숙한 상황이라면 반드시 알고 넘어가야할 Indexed DB 의

주요 요소와 개념은 아래와 같습니다.




| Database


Version/Name 을 가지며 하나 이상의 Object Store 로 구성되는 저장소로서 , 브라우저는 하나 이상의 Database 를 가질 수 있습니다.




| Object Store


실제 데이터를 담는 공간으로서, N개의 Key-Value 레코드를 영구적으로 저장함 키(key)에 따라 오름차순으로 정렬됩니다. Object Store의 이름은 고유해야하며, 선택적으로 key generator와 key path를 가질 수 있는데, key path를 설정하면 in-line key (내부 key) 를 사용해야하고 아닐 경우 out-of-line key (외부 key) 를 사용해야 합니다.




| Transaction


특정 Database 에 대한 data-access 와 data-modification 등 Data 상호작용을 의미하며, 모든 Data의 Read/Write는 Transaction 내에서 일어나야 합니다. Transaction은 세가지 모드 readwrite, readonly, versionchange를 가지며, Object Store와 Indexes 생성은 versionchange transaction 내에서 가능합니다.




| Key Path


Object Store 또는 Index에서 브라우저가 어디로부터 key를 추출해야 하는지 정의합니다.




| Index


하나의 Index는 다른 Object Stored의 레코드를 찾기 위한 specialized object store로, refereced object store 라 불립니다. Object Store에 Index와 관련된 레코드가 업데이트 되면 자동으로 업데이트 됩니다.




| Request

Database에 읽고 쓰기를 행하는 Operation을 의미하며, 모든 Reqeust는 하나의 읽기 또는 쓰기 Operation을 나타냅니다.




| Scope

특정 Transaction이 적용되는 Object Store들과.. Index들의 범위를 의미하며, read-only transaction들의 scope는 겹칠 수 있고 동시에 실행될 수 있지만, writing transaction들의 scope는 겹칠 수 없습니다. 동시에 같은 scope의 여러 transaction을 실행할 수 있지만, 작업들은 queue up 되어 하나씩 차례로 수행됩니다.




| Cursor

특정 key range의 여러 레코드들에 대한 iterating을 위한 매커니즘으로서, 여러 레코드들에 대한 순회/반복 기능을 제공합니다.





DB 구축하기


| Indexed DB 지원 여부 확인

위와 window 객체를 참조하여 indexedDB 지원 여부를 체크할 수 있습니다. 자세한 브라우저 별 지원 정보는 브라우저 별 지원 을 통해 확인 가능합니다.




| 초기화

Indexed DB Wrapper 객체의 초기화 함수입니다. Database를 사용자 캐릭터 별로 생성하기 위해 함수 인자로 현재 선택되어 있는 사용자의 캐릭터 번호를 전달받으며, 이 정보는 Database의 이름으로 사용될 것입니다. 별도의 에러 콜백 함수 등록을 위해 해당 객체를 bind 합니다.




| Database 및 Object Store 생성


OpenDB 함수는 Database open 결과를 promise 로 반환하여 Wrapper 객체 안에서 지속적으로 참조됩니다. 실제 resolve 함수로 반환되는 결과는 Indexed DB의 open API 함수의 결과인 IDBDatabase 객체 입니다.


DB 가 최초 생성될 경우 onupgradeneeded 콜백과 onsuccess 콜백이 차례로 호출되며, 이미 생성된 Database가 존재할 경우 onsuccess 콜백만 호출됩니다.


onupgradeneeded 콜백 함수에서는 Object Store를 생성하며, 이미 동일 이름의 Object Store가 존재하는지 체크합니다.


onsuccess 콜백 함수에서 Indexed DB open 결과를 resolve 함수를 통해 반환합니다.

onerror 콜백 함수는 open 한 Database의 모든 에러 발생시 에러 정보와 함께 호출됩니다.

onblocked 콜백 함수는 다른 브라우저 탭에서 동일 Database에 동시 접근할 경우 호출됩니다.

onversionchange 콜백 함수는 Database가 새로 생성될 때 또는 DB 버전이 이전 버전보다 높은 버전으로 지정될 때 호출됩니다.




| 데이터 읽기

데이터를 읽기 위해선 Object Store 객체의 get API 함수를 사용합니다. 먼저 Database의 transaction을 readonly 모드로 열고 get 함수로 데이터를 읽으며, get 함수는 결과 정보를 담은 IDBRequest 객체의 onsuccess 콜백 함수로부터 읽기 데이터를 최종 반환하는 함수입니다. Transaction 내에 특정 Object Store만 지정함으로서 scope가 중복되지 않는한 여러 개의 Transaction을 동시에 실행할 수 있게 됩니다.




| 데이터 쓰기

데이터를 쓰기 위해선 set 또는 put API 함수를 사용할 수 있는데, 각각 새로 쓰기와 업데이트 역할을 수행하며, put 함수로 새로 쓰기와 업데이트 모두 가능합니다.

저는 key path를 별도로 설정하지 않아서 put 함수 파라미터로 outline key를 사용하였습니다.

readwrite 모드는 필요시에만 사용해야 합니다. readonly 모드는 scope가 중복되더라도 동시에 실행 가능하지만, readwrite 모드는 하나의 Object Store에 대해 한번에 하나의 요청만 처리 가능하기 때문입니다.




| 데이터 지우기

데이터를 지우기 위해선 delete API 함수를 사용합니다 . 위 두 함수들과 마찬가지로 Transaction 을 통해 모든 처리가 이루어지며 , 콜백 함수로부터 결과 정보를 반환 받습니다.





Data Migration


| Migration 함수

Migration은 Local Storage와 Indexed DB 의 공통된 key-value 접근 방식을 기반으로, 특정 키로 기존에 Local Storage에 저장되어있는 value가 존재하는지 확인하고,, 존재할 경우 Indexed DB 에 새로 write 합니다. 해당 작업은 key 갯수만큼 이루어지며, promiseall 함수에 작업 결과 promise 배열을 넘겨줍니다.




| 함수 연동

실제 기존 코드에 Migration 함수를 연동한 모습입니다. Migration 함수가 잘 동작하는지 결과를 브라우저 상에서 확인해보면 아래와 같습니다.



Migration 후 브라우저 내 Indexed DB 모습


마무리


기존의 문제점 해결을 통해 서비스를 개선해보면서, 점차 증가하는 FLO 서비스의 개인화 데이터와 더불어, 클라이언트가 처리해야 될 데이터 양은 앞으로도 계속 증가할 것이라 생각됩니다.

또한 이를 기반으로 다양한 사용자 시나리오를 구현하기 위해선 클라이언트의 역할은 계속해서 커질 것입니다.


이와 같이 클라이언트 사이드의 데이터 저장 관리에 대한 중요성을 재확인할 수 있었는데요.

실제 적용해보면서 겪은 이슈를 기반으로 향후 과제를 정리해보았습니다.



| 데이터 검색 효율 개선

현재 기존의 Local Storage 기반의 코드 구조에 효율적으로 연동하기 위해 별도의 인덱스를 생성하지 않았고, 또한 외부 키 기반의 key-value 데이터 접근 방식으로 인터페이스를 구축하였습니다. 향후 데이터 모델링 및 코드 구조 개선과 함께 Index 및 Cursor API 를 적용하여 데이터 검색 효율을 개선할 것입니다.



| onblock 콜백 함수

여러 탭에서 Indexed DB에 동시에 접근시 자동으로 호출되는 onblock 콜백 함수기반의 사용자 가이드 시나리오가 추후 필요할 것입니다.



| Database 버전 관리

Indexed DB API 스펙인 1 이상의 정수 기반으로 이전 버전보다 높은 값으로 버저닝 하는 단순한 구조보다는, 웹앱의 버전과 싱크를 맞추어 관리한다면 향후 개선될 버전 트랙킹에 도움이 될 것입니다.



| Object Store 분류

하나 이상의 Object Store 를 생성할 수 있으므로, 캐릭터 별 Database 내에 데이터의 성격 또는 서비스 시나리오 별로 Object Store 를 구분한다면, 데이터 트랜잭션의 성능에 이로울 것입니다.



| 만료 기간 설정

데이터 별로 만료 기간을 설정 옵션 및 인터페이스를 제공한다면 보다 다양한 사용자 시나리오에 대응할 수 있을 것입니다.



| 용량 관리

Indexed DB는 용량 제한이 특별히 없고, 사용자의 HDD 저장소 상태 또는 브라우저 상태에 따라 상이해질 수 있으므로, Database 상태 관리 및 용량 확보를 위한 모듈이 필요할 것입니다.



지금까지 Indexed DB 적용기를 읽어주셔서 감사드리며, FLO 웹 서비스의 성장 과정을 계속해서 지켜봐 주시길 바랍니다.



출처:

https://developer.mozilla.org/ko/docs/Web/API/IndexedDB_API/Basic_Concepts_Behind_IndexedDB#gloss_inline_key


https://developer.mozilla.org/ko/docs/Web/API/IndexedDB_API/Using_IndexedDB#updating_an_entry_in_the_database


https://developers.google.com/web/ilt/pwa/working-with-indexeddb#checking_for_indexeddb_support