agentscope-ai / agentscope-ai/agentscope-java

[Bug]:配了远程 store 后 session 仍写本地工作副本,且该副本的路径与路由下层不一致、无人可读

Đang mở
#2,691 0 bình luận 0 reaction 0 người được giao Xem trên GitHub
area/core/memory bug
Ngôn ngữ chính
Java
Star
5.6k
Fork
1.3k
Merge trung bình
4 ngày 12 giờ
Pull request đã merge (30 ngày)
77

Mô tả

> Labels 建议:`bug` / `harness` / `memory`

## 环境

| | |
|---|---|
| agentscope-harness | 2.0.2 |
| store | `agentscope-extensions-mysql` 2.0.2(`JdbcStore` → MySQL 8.0.21) |
| 场景 | **非 sandbox**,`CompositeFilesystem` + 远程 store,`IsolationScope.USER` |
| 部署 | K8s 2 副本,workspace 是 **`emptyDir`**(每 Pod 独立、随 Pod 生灭) |

## 问题

配置了远程 store 之后,两类运行态文件走了**两条完全不同的持久化路径**:

- `MEMORY.md` / `memory/*.md` → 只走 `AbstractFilesystem`,落远程,本地不留任何文件 ✅
- `sessions/*.jsonl` → **先同步写本地**,再异步镜像到远程

```java
// SessionTree.java:310-322
public void flush() {
...
appendToFile(contextFile, toWrite); // 本地,Files.newBufferedWriter (:534/:556)
appendToFile(logFile, toWrite);
scheduleMirror(); // 异步 fire-and-forget 推远程 (:459-467)
}
```

在 emptyDir + 多副本部署下,这个「本地工作副本」既不持久、也不共享,是纯粹的写放大。

## 更关键的:本地那份文件没有任何读者

我们实测发现 session 文件在系统里存在**三个互不相通的位置**:

| | 路径 | 由谁决定 |
|---|---|---|
| ① 实际写入的本地副本 | `workspace//agents//sessions/` | `resolveSessionContextFile` → `getSessionDir:332` → `resolveRuntimeDataPath:235`(**带命名空间前缀**) |
| ② 路由的下层模板 | `workspace/agents//sessions/` | `RemoteFilesystemSpec.overlayRoute():250` 的 `new LocalFilesystem(dir, true, 10, null)`(**namespaceFactory = null,不带前缀**) |
| ③ 远程 | `agents␟␟users␟␟sessions␟` | `mirrorToFilesystem` 用显式传入的 `contextRelPath`(`resolveRelativePath:634-644`) |

**①和②永远不会相遇**:overlay 的下层去 `workspace/agents/...` 找,而文件写在 `workspace//agents/...`。

所以那份本地副本,唯一的读者只有 `SessionTree` 自己(`load()`)和 `SessionSearchTool`(它构造 `SessionTree` 时第三个参数传的是 `null`,即 **local-only 模式**,见 `SessionSearchTool:199/279`)。经由 `AbstractFilesystem` 的任何读取路径都看不见它。

### 实测数据

本地(`ls`):

```
workspace/CY25352/agents/agent-qa/sessions/2087822803351994370.jsonl
workspace/CY25352/agents/agent-qa/sessions/2087822803351994370.log.jsonl
```

远程(`SELECT` 自 `agentscope_store`):

```
agents␟agent-qa␟users␟CY25352␟sessions␟ /2087822803351994370.jsonl
agents␟agent-qa␟users␟CY25352␟sessions␟ /2087822803351994370.log.jsonl
```

镜像本身是成功的;问题在于本地那份的存在既无必要,路径也与 overlay 下层对不齐。

## 影响

1. **写放大**:每轮问答都同步写两个本地文件,而它们在 emptyDir 上重启即丢
2. **丢数据窗口**:`scheduleMirror()` 是 fire-and-forget(`MIRROR_EXECUTOR` 是单线程 daemon,`:80-89`),失败只打 WARN;进程被杀时队列里未完成的镜像直接丢失
3. **副本间分歧**:每个 Pod 各写各的本地副本,靠 `load()` 时的 union-merge 才能拼全
4. **路径不一致本身就是缺陷**:即使有人想利用「本地作为远程不可用时的降级读」,也会因为①②路径不同而失效

### 5. 「本地优先」强制了整份重传,单会话内成本按 O(n²) 增长

因为本地是权威工作副本、远程只是镜像,同步时只能把**整个文件**推上去 ——
`RemoteFilesystem` 没有追加语义,`uploadFiles` 是整份覆盖:

```java
// SessionTree.java:576-590
private void mirrorToFilesystem(Path file, String relativePath) {
...
byte[] bytes = Files.readAllBytes(file); // 整份读
filesystem.uploadFiles(fsRc, List.of(Map.entry(relativePath, bytes))); // 整份传
}
```

而 `.log.jsonl` 按设计是 **append-only、从不压缩**。于是一个 n 轮的会话,
第 n 轮要重传 n 轮的内容,**整场会话的传输量是 O(n²)**,两个文件各来一遍。

每轮的完整开销(都在响应路径上):

| 动作 | 代价 |
|---|---|
| `tree.load()` | 整份读本地 |
| `tree.syncFromRemote()` | **一次同步的远程读**(整份) |
| `tree.flush()` → `appendToFile` ×2 | 本地追加(廉价) |
| `scheduleMirror()` → `mirrorToFilesystem` ×2 | 整份读 + **整份远程写**,×2 个文件 |

如果 session 直接走 `AbstractFilesystem`(或至少做增量镜像),这一项可以降下来。

## 建议

任一即可:

- 配置了远程 store(或 `filesystem != null`)时,`SessionTree` 直接走 `AbstractFilesystem`,不再维护本地工作副本
- 或提供开关(如 `SessionTree` 的 local-copy 模式 / `CompactionConfig` 级别的配置)让部署方选择
- 至少**让①和②的路径对齐**:要么本地副本不加命名空间前缀,要么路由下层也加上 —— 现状是两处对同一份数据用了不同的路径约定

## 相关

[#2654](https://github.com/agentscope-ai/agentscope-java/issues/2654) 从 **e2b sandbox** 视角提出了同一组现象,并询问这是否为有意设计。本条补充的新信息:

- **非 sandbox 场景(`CompositeFilesystem` + `JdbcStore`)同样复现**,说明这不是 sandbox 特有
- 本地副本的路径与路由下层**不一致**,因此它并不能充当降级读的来源
- 「本地优先」直接导致每轮整份重传,单会话内成本 O(n²)(见影响第 5 点)

[#2682](https://github.com/agentscope-ai/agentscope-java/issues/2682)(`MemoryFlushMiddleware` 拖慢事件流完成、回复后有可感延迟)
与本条是同一条链路上的问题:那一轮同步的 `syncFromRemote()` 远程读正落在响应路径上。
本条主张的「远程 store 在场时不再维护本地工作副本」如果成立,可以一并缓解。

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.