缓存复仇记:挡在我完美缓存前面的,是那个更傻的缓存
内容提要
作者在博客缓存优化中遇到问题:修改文章后页面迟迟不更新。排查发现,Apollo的InMemoryCache在ssrMode下将network-only策略静默转为cache-first,导致请求被内存缓存拦截,LruLink缓存层形同虚设。改用no-cache策略后修复,并补充回归测试防止复发。
延伸解读
ssrMode 下的隐藏策略转换
Apollo 在 ssrMode: true 时,会静默将 network-only 和 cache-and-network 强制转为 cache-first,这是 prioritizeCacheValues 机制。这意味着即使显式指定 network-only,也无法绕过 InMemoryCache。开发者需注意,ssrMode 下 no-cache 是唯一能真正穿透到自定义缓存层的策略。
分层排查法定位缓存问题
作者通过五步分层排除法,从数据源、代理层、LruLink、独立进程到重启进程,逐步缩小范围,最终锁定问题在进程内缓存。这种 bisection 方法对排查多层缓存问题非常有效,值得借鉴。
单例 ApolloClient 的取舍
官方建议 SSR 时每个请求新建 ApolloClient 实例,以防缓存数据串用。但作者采用单例 + no-cache + LruLink 隔离方案,通过缓存 key 带用户哈希、TTL 和 LRU 容量限制来规避风险。这种定制方案在保证安全的同时,还保留了跨请求缓存的性能优势。
Q&A
为什么修改文章后页面迟迟不更新?
因为Apollo的InMemoryCache在ssrMode下将network-only策略静默转为cache-first,导致请求被内存缓存拦截,LruLink缓存层无法执行,所以页面一直显示旧内容。
Apollo的ssrMode对fetchPolicy有什么影响?
在ssrMode下,Apollo会优先使用缓存值,将network-only和cache-and-network策略静默转换为cache-first,导致这些策略无法真正绕过缓存。
为什么no-cache是唯一能穿透Apollo缓存的策略?
因为Apollo的缓存优先转换只处理network-only和cache-and-network,不处理no-cache,所以no-cache能真正绕过InMemoryCache,让请求到达LruLink。
如何排查缓存层问题?
采用分层排除法,依次测试数据源、代理层、LruLink层、独立进程和重启进程,逐步缩小范围,最终定位到InMemoryCache。
为什么transition:persist会导致正文不更新?
因为transition:persist会保留旧DOM元素,而LayoutShell包裹着页面内容,导致内容也被保留,新内容无法替换。
为什么选择Astro原生ClientRouter而不是swup?
因为swup是框架无关的DOM替换工具,不识别React,而Astro原生ClientRouter能正确处理React组件树,适合全站内容都在一个React组件中的架构。
如何防止缓存问题复发?
补充回归测试,专门验证每次请求都到达LruLink,并故意改回cache-first确认测试能捕获问题,确保未来改动不会引入类似问题。
为什么项目使用单例ApolloClient而没有数据泄漏?
因为项目使用no-cache架空InMemoryCache,真正的缓存由LruLink管理,LruLink通过用户隔离、TTL和容量上限来避免数据串扰,所以单例是安全的。