back

Posts

Storage말고 IndexedDB는 어때요?

2025-05-21

새로운 회사에서 레거시 코드와 버그를 해결하던 중, 다음과 같은 상황을 맞닥뜨렸습니다.

1. 문제 상황

시나리오:

  1. 외부 결제 모듈 사용 때문에 외부 페이지를 접속했다가 결제가 끝나면 다시 앱으로 복귀합니다
  2. 이미지로 구성된 기프트 카드는 결제가 완료된 후, 결제 완료 정보와 함께 카드 정보를 서버로 전달해야 합니다
  3. 해당 기프트 카드 정보는 그동안 Recoil로 관리되고 있어서, 페이지 이동 시 카드 정보가 소멸된 채 결제가 완료되었습니다

처음 이 버그를 발견했을 때는 "뭐야? Recoil 대신 Storage로 관리하면 되는 거 아니야?"라고 생각했습니다.

하지만 로직을 구성하던 중 또 다른 문제를 발견했습니다. 해당 기프트 카드의 이미지가 Base64 형태였던 것이죠.

2. Base64란?

Base64는 바이너리 데이터를 텍스트로 인코딩하는 방식 중 하나입니다.

Base64 구성

64개의 ASCII 문자만을 사용해 데이터를 표현하는 인코딩 방식입니다:

  • A-Z (26개)
  • a-z (26개)
  • 0-9 (10개)
  • +, / (2개)

총 64개의 문자로 구성되어 있어 이름이 Base64입니다.

프로젝트 상황

우리 회사 앱은 Next.js와 Flutter로 구성된 웹뷰 형태이며, 해당 카드의 이미지는 Flutter에서 Base64로 가공하여 웹으로 전달하는 방식이었습니다.

문제점: Base64는 텍스트로 인코딩되기 때문에 그 길이가 무지막지하게 깁니다. 따라서 LocalStorage나 SessionStorage에 그대로 저장할 수 없는 문제가 있었죠.

그래서 예전에 이론만 봤었던 IndexedDB를 활용해보기로 했습니다.

3. IndexedDB란?

IndexedDB는 브라우저에서 사용 가능한 로컬 DB API입니다.

주요 특징:

  • NoSQL 계열로 key-value 형식으로 데이터를 저장할 수 있습니다
  • 수 MB~수 GB의 데이터를 저장 가능합니다
  • 비동기로 작동합니다
  • 트랜잭션 기반으로 작동하여 안전합니다
  • 대부분의 브라우저에서 지원합니다

LocalStorage vs IndexedDB

특성LocalStorageIndexedDB
저장 용량~5-10MB수백 MB ~ 수 GB
데이터 타입문자열만객체, Blob 등 다양
동작 방식동기비동기
트랜잭션없음있음

4. IndexedDB 사용하기

DB 생성

IndexedDB를 사용하려면 먼저 DB부터 만들어야 합니다.

const request = indexedDB.open("MyDB", 1);
  • 첫 번째 매개변수: DB명
  • 두 번째 매개변수: DB의 버전

중요: IndexedDB에서는 버전이 가장 중요합니다. Object Store(테이블)를 만들거나 수정하면 반드시 버전을 업그레이드해야 합니다.

Object Store 생성

DB를 만들었으니 이제 Object Store(테이블)를 만들어야 합니다.

request.onupgradeneeded = function (event) {
  const db = request.result;

  if (!db.objectStoreNames.contains("images")) {
    db.createObjectStore("images", { keyPath: "id" });
  }
};

request.onerror = () => {
  console.error("IndexedDB 열기 실패");
};

request.onsuccess = () => {
  console.log("IndexedDB 연결 성공");
};
  • db.objectStoreNames.contains('images'): 해당 테이블이 있는지 확인합니다
  • createObjectStore: 테이블을 생성합니다
  • keyPath: 고유 키입니다 (MySQL의 PRIMARY KEY 같은 개념)

5. CRUD 작업

Create - 데이터 추가

function addImage(id: string, base64: string) {
  const request = indexedDB.open("MyDB");

  request.onsuccess = () => {
    const db = request.result;
    const tx = db.transaction("images", "readwrite");
    const store = tx.objectStore("images");

    store.add({ id, base64 });

    tx.oncomplete = () => {
      console.log("이미지 저장 완료");
    };
  };
}

순서:

  1. DB에 접속합니다
  2. db.transaction('images', 'readwrite')로 images 테이블에 대해 읽기/쓰기 권한을 가진 트랜잭션을 생성합니다
  3. 트랜잭션에서 images 테이블을 가져옵니다
  4. 데이터를 Create하고 트랜잭션 완료를 확인합니다

Read - 데이터 조회

function getImage(id: string) {
  const request = indexedDB.open("MyDB");

  request.onsuccess = () => {
    const db = request.result;
    const tx = db.transaction("images", "readonly");
    const store = tx.objectStore("images");

    const getRequest = store.get(id);

    getRequest.onsuccess = () => {
      const result = getRequest.result;
      if (result) {
        console.log("이미지 데이터:", result.base64);
      } else {
        console.log("해당 ID의 데이터 없음");
      }
    };
  };
}

Read도 동일하게 DB 연결 → 트랜잭션 생성 → 테이블 접근 순으로 동작합니다. DB를 생성할 때 지정했던 keyPath로 데이터를 접근할 수 있습니다.

Update - 데이터 수정

function updateImage(id: string, newBase64: string) {
  const request = indexedDB.open("MyDB");

  request.onsuccess = () => {
    const db = request.result;
    const tx = db.transaction("images", "readwrite");
    const store = tx.objectStore("images");

    store.put({ id, base64: newBase64 });

    tx.oncomplete = () => {
      console.log("이미지 업데이트 완료");
    };
  };
}

주의: Update는 기존 정보를 덮어쓰기 때문에 데이터 관리에 주의를 기울여야 합니다.

Delete - 데이터 삭제

function deleteImage(id: string) {
  const request = indexedDB.open("MyDB");

  request.onsuccess = () => {
    const db = request.result;
    const tx = db.transaction("images", "readwrite");
    const store = tx.objectStore("images");

    store.delete(id);

    tx.oncomplete = () => {
      console.log("이미지 삭제 완료");
    };
  };
}

정리

IndexedDB는 다음과 같은 상황에서 유용합니다:

사용하기 좋은 경우:

  • 대용량 데이터를 저장해야 할 때
  • Base64 이미지나 Blob 데이터를 저장할 때
  • 복잡한 객체를 저장해야 할 때
  • 오프라인 데이터 캐싱이 필요할 때

LocalStorage가 더 나은 경우:

  • 간단한 문자열 데이터만 저장할 때
  • 동기적으로 빠르게 접근해야 할 때
  • 5MB 이하의 작은 데이터일 때

IndexedDB는 실제 DB처럼 트랜잭션도 존재하며 비동기로 작동하여 실무에서 유용하게 쓰일 경우가 많이 있습니다. 여러분도 기회가 된다면 IndexedDB를 한번 사용해보는 건 어떨까요?