10 KiB
Assistant 库:SQLite → PostgreSQL 迁移指南
本文说明如何将 助手主持久化(会话、chat_message、设置、租户等)从默认 SQLite 迁到 PostgreSQL,以及割接前后的操作顺序与排障要点。实现上仍通过同一套 SqliteStore API;仅底层连接与 SQL 方言不同。
更完整的环境变量说明见 ENVIRONMENT_VARIABLES.md 中的「存储与迁移」一节;本文侧重 操作步骤与脚本。
1. 何时需要读本文
- 生产或预发要把 assistant 主库 从
data/ai_ops.sqlite(或自定义 SQLite 路径)迁到 PostgreSQL。 - 需要 一次性导入历史数据,再切换
AIA_ASSISTANT_DB_BACKEND=postgresql上线。 - 排查「PG 上表空 / 导入失败 / 网关连错库」等问题。
若只做 新部署、无历史 SQLite 数据:在空库上建 schema 后,直接配置 PG 与 postgresql 后端即可,不必跑数据导入脚本。
2. 核心概念
| 项目 | 说明 |
|---|---|
| 后端选择 | AIA_ASSISTANT_DB_BACKEND:sqlite(默认)或 postgresql(别名 pg / postgres 等)。 |
| PG 连接串 | AIA_ASSISTANT_DATABASE_URL(或 OPS_ASSISTANT_DATABASE_URL / AIA_ASSISTANT_PG_DSN 等别名)。SQLAlchemy 使用时会规范为 postgresql+psycopg://…。 |
| Schema 来源 | 推荐 alembic upgrade head(alembic.ini 的 script_location = assistant_migrations)。等价方式:在目标库执行 svc/persistence/ddl/postgresql_bootstrap.sql(与迁移首版语义一致,二选一即可,勿混用导致重复建表报错)。 |
| 数据导入 | runtime/operations/scripts/migrate_assistant_sqlite_to_postgresql.py:只拷数据,不建表;要求目标 PG 已存在 schema。默认要求 PG 中待拷贝表 为空(可用 --allow-non-empty 跳过检查,自担重复/FK 风险)。 |
| 网关 | 进程内通过 get_assistant_store() 单例连库;切换 URL 或后端后需重启网关(必要时 reset 相关进程环境)。 |
3. 推荐总体顺序( checklist )
- 在 PostgreSQL 上创建数据库(例如
oclaw),为应用用户授予CREATE/USAGEon schemapublic及后续表所需权限。 - 在目标库上完成 schema:
alembic upgrade head(仓库根执行,且已配置指向该库的DATABASE_URL/ 环境变量),或执行postgresql_bootstrap.sql。 - 不要在未建表的空库上直接启动依赖全表的生产网关(会报错或产生不一致状态)。应完成第 2 步后再启动,或先保持
sqlite仅跑导入机。 - 使用下文 割接脚本 或 直接调用 migrator:先做
--dry-run,确认行数与表列表无误后再实导。 - 将运行环境改为
AIA_ASSISTANT_DB_BACKEND=postgresql并设置AIA_ASSISTANT_DATABASE_URL(与导入时同一目标库)。 - 重启网关及所有使用
get_assistant_store()的 worker,使新配置生效。 - 验证管理台会话列表、发一条测试消息、必要时跑
AIA_TEST_PG_URL相关测试(见下文)。
4. Schema:Alembic(推荐)
仓库根:
# 已设置 AIA_ASSISTANT_DATABASE_URL 指向目标 PG,且已安装依赖(含 alembic、psycopg)
cd /path/to/oclaw
export PYTHONPATH=.
alembic upgrade head
首版迁移 assistant_migrations/versions/001_assistant_pg_initial.py 会读取 svc/persistence/ddl/postgresql_bootstrap.sql 并逐条执行(仅当方言为 postgresql 时)。
说明:downgrade() 未实现;回滚依赖 数据库备份 / 文件备份,而非 Alembic 反向迁移。
5. 数据导入:migrate_assistant_sqlite_to_postgresql.py
路径:runtime/operations/scripts/migrate_assistant_sqlite_to_postgresql.py。
行为摘要
- 从 SQLite 读表顺序(按
PRAGMA foreign_key_list拓扑),仅插入 SQLite 与 PGpublic中同名的共有列。 - 默认若任一待拷贝表在 PG 中 已有行 则 退出(避免重复);
--allow-non-empty可关闭该检查。 --dry-run:只统计源表行数,不写 PG。
常用参数
| 参数 | 含义 |
|---|---|
--sqlite PATH |
源 SQLite 文件路径。 |
--sqlite-from-db-path |
使用 db_path()(受 AIA_ASSISTANT_DB_PATH 等影响)作为源库。 |
--load-system-env |
合并 _local/system.env(与网关 load_system_env 思路一致,便于设备上只维护一份 env)。 |
--pg-url URL |
目标 PG;不设则从环境变量读取(见脚本 --help)。 |
--dry-run |
预演。 |
--allow-non-empty |
允许目标表非空(慎用)。 |
--batch N |
批量插入行数,默认 500。 |
示例(本机预演)
cd /path/to/oclaw
export PYTHONPATH=.
export AIA_ASSISTANT_DATABASE_URL='postgresql+psycopg://USER:PASS@127.0.0.1:5432/oclaw'
python runtime/operations/scripts/migrate_assistant_sqlite_to_postgresql.py \
--load-system-env --sqlite-from-db-path --dry-run
# 确认无报错后去掉 --dry-run 再执行
显式路径示例
python runtime/operations/scripts/migrate_assistant_sqlite_to_postgresql.py \
--sqlite data/ai_ops.sqlite \
--pg-url 'postgresql+psycopg://postgres:pass@127.0.0.1:5432/oclaw'
6. 割接包装脚本(备份 + dry-run + 导入)
在跑正式导入前,强烈建议备份 SQLite 文件。脚本会把副本放到 data/pg_cutover_backups/(可改参数或环境变量)。
6.1 Windows(PowerShell)
runtime/operations/scripts/cutover_sqlite_to_postgresql.ps1
典型用法:
cd D:\path\to\oclaw
# 若 URL 写在 _local\system.env 中:
.\runtime\operations\scripts\cutover_sqlite_to_postgresql.ps1 -LoadSystemEnv
# 或显式传入:
.\runtime\operations\scripts\cutover_sqlite_to_postgresql.ps1 -PgUrl "postgresql+psycopg://..."
常用开关:-SqlitePath、-BackupDir、-SkipDryRun、-DryRunOnly、-NoBackup(不推荐生产)、-LoadSystemEnv。
6.2 Linux / bash
runtime/operations/scripts/cutover_sqlite_to_postgresql.sh
- 环境变量:
SQLITE_PATH、BACKUP_DIR、NO_BACKUP、SKIP_DRY_RUN、DRY_RUN_ONLY等(见脚本头部注释)。 - 默认会
--load-system-env并解析 SQLite 路径。
仅 导入(不复制备份逻辑时也可用轻量包装):
runtime/operations/scripts/assistant_import_sqlite_to_postgresql.sh(内部调用 migrator 的 --load-system-env --sqlite-from-db-path)。
7. 切换运行时与网关
导入完成后:
- 设置
AIA_ASSISTANT_DB_BACKEND=postgresql。 - 设置
AIA_ASSISTANT_DATABASE_URL(与导入目标一致)。 - 重启 网关、wiki worker、渠道 worker 等所有持有 DB 连接的进程。
注意:get_assistant_store() 在进程内会按 (backend, 连接键) 缓存;连接键对 PostgreSQL 使用 URL 的哈希(避免在内存键中携带明文口令)。改环境后必须重启进程,否则会连旧库。
8. 验证与自动化测试
- 管理台:会话列表、打开历史会话、发送一条新消息,确认读写正常。
- 单测:设置环境变量
AIA_TEST_PG_URL后,部分用例会对真实 PG 跑冒烟(例如tests/test_database_backend.py)。详见ENVIRONMENT_VARIABLES.md中AIA_TEST_PG_URL说明。
9. 备份与回滚
- 割接脚本 默认将 SQLite 复制到
data/pg_cutover_backups/<原名>_pre_pg_<时间戳>.sqlite。 - 若导入后需回退到 SQLite:恢复该备份文件为
data/ai_ops.sqlite(或你的AIA_ASSISTANT_DB_PATH),将AIA_ASSISTANT_DB_BACKEND改回sqlite或未设置,重启服务。 - PostgreSQL 侧回滚:依赖实例级备份(快照 /
pg_dump),不在应用脚本内自动完成。
10. 仅清空 PG 中的对话数据(可选)
若要在 PostgreSQL 上删除所有会话及相关行(危险操作),使用:
runtime/operations/scripts/clear_postgres_chat_sessions.ps1(加载_local/system.env、强制 PG、设置确认变量后调用 Python),或python runtime/operations/scripts/clear_all_chat_sessions.py --yes --postgresql
且必须同时设置AIA_CONFIRM_CHAT_SESSION_WIPE=1。
详见脚本内说明;勿在未确认库名时针对生产执行。
11. 常见问题(排障)
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 导入报「表非空」 | PG 已有数据 | 换新库或 --allow-non-empty(谨慎) |
| 网关仍像连 SQLite | 环境未生效 / 未重启 | 检查进程环境、assistant_store 单例 |
invalid_credentials / 连接失败 |
URL 或网络或权限 | 用 psql 或 psycopg 单独测连 |
| 部分表未拷贝 | PG 无同名表 | 先 alembic upgrade head;查看 migrator 打印的 skip (not in PG public schema) |
| 仍为 SQLite 但已填 PG URL | 后端未切到 postgresql |
首次解析 URL 会 UserWarning;确认 AIA_ASSISTANT_DB_BACKEND 与重启 |
| 连接 PG 长时间无响应 | 网络/防火墙 | 可调 AIA_ASSISTANT_PG_CONNECT_TIMEOUT(默认 10s,见 ENVIRONMENT_VARIABLES.md) |
12. 相关文件索引
| 路径 | 用途 |
|---|---|
alembic.ini |
Alembic 配置,script_location = assistant_migrations |
assistant_migrations/ |
PG schema 版本链 |
svc/persistence/ddl/postgresql_bootstrap.sql |
初始 DDL(与首版迁移同源) |
svc/persistence/pg_adapter.py |
psycopg 连接(含 connect_timeout) |
svc/persistence/assistant_store.py |
get_assistant_store() 工厂 |
runtime/operations/scripts/migrate_assistant_sqlite_to_postgresql.py |
数据导入 |
runtime/operations/scripts/cutover_sqlite_to_postgresql.ps1 / .sh |
备份 + 预演 + 导入 |
runtime/operations/scripts/clear_all_chat_sessions.py |
清空会话(需确认 env) |
docs/ENVIRONMENT_VARIABLES.md |
环境变量权威列表 |
维护建议:若变更割接流程或新增迁移步骤,请同步更新本文与 ENVIRONMENT_VARIABLES.md 中的摘要,避免文档分叉。