Files
MoviePilot/docs/v3t-runtime-governance.md
T

18 KiB
Raw Blame History

MoviePilot V3t 运行时治理

状态:持续维护。本文定义 moviepilot-v3moviepilot-v3t 的产品边界、运行依赖分层、 故障恢复合同和兼容项退场门禁。具体版本以 pyproject.tomluv.lock 为准。

1. 治理目标

MoviePilot 同一版本提供两套镜像:

  • moviepilot-v3 使用标准 CPython 3.14,是默认稳定镜像和故障回滚基线。
  • moviepilot-v3t 使用 CPython 3.14 free-threaded 构建,用于获得多线程 CPU 并行能力; Rust 能力固定启用,不能在运行时关闭。

两套镜像必须来自相同源码 revision、使用相同产品版本和同一份 uv.lock。V3t 不替换默认镜像, 也不在业务代码中维护一套平行实现;解释器、原生 ABI 或插件不兼容时,应切回同版本标准 V3。 正式发布按同版本制品对验收和晋升 latest;任一变体未通过构建、扫描或发布时,本次版本不移动 两边的 latest。这不会回退已经发布的标准 V3,且避免两个 latest 指向不同源码版本。

V3t 的目标不是让所有请求都更快。它主要改善可并行的 Python CPU 热点,同时验证主程序、原生扩展 和插件生态在 free-threaded 解释器下的正确性。启动、内存、普通 API 和数据库路径不得为获得局部 并发收益而出现不可接受的退化。

2. 单一依赖事实源

pyproject.toml 中的 project.dependencies 是两套镜像共享的运行依赖,ABI 敏感依赖放在两个互斥组:

  • runtime-standard
  • runtime-free-threaded

tool.uv.conflicts 保证两个组不能同时解析。Docker 的两个构建 stage 分别固定选择对应组;源码升级、 启动恢复和插件安装后的宿主恢复由 app.runtime.dependencies.runtime_dependency_group() 根据当前 解释器的 Py_GIL_DISABLED 构建标志选择 profile。

依赖选择不得改为镜像标签判断,也不得在业务模块中按包名散落 V3/V3t 分支。Dockerfile 只选择运行 profile,依赖名称、版本和 source 语义全部由 pyproject.tomluv.lock 管理。

3. 当前原生依赖矩阵

下表中的版本用于解释当前分叉原因,不代替锁文件。版本变化后应同步更新本表的治理状态。

能力 标准 V3 V3t 当前处理与上游解除条件
Python CPython 3.14 CPython 3.14t 跟随同一 Python 3.14 patch 版本;升级后必须重新验证解释器 ABI、GIL 状态、启动与完整测试。
moviepilot-rust cp314-abi3 cp314t 同一包版本按 wheel tag 选择;两套 ABI 和 V2 使用的 cp311-abi3 必须在发布链中保持独立可用。
bcrypt 4.x 5.x V3t 使用提供 free-threaded 制品的版本;当同一稳定版本同时满足两套 ABI 与密码合同后可合并约束。
Brotli 1.2.0 wheel 同版本固定源码构建 V3t 当前只对该 profile 使用固定上游源码。上游提供可复现的稳定 cp314t wheel 后,验证导入、压缩结果、并发与 GIL,再删除 source 覆盖。
CRC 加速 crcmod-plus 2.3.1 crcmod-plus 2.3.1 两套镜像统一使用继续维护且兼容 crcmod 导入接口的实现。oss2 的陈旧元数据仍声明不再维护的 crcmod,由 uv 在解析时排除该传递依赖;宿主不在运行时映射、卸载或替插件兼容旧分发包。
lxml 6.1.2 7.0.0b1 V3t 暂用提供目标 ABI 的预发布版本,是当前最高风险项。稳定版提供 cp314t wheel 后,需通过 XML、HTML、RSS、站点解析、并发和内存验证再替换。
PostgreSQL 同步驱动 psycopg2-binary 2.x psycopg[c] 3.3.4 当前分叉同时受 ABI 与实测性能影响,不要求仅为版本统一而收敛。若上游能力或性能变化,必须重跑三方案 PostgreSQL A/B 后再决策。异步路径继续使用 asyncpg
orjson 3.12.0 wheel 同版本源码构建 V3t 使用同一锁定版本并启用 free-threaded 构建变量。上游发布覆盖 Linux amd64/arm64 的稳定 cp314t wheel 后可删除本地构建要求。
中文转换 zhconv-rs moviepilot-rust.zhconv_fast() 主程序统一经 app.foundation.text.convert(),插件统一经 SDK;不得让调用方感知后端差异。只有语义、性能、体积和 ABI 均更优时才考虑统一实现。
站点资源 cpython-314 cpython-314t 资源文件必须按解释器 ABI 独立构建和选取,不能让 V3t 复用普通 CPython 扩展,也不能影响 V2 的历史 ABI 制品。

