本文主要说明一类自托管媒体库(以 Octans 后端为例)在选用 PostgreSQL 作为可选数据库 Provider 时,如何完成安全部署、停服双向迁移与部署冒烟;同时划清破坏性重建(删除并重建整个业务库)的适用场景、数据影响与「无备份、无窗口计划绝不在生产执行」的边界。密码与主机一律占位。
两套流程容易被混成「升级就是重建」。实际上:
| 流程 | 是否保留业务数据 | 典型用途 | 生产可用性 |
|---|---|---|---|
| 安全部署 / schema migration | 保留(空库建表或在已有库上升级 schema) | 新部署、Provider 切换后的正式接入 | 可按变更窗口执行 |
| 停服迁移(SQLite ↔ Postgres) | 保留(工具复制业务表) | 从默认 SQLite 切到 Postgres,或回切 | 可按变更窗口执行 |
破坏性重建(DROP DATABASE) | 不保留 | 0.x 测试库、明确可清空的预发 / 临时实例 | 默认禁止;无备份与回滚计划不得执行 |
下面先讲安全路径,再单独写破坏性重建,避免命令串台。
边界与前提
- 默认 Provider 仍是 SQLite;PostgreSQL 必须显式配置
Database__Provider=Postgres。 - Provider 切换是停服操作,不支持在线切库。
- SQLite 与 PostgreSQL 之间的双向迁移走专用迁移工具;目标库必须先用目标 Provider 的 migration 建好 schema,且业务表为空;工具失败后不会自动清理目标库。
- 破坏性重建不是数据迁移,也不是版本升级的默认路径。
- 生产密码不要写进仓库;优先环境变量、systemd drop-in、容器 secret 或平台 secret 注入。
版本底线(以当前项目支持矩阵为准,落地前请对照目标版本发布说明):
| PostgreSQL 版本 | 状态 | 说明 |
|---|---|---|
| 16 | 支持底线 | 最低支持 major |
| 17 / 18 | 支持 | 通常纳入本地部署 smoke 矩阵 |
| 15 及以下 | 不支持 | 启动期应 fail-fast |
新 major 一般不会在 day 0 自动宣称支持;常见策略是等到 x.1 / x.2,并通过迁移、启动、扫库、任务查询等冒烟后再扩矩阵。
安全部署:配置与接入
环境变量示例
export Database__Provider="Postgres"
export Database__Postgres__Url="postgresql://octans:<password>@<pg-host>:5432/octans"
export Database__Postgres__SslMode="Prefer"
appsettings.Prod.json 结构示例
{
"Database": {
"Provider": "Postgres",
"Postgres": {
"Url": "postgresql://octans:<password>@<pg-host>:5432/octans",
"SslMode": "Prefer"
}
}
}
生产环境不要把真实密码提交到仓库。
systemd drop-in(推荐避免密码进主 unit)
[Service]
Environment=Database__Provider=Postgres
Environment="Database__Postgres__Url=postgresql://octans:<password>@127.0.0.1:5432/octans"
Environment=Database__Postgres__SslMode=Prefer
更稳妥的做法是把连接串放进仅 root 可读的 env 文件(例如 /etc/octans/octans-db.env,权限 0600),由 drop-in 引用,再:
sudo systemctl daemon-reload
sudo systemctl restart octans.service
sudo journalctl -u octans.service -f
Docker Compose environment 示例
services:
octans:
environment:
Database__Provider: Postgres
Database__Postgres__Url: "postgresql://octans:<password>@postgres:5432/octans"
Database__Postgres__SslMode: Prefer
服务端启用 TLS 时,SslMode 不要随便写 Disable;按实际策略使用 Prefer / Require / VerifyCA / VerifyFull。
新部署推荐步骤
- 准备 PostgreSQL 16+,创建业务库
octans与专用账号(owner)。 - 确认编码 / locale / 时区策略(开发容器常见:
UTF8、C.UTF-8、UTC)。 - 停止后端。
- 配置
Database__Provider=Postgres、Database__Postgres__Url、Database__Postgres__SslMode。 - 对目标库执行 EF migration(上下文为 PostgreSQL 对应
DbContext)。 - 启动后端,确认日志无 Provider / 版本 / 连接错误。
- 登录并做一次扫库 / 任务查询冒烟。
手工执行 migration 的示意(端口与路径按环境替换):
Database__Provider=Postgres \
Database__Postgres__Url="postgresql://octans:<password>@127.0.0.1:<pg-port>/octans" \
Database__Postgres__SslMode="Disable" \
dotnet ef database update --context PostgresAppDbContext -- --environment Dev
停服迁移:SQLite ↔ PostgreSQL
这是保留数据的正式切换路径。所有命令都应在服务停止后执行;迁移期间不要同时启动后端或后台 worker。
从 SQLite 切到 PostgreSQL
- 停止后端。
- 备份当前 SQLite 库、配置目录与媒体元数据目录;不要在原库上做破坏性试验。
- 准备空业务库的 PostgreSQL 16+ 实例。
- 对目标库只跑 schema migration,不复制业务数据。
- 使用发布产物的迁移工具(正式演练优先自包含发布,而不是临时
dotnet run)。 dry-run:若目标非空、有 pending migration、活动任务、未完成恢复、运行时锁或字段扫描失败,先处理。migrate(建议--search-index-mode rebuild),完成后看自动 / 独立verify报告并归档。- 切换 Provider 与连接配置,启动后端并冒烟。
连接串示意(密码占位):
Host=<pg-host>;Port=5432;Database=octans;Username=octans;Password=<password>;Application Name=octans-migration
不建议用手工 SQL 拼接替代迁移工具:Guid、boolean、瞬时点 / 日历日期、搜索派生字段、计算列与 sequence 等语义需要工具显式处理。
瞬时点策略注意:
reject:拒绝没有明确 UTC kind / offset 的瞬时点。assume-utc:仅在确认历史值应按 UTC 解释时使用,并保留迁移报告。- 历史 SQLite 库通常先
dry-run --instant-timestamp-policy reject看阻断项。
从 PostgreSQL 回切到 SQLite
同样是停服操作:备份 Postgres → 准备新的 SQLite 文件路径(不覆盖可回滚备份)→ 空 schema migration → dry-run / migrate / verify → 切回 Database__Provider=Sqlite → 启动与冒烟。
连接池与超时(安全运维侧)
应用侧通常直接使用 Npgsql 连接池。Application Name 建议固定(例如 octans),便于 pg_stat_activity 过滤。
计算连接上限至少满足:
实例数 * 每实例 Maximum Pool Size + 运维连接 + 备份/监控连接 <= PostgreSQL max_connections
不要把 Maximum Pool Size 单独调大来掩盖连接泄漏或慢查询。Timeout / Command Timeout 是暴露问题的边界,不是吞错降级策略。
连接池排查可先过滤应用名:
select
pid,
usename,
application_name,
state,
wait_event_type,
wait_event,
now() - xact_start as xact_age,
left(query, 160) as query
from pg_stat_activity
where application_name = 'octans'
order by xact_start nulls last, backend_start;
部署冒烟
本地 / 预发可用部署冒烟脚本做冷启链路(启动 PG → migration → 冷启后端 → 登录 → 固定 smoke 媒体库 → 扫库 → 任务 Completed)。注意:
- 脚本会冷启后端,运行前需避免端口冲突;可换空闲
API base或先停现有实例。 - 需要本机
ffmpeg生成可被 MediaInfo 识别的短视频 fixture。 - smoke 媒体库通常会被复用固定名称,配置漂移时应 fail-fast,而不是静默改写。
破坏性重建:定义、影响与禁区
什么叫破坏性重建
这里的「破坏性重建」指:
- 停止应用(容器或 systemd 服务)。
- 删除 PostgreSQL 中已有的业务库(例如
octans)。 - 重新创建同名空库并修正
publicschema 权限。 - 启动新版本应用,依赖启动期 migration 重建 schema。
它不保留任何数据库数据。适合:
- 0.x 阶段测试库;
- 预发 / 开发库;
- 已书面确认「数据可丢」的临时实例。
何时绝不执行(或必须先有计划)
在下列任一条件下,不要把破坏性重建当作升级手段:
- 库中存在不可再生的账号、权限、媒体库配置、刮削结果、播放进度、收藏、任务历史等。
- 没有可验证的
pg_dump/ 物理备份与恢复演练记录。 - 没有变更窗口、回滚负责人与「重建失败如何回到旧库」的书面步骤。
- 生产或准生产环境,且业务未明确签字允许清空。
- 只是「想升级 schema」——应走 migration 或停服迁移工具,而不是
DROP DATABASE。
一句话:破坏性重建默认禁止上生产;没有备份与回滚计划,就没有执行资格。
影响范围
执行 DROP DATABASE 后会全部丢失(非穷尽):
- 用户账号、登录会话与权限;
- 媒体库配置、根路径、排除规则与库权限;
- 扫描出的媒体项、文件、音视频 / 字幕轨与资产关系;
- 刮削结果、provider 缓存、评分与演职员关系;
- 配置中心写入数据库的值(含 metadata provider API key);
- 播放进度、收藏、稍后看、任务历史与任务日志。
通常不会被该流程直接删除:
- 宿主机上的真实媒体文件;
- 应用数据 volume 中的非 PostgreSQL 文件(元数据缓存、播放缓存、日志等);
- PostgreSQL 服务中的其他数据库。
注意:库清空后,volume 里旧图片 / 缓存可能变成孤立文件;本文只处理数据库,不顺带清数据目录。
执行前强制检查清单
- 确认目标:host / port / 库名 / owner 与变更单一致(写错库名的代价是整库清空)。
- 确认数据可丢,或已完成并校验备份。
- 停止应用,确认无业务连接占用目标库。
- 维护账号与
~/.pgpass(权限0600)准备就绪;密码不进文档与仓库。
环境变量示意(主机占位):
export PGHOST=<pg-host>
export PGPORT=5432
export PGUSER=postgres
export PGMAINTDB=postgres
export OCTANS_DB=octans
export OCTANS_OWNER=octans
pg_isready -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$OCTANS_DB"
确认库与角色存在、再确认连接占用:
-- 在维护库上查询目标库大小与 owner
SELECT datname,
pg_get_userbyid(datdba) AS owner,
pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database
WHERE datname = 'octans';
-- 查占用连接
SELECT pid, usename, application_name, client_addr, state, query_start
FROM pg_stat_activity
WHERE datname = 'octans'
ORDER BY backend_start;
若仍有残留连接:先确认应用已停,再 pg_terminate_backend 终止目标库上非本会话连接。
应用已停示例(Docker):
docker ps --filter name=octans
docker stop octans # 仍 Up 时
删除与重建(高危命令区)
以下命令会永久删除业务库。仅在检查清单全部勾选后执行。
# DROP:确认 :db 变量无误
psql -X -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGMAINTDB" \
-v ON_ERROR_STOP=1 -v db="$OCTANS_DB" <<'SQL'
SELECT format('DROP DATABASE IF EXISTS %I', :'db')
\gexec
SQL
# 确认 0 rows
psql -X -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGMAINTDB" \
-v ON_ERROR_STOP=1 -v db="$OCTANS_DB" \
-c "SELECT datname FROM pg_database WHERE datname = :'db';"
重建空库并保证应用 owner 可在 public 建表:
psql -X -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGMAINTDB" \
-v ON_ERROR_STOP=1 -v db="$OCTANS_DB" -v owner="$OCTANS_OWNER" <<'SQL'
SELECT format('CREATE DATABASE %I OWNER %I', :'db', :'owner')
\gexec
SQL
psql -X -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$OCTANS_DB" \
-v ON_ERROR_STOP=1 -v owner="$OCTANS_OWNER" <<'SQL'
SELECT format('ALTER SCHEMA public OWNER TO %I', :'owner')
\gexec
SELECT format('GRANT USAGE, CREATE ON SCHEMA public TO %I', :'owner')
\gexec
SQL
确认 public 尚无业务表后再启动应用。
启动应用与验证
镜像若默认 Database__ApplyMigrationsOnStartup=true,空库启动时会在监听 HTTP 前做 Provider 校验与 EF migration。关键环境变量:
Database__Provider=Postgres
Database__Postgres__Url=postgresql://octans:<password>@<pg-host>:5432/octans
Database__Postgres__SslMode=<Prefer|Require|Disable|...>
验证要点:
- 应用日志:校验与 migration 成功,再进入 Web 服务。
- ready 接口(端口按部署替换):
curl --fail http://127.0.0.1:<listen-port>/api/ready __EFMigrationsHistory中有当前版本预期 migration id。public下业务表已创建;表数量不是长期契约,重点是 migration 成功、ready 正常、可进入初始化流程。
重建后的业务动作
破坏性重建后系统回到「全新安装」:
- Web 初始化,创建 root。
- 重新填写 metadata provider API key 等配置中心项。
- 重新创建媒体库与排除规则。
- 重新扫库 / 按需刮削。
- 旧账号不可用;媒体文件本身未被
DROP DATABASE删除,但与库记录的绑定需重建。
常见问题速查
| 现象 | 优先排查 |
|---|---|
| 启动提示仅支持 PostgreSQL 16.0+ | URL 是否指错实例;select version();;升级服务端,勿绕过启动校验 |
DROP DATABASE 报 database is being accessed | 应用是否已停;pg_stat_activity;pg_terminate_backend |
| 容器 / 服务 unhealthy | URL / 密码 / SslMode / owner 的 public 建表权限 / 镜像 tag;首次 migration 是否超过 healthcheck start_period |
| pending migration 未自动应用 | 是否把 Database__ApplyMigrationsOnStartup 显式改成了 false |
| 连接池耗尽 | 长事务、扫库/刮削并发、实例数 × pool 是否超过 max_connections |
| 配置像「生效了却不对」 | 环境变量优先级高于 appsettings;systemd / 容器里是否残留旧变量 |
配置不匹配的常见组合:
Provider=Postgres但 URL 未配或指错环境;- 缺
SslMode; Provider=Sqlite仍残留 Postgres 环境变量,排障时误判配置来源。
实践建议(收口)
- 先分清路径:要保留数据 → migration / 迁移工具;可丢测试数据 → 才考虑破坏性重建。
- 任何删库命令前再读一遍 host、库名、变更单与备份校验结果。
- 生产变更:备份 → 停服 → 变更 → 冒烟 → 保留回滚窗口;把迁移 / verify 报告当变更记录存档。
- 文档与工单中统一使用
<password>、<pg-host>等占位符,避免粘贴真实连接串。
相关阅读
同系列还可对照:
- Octans Docker 私有化部署:单镜像、Compose 叠加与健康检查:容器侧环境变量、卷挂载与镜像发布入口,和本文数据库 Provider 配置衔接。
- 媒体库播放排障:从会话创建到 HLS / DirectPlay / 字幕链路:库可用之后的播放控制面;数据库重建后会话 / 进度需重新积累。
来源
本文合并改写自 Octans 项目 usage 层《后端 PostgreSQL 部署指南》与《PostgreSQL 破坏性重建指南》。命令与路径已通用化;主机、密码与镜像仓库已脱敏为占位符;具体支持版本与 migration id 以目标环境当前发布说明为准。