本文主要说明自托管媒体库(以 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)、挂载点是否在升级后变成可写空目录、以及清理策略是否只删缓存子树而不误伤数据卷。

明确不采用的方向

  1. 继续扩大 tmpfs 充当长窗口主缓存 — 100Mbps × 多分钟 × 多 session 会变成系统级内存事故。
  2. 只改路径、不改 playlist/治理 — SSD 上的 24 秒滚动窗口,pause/seek 体验几乎不比 tmpfs 好。
  3. 无限累积、无全局与剩余空间保护 — event 时间线可以学,资源边界必须自有。
  4. 默认把热写目录放到网络盘 — 除非有经过验证的延迟、锁与故障预案;家用/单机私有化没有必要引入该复杂度。

实施前仍需钉死的配置项

落地前建议在计划文档中写死(公开文只列项,不绑内网真值):

  • 生产默认 SessionRoot 与 Compose/卷覆盖方式;
  • 是否保留 WindowDurationSeconds 作为 bounded event 上限;
  • 单 session / 全局 cache 默认容量与最小剩余磁盘阈值;
  • pause grace 与 FFmpeg 停止后缓存保留时长;
  • token、hlsWindowId、logicalSessionId 在 event 模型下的生命周期;
  • 启动清理与运行时清理的测试覆盖;
  • 既有 tmpfs 配置的迁移与回滚开关。

注意事项

  1. 路径如 /var/cache/octans/.../dev/shm/... 均为示意;以部署配置与启动可写检查为准。
  2. 100Mbps 是容量规划口径,不是承诺的恒定输出码率;峰值与音频/封装会抬高占用。
  3. 清理脚本与自动任务必须限定在 HLS cache 子树;误删媒体库数据卷不可恢复。
  4. 磁盘类错误应可观测、可告警;客户端与运维文档避免「神秘卡死」描述。
  5. 本文是选型与治理口径,不描述某一提交已落地的代码变更。

相关阅读

同系列还可对照:

来源

本文综合改写自 Octans 项目文档中的播放转码缓存存储介质选型研究(octans-docs research 层),并对照当前 HLS 短窗口配置与同类媒体服务器(如 Jellyfin)动态 HLS 缓存目录取向。路径、容量数字与配置键已按公开口径通用化,不绑定特定内网主机;实施参数以目标环境计划文档为准。