表中差异不是全部都要消除。只有临时 source 构建、预发布依赖或第三方元数据兼容适合在上游成熟后 优先退场;已由性能与产品边界证明合理的驱动或实现选择,可以继续由 profile 集中管理。

4. 依赖检查与自愈合同

“自愈”包含两个不同的恢复边界,不能与普通依赖校验混为一谈。

4.1 启动前恢复

容器启动前先导入一组后端核心依赖。导入成功时不运行 uv 同步;导入失败时才选择可用依赖源,并对 /app 执行锁定的项目同步。同步命令必须:

  • 使用当前虚拟环境中的解释器推导 runtime profile
  • 使用 --locked,禁止启动时重新求解未锁定版本;
  • 使用 --inexact,保留共享环境中的插件额外依赖;
  • 只恢复主项目运行依赖,不安装项目本身或开发依赖;
  • 恢复后再次执行核心导入探针,仍失败则停止后端启动并保留诊断入口。

源码更新事务中的依赖同步和失败回滚使用同一 profile 选择入口。否则 V3t 在恢复时可能被普通默认组 覆盖,得到“3.14t 解释器 + 标准原生依赖”的无效组合。

4.2 插件安装后的宿主恢复

插件与主程序共享虚拟环境。插件依赖安装前后都会采集宿主健康快照,只对安装后新增的异常执行补偿:

  1. 优先使用安装前生成的主程序保护约束恢复被修改的包;
  2. 约束不可用时,按主项目 pyproject.tomluv.lock 和当前 runtime profile 恢复;
  3. 恢复完成后重新执行依赖诊断与核心能力探针;
  4. 宿主即使恢复成功,本次插件安装仍返回失败,不能把被回滚的安装报告为成功。

依赖诊断使用 uv pip check,项目在 tool.uv.exclude-dependencies 中明确排除的传递依赖不进入健康 异常集合,其余诊断按安装前后的稳定错误集合识别新增问题;这样既不会反复报告 oss2 对旧 crcmod 的陈旧元数据,也不会让既有告警遮蔽其他新增错误。核心能力 探针统一由 app.doctor.dependencies 执行。普通启动使用轻量模式验证 Web 栈、中文分词与转换; 镜像构建及插件安装前后使用完整模式,继续验证 ABI 敏感原生扩展、CRC C 实现、PostgreSQL C 实现及 导入后的 GIL 状态。插件允许的非核心依赖升级不要求与宿主锁文件逐版本相同,因此插件健康检查不得 用整份 uv.lock 强制回滚共享环境。

自愈是异常补偿,不是每次启动的常态安装流程。任何新增恢复入口都必须复用统一 profile 选择函数, 不得自己拼装默认 uv group。

5. GIL 与原生扩展可观测性

