IndexedDB 사용 권장사항

널리 사용되는 상태 관리 라이브러리인 IndexedDB 간에 애플리케이션 상태를 동기화하기 위한 권장사항을 알아봅니다.

사용자가 웹사이트나 애플리케이션을 처음 로드할 때 UI를 렌더링하는 데 사용되는 초기 애플리케이션 상태를 구성하는 데 상당한 양의 작업이 필요한 경우가 많습니다. 예를 들어 앱이 페이지에 표시해야 하는 모든 데이터를 얻기 전에 클라이언트 측에서 사용자를 인증한 다음 여러 API 요청을 해야 하는 경우가 있습니다.

애플리케이션 상태를 IndexedDB에 저장하면 재방문 시 로드 시간을 단축할 수 있습니다. 그러면 앱이 백그라운드에서 모든 API 서비스와 동기화하고 stale-while- revalidate 전략을 사용하여 새 데이터로 UI를 느리게 업데이트할 수 있습니다.

IndexedDB의 또 다른 유용한 용도는 사용자 제작 콘텐츠를 서버에 업로드되기 전 임시 저장소로 저장하거나 원격 데이터의 클라이언트 측 캐시로 저장하거나 둘 다 저장하는 것입니다.

하지만 IndexedDB를 사용할 때는 API를 처음 접하는 개발자에게는 바로 이해되지 않을 수 있는 중요한 요소들이 많이 있습니다. 이 문서에서는 일반적인 질문에 답변하고 IndexedDB에 데이터를 유지할 때 유의해야 할 가장 중요한 몇 가지 사항을 설명합니다.

앱의 예측 가능성 유지

IndexedDB와 관련된 많은 복잡성은 개발자가 제어할 수 없는 요소가 너무 많기 때문입니다. 이 섹션에서는 IndexedDB를 사용할 때 염두에 두어야 하는 여러 가지 문제를 살펴봅니다.

일부 플랫폼에서는 모든 항목을 IndexedDB에 저장할 수 없습니다.

이미지나 동영상과 같이 사용자가 생성한 대용량 파일을 저장하는 경우 이러한 파일을 File 또는 Blob 객체로 저장해 볼 수 있습니다. 이 방법은 일부 플랫폼에서는 작동하지만 다른 플랫폼에서는 작동하지 않습니다. 특히 iOS용 Safari는 IndexedDB에 Blob을 저장할 수 없습니다.

다행히 BlobArrayBuffer로 변환하거나 그 반대로 변환하는 것은 그리 어렵지 않습니다. IndexedDB에 ArrayBuffer를 저장하는 기능은 잘 지원됩니다.

그러나 Blob에는 MIME 유형이 있지만 ArrayBuffer에는 없습니다. 변환을 올바르게 실행하려면 버퍼와 함께 유형을 저장해야 합니다.

ArrayBufferBlob로 변환하려면 Blob 생성자를 사용하면 됩니다.

function arrayBufferToBlob(buffer, type) {
  return new Blob([buffer], { type: type });
}

다른 방향은 약간 더 복잡하며 비동기식 프로세스입니다. FileReader 객체를 사용하여 blob을 ArrayBuffer로 읽을 수 있습니다. 읽기가 끝나면 loadend 이벤트가 판독기에서 트리거됩니다. 다음과 같이 이 프로세스를 Promise에 래핑할 수 있습니다.

function blobToArrayBuffer(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.addEventListener('loadend', () => {
      resolve(reader.result);
    });
    reader.addEventListener('error', reject);
    reader.readAsArrayBuffer(blob);
  });
}

스토리지에 쓰기가 실패할 수 있음

IndexedDB에 쓸 때 오류는 다양한 이유로 발생할 수 있으며 어떤 경우에는 개발자가 제어할 수 없습니다. 예를 들어 일부 브라우저는 현재 시크릿 브라우징 모드에서 IndexedDB에 쓰기를 허용하지 않습니다. 또한 사용자가 디스크 공간이 거의 없는 기기를 사용하고 있으며 브라우저에서 아무것도 저장하지 못하도록 제한할 수도 있습니다.

따라서 IndexedDB 코드에서 항상 적절한 오류 처리를 구현하는 것이 매우 중요합니다. 또한 일반적으로 비공개 탐색 모드에서 실행되거나 저장공간을 사용할 수 없을 때 (저장소가 필요한 다른 앱 기능이 작동하지 않더라도) UI가 손상되지 않도록 (저장하는 것 외에도) 애플리케이션 상태를 메모리에 유지하는 것이 좋습니다.

IDBDatabase, IDBTransaction 또는 IDBRequest 객체를 만들 때마다 error 이벤트에 이벤트 핸들러를 추가하여 IndexedDB 작업에서 오류를 포착할 수 있습니다.

