我做的那个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存会话元信息,messages按conversationId建索引存每条消息。新增一条消息就是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,我只操心前端这摊存储和渲染——能把精力收回到自己最该打磨的体验层,挺好。