Browser Storage APIs for 파일 전송 Applications
Leverage IndexedDB, File System Access API, and Cache API for 파일 전송 apps. Storage limits, performance, and browser compatibility.
파일 전송 앱의 브라우저 스토리지는 네 가지 API로 명확히 구분됩니다. 구조화된 세션과 청크 메타데이터용 IndexedDB(트랜잭션, 비동기, 타입 지원), 사용자 디스크에서 직접 멀티기가바이트 파일을 읽고 쓰는 File System Access API, HTTP 응답과 앱 셸 자산용 Cache API, 할당량 관리와 영속성 힌트를 위한 Storage Manager API입니다. Origin Private File System(OPFS)은 샌드박스 고성능 I/O용으로 이들과 함께 사용됩니다. 올바른 API를 선택하는 것이 중요한 이유는 iOS Safari에서 1 GB부터 데스크톱 Chrome에서 "여유 디스크의 60%"까지 한도가 다양하고, 잘못된 선택은 결국 프로덕션에서 QuotaExceededError로 이어지기 때문입니다.
API별 적합한 용도
행 형태의 데이터에는 IndexedDB를 사용하세요. 업로드 세션, 완료된 청크 인덱스, 공유 메타데이터, 취소 토큰 등입니다. 비동기, 트랜잭션, 인덱싱 가능, 세션 간 지속성을 모두 갖추고 있습니다.
500 MB 이상의 파일을 RAM에 전부 올리지 않고 디스크에 바이트를 직접 쓰려면 File System Access API를 사용하세요. Chrome, Edge, Opera에서 지원하며 Firefox와 Safari는 showOpenFilePicker를 통한 읽기 전용 서브셋만 구현합니다.
HTTP Response 객체에는 Cache API를 사용하세요. JS 번들, CSS, 아이콘, 캐시된 API 응답이 해당됩니다. Service Worker의 fetch 인터셉션에 최적화되어 있습니다.
OPFS(File System Access API의 Origin-Private 브랜치)는 빠르고 샌드박스화된 사용자에게 보이지 않는 스토리지가 필요할 때 사용합니다. 예를 들어 멀티기가바이트 암호화 패스 중의 쓰기 버퍼입니다. 이진 블롭에 대한 디스크 처리량이 IndexedDB보다 한 자릿수 높습니다.
불편함을 제거한 IndexedDB
기본 IndexedDB는 이벤트 기반 API가 매우 불편합니다. Jake Archibald의 idb 패키지(gzip 1.5 KB) 또는 Dexie.js(20 KB, 더 풍부한 쿼리 API)를 사용하세요:
import { openDB } from 'idb';
const db = await openDB('transfers', 2, {
upgrade(db, oldVersion) {
if (oldVersion < 1) {
const sessions = db.createObjectStore('sessions', { keyPath: 'id' });
sessions.createIndex('by_expiry', 'expiresAt');
}
if (oldVersion < 2) {
db.createObjectStore('chunks', { keyPath: ['sessionId', 'index'] });
}
}
});
await db.put('sessions', { id: 'abc', fileName: 'report.pdf', expiresAt: Date.now() + 86400000 });
버전 마이그레이션은 upgrade 콜백에서 실행됩니다. v1에서 v3으로 건너뛰는 사용자도 두 단계를 모두 거치도록 oldVersion으로 마이그레이션을 항상 가드하세요.
IndexedDB는 구조화된 클론을 통해 Blob과 File 참조를 포함한 대부분의 데이터 형태를 처리합니다. 즉, 세션 레코드에 File 핸들을 저장하고 탭 새로고침 후에도 원본 파일 바이트를 다시 읽을 수 있어, 재시작 가능한 업로드에 완벽합니다.
대용량 다운로드를 위한 File System Access API
이 API는 브라우저의 저장 대화상자에 쓰기 가능한 스트림을 전달합니다:
const handle = await window.showSaveFilePicker({
suggestedName: 'decrypted-archive.zip',
types: [{ description: 'Zip', accept: { 'application/zip': ['.zip'] } }]
});
const writable = await handle.createWritable();
await decryptionStream.pipeTo(writable);
바이트가 JS 힙을 거치지 않고 곧바로 디스크로 흐릅니다. 브라우저에서 10 GB 복호화 파일을 저장하는 유일하게 실용적인 방법입니다.
Firefox와 Safari에서는 StreamSaver.js로 폴백하세요. Service Worker를 이용해 다운로드 UI를 트리거하는 스트리밍 응답을 합성합니다. 사용성은 같고 구성 요소가 약간 더 복잡합니다.
영속 파일 핸들을 통해 앱이 세션 간 파일을 다시 열 수 있습니다. 사용자가 showOpenFilePicker로 권한을 한 번 부여하면 FileSystemFileHandle을 IndexedDB에 저장해두고 이후 handle.requestPermission()을 호출해 매번 파일을 다시 선택하지 않고도 접근을 재획득할 수 있습니다.
임시 공간을 위한 OPFS
Origin Private File System은 오리진별 샌드박스 스토리지로, 파일 시스템처럼 동작하지만 사용자에게 보이지 않습니다:
const root = await navigator.storage.getDirectory();
const fh = await root.getFileHandle('scratch.bin', { create: true });
const access = await fh.createSyncAccessHandle(); // workers only
access.write(buffer, { at: offset });
access.flush();
access.close();
createSyncAccessHandle은 Web Worker(Service Worker 포함) 내부에서만 사용 가능합니다. 동기식이고 매우 빠릅니다. 순차 쓰기 벤치마크에서 IndexedDB보다 3~10배 빠릅니다. 몇백 메가바이트의 암호화 출력을 업로드 전 버퍼링하거나, 사용자의 다운로드 폴더를 오염시키지 않고 복호화된 작업 복사본을 캐싱하는 데 사용하세요.
Safari 17에서 sync access handle을 포함한 OPFS가 출시되었고 Firefox 111이 뒤따랐습니다. 세 주요 브라우저 모두 지원하므로 프로덕션 코드에서 사용 가능합니다.
스토리지 할당량과 생존 방법
모든 API는 동일한 오리진 할당량 풀을 공유합니다. 대략적인 상한:
- 데스크톱 Chrome: 여유 디스크의 60%
- 데스크톱 Firefox: 여유 디스크의 50%, 기본적으로 오리진당 2 GB 상한
- 데스크톱 Safari: 1 GB 경고, 사용자 승인 시 디스크의 약 20%까지 확장
- iOS Safari: 오리진당 1 GB, 미사용 시 7일 후 적극적 제거
- Chrome Android: 여유 디스크의 10%, 압박 시 제거
런타임에 할당량 확인:
const { quota, usage } = await navigator.storage.estimate();
console.log(`Using ${(usage/1e9).toFixed(2)} GB of ${(quota/1e9).toFixed(2)} GB`);
중요한 저장소에 영속성 요청:
const persisted = await navigator.storage.persist();
브라우저가 영속 스토리지를 부여했다면 true를 반환합니다. 압박 시에도 제거하지 않겠다는 의미입니다. Chrome은 충분히 참여한 사이트에 자동으로 부여하고, Firefox는 사용자에게 묻습니다.
앱 셸과 오프라인을 위한 Cache API
Cache API는 Request + Response 쌍을 저장하며 Service Worker 내부에서 사용하기에 적합합니다:
const cache = await caches.open('shell-v7');
await cache.addAll([
'/', '/app.js', '/app.css', '/icons/192.png'
]);
인터셉트 시 검색:
self.addEventListener('fetch', (e) => {
e.respondWith(caches.match(e.request).then(r => r ?? fetch(e.request)));
});
암호화된 파일 바이트는 Cache API에 넣지 마세요. 2 GB 응답 객체는 iOS Safari의 할당량을 단번에 초과하고, 이후 범위 요청이 불가능합니다. 바이트는 OPFS나 File System Access를 통해 직접 디스크로 보내세요.
제거와 데이터 손실 우아하게 처리
비영속 스토리지는 제거될 수 있습니다. 반드시 계획해야 합니다. iOS Safari는 할당량과 무관하게 7일 미사용 시 제거합니다. Chrome은 디스크가 실제로 압박받을 때만 제거합니다. Firefox는 할당량 풀이 차면 가장 오래 사용하지 않은 오리진을 제거합니다.
두 가지 방어 패턴:
- 재생성할 수 없는 상태(업로드 세션 ID, 부분 청크 오프셋)는 재로드 친화적 형식으로 기록해서 새 페이지 로드 시 서버에서 다시 가져와 계속 진행할 수 있게 하세요.
- 오래 유지해야 하는 상태는
navigator.storage.persist()를 요청하고 브라우저가 프롬프트를 표시할 때 사용자가 확인할 수 있는 UI를 제공하세요.
잃어버릴 수 없는 데이터는 서버를 진실의 원천으로 유지하세요. 브라우저 스토리지는 하룻밤 사이에 사라질 수 있는 빠른 캐시로 취급하세요.
브라우저 지원 함정
세 가지 함정이 반복해서 등장합니다:
indexedDB.databases()열거는 Firefox에서 지원하지 않습니다. "닫을 때 쿠키 삭제"를 설정한 사용자는 이벤트 발생 없이 모든 IndexedDB 콘텐츠를 잃습니다.FileSystemFileHandle.queryPermission()은 재로드 후 동작이 달라집니다. 권한이 부여되었을 때도'prompt'를 반환하기도 합니다. 방어적으로 항상requestPermission()을 호출하세요.- 프라이빗/시크릿 모드는 세 API 모두에 별도의 더 작은 세션 전용 할당량을 제공합니다. 일반 브라우징에서 작동하는 코드가 프라이빗 창에서는 즉시
QuotaExceededError를 발생시킬 수 있습니다.
HexaTransfer는 세션 상태에 IndexedDB, 스트리밍 암호화 중 암호문 버퍼링에 OPFS, 지원 브라우저에서의 10 GB 복호화 다운로드에 File System Access API를 사용합니다. hexatransfer.com에서 사용해보세요 — 무료, 계정 불필요, 최대 10 GB.
앱을 위한 스택 선택
대부분의 전송 앱에 적합한 조합: 메타데이터에는 IndexedDB 위의 idb 래퍼, 암호화/복호화 임시 공간에는 OPFS sync access handle, Service Worker 내부 앱 셸에는 Cache API, 최종 다운로드에는 StreamSaver 폴백이 포함된 File System Access API, 온보딩 중 navigator.storage.persist() 호출. 현재 출시된 모든 브라우저를 커버하고, 모바일 할당량 이내를 유지하며, 데이터 제거 시 우아하게 복구합니다. 각 API 주변에 작은 어댑터를 구축해두면, OPFS에 새 메서드가 생기거나 Safari의 할당량이 늘어나도 파일 하나만 수정하고 배포할 수 있습니다.
엔드투엔드 암호화로 대용량 파일을 안전하게 전송
엔드투엔드 암호화로 최대 10GB의 파일을 무료로 전송하세요. 계정이 필요하지 않습니다. 업로드 전에 브라우저에서 파일이 암호화되어 다른 사람은 읽을 수 없습니다.
파일 보내기