Prhub

#29716 feat(mem_cache): add client-side metadata cache for HiCacheFile storage

原始 PR 作者 tyuchn 合并时间 2026-07-08 09:21 文件变更 4 提交数 3 评论 2 代码增减 +248 / -8

执行摘要

为 HiCacheFile 添加客户端元数据缓存

PR #29716 旨在解决多层级卸载中使用文件后端时的文件系统元数据瓶颈(PR body)。在 Lustre 等共享文件系统上,大量缓存文件导致 os.scandir 和 os.path.exists 调用阻塞事件循环,引入调度延迟。通过客户端内存正缓存直接避免这些文件系统调用。

推荐阅读此 PR 的设计决策:如何通过回调将缓存失效与 LRU 驱逐器同步,以及 TTL 处理策略。测试覆盖完整,可作为模块级缓存的实现模板。

讨论亮点

此 PR 的 review 没有产生实质性讨论;xiezhq-hermann 直接批准。PR body 和代码变更说明清晰,无争议。

实现拆解

  1. 新增 MetadataCache 类hicache_storage.py):线程安全的 TTL 缓存,存储 key 到时间戳映射。contains() 惰性移除过期项,支持 -1 无限 TTL。
  2. 环境变量配置environ.py):新增 SGLANG_HICACHE_FILE_BACKEND_ENABLE_METADATA_CACHE(默认 False)和 SGLANG_HICACHE_FILE_BACKEND_METADATA_TTL(默认 5.0 秒),可通过 extra_config 覆盖。
  3. 集成到 HiCacheFilehicache_storage.py):根据配置创建 MetadataCache 实例,启用时调用 _scan_existing_files_to_metadata_cache() 预填充,并将 remove 回调传递给 LRUFileEvictor。
  4. LRU 驱逐回调lru_file_evictor.py):新增 on_evict 参数,文件驱逐后调用确保缓存与磁盘一致。
  5. 配套测试test_hicache_file_lru_unit.py):新增 TestMetadataCacheTestHiCacheFileMetadataIntegration,35 个测试覆盖基本功能、TTL、集成场景。
文件 模块 状态 重要度
python/sglang/srt/mem_cache/hicache_storage.py 缓存层 modified 8.57
test/registered/unit/mem_cache/test_hicache_file_lru_unit.py 测试 modified 7.5
python/sglang/srt/mem_cache/storage/file/lru_file_evictor.py 存储层 modified 6.01
python/sglang/srt/environ.py 配置管理 modified 5.11

关键符号

MetadataCache.__init__ MetadataCache.add MetadataCache.remove MetadataCache.contains MetadataCache.clear HiCacheFile._scan_existing_files_to_metadata_cache

关键源码片段

python/sglang/srt/mem_cache/hicache_storage.py dependency-wiring

核心变更:实现 MetadataCache 类并集成到 HiCacheFile 中,包括初始化、预扫描和回调传递。

# MetadataCache 是一个线程安全的、基于 TTL 的正缓存,
# 用于缓存文件系统中已存在的文件键。
# 它只缓存“存在”的正向信息,避免重复的 os.path.exists 或 os.scandir 调用。
class MetadataCache:
    def __init__(self, ttl_seconds: float):
        self.ttl_seconds = ttl_seconds # TTL 秒数,-1 表示无限
        # cache 映射:key -> 添加时的单调时间戳
        self.cache: dict[str, float] = {}
        self.lock = threading.Lock()
​
    def add(self, key: str):
        """添加一个键到缓存(如果尚未存在)。"""
        with self.lock:
            if key not in self.cache:
                self.cache[key] = time.monotonic()
​
    def remove(self, key: str):
        """从缓存中移除一个键(若不存在则忽略)。"""
        with self.lock:
            self.cache.pop(key, None)
​
    def contains(self, key: str) -> bool:
        """
        检查键是否在缓存中且未过期。
        如果 TTL 为 -1,则永不过期。
        否则,在检查时如果发现过期则惰性移除并返回 False。
        """
        with self.lock:
            if key not in self.cache:
                return False
            if self.ttl_seconds == -1.0:
                return True
            if time.monotonic() - self.cache[key] > self.ttl_seconds:
                # 过期,惰性移除并返回 False
                del self.cache[key]
                return False
            return True
​
    def clear(self):
        """清空整个缓存。"""
        with self.lock:
            self.cache.clear()
test/registered/unit/mem_cache/test_hicache_file_lru_unit.py test-coverage

配套测试:新增 MetadataCache 单元测试和 HiCacheFile 集成测试,覆盖基本功能、TTL、启动扫描、写后回填等。

class TestMetadataCache(CustomTestCase):
    def test_metadata_cache_basic(self):
        """测试基本的 add / contains / remove 流程。"""
        cache = MetadataCache(ttl_seconds=1.0)
        cache.add("k1")
        self.assertTrue(cache.contains("k1"))
        self.assertFalse(cache.contains("k2"))
        cache.remove("k1")
        self.assertFalse(cache.contains("k1"))
​
    def test_metadata_cache_ttl(self):
        """测试 TTL 过期:等待 TTL 后键应被移除。"""
        cache = MetadataCache(ttl_seconds=0.1)
        cache.add("k1")
        self.assertTrue(cache.contains("k1"))
        time.sleep(0.2) # 等待超过 TTL
        self.assertFalse(cache.contains("k1"))
​
    def test_metadata_cache_hard_ttl(self):
        """
        测试硬 TTL:再次 add 不会重置过期时间,
        过期时间基于第一次 add 的时间戳。
        """
        cache = MetadataCache(ttl_seconds=0.3)
        cache.add("k1")
        time.sleep(0.15)
        # 再次添加同一个 key,不应延长 TTL
        cache.add("k1")
        # 从第一次添加至今已 0.35s,因此应过期
        time.sleep(0.2)
        self.assertFalse(cache.contains("k1"))
​
    def test_metadata_cache_infinite_ttl(self):
        """测试无限 TTL(ttl_seconds=-1):永远不会过期。"""
        cache = MetadataCache(ttl_seconds=-1.0)
        cache.add("k1")
        time.sleep(0.3)
        self.assertTrue(cache.contains("k1")) # 仍然存在

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  1. 缓存一致性风险:正缓存可能返回已过期条目;外部删除文件后缓存仍认为存在,直到 TTL 到期。默认 5 秒 TTL 可缓解。
  2. 默认关闭ENABLE_METADATA_CACHE 默认 False,现有用户无影响。
  3. 线程安全:MetadataCache 使用 Lock,需确保不在事件循环中长时间阻塞。
  4. 回调异常传播:若 on_evict 抛出异常,可能导致驱逐失败;当前调用简单 remove 风险低。
  5. 启动扫描性能:大量文件时初始化遍历目录可能增加启动时间,但不影响 TFFT。

对用户:在共享文件系统上启用后可见显著性能提升(P99 TTFT -22%)。新增两个环境变量。对系统:248 行新增,测试覆盖完整。对团队:设计清晰,维护成本低。对其他模块:无影响,可选特性。

缓存一致性依赖 TTL 默认关闭无回归风险 线程安全但需注意阻塞

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论