Files
memind/docs/goose-session-postgres-migration.md
john 1798c07d42 docs: Update documentation and release rules
- 架构和规划文档更新
- 开发、工程、生产发布规则更新
- 服务隔离和升级指南
- README 更新
2026-06-27 08:25:06 +08:00

4.6 KiB

Goose SessionStorage SQLite → PostgreSQL 迁移指南

目标:让 goosed 多实例真正 stateless,session 状态存 Postgres 而非本地 SQLite。

工作量:改 1 个结构体 + 3 处 SQL,新增 connection pool 初始化。预计 2-4 小时。

前置:fork /Users/john/Project/tkmind_go 或在那个仓库新建分支。

改造点

1. crates/goose/src/session/session_manager.rs:525

当前代码结构:

pub struct SessionStorage {
    pool: SqlitePool,  // <-- 改这里
    // ...
}

impl SessionStorage {
    pub fn new(data_dir: PathBuf) -> Self {
        let options = SqliteConnectOptions::default()
            .filename(data_dir.join(SESSIONS_FOLDER))
            .create_if_missing(true)
            .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal);
        
        let pool = SqlitePoolOptions::new()
            .connect_lazy_with(options);  // <-- 这里要改
        // ...
    }
}

改成:

pub struct SessionStorage {
    pool: sqlx::postgres::PgPool,  // 改这行
    // ...
}

impl SessionStorage {
    pub fn new(connection_string: String) -> Self {
        // 从环境变量或参数读取 PostgreSQL 连接串
        // e.g. GOOSE_SESSION_DB_URL=postgresql://...
        let pool = sqlx::postgres::PgPoolOptions::new()
            .max_connections(20)
            .connect_lazy(&connection_string);
        // ...
    }
}

2. schema.sql / 迁移 SQL

SQLite 专用语法转 PostgreSQL:

问题 SQLite PostgreSQL 改法
主键自增 INTEGER PRIMARY KEY AUTOINCREMENT SERIAL PRIMARY KEY 改为 BIGSERIALGENERATED ALWAYS AS IDENTITY
类型 TEXT(什么都行) 严格类型(JSONB/TEXT/TIMESTAMP) JSONB 存 JSON,用 TIMESTAMP 存时间戳
模式检查 sqlite_master information_schema 改用 information_schema.tables
WAL/Journal PRAGMA journal_mode=WAL N/A 删除,PG 有自己的 WAL
Collate COLLATE NOCASE 用 collation 或 LOWER() 改成 LOWER(column) = LOWER($1)

例:

-- SQLite
CREATE TABLE sessions (
    id TEXT PRIMARY KEY,
    data JSON,
    created_at INTEGER
);

-- PostgreSQL
CREATE TABLE sessions (
    id TEXT PRIMARY KEY,
    data JSONB,
    created_at BIGINT
);

3. FromRow<SqliteRow>FromRow<PgRow>

当前:

impl sqlx::FromRow<'_, sqlx::sqlite::SqliteRow> for Session {
    fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
        // column 取值方式对 SQLite 优化
    }
}

改成:

impl sqlx::FromRow<'_, sqlx::postgres::PgRow> for Session {
    fn from_row(row: &sqlx::postgres::PgRow) -> Result<Self, sqlx::Error> {
        // 取值方式对 PG 优化(JSONB 用 get_raw 后 JSON 解析)
    }
}

4. 调用处:初始化时读环境变量

地点: main.rs / 启动 SessionManager

改法:

// 改前
let session_manager = SessionManager::new(data_dir);

// 改后
let session_db_url = std::env::var("GOOSE_SESSION_DB_URL")
    .expect("set GOOSE_SESSION_DB_URL env");
let session_manager = SessionManager::new(session_db_url);

环境变量

启动 goosed 时设置:

export GOOSE_SESSION_DB_URL=postgresql://boot:PASSWORD@120.26.184.105:5432/goose_sessions
goosed agent

数据库需提前创建:

psql -U boot -h 120.26.184.105 -c "CREATE DATABASE goose_sessions;"

测试步骤

  1. 编译:

    cd /Users/john/Project/tkmind_go
    GOOSE_SESSION_DB_URL=... cargo build --release --package goose-server --bin goosed
    
  2. 启两个实例,挂同一 PG:

    goosed agent &
    goosed agent &  # 另一个进程/端口
    
  3. 同一 session 轮换到两个实例:

    • 客户端起 session on instance 1
    • 后续命令路由到 instance 2 —— 状态应无缝恢复

文件清单

修改的文件(全在 crates/goose/src/):

  • session/session_manager.rs —— 3-5 处改动
  • session/mod.rs —— 可能有 FromRow(如果有多个 impl)
  • session/legacy.rs —— 如果涉及导入(通常不用改)
  • Cargo.toml —— 检查 sqlx features(postgres 要打开)

风险与回滚

  • 数据迁移: SQLite→PG 用 pgloader 命令行工具(一条命令搞定 schema+数据+类型映射)
  • 灰度: 先让新 goosed 实例用 PG,老实例仍用 SQLite,流量按 session 分配。验证无误后全量切换。
  • 回滚: PG 有完整 session 数据,可随时起新 SQLite 实例从 PG 导回。

相关文档