数据库
网关用 PostgreSQL 存放请求日志、用户账号、API key,以及每一项要跨重启存活的管理/运行时设置。本页讲清楚存了什么、表结构是怎么产生的,以及如何备份、恢复和重置。
数据库是必需的吗?
不是。DB_ENABLED 默认为 true,但以 DB_ENABLED=false 启动的网关照样正常路由请求——只是没有请求历史、没有用户账号、签发不了 API key,那些依赖已存设置的管理界面也都没有。
读 /health 时这个区别很重要,因为两种「没有数据库」的状态给出的并不是同一个答案:
{"status": "healthy", "routes_configured": 3, "database_configured": false, "database_connected": false}
{"status": "unhealthy", "reason": "database_unavailable_at_startup", "database_configured": true, "database_connected": false}
第一种是这个部署本来就没要数据库。第二种是它要了却没拿到,于是报 unhealthy,好让负载均衡器把它摘出轮转。
连接设置
由 apps/backend/serving/config/settings.py 里的 Settings 从 .env 或进程环境读取。
变量 |
默认值 |
说明 |
|---|---|---|
|
|
|
|
|
Compose 栈在 backend 容器内把它覆盖为 |
|
|
在 Compose 里这是宿主机一侧的端口映射;容器内部始终连 5432。 |
|
|
Compose 必需( |
|
|
Compose 必需。 |
|
(空) |
Compose 必需。 |
|
|
是否原样存储 prompt 和响应。见请求日志与隐私。 |
自带的这套栈(deploy/docker/docker-compose.yml)跑的是 postgres:16,以 -E UTF8 --locale=C.UTF-8 初始化,并发布在 127.0.0.1:${DB_PORT:-5432}——只监听回环。要从别的机器访问它,用 SSH 隧道,而不是把这个绑定放宽。
表结构是怎么创建出来的
这个仓库里没有迁移工具——没有 Alembic,也没有 SQL 迁移目录。表结构由应用在启动时创建并迁移,而且是幂等的。每条语句都是 CREATE TABLE IF NOT EXISTS,所以下面这些构建器即便有两个定义了同一张表,重叠也无害:
代码 |
创建什么 |
|---|---|
|
|
|
先调用上面那个,再建认证/管理相关的表 |
|
运行时数据表(设置、覆盖项、provider 注册表) |
|
|
|
|
所以把网关指向一个空数据库,就是全部的「迁移」了:启动它,表就出现了。用本版本对着一个空的 postgres:16 数据库启动,会在 public 里建出 33 张表。
上手运维之前,有两个特性值得先知道:
表结构 DDL 由 Postgres 的系统目录(system catalog)把关。每次启动先读 pg_attribute / pg_indexes,只发出真正缺失的那些 ALTER/CREATE INDEX 语句,所以稳态下的重启不会拿任何强表锁。这一点之所以重要,是因为 ALTER TABLE 是在 Postgres 求值 IF NOT EXISTS 之前就取得 ACCESS EXCLUSIVE 锁的,而一个排队中的排他锁会把它身后的每个读者都堵住。
拿不到锁的迁移会被推迟,而不是致命失败。DDL 阶段跑在 3 秒的 lock_timeout 之下,拿不到就抛 SchemaLockUnavailable,而不是干等;调用方在后台重试,启动流程继续往下走。常见的持锁者是跑得很久的 pg_dump,它可能在 api_logs 上持有 ACCESS SHARE 好几个小时。如果备份是按计划跑的,那么在一次新增列的部署之后,日志里偶尔出现一行迁移被推迟是正常的。
如果你要给 api_logs 加一列,只在 log_schema.py 里加。这个模块之所以存在,是因为当初 DDL 在两个构建器里各写了一份、两份逐渐漂移,而启动时真正跑的那一个从来没建出新列。
各张表存了什么
分组 |
表 |
存放的内容 |
|---|---|---|
请求历史 |
|
每个请求一行(模型、provider、token、延迟、TTFT、状态、成本),外加仪表盘用的小时级汇总 |
账号与认证 |
|
用户记录、哈希后的 API key 及其配额、刷新会话、登录历史 |
管理操作 |
|
受审计的管理变更、注册策略、运行时设置、公告、群发投递状态 |
运行时路由覆盖项 |
|
管理界面不用改 YAML 就能调整的全部路由相关内容 |
成本与配额 |
|
用于执行配额的按用户每日花费 |
RouteWise |
|
延迟探测样本,以及防止两个 worker 同时探测的租约 |
Responses API |
|
启用内容存储时保存下来的 |
地理分析 |
|
按国家统计的小时级请求数和 token 数;只有聚合值,不存储 IP 地址 |
Agent grants |
|
本网关为外部 agent 控制平面签发的、短时效且限定模型范围的能力凭据 |
在活着的数据库上跑 \dt 才是权威的表清单;上面那些代码路径才是权威的定义。
请求日志与隐私
DB_STORE_FULL_CONTENT 默认为 false,而这个默认值并不是把已存下来的文本做脱敏——那些文本压根就不会写入。关掉它时,api_logs.prompt、api_logs.response 和 api_logs.request_payload 都按 NULL 插入,/v1/responses 的状态也不持久化。
不论开关如何,派生出来的非内容列都会记录,因为仪表盘读的是它们,而不是去 de-TOAST 原始载荷:token 计数、成本、延迟和 TTFT,会话形态(num_turns、num_user_turns、num_tool_calls),以及最新一条用户消息的指纹(last_user_msg_chars、last_user_msg_entropy、last_user_msg_hash)。
打开它就会存下完整的 prompt 和响应。动手之前,先掂量一下这与用户的预期是否相符。
备份
直接从容器里做 custom 格式的 pg_dump:
docker exec hybridinference-postgres \
pg_dump -U "$DB_USER" -d "$DB_NAME" -Fc > hybridinference-$(date +%F).dump
不要给 docker exec 加 -t:分配 TTY 会破坏二进制流。
恢复到一个已存在且正在运行的数据库:
docker exec -i hybridinference-postgres \
pg_restore -U "$DB_USER" -d "$DB_NAME" --clean --if-exists < hybridinference-2026-01-01.dump
覆盖一个在线数据库之前,先停掉 backend(make down,或者 docker stop hybridinference-backend),并且记住:备份里含有哈希后的 API key,以及——只要 DB_STORE_FULL_CONTENT 曾经打开过——用户的 prompt 内容。请按相应的密级保管。
重置
删掉数据库比看上去别扭,因为 Postgres 的卷在 deploy/docker/docker-compose.yml 里声明为 external::
volumes:
postgres_data:
external: true
name: hybridinference_postgres_data
docker compose down --volumes 不会删除外部卷。make down 也不会,它压根没传 --volumes。依赖这两者中任何一个的「重置」,都会静默地把每一行数据原样留在那里。要按名字删除这个卷:
make down
docker volume rm hybridinference_postgres_data # destroys all data
make up # recreates an empty volume
make up 依赖 docker-volumes 这个 target,具名卷缺失时它会重新创建,于是整套栈在一个空数据库上起来,由启动时的初始化逻辑重建表结构。只要还有一丝想把数据找回来的可能,就先做一份 dump。
那个可运行的示例(make demo-reset DISTRIBUTION=example)用的是它自己的、限定在 example 范围内的卷,删不掉 hybridinference_postgres_data。
查看数据库
docker exec -it hybridinference-postgres psql -U "$DB_USER" -d "$DB_NAME"
几个好用的起点:\dt 看表清单,\d api_logs 看请求日志的列。make ps 会列出发布出来的绑定——Postgres 和 pgAdmin 都应该显示成 127.0.0.1:...;不是这样就说明数据库在这台机器之外也在监听。
可选:pgAdmin
Compose 栈里带了一个 pgAdmin 服务,给偏好图形界面的人用。它由 profile 把关——不点名 admin profile 就没有任何东西会启动它——而且它完全可选;上面的 psql 什么都能做。
启动与重启
这个 profile 必须出现在每一条 Compose 命令上,不只是第一条:
make up COMPOSE_PROFILES=admin
make down COMPOSE_PROFILES=admin
漏掉它不会大声报错,只会在两个方向上都做错事:不带 profile 的 make up 会启动其他所有服务、跳过 pgAdmin;不带 profile 的 make down 会把周围的一切都删掉,却留着 pgAdmin 容器在跑(然后 Compose 报告网络仍在使用中)。重启的两半都要带上 COMPOSE_PROFILES=admin。
认证
两道互相独立的关卡,而且默认情况下没有一道是 pgAdmin 自己的登录:
PGADMIN_CONFIG_SERVER_MODE默认为False,这样提供出来的 pgAdmin 自身没有登录。在.env里把它设为True,pgAdmin 就会要PGADMIN_EMAIL/PGADMIN_PASSWORD(这两个自身的默认值是admin@local.dev/admin——启用之前先改掉)。从控制台的
/pgadmin/进去时,请求由一个 Next.js route handler(apps/frontend/src/app/pgadmin/[[...path]]/route.ts)按管理员会话把关,它会请后端校验调用方。直接走它发布的端口(127.0.0.1:${PGADMIN_PORT:-5050},只监听回环)进去时,这道关卡不生效——请用 SSH 隧道:ssh -L 5050:127.0.0.1:5050 <user>@<your-gateway-host>
pgAdmin 的主密码(master password)提示永远不会出现:PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED 在 Compose 文件里被钉死为 "False",而且没有任何环境变量能改它。
注册数据库
Servers→ 右键 →Register→Server。General:名字随便起。
Connection:host 填
postgres,port 填5432,maintenance database 填DB_NAME,username 填DB_USER,password 填DB_PASSWORD——都用容器内部的值,不是宿主机那侧的端口映射。
重置 pgAdmin
它保存的连接放在 hybridinference_pgadmin_data,这是个普通的项目内卷(不像 Postgres 那个卷,它不是 external):
make down COMPOSE_PROFILES=admin
docker volume rm hybridinference_pgadmin_data # destroys saved connections only
make up COMPOSE_PROFILES=admin