Emby 大片库卡、内存高?

片库一大,媒体服务器就容易变慢:海报墙停顿、筛选要等、内存一路上涨。Emby 的社区论坛上,多年来一直有人反映大片库不断超时。这是介绍 Kryspal Media 技术选择的系列的第一篇。

大片库为什么会变慢

播放器的每个界面都是一次查询:按名称排的前 50 部电影、只看没看过的、某一季的所有集。如果服务器得看遍每一部才能回答,片库越大就越慢。解决办法是根本不去看其中的大部分。

两栏对比:没有索引时每部片都要读一遍,耗时随片库增长;有索引时直接找到条目
同一个查询,有索引和没有索引。

索引对准播放器的提问

Kryspal Media 把影片、演员、标签和观看记录放在 PostgreSQL 里,为每一类常用查询各建一个复合索引:按名称浏览,按加入日期、上映日期和评分排序,季和集,演员和标签(双向都能查)。「继续观看」用部分索引,也就是只覆盖满足某个条件的行,这里是开始看还没看完的条目,所以片库再大,它也很小。服务器按索引顺序读出答案,够一页就停。

为每类常用查询各建一个索引:按名称、加入日期、评分、演员到影片,以及「继续观看」的短部分索引;每个都按顺序读出答案,够一页就停
每类常用查询一个索引,已经按播放器要的顺序排好。

翻页不用数行数

偏移分页(offset)是「跳过前 4 万条,再给我 50 条」,要先数过那 4 万行,所以越往后翻越慢。Kryspal Media 用 keyset 分页(也叫游标分页):记住上一页最后一项的排序键和 ID,用索引直接取它后面的 50 条,翻多深耗时都一样。只会按位置要数据的播放器,则从一份短时间有效、已排好序的 ID 列表里取页。

第 800 页的两栏对比:OFFSET 40000 LIMIT 50 要读掉 4 万行再丢弃,keyset 分页从上一页最后一项之后开始
偏移分页与 keyset 分页。

两种数据库,两种设计

Emby 把片库存在 SQLite 里:单个文件,跑在服务器进程内,同一时刻只有一个写入者。SQLite 自带的全文检索按空格分词,词中的一段(如 Interstellar 里的 stellar),或没有空格的中文片名,都没法通过索引找到。

Kryspal Media 用 PostgreSQL。读写可以同时进行(MVCC,多版本并发控制),扫描不会卡住浏览。二元组索引(pg_bigm)把文字切成两个字一组,片名的任意一段,哪怕一两个汉字,都直接从索引找到。片库越大,差距越明显。

Emby(SQLite)与 Kryspal Media(PostgreSQL)的两个场景对比。搜片名的一部分:SQLite 的全文索引对不上,只能一部一部比对;PostgreSQL 把片名切成两个字一组,直接命中。扫描新片的同时:SQLite 的写入排队,一个接一个;PostgreSQL 的扫描、播放进度、标记已看和浏览同时进行。
示意图:同一个问题,两种数据库的做法。

重启之后不用重来

片库放在数据库里,所以重启不会重新扫描文件,也不会重新载入每部片子。服务器和数据库的内存设置,在启动时按容器的内存自动算出。

从启动到就绪的一条横条,5 秒以内,不重新扫描文件,也不重新载入每部片子
重启:从启动到就绪。

内存占用:从一开始就设上限

服务器(用 Go 编写) 启动时读取容器内存,把 Go 运行时的软内存上限(GOMEMLIMIT)设为约四分之一,并有固定上下界;你自己设置的值优先。接近上限时垃圾回收更频繁,空闲内存归还操作系统。

数据库(PostgreSQL) 的页缓存 shared_buffers 按可用内存自动设定,大小固定。满了就按 clock-sweep 淘汰最近最少使用的页,所以缓存不会随运行时间变大;每条查询的 work_mem 在查询结束时释放。

在约 17.7 万条目的真实片库上,服务器约 140 MB,数据库连同缓冲区约 1 GB。在一个约 2.1 万条的合成片库上,1 GB 内存的机器完成了首次扫描,没有出现内存不足,界面响应和 4 GB 时一样。

上:内存随时间变化,Go 软上限,接近上限时垃圾回收更频繁。下:PostgreSQL 的 shared_buffers 大小固定,新页进来时淘汰最近最少使用的页
内存怎么管住:Go 软上限和固定大小的 PostgreSQL 缓冲区。

实际效果

条形图:翻页、排序、筛选和演员作品页的响应时间中位数,Emby 4.10 与 Kryspal Media 对比
六个浏览、排序和筛选操作的响应时间中位数,单用户。
  • 浏览和筛选最高可比 Emby 4.10 快 30 倍,详见对比页。
  • 在约 20 万部电影的片库上,浏览响应时间小于 50 毫秒。
  • 重启后 5 秒内就能用,大片库也一样。

免费方案同样如此。

想试试,不用从零开始

用 Docker 安装 Kryspal Media,装在 Emby 旁边,指向同一批文件夹,把大片库在两边都打开看看。导入向导只读取 Emby:媒体库、成员、元数据、图片、观看记录、收藏和标签都原样带过来,Infuse、SenPlayer、VidHub 连接的方式和连接 Emby 一样。见迁移指南。

示意图:Emby(只读)经导入向导到 Kryspal Media,带过来媒体库、成员、元数据、图片、观看记录、收藏和标签;Infuse、SenPlayer、VidHub 照常连接
从 Emby 迁移:只读导入。

测试环境:家用 NAS(Intel i5-1235U),数据库位于 NVMe SSD;约 17.7 万条目的真实片库,浏览对比在其中约 6.8 万部的电影库上进行;2026 年 10 月。

全部文章