오프라인 환경을 제대로 구축하려면 PWA에 저장소 관리가 필요합니다. 캐싱 챕터에서는 캐시 스토리지가 기기에 데이터를 저장하는 한 가지 옵션이라고 설명했습니다. 이 장에서는 데이터 지속성, 한도, 사용 가능한 도구 등 오프라인 데이터를 관리하는 방법을 알아봅니다.
스토리지
스토리지는 파일과 애셋뿐만 아니라 다른 유형의 데이터를 포함할 수 있습니다. PWA를 지원하는 모든 브라우저에서 다음 API를 온디바이스 저장소에 사용할 수 있습니다.
- IndexedDB: 구조화된 데이터 및 blob (바이너리 데이터)을 위한 NoSQL 객체 스토리지 옵션입니다.
- WebStorage: 로컬 스토리지 또는 세션 스토리지를 사용하여 키/값 문자열 쌍을 저장하는 방법입니다. 서비스 워커 컨텍스트에서는 사용할 수 없습니다. 이 API는 동기식이므로 복잡한 데이터 스토리지에는 권장되지 않습니다.
- 캐시 저장소: 캐싱 모듈에서 다룬 내용과 같습니다.
지원되는 플랫폼에서 저장소 관리자 API를 사용하여 모든 기기 저장소를 관리할 수 있습니다. Cache Storage API와 IndexedDB는 PWA의 영구 저장소에 대한 비동기 액세스를 제공하며 기본 스레드, 웹 작업자, 서비스 작업자에서 액세스할 수 있습니다. 두 가지 모두 네트워크가 불안정하거나 존재하지 않을 때 PWA가 안정적으로 작동하도록 하는 데 필수적인 역할을 합니다. 그렇다면 언제 어떤 플랫폼을 사용해야 할까요?
네트워크 리소스, 즉 HTML, CSS, JavaScript, 이미지, 동영상, 오디오와 같이 URL을 통해 요청하여 액세스하는 항목에는 Cache Storage API를 사용합니다.
IndexedDB를 사용하여 구조화된 데이터를 저장합니다. 여기에는 NoSQL과 유사한 방식으로 검색하거나 결합해야 하는 데이터 또는 URL 요청과 반드시 일치하지 않는 사용자별 데이터와 같은 기타 데이터가 포함됩니다. IndexedDB는 전체 텍스트 검색을 위해 설계되지 않았습니다.
IndexedDB
IndexedDB를 사용하려면 먼저 데이터베이스를 엽니다. 데이터베이스가 없으면 새 데이터베이스가 생성됩니다.
IndexedDB는 비동기 API이지만 Promise를 반환하는 대신 콜백을 사용합니다. 다음 예에서는 IndexedDB용 소형 Promise 래퍼인 제이크 아치볼드의 idb 라이브러리를 사용합니다. IndexedDB를 사용하는 데 헬퍼 라이브러리가 필요한 것은 아니지만 Promise 구문을 사용하려면 idb
라이브러리를 사용하면 됩니다.
다음 예에서는 요리 레시피를 저장할 데이터베이스를 만듭니다.
데이터베이스 만들기 및 열기
데이터베이스를 열려면 다음 단계를 따르세요.
openDB
함수를 사용하여cookbook
이라는 새 IndexedDB 데이터베이스를 만듭니다. IndexedDB 데이터베이스는 버전이 지정되므로 데이터베이스 구조를 변경할 때마다 버전 번호를 늘려야 합니다. 두 번째 매개변수는 데이터베이스 버전입니다. 예시에서는 1로 설정됩니다.upgrade()
콜백이 포함된 초기화 객체가openDB()
에 전달됩니다. 콜백 함수는 데이터베이스가 처음 설치되거나 새 버전으로 업그레이드될 때 호출됩니다. 이 함수는 작업이 발생할 수 있는 유일한 위치입니다. 작업에는 새 객체 스토어 (IndexedDB가 데이터를 정리하는 데 사용하는 구조) 또는 색인 (검색하려는 색인)을 만드는 것이 포함될 수 있습니다. 데이터 이전도 여기에서 이루어져야 합니다. 일반적으로upgrade()
함수에는break
문장 없이switch
문장이 포함되어 이전 버전의 데이터베이스를 기반으로 각 단계가 순서대로 진행되도록 합니다.
import { openDB } from 'idb';
async function createDB() {
// Using https://github.com/jakearchibald/idb
const db = await openDB('cookbook', 1, {
upgrade(db, oldVersion, newVersion, transaction) {
// Switch over the oldVersion, *without breaks*, to allow the database to be incrementally upgraded.
switch(oldVersion) {
case 0:
// Placeholder to execute when database is created (oldVersion is 0)
case 1:
// Create a store of objects
const store = db.createObjectStore('recipes', {
// The `id` property of the object will be the key, and be incremented automatically
autoIncrement: true,
keyPath: 'id'
});
// Create an index called `name` based on the `type` property of objects in the store
store.createIndex('type', 'type');
}
}
});
}
이 예시에서는 cookbook
데이터베이스 내에 recipes
이라는 객체 저장소를 만듭니다. id
속성은 저장소의 색인 키로 설정되고 type
속성을 기반으로 type
이라는 다른 색인이 생성됩니다.
방금 생성된 객체 저장소를 살펴보겠습니다. 객체 저장소에 레시피를 추가하고 Chromium 기반 브라우저에서 DevTools를 열거나 Safari에서 Web Inspector를 열면 다음과 같은 결과가 표시됩니다.
데이터 추가
IndexedDB는 트랜잭션을 사용합니다. 트랜잭션은 작업을 그룹화하여 하나의 단위로 실행되도록 합니다. 이를 통해 데이터베이스가 항상 일관된 상태를 유지할 수 있습니다. 앱의 여러 복사본이 실행 중인 경우 동일한 데이터에 동시에 쓰는 것을 방지하는 데도 중요합니다. 데이터를 추가하려면 다음 안내를 따르세요.
mode
이readwrite
로 설정된 트랜잭션을 시작합니다.- 데이터를 추가할 객체 저장소를 가져옵니다.
- 저장할 데이터로
add()
을 호출합니다. 이 메서드는 사전 형식 (키-값 쌍)으로 데이터를 수신하여 객체 저장소에 추가합니다. 사전은 구조화된 클론을 사용하여 클론할 수 있어야 합니다. 기존 객체를 업데이트하려면 대신put()
메서드를 호출합니다.
트랜잭션에는 트랜잭션이 성공적으로 완료되면 해결되거나 트랜잭션 오류로 거부되는 done
프로미스가 있습니다.
IDB 라이브러리 문서에 설명된 대로 데이터베이스에 쓰는 경우 tx.done
는 모든 항목이 데이터베이스에 성공적으로 커밋되었음을 나타내는 신호입니다. 하지만 트랜잭션이 실패하는 원인이 되는 오류를 확인할 수 있도록 개별 작업을 대기하는 것이 좋습니다.
// Using https://github.com/jakearchibald/idb
async function addData() {
const cookies = {
name: "Chocolate chips cookies",
type: "dessert",
cook_time_minutes: 25
};
const tx = await db.transaction('recipes', 'readwrite');
const store = tx.objectStore('recipes');
store.add(cookies);
await tx.done;
}
쿠키를 추가하면 레시피가 다른 레시피와 함께 데이터베이스에 저장됩니다. ID는 indexedDB에 의해 자동으로 설정되고 증가합니다. 이 코드를 두 번 실행하면 동일한 쿠키 항목이 두 개가 됩니다.
데이터 가져오는 중
IndexedDB에서 데이터를 가져오는 방법은 다음과 같습니다.
- 트랜잭션을 시작하고 객체 저장소 또는 저장소를 지정하며, 선택적으로 트랜잭션 유형을 지정합니다.
- 해당 거래에서
objectStore()
를 호출합니다. 객체 저장소 이름을 지정해야 합니다. - 가져오려는 키로
get()
을 호출합니다. 기본적으로 저장소는 키를 색인으로 사용합니다.
// Using https://github.com/jakearchibald/idb
async function getData() {
const tx = await db.transaction('recipes', 'readonly')
const store = tx.objectStore('recipes');
// Because in our case the `id` is the key, we would
// have to know in advance the value of the id to
// retrieve the record
const value = await store.get([id]);
}
스토리지 관리자
PWA의 저장소를 관리하는 방법을 아는 것은 네트워크 응답을 올바르게 저장하고 스트리밍하는 데 특히 중요합니다.
스토리지 용량은 캐시 스토리지, IndexedDB, 웹 스토리지, 서비스 워커 파일 및 종속 항목을 비롯한 모든 스토리지 옵션 간에 공유됩니다.
하지만 사용 가능한 저장용량은 브라우저마다 다릅니다. 소진될 가능성은 낮습니다. 일부 브라우저에서는 사이트가 메가바이트에서 기가바이트에 이르는 데이터를 저장할 수 있습니다. 예를 들어 Chrome에서는 브라우저가 전체 디스크 공간의 최대 80% 를 사용할 수 있으며 개별 출처는 전체 디스크 공간의 최대 60% 를 사용할 수 있습니다. Storage API를 지원하는 브라우저의 경우 앱의 사용 가능한 스토리지, 할당량, 사용량을 알 수 있습니다.
다음 예에서는 Storage API를 사용하여 예상 할당량과 사용량을 가져온 다음 사용된 비율과 남은 바이트를 계산합니다. navigator.storage
는 StorageManager
의 인스턴스를 반환합니다. 별도의 Storage
인터페이스가 있으며 혼동하기 쉽습니다.
if (navigator.storage && navigator.storage.estimate) {
const quota = await navigator.storage.estimate();
// quota.usage -> Number of bytes used.
// quota.quota -> Maximum number of bytes available.
const percentageUsed = (quota.usage / quota.quota) * 100;
console.log(`You've used ${percentageUsed}% of the available storage.`);
const remaining = quota.quota - quota.usage;
console.log(`You can write up to ${remaining} more bytes.`);
}
Chromium DevTools에서 애플리케이션 탭의 스토리지 섹션을 열면 사이트의 할당량과 사용 중인 스토리지가 사용 항목별로 분류되어 표시됩니다.
Firefox와 Safari는 현재 출처의 모든 스토리지 할당량과 사용량을 확인할 수 있는 요약 화면을 제공하지 않습니다.
데이터 지속성
호환되는 플랫폼에서 브라우저에 영구 스토리지를 요청하여 비활성 상태이거나 스토리지 압력이 있을 때 데이터가 자동으로 삭제되지 않도록 할 수 있습니다. 권한이 부여되면 브라우저는 스토리지에서 데이터를 삭제하지 않습니다. 이 보호에는 서비스 워커 등록, IndexedDB 데이터베이스, 캐시 저장소의 파일이 포함됩니다. 사용자는 항상 관리자이며 브라우저에서 영구 스토리지를 부여한 경우에도 언제든지 스토리지를 삭제할 수 있습니다.
영구 스토리지를 요청하려면 StorageManager.persist()
를 호출합니다. 이전과 마찬가지로 StorageManager
인터페이스는 navigator.storage
속성을 통해 액세스합니다.
async function persistData() {
if (navigator.storage && navigator.storage.persist) {
const result = await navigator.storage.persist();
console.log(`Data persisted: ${result}`);
}
StorageManager.persisted()
를 호출하여 현재 출처에 영구 스토리지가 이미 부여되었는지 확인할 수도 있습니다. Firefox는 영구 스토리지를 사용하기 위해 사용자에게 권한을 요청합니다. Chromium 기반 브라우저는 사용자의 콘텐츠 중요도를 판단하는 휴리스틱에 따라 지속성을 부여하거나 거부합니다. 예를 들어 Google Chrome의 기준 중 하나는 PWA 설치입니다. 사용자가 운영체제에 PWA 아이콘을 설치한 경우 브라우저에서 영구 스토리지를 부여할 수 있습니다.
API 브라우저 지원
웹 스토리지
파일 시스템 액세스
스토리지 관리자