本文主要说明自托管媒体库(以 Octans 播放子系统为例)在 HLS 转码缓存 上的存储选型:当前短窗口 + tmpfs 适合什么、为何生产默认应迁到 SSD 本地盘 + 应用层治理,以及容量模型、网络文件系统边界和清理优先级如何一起约束架构。
播放一旦进入 Transcode / DirectStream 的 HLS 路径,服务端会持续写出 playlist 与 segment。介质选错,轻则 pause / seek 抖动、缓存打满,重则把内存或系统盘拖垮。下面按「当前事实 → 容量模型 → 介质对比 → 主线结论 → 治理清单」整理,路径一律用占位符。
问题边界:缓存目录在回答什么
转码缓存目录不是媒体库本体,也不是对象存储里的片源。它只服务正在生成的 HLS 输出:
FFmpeg 写出 playlist + segment
→ 播放器按 token 拉取
→ session 结束或超限后回收
选型至少要同时回答四件事:
| 维度 | 要回答的问题 |
|---|---|
| 介质 | tmpfs、本地 SSD/HDD、网络文件系统各自是否可写、延迟与故障域是否可接受 |
| 容量 | 单 session、多用户并发、目标缓存窗口下磁盘/内存是否够 |
| 时间线 | 短 rolling playlist 还是 event 风格长窗口;旧 segment 是否仍可 seek |
| 治理 | 谁删目录、删谁优先、剩余空间与孤儿 session 如何兜底 |
不要把「目录能挂上」当成选型完成。 介质只解决放得下、写得动;playlist 模型与清理策略才决定暂停恢复、seek 复用与故障恢复是否稳。
当前常见基线:短窗口 + 热缓存
一类实现(含当前 Octans HLS session 的默认取向)会把 session root 放在内存盘,并用短 rolling playlist 限制规模,概念上类似:
SessionRoot: <cache-root>/playback-hls/sessions
SegmentDurationSeconds: 2
PlaylistSegmentCount: 8
UnreferencedSegmentRetention: 4
MaxSessionMegabytes: 512
WindowDurationSeconds: 1200
有效热窗口大约是:
2s × (8 + 4) ≈ 24s
FFmpeg 侧常见组合是:
-hls_list_size <PlaylistSegmentCount>
-hls_delete_threshold <UnreferencedSegmentRetention>
-hls_flags independent_segments+temp_file+delete_segments
含义很直接:playlist 只保留短窗口,旧 segment 由 FFmpeg 滚走。这对极短热缓存、单次连续播放、开发机试跑足够;一旦要谈「暂停几分钟再恢复」「在已生成范围内 seek」「多用户 100Mbps 级输出」——短窗口本身就会成为体验与资源的双重瓶颈。
对照另一类常见媒体服务器(如 Jellyfin 动态 HLS)的取向:更接近 event/vod playlist、-hls_list_size 0,转码目录落在 Cache/ProgramData 一类持久路径,再用 job 生命周期、启动清理、定时任务等做治理,而不是依赖 20 多秒的滚动删除。
容量模型:先按码率算,再乘 session 数
高码率转码不要按「几十 Mbps 随便估」。家用/轻量多用户场景里,单用户单部电影转码输出按 约 100Mbps 做容量规划更稳妥,并显式考虑并发 session。
基础换算:
100 Mbps ÷ 8 = 12.5 MB/s
单 session 理论占用(仅视频量级,未含开销):
| 缓存时长 | 约 100Mbps 占用 |
|---|---|
| 24s | ~300MB |
| 60s | ~750MB |
| 120s | ~1.5GB |
| 5min | ~3.75GB |
| 20min | ~15GB |
| 2h | ~90GB |
多用户近似线性:
总占用 ≈ 输出码率(MB/s) × 缓存秒数 × 活跃转码 session 数
真实规划还要留:
- 封装与音频、可选字幕 sidecar;
- 峰值码率高于标称值;
- 写入中的
.tmp/temp_file; - 1.2~1.5× 安全系数;
- 系统盘上数据库、日志、镜像层等与缓存共享剩余空间时的保护线。
结论先写死:20 分钟级窗口在 100Mbps 下单 session 就到十几 GB。把它塞进常见 /dev/shm 或小内存机,不是「调大一点 tmpfs」能体面解决的。
介质对比
1. tmpfs / 内存盘(如 /dev/shm)
| 优点 | 延迟低、吞吐高;重启即清空;对 SSD 写入压力几乎为零 |
| 缺点 | 容量直接吃内存或 swap;多用户线性放大;写满可能拖垮整机而不只是播放;无法可靠保留长暂停 / seek 所需历史 segment |
| 适用 | 极短 rolling 热缓存、单机开发/测试、用户显式选择的试验路径 |
| 不适用 | 生产默认的长窗口、event playlist、高码率多用户主线 |
选型结论: 不要把 tmpfs 当成长窗口或 event 缓存的默认根。扩大 tmpfs 只能延后内存风险,不能形成稳定架构。
2. 本地 SSD(推荐主线介质)
| 优点 | 容量远大于内存盘;现代 SSD 顺序写足以覆盖单用户乃至少量并发的 100Mbps 级 HLS;故障域清晰,权限与挂载可控 |
| 缺点 | 需要应用自己做限额与清理;相对 tmpfs 有写入放大(播放场景多为顺序写,寿命通常不是首要矛盾) |
| 适用 | 生产默认 SessionRoot;配合 bounded event playlist 与磁盘治理 |
| 注意 | 只把目录从 tmpfs 改到 SSD、仍保留短 rolling + delete_segments,只能降内存风险,解决不了 pause/seek 时间线问题 |
推荐形态(概念路径):
Playback:Hls:SessionRoot = /var/cache/octans/playback-hls/sessions
# 或部署卷映射到等价 SSD 路径;要求绝对路径且启动时可写
3. 本地 HDD
| 优点 | 容量便宜,适合「能容忍更高延迟」的冷数据 |
| 缺点 | 多路顺序写 + 随机读 playlist/segment 时延迟与队列深度不如 SSD;与系统盘争用时影响面更大 |
| 适用 | 仅当预算/容量强制、且并发与码率都低时的妥协 |
| 建议 | 生产转码热缓存优先 SSD;HDD 更适合片源库本身,而不是 HLS 热写目录 |
4. 网络文件系统(NFS / CIFS / 部分分布式挂载)
| 优点 | 多节点可共享同一缓存视图(若架构真需要) |
| 缺点 | 延迟与抖动放大;锁语义、temp_file 原子替换、删除与 rename 行为因协议/挂载选项差异大;网络抖动会直接变成播放卡顿或 FFmpeg 写失败;故障域跨主机,排障面变宽 |
| 适用 | 一般不作为 HLS segment 热写默认路径 |
| 若必须 | 单独评估挂载选项、超时、配额与「缓存丢失即 rebase」的产品预期;更常见的做法是每节点本地 SSD 缓存,而不是把热写目录放到共享网盘 |
对象存储(S3 兼容等)同理:适合成品分发,不适合 FFmpeg 本地 HLS 的高频小文件追加与即时删除语义。转码热路径应落在本地 POSIX 语义清晰的文件系统。
主线方向:SSD + bounded event playlist + 应用治理
综合容量与体验,后续主线应是:
SSD 本地 cache root
+ hls_playlist_type event(或等价长窗口)
+ hls_list_size 0(playlist 不因短 list 滚丢历史项)
+ 不依赖 FFmpeg delete_segments 做主清理
+ 应用层:单 session 限额 / 全局 cache 限额 / 最小剩余磁盘 / inactive 与启动清扫
与「完全照搬无限累积」不同:参考 event 时间线稳定性的同时,必须保留明确资源边界——单一主线部署往往更需要「打满可预期失败(如磁盘类错误码)」,而不是静默吃光盘。
短窗口策略可以保留为:
- 开发默认或显式 opt-in;
- 极短预览 / 低码率试验;
- 与「生产 SSD + event + 治理」配置分轨。
清理与生命周期(比选盘更关键)
介质选定后,至少要把下面保护写进运维与实现清单:
| 保护项 | 作用 |
|---|---|
| 单 session 最大容量 | 防止单路异常码率或僵尸 window 独占磁盘 |
| 全局 HLS cache 上限 | 多用户合计可控 |
| 最小剩余磁盘空间 | 保护数据库、日志与其它系统服务 |
| inactive / pause grace | 暂停不立刻撕掉可复用缓存;超时后再停 FFmpeg 并延迟删目录 |
| logical stop / unload 清理 | 正常结束路径及时回收 |
| 启动扫描孤儿目录 | 异常退出后的残留 session |
| 清理优先级 | 建议:stopped > expired > inactive > oldest paused > oldest playing |
Seek / pause 与缓存的配合(产品预期,不是存储驱动细节):
- 暂停:前端尽量保留画面与 MediaSource;后端标 paused + grace;grace 内复用当前 HLS window。
- Seek:目标落在已生成 event 范围内 → 播放器本地 seek;仍在可继续生成范围 → 等 segment;不可复用 → rebase 新 window。
- 超限:单 session 体积打满时,不要无脑无限 rebase;应可观测地失败并提示容量问题(与播放排障文中的缓存/磁盘类错误口径一致)。
Docker / systemd 部署还要单独核对:卷是否落在 SSD、权限(PUID/PGID)、挂载点是否在升级后变成可写空目录、以及清理策略是否只删缓存子树而不误伤数据卷。
明确不采用的方向
- 继续扩大 tmpfs 充当长窗口主缓存 — 100Mbps × 多分钟 × 多 session 会变成系统级内存事故。
- 只改路径、不改 playlist/治理 — SSD 上的 24 秒滚动窗口,pause/seek 体验几乎不比 tmpfs 好。
- 无限累积、无全局与剩余空间保护 — event 时间线可以学,资源边界必须自有。
- 默认把热写目录放到网络盘 — 除非有经过验证的延迟、锁与故障预案;家用/单机私有化没有必要引入该复杂度。
实施前仍需钉死的配置项
落地前建议在计划文档中写死(公开文只列项,不绑内网真值):
- 生产默认
SessionRoot与 Compose/卷覆盖方式; - 是否保留
WindowDurationSeconds作为 bounded event 上限; - 单 session / 全局 cache 默认容量与最小剩余磁盘阈值;
- pause grace 与 FFmpeg 停止后缓存保留时长;
- token、
hlsWindowId、logicalSessionId 在 event 模型下的生命周期; - 启动清理与运行时清理的测试覆盖;
- 既有 tmpfs 配置的迁移与回滚开关。
注意事项
- 路径如
/var/cache/octans/...、/dev/shm/...均为示意;以部署配置与启动可写检查为准。 - 100Mbps 是容量规划口径,不是承诺的恒定输出码率;峰值与音频/封装会抬高占用。
- 清理脚本与自动任务必须限定在 HLS cache 子树;误删媒体库数据卷不可恢复。
- 磁盘类错误应可观测、可告警;客户端与运维文档避免「神秘卡死」描述。
- 本文是选型与治理口径,不描述某一提交已落地的代码变更。
相关阅读
同系列还可对照:
- 媒体库 FFmpeg 工具链:从 Jellyfin 构建到自维护 runtime:转码实际执行的二进制、能力探测与 runtime 边界。
- Octans Docker 私有化部署:单镜像、Compose 叠加与健康检查:卷挂载、数据目录与缓存目录如何落到容器文件系统。
- 媒体库播放排障:从会话创建到 HLS / DirectPlay / 字幕链路:HLS window、pause/seek、磁盘/缓存类错误的分诊口径。
来源
本文综合改写自 Octans 项目文档中的播放转码缓存存储介质选型研究(octans-docs research 层),并对照当前 HLS 短窗口配置与同类媒体服务器(如 Jellyfin)动态 HLS 缓存目录取向。路径、容量数字与配置键已按公开口径通用化,不绑定特定内网主机;实施参数以目标环境计划文档为准。