1051 字
3 分钟
中后台系统重构实践:IndexedDB 数据版本门禁缓存
这篇文章聚焦一件事:
在中后台实时场景里,如何把 IndexedDB 从“简单本地存储”升级成“带版本门禁的缓存层”。
核心结论先给出来:
- 用
id + key + list建立版本门禁缓存协议 - 前端先比版本,命中读本地,未命中回源并更新
- 通过该机制把高频字典请求拦截在本地,提升首屏与切页体验
背景:为什么要重构这层
中后台的实时页面通常有两个冲突:
- 一边是高频事件流(通话、转写、告警、状态变更)
- 一边是频繁页面切换(详情、审核、工单、报表)
只靠内存态会丢上下文;全塞进重型全局状态又会导致耦合高、排障难。
所以我在重构里先把“历史/配置数据层”独立出来,专门由 IndexedDB 承接。
方案全景
flowchart LR
API[后端接口] --> VER[获取当前版本 key]
VER --> HIT{本地 key 是否命中?}
HIT -->|是| IDB[直接返回 IndexedDB list]
HIT -->|否| NET[请求最新数据]
NET --> SAVE[写回 IndexedDB: id/key/list]
SAVE --> IDB
IDB --> UI[页面渲染]
这张图对应的是“版本门禁 + 读穿缓存”单主线:
- 读取优先本地命中
- 版本不一致才回源
- 回源结果立即写回本地,供后续命中
一、IndexedDB:从“缓存”升级为“版本门禁缓存”
项目里的 IDB 封装很薄(openDB / put / cursor / delete),但关键不在 API 包装,而在 缓存协议。
1) 缓存记录结构
对字典类数据,统一按这三个字段存:
id:数据集标识(例如某个下拉选项集合)key:版本键(后端版本号或前端失效键)list:实际数据
也就是你说的这套机制:
- 先拿“当前数据版本”
- 读本地记录并比较
record.key === currentKey - 命中就直接用本地;不命中就请求后端并更新本地
这其实是标准的 read-through cache(读穿缓存),区别在于它是前端本地版本门禁。
2) key 的来源策略
key 不一定只能是后端 dataVersion,但优先级建议如下:
- 后端版本号 / etag(最优):语义清晰,可控
- 业务批次号:配置变更时手动切版本
- 时间键(如 YYYY-MM-DD):适合日更字典兜底
经验上,后端有明确版本号时,前端逻辑会简单非常多:
“比较版本 -> 决定是否回源”,几乎不需要猜测过期条件。
3) 实时数据仓与索引
转写类数据仓除了主键,还要建 callId 索引。
否则回放或按通话聚合时会退化成全量扫描,数据量一上来就卡。
在实时写入侧我只持久化“稳定片段”(结束态),而不是每个中间增量,原因很直接:
- 降低写放大
- 避免历史回放出现大量中间脏版本
4) 这层的工程边界
当前这类原生 IDB 封装常见短板也要正视:
- schema 升级缺少统一迁移函数
- 读事务误用
readwrite - 缺少 TTL/容量治理
所以重构不是“能存就完事”,而是至少要补上:
- 版本迁移策略
- 容量裁剪策略
- 错误可观测(命中率、回源率、失败率)
二、这次重构最有价值的点
从结果看,这套方案的收益不是“技术栈更新”,而是数据链路更可控:
- 页面切换后不再频繁丢上下文
- 高频字典请求被版本门禁拦住
- 实时响应和历史回放职责清晰
它不追求最炫,而是追求“复杂业务里长期稳定”。
三、后续演进建议
- 给 IDB 增加统一 schema migration
- 给版本缓存增加命中率埋点
- 回放查询按
callId分段分页,避免全量游标
如果再做一次同类重构,我依然会优先做这层版本缓存:
先把“本地命中与回源边界”定义清楚,后续性能与稳定性会容易很多。
中后台系统重构实践:IndexedDB 数据版本门禁缓存
https://kongdf.com/posts/learning/projects/crm-indexeddb-light-subscribe/ 部分信息可能已经过时