IndexedDB 实战:浏览器端大文件存储与离线架构

技术架构

为什么需要 IndexedDB?

浏览器存储方案各有局限:

方案 容量 数据类型 同步 API 适用场景
Cookie ~4KB 字符串 会话标识
localStorage ~5MB 字符串 简单配置
sessionStorage ~5MB 字符串 临时状态
IndexedDB 数百 MB~GB 任意结构化数据 ❌ 异步 大文件、离线应用

对于需要在浏览器中缓存 PDF 处理中间结果、WASM 模块、或用户上传历史记录的工具站,IndexedDB 是唯一可行的选择。


核心概念

数据库 → 对象存储 → 记录

Database: "toolsku-cache"
  ├── ObjectStore: "wasm-modules"     (key: moduleName)
  ├── ObjectStore: "file-cache"       (key: fileHash, index: timestamp)
  └── ObjectStore: "user-preferences" (key: settingKey)

事务与并发

IndexedDB 的事务是自动提交的,且同一时刻每个 ObjectStore 只能有一个读写事务:

const tx = db.transaction(['file-cache'], 'readwrite');
const store = tx.objectStore('file-cache');
await store.put({ hash: 'abc123', data: arrayBuffer, timestamp: Date.now() });
await tx.done; // 等待事务完成

工具库中的 IndexedDB 应用

WASM 模块缓存

ffmpeg.wasm 约 30MB,每次加载耗时 3-5 秒。缓存到 IndexedDB 后,二次访问几乎零延迟:

async function getWasmModule(name: string): Promise<ArrayBuffer> {
  const cached = await idb.get('wasm-modules', name);
  if (cached) return cached;

  const response = await fetch(`/wasm/${name}.wasm`);
  const buffer = await response.arrayBuffer();
  await idb.put('wasm-modules', buffer, name);
  return buffer;
}

大文件分块存储

超过 50MB 的文件不适合一次性存入 IndexedDB。分块策略:

文件 (200MB) → 切分为 4MB chunks → 分别存入 IndexedDB
key: "file:{hash}:chunk:{index}"
读取时按序拼接 → 还原完整文件

版本迁移

数据库 schema 变更时需要 onupgradeneeded 处理迁移:

const request = indexedDB.open('toolsku-cache', 2); // 版本号 2

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  if (event.oldVersion < 2) {
    db.createObjectStore('processing-history', { keyPath: 'id' });
  }
};

最佳实践:只增不减 ObjectStore,通过版本号递增触发迁移,避免删除用户数据。


IndexedDB vs OPFS

特性 IndexedDB OPFS (Origin Private File System)
API 成熟度 广泛支持 Chrome 86+ / Safari 15.2+
读写模式 键值存储 真正的文件系统
性能 中等 更高(尤其同步访问)
适用 结构化缓存 大文件读写

工具库目前以 IndexedDB 为主,OPFS 作为 Chrome 上的性能优化备选。



游标操作与索引优化

IndexedDB 支持通过游标(Cursor)遍历存储记录,按索引排序访问:

// 按时间戳倒序遍历文件缓存
const index = store.index('timestamp');
const range = IDBKeyRange.lowerBound(Date.now() - 7 * 24 * 3600 * 1000);
let count = 0;

index.openCursor(range, 'prev').onsuccess = (event) => {
  const cursor = event.target.result;
  if (cursor) {
    if (count++ >= 10) return; // 只取前10条
    console.log(cursor.key, cursor.value);
    cursor.continue();
  }
};

索引最佳实践

  • 按访问频率建立复合索引 [name, timestamp],避免全表扫描
  • 对频繁范围查询的字段(如日期、文件大小)单独建索引
  • 使用 IDBKeyRange 精确控制查询范围,减少遍历开销

Blob 大文件存储模式

IndexedDB 可以直接存储 BlobArrayBuffer,无需 base64 编码(编码浪费 33% 空间):

// ✅ 推荐:直接存储 Blob
const blob = new Blob([chunkData], { type: 'application/octet-stream' });
await store.put(blob, `chunk:${fileHash}:${index}`);

// ❌ 不推荐:先转 base64
const base64 = arrayBufferToBase64(chunkData);
await store.put({ data: base64 }, ...);

存储估算:Chromium 的 IndexedDB 默认最大存储量约为磁盘可用空间的 60%,单条记录大小理论上没有硬限制,但建议控制在 100MB 以内以保证交互流畅。


LRU 淘汰与存储管理

当存储空间不足时,需要自动清理旧数据:

async function evictOldest(maxSize: number): Promise<void> {
  const tx = db.transaction('file-cache', 'readwrite');
  const store = tx.objectStore('file-cache');
  const index = store.index('timestamp');

  let totalSize = await estimateSize();
  const cursorReq = index.openCursor(null, 'next'); // 从最旧的开始

  cursorReq.onsuccess = async (event) => {
    const cursor = event.target.result;
    if (cursor && totalSize > maxSize) {
      totalSize -= cursor.value.size;
      cursor.delete();
      cursor.continue();
    }
  };
}

工具库采用 LRU + 文件大小限制 的双重策略:缓存最多保留 500MB,同时单文件上限 100MB,超出自动淘汰最旧的条目。


迁移测试与回滚

IndexedDB 版本迁移在生产中是最容易出错的环节。推荐模式:

const migrations: Record<number, (db: IDBDatabase) => void> = {
  1: (db) => { /* 初始 schema */ },
  2: (db) => { db.createObjectStore('tasks', { keyPath: 'id' }); },
  3: (db) => { db.deleteObjectStore('deprecated_store'); },
};

function applyMigrations(db: IDBDatabase, oldVersion: number, newVersion: number) {
  for (let v = oldVersion + 1; v <= newVersion; v++) {
    if (migrations[v]) migrations[v](db);
  }
}

测试检查清单

  • 模拟从每个旧版本逐步升级到最新版本
  • 确保迁移过程中不会丢失用户已有的缓存数据
  • 在浏览器 DevTools → Application → IndexedDB 中手动验证 schema 变更

总结

IndexedDB 是构建离线优先浏览器工具的关键基础设施。合理设计 ObjectStore、利用 WASM 缓存、实施分块存储策略,配合游标索引、Blob 直接存储和 LRU 淘汰机制,可以让工具库在弱网环境下仍提供流畅体验。

本站提供浏览器本地工具,免注册即可试用 →

#IndexedDB#浏览器存储#离线#大文件#架构