构建阶段必须导入并执行已知核心原生依赖,验证 V3t 在导入前、导入后和热点执行后均保持 GIL 关闭。 运行阶段还必须保留动态观测,因为构建探针无法穷举插件延迟导入的第三方扩展:

  • 启动收尾日志记录解释器类型和实际 GIL 状态;
  • 登录后全局设置与系统环境 API 返回 PYTHON_FREE_THREADEDPYTHON_GIL_ENABLED,系统环境 API 同时返回 RUST_ACCEL_REQUIRED
  • 插件安装或重载导致 GIL 从关闭变为开启时,日志记录对应插件归因;
  • 前端在 V3t 实际启用 GIL 时显示兼容告警,正常状态不误报。

不得通过 PYTHON_GIL=0 或等效方式强制绕过扩展的 GIL 声明。扩展一旦使进程退化为 GIL 模式, 应保留可观测性并修复或替换依赖;无法及时处理时回退标准 V3。

6. 当前 A/B 基线与决策

以下数据来自同一源码 revision、相同资源限制的 arm64 候选镜像,用于确定当前架构和依赖策略。后续 上游重放、依赖更新、构建链变化或采集 schema 扩充都会使其失去“最终发布验收”资格,但不抹去已经 建立的设计结论;新的发布候选必须重跑并在本节追加或替换相应数据。

6.1 镜像与解释器基线

指标 标准 V3 V3t 结论
arm64 镜像体积 647.5 MiB 674.0 MiB V3t 增加 26.5 MiB,约 4.1%;标准镜像仍处于既有 660+ MB 发布口径。
启动就绪中位数 6.177 s 5.608 s V3t 约快 9.2%,未造成启动退化;不据此承诺所有部署都会更快。
空载 working set 274.9 MiB 336.8 MiB V3t 增加约 22.6%,低于 25% 验收阈值。
主工作负载 PSS 296.4 MiB 356.3 MiB V3t 增加约 20.2%,属于实验镜像的明确资源成本。
32 线程纯 Python CPU 探针 13,143.7 ops/s 42,583.5 ops/s V3t 约为 3.24 倍,证明解释器并行收益;该探针不经过 Rust 开关。
32 线程直接 Rust ABI 探针 40,832.4 ops/s 61,550.9 ops/s V3t 约为 1.51 倍;两边都直接调用 Rust,仅验证原生 ABI 与并发。
普通 API 最大 p95 比例 基线 1.10x 四个本地 API 中最大退化来自订阅列表,仍低于 1.25x 门禁。

同 revision 的 amd64 最终候选也已完成交叉架构验收。标准 V3 与 V3t 的本地镜像体积分别为 680.6 MiB 和 714.0 MiBV3t 增加 33.4 MiB、约 4.9%。两者均使用 Python 3.14.7,标准 V3 保持 GIL 启用;V3t 使用 free-threaded 解释器,完整原生依赖、psycopg C 实现、Rust 文本能力及 sites.cpython-314t-x86_64-linux-gnu.so 依次加载后 GIL 始终关闭。两个镜像均以空白配置启动到 /health/ready 且 Docker health 为 healthy。该启动运行在 arm64 宿主的 amd64 模拟环境中,只作为 发布候选功能与 ABI 验收,不纳入性能比例。

该候选还通过了四分片全量回归(5,981 passed、3 skipped、14 subtests passed)、PostgreSQL 18.6 迁移/提交/回滚、现代插件清单、历史 requirements.txt、插件源码与依赖恢复以及恢复后单实例加载。 这些结果证明双 profile 方案可行;最终发布仍必须使用最新 revision 和正式不可变 digest 重跑。

6.2 应用热点三组

真正受产品 Rust 开关影响的应用识别热点必须分成三个对象:

运行方式 解释器 应用实现 产品语义
V3 + Python 标准 CPython Python fallback 标准镜像关闭 RUST_ACCEL 的兼容基线。
V3 + Rust 标准 CPython moviepilot-rust 标准镜像启用 RUST_ACCEL 的默认性能路径。
V3t + Rust free-threaded CPython moviepilot-rust V3t 固定路径,Rust 不允许关闭。