const request = db.open('example-db', 1);
request.addEventListener('error', (event) => {
  console.log('Request error:', request.error);
};

저장된 데이터가 사용자가 수정했거나 삭제했을 수 있습니다.

무단 액세스를 제한할 수 있는 서버 측 데이터베이스와 달리 클라이언트 측 데이터베이스는 브라우저 확장 프로그램 및 개발자 도구에 액세스할 수 있으며 사용자가 삭제할 수 있습니다.

사용자가 로컬에 저장된 데이터를 수정하는 경우는 드물지만 사용자가 이 데이터를 지우는 것은 매우 일반적입니다. 애플리케이션에서 오류 없이 이 두 사례를 모두 처리할 수 있어야 합니다.

저장된 데이터가 오래되었을 수 있습니다.

이전 섹션과 마찬가지로 사용자가 데이터를 직접 수정하지 않았더라도 저장소에 있는 데이터가 이전 버전의 코드(버그가 있는 버전일 수 있음)에서 작성되었을 수도 있습니다.

IndexedDB는 스키마 버전 및 IDBOpenDBRequest.onupgradeneeded() 메서드를 통한 업그레이드를 기본적으로 지원합니다. 하지만 이전 버전 (버그가 있는 버전 포함)에서 유입되는 사용자를 처리할 수 있는 방식으로 업그레이드 코드를 작성해야 합니다.

가능한 모든 업그레이드 경로와 사례를 수동으로 테스트할 수 없는 경우가 많으므로 단위 테스트가 매우 유용할 수 있습니다.

앱 성능 유지

IndexedDB의 주요 기능 중 하나는 비동기 API이지만 사용 시 성능에 대해 걱정할 필요가 없다는 생각에 속지 마세요. 부적절한 사용으로 인해 여전히 기본 스레드가 차단되어 버벅거림과 반응하지 않을 수 있는 경우가 많습니다.

일반적으로 IndexedDB에 대한 읽기 및 쓰기는 액세스하는 데이터에 필요한 크기보다 크지 않아야 합니다.

IndexedDB를 사용하면 대용량의 중첩된 객체를 단일 레코드로 저장할 수 있지만, 그렇게 하면 개발자의 관점에서 매우 편리합니다. 하지만 이 방법은 피해야 합니다. 그 이유는 IndexedDB가 객체를 저장할 때 먼저 해당 객체의 구조화된 클론을 만들어야 하고 구조화된 클론 프로세스가 기본 스레드에서 발생하기 때문입니다. 객체가 클수록 차단 시간이 길어집니다.

대부분의 인기 있는 상태 관리 라이브러리 (예: Redux)는 전체 상태 트리를 단일 JavaScript 객체로 관리하므로 애플리케이션 상태를 IndexedDB에 유지하는 방법을 계획할 때 이로 인해 몇 가지 문제가 발생합니다.

이러한 방식으로 상태를 관리하면 많은 이점이 있지만 (예: 코드를 쉽게 추론하고 디버그할 수 있음) 전체 상태 트리를 IndexedDB에 단일 레코드로 저장하는 것은 유혹적이고 편리할 수 있지만, 모든 변경 (제한/디바운싱 여부 포함) 후에 이 작업을 수행하면 기본 스레드가 불필요하게 차단되고 경우에 따라 쓰기 오류가 발생하거나 경우에 따라 다운될 수도 있습니다.

전체 상태 트리를 단일 레코드에 저장하는 대신 개별 레코드로 나누고 실제로 변경되는 레코드만 업데이트해야 합니다.

IndexedDB에 이미지, 음악, 동영상과 같은 대용량 항목을 저장하는 경우에도 마찬가지입니다. 바이너리 파일을 검색하는 데 드는 비용을 지불하지 않고 구조화된 데이터를 검색할 수 있도록 더 큰 객체 내부가 아니라 자체 키로 각 항목을 저장합니다.

대부분의 권장사항과 마찬가지로 이는 '모 아니면 도' 규칙이 아닙니다. 상태 객체를 분할하고 최소한의 변경 집합만 작성하는 것이 불가능한 경우에는 데이터를 하위 트리로 분할한 후 하위 트리만 작성하는 것이 항상 전체 상태 트리를 작성하는 것보다 낫습니다. 개선이 거의 없는 것이 전혀 개선되지 않는 것보다 낫습니다.

마지막으로, 작성하는 코드가 항상 성능에 미치는 영향을 측정해야 합니다. IndexedDB에 대한 작은 쓰기가 대량 쓰기보다 성능이 우수한 것은 사실이지만 이는 애플리케이션에서 수행하는 IndexedDB에 대한 쓰기로 인해 실제로 기본 스레드를 차단하고 사용자 환경을 저하시키는 장기 작업이 발생하는 경우에만 중요합니다. 어떤 기준으로 최적화할지 파악하는 것이 중요합니다

결론

개발자는 IndexedDB와 같은 클라이언트 스토리지 메커니즘을 활용하여 세션 간에 상태를 유지할 뿐만 아니라 재방문 시 초기 상태를 로드하는 데 걸리는 시간을 줄여 애플리케이션의 사용자 환경을 개선할 수 있습니다.

IndexedDB를 올바르게 사용하면 사용자 환경을 크게 개선할 수 있지만 잘못 사용하거나 오류 사례를 처리하지 못하면 앱이 손상되고 사용자가 불만족스러워할 수 있습니다.

클라이언트 스토리지에는 개발자가 제어할 수 없는 많은 요소가 포함되어 있으므로 처음에는 일어날 가능성이 낮더라도 코드를 잘 테스트하고 오류를 올바르게 처리하는 것이 중요합니다.