You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python中TTLCache缓存访问模式Bug排查:为何触发KeyError?

TTLCache存入后触发KeyError的成因及解决办法

问题背景

以下是实现的TTL缓存代码片段,用于缓存数据库查询结果:

from cachetools import TTLCache

cache = TTLCache(maxsize=SOME_SIZE, ttl=SOME_TTL)

def fetch(key):
  if key not in cache:
    result = database.get_result(key)
    cache[key] = result
  result = cache[key]
  return result

设置TTL为5分钟,但部分边缘场景下会触发KeyError,表现为缓存刚存入就过期。

核心成因分析

  • 多线程/进程下的竞态条件:当多个线程同时执行fetch时,会出现时序冲突:

    1. 线程A检测到key不在缓存,开始执行慢查询
    2. 线程B同样检测到key不在缓存,也启动查询
    3. 线程A先完成查询并将结果存入缓存,此时缓存项开始计时
    4. 如果线程B的查询耗时超过TTL(极端慢查询场景),当它存入结果时,线程A存入的缓存项已过期被清理;或者线程A存入后,在执行result = cache[key]前,该缓存项因其他线程的操作被清理,最终触发KeyError。
  • 系统时间跳变:如果服务器系统时间被向前大幅调整(比如NTP同步修正、手动调时),会直接导致刚存入的缓存项过期时间提前。例如存入时系统时间为10:00,TTL5分钟,过期时间10:05;若系统时间被调到10:06,该缓存项会立刻被判定为过期,后续访问就会触发KeyError。

  • 缓存满溢的LRU清理:如果maxsize设置过小,当缓存达到上限时,TTLCache会按LRU规则清理旧项。若存入新项后,立刻有大量其他新项存入,刚存入的项可能被判定为“最近最少使用”而被清理,但这种情况概率极低,不是“刚存入就过期”的主要原因。

  • 垃圾回收的影响:Python GC不会直接清理TTLCache中的缓存项,因为缓存内部维护着对项的强引用。除非缓存项本身是弱引用类型,但你的代码中存入的是查询结果,只要被缓存引用,GC就不会回收它,因此GC不是直接诱因。

修复方案

  • 原子化缓存操作:使用TTLCache的get方法替代in判断,避免竞态条件:
    def fetch(key):
        result = cache.get(key)
        if result is None:
            result = database.get_result(key)
            cache[key] = result
        return result
    
    更推荐使用cachetools内置的cached装饰器,它已封装了竞态处理和过期逻辑:
    from cachetools import cached, TTLCache
    
    cache = TTLCache(maxsize=SOME_SIZE, ttl=SOME_TTL)
    
    @cached(cache)
    def fetch(key):
        return database.get_result(key)
    
  • 检查系统时间稳定性:确保服务器时间同步正常,避免大幅向前跳变;
  • 合理设置maxsize:根据实际访问量调整缓存上限,减少频繁LRU清理的概率。

内容的提问来源于stack exchange,提问作者Jill Stein

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 18:52:55