当前六个交替样本的中位耗时分别为 0.068769s0.035339s0.035511s,三组业务结果校验和 一致。标准 V3 启用 Rust 后耗时约为 Python fallback 的 0.514x;V3t 固定 Rust 路径约为标准 V3 Rust 路径的 1.005x,即慢约 0.5%,仍低于 1.25x 门禁。解释器探针的并发收益不能抵销产品路径 退化,因此两组比例必须继续独立验收。

6.3 PostgreSQL 三方案

三组使用同一 PostgreSQL 18.6、相同 SQL 和六轮全排列顺序,以标准 V3 + psycopg2 为 1.00 倍基线:

方案 单连接查询吞吐 16 线程查询吞吐 批量事务写耗时
V3 + psycopg2 1.00x 1.00x 1.00x
V3 + psycopg3 binary 0.946x 0.367x 0.094x
V3t + psycopg3 C 0.931x 1.258x 0.096x

因此标准 V3 保留 psycopg2V3t 为满足 free-threaded ABI 使用 psycopg3 Cpsycopg3 的批量 事务写明显更快,但查询吞吐没有形成全面优势;当前不为统一驱动扩大数据库重构。18 个当前镜像样本 均通过驱动实现、GIL、SQL 结果、长事务并行和样本完整性门禁。三种制品内嵌的 libpq 分别为 17.9、 18.0 和 18.6,性能差异不能全部归因于 Python 驱动。未来驱动或应用访问模式变化 时,必须重跑真实短事务、批量写、连接池、长事务和迁移场景,不能只用单项微基准改写结论。

6.4 环境检查成本

uv sync --check --locked --offline --inexact 适合验证镜像构建、开发环境和锁定项目能否复现, 不用于覆盖插件允许的非核心依赖升级。真正恢复可能访问包索引,耗时仍受缺失制品、缓存命中、网络和 原生构建影响,不能用检查耗时推断用户故障恢复时长。

当前公共 A/B schema 2 已记录完整安装包集合及哈希、全部原生 distribution 和 wheel tag、导入前后 GIL、working set、RSS/PSS/USS、API p50/p95、SQLite 同步/异步结果和 1/8/16/32 线程热点。两套镜像 各安装 174 个包;V3t 的 16 个核心原生模块在同一进程依次导入后,GIL 始终保持关闭。

6.5 冷启动、插件恢复与宿主自愈

空白配置卷首次启动需要下载 CloakBrowser 内核。该场景可能接近用户报告的“两分钟启动”,主要瓶颈 是持久浏览器缓存未命中和下载网络,不是插件初始化;缓存命中后的普通重启不重复下载。

现代 pyproject.toml 插件(boltons==25.0.0)与历史 requirements.txt 插件 humanize==4.15.0)均已在 V3t 真实容器中验收。首次运行从快照恢复两个源码目录耗时 1.60ms, 联网安装依赖及激活耗时 4.64s;优雅关停后使用同一不可变镜像和持久 /config 断网重建,源码恢复 耗时 1.95ms,仅从持久 uv 缓存恢复依赖及激活耗时 1.56s。两轮各插件都只产生一条实例初始化记录, 最终 API 状态均为 active、无 pending/failed,且没有启用 GIL。该结果证明插件源码恢复不应被依赖 下载阻塞,依赖未就绪的插件可以在 Web 可用后渐进加载。

删除一个主程序核心依赖后重启,启动前探针能触发锁定自愈,--inexact 保留插件额外依赖;恢复后 插件仍为单实例 active,V3t GIL 仍关闭。该路径的价值是让受污染共享环境能够恢复启动,不是性能 优化;它只在核心导入失败时执行,正常重启不会承担这段解析成本。

7. 上游能力的渐进式接入

