README「用 PostgreSQL」按复核补全:SSL 的写法(sslmode= 自动换、证书走 PGSSLROOTCERT 等环境变量、别的 libpq 参数启动时报错)、没装 [postgres] 的提示、每个进程的连接数预算(应用库最多 30、断点池最多 20,max_connections=100 时起几个进程怎么算、怎么调)、孤儿行和 NOT NULL 空值导入时怎么处理、NULL 排序补写的范围和列表的决胜键、JSON 按类型比、文本列的 NUL、不分大小写的搜索看建库的区域设置、记录表 records/*.db 原地写要停服务或用 SQLite 备份接口拷;.env.example 补 SSL 的写法

This commit is contained in:
ThearchyHelios
2026-10-19 12:07:31 +08:00
parent 5272d8f028
commit 1ab500dfbd
2 changed files with 28 additions and 6 deletions
+2
View File
@@ -14,6 +14,8 @@
# 设成 PostgreSQL 时整个应用库和断点都放进去(要 [postgres] 额外依赖,environment.yml 已带);数据目录照样要有。
# 从现有的 SQLite 数据目录搬过去:python -m app.db copy-to-postgres data(README「用 PostgreSQL」)
# AGENTLAB_DATABASE_URL=postgresql+asyncpg://agentlab:密码@127.0.0.1:5432/agentlab
# 要 SSL 时加 ?sslmode=require(verify-ca / verify-full 同理);证书文件用环境变量 PGSSLROOTCERT / PGSSLCERT / PGSSLKEY,不写在地址里
# AGENTLAB_DATABASE_URL=postgresql+asyncpg://agentlab:密码@db.example.com:5432/agentlab?sslmode=verify-full
# 断点放另一个库时才设(不设就和应用库同一个库)
# AGENTLAB_CHECKPOINT_URL=postgresql://agentlab:密码@127.0.0.1:5432/agentlab_checkpoints
# 连接池:每个进程常驻几条、高峰再多开几条;设成 0 不用连接池(前面有 PgBouncer 时)
+26 -6
View File
@@ -1044,16 +1044,30 @@ AGENTLAB_DATABASE_URL=postgresql+asyncpg://agentlab:<密码>@127.0.0.1:5432/agen
- 要 `[postgres]` 额外依赖(`environment.yml`、Docker 镜像都已带上);库要先建好(`CREATE DATABASE agentlab`),
表由服务启动时建,老版本建的库启动时补列补索引,同 SQLite。启动日志第一句写用的是哪种库、哪台主机哪个库(不写密码)。
- 没装 `[postgres]` 就设了这一项,启动时直接报要装什么(`pip install -e 'backend[postgres]' -c backend/requirements.lock`)。
- SSL:地址后加 `?sslmode=require`(或 `verify-ca`、`verify-full`;asyncpg 的写法 `?ssl=require` 也认),启动时换成 asyncpg
认的参数。证书文件不写在地址里(asyncpg 不收 `sslrootcert` 这类参数,写了启动就报错),设环境变量 `PGSSLROOTCERT`、
`PGSSLCERT`、`PGSSLKEY`——应用库的 asyncpg 和断点库的 libpq 都认。地址里别的 libpq 参数(`application_name`、
`connect_timeout` 之类)asyncpg 也不收,同样启动时报出来。
- 断点(LangGraph 的 checkpoints、checkpoint_blobs、checkpoint_writes、checkpoint_migrations 四张表,不和应用的表重名)
缺省放在同一个库;要放别处设 `AGENTLAB_CHECKPOINT_URL=postgresql://…`。
缺省放在同一个库;要放别处设 `AGENTLAB_CHECKPOINT_URL=postgresql://…`(这一项是 libpq 的写法,`sslrootcert` 等可以直接写)。
- 连接池:每个进程常驻 `AGENTLAB_DB_POOL_SIZE`(缺省 10)条、高峰再多开 `AGENTLAB_DB_MAX_OVERFLOW`(缺省 20)条,取连接前探活,
30 分钟换一次;断点库另有一个小池子。前面有 PgBouncer(事务模式)时:`AGENTLAB_DB_POOL_SIZE` 设成 0(不用连接池)、
地址后加 `?prepared_statement_cache_size=0`;断点库用预备语句,要 PgBouncer 1.21 以上开 `max_prepared_statements`,
或者用 `AGENTLAB_CHECKPOINT_URL` 让它直连 PostgreSQL。
- 连接数的账:每个进程最多 `AGENTLAB_DB_POOL_SIZE + AGENTLAB_DB_MAX_OVERFLOW`(缺省 10 + 20 = 30)条应用库连接,断点库的池子
最多 `min(AGENTLAB_MAX_CONCURRENT_RUNS, 20)` 条(至少 4,缺省 20),合起来一个进程最多 50 条;导入命令另占几条。PostgreSQL
缺省 `max_connections=100`,还要留几条给 `psql`、备份和超级用户(`superuser_reserved_connections` 缺省 3):一个进程够用,
起几个进程就是几倍——要么把 `max_connections` 调大(约「进程数 × 50 + 20」),要么调小两边的池子(比如 POOL_SIZE=5、
MAX_OVERFLOW=10、MAX_CONCURRENT_RUNS=10,一个进程最多 25 条),要么前面放 PgBouncer。
- 和 SQLite 上的行为一致:字符串列不限长(SQLite 本来就不查长度,模型里写的长度只是示意)、文本按字节比大小和排序
(列上带 COLLATE "C",同 SQLite,不看建库时选的区域设置)、整数列是 BIGINT、JSON 列存原文(PostgreSQL 的 json,不是 JSONB:
JSONB 会重排对象里的键)、时间存不带时区的 UTC、排序时可空列的 NULL 照 SQLite 的位置(升序在前、降序在后)。两处 SQLite 照存而 PostgreSQL 不收的值会被换掉:JSON 里的 NaN / Infinity 写成 null,字符串里的
NUL 字符去掉。
JSONB 会重排对象里的键)、时间存不带时区的 UTC、排序时可空列的 NULL 照 SQLite 的位置(升序在前、降序在后;只管直接按列
排的,按聚合、coalesce 之类的表达式排的不管,列表都另带了 id 之类的决胜键,并列时两种库排出来一样)、按 JSON 里的键筛选
按 JSON 的类型比(true 不等于 1)。两处 SQLite 照存而 PostgreSQL 不收的值会被换掉:JSON 里的 NaN / Infinity 写成 null,
文本(JSON 里的、普通文本列里的)中的 NUL 字符去掉。
- 不分大小写的搜索(运行列表按名字、对象索引按注释):文本列是 COLLATE "C",列上的 ILIKE 只认 ASCII 的大小写,所以按建库时的
区域设置折大小写——建库用 `en_US.UTF-8`、`C.UTF-8` 这类区域,É/é、全角A/a 都认;用纯 `C` 区域建的库只认 ASCII。
- `AGENTLAB_DATA_DIR` 照旧要设:工件、上传表格的版本库、原件、附件、记录表、沙箱工作区、`.secret_key` 都还是文件。
**从 SQLite 搬过来。** `python -m app.db copy-to-postgres` 把数据目录里的 `agentlab.db` 和 `checkpoints.db` 导进 PostgreSQL,
@@ -1073,12 +1087,18 @@ cd backend && python -m app.db copy-to-postgres ../data postgresql+asyncpg://age
空间,缺省系统临时目录)。读的是 SQLite 备份接口拷出的一份快照,在快照上先补到当前的表结构,来源的内容不改。
- 重跑是幂等的:目标上已有同一主键(或撞了别的唯一约束)的行一律不动,只补上还没有的,不重复、也不覆盖——所以导入之后又在
SQLite 上改过的行,重跑不会带过去;要整份重来就清空目标库再导。断点同理,已有的不重写。
- 能查到的只有上面两样:服务连着别的数据目录、另一台机器上的服务连着目标库,查不出来,要自己保证。来源旁边的 -wal 是上次
强杀留下的,确认服务已停后加 `--assume-stopped`。
- 能查到的只有上面两样:服务连着别的数据目录、另一台机器上的服务连着目标库,查不出来,要自己保证。服务正常停下时会关掉
所有连接、-wal 随之消失;还在的是上次强杀留下的,确认服务已停后加 `--assume-stopped`。
- 老库里 SQLite 存得下、PostgreSQL 不收的会先在快照上处理、在报告里照实列出:老版本补列时没带缺省值、留着 NULL 的 NOT NULL 列
填成模型的缺省值(启动时的迁移在 SQLite 上也这么填,两边存的一样);**孤儿行**——外键指向已经不在的行(SQLite 当时没强制
外键留下的)——外键是 SET NULL 的置空,别的(CASCADE、不可空)不导入,按表列出条数,不算核对失败(父行被删时它们本来就该
跟着删)。自增主键的序列接在 SQLite 用过的最大号后面(删过的号不复用)。
- 导完 `agentlab.db` 留在数据目录里也没关系(可以当回退用):它照样算 AgentLab 自己的文件,不能登记成数据源。
**备份。** PostgreSQL 用 `pg_dump`(应用表和断点在同一个库里就一次带上),数据目录照旧整份拷。两样最好在停服务时一起做;
不停服务的话先 dump 库、再拷数据目录:工件、原件按内容寻址、只增不改,库里引用到的文件拷数据目录时都在。
不停服务的话先 dump 库、再拷数据目录:工件、原件按内容寻址、只增不改,库里引用到的文件拷数据目录时都在。例外是填报的记录表
`records/*.db`:SQLite 文件、原地追加写,开着服务直接拷可能拷到写了一半的——要么停服务拷,要么一个个用 SQLite 的备份接口拷
(`sqlite3 data/records/x.v1.db ".backup /备份目录/x.v1.db"`)。
**多台机器。** 应用库换成 PostgreSQL 之后,几台机器要用同一份数据目录:放在共享存储(NFS 之类)上,每台挂在**同一个绝对路径**
(库里记着上传表格各版本库文件的绝对路径)。文件都是先写临时文件再改名,读的一方看不到写了一半的。但要说实话:数据目录里还有