feat: pool spacetime client connections
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
# api-server SpacetimeClient 连接池化设计 2026-04-23
|
||||
|
||||
更新时间:`2026-04-23`
|
||||
|
||||
## 1. 背景
|
||||
|
||||
当前 `api-server` 虽然在 `AppState` 中只持有一个 `SpacetimeClient` 实例,但 `spacetime-client` 内部仍然是:
|
||||
|
||||
1. 每次 procedure / reducer 调用都执行一次 `DbConnection::builder().build()`
|
||||
2. 建连后立即 `run_threaded()`
|
||||
3. 拿到结果后立刻 `disconnect()`
|
||||
|
||||
也就是说,当前问题不是 `api-server` 每次请求都 new 一个 client,而是:
|
||||
|
||||
**每次 client 调用都新建并销毁一条 SpacetimeDB 连接。**
|
||||
|
||||
## 2. 本轮目标
|
||||
|
||||
本轮不继续维持“每次 HTTP 请求一条短连接”的阶段性策略。
|
||||
|
||||
本轮目标改为:
|
||||
|
||||
1. `api-server` 进程内预热并持有一组可复用的 SpacetimeDB 连接
|
||||
2. 每次 HTTP 请求只从池里借一个可用连接执行 procedure / reducer
|
||||
3. 请求完成后归还连接,不主动断开
|
||||
4. 连接失效时自动剔除并按需重建
|
||||
|
||||
## 3. 为什么不直接引第三方池库
|
||||
|
||||
当前仓库使用的是 `spacetimedb-sdk` 生成的 `DbConnection`,不是传统 SQL client。
|
||||
|
||||
它的连接模型包含:
|
||||
|
||||
1. `on_connect`
|
||||
2. `on_disconnect`
|
||||
3. `run_threaded`
|
||||
4. reducer / procedure callback
|
||||
|
||||
这类对象不是标准的 `bb8` / `deadpool` 资源接口。
|
||||
|
||||
当前仓库也没有已经接入的通用资源池库,因此本轮优先在 `spacetime-client` 内实现最小可控池化层,而不是强行套第三方 SQL 风格池库。
|
||||
|
||||
## 4. 池化设计
|
||||
|
||||
## 4.1 结构
|
||||
|
||||
`SpacetimeClient` 内新增一个共享池状态:
|
||||
|
||||
1. `pool_size`
|
||||
2. `Semaphore`
|
||||
3. `Vec<Mutex<Option<PooledConnection>>>`
|
||||
|
||||
其中 `PooledConnection` 持有:
|
||||
|
||||
1. `DbConnection`
|
||||
2. `run_threaded` 返回的后台线程句柄
|
||||
3. 连接唯一 id
|
||||
|
||||
## 4.2 借还模型
|
||||
|
||||
每次调用 procedure / reducer 时:
|
||||
|
||||
1. 先获取 `Semaphore permit`
|
||||
2. 选取一个空闲槽位
|
||||
3. 若槽位已有健康连接,则直接复用
|
||||
4. 若槽位为空或连接已坏,则现场重建
|
||||
5. 调用完成后归还槽位,但不主动断开连接
|
||||
|
||||
## 4.3 健康判断
|
||||
|
||||
当前阶段不做复杂心跳表。
|
||||
|
||||
最小健康策略如下:
|
||||
|
||||
1. procedure / reducer callback 正常完成:连接保持在池中
|
||||
2. 连接在调用期间触发 `on_disconnect`:标记该槽位失效
|
||||
3. 下次借用该槽位时重建连接
|
||||
|
||||
## 4.4 并发策略
|
||||
|
||||
不共享同一个 `DbConnection` 给多个并发请求同时发 procedure。
|
||||
|
||||
原因:
|
||||
|
||||
1. SDK callback 是异步回调模型
|
||||
2. 当前仓库调用层没有 request id 级别的统一 dispatcher
|
||||
3. 多请求共用一条连接容易把回调和调用方绑定关系搞乱
|
||||
|
||||
所以本轮采取:
|
||||
|
||||
**一个池槽位同一时刻只服务一个请求。**
|
||||
|
||||
这本质上是“连接池”,不是“多路复用单连接”。
|
||||
|
||||
## 5. 默认规模
|
||||
|
||||
默认池大小取小值,避免本地开发和轻量部署浪费连接:
|
||||
|
||||
1. 默认 `4`
|
||||
2. 允许通过环境变量覆盖,例如 `GENARRATIVE_SPACETIME_POOL_SIZE`
|
||||
|
||||
## 6. 错误与超时策略
|
||||
|
||||
沿用现有 `SpacetimeClientError` 口径:
|
||||
|
||||
1. 建连失败:`Build` / `Runtime`
|
||||
2. 连接在返回前断开:`ConnectDropped` 或 `Procedure`
|
||||
3. 调用超时:`Timeout`
|
||||
|
||||
新增规则:
|
||||
|
||||
1. 借用池槽位超时,也映射为 `Timeout`
|
||||
2. 某槽位一旦确认断线,必须在池中清空,不能继续复用脏连接
|
||||
3. procedure / reducer 等待结果无论成功、失败还是超时,都必须先归还租约再向上层返回,避免槽位泄漏把池卡死
|
||||
4. 调用期间若连接先收到 `on_disconnect`,当前阶段只标记坏连接;若业务回调未及时返回,则最终由调用超时路径统一清槽并回传错误
|
||||
|
||||
## 7. 与现有文档的关系
|
||||
|
||||
之前 [`AXUM_TO_SPACETIMEDB_ASSET_OBJECT_CONFIRM_CALL_DESIGN_2026-04-21.md`](D:/Genarrative/docs/technical/AXUM_TO_SPACETIMEDB_ASSET_OBJECT_CONFIRM_CALL_DESIGN_2026-04-21.md)
|
||||
中写明“当前阶段每次 HTTP 请求可以建立一条短连接,待真实链路验证稳定后再评估连接池或长连接复用”。
|
||||
|
||||
本轮就是进入这个“下一阶段”:
|
||||
|
||||
1. 保留 `on_connect` 后再发请求的约束
|
||||
2. 去掉“请求完成立即断开”的短连接策略
|
||||
3. 改成 `spacetime-client` 进程内连接池
|
||||
|
||||
## 8. 验收标准
|
||||
|
||||
落地后至少满足:
|
||||
|
||||
1. `api-server` 启动后,`SpacetimeClient` 不再为每次调用单独建连
|
||||
2. 同一进程内连续多个 API 请求可以复用池中连接
|
||||
3. 单个连接断开后不会污染后续请求
|
||||
4. `api-server` 调用侧无需修改业务 handler
|
||||
|
||||
## 9. 一句话结论
|
||||
|
||||
本轮不引第三方 SQL 风格池库,而是在 `spacetime-client` 内实现一层:
|
||||
|
||||
**面向 `DbConnection` 的最小连接池,让 `api-server` 复用长活连接,而不是每次调用都单独建连。**
|
||||
Reference in New Issue
Block a user