第三方上游发布新版本或新 wheel 后,按以下顺序处理,不能看到文件名包含 cp314t 就直接删除本地兼容:

  1. 确认制品:稳定版本覆盖 Linux amd64/arm64wheel tag 与解释器 ABI 匹配;源码构建路线还要固定 可审计的源版本和构建工具链。
  2. 建立候选锁:只修改对应 runtime group 或 source,更新单一 uv.lock,确认标准与 free-threaded 两套 profile 均可 uv sync --locked 重建。
  3. 验证原生合同:导入、核心功能、错误边界、首次初始化和 1/8/16/32 线程并发结果一致;V3t 在每个阶段保持 GIL 关闭。
  4. 验证恢复链:覆盖启动前恢复、源码更新与回滚、现代插件清单、历史 requirements.txt 和插件 安装后宿主恢复,确认 profile 不串组且插件额外依赖不被裁剪。
  5. 执行同 revision A/B:先证明标准 V3 修改前后无可重复退化,再比较同一 revision、同一资源限制 和不可变镜像 digest 的 V3/V3t。至少保留镜像体积、启动、RSS/PSS/USS、API p50/p95、SQLite、 PostgreSQL 和 CPU 热点原始样本。
  6. 完成双架构验收amd64、arm64 的构建、漏洞扫描、启动和核心功能均通过后,才发布候选制品。
  7. 删除临时处理:同一变更中移除不再需要的 source、版本分叉或排除规则,并更新本表、锁文件、 构建探针和回归测试;不要永久保留失效的兼容分支。

公共 A/B 基线为 scripts/perf/free_threaded_ab.py,使用方法和输出合同见 scripts/perf/README.md。正式验收原始数据应按发布候选独立留存,PR 只提交 可复用脚本和维护者可判断的汇总结论。

8. 插件生态边界

  • 插件不得直接依赖 V3/V3t 的内部实现选择,应使用 app.sdk 暴露的文本、网络和运行时能力。
  • 插件直接导入 zhconv_rs 等标准 V3 专属包时,V3t 可以明确判定为不兼容;宿主不伪造第三方模块。
  • 插件若自行声明不再维护的 crcmod,必须由插件迁移到 crcmod-plus;宿主不映射分发包、不增加插件 特判,也不在运行期卸载插件声明的依赖。
  • 插件携带原生 wheel 时必须匹配当前解释器和平台 ABI。缺少 cp314cp314t 制品属于插件依赖 兼容问题,不通过宿主插件 ID 特判绕过。
  • 插件安装后若使 V3t 重新启用 GIL,功能可能仍可运行,但该进程已经失去 free-threaded 产品语义, 必须告警并引导用户升级插件、移除依赖或切回标准 V3。

V3 3.0.0 的依赖集合随镜像交付。相同版本 Tag 的 release 自动更新只执行版本比较,不下载源码或 同步依赖;用户必须拉取并重建容器后才会使用新镜像内的锁定环境。/app/opt/venv 不属于标准 持久化卷,非标准挂载和 dev 自动更新不属于正式镜像迁移合同。

9. 变更检查表

修改 Python patch 版本、runtime group、原生依赖、Rust 制品、资源 ABI 或双镜像构建链时,至少确认:

  • pyproject.tomluv.lock 同步,两个互斥 profile 均能锁定重建。
  • 标准 V3 依赖集合、性能、体积和功能没有因 V3t 改动退化。
  • V3t 核心原生扩展导入及并发执行后 GIL 仍关闭。
  • amd64、arm64 的 Python、Rust 和站点资源 ABI 匹配。
  • SQLite、PostgreSQL、插件安装/恢复、源码升级/回滚和启动自愈通过。
  • 动态 GIL API、启动日志、插件归因和前端告警保持一致。
  • 安全扫描结果已按实际安装制品、利用面和上游修复状态审计。
  • 上游已成熟的临时兼容已删除,仍保留的例外在本文有明确解除条件。