From 13fd7b3a55769778fa2926336e379ac6ca3df424 Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 30 Sep 2026 01:51:11 +0000 Subject: [PATCH] Document the dual-tier storage Details (original message): Document the dual-tier storage, multitenancy, and scaling delivery (proposal, implementation record, test report, usage guide) --- docs/guides/usage.md | 34 ++++++++++- ...9-30-dual-tier-storage-and-multitenancy.md | 39 +++++++++++++ .../2026-09-30-dual-tier-implementation.md | 26 +++++++++ .../2026-09-30-dual-tier-test-report.md | 57 +++++++++++++++++++ 4 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 docs/proposals/2026-09-30-dual-tier-storage-and-multitenancy.md create mode 100644 docs/records/2026-09-30-dual-tier-implementation.md create mode 100644 docs/reports/2026-09-30-dual-tier-test-report.md diff --git a/docs/guides/usage.md b/docs/guides/usage.md index 786effe..9a684e9 100644 --- a/docs/guides/usage.md +++ b/docs/guides/usage.md @@ -164,7 +164,35 @@ HITL POST /api/hitl/{id}/resolve {choiceId|freeText} ## 8. 运维要点 -- 数据:`/agent-system.db`(SQLite WAL)+ `/artifacts/`(对象目录);备份即拷目录。 -- 服务停机:SIGTERM 优雅关闭,在途任务标记 `interrupted`(界面提示,可一键重发)。 -- 升级/回退:`install.sh install/rollback`;数据库迁移随启动自动执行(本版为初始 schema)。 +### 8.1 存储双档(默认嵌入式;多用户切 PG) + +- 默认档(不变):`/agent-system.db`(SQLite WAL)+ `/artifacts/`(对象目录);单二进制、零外部服务。 +- PostgreSQL 档(多用户/多实例):`agent-system serve --dsn "postgres://user:pass@host/db?sslmode=disable"`;原生安装,非容器。自动启用认证与事件扇出(NOTIFY/LISTEN);多实例时工件目录指共享卷(`--artifacts`)。 +- schema 迁移:随启动自动执行(版本表 `schema_migrations`,v1–v5,双方言可回滚:`MigrateDown` 为运维入口)。 + +### 8.2 认证与租户(API key) + +- 嵌入式档默认无认证(本地单用户);PG 档默认要求 API key(`--auth auto|on|off`)。 +- 密钥管理(明文仅创建时显示一次): + +```bash +agent-system apikey create --owner alice --name dev # 嵌入式档 +agent-system apikey create --dsn "postgres://…" --owner bob # PG 档 +agent-system apikey list|revoke --owner alice [--name dev] +``` + +- 客户端:`Authorization: Bearer ask_…`;浏览器 SSE 用 `?key=` 兜底;前端设置登录门自动出现。 +- 租户隔离:跨租户访问一律 404;共享知识(owner='')对全体可见。 + +### 8.3 备份与限流 + +- 嵌入式档在线备份:`POST /api/admin/backup` → `/artifacts/backups/agent-system-.db`(VACUUM INTO,一致性快照)。日常"拷目录"前建议先执行(活库 WAL 直接拷贝不安全)。 +- PG 档:`pg_dump` + WAL 归档(标准 PG 运维;接口按设计返回指引)。 +- 租户限流:设置 `runtime.rateLimitPerMin`(0=不限,多用户建议 300–1200;429 超限)。多实例共享限流设 `runtime.redisAddr`(原生 Redis,重启生效)。 + +### 8.4 其他 + +- 服务停机:SIGTERM 优雅关闭,在途任务标记 `interrupted`(界面提示,可一键重发);事件缓冲停机前冲净。 +- 会话内串行:一个会话同时至多一个在途 run(重复发送返回 409)。 +- 向量 RAG:设置 `models.embedding.*`(OpenAI 兼容 `/embeddings`)后自动启用分块+向量+RRF 混合检索;`models.rerank.*` 可选重排;未配置 = 纯全文(默认)。 - 日志:标准输出(结构化程度为本版最小实现)。 diff --git a/docs/proposals/2026-09-30-dual-tier-storage-and-multitenancy.md b/docs/proposals/2026-09-30-dual-tier-storage-and-multitenancy.md new file mode 100644 index 0000000..1880c2a --- /dev/null +++ b/docs/proposals/2026-09-30-dual-tier-storage-and-multitenancy.md @@ -0,0 +1,39 @@ +# 提案:双档存储、多租户与规模化(2026-09-30) + +状态:accepted(实施已完成,见[实施记录](../records/2026-09-30-dual-tier-implementation.md))。 +前置:[系统骨架设计与计划](2026-09-29-system-skeleton-design-and-plan.md)(D5/D16 双档存储预留)。 + +## 1. 背景与决策 + +产品方向确认为多租户、多用户、高并发(用户确认,2026-09-30)。骨架期"单用户本地部署"假设据此修订: +多租户为既定主线,嵌入式单二进制仍为默认档与最高部署约束(非容器化不变)。 + +对既有设计的影响: + +- §2.2 非目标中"多租户/计费/注册远期不做"修订为"多租户已立项(本提案);计费/注册仍远期"。 +- D5/D16 的 PostgreSQL 可选档转正为多用户主机档路线;SQLite 嵌入式档保持默认。 +- 7.2 远期表中"向量 RAG 管线"前移至本期交付。 + +## 2. 决策(D17–D24) + +| # | 决策 | 理由 | +|---|---|---| +| D17 | 租户模型:user 即 owner;API key(`ask_` 前缀,库存 SHA-256 摘要,明文仅创建时返回一次)认证;嵌入式档默认关闭认证(回环单用户),PG 档默认开启(`--auth auto/on/off`) | 最小可审计的 API-first 租户身份;不引入注册/计费 | +| D18 | 租户列 `owner` 冗余落位(projects/conversations/runs/artifacts/knowledge/hitl_cards/profiles/settings),跨租户访问一律 NotFound(不泄露存在性);知识 `owner=''` 为共享 | 现在零成本落位;后补需全量回填且易漏 | +| D19 | 存储双档:默认 SQLite(单二进制不变);`--dsn` 显式切换 PostgreSQL 档(原生安装,非容器);方言差异收敛在 store 包 dialect;查询统一 `?` 书写、PG 重绑 `$n` | D16 落地;业务层零感知 | +| D20 | schema 版本化迁移:`schema_migrations` 版本表 + 双方言 up/down + 基线 v1=旧形态(既有部署与新建库同路径演进);回滚为运维入口(多租户数据折回限制在 down 注释标明) | 骨架期"CREATE IF NOT EXISTS"不可演进;补齐设计 446 的回滚要求 | +| D21 | 一致性加固:消息 seq 单语句分配 + `UNIQUE(conversation_id,seq)` 约束兜底重试;设置合并/读改写走事务 + 会话咨询锁(PG)/单写连接(SQLite);HITL resolve 原子条件更新;工件写临时文件+rename 且删行先行 | 数据可靠性标准(同号并发、CAS、跨租户拒绝) | +| D22 | 事件写放大治理:高频增量(delta/progress)批处理(150ms/64 条冲盘),seq 改为会话内连续并在落盘事务内分配;立即事件(终态/HITL/工具)落盘后发布;优雅停机冲净缓冲 | 每 delta 一次 fsync 不可扩展;SSE 游标/重放语义不变 | +| D23 | 向量 RAG:Chunker/EmbeddingProvider/VectorIndex/Reranker/Retriever 契约(Core);段落感知分块(默认 800/重叠 120)→ 全文入库 →(配置 embedding 时)向量入库;检索 = 向量+全文 RRF 融合(k=60)→(配置 rerank 时)重排;向量双档同构 BLOB+进程内余弦,pgvector+HNSW 为 PG 档优化路径 | 设计 4.12 前移;未配置自动退化为纯全文(默认行为不变) | +| D24 | 多实例地基:工件对象存储抽象(本地目录档;共享卷=改路径);PG 档事件扇出 NOTIFY/LISTEN(`agent_events`,载荷 `会话|末seq`,按 seq 取回,SSE 去重容忍双投);租户限流固定窗口(进程内档默认,设置 `runtime.redisAddr` 切原生 Redis 共享档);多实例部署契约 = 会话亲和路由(run 归属实例内存执行) | 单实例优先垂直扩容;跨实例只解决订阅可见性与共享限流,不伪装多写者安全 | + +## 3. 边界与不做 + +- 不引入容器:开发验证使用本机原生 PostgreSQL/Redis 实例(多实例由 initdb/端口区分)。 +- 不做注册/计费/密码登录(远期);不做跨实例 run 迁移(依赖会话亲和)。 +- pgvector 列级索引(HNSW)未启用:BLOB+余弦扫描面向 ≤ 数万块/实例,规模到此再优化(换实现不动契约)。 +- SSE 认证用 `?key=` 兜底(EventSource 无法带 header);密钥经 URL 属于已知权衡,网关侧可剥离日志。 + +## 4. 验收与证据 + +见[测试报告 2026-09-30](../reports/2026-09-30-dual-tier-test-report.md)(双档全量、真实约束/并发/越权负例、原生 Redis/PG 集成、单二进制构建)。 diff --git a/docs/records/2026-09-30-dual-tier-implementation.md b/docs/records/2026-09-30-dual-tier-implementation.md new file mode 100644 index 0000000..885ba5e --- /dev/null +++ b/docs/records/2026-09-30-dual-tier-implementation.md @@ -0,0 +1,26 @@ +# 实施记录:双档存储、多租户与规模化(2026-09-30) + +按[提案 D17–D24](../proposals/2026-09-30-dual-tier-storage-and-multitenancy.md) 完成四阶段实施。 +验证证据见[测试报告](../reports/2026-09-30-dual-tier-test-report.md)。 + +## 阶段记录 + +| 阶段 | 内容 | 关键提交 | +|---|---|---| +| 阶段 0:地基 | Core 契约 owner 字段(Project/Conversation/Run/Artifact/KnowledgeDoc/AgentProfile + TS 镜像);store 重构:dialect 方言收敛、版本化双方言迁移 v1–v5(基线→租户列/唯一约束/事件重建/设置重建→认证表→PG 全文→向量表)、`INSERT OR REPLACE` 全量改 `ON CONFLICT`、消息 seq 单语句分配+唯一约束重试、设置合并事务化(PG 咨询锁)、HITL 原子 resolve、工件临时文件+rename 且删除联动清索引、`VACUUM INTO` 备份接口与 `POST /api/admin/backup`;runmgr:单会话单 run 强制(409)、事件批处理器(150ms/64 条;立即事件先冲缓冲保序;停机冲净);server:owner 管道、SSE 越权 404、`/api/*` 未匹配 404 不回落 SPA | Core `6f9e1a8`/`03ba8dd`;Backend `00a0453` | +| 阶段 1:认证与 PG 档 | users/api_keys 表与存储方法(SHA-256 摘要、撤销、last_used);认证中间件(Bearer/`?key=`、auto/on/off);`apikey create/list/revoke` 子命令;前端登录门 + 凭证感知 API/SSE 客户端;PG 方言:pgx 连接池、tsvector 生成列 + pg_trgm GIN(中文子串)、`websearch_to_tsquery` 混合检索、会话咨询锁;设置/密钥按租户隔离(scope PK=(owner,scope)) | Backend `00a0453`;Frontend `6395cc9` | +| 阶段 2:向量 RAG | Core retrieval 契约(Chunker/EmbeddingProvider/VectorIndex/Reranker/Retriever/DocumentIndexer);rag 包:段落感知分块(800/120)、OpenAI 兼容 `/embeddings` 与 `/rerank` 客户端(失败降级不阻断)、RRF 融合(k=60)、知识 ID 回写关联向量;store:`knowledge_vectors`(双方言 BLOB/BYTEA)+ 余弦扫描(租户+维度过滤);upload/工件统一走索引入口;设置路由复用既有 embedding/rerank 类别 | Core `019617e`;Backend `b3fbf1f` | +| 阶段 3:多实例地基 | blobstore 抽象(本地目录档,原子 Put/覆写/删除;store 工件方法只依赖接口);pgbus:NOTIFY `agent_events`(载荷 `会话|末seq`)+ 每实例 LISTEN 按 seq 取回转发本地 bus;租户限流固定窗口中间件(进程内默认;`runtime.redisAddr` 切原生 Redis 共享档,429);设置新增 `runtime.rateLimitPerMin`/`runtime.redisAddr`;多实例契约=会话亲和路由(提案 D24) | Backend `3f43927` | + +## 偏差与说明 + +- 事件 seq 由全局自增改为会话内连续(v2 重建,按会话分区重编号):老客户端的一次性游标语义变化(重放窗口内可能少量重复投递,UI 幂等),迁移说明见 v2 语句注释。 +- 认证下 SSE/静态资源经 `?key=` 兜底(EventSource 限制);已在提案 §3 记录权衡。 +- pgvector 扩展已随环境可用(本机原生 PG 含 vector/trgm),但列级 HNSW 索引未启用(D23 规模注记)。 +- 曾观察到一次并行全量 PG 档运行中 store 单包失败(疑似瞬时连接争用,0.24s 快败),随后 3×PG -race、2×全量 -race、1×全量共 5 次复跑均通过;如复现按验证标准升级排查。 + +## 下一责任人/后续项 + +- 多实例上线前的会话亲和路由(网关或客户端择点)与共享卷 artifactsDir 指配 —— 部署设计,需立项。 +- pgvector HNSW 优化(向量规模 ≥ 数万块时);历史滚动压缩(设计 4.10 远期项不变)。 +- 管理面:租户管理 UI/密钥自助(现为 CLI)。 diff --git a/docs/reports/2026-09-30-dual-tier-test-report.md b/docs/reports/2026-09-30-dual-tier-test-report.md new file mode 100644 index 0000000..7dfad97 --- /dev/null +++ b/docs/reports/2026-09-30-dual-tier-test-report.md @@ -0,0 +1,57 @@ +# 测试报告:双档存储、多租户与规模化(2026-09-30) + +对象:[提案 D17–D24](../proposals/2026-09-30-dual-tier-storage-and-multitenancy.md) 全量实施。 +环境:linux/amd64,go1.27.1,node v26.10.0;本机原生 PostgreSQL 18.6(临时测试簇 initdb,端口 55434,扩展 vector/pg_trgm 可用)与原生 Redis 8.10.2(临时实例,端口 55379);**全程无容器**。 +组合基线:Dev 仓工作区(core/backend/frontend/documents 见下述提交)。 + +## 1. 自动化测试 + +| 验证 | 命令 | 结果 | +|---|---|---| +| 组合全量 | `make test`(core + backend + frontend) | PASS:core `ok`;backend 11 包全 `ok`;前端 vitest 9/9(含新增 auth 门 4 例) | +| 竞态全量 | `go test -race ./...`(backend) | PASS(11 包;重复 2 次) | +| 嵌入式档存储 | `go test ./internal/store/` | PASS:迁移升降级(老库→v5 数据保留/事件重编号)、租户隔离负例、并发消息 seq 唯一(24 并发)、并发设置合并不丢键(8 写者)、批量事件 seq、HITL 双重 resolve 仅一次、VACUUM INTO 备份可开、工件原子性、画像租户边界 | +| PG 档存储/检索 | `AGENT_PG_TEST_DSN=<原生PG> go test -race ./internal/store/ ./internal/rag/ ./internal/pgbus/` | PASS(-race 重复 3 次):同套用例在真实 PG 约束下通过;PG 专属:tsvector+trigram 中文子串检索、咨询锁并发、BYTEA 向量 | +| 限流 | `AGENT_REDIS_TEST_ADDR=<原生Redis> go test ./internal/ratelimit/`;`go test ./internal/server/`(429 e2e) | PASS:进程内窗口滚动、Redis 共享窗口、429 中间件与放行 | +| 向量 RAG | `go test -race ./internal/rag/` | PASS:分块(聚包/硬切重叠/空输入)、索引分块+向量入库、embedding 失败降级纯全文、RRF 向量路主导排序、租户/维度过滤 | +| 单二进制 | `make build`(CGO_ENABLED=0) | PASS:`bin/agent-system` 18MB 静态 ELF(file 确认 statically linked),默认档零外部服务不变 | + +说明:曾出现一次并行全量 PG 档运行中 store 单包快败(0.24s,疑似瞬时连接争用);随后 3×PG -race、2×全量 -race、1×全量共 5 次复跑均 PASS。未复现,留档观察。 + +## 2. 真实链路冒烟(真实二进制 + 原生服务) + +### 2.1 PG 多用户档(`--dsn`,认证 auto=on) + +| 步骤 | 证据 | +|---|---| +| 密钥创建 | `agent-system apikey create --dsn … --owner alice` → `ask_b237…`(明文仅一次) | +| 启动 | serve 日志:`storage tier: postgres`、`auth: API key required`、`pgbus: LISTEN agent_events` | +| 未带 key | `GET /api/projects` → 401 | +| alice 带 key | `POST /api/projects` → 200,响应含 `"owner":"alice"` | +| 知识入库+中文检索 | manual 入库 → `GET /api/knowledge/search?q=中文子串` 命中(PG tsvector+trigram 路径) | +| 跨租户负例 | bob 密钥读 alice 项目 → 404 | +| PG 档备份 | `POST /api/admin/backup` → 501 + pg_dump 指引(按设计) | + +### 2.2 嵌入式默认档(单二进制,无认证) + +| 步骤 | 证据 | +|---|---| +| 启动/健康 | `GET /api/health` → `{"ok":true…}` | +| mock 对话 | 消息 → run `completed`;会话终态对账 `lastSeq 103 / msgs 2 / runs 1`(事件批量落盘,会话内连续 seq) | +| 在线备份 | `POST /api/admin/backup` → 200,产出 `agent-system-20260930T015032Z.db`(VACUUM INTO) | + +## 3. 覆盖对照(提案决策 → 证据) + +- D17/D18 认证与租户:§1 认证 e2e + §2.1 401/404 负例;store 层租户隔离负例(§1)。 +- D19/D20 双档与迁移:§1 双档同套用例;迁移升降级含老库升级路径(数据保留)。 +- D21 一致性:并发 seq 唯一、并发设置合并、HITL 原子、工件原子(§1,真实库约束)。 +- D22 事件批处理:§2.2 lastSeq 103 + 会话内连续 seq;race 全量。 +- D23 向量 RAG:§1 rag 包 + PG 档向量用例;未配置退化为全文(默认行为不变)。 +- D24 多实例地基:pgbus 集成(NOTIFY→LISTEN→bus 转发 3s 内到达);限流双后端。 + +## 4. 遗留与风险 + +- pgvector HNSW 列级索引未启用(BLOB 余弦扫描,≤数万块/实例;超限再优化,契约不动)。 +- SSE `?key=` 兜底(EventSource 限制):网关日志侧需注意脱敏。 +- 多实例上线前置:会话亲和路由与共享卷 artifactsDir 指配(部署设计待立项)。 +- 事件 seq 迁移一次性语义变化(老客户端重放窗口可能少量重复投递,UI 幂等)。