对话历史本地存:localStorage换IndexedDB

3 阅读2分钟

我做的那个AI助手,对话历史一开始全扔localStorage。用了俩月,用户开始反馈「打开就卡半秒」「历史莫名少了几条」。我去翻存储,乐了——localStorage里塞了快4MB的对话JSON。

这篇讲一下我从localStorage搬到IndexedDB的过程,以及中间那些值得记的取舍。聚焦存储选型本身,不展开离线那一摊事。

localStorage为啥撑不住

它的几个硬伤,对「AI对话历史」这种数据特别致命:

  • 容量小,一般5MB封顶。AI对话动辄长文本,几十轮就逼近上限。
  • 同步API。读写都阻塞主线程,4MB的JSON一次JSON.parse,那半秒卡顿就来自这儿。
  • 只能存字符串。每次存取都要序列化/反序列化整坨数据,哪怕只改最后一条消息,也得把全部历史重写一遍。

最后这条是压垮我的:用户每发一句话,我就把整个对话列表JSON.stringify再写回去,O(n)的写入,对话越长越慢。

IndexedDB解决了什么

搬过去之后,对应地:

  • 容量大得多,浏览器一般给到几百MB甚至按磁盘比例分配。
  • 异步API,读写不卡主线程。
  • 结构化存储,能按对话ID、消息ID建索引,单独读写某一条,不用整坨搬。

我把数据建模成两个对象仓库:conversations存会话元信息,messagesconversationId建索引存每条消息。新增一条消息就是add一条记录,不再重写全量。

const tx = db.transaction('messages', 'readwrite');
tx.objectStore('messages').add({
  id: crypto.randomUUID(),
  conversationId,
  role, content, ts: Date.now(),
});

但IndexedDB也不是白嫖

它的API啰嗦,事务、游标、版本升级,裸写很难受。我引了个薄封装(idb那种Promise包装),不然回调地狱劝退。这是搬迁的隐性成本,得算进去。

还有个坑:IndexedDB的schema升级靠onupgradeneeded,我第一版索引没建对,第二版加索引时得写迁移逻辑,处理老用户已有的数据。这块比localStorage「改个key就完事」麻烦不少。

我的选型结论

不是所有东西都该上IndexedDB。我现在是这么分的:

  • 轻量、读多写少、能容忍丢——比如UI偏好、主题、上次选的模型——继续放localStorage,简单够用。
  • 量大、频繁增量写、要查询——比如对话历史、草稿、缓存的AI返回——IndexedDB。

对话历史这种「只增、量大、要按会话查」的数据,IndexedDB几乎是唯一合理解。

顺一句,我这个助手的AI能力是在讯飞这类MaaS平台上零代码搭的,平台管模型和prompt,我只操心前端这摊存储和渲染——能把精力收回到自己最该打磨的体验层,挺好。