Vocabulary cache staging file: pid-based name collides across containers, and leaks when the rename fails

这个报错通常出现在多个 InvokeAI 实例共用同一个 db_dir (例如同一卷上的两个容器,或 NFS 目录被多台主机挂载)并同时构建 cluster vocabulary 缓存时。缓存写入的原子替换使用了以 os.getpid() 命名的暂存文件,PID 在不同容器/主机间并不唯一,可能互相

快速结论:这个报错通常出现在多个 InvokeAI 实例共用同一个 db_dir(例如同一卷上的两个容器,或 NFS 目录被多台主机挂载)并同时构建 cluster vocabulary 缓存时。缓存写入的原子替换使用了以 os.getpid() 命名的暂存文件,PID 在不同容器/主机间并不唯一,可能互相覆盖;同时 os.replace 失败时暂存文件不会被清理,导致约 5 MB 的 .tmp.npz 每轮残留。应优先排查共用目录的多实例并发和替换失败(Windows 下表现为 PermissionError)。

适用环境:Issue 中确认的场景为 InvokeAI 使用 invokeai/app/services/image_index/image_index_default.py 中的 _build_vocab_embeddings 写 vocabulary 缓存;复现环境为 Python 3.12、Windows(测试中出现真实的 WinError 32)。Issue 未确认具体 InvokeAI 版本、Linux/NFS/容器/GPU 上的实际复现结果,这些场景属于推断。

最快修复方案:暂无确认的一步修复方案。上游主分支尚未看到该 Issue 的合入修复;Issue 中验证过的方向是在目标缓存同目录用 tempfile.mkstemp(dir=cache_path.parent, suffix=".npz")(或文件名追加 uuid4().hex)创建独占暂存文件,写入后关闭句柄再 os.replace,并在 finally 中仅 unlink 自己的暂存路径。在修复合入前,可优先尝试避免多个实例同时写同一 db_dir,或降低并发首次建缓存的概率。

注意事项:重启或下一次加载缓存时,np.load 失败会触发“Discarding unreadable cluster vocabulary cache”并重建,因此损坏缓存通常能自愈,但会付出一次完整重新嵌入的代价。残留的暂存文件不会被自动回收,需要手动清理;请只删除匹配 <cache>.<pid>.tmp.npz 的文件,不要动正式缓存。Issue 提到 mkstemp 保留仅属主可读写的 POSIX 权限,若不同 UID 的实例共享同一可写目录,可能导致缓存无法复用,这一点尚未被上游确认如何处理。另需注意:该方案本身没有 fsync,断电场景下的持久性仍有局限。

问题场景

在 InvokeAI 中构建 cluster vocabulary 缓存(vocabulary cache)时,_build_vocab_embeddings 会先把嵌入结果写到名为 {cache_path.name}.{os.getpid()}.tmp.npz 的暂存文件,再用 os.replace 原子替换为正式缓存。当两个进程共享同一个 db_dir——例如同一卷上的两个容器(各自应用都是 PID 1),或两台主机挂载同一 NFS 数据库目录——它们可能算出完全相同的暂存路径并同时写入,导致其中一个进程发布出损坏的缓存;而 os.replace 一旦失败(如 Windows 上另一实例仍持有 np.load 的文件句柄),暂存文件会残留,缓存始终无法持久化。

报错原文

Vocabulary cache staging file: pid-based name collides across containers, and leaks when the rename fails

B published: 5204752 bytes
A's replace -> FileNotFoundError   (swallowed by the handler)
*** PUBLISHED CACHE IS UNREADABLE -> BadZipFile: Bad CRC-32 for file 'embeddings.npy'

原因分析

最可能的原因是暂存文件名只用 os.getpid() 区分进程,而 PID 不是全局唯一的。两个容器或两台主机各自命名空间里的相同 PID 会得到同一个暂存路径:后写入者截断并替换该文件,先写入者剩余的写入落入已被发布的缓存中,其自身的 os.replace 因暂存路径已不存在而抛出 FileNotFoundError,最终发布的 NPZ 不可读。此外,重命名失败路径没有 try/finally 去 unlink 暂存文件,每次运行都会泄漏一个约 5 MB 的临时文件。

Windows 多实例场景下,一个实例仍在 with np.load(cache_path) 中持有句柄,另一个实例对同一路径调用 os.replace,Python 打开文件时不带 FILE_SHARE_DELETE,于是抛出 PermissionError,复现“缓存永不持久化、每轮一个孤儿文件”的症状。另据 Issue 分析,暂存与目标同目录,因此 EXDEV 不可达;缺少 fsync 时断电可能导致重命名先于数据落盘,发布空或残缺归档。

环境排查

  • 确认是否多个 InvokeAI 实例(容器、进程或不同主机)共用同一个 db_dir 或 NFS 数据库目录。
  • 若为容器部署,确认各容器的 PID 命名空间与应用进程 PID 是否可能相同。
  • Windows 下检查是否有其他实例正通过 np.load 持有缓存文件句柄。
  • 确认 InvokeAI 版本是否包含 Issue 所引用的 _build_vocab_embeddings 暂存写入逻辑;Issue 复现基于 main 分支提交 0ff4a63f0b0aa1ad9a162a17d0c7e62eaece2112。
  • Issue 的验证测试环境为 Python 3.12、Windows、CPU/test 环境;未验证 Linux、NFS、容器与 GPU 部署。

解决步骤

  1. 先在 db_dir 中查找残留的 *.tmp.npz 暂存文件,确认其大小与数量(Issue 中单文件约 5.1 MB);这些文件是惰性的,可手动清理,但不要删除正式缓存文件。
  2. 检查并消除多实例同时首次写缓存的冲突:尽量不要让两个容器或主机同时在同一 db_dir 上执行首次 vocabulary 缓存构建(可优先尝试,Issue 未给出固定操作流程)。
  3. Windows 多实例场景下,避免一个实例写入替换缓存时另一个实例仍持有该缓存的 np.load 句柄;可先停止其他实例,再让单实例完成构建。
  4. 如需代码层面修复,按 Issue 验证过的方式修改写入逻辑:用 tempfile.mkstemp(dir=cache_path.parent, suffix=".npz") 或文件名追加 uuid4().hex 生成独占暂存名,向句柄序列化后先关闭句柄再 os.replace(Windows 需要),并在 finally 中 unlink 该暂存路径。
  5. 修改后确认缓存写入仍为 best-effort,不改变现有缓存内容、归档/指纹格式与内存中的可用性;现有缓存的清理、旧孤儿文件扫描和断电持久性不在该修复范围内。
  6. 如果共享目录中不同 UID 的实例依赖缓存复用,先与维护者确认 mkstemp 的仅属主权限是否可接受,不要擅自放宽文件权限。

验证方法

可在同一 db_dir 下并发触发两个实例的 vocabulary 缓存构建,确认不再出现 BadZipFile: Bad CRC-32,正式缓存可正常 np.load;同时检查目录中不再残留 <cache>.<pid>.tmp.npz。Issue 中对应的回归测试覆盖了两个写入者重叠、序列化/发布失败与首次/替换构建的组合,并在 Python 3.12 Windows 环境执行 pytest tests/app/services/image_index/test_image_index_service.py -k 'overlapping_vocabulary_writers or failed_vocabulary_publication or cached_vocabulary or cache_reaches_disk or failed_cache_write' -q -n 0 得到 8 passed,整个 tests/app/services/image_index 目录得到 235 passed。该结果来自 Issue 作者的 fork 验证,不代表上游已合入。

参考来源

invoke-ai/InvokeAI #9624

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 31625

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注