IndexedDB의 트랜잭션 수명주기와 브라우저 클라이언트 영속 스토리지 모델
브라우저 내 대용량 구조화 데이터베이스인 IndexedDB의 트랜잭션 수명주기, 자동 커밋 메커니즘, 그리고 디스크 영속성 내구성(Durability Mode)을 분석합니다.
발행·오류 제보: infostoa · 편집 기준
이 글의 개별 작성자와 전문 검토자 정보는 제공되지 않습니다.
IndexedDB 트랜잭션은 읽기와 쓰기를 원자적으로 처리합니다. durability는 커밋 시 성능과 내구성 중 어느 쪽을 우선할지 브라우저에 전달하는 힌트이며, 물리 저장장치나 장애 복구를 보장하지는 않습니다.
저장소로 옮기는 비유이며 IndexedDB 트랜잭션 상태나 strict·relaxed·default 선택, 실제 커밋 결과를 보여주지 않습니다.
객체 저장소와 인덱스
객체 저장소는 키와 값으로 된 레코드를 보관하며, 같은 저장소 안에서 키는 고유합니다. 인덱스는 저장된 값의 다른 필드를 기준으로 조회하는 경로입니다. 이 논리 모델만으로 브라우저의 디스크 구현이나 고정 용량을 추정할 수 없습니다.
트랜잭션이 요청을 받을 수 있는 시점
W3C 규격은 상태를 active, inactive, committing, finished로 구분합니다. 생성 직후와 관련 요청의 이벤트를 처리하는 동안 트랜잭션은 active이며, 이때 추가 요청을 등록할 수 있습니다. 이벤트 처리가 끝나면 inactive가 됩니다. 구현은 요청과 결과 처리가 끝나고 새 요청이 없으며 abort되지 않은 inactive 트랜잭션의 커밋을 시도합니다. 커밋은 전부 반영되거나 실패 시 abort 절차를 따르며, abort는 해당 트랜잭션의 변경을 되돌립니다. 타이머나 네트워크 응답을 기다린 뒤에도 트랜잭션이 계속 active일 것이라고 가정하지 마세요. 다만 setTimeout()이나 fetch()를 호출했다는 사실만으로 항상 중단되는 것도 아닙니다. 이후 데이터베이스 요청을 등록할 때 트랜잭션 상태가 중요합니다.
가상 사례: 통신 응답 뒤에 트랜잭션을 열기
예를 들어 서버에서 초안 내용을 받아 로컬에 저장한다고 해 봅시다. 먼저 네트워크 응답과 JSON 변환을 끝낸 다음 트랜잭션을 만들고, 생성 직후 동기적으로 put() 요청을 등록합니다. 마지막으로 complete를 기다려 브라우저가 트랜잭션 완료를 알렸는지 확인합니다.
예제 전제: db는 이미 열린 데이터베이스이고 drafts 객체 저장소는 keyPath: "id"로 만들어져 있으며, 응답 객체에 id가 있습니다.
function completed(tx) {
return new Promise((resolve, reject) => {
tx.addEventListener("complete", resolve, { once: true });
tx.addEventListener("abort", () => reject(tx.error ?? new Error("Transaction aborted")), { once: true });
});
}
async function saveDraft(db) {
const response = await fetch("/api/draft/42");
if (!response.ok) throw new Error("Draft request failed");
const draft = await response.json();
const tx = db.transaction("drafts", "readwrite");
const done = completed(tx);
tx.objectStore("drafts").put(draft);
await done;
}
이 코드는 설명용이며 실행 검증하지 않았습니다. 반대로 트랜잭션을 먼저 만든 뒤 await fetch(...)를 기다리고 나서 put()을 호출하면, 대기 중 트랜잭션이 inactive 또는 완료 상태가 되어 요청 등록이 실패할 수 있습니다. W3C 규격상 트랜잭션은 이벤트 루프에 제어가 돌아간 뒤 inactive가 되며 그동안 새 요청을 받을 수 없습니다.
또 다른 가상 사례로, 편집 21과 편집 22의 네트워크 응답이 역순으로 도착할 수 있습니다. 도착 순서대로 덮어쓰면 오래된 21이 나중에 저장되어 최신 22를 되돌릴 수 있습니다. 서버의 단조 증가 revision을 저장하고, 동일한 readwrite 트랜잭션 안에서 현재 revision과 비교해 더 새 버전만 쓰도록 설계하세요. 기대 결과는 늦게 도착한 오래된 응답이 최신 저장값을 덮지 않는 것입니다. 이 경쟁 조건과 처리 결과도 실제 환경에서는 별도로 시험해야 합니다.
`durability`가 바꾸는 판단
strict는 미결 변경이 영속 저장 매체에 기록됐음을 확인한 뒤 커밋된 것으로 볼 수 있게 합니다. relaxed는 운영체제에 변경 내용이 전달된 시점에 추가 확인 없이 완료로 볼 수 있습니다. default는 브라우저의 기본 선택에 맡깁니다. 이는 사용자 에이전트가 취할 수 있는 커밋 동작이지, 앱이 디스크 플러시 방식이나 전원 장애 내성을 지정하는 설정이 아닙니다.
따라서 재생성 가능한 캐시와 최근 쓰기를 잃으면 곤란한 데이터를 구분해 옵션을 선택하고, 지원 여부도 확인하세요. 완료 이벤트는 브라우저가 트랜잭션을 완료했다고 알리는 신호이지 백업이 아닙니다. 기기 분실이나 저장소 손상 뒤에도 필요한 데이터에는 서버 동기화나 별도 백업을 마련하고, 로컬 저장만으로 영구 보존된다고 사용자에게 약속하지 마세요.