[TIL] cursor를 활용한, IndexedDB 저장 데이터 갯수 제한하기 (feat.idb library)

최소희·2022년 11월 16일
0

프론트엔드 학습

목록 보기
5/13

idb library 공식 문서를 토대로, idb library 특징저장할 수 있는 데이터 갯수를 제한하는 리팩토링 과정을 정리하였습니다.

🙋‍♀️ IndexedDB API에 대해 궁금하신 분들은 Indexed API 분석 정리 게시물을 먼저 참고해주시면 감사하겠습니다.

이슈 상황

IndexedDB에 데이터를 누적 저장한 후, 특정 로직을 통해 최종 데이터를 보여주는 기능에서 초반에는 3~4초

후반에는 7~8초의 로딩 시간이 걸리면서 UI 동작을 할 수 없는 문제가 발생하였다.

에러 발생 원인

해당 문제는 IndexedDB에 저장하는 데이터가 증가함에 따라 로직 처리 시간이 오래 걸려 싱글쓰레드에서 UI blocking 현상으로 인해 발생된 것이었다.

이는 유저 경험을 저하시키고, 시간이 지날수록 해당 로직 처리 시간이 길어진다는 단점이 생겨 최근 데이터를 기준으로 특정 갯수까지만 저장하도록 limit을 걸기로 하였다.

idb 라이브러리와 기존 IndexedDB API의 차이점

해결 방법을 구체화하기 전, 이번에 사용한 idb라이브러리와 IndexedDB API에 대해 잠깐 다루어본다.

  • IndexedDB API에서 IDBRequest object를 리턴하는 메서드들은 idb에서 결과에 대한 Promise를 반환한다.
  • idb에서 transaction을 연결 시, transaction 시작과 끝 사이에서는 await 를 걸지 않는다. 걸게 되면, transaction이 닫혀 이후 작업이 불가하다.
  • 또한 idb transaction은 특정 작업을 처리한 후, 남은 작업이 없으면 자동을 닫힌다(auto-close).
    아래 공식 문서 예시 코드와 같이 fetch 비동기 요청으로 인해 당장 작업할 것이 없게 되면, 브라우저가 fetch 요청을 보내는 동안 transaction이 자동으로 닫혀, store.put 에서 오류가 발생하게 되는 것이다.
	const tx = db.transaction('keyval', 'readwrite');
    const store = tx.objectStore('keyval');
    const val = (await store.get('counter')) || 0;
    // This is where things go wrong:
    const newVal = await fetch('/increment?val=' + val);
    // And this throws an error:
    await store.put(newVal, 'counter');
    await tx.done;

해결 방법

현재 IndexedDB API를 반영한 idb 라이브러리를 사용하여 DB를 구축하였기 때문에, idb 공식 자료를 기반으로 리팩토링을 진행하였다.

주요 핵심 기능은 cursor이라는 idb method이다.

indexedDB는 객체 저장소를 순회하기 위해서 for문을 바로 사용해서는 안되고, cursor로 순회하고자 하는 객체 저장소의 cursor를 얻은 후 순회해야한다.

  • Cursor은 데이터베이스의 여러 레코드를 순회하거나 반복할 수 있다.
    • openCursor로 Cursor를 요청하는 IDBRequest를 전달 받는다.
    • cursor을 받으면, 원하는 레코드에 접근한다.
  • continue로 반복 조회를 한다.

해결 순서

  1. db.transaction 메서드로, 연결된 DB에서 문제가 발생하는 store를 기준으로, 해당 스토어의 모든 키들을 꺼내온다
    • 스토어에 저장된 데이터를 key를 통해 접근하기 위해, getAllKeys 메서드를 사용했다.
// 1번째 방식
.
.
// db 연결 완료 
.
.    
// 기본적인 스토어 얻는 방법
const tx = db.transaction("store name", "readwrite");
const store = tx.objectStore("store name");

// key로 데이터에 접근하여 삭제하기 위해, key 배열 얻어내기
const keys = await db.getAllKeys("store name");
.
.

참고로 1번과 2번의 코드는 같다. 앞서 차이점에서 언급했듯이, 1번 코드에서는 transaction의 시작과 끝 사이에서는 await를 걸지 않았다는 것을 볼 수 있다.
나는 코드 가독성을 위해 위의 방식대로 진행하였다.

// 2번째 방식
.
.
// db 연결 완료 
.
.   
const tx = db.transaction("storeName", "readwrite").objectStore("storeName");
const keys = await db.getAllKeys("storeName");
.
.
  1. 해당 store를 순회하기 위해 cursor을 open한다
    • openCurosr()메서드로 cursor를 얻어, 해당 cursor가 있을 경우 삭제할 갯수만큼 for문으로 순회한다.
    • 이때, 다음 커서로 이동(즉, 순회)하기 위해서는 cursor.continue()로 cursor를 갱신한다.
.
.

let cursor = await db.transaction("storeName").store.openCursor();
if (cursor) {
  for (let i = 0; i < "순회횟수"; i++) {
    db.delete("storeName", keys[i])
	// 예외처리
    if (cursor === null) {
      return;
    }
	// continue로 다음 커서를 요청한다.
    cursor = await cursor.continue();
  }

}
.
.

이외에도 getgetKeygetAllgetAllKeyscountputadddelete, 그리고 clear 메서드로 필요한 작업을 진행할 수 있다.

참고 자료

npm idb library

profile
프론트엔드 개발자 👩🏻‍💻

0개의 댓글