agentscope-ai / agentscope-ai/agentscope-java
[Bug]:配置 AgentStateStore 时 stateCache 只写不读却无界驻留,建议提供失效策略或不填充
- 主要语言
- Java
- 星标
- 5.6k
- 派生
- 1.3k
- 平均合并
- 4 天 12 小时
- 30 天内合并 PR
- 77
描述
**版本:** 2.0.0(`main` 分支同一份代码)
**模块:** `agentscope-core`
**环境:** 长驻服务进程,通过 `.stateStore(...)` 配置 `MysqlAgentStateStore`
## 概述
配置了 `AgentStateStore` 时,`ReActAgent#stateCache` 在调用路径上**只写不读**——每次调用都无条件回源并覆盖该条目,而调用期间的状态访问走的是 `CallExecution` 的直接引用。也就是说它没有承担任何缓存收益,却无界驻留、从不失效。
文档提到「缓存随会话数增长,除非单进程处理数百万会话否则通常可忽略」。本 issue 想说明的是:在配置了状态存储的部署形态下,这份驻留**没有对应的上行收益**,因此值得提供一个失效手段。
## 依据
`activateSlotForContext` 的两个分支使用了语义不同的方法:
```java
if (stateStore != null) {
loaded = loadOrCreateAgentStateForSlot(stateStore, uid, sid, ...); // 无条件回源
stateCache.put(slot, loaded); // 无条件覆盖 —— 非缓存语义
} else {
loaded = stateCache.computeIfAbsent(slot, k -> // 命中则复用 —— 缓存语义
loadOrCreateAgentStateForSlot(null, uid, sid, ...));
}
```
真正的「命中即复用」只存在于**没有**配置状态存储的分支。配置了存储时永远是 `put`,每次调用都查一次后端。
调用进行期间也不读该 map——`CallExecution` 持有的是对象硬引用,随 Reactor Context 传递:
```java
CallExecution scope = new CallExecution(loaded, loadedEngine, slot); // 同一实例的直接引用
private CallExecution scopeFrom(ContextView cv) {
Object scope = cv.getOrDefault(CALL_SCOPE_KEY, null); // 从 Reactor Context 取,不碰 stateCache
...
}
protected AgentState stateForCall(Object callScope) {
return callScope instanceof CallExecution ce ? ce.state : getAgentState();
} // ↑ 仅在调用之外才退回 map(且是 default 槽位)
```
因此在配置了状态存储时,`stateCache` 的实际读取者只有:
| 读取者 | 说明 |
|---|---|
| `getAgentState(userId, sessionId)` | admin API,javadoc 标注 "suitable for admin APIs and tests" |
| `saveAgentState(userId, sessionId)` | 同上 |
| 已废弃的 `interrupt(InterruptSource)` | 读取的是 `(null, defaultSessionId)` 默认槽位,非用户会话 |
而在 `agentscope-core` 与 `agentscope-harness` 中,对 `stateCache` 和 `permissionEngineCache` **没有任何** `remove` / `clear` / 失效 / TTL / 容量上限。`activateSlotForContext` 放进去的条目驻留至 JVM 结束。
## 影响
以我们的生产数据为例(内部只读答疑服务,流量很低):
| 指标 | 值 |
|---|---|
| 会话数 | 76 |
| 序列化总量 | 3.49 MB |
| 单会话均值 | 47 KB |
| 单会话峰值 | 315 KB |
| 新增速率 | 约 11 会话/天 |
堆内 `AgentState` 比序列化 JSON 更大(字符按 UTF-16 存储,另加 `Msg` / `ContentBlock` / 集合的对象开销),按 2~4 倍估算约 100~250 KB/会话。
当前量级完全无害,且滚动发布会周期性重置。但由于无上界,占用随「流量 × 进程存活时长」线性增长:例如 500 会话/天、30 天不重启时,单进程约 15000 个条目、1.5~3.7 GB。对于高流量或长驻不发布的部署,这会成为实际约束,而目前没有任何配置手段可以介入。
## 诉求(按侵入性从低到高)
1. **提供可配置的失效策略**,例如 `stateCacheMaxSize` / `stateCacheTtl`,或允许注入自定义 `Map` 实现。
2. **配置了 `AgentStateStore` 时不填充 `stateCache`。** 调用路径本就不读它;`getAgentState` / `saveAgentState` 这两个 admin API 在配置了存储时本就应当回源——这与 #_____(issue ① 编号)的修复方向一致,可一并处理。
3. **暴露公开的逐出 API**,例如 `evictSession(userId, sessionId)`,让使用方在会话结束时自行释放。
我们目前只能依赖发版重启兜底,并添加堆内存监控,没有代码层面的手段。
## 关联
`setPermissionMode` 读取该缓存导致的正确性问题另见 #_____(issue ① 编号)。两者共享同一根因:一个进程级、不回源、不失效的 `stateCache`。
贡献指南
评估
这个 Issue 还没有评估数据。