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

166 lines
4.6 KiB
Markdown

# 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`
**当前代码结构:**
```rust
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); // <-- 这里要改
// ...
}
}
```
**改成:**
```rust
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` | 改为 `BIGSERIAL``GENERATED 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)` |
**例:**
```sql
-- 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>`
**当前:**
```rust
impl sqlx::FromRow<'_, sqlx::sqlite::SqliteRow> for Session {
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
// column 取值方式对 SQLite 优化
}
}
```
**改成:**
```rust
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`
**改法:**
```rust
// 改前
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 时设置:
```bash
export GOOSE_SESSION_DB_URL=postgresql://boot:PASSWORD@120.26.184.105:5432/goose_sessions
goosed agent
```
数据库需提前创建:
```bash
psql -U boot -h 120.26.184.105 -c "CREATE DATABASE goose_sessions;"
```
## 测试步骤
1. **编译:**
```bash
cd /Users/john/Project/tkmind_go
GOOSE_SESSION_DB_URL=... cargo build --release --package goose-server --bin goosed
```
2. **启两个实例,挂同一 PG:**
```bash
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 导回。
## 相关文档
- [Goose Scale Architecture](goose-scale-architecture-2026-06-26.md) —— 背景与决策
- [g2 Load Balancing](g2-load-balancing.md) —— 路由与灰度机制