18 KiB
MoviePilot V3t 运行时治理
状态:持续维护。本文定义
moviepilot-v3与moviepilot-v3t的产品边界、运行依赖分层、 故障恢复合同和兼容项退场门禁。具体版本以pyproject.toml与uv.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-standardruntime-free-threaded
tool.uv.conflicts 保证两个组不能同时解析。Docker 的两个构建 stage 分别固定选择对应组;源码升级、
启动恢复和插件安装后的宿主恢复由 app.runtime.dependencies.runtime_dependency_group() 根据当前
解释器的 Py_GIL_DISABLED 构建标志选择 profile。
依赖选择不得改为镜像标签判断,也不得在业务模块中按包名散落 V3/V3t 分支。Dockerfile 只选择运行
profile,依赖名称、版本和 source 语义全部由 pyproject.toml 与 uv.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 插件安装后的宿主恢复
插件与主程序共享虚拟环境。插件依赖安装前后都会采集宿主健康快照,只对安装后新增的异常执行补偿:
- 优先使用安装前生成的主程序保护约束恢复被修改的包;
- 约束不可用时,按主项目
pyproject.toml、uv.lock和当前 runtime profile 恢复; - 恢复完成后重新执行依赖诊断与核心能力探针;
- 宿主即使恢复成功,本次插件安装仍返回失败,不能把被回滚的安装报告为成功。
依赖诊断使用 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_THREADED和PYTHON_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 MiB,V3t 增加 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.068769s、0.035339s、0.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 保留 psycopg2,V3t 为满足 free-threaded ABI 使用 psycopg3 C。psycopg3 的批量
事务写明显更快,但查询吞吐没有形成全面优势;当前不为统一驱动扩大数据库重构。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 就直接删除本地兼容:
- 确认制品:稳定版本覆盖 Linux amd64/arm64,wheel tag 与解释器 ABI 匹配;源码构建路线还要固定 可审计的源版本和构建工具链。
- 建立候选锁:只修改对应 runtime group 或 source,更新单一
uv.lock,确认标准与 free-threaded 两套 profile 均可uv sync --locked重建。 - 验证原生合同:导入、核心功能、错误边界、首次初始化和 1/8/16/32 线程并发结果一致;V3t 在每个阶段保持 GIL 关闭。
- 验证恢复链:覆盖启动前恢复、源码更新与回滚、现代插件清单、历史
requirements.txt和插件 安装后宿主恢复,确认 profile 不串组且插件额外依赖不被裁剪。 - 执行同 revision A/B:先证明标准 V3 修改前后无可重复退化,再比较同一 revision、同一资源限制 和不可变镜像 digest 的 V3/V3t。至少保留镜像体积、启动、RSS/PSS/USS、API p50/p95、SQLite、 PostgreSQL 和 CPU 热点原始样本。
- 完成双架构验收:amd64、arm64 的构建、漏洞扫描、启动和核心功能均通过后,才发布候选制品。
- 删除临时处理:同一变更中移除不再需要的 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。缺少
cp314或cp314t制品属于插件依赖 兼容问题,不通过宿主插件 ID 特判绕过。 - 插件安装后若使 V3t 重新启用 GIL,功能可能仍可运行,但该进程已经失去 free-threaded 产品语义, 必须告警并引导用户升级插件、移除依赖或切回标准 V3。
V3 3.0.0 的依赖集合随镜像交付。相同版本 Tag 的 release 自动更新只执行版本比较,不下载源码或
同步依赖;用户必须拉取并重建容器后才会使用新镜像内的锁定环境。/app 与 /opt/venv 不属于标准
持久化卷,非标准挂载和 dev 自动更新不属于正式镜像迁移合同。
9. 变更检查表
修改 Python patch 版本、runtime group、原生依赖、Rust 制品、资源 ABI 或双镜像构建链时,至少确认:
pyproject.toml与uv.lock同步,两个互斥 profile 均能锁定重建。- 标准 V3 依赖集合、性能、体积和功能没有因 V3t 改动退化。
- V3t 核心原生扩展导入及并发执行后 GIL 仍关闭。
- amd64、arm64 的 Python、Rust 和站点资源 ABI 匹配。
- SQLite、PostgreSQL、插件安装/恢复、源码升级/回滚和启动自愈通过。
- 动态 GIL API、启动日志、插件归因和前端告警保持一致。
- 安全扫描结果已按实际安装制品、利用面和上游修复状态审计。
- 上游已成熟的临时兼容已删除,仍保留的例外在本文有明确解除条件。