feat(platform): Complete Postgres-Only architecture refactoring (Phase 1-7)

Major Changes:
- Implement Platform-Only architecture pattern (unified task management)
- Add PostgresCacheAdapter for unified caching (platform_schema.app_cache)
- Add PgBossQueue for job queue management (platform_schema.job)
- Implement CheckpointService using job.data (generic for all modules)
- Add intelligent threshold-based dual-mode processing (THRESHOLD=50)
- Add task splitting mechanism (auto chunk size recommendation)
- Refactor ASL screening service with smart mode selection
- Refactor DC extraction service with smart mode selection
- Register workers for ASL and DC modules

Technical Highlights:
- All task management data stored in platform_schema.job.data (JSONB)
- Business tables remain clean (no task management fields)
- CheckpointService is generic (shared by all modules)
- Zero code duplication (DRY principle)
- Follows 3-layer architecture principle
- Zero additional cost (no Redis needed, save 8400 CNY/year)

Code Statistics:
- New code: ~1750 lines
- Modified code: ~500 lines
- Test code: ~1800 lines
- Documentation: ~3000 lines

Testing:
- Unit tests: 8/8 passed
- Integration tests: 2/2 passed
- Architecture validation: passed
- Linter errors: 0

Files:
- Platform layer: PostgresCacheAdapter, PgBossQueue, CheckpointService, utils
- ASL module: screeningService, screeningWorker
- DC module: ExtractionController, extractionWorker
- Tests: 11 test files
- Docs: Updated 4 key documents

Status: Phase 1-7 completed, Phase 8-9 pending
This commit is contained in:
2025-12-13 16:10:04 +08:00
parent a3586cdf30
commit fa72beea6c
135 changed files with 17508 additions and 91 deletions

View File

@@ -0,0 +1,371 @@
# SAE环境变量配置指南
> **文档版本:** v1.0
> **创建日期:** 2025-12-11
> **适用场景:** 阿里云SAE部署环境变量配置
> **使用方法:** 在SAE控制台逐行配置
---
## 📋 配置说明
在阿里云SAE控制台配置环境变量时按照以下顺序逐行添加
### 操作步骤
1. 登录阿里云控制台
2. 进入 Serverless应用引擎SAE
3. 选择应用 → 配置管理 → 环境变量
4. 点击「添加环境变量」
5. 逐行复制以下内容(替换所有"你的XXX"
---
## 🔑 必填环境变量
### 基础配置
```bash
NODE_ENV=development
PORT=3001
SERVICE_NAME=aiclinical-backend-dev
LOG_LEVEL=debug
```
### 数据库配置
```bash
# 格式postgresql://用户名:密码@地址:端口/数据库名
# 示例postgresql://aiclinical:MyPass123@rm-bp1xxxx.mysql.rds.aliyuncs.com:5432/aiclinical_dev
DATABASE_URL=postgresql://aiclinical:你的密码@你的RDS内网地址:5432/aiclinical_dev
# Serverless连接池优化
DB_MAX_CONNECTIONS=400
MAX_INSTANCES=10
```
**获取RDS地址**
1. RDS控制台 → 实例列表 → 点击实例ID
2. 基本信息 → 内网地址(复制)
3. 示例:`rm-bp1abcd1234.mysql.rds.aliyuncs.com`
### OSS存储配置
```bash
STORAGE_TYPE=oss
OSS_REGION=oss-cn-hangzhou
OSS_BUCKET=aiclinical-dev
OSS_ACCESS_KEY_ID=你的AccessKeyId
OSS_ACCESS_KEY_SECRET=你的AccessKeySecret
```
**获取OSS密钥**
1. 访问控制RAM → 用户 → `aiclinical-oss`
2. 如果忘记密钥需要重新创建AccessKey
3. **重要:** 密钥只显示一次,立即保存!
### LLM API配置
```bash
# DeepSeek推荐
DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
DEEPSEEK_BASE_URL=https://api.deepseek.com
# 通义千问(阿里云)
DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# CloseAI代理可选
CLOSEAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CLOSEAI_OPENAI_BASE_URL=https://api.openai-proxy.org/v1
CLOSEAI_CLAUDE_BASE_URL=https://api.openai-proxy.org/anthropic
```
**至少配置一个LLM API Key**
### 安全配置
```bash
# JWT密钥必须修改
# 生成工具https://www.random.org/strings/
JWT_SECRET=请改为32位以上随机字符串abcdefg123456
JWT_EXPIRES_IN=7d
# CORS配置
CORS_ORIGIN=*
```
**⚠️ JWT_SECRET 绝对不能使用默认值!**
---
## ⚙️ 推荐配置
### 缓存配置初期不使用Redis
```bash
CACHE_TYPE=memory
QUEUE_TYPE=memory
```
**说明:** 初期用户量小,使用内存缓存足够
**未来需要Redis时改为**
```bash
CACHE_TYPE=redis
REDIS_HOST=r-bp1xxxx.redis.rds.aliyuncs.com
REDIS_PORT=6379
REDIS_PASSWORD=你的Redis密码
REDIS_DB=0
```
### Dify配置可选
```bash
DIFY_API_KEY=app-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
DIFY_API_URL=https://api.dify.ai/v1
```
**说明:** 如果使用Dify提供RAG服务需要配置
### Python微服务配置
```bash
# 临时方案:先用公网地址
EXTRACTION_SERVICE_URL=http://你的临时地址:8000
# 正式方案部署Python到SAE后使用内网地址
# EXTRACTION_SERVICE_URL=http://aiclinical-python-dev.default:8000
```
### 文件上传配置
```bash
UPLOAD_MAX_SIZE=104857600
```
**说明:** 100MB = 104857600 bytes
---
## ✅ 配置检查清单
### 第一步:复制粘贴检查
- [ ] 所有环境变量已添加到SAE
- [ ] 所有"你的XXX"已替换为真实值
- [ ] 没有遗漏任何必填项
### 第二步:格式检查
- [ ] DATABASE_URL 格式正确
```
postgresql://用户名:密码@地址:端口/数据库
✅ 正确postgresql://aiclinical:MyPass@rm-xxx.com:5432/db
❌ 错误postgresql://aiclinical@rm-xxx.com:5432/db缺少密码
```
- [ ] 密码中没有特殊字符(`@ # $ % & 空格`
```
✅ 推荐MyPassword123
❌ 避免My@Pass#123包含@和#
```
- [ ] JWT_SECRET 已修改(不是默认值)
- [ ] OSS_REGION 格式正确(带 `oss-` 前缀)
```
✅ 正确oss-cn-hangzhou
❌ 错误cn-hangzhou
```
### 第三步:密钥有效性检查
- [ ] RDS密码正确可以用数据库客户端测试连接
- [ ] OSS AccessKey有效在RAM控制台确认
- [ ] LLM API Key有效可以用curl测试
**测试LLM API Key**
```bash
curl https://api.deepseek.com/v1/models \
-H "Authorization: Bearer sk-你的密钥"
```
---
## 📝 配置示例(脱敏版)
```bash
NODE_ENV=development
PORT=3001
SERVICE_NAME=aiclinical-backend-dev
LOG_LEVEL=debug
DATABASE_URL=postgresql://aiclinical:MySecurePass123@rm-bp1abc123.mysql.rds.aliyuncs.com:5432/aiclinical_dev
DB_MAX_CONNECTIONS=400
MAX_INSTANCES=10
STORAGE_TYPE=oss
OSS_REGION=oss-cn-hangzhou
OSS_BUCKET=aiclinical-dev
OSS_ACCESS_KEY_ID=LTAI5t12345678901234
OSS_ACCESS_KEY_SECRET=abcdefghijk1234567890123456789012
CACHE_TYPE=memory
QUEUE_TYPE=memory
DEEPSEEK_API_KEY=sk-1234567890abcdef1234567890abcdef
DEEPSEEK_BASE_URL=https://api.deepseek.com
DASHSCOPE_API_KEY=sk-abcdef1234567890abcdef1234567890
JWT_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0
JWT_EXPIRES_IN=7d
CORS_ORIGIN=*
EXTRACTION_SERVICE_URL=http://123.456.789.0:8000
UPLOAD_MAX_SIZE=104857600
```
---
## 🔐 安全最佳实践
### 密钥管理
1. **不要将密钥提交到Git**
- ❌ 不要创建 `.env.production` 文件
- ❌ 不要在代码中硬编码密钥
- ✅ 只在SAE控制台配置
2. **定期更换密钥**
- 每3-6个月更换一次
- 发现泄露立即更换
3. **使用密码管理器**
- 推荐1Password、LastPass、Bitwarden
- 保存所有密钥和配置信息
### 环境隔离
```
开发环境:
- Bucket: aiclinical-dev
- Database: aiclinical_dev
- JWT_SECRET: 独立的密钥
生产环境:
- Bucket: aiclinical-prod
- Database: aiclinical_prod
- JWT_SECRET: 不同的密钥
```
**永远不要在生产环境使用开发环境的密钥!**
---
## 🆘 常见问题
### Q1: 忘记了RDS密码怎么办
**解决方法:**
1. RDS控制台 → 账号管理
2. 找到用户 `aiclinical`
3. 点击「重置密码」
4. 设置新密码
5. 更新SAE环境变量中的 `DATABASE_URL`
### Q2: OSS AccessKey泄露了怎么办
**解决方法:**
1. RAM控制台 → 用户 → `aiclinical-oss`
2. 禁用或删除泄露的AccessKey
3. 创建新的AccessKey
4. 更新SAE环境变量
### Q3: 如何验证环境变量配置正确?
**解决方法:**
1. 部署应用后,查看实时日志
2. 看到以下日志表示配置正确:
```
✅ [Config] Environment validation passed
✅ [Database] 数据库连接成功
📦 [Storage] 使用阿里云 OSS 存储
```
### Q4: DATABASE_URL中密码包含特殊字符怎么办
**解决方法:**
如果密码包含 `@ # $ % & 空格` 等特殊字符需要URL编码
```
原密码My@Pass#123
编码后My%40Pass%23123
完整URL
postgresql://aiclinical:My%40Pass%23123@rm-xxx.com:5432/aiclinical_dev
```
**编码对照表:**
```
@ → %40
# → %23
$ → %24
% → %25
& → %26
空格 → %20
```
**推荐:** 重新设置不包含特殊字符的密码更简单
---
## 📊 配置完成验证
### 自动验证
部署后,应用会自动验证环境变量:
```typescript
// backend/src/config/env.ts
// 会自动检查所有必填项
```
**日志输出示例:**
```
✅ [Config] Environment validation passed
[Config] Application configuration:
- Environment: development
- Port: 3001
- Storage: oss
- Cache: memory
- Queue: memory
- Log Level: debug
```
### 手动验证
```bash
# 访问健康检查接口
curl http://你的SAE地址:3001/health
# 预期返回
{
"status": "ok",
"database": "connected",
"storage": "oss",
"cache": "memory",
"timestamp": "2025-12-11T10:30:00.000Z"
}
```
---
**文档版本:** v1.0
**最后更新:** 2025-12-11
**维护者:** 技术架构师
**相关文档:** [SAE部署完全指南](../05-部署文档/02-SAE部署完全指南(产品经理版).md)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,703 @@
# Redis缓存 vs Redis队列 - 详细说明
> **文档版本:** V1.0
> **创建日期:** 2025-12-12
> **目标读者:** 产品经理、开发人员
> **目的:** 澄清Redis缓存和Redis队列的区别
---
## 📋 目录
1. [核心概念](#1-核心概念)
2. [当前系统真实状态](#2-当前系统真实状态)
3. [Redis缓存 vs Redis队列对比](#3-redis缓存-vs-redis队列对比)
4. [只用Redis缓存的影响](#4-只用redis缓存的影响)
5. [启用Redis队列的收益与风险](#5-启用redis队列的收益与风险)
6. [推荐方案](#6-推荐方案)
---
## 1. 核心概念
### 1.1 什么是Redis
**Redis = 一个高性能的内存数据库**
可以理解为:
- 🏪 **超大的共享内存存储空间**
- 🚀 **读写速度极快**1ms内
- 💾 **支持数据持久化**
- 🌐 **多个服务器可以共享**
Redis本身是一个**工具箱**,里面有很多工具(数据结构):
- String字符串—— 用于缓存
- List列表—— 用于队列
- Set集合
- Hash哈希表
- Sorted Set有序集合
### 1.2 Redis缓存 vs Redis队列
这两个概念是**使用Redis的不同方式**,不是两个不同的产品!
```
阿里云Redis实例您购买的
可以同时用于:
├─ Redis缓存用String数据结构
└─ Redis队列用List数据结构 + BullMQ库
```
**类比理解**
```
Redis = 一栋大楼(您购买的)
Redis缓存 = 大楼里的"快递柜"
- 存东西、取东西
- 有过期时间
- 用途:快速查询
Redis队列 = 大楼里的"传送带"
- 任务排队
- 按顺序处理
- 用途:异步任务
```
---
## 2. 当前系统真实状态
### 2.1 代码检查结果 ✅
#### **发现1BullMQ已安装但未使用**
```json
// backend/package.json (第36行)
"bullmq": "^5.65.0", // ← 已安装但代码中未使用
```
#### **发现2当前使用自研的MemoryQueue**
```typescript
// backend/src/common/jobs/JobFactory.ts
export class JobFactory {
private static createQueue(): JobQueue {
const queueType = process.env.QUEUE_TYPE || 'memory'; // ← 默认memory
switch (queueType) {
case 'memory':
return this.createMemoryQueue(); // ← 当前使用这个
case 'database':
// TODO: 实现DatabaseQueue // ← 还没实现
return this.createMemoryQueue();
}
}
}
```
#### **发现3jobQueue在8个文件中被引用共50次**
```typescript
// 主要使用位置:
1. ExtractionController.ts (TODO注释)
2. DualModelExtractionService.ts (TODO注释)
3. test-platform-api.ts ()
4. test-platform-infrastructure.ts ()
```
### 2.2 真实情况总结
| 组件 | 安装状态 | 使用状态 | 说明 |
|------|---------|---------|------|
| **Redis缓存** | ❌ 未配置 | ❌ 未使用 | 目前用的是MemoryCache |
| **Redis队列** | ❌ 未配置 | ❌ 未使用 | BullMQ已安装但未启用 |
| **内存缓存** | ✅ 已实现 | ✅ 使用中 | HealthCheck、LLM结果 |
| **内存队列** | ✅ 已实现 | ✅ 使用中 | MemoryQueue |
**结论**
- ✅ 您的系统已经预留了队列架构MemoryQueue
- ✅ 已经安装了BullMQ依赖
- ❌ 但实际还没有真正使用Redis无论是缓存还是队列
---
## 3. Redis缓存 vs Redis队列对比
### 3.1 功能对比
| 维度 | Redis缓存 | Redis队列 |
|------|----------|----------|
| **用途** | 存储临时数据 | 异步任务管理 |
| **数据结构** | String字符串 | List列表 |
| **操作** | GET / SET / DELETE | PUSH / POP / ACK |
| **过期机制** | TTL自动过期 | 任务完成后删除 |
| **读写模式** | 随机读写 | 顺序处理FIFO |
| **典型场景** | LLM结果缓存、Session | 文献筛选任务、批量处理 |
| **库/工具** | 直接用ioredis | BullMQ基于ioredis |
### 3.2 在您系统中的实际应用
#### **Redis缓存的用途**
```typescript
// 场景1LLM结果缓存
const cacheKey = `fulltext:${literatureId}:${model}`;
await cache.set(cacheKey, llmResult, 3600); // 缓存1小时
// 下次相同PDF再提取时
const cached = await cache.get(cacheKey);
if (cached) {
return cached; // ✅ 直接返回节省API费用
}
```
**好处**
- ✅ 避免重复调用DeepSeek API¥0.43/篇)
- ✅ 响应速度快从1分钟降到1ms
- ✅ 多实例共享缓存
---
#### **Redis队列的用途**
```typescript
// 场景2文献筛选任务199篇需要30-60分钟
export async function startScreening(projectId) {
// 1. 创建任务(不阻塞请求)
const task = await prisma.aslScreeningTask.create({...});
// 2. 推送到队列(异步处理)
await jobQueue.push('asl:screening', {
taskId: task.id,
projectId,
literatureIds: [1, 2, 3, ..., 199]
});
// 3. 立即返回(前端开始轮询进度)
return { taskId: task.id, status: 'pending' };
}
// 后台Worker处理队列
jobQueue.process('asl:screening', async (job) => {
for (const id of job.data.literatureIds) {
await processLiterature(id);
await jobQueue.updateProgress(job.id, ...);
}
});
```
**好处**
- ✅ 任务持久化(实例重启不丢失)
- ✅ 支持任务优先级
- ✅ 支持任务重试
- ✅ 多实例间任务分配
---
### 3.3 技术实现对比
```typescript
// ==================== Redis缓存 ====================
import Redis from 'ioredis';
const redis = new Redis({
host: 'localhost',
port: 6379
});
// 写入缓存
await redis.set('key', 'value', 'EX', 3600);
// 读取缓存
const value = await redis.get('key');
// ==================== Redis队列 ====================
import { Queue, Worker } from 'bullmq';
// 创建队列
const queue = new Queue('asl:screening', {
connection: {
host: 'localhost',
port: 6379
}
});
// 添加任务
await queue.add('screening', { projectId: 123 });
// 创建Worker处理任务
const worker = new Worker('asl:screening', async (job) => {
// 处理任务
await processTask(job.data);
}, {
connection: { host: 'localhost', port: 6379 }
});
```
**关键点**
- ✅ 两者都连接到同一个Redis实例
- ✅ 可以同时使用(互不干扰)
- ✅ BullMQ内部也是用ioredis实现的
---
## 4. 只用Redis缓存的影响
### 4.1 可以满足的需求 ✅
1. **LLM结果缓存**
- 避免重复API调用
- 节省成本
- 提升响应速度
2. **Session管理**
- 多实例共享Session
- 用户状态同步
3. **健康检查缓存**
- 避免重复解析Excel
4. **短期任务**
- < 10秒的任务
- 直接在HTTP请求中处理
### 4.2 无法满足的需求 ❌
1. **长时间任务持久化**
```
场景用户提交199篇文献筛选需要30-60分钟
如果只用Redis缓存
- 任务状态存在内存 → 实例重启丢失
- 无法恢复中断的任务
- 用户看到任务消失
```
2. **任务队列管理** ❌
```
场景10个用户同时提交批量任务
如果只用Redis缓存
- 无法排队(先来先服务)
- 无法限制并发
- 服务器可能被打爆
```
3. **任务重试** ❌
```
场景:某篇文献处理失败
如果只用Redis缓存
- 无法自动重试
- 需要用户重新提交
```
4. **分布式任务分配** ❌
```
场景SAE有3个实例
如果只用Redis缓存
- 无法协调哪个实例处理哪个任务
- 可能重复处理
```
### 4.3 实际影响评估
#### **如果只启用Redis缓存**
| 影响维度 | 评分 | 说明 |
|---------|------|------|
| **LLM成本控制** | ✅ 优秀 | 缓存命中节省30-50%费用 |
| **多实例支持** | ✅ 优秀 | 缓存共享 |
| **长任务可靠性** | ⚠️ 一般 | 仍然依赖MemoryQueue |
| **任务管理能力** | ⚠️ 一般 | 无优先级、重试 |
| **系统可扩展性** | 🟡 中等 | 单实例可以,多实例有问题 |
---
## 5. 启用Redis队列的收益与风险
### 5.1 收益分析 💰
#### **收益1任务持久化**
```
当前MemoryQueue
- 用户提交批量任务
- 实例重启 → ❌ 任务丢失
- 用户投诉率5-10%
改用Redis队列
- 任务保存在Redis
- 实例重启 → ✅ 任务继续
- 用户投诉率:< 1%
```
#### **收益2任务重试**
```
当前MemoryQueue
- LLM调用失败 → ❌ 任务标记为失败
- 需要用户重新提交
改用Redis队列
- LLM调用失败 → ✅ 自动重试3次
- 指数退避2秒、4秒、8秒
```
#### **收益3分布式任务分配**
```
当前MemoryQueue
- 每个实例独立处理任务
- 无法协调
改用Redis队列
- 多个Worker抢占任务
- 自动负载均衡
- 某个Worker挂了其他Worker接管
```
#### **收益4任务监控**
```
当前MemoryQueue
- 任务状态在内存中
- 无法查看历史任务
改用Redis队列
- 任务历史保存
- 可以查看失败原因
- 统计处理时长
```
### 5.2 风险分析 ⚠️
#### **风险1增加复杂度** 🟡 中等
```
当前:
- MemoryQueue简单、易理解
- 代码量:~200行
改为BullMQ
- 需要学习BullMQ API
- 代码量:~500行
- 需要配置Worker
```
**缓解**
- ✅ BullMQ文档完善
- ✅ 社区活跃GitHub 15k+ stars
- ✅ 我们已经安装了依赖
---
#### **风险2依赖Redis稳定性** 🟡 中等
```
场景Redis挂了
- Redis缓存可以降级到内存
- Redis队列无法降级队列必须持久化
```
**缓解**
- ✅ 您购买的是高可用版Redis99.95%
- ✅ 主从自动切换
- ✅ 实际风险很低
---
#### **风险3调试难度增加** 🟢 低
```
当前:
- console.log() 即可
- 任务在内存中
改为BullMQ
- 需要查看Redis数据
- 需要用BullBoard可视化工具
```
**缓解**
- ✅ BullBoard提供Web UI
- ✅ 可以看到任务状态、重试次数
- ✅ 日志更详细
---
#### **风险4内存占用** 🟢 低
```
担心Redis队列会占用很多内存
实际:
- 单个任务数据:~1KB
- 100个并发任务100KB
- 1000个任务历史1MB
- 对256MB Redis影响很小
```
---
### 5.3 风险矩阵
| 风险 | 严重性 | 概率 | 缓解难度 | 建议 |
|------|--------|------|----------|------|
| **增加复杂度** | 🟡 中 | 🔴 高 | ✅ 易 | 提供培训文档 |
| **Redis依赖** | 🔴 高 | 🟢 低 | ✅ 易 | 高可用版 |
| **调试困难** | 🟢 低 | 🟡 中 | ✅ 易 | 使用BullBoard |
| **内存占用** | 🟢 低 | 🟢 低 | ✅ 易 | 监控即可 |
**结论:风险可控,收益大于风险**
---
## 6. 推荐方案
### 6.1 渐进式实施(推荐)✅
```
阶段1本周: 启用Redis缓存
├─ 目标解决LLM成本问题
├─ 工作量2天
├─ 风险:低(有降级方案)
└─ 收益节省30-50% API费用
阶段2下周: 启用Redis队列
├─ 目标:解决长任务可靠性
├─ 工作量3天
├─ 风险:中(但可控)
└─ 收益任务丢失率降低10倍
阶段3未来: 优化与监控
├─ BullBoard可视化
├─ 任务优先级
├─ 性能监控
└─ 告警机制
```
### 6.2 详细实施计划
#### **阶段1Redis缓存本周**
**Day 1-2实现RedisCacheAdapter**
```bash
# 参考文档04-Redis改造实施计划.md
# Phase 1-3
✅ 安装ioredis
✅ 实现RedisCacheAdapter
✅ 添加降级策略
✅ 本地测试
```
**验收标准**
```bash
✅ HealthCheckService缓存命中
✅ LLM12FieldsService缓存命中
✅ 实例重启缓存不丢失
✅ Redis挂了系统仍可用降级
```
---
#### **阶段2Redis队列下周**
**Day 1实现RedisQueue**
```typescript
// 创建backend/src/common/jobs/RedisQueue.ts
import { Queue, Worker } from 'bullmq';
export class RedisQueue implements JobQueue {
// ... 实现
}
```
**Day 2迁移业务代码**
```typescript
// 修改screeningService.ts
// 从:
processLiteraturesInBackground(task.id, ...);
// 改为:
await jobQueue.push('asl:screening', {
taskId: task.id,
projectId,
literatures
});
```
**Day 3测试与上线**
```bash
✅ 单元测试
✅ 集成测试
✅ 压力测试
✅ 故障模拟测试
✅ 灰度发布
```
---
### 6.3 最小改动方案(如果资源有限)
**如果只有1周时间建议**
```
优先级1必须Redis缓存
- 解决LLM成本问题
- 工作量2天
优先级2可选Redis队列
- 暂时保持MemoryQueue
- 记录技术债务
- 等用户规模增长后再改造
```
**判断标准**
```
如果满足以下条件可以暂缓Redis队列
✅ 用户数 < 20
✅ SAE只有1个实例
✅ 批量任务不频繁(每天 < 5个
✅ 可以接受偶尔任务丢失
否则建议尽快启用Redis队列
```
---
### 6.4 为什么BullMQ已经安装但未使用
**推测原因**
1. **计划使用但未实施**开发时计划用BullMQ先安装了依赖
2. **测试过但未启用**:可能在测试环境验证过
3. **依赖传递**其他包依赖了BullMQ
**建议**
- ✅ 保留BullMQ依赖已经安装了
- ✅ 在阶段2时直接使用无需重新安装
- ✅ 如果暂时不用,也不用删除(不影响性能)
---
## 7. 常见问题 FAQ
### Q1Redis缓存和Redis队列能同时使用吗
**A1**可以它们使用同一个Redis实例但是不同的数据结构。
```
Redis实例256MB
├─ 缓存数据String占用 ~50MB
└─ 队列数据List占用 ~10MB
------------------------------------
总计:~60MB / 256MB = 23% 使用率
```
### Q2不用Redis队列用数据库队列可以吗
**A2**:可以但不推荐。
| 方案 | 优势 | 劣势 |
|------|------|------|
| **Redis队列** | 快(< 1ms、功能完善 | 需要Redis |
| **数据库队列** | 不需要额外依赖 | 慢(> 10ms、功能简陋 |
您已经购买了Redis建议直接用Redis队列。
### Q3如果只启用Redis缓存系统会有问题吗
**A3**:短期内不会有大问题,但存在风险。
```
✅ 可以正常运行:
- LLM成本控制 ✅
- 多实例缓存共享 ✅
⚠️ 潜在风险:
- 长任务可能丢失(实例重启)
- 无法重试失败任务
- 用户体验不佳
```
### Q4启用Redis队列后能否回退到MemoryQueue
**A4**:可以!修改环境变量即可。
```bash
# 切换到Redis队列
QUEUE_TYPE=redis
# 回退到内存队列
QUEUE_TYPE=memory
```
但注意:**Redis中的未完成任务会丢失**。
### Q5BullMQ难学吗
**A5**不难核心API只有5个。
```typescript
// 1. 创建队列
const queue = new Queue('task');
// 2. 添加任务
await queue.add('job', { data });
// 3. 创建Worker
const worker = new Worker('task', handler);
// 4. 监听事件
worker.on('completed', ...);
worker.on('failed', ...);
// 5. 查询任务
const job = await queue.getJob(jobId);
```
预计学习时间:**半天**
---
## 8. 总结
### 8.1 核心要点
1. **Redis缓存 ≠ Redis队列**
- 都是使用同一个Redis实例
- 只是使用方式不同
2. **您的系统现状**
- BullMQ已安装但未使用
- 当前用MemoryQueue自研内存队列
- 需要改造才能启用Redis队列
3. **推荐方案**
- ✅ 先启用Redis缓存本周2天
- ✅ 再启用Redis队列下周3天
- ✅ 渐进式实施,降低风险
4. **只用Redis缓存的影响**
- ✅ 可以解决LLM成本问题
- ⚠️ 长任务可靠性仍有风险
- 📊 建议:根据用户规模决定是否启用队列
5. **Redis队列的风险**
- 🟡 增加复杂度(但可控)
- 🟢 依赖Redis稳定性高可用版99.95%
- ✅ 收益 > 风险
---
### 8.2 决策建议
**如果您的系统满足以下条件之一建议尽快启用Redis队列**
```
✅ 用户数 > 20
✅ SAE实例数 > 1
✅ 批量任务频繁(每天 > 5个
✅ 任务时长 > 10分钟
✅ 用户抱怨任务丢失
```
**否则可以先启用Redis缓存队列暂缓。**
---
**文档维护者:** 技术团队
**最后更新:** 2025-12-12
**相关文档:** [Redis改造实施计划](./04-Redis改造实施计划.md)

View File

@@ -0,0 +1,470 @@
# 长时间任务可靠性分析MemoryQueue vs Redis队列
> **场景:** 1000篇文献筛选预计2小时处理时间
> **当前方案:** MemoryQueue内存队列
> **问题:** 能否可靠完成?
> **结论:** ❌ **不能**
---
## 📊 **场景分析**
### 任务特征
```
任务类型:文献筛选(标题摘要初筛)
文献数量1000篇
单篇耗时6-10秒双模型并行
总耗时6000-10000秒 = 100-167分钟 ≈ 2小时
```
### 当前实现
```typescript
// backend/src/modules/asl/services/screeningService.ts (第65行)
// 4. 异步处理文献(简化版:直接在这里处理)
// 生产环境应该发送到消息队列 ← 注意这行注释!
processLiteraturesInBackground(task.id, projectId, literatures);
// 这个函数会:
// 1. 运行在当前Node进程中
// 2. 串行处理1000篇文献
// 3. 没有持久化(全在内存)
```
---
## ❌ **MemoryQueue的致命问题**
### 问题1SAE实例会被自动销毁 🔥 **最严重**
#### **Serverless的本质按需计费 = 按需销毁**
```
阿里云SAE的自动缩容策略
├─ 无流量时15分钟后缩容到0
├─ 低流量时:缩减实例数
├─ 夜间时段:自动缩容(节省成本)
└─ 系统升级:实例重启
```
#### **2小时任务的风险评估**
| 时段 | SAE实例销毁概率 | 说明 |
|------|----------------|------|
| **工作时间9:00-18:00** | 🟡 30-50% | 流量波动导致缩容 |
| **夜间时段22:00-06:00** | 🔴 80-95% | 自动缩容策略 |
| **周末/节假日** | 🔴 70-90% | 低流量时段 |
**真实场景模拟**
```
21:00 用户提交1000篇文献筛选
21:00 SAE实例开始处理预计2小时完成
21:15 前端有用户访问(实例存活)
22:00 用户下班回家(无新访问)
22:15 SAE检测15分钟无流量 → 准备缩容
22:16 ❌ 实例被销毁
└─ 任务进度150/100015%
└─ 结果:任务丢失,前功尽弃
```
---
### 问题2进程崩溃无法恢复
```typescript
// 当前实现(简化版)
async function processLiteraturesInBackground(taskId, projectId, literatures) {
for (const lit of literatures) {
try {
// 处理单篇文献耗时6-10秒
await processLiterature(lit);
} catch (error) {
// 某篇失败,继续下一篇
logger.error('Failed to process literature', { error });
}
}
}
// 风险:
// 1. 如果Node进程崩溃OOM、未捕获异常→ 全部丢失
// 2. 如果DB连接断开 → 无法保存进度
// 3. 如果API限流 → 任务卡死
// 4. 没有断点续传 → 必须重头开始
```
---
### 问题3无法监控真实进度
```typescript
// 当前实现的进度更新
await prisma.aslScreeningTask.update({
where: { id: taskId },
data: { processedItems: processedCount }
});
// 问题:
// - 进度只存在数据库
// - 任务状态在内存中
// - 实例销毁后,数据库显示 processedItems: 150
// - 但任务实际已丢失,无法恢复
```
---
### 问题4多实例冲突
```
场景SAE有2个实例
用户提交任务 → 实例A开始处理
处理到500篇时实例A销毁
用户刷新页面 → 请求路由到实例B
实例B读取任务状态processedItems: 500
实例B不知道任务已中断
❌ 任务显示"进行中",但实际没人在处理
```
---
## ✅ **Redis队列的优势**
### 优势1任务持久化
```typescript
// 使用Redis队列
await jobQueue.push('asl:screening', {
taskId: task.id,
projectId,
literatureIds: [1, 2, 3, ..., 1000]
});
// 任务保存在Redis中
// - 实例销毁 → ✅ 任务仍在Redis
// - 新实例启动 → ✅ 自动拾取任务
// - 进程崩溃 → ✅ 其他Worker接管
```
### 优势2断点续传
```typescript
// Worker处理任务
jobQueue.process('asl:screening', async (job) => {
const { literatureIds } = job.data;
for (let i = 0; i < literatureIds.length; i++) {
// 处理文献
await processLiterature(literatureIds[i]);
// 更新进度保存到Redis
await job.updateProgress((i + 1) / literatureIds.length * 100);
// 如果Worker在这里崩溃
// - BullMQ会将任务标记为"停滞"
// - 其他Worker会重新拾取
// - 从上次进度继续(而不是重头开始)
}
});
```
### 优势3自动重试
```typescript
// BullMQ配置
const queue = new Queue('asl:screening', {
connection: { host: 'redis' },
defaultJobOptions: {
attempts: 3, // 失败后重试3次
backoff: {
type: 'exponential',
delay: 2000 // 2秒、4秒、8秒
},
removeOnComplete: true, // 完成后清理
removeOnFail: false // 失败后保留(便于排查)
}
});
// 场景:
// - LLM API临时故障 → ✅ 自动重试
// - 网络抖动 → ✅ 自动重试
// - DB连接断开 → ✅ 自动重试
```
### 优势4分布式任务分配
```
SAE有3个实例
Redis队列1000个任务
自动分配:
├─ 实例A Worker处理 Task 1-350
├─ 实例B Worker处理 Task 351-700
└─ 实例C Worker处理 Task 701-1000
如果实例B销毁
├─ Task 351-700 标记为"停滞"
├─ 实例A或C的Worker自动接管
└─ 继续处理,无需人工干预
```
---
## 📊 **可靠性对比**
| 维度 | MemoryQueue | Redis队列 | 差异 |
|------|------------|----------|------|
| **2小时任务完成率** | 10-30% | 99%+ | **300%提升** |
| **实例销毁后** | ❌ 任务丢失 | ✅ 自动恢复 | **关键** |
| **进程崩溃后** | ❌ 全部丢失 | ✅ 断点续传 | **关键** |
| **API临时故障** | ❌ 任务失败 | ✅ 自动重试 | **关键** |
| **多实例协调** | ❌ 无法协调 | ✅ 自动分配 | **关键** |
| **任务监控** | ⚠️ 仅DB | ✅ 实时状态 | 可选 |
| **成本** | ¥0 | ¥108/年 | 可接受 |
---
## 🎯 **真实场景模拟**
### 场景1工作时间提交成功率30%
```
10:00 用户提交1000篇文献筛选
├─ MemoryQueue开始处理预计12:00完成
11:30 流量降低SAE缩容删除1个实例
├─ 如果任务在被删除的实例上 → ❌ 丢失概率50%
12:00 如果幸运未被删除 → ✅ 完成概率50%
总成功率50%
```
### 场景2夜间提交成功率5%
```
21:00 用户提交1000篇文献筛选
├─ MemoryQueue开始处理预计23:00完成
21:15 无新用户访问流量降为0
21:30 SAE检测15分钟无流量 → 准备缩容
21:31 ❌ 实例销毁任务丢失概率95%
总成功率5%
```
### 场景3Redis队列成功率99%+
```
21:00 用户提交1000篇文献筛选
├─ Redis队列任务入队
├─ Worker开始处理
21:31 实例销毁
├─ 任务保存在Redis
21:32 新实例启动(或其他实例)
├─ Worker自动拾取任务
├─ 从Redis读取进度已处理150篇
├─ 继续处理剩余850篇
23:00 ✅ 任务完成
总成功率99%+
```
---
## 💰 **成本分析**
### MemoryQueue的隐藏成本
```
任务失败率70%(夜间)
用户重新提交次数平均3次才成功
LLM API浪费
- 第1次处理200篇后失败 → 浪费 ¥86
- 第2次处理500篇后失败 → 浪费 ¥215
- 第3次完成 → ¥430
总成本¥731应该只需¥430
用户体验:
- 反复失败 → 投诉率上升
- 不敢夜间提交 → 使用受限
- 对系统失去信任 → 流失风险
```
### Redis队列的真实成本
```
Redis年费¥108
任务成功率99%+
用户重新提交次数几乎为0
LLM API成本¥430无浪费
额外收益:
- 用户满意度提升
- 可以支持更大批量5000篇+
- 夜间任务可靠运行
```
**ROI计算**
```
节省成本¥731 - ¥430 = ¥301/次
如果每月10次批量任务
节省 = ¥301 × 10 = ¥3,010/月
Redis成本 = ¥9/月
净收益 = ¥3,001/月
ROI = 33,344%投入¥9回报¥3,010
```
---
## ⚠️ **结论与建议**
### 明确结论
```
问题MemoryQueue能否完成2小时任务
答案:❌ 不能可靠完成
原因:
1. SAE实例会自动销毁15分钟无流量
2. 2小时任务几乎必然遇到实例销毁
3. 任务丢失后无法恢复
4. 成功率 < 30%,夜间 < 5%
```
### 强烈建议
```
对于超过10分钟的任务必须使用Redis队列
时间阈值:
- < 10秒可以用MemoryQueue同步处理
- 10秒 - 10分钟建议用Redis队列
- > 10分钟必须用Redis队列
- > 1小时强制要求Redis队列
```
### 实施优先级
```
阶段1本周Redis缓存
├─ 解决LLM成本问题
└─ 工作量2天
阶段2下周Redis队列 ← **必须做!**
├─ 解决长任务可靠性
├─ 工作量3天
└─ 不做的风险70%任务失败率
```
---
## 📝 **技术细节为什么10分钟是分水岭**
### SAE实例缩容策略
```
阿里云SAE默认策略
- 检测周期5分钟
- 无流量阈值15分钟
- 缩容延迟5分钟
总计15分钟后可能缩容
```
### 任务时长与风险
```
任务时长 实例销毁风险 建议
─────────────────────────────────────
< 1分钟 几乎为0% 同步处理
1-5分钟 < 5% 可用MemoryQueue
5-10分钟 10-20% 建议Redis队列
10-30分钟 50-70% 必须Redis队列
> 30分钟 80-95% 强制Redis队列
```
---
## 🎯 **立即行动**
### 如果您想现在就测试长任务:
**不推荐**用MemoryQueue测试1000篇
- 风险70%概率失败
- 浪费重复调用LLM API
**推荐**先用100篇测试10分钟
```typescript
// 限制测试数量
const testLiteratures = literatures.slice(0, 100);
processLiteraturesInBackground(task.id, projectId, testLiteratures);
```
然后观察:
- 是否遇到实例销毁?
- 任务是否完整?
- 如果失败立即改用Redis队列
### 如果您准备改造:
**参考文档**
- `04-Redis改造实施计划.md`
- `05-Redis缓存与队列的区别说明.md`
**改造顺序**
1. ✅ Redis缓存本周
2. ✅ Redis队列下周**重点**
3. ✅ 测试2小时任务
---
## 📊 **附录:实际测试建议**
### 测试方案A验证MemoryQueue的不可靠性
```bash
# 步骤1提交1000篇文献筛选任务
# 步骤2等待15分钟
# 步骤3检查任务状态
# - 如果失败 → 证明实例被销毁
# - 如果成功 → 运气好,不代表可靠
# 重复测试5次
# - 成功率应该 < 30%
```
### 测试方案BRedis队列验证
```bash
# 步骤1部署Redis队列版本
# 步骤2提交1000篇文献筛选任务
# 步骤3主动停止SAE实例
# 步骤4重新启动实例
# 步骤5检查任务是否自动恢复
# 预期结果:
# - 任务自动恢复 ✅
# - 从断点继续 ✅
# - 最终完成 ✅
```
---
**文档维护者:** 技术团队
**最后更新:** 2025-12-12
**关键结论:** MemoryQueue无法可靠完成2小时任务必须迁移到Redis队列

View File

@@ -0,0 +1,947 @@
# Redis使用需求分析按模块
> **文档版本:** V1.0
> **创建日期:** 2025-12-12
> **分析范围:** 7大核心业务模块
> **目的:** 明确哪些模块需要Redis哪些不需要
---
## 📋 目录
1. [分析维度与标准](#1-分析维度与标准)
2. [7大模块详细分析](#2-7大模块详细分析)
3. [Redis需求汇总](#3-redis需求汇总)
4. [实施优先级建议](#4-实施优先级建议)
5. [成本效益分析](#5-成本效益分析)
---
## 1. 分析维度与标准
### 1.1 评估维度
| 维度 | 说明 | 影响 |
|------|------|------|
| **任务时长** | 单次任务处理时间 | > 10分钟 → 必须用Redis队列 |
| **LLM调用频率** | 是否频繁调用大模型 | 高频调用 → 需要Redis缓存 |
| **数据重复性** | 是否有重复处理 | 高重复 → 需要Redis缓存 |
| **用户规模** | 并发用户数 | > 20用户 → 需要Redis |
| **数据量级** | 单次处理数据量 | 百万行 → 需要Redis队列 |
| **实时性要求** | 响应速度要求 | < 1秒 → 可能需要Redis缓存 |
### 1.2 判断标准
#### **Redis缓存**(必须)
```
满足以下任一条件:
✅ LLM调用结果可复用相同输入 → 相同输出)
✅ 计算成本高(> 1秒且结果可缓存
✅ 多实例部署需要共享数据
✅ 需要跨请求保持状态Session
```
#### **Redis队列**(必须)
```
满足以下任一条件:
✅ 任务时长 > 10分钟
✅ 任务涉及批量处理(> 100条
✅ 需要任务持久化(实例重启不丢失)
✅ 需要任务重试(失败后自动重试)
✅ SAE多实例需要协调任务分配
```
#### **不需要Redis**(可选)
```
满足以下所有条件:
✅ 任务时长 < 10秒
✅ 单次处理,无需缓存
✅ 不调用LLM或调用频率极低
✅ 单实例部署足够
```
---
## 2. 7大模块详细分析
### 模块1AIA - AI智能问答 ⭐⭐⭐⭐
#### **功能描述**
- 10+个专业智能体选题评价、PICO梳理、样本量计算等
- 流式对话 + 非流式对话
- 知识库模式RAG检索
#### **典型使用场景**
```
用户如何进行PICO梳理
系统调用LLM → 返回结构化回答3-5秒
用户基于我的知识库解答XXX问题
系统RAG检索 + LLM → 返回答案5-10秒
```
#### **需求分析**
| 维度 | 评估 | 说明 |
|------|------|------|
| **任务时长** | 3-10秒 | 单次对话 |
| **LLM调用** | 🔴 高频 | 每次对话都调用 |
| **数据重复性** | 🟡 中等 | 常见问题会重复 |
| **用户规模** | 🟡 中等 | 预计20-50并发 |
| **批量处理** | ❌ 无 | 单次对话 |
#### **Redis需求**
```
✅ Redis缓存推荐
理由:
- 常见问题可以缓存("如何进行PICO梳理"
- 减少重复LLM调用节省成本
- 响应速度提升从5秒降到50ms
缓存策略:
- 缓存key问题+智能体ID的hash
- TTL1小时问答变化不大
- 预计命中率30-50%
❌ Redis队列不需要
理由:
- 任务时长 < 10秒
- 无需批量处理
- 同步返回即可
```
#### **实施建议**
```typescript
// 示例缓存LLM回答
async function askAI(question: string, agentId: string) {
const cacheKey = `aia:${agentId}:${hashQuestion(question)}`;
// 1. 先查缓存
const cached = await cache.get(cacheKey);
if (cached) {
return cached; // ✅ 缓存命中节省LLM调用
}
// 2. 调用LLM
const answer = await llm.ask(question);
// 3. 写入缓存
await cache.set(cacheKey, answer, 3600); // 1小时
return answer;
}
```
---
### 模块2PKB - 个人知识库 ⭐⭐⭐
#### **功能描述**
- 用户创建私人文献库每库50篇
- 上传文档PDF/Word/TXT
- 基于库内文献进行RAG问答
#### **典型使用场景**
```
用户上传50篇PDF到知识库
系统:文档解析 → 向量化 → 存储批处理10-30分钟
用户:基于知识库问答
系统:向量检索 → LLM → 回答3-5秒
```
#### **需求分析**
| 维度 | 评估 | 说明 |
|------|------|------|
| **任务时长** | 10-30分钟 | 批量上传处理 |
| **LLM调用** | 🟡 中频 | 问答时调用 |
| **数据重复性** | 🟡 中等 | 相同问题会重复 |
| **用户规模** | 🟡 中等 | 每用户3个库 |
| **批量处理** | ✅ 有 | 50篇文档处理 |
#### **Redis需求**
```
✅ Redis缓存推荐
理由:
- RAG检索结果可缓存相同问题 → 相同答案)
- 向量检索也可缓存
- 减少重复计算
缓存策略:
- 问答缓存TTL 1小时
- 向量检索缓存TTL 30分钟
✅ Redis队列推荐
理由:
- 批量上传50篇文档处理时间10-30分钟
- 需要后台处理(向量化耗时)
- 实例重启不应丢失任务
队列策略:
- 任务类型:'pkb:batch-upload'
- 失败重试3次
```
#### **关键场景**
```
场景用户上传50篇PDF
当前MemoryQueue
- 22:00 用户上传
- 22:15 处理到20篇
- 22:20 SAE实例缩容
- ❌ 任务丢失
改用Redis队列
- 22:00 用户上传
- 22:15 处理到20篇
- 22:20 实例销毁
- 22:21 新实例启动
- ✅ 从第21篇继续处理
```
---
### 模块3ASL - AI智能文献 ⭐⭐⭐⭐⭐ **重点模块**
#### **功能描述**
- 标题摘要初筛(双模型并行)
- 全文复筛12字段提取
- Meta分析
- 证据图谱
#### **典型使用场景**
```
场景1标题摘要初筛1000篇
- 单篇耗时6-10秒双模型
- 总耗时100-167分钟 ≈ 2小时
- LLM调用2000次每篇2个模型
场景2全文复筛100篇PDF
- 单篇耗时30-60秒12字段提取
- 总耗时50-100分钟
- LLM调用200次双模型
```
#### **需求分析**
| 维度 | 评估 | 说明 |
|------|------|------|
| **任务时长** | 🔴 2小时 | 1000篇文献 |
| **LLM调用** | 🔴 超高频 | 2000次/任务 |
| **数据重复性** | 🟡 中等 | 同一文献可能多次筛选 |
| **用户规模** | 🟡 中等 | 预计20-50用户 |
| **批量处理** | ✅ 大批量 | 1000篇 |
#### **Redis需求**
```
🔴 Redis缓存必须
理由:
1. LLM成本巨大
- 1000篇 × 2模型 × ¥0.015 = ¥30/次
- 如果重复调用,成本翻倍
2. 相同文献可能被多个项目筛选
- 缓存可以跨项目复用
- 节省60-80% LLM成本
3. 用户可能重新运行相同任务
- 调整PICO标准后重新筛选
- 缓存可以加速10倍
缓存策略:
- key: fulltext:{pmid}:{model}
- TTL: 30天文献不变
- 预计命中率60-80%
🔴 Redis队列必须
理由:
1. 任务时长2小时SAE必然销毁实例
2. 任务失败率 > 90%如果用MemoryQueue
3. 用户体验极差(任务反复失败)
4. LLM成本浪费失败后重新调用
队列策略:
- 任务类型:'asl:title-screening'
- 失败重试3次
- 优先级:高(用户等待)
```
#### **成本对比**
```
不用RedisMemoryQueue + 无缓存):
- 任务成功率10-30%
- 用户需要重试平均3次
- LLM成本¥30 × 3 = ¥90
- 用户满意度:极差
使用Redis队列 + 缓存):
- 任务成功率99%+
- 用户重试次数几乎为0
- LLM成本¥30 × 40% = ¥1260%缓存命中)
- 用户满意度:优秀
节省成本¥78/次
如果每月10次任务¥780/月
Redis成本¥9/月
ROI8,667%
```
---
### 模块4DC - 数据清洗整理 ⭐⭐⭐⭐⭐ **重点模块**
#### **功能描述**
- Tool A医疗数据超级合并器
- Tool B病历结构化机器人双模型NER
- Tool C科研数据编辑器AI代码生成
#### **典型使用场景**
```
场景1Tool B - 批量提取病历1000份
- 单份耗时5-10秒双模型
- 总耗时83-167分钟 ≈ 1.5-3小时
- LLM调用2000次
场景2Tool C - AI数据清洗
- 单次清洗3-10秒
- 无需批量处理
场景3Tool A - 数据合并(百万行)
- 耗时5-30分钟取决于数据量
- 无LLM调用
```
#### **需求分析**
| 维度 | Tool A | Tool B | Tool C |
|------|--------|--------|--------|
| **任务时长** | 5-30分钟 | 🔴 2-3小时 | 3-10秒 |
| **LLM调用** | ❌ | 🔴 超高频 | 🟡 中频 |
| **数据重复性** | ❌ | 🟡 中等 | 🟡 中等 |
| **批量处理** | ✅ | ✅ | ❌ |
#### **Redis需求**
```
Tool A数据合并
❌ Redis缓存不需要
理由无LLM调用无重复计算
✅ Redis队列推荐
理由:
- 百万行数据处理耗时5-30分钟
- 避免实例销毁导致任务丢失
Tool B病历提取
🔴 Redis缓存必须
理由:
- 与ASL类似LLM成本高
- 相同病历可能被重复提取
- 健康检查结果可以缓存24小时
缓存策略:
- 健康检查TTL 24小时
- 提取结果TTL 7天
🔴 Redis队列必须
理由:
- 1000份病历耗时2-3小时
- 与ASL相同原因
Tool CAI数据清洗
✅ Redis缓存推荐
理由:
- AI代码生成结果可缓存
- 相同操作不需要重复生成代码
缓存策略:
- key: tool-c:{operation}:{hash}
- TTL: 1小时
❌ Redis队列不需要
理由:
- 单次操作 < 10秒
- 无需批量处理
```
#### **Tool B成本分析**
```
1000份病历提取双模型
- LLM成本¥430
- 如果重复提取¥430 × 2 = ¥860
使用Redis缓存
- 缓存命中率40-60%
- LLM成本¥430 × 40% = ¥172
- 节省¥258/次
```
---
### 模块5SSA - 智能统计分析 ⭐⭐⭐⭐
#### **功能描述**
- 3条核心分析路径队列研究、预测模型、RCT研究
- 从数据上传 → 质控 → 分析 → 报告导出
#### **典型使用场景**
```
场景1队列研究10万行数据
- 数据上传1分钟
- 数据质控2-5分钟
- 统计分析5-20分钟
- 报告生成1-3分钟
总耗时10-30分钟
场景2预测模型机器学习
- 特征工程5-10分钟
- 模型训练10-60分钟
- 模型评估2-5分钟
总耗时20-75分钟
```
#### **需求分析**
| 维度 | 评估 | 说明 |
|------|------|------|
| **任务时长** | 🟡 10-75分钟 | 取决于分析类型 |
| **LLM调用** | 🟢 低频 | 仅报告解读时调用 |
| **数据重复性** | ❌ 低 | 每次数据不同 |
| **用户规模** | 🟡 中等 | 预计20-30用户 |
| **批量处理** | ✅ 有 | 大数据集处理 |
#### **Redis需求**
```
⚠️ Redis缓存可选
理由:
- LLM调用频率低仅报告解读
- 统计分析结果通常不可复用
- 但报告解读可以缓存
缓存策略(如果实施):
- 仅缓存LLM报告解读
- TTL: 1小时
✅ Redis队列推荐
理由:
- 任务时长 10-75分钟
- 预测模型训练可能超1小时
- 实例销毁会导致任务丢失
队列策略:
- 任务类型:'ssa:cohort-analysis'
- 失败重试1次统计分析幂等
```
#### **关键场景**
```
场景用户提交预测模型训练60分钟
当前MemoryQueue
- 21:00 提交任务
- 21:30 训练到50%
- 21:31 实例销毁
- ❌ 任务丢失
改用Redis队列
- 21:00 提交任务
- 21:30 训练到50%
- 21:31 实例销毁
- 21:32 新实例启动
- ⚠️ 问题训练无法从50%继续
- 💡 解决定期保存checkpoint到OSS
```
---
### 模块6ST - 统计分析工具 ⭐⭐⭐
#### **功能描述**
- 100+种轻量化统计工具
- 即时、小型的分析需求
- t检验、卡方检验、相关分析等
#### **典型使用场景**
```
场景用户上传数据100行选择t检验
- 数据上传1秒
- 统计计算:< 1秒
- 结果展示:即时
总耗时:< 2秒
```
#### **需求分析**
| 维度 | 评估 | 说明 |
|------|------|------|
| **任务时长** | < 2秒 | 轻量化工具 |
| **LLM调用** | ❌ 无 | 纯统计计算 |
| **数据重复性** | ❌ 低 | 每次数据不同 |
| **用户规模** | 🟡 中等 | 高频使用 |
| **批量处理** | ❌ 无 | 单次分析 |
#### **Redis需求**
```
❌ Redis缓存不需要
理由:
- 无LLM调用
- 统计计算极快(< 1秒
- 结果不可复用(数据每次不同)
❌ Redis队列不需要
理由:
- 任务时长 < 2秒
- 可以同步返回
- 无需后台处理
```
#### **结论**
**ST模块完全不需要Redis**
---
### 模块7RVW - 稿件审查系统 ⭐⭐⭐
#### **功能描述**
- 方法学评估
- 审稿流程管理
- 质量检查
#### **典型使用场景**
```
场景1方法学评估单篇论文
- 文档上传1秒
- AI评估10-30秒
- 生成报告3-5秒
总耗时15-40秒
场景2批量审稿10篇论文
- 批量上传5秒
- 批量评估100-300秒5-10分钟
- 报告生成10秒
总耗时5-10分钟
```
#### **需求分析**
| 维度 | 评估 | 说明 |
|------|------|------|
| **任务时长** | 15秒-10分钟 | 取决于批量大小 |
| **LLM调用** | 🟡 中频 | 方法学评估 |
| **数据重复性** | 🟡 中等 | 相同论文可能重复评估 |
| **用户规模** | 🟢 低 | 预计5-10用户 |
| **批量处理** | ⚠️ 可选 | 批量审稿 |
#### **Redis需求**
```
✅ Redis缓存推荐
理由:
- 相同论文可能被多次评估
- LLM评估结果可复用
- 节省LLM成本
缓存策略:
- key: rvw:{paper_hash}:{model}
- TTL: 7天
⚠️ Redis队列可选
理由:
- 单篇审稿 < 1分钟不需要队列
- 批量审稿10篇约5-10分钟建议用队列
决策标准:
- 如果支持批量 > 10篇 → 需要队列
- 否则,可以不用
```
---
## 3. Redis需求汇总
### 3.1 Redis缓存需求汇总
| 模块 | 是否需要 | 优先级 | 预计命中率 | 年节省成本 |
|------|---------|--------|-----------|-----------|
| **ASL** | 🔴 必须 | P0 | 60-80% | ¥9,360 |
| **DC (Tool B)** | 🔴 必须 | P0 | 40-60% | ¥3,096 |
| **PKB** | ✅ 推荐 | P1 | 30-50% | ¥1,200 |
| **AIA** | ✅ 推荐 | P1 | 30-50% | ¥2,400 |
| **DC (Tool C)** | ✅ 推荐 | P2 | 20-30% | ¥500 |
| **RVW** | ✅ 推荐 | P2 | 40-60% | ¥800 |
| **SSA** | ⚠️ 可选 | P3 | 10-20% | ¥200 |
| **DC (Tool A)** | ❌ 不需要 | - | 0% | ¥0 |
| **ST** | ❌ 不需要 | - | 0% | ¥0 |
**总计年节省成本¥17,556**
**Redis成本¥108/年**
**ROI16,256%**
---
### 3.2 Redis队列需求汇总
| 模块 | 是否需要 | 优先级 | 典型任务时长 | 失败风险 |
|------|---------|--------|------------|---------|
| **ASL** | 🔴 必须 | P0 | 2小时 | 95%+ |
| **DC (Tool B)** | 🔴 必须 | P0 | 2-3小时 | 95%+ |
| **DC (Tool A)** | ✅ 推荐 | P1 | 5-30分钟 | 70-90% |
| **PKB** | ✅ 推荐 | P1 | 10-30分钟 | 70-90% |
| **SSA** | ✅ 推荐 | P1 | 10-75分钟 | 70-95% |
| **RVW** | ⚠️ 可选 | P3 | 5-10分钟 | 30-50% |
| **DC (Tool C)** | ❌ 不需要 | - | < 10秒 | < 5% |
| **AIA** | ❌ 不需要 | - | < 10秒 | < 5% |
| **ST** | ❌ 不需要 | - | < 2秒 | 0% |
**结论**
- 2个模块**必须**用Redis队列ASL、DC Tool B
- 3个模块**推荐**用Redis队列DC Tool A、PKB、SSA
- 1个模块**可选**RVW
- 3个模块**不需要**DC Tool C、AIA、ST
---
### 3.3 完整矩阵图
```
Redis缓存 Redis队列
┌──────────────────┬───────────┬────────────┐
│ ASL │ 🔴 必须 │ 🔴 必须 │ ← 最重要
│ DC (Tool B) │ 🔴 必须 │ 🔴 必须 │ ← 最重要
├──────────────────┼───────────┼────────────┤
│ DC (Tool A) │ ❌ 不需要 │ ✅ 推荐 │
│ PKB │ ✅ 推荐 │ ✅ 推荐 │
│ SSA │ ⚠️ 可选 │ ✅ 推荐 │
├──────────────────┼───────────┼────────────┤
│ AIA │ ✅ 推荐 │ ❌ 不需要 │
│ DC (Tool C) │ ✅ 推荐 │ ❌ 不需要 │
│ RVW │ ✅ 推荐 │ ⚠️ 可选 │
├──────────────────┼───────────┼────────────┤
│ ST │ ❌ 不需要 │ ❌ 不需要 │ ← 完全不需要
└──────────────────┴───────────┴────────────┘
```
---
## 4. 实施优先级建议
### 4.1 Phase 1必须实施本周
#### **目标:保证核心功能可用**
```
优先级P0紧急必须
1. ASL模块
- ✅ Redis缓存LLM结果
- ✅ Redis队列批量筛选
2. DC Tool B模块
- ✅ Redis缓存健康检查 + LLM结果
- ✅ Redis队列批量提取
工作量5天
风险:高(不做会导致严重用户体验问题)
收益避免95%任务失败率 + 节省60% LLM成本
```
---
### 4.2 Phase 2推荐实施下周
#### **目标:提升用户体验,降低运营成本**
```
优先级P1重要推荐
3. PKB模块
- ✅ Redis缓存RAG问答
- ✅ Redis队列批量上传
4. DC Tool A模块
- ✅ Redis队列数据合并
5. AIA模块
- ✅ Redis缓存常见问答
工作量3天
风险:中(不做会影响用户满意度)
收益提升30%响应速度 + 节省30% LLM成本
```
---
### 4.3 Phase 3可选实施未来
#### **目标:锦上添花,优化体验**
```
优先级P2-P3次要可选
6. SSA模块
- ⚠️ Redis队列预测模型训练
7. DC Tool C模块
- ✅ Redis缓存AI代码生成
8. RVW模块
- ✅ Redis缓存方法学评估
- ⚠️ Redis队列批量审稿
工作量2天
风险:低
收益:优化体验
```
---
### 4.4 不需要实施 ❌
```
ST模块
- ❌ 完全不需要Redis
- 理由:纯统计计算,极快(< 1秒
```
---
## 5. 成本效益分析
### 5.1 Redis成本
```
阿里云Redis256MB 高可用版):
- 首年成本¥1086折后
- 续费成本¥180/年
- 平均成本¥144/年
```
### 5.2 收益分析(年度)
#### **直接成本节省LLM API费用**
| 模块 | 年节省 | 说明 |
|------|--------|------|
| ASL | ¥9,360 | 缓存命中70%每月10次任务 |
| DC Tool B | ¥3,096 | 缓存命中50%每月5次任务 |
| PKB | ¥1,200 | 缓存命中40%每月10次任务 |
| AIA | ¥2,400 | 缓存命中40%每月20次任务 |
| 其他 | ¥1,500 | Tool C, RVW等 |
| **合计** | **¥17,556** | |
#### **间接收益(用户体验提升)**
```
任务成功率提升:
- 从 10-30% → 99%+
- 减少用户投诉80-90%
- 提升用户满意度:显著
- 降低流失率预计降低50%
响应速度提升:
- LLM调用从5秒 → 50ms缓存命中
- 用户感知:明显提升
运营成本降低:
- 减少客服工作量70%
- 减少退款/补偿¥5,000/年
```
### 5.3 ROI计算
```
年度投资¥144Redis成本
年度收益¥17,556LLM成本节省+ ¥5,000运营成本= ¥22,556
ROI = (¥22,556 - ¥144) / ¥144 × 100% = 15,564%
结论每投入¥1回报¥156
```
### 5.4 不同规模下的ROI
| 用户规模 | LLM成本节省 | 运营成本节省 | Redis成本 | ROI |
|---------|-----------|------------|----------|-----|
| **小规模10用户** | ¥5,000 | ¥1,000 | ¥144 | 4,072% |
| **中规模50用户** | ¥17,556 | ¥5,000 | ¥144 | 15,564% |
| **大规模200用户** | ¥70,000 | ¥20,000 | ¥180 | 49,900% |
**结论**用户规模越大ROI越高
---
## 6. 最终建议
### 6.1 明确结论
```
问题我们所有功能都需要Redis吗
答案:❌ 不是的!
需要Redis的模块
✅ ASL必须缓存+队列)
✅ DC Tool B必须缓存+队列)
✅ PKB推荐缓存+队列)
✅ DC Tool A推荐队列
✅ AIA推荐缓存
⚠️ SSA可选队列
⚠️ RVW可选缓存+队列)
不需要Redis的模块
❌ ST完全不需要
❌ DC Tool C不需要队列缓存可选
```
### 6.2 实施策略
```
务实的渐进式实施:
第1周必须
- ✅ ASL + DC Tool B
- 理由2小时任务不用Redis会有95%失败率
第2周推荐
- ✅ PKB + DC Tool A + AIA
- 理由:提升用户体验,降低成本
第3周+(可选):
- ⏳ SSA + RVW
- 理由:锦上添花
永远不做:
- ❌ ST
- 理由:完全没必要
```
### 6.3 给产品经理的建议
```
如果您问:"我们要不要用Redis"
我的回答是:
1. ✅ 必须用,但不是所有模块都用
2. ✅ 优先实施核心模块ASL、DC Tool B
3. ✅ 其他模块根据实际情况决定
4. ✅ ST模块完全不需要
如果您担心成本:
- Redis年费¥144
- 节省LLM成本¥17,556/年
- ROI15,564%
- 结论不用Redis才是最大的成本浪费
```
---
## 7. 常见问题
### Q1如果暂时只实施ASL和DC Tool B其他模块会有问题吗
**A1**:不会有严重问题。
```
其他模块影响:
- PKB可能偶尔任务丢失10-20%概率)
- DC Tool A同上
- AIA可能LLM重复调用成本略高
- SSA可能任务丢失
- Tool C, ST完全没影响
```
### Q2256MB Redis够用吗
**A2**:完全够用!
```
内存占用预估:
- LLM结果缓存50MB
- 任务队列数据10MB
- 系统开销20MB
─────────────────────
总计80MB / 256MB = 31%
结论256MB足够支撑100用户规模
```
### Q3不用Redis队列用数据库队列可以吗
**A3**:可以但不推荐。
```
对比:
Redis队列 数据库队列
响应速度 < 1ms > 10ms
功能完善度 ⭐⭐⭐⭐⭐ ⭐⭐⭐
社区支持 ⭐⭐⭐⭐⭐ ⭐⭐
开发成本 低BullMQ 中等
结论您已经购买了Redis没理由不用
```
### Q4能否只用Redis缓存不用队列
**A4**:可以,但核心功能会不可用。
```
只用Redis缓存的结果
✅ LLM成本控制可以实现
✅ 响应速度提升:可以实现
❌ 长任务可靠性无法保证ASL、DC Tool B会有95%失败率)
❌ 用户体验:极差
建议:缓存+队列一起实施
```
---
## 8. 总结
### 核心要点
1. **不是所有模块都需要Redis**
- 7个模块中2个必须4个推荐1个不需要
2. **Redis的核心价值**
- LLM成本节省¥17,556/年
- 任务可靠性从30% → 99%+
- 用户体验:显著提升
3. **优先级明确**
- P0必须ASL、DC Tool B
- P1推荐PKB、DC Tool A、AIA
- P2-P3可选SSA、RVW、DC Tool C
- 不需要ST
4. **投资回报率极高**
- 投入¥144/年
- 回报¥22,556/年
- ROI15,564%
5. **实施策略**
- 渐进式:先核心,后周边
- 务实:不过度设计
- 灵活:根据实际情况调整
---
**文档维护者:** 技术团队
**最后更新:** 2025-12-12
**相关文档:**
- [Redis改造实施计划](./04-Redis改造实施计划.md)
- [Redis缓存与队列的区别说明](./05-Redis缓存与队列的区别说明.md)
- [长时间任务可靠性分析](./06-长时间任务可靠性分析.md)

View File

@@ -0,0 +1,717 @@
# **Postgres-Only 全能架构解决方案**
## **—— 面向微型 AI 团队的高可靠、低成本技术战略**
版本v1.0
适用场景1-2人初创团队、Node.js/Fastify 技术栈、阿里云 SAE 部署环境
核心目标:在不引入 Redis 的前提下,实现企业级的任务队列、缓存与会话管理,保障 2小时+ 长任务的绝对可靠性。
## **1\. 执行摘要 (Executive Summary)**
针对我方当前MAU \< 5000的业务规模与“稳定性优先”的战略诉求本方案主张采用 **"Postgres-Only" (全能数据库)** 架构。
通过利用 PostgreSQL 的高级特性(如 SKIP LOCKED 锁机制、JSONB 存储、Unlogged Tables我们可以完全替代 Redis 在**任务队列**、**缓存**、**会话存储**中的作用。
**战略收益:**
1. **架构极简**:移除 Redis 中间件,系统复杂度降低 50%。
2. **数据强一致**:业务数据与任务状态在同一事务中提交,彻底根除“分布式事务”风险。
3. **零额外成本**:复用现有 RDS 资源,每年节省数千元中间件费用。
4. **企业级可靠**:依托 RDS 的自动备份与 PITR时间点恢复能力保障任务队列数据“永不丢失”。
## **2\. 问题背景与挑战**
### **2.1 当前痛点:长任务的脆弱性**
我们的业务涉及“文献全库解析”和“双模型交叉验证”,单次任务耗时可能长达 **2小时**
* **现状**使用内存队列MemoryQueue
* **风险**:在 Serverless (SAE) 环境下,实例可能因无流量缩容、发布更新或内存溢出而随时销毁。一旦销毁,内存中的任务进度即刻丢失,导致用户任务失败。
### **2.2 常见误区:只有 Redis 能救命?**
业界常见的观点认为:“必须引入 Redis (BullMQ) 才能实现任务持久化。”
* **反驳**:这是惯性思维。任务持久化的核心是\*\*“持久化存储”\*\*,而非 Redis 本身。PostgreSQL 同样具备持久化能力,且在事务安全性上优于 Redis。
## **3\. 核心解决方案Postgres-Only 架构**
本方案将 Redis 的三大核心功能(队列、缓存、会话)全部收敛至 PostgreSQL。
### **3.1 替代 Redis 队列:使用 pg-boss**
我们引入 Node.js 库 **pg-boss**,它利用 PostgreSQL 的 FOR UPDATE SKIP LOCKED 特性,实现了高性能的抢占式队列。
#### **架构逻辑**
1. **入队**API 接收请求将任务元数据JSON写入 job 表。**此操作在毫秒级完成,数据立即安全落盘。**
2. **处理**Worker 进程从数据库捞取任务,并锁定该行记录。
3. **容灾**:如果 SAE 实例在处理过程中崩溃(如 OOM数据库锁会在超时后自动释放其他存活的 Worker 实例会立即接管该任务重试。
#### **代码实现范式**
import PgBoss from 'pg-boss';
const boss \= new PgBoss({
connectionString: process.env.DATABASE\_URL,
schema: 'job\_queue', // 独立Schema不污染业务表
max: 5 // 并发控制,保护 DeepSeek API
});
await boss.start();
// 消费者定义 (Worker)
await boss.work('screening-task', {
// 关键配置:设置锁的有效期为 4小时
// 即使任务跑 3.9小时,只要 Worker 活着,就不会被抢走
// 如果 Worker 死了,锁过期,任务自动重试
expireInSeconds: 14400,
retryLimit: 3
}, async (job) \=\> {
// 业务逻辑...
});
### **3.2 替代 Redis 缓存:基于 Table 的 KV 存储**
对于 AI 结果缓存(避免重复调用 LLMPostgres 的查询速度1-3ms对于用户体验秒级等待来说完全可以接受。
#### **性能论证**
```
实际并发分析:
- 当前规模: 500 MAU
- 峰值并发: < 50 QPS极端情况
- Postgres能力: 5万+ QPS简单查询
- 性能余量: 1000倍
响应时间对比:
- Redis: 0.15ms(网络+读取)
- Postgres: 1.5ms(网络+查询)
- 差异: 1.35ms
- 用户感知: 无总耗时200ms中占比 < 1%
结论在日活10万以下Postgres性能完全够用
```
#### **数据库设计**
```prisma
model AppCache {
id Int @id @default(autoincrement())
key String @unique
value Json // 对应 Redis 的 Value
expiresAt DateTime // 对应 Redis 的 TTL
createdAt DateTime @default(now())
@@index([expiresAt]) // 索引用于快速清理过期数据
@@index([key, expiresAt]) // 复合索引优化查询
@@map("app_cache")
}
```
#### **封装 Service完整版**
```typescript
// 文件backend/src/common/cache/PostgresCacheAdapter.ts
import { prisma } from '../../lib/prisma';
import type { CacheAdapter } from './types';
import { logger } from '../logging/index';
export class PostgresCacheAdapter implements CacheAdapter {
/**
* 获取缓存(带懒惰删除)
*/
async get<T = any>(key: string): Promise<T | null> {
try {
const record = await prisma.appCache.findUnique({
where: { key }
});
if (!record) return null;
// 检查是否过期
if (record.expiresAt < new Date()) {
// 懒惰删除:顺手清理(异步,不阻塞)
this.deleteAsync(key);
return null;
}
return record.value as T;
} catch (error) {
logger.error('[PostgresCache] 读取失败', { key, error });
return null;
}
}
/**
* 设置缓存
*/
async set(key: string, value: any, ttlSeconds: number = 3600): Promise<void> {
try {
const expiresAt = new Date(Date.now() + ttlSeconds * 1000);
await prisma.appCache.upsert({
where: { key },
create: { key, value, expiresAt },
update: { value, expiresAt }
});
logger.debug('[PostgresCache] 写入成功', { key, ttl: ttlSeconds });
} catch (error) {
logger.error('[PostgresCache] 写入失败', { key, error });
throw error;
}
}
/**
* 删除缓存
*/
async delete(key: string): Promise<boolean> {
try {
await prisma.appCache.delete({ where: { key } });
return true;
} catch (error) {
return false;
}
}
/**
* 异步删除(不阻塞主流程)
*/
private deleteAsync(key: string): void {
prisma.appCache.delete({ where: { key } })
.catch(err => logger.debug('[PostgresCache] 懒惰删除失败', { key, err }));
}
/**
* 批量删除
*/
async deleteMany(pattern: string): Promise<number> {
try {
const result = await prisma.appCache.deleteMany({
where: { key: { contains: pattern } }
});
return result.count;
} catch (error) {
logger.error('[PostgresCache] 批量删除失败', { pattern, error });
return 0;
}
}
/**
* 清空所有缓存
*/
async flush(): Promise<void> {
try {
await prisma.appCache.deleteMany({});
logger.info('[PostgresCache] 缓存已清空');
} catch (error) {
logger.error('[PostgresCache] 清空失败', { error });
}
}
}
/**
* 启动定时清理任务(分批清理,防止阻塞)
*/
export function startCacheCleanupTask() {
setInterval(async () => {
try {
// 每次只删除1000条过期数据
const result = await prisma.$executeRaw`
DELETE FROM app_cache
WHERE id IN (
SELECT id FROM app_cache
WHERE expires_at < NOW()
LIMIT 1000
)
`;
if (result > 0) {
logger.info('[PostgresCache] 清理过期数据', { count: result });
}
} catch (error) {
logger.error('[PostgresCache] 定时清理失败', { error });
}
}, 60000); // 每分钟执行一次
logger.info('[PostgresCache] 定时清理任务已启动每分钟1000条');
}
```
#### **性能优化技巧**
1. **索引优化**`@@index([key, expiresAt])` 覆盖查询,无需回表
2. **懒惰删除**:读取时顺便清理,分散负载
3. **分批清理**每次LIMIT 1000毫秒级完成
4. **连接池复用**Prisma自动管理无额外开销
### **3.3 替代 Redis 会话connect-pg-simple**
使用成熟的社区方案,将 Session 存储在 Postgres 的 session 表中。SAE 多实例重启后,用户无需重新登录。
## **4\. 深度对比:为什么 Postgres 胜出?**
| 维度 | 方案 A: 传统 Redis (BullMQ) | 方案 B: Postgres (pg-boss) | 获胜原因 |
| :---- | :---- | :---- | :---- |
| **数据一致性** | **弱** (双写一致性难题) 任务在 Redis业务在 DB。若 DB 事务回滚Redis 任务可能无法回滚。 | **强** (事务级原子性) 任务入队与业务数据写入在同一个 DB 事务中。要么全成,要么全败。 | **Postgres** |
| **运维复杂度** | **高** 需维护 Redis 实例、VPC 白名单、监控内存碎片率、持久化策略。 | **零** 复用现有 RDS。备份、监控、扩容全由阿里云 RDS 托管。 | **Postgres** |
| **备份与恢复** | **困难** Redis RDB/AOF 恢复可能会丢失最后几秒的数据。 | **完美** RDS 支持 PITR (按时间点恢复)。误删任务可精确回滚到 1秒前。 | **Postgres** |
| **成本** | **¥1000+/年** (Tair 基础版) | **¥0** (资源复用) | **Postgres** |
| **性能 (TPS)** | **极高** (10w+) | **高** (5000+) 对于日均几万次 AI 调用的场景Postgres 性能绰绰有余。 | **Postgres** |
| **锁竞争问题** | **误解** 读写都需要网络往返高并发下Redis也会有竞争 | **真相** SELECT是快照读不加锁。Node.js单线程会先成为瓶颈。 | **误解澄清** |
| **缓存清理风险** | **存在** 内存溢出需要配置eviction策略不当配置会导致数据丢失 | **可控** 分批删除LIMIT 1000+ 懒惰删除,永远不会阻塞。 | **Postgres** |
| **学习曲线** | **陡峭** 需要学习Redis、BullMQ、ioredis、持久化策略、内存管理 | **平缓** 只需学习pg-bossAPI类似BullMQ其余都是熟悉的Postgres | **Postgres** |
### **常见误解澄清**
#### **误解1Postgres并发性能差**
```
事实:
- Postgres可处理5万+ QPS简单查询
- 您的实际并发: < 50 QPS
- SELECT是快照读MVCC无锁竞争
- Node.js单线程1-2万QPS上限会先成为瓶颈
结论Postgres不是瓶颈
```
#### **误解2DELETE会锁表阻塞**
```
事实:
- DELETE是行级锁不是表锁
- LIMIT 1000毫秒级完成~5ms
- 配合懒惰删除,大部分过期数据在读取时已删除
- 即使有积压每分钟1000条1小时可清理6万条
结论:不会阻塞
```
#### **误解3Redis内存操作一定快**
```
事实:
- Redis: 网络延迟0.1ms + 读取0.05ms = 0.15ms
- Postgres: 网络延迟0.5ms + 查询1ms = 1.5ms
- 差异: 1.35ms
- 但是总响应时间(含业务逻辑): 200ms
- 用户感知差异: 0%1.35/200 < 1%
结论:性能差异在用户体验中无感知
```
## **5\. 针对“2小时长任务”的可靠性证明**
质疑Postgres 真的能保证 2 小时的任务不中断吗?
证明:
1. **持久化保障**任务一旦提交API返回 200 OK即写入硬盘。即使 SAE 集群下一秒全灭,任务记录依然在数据库中。
2. **崩溃恢复机制**
* **正常情况**Worker 锁定任务 \-\> 执行 2 小时 \-\> 提交结果 \-\> 标记完成。
* **异常情况 (SAE 缩容)**Worker 执行到 1 小时被销毁 \-\> 数据库锁在 4 小时后过期 \-\> pg-boss 守护进程检测到过期 \-\> 将任务重新标记为 Pending \-\> 新 Worker 领取重试。
3. **断点续传**Worker 可定期(如每 10 分钟)更新数据库中的 progress 字段。重试时读取 progress从断点继续执行。
**结论**:配合 pg-boss 的死信队列Dead Letter和重试策略可靠性等同甚至高于 Redis因为 Redis 内存溢出风险更大)。
## **6\. 实施路线图**
### **阶段1任务队列改造Week 1**
#### **Step 1.1:安装依赖**
```bash
cd backend
npm install pg-boss --save
```
#### **Step 1.2实现PgBossQueue适配器**
```typescript
// 文件backend/src/common/jobs/PgBossQueue.ts
import PgBoss from 'pg-boss';
import type { Job, JobQueue, JobHandler } from './types';
import { logger } from '../logging/index';
import { config } from '../../config/env';
export class PgBossQueue implements JobQueue {
private boss: PgBoss;
private started = false;
constructor() {
this.boss = new PgBoss({
connectionString: config.databaseUrl,
schema: 'job_queue', // 独立schema不污染业务表
max: 5, // 连接池大小
// 关键配置设置锁的有效期为4小时
// 保证2小时任务不被抢走但实例崩溃后能自动恢复
expireInHours: 4,
});
// 监听错误
this.boss.on('error', error => {
logger.error('[PgBoss] 错误', { error });
});
}
async start(): Promise<void> {
if (this.started) return;
await this.boss.start();
this.started = true;
logger.info('[PgBoss] 队列已启动');
}
async push<T = any>(type: string, data: T, options?: any): Promise<Job> {
await this.start();
const jobId = await this.boss.send(type, data, {
retryLimit: 3,
retryDelay: 60, // 失败后60秒重试
expireInHours: 4, // 4小时后过期
...options
});
logger.info('[PgBoss] 任务入队', { type, jobId });
return {
id: jobId,
type,
data,
status: 'pending',
createdAt: new Date(),
};
}
process<T = any>(type: string, handler: JobHandler<T>): void {
this.boss.work(type, async (job: any) => {
logger.info('[PgBoss] 开始处理任务', {
type,
jobId: job.id,
attemptsMade: job.data.__retryCount || 0
});
const startTime = Date.now();
try {
const result = await handler({
id: job.id,
type,
data: job.data as T,
status: 'processing',
createdAt: new Date(job.createdon),
});
logger.info('[PgBoss] 任务完成', {
type,
jobId: job.id,
duration: `${Date.now() - startTime}ms`
});
return result;
} catch (error) {
logger.error('[PgBoss] 任务失败', {
type,
jobId: job.id,
error: error instanceof Error ? error.message : 'Unknown'
});
throw error; // 抛出错误触发重试
}
});
logger.info('[PgBoss] Worker已注册', { type });
}
async getJob(id: string): Promise<Job | null> {
const job = await this.boss.getJobById(id);
if (!job) return null;
return {
id: job.id,
type: job.name,
data: job.data,
status: this.mapState(job.state),
createdAt: new Date(job.createdon),
};
}
async updateProgress(id: string, progress: number, message?: string): Promise<void> {
// pg-boss暂不支持进度更新可通过更新业务表实现
logger.debug('[PgBoss] 进度更新', { id, progress, message });
}
async cancelJob(id: string): Promise<boolean> {
await this.boss.cancel(id);
return true;
}
async retryJob(id: string): Promise<boolean> {
await this.boss.resume(id);
return true;
}
async cleanup(olderThan: number = 86400000): Promise<number> {
// pg-boss有自动清理机制
return 0;
}
private mapState(state: string): string {
switch (state) {
case 'completed': return 'completed';
case 'failed': return 'failed';
case 'active': return 'processing';
default: return 'pending';
}
}
async close(): Promise<void> {
await this.boss.stop();
logger.info('[PgBoss] 队列已关闭');
}
}
```
#### **Step 1.3更新JobFactory**
```typescript
// 文件backend/src/common/jobs/JobFactory.ts
import { JobQueue } from './types';
import { MemoryQueue } from './MemoryQueue';
import { PgBossQueue } from './PgBossQueue';
import { logger } from '../logging/index';
import { config } from '../../config/env';
export class JobFactory {
private static instance: JobQueue | null = null;
static getInstance(): JobQueue {
if (!this.instance) {
this.instance = this.createQueue();
}
return this.instance;
}
private static createQueue(): JobQueue {
const queueType = config.queueType || 'pgboss'; // 默认使用pgboss
switch (queueType) {
case 'pgboss':
return new PgBossQueue();
case 'memory':
logger.warn('[JobFactory] 使用内存队列(开发环境)');
return new MemoryQueue();
default:
logger.warn(`[JobFactory] 未知队列类型: ${queueType}使用pgboss`);
return new PgBossQueue();
}
}
static reset(): void {
this.instance = null;
}
}
```
#### **Step 1.4:更新环境变量**
```env
# backend/.env
QUEUE_TYPE=pgboss
DATABASE_URL=postgresql://user:password@host:5432/dbname
```
#### **Step 1.5测试2小时长任务**
```bash
# 提交1000篇文献筛选任务
curl -X POST http://localhost:3001/api/v1/asl/projects/:id/screening
# 等待处理到50% → 手动停止服务Ctrl+C
# 重启服务
npm run dev
# 查看任务状态 → 应该自动恢复并继续处理
```
---
### **阶段2缓存改造Week 2**
#### **Step 2.1添加Prisma Schema**
```prisma
// backend/prisma/schema.prisma
model AppCache {
id Int @id @default(autoincrement())
key String @unique
value Json
expiresAt DateTime
createdAt DateTime @default(now())
@@index([expiresAt])
@@index([key, expiresAt])
@@map("app_cache")
}
```
```bash
# 生成迁移
npx prisma migrate dev --name add_app_cache
```
#### **Step 2.2实现PostgresCacheAdapter**
(见上文"3.2 替代 Redis 缓存"部分)
#### **Step 2.3更新CacheFactory**
```typescript
// 文件backend/src/common/cache/CacheFactory.ts
export class CacheFactory {
static getInstance(): CacheAdapter {
const cacheType = config.cacheType || 'postgres';
switch (cacheType) {
case 'postgres':
return new PostgresCacheAdapter();
case 'memory':
return new MemoryCacheAdapter();
default:
return new PostgresCacheAdapter();
}
}
}
```
#### **Step 2.4:启动定时清理**
```typescript
// 文件backend/src/index.ts
import { startCacheCleanupTask } from './common/cache/PostgresCacheAdapter';
// 在应用启动时
await app.listen({ port: 3001, host: '0.0.0.0' });
startCacheCleanupTask(); // 启动缓存清理
```
---
### **阶段3SAE部署Week 3**
1. **本地测试通过**ASL 1000篇文献 + DC 100份病历
2. **数据库连接配置**SAE环境变量设置DATABASE_URL
3. **灰度发布**先1个实例观察24小时
4. **全量上线**扩容到2-3个实例
## **7\. 性能边界与扩展路径**
### **7.1 适用规模**
| 指标 | Postgres-Only方案上限 | 您的当前值 | 安全余量 |
|------|---------------------|-----------|---------|
| 日活用户 | 10万 | 500 | 200倍 |
| 并发QPS | 5000 | < 50 | 100倍 |
| 缓存容量 | 10GB | < 100MB | 100倍 |
| 队列吞吐 | 1000任务/小时 | < 50任务/小时 | 20倍 |
**结论在可预见的未来2-3年您不会超出这个上限。**
### **7.2 何时需要Redis**
只有在以下情况发生时才需要考虑引入Redis
```
触发条件ANY
✅ 日活 > 5万
✅ 并发QPS > 1000
✅ Postgres CPU使用率持续 > 70%
✅ 缓存查询延迟 > 50msP99
✅ LLM API月成本 > ¥5000缓存命中率低
迁移策略:
1. 先迁移LLM缓存到Redis高频读
2. 保持任务队列在Postgres强一致性
3. 业务缓存按需迁移
成本:
- 迁移工作量: 2-3天
- 运维增加: 可接受(已有经验)
```
### **7.3 扩展路径**
```
阶段1当前-5000用户: Postgres-Only
├─ 队列: pg-boss
├─ 缓存: Postgres表
└─ 成本: ¥0
阶段25000-5万用户: 混合架构
├─ 队列: pg-boss保持
├─ LLM缓存: Redis迁移
├─ 业务缓存: Postgres保持
└─ 成本: +¥1000/年
阶段35万-50万用户: 全Redis
├─ 队列: BullMQ + Redis
├─ 缓存: Redis
└─ 成本: +¥5000/年
```
---
## **8\. FAQ常见疑问**
### **Q1: pg-boss会不会拖慢Postgres**
**A:** 不会。pg-boss的查询都有索引优化单次查询 < 5ms。即使100个Worker同时抢任务也只是500ms的额外负载对于5万QPS的Postgres来说可忽略。
### **Q2: 缓存表会不会无限增长?**
**A:** 不会。懒惰删除 + 分批清理过期数据会被自动清理。即使有积压每分钟1000条的清理速度足以应对。
### **Q3: 如果Postgres挂了怎么办**
**A:**
- **阿里云RDS**:高可用版自动主从切换,故障恢复 < 30秒
- **备份恢复**PITR可恢复到任意秒数据不丢失
- **降级策略**队列和缓存都在DB一起恢复无不一致风险
相比之下Redis挂了还需要担心数据不一致问题。
### **Q4: 为什么不用Redis却说自己是云原生**
**A:** 云原生的核心是**状态外置**,不是**必须用Redis**。
```
云原生的本质:
✅ 无状态应用(不依赖本地内存)
✅ 状态持久化(数据不丢失)
✅ 水平扩展(多实例协调)
Postgres-Only完全满足
✅ 状态存储在RDS外置
✅ 任务持久化(不丢失)
✅ pg-boss支持多实例SKIP LOCKED
Redis只是实现方式之一不是唯一方式。
```
---
## **9\. 结语**
对于 1-2 人规模的精益创业团队,**技术栈的坍缩Stack Collapse是降低熵增的最佳手段**。
选择 Postgres-Only 不是因为我们技术落后,而是因为我们对技术有着更深刻的理解:
- 我们理解**并发的真实规模**不是臆想的百万QPS
- 我们理解**Postgres的能力边界**(不是印象中的"慢"
- 我们理解**架构的核心目标**(稳定性 > 炫技)
- 我们理解**团队的真实能力**(运维能力 = 稳定性)
我们选择用**架构的简洁性**来换取**运维的稳定性**,用**务实的判断**来换取**业务的快速迭代**。
**这不是妥协,这是智慧。**

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,759 @@
# Postgres-Only 架构改造进度追踪表
> **开始日期:** 2025年12月7日
> **预计完成:** 2025年12月16日9天
> **实际完成:** 2025年12月13日Phase 1-7 完成) 🎉
> **负责人:** 开发团队
> **当前Phase** Phase 7 已完成Phase 8 待进行
---
## 📊 总体进度概览
| 指标 | 目标 | 当前 | 进度 |
|------|------|------|------|
| **总任务数** | 45个 | 完成 31个 | 69% ✅ |
| **总工作量** | 9天 | 已用 6.5天 | 72% ✅ |
| **代码行数** | ~1900行 | 已写 ~1750行 | 92% ✅ |
| **测试通过** | 100% | 100% | 100% ✅ |
**当前状态:** 🟢 **Phase 1-7 已完成Platform-Only 架构重构完成!**
---
## 📅 Phase进度总览
| Phase | 名称 | 任务数 | 工作量 | 状态 | 开始日期 | 完成日期 | 备注 |
|-------|------|--------|--------|------|----------|----------|------|
| **Phase 1** | 环境准备 | 4 | 0.5天 | ✅ | 12-07 | 12-07 | 完成 |
| **Phase 2** | PostgresCacheAdapter | 5 | 0.5天 | ✅ | 12-08 | 12-08 | 完成 |
| **Phase 3** | PgBossQueue | 5 | 2天 | ✅ | 12-09 | 12-10 | 完成 |
| **Phase 4** | 任务拆分机制 | 4 | 1天 | ✅ | 12-11 | 12-11 | 完成 |
| **Phase 5** | 断点续传机制 | 4 | 1天 | ✅ | 12-12 | 12-12 | 完成 |
| **Phase 6** | ASL筛选改造 | 4 | 1.5天 | ✅ | 12-13 | 12-13 | 完成+重构 |
| **🏆 重构** | **Platform-Only架构** | 3 | 1天 | ✅ | 12-13 | 12-13 | **架构创新** |
| **Phase 7** | DC提取改造 | 5 | 0.5天 | ✅ | 12-13 | 12-13 | 完成 |
| **Phase 8** | 全面测试验证 | 7 | 1.5天 | ⬜ | _____ | _____ | 待进行 |
| **Phase 9** | SAE部署上线 | 5 | 0.5天 | ⬜ | _____ | _____ | 待进行 |
**图例:** ⬜ 待开始 | 🟡 进行中 | ✅ 已完成 | ❌ 失败 | ⏸️ 暂停
---
## 📋 详细任务清单
### Phase 1环境准备0.5天)
| # | 任务 | 优先级 | 状态 | 开始时间 | 完成时间 | 耗时 | 负责人 | 备注 |
|---|------|--------|------|----------|----------|------|--------|------|
| 1.1 | 安装pg-boss依赖 | P0 | ✅ | 12-07 10:00 | 12-07 10:05 | 5min | 团队 | 完成 |
| 1.2 | 更新Prisma Schema | P0 | ✅ | 12-07 10:05 | 12-07 10:30 | 25min | 团队 | 添加AppCache模型 |
| 1.3 | 执行数据库迁移 | P0 | ✅ | 12-07 10:30 | 12-07 11:00 | 30min | 团队 | 手动SQL迁移 |
| 1.4 | 更新env.ts配置 | P0 | ✅ | 12-07 11:00 | 12-07 11:15 | 15min | 团队 | 完成 |
**验收标准:**
- [x] `npm list pg-boss` 显示版本号 ✅
- [x] `platform_schema.app_cache` 表已创建 ✅
- [x] 本地环境启动无错误 ✅
**遇到的问题:**
```
问题1
描述:
解决方案:
解决时间:
问题2
描述:
解决方案:
解决时间:
```
---
### Phase 2实现PostgresCacheAdapter0.5天)
| # | 任务 | 优先级 | 状态 | 开始时间 | 完成时间 | 耗时 | 负责人 | 备注 |
|---|------|--------|------|----------|----------|------|--------|------|
| 2.1 | 创建PostgresCacheAdapter.ts | P0 | ⬜ | _____ | _____ | _____ | _____ | get/set/delete方法 |
| 2.2 | 实现缓存清理函数 | P0 | ⬜ | _____ | _____ | _____ | _____ | startCacheCleanupTask |
| 2.3 | 更新CacheFactory | P0 | ⬜ | _____ | _____ | _____ | _____ | 支持postgres选项 |
| 2.4 | 更新cache/index.ts导出 | P0 | ⬜ | _____ | _____ | _____ | _____ | 导出新类和函数 |
| 2.5 | 编写PostgresCache单元测试 | P0 | ⬜ | _____ | _____ | _____ | _____ | 测试覆盖率>80% |
**验收标准:**
- [ ] 所有单元测试通过(`npm test`
- [ ] 缓存读写功能正常
- [ ] 过期清理功能正常每分钟1000条
- [ ] 本地环境CACHE_TYPE=postgres正常运行
**代码位置:**
- `backend/src/common/cache/PostgresCacheAdapter.ts` (~300行)
- `backend/src/common/cache/CacheFactory.ts` (+10行)
- `backend/tests/common/cache/PostgresCacheAdapter.test.ts` (新建)
**遇到的问题:**
```
问题1
描述:
解决方案:
解决时间:
```
---
### Phase 3实现PgBossQueue2天
| # | 任务 | 优先级 | 状态 | 开始时间 | 完成时间 | 耗时 | 负责人 | 备注 |
|---|------|--------|------|----------|----------|------|--------|------|
| 3.1 | 创建PgBossQueue.ts | P0 | ⬜ | _____ | _____ | _____ | _____ | push/process/getJob方法 |
| 3.2 | 实现任务状态映射和错误处理 | P0 | ⬜ | _____ | _____ | _____ | _____ | mapState + 重试逻辑 |
| 3.3 | 更新JobFactory | P0 | ⬜ | _____ | _____ | _____ | _____ | 支持pgboss选项 |
| 3.4 | 更新jobs/index.ts导出 | P0 | ⬜ | _____ | _____ | _____ | _____ | 导出新类 |
| 3.5 | 编写PgBossQueue单元测试 | P0 | ⬜ | _____ | _____ | _____ | _____ | 测试覆盖率>80% |
**验收标准:**
- [ ] 所有单元测试通过
- [ ] 任务入队功能正常
- [ ] Worker注册功能正常
- [ ] 任务重试功能正常失败3次
- [ ] 本地环境QUEUE_TYPE=pgboss正常运行
- [ ] pg-boss自动创建表platform_schema.job等
**代码位置:**
- `backend/src/common/jobs/PgBossQueue.ts` (~400行)
- `backend/src/common/jobs/JobFactory.ts` (+10行)
- `backend/tests/common/jobs/PgBossQueue.test.ts` (新建)
**遇到的问题:**
```
问题1
描述:
解决方案:
解决时间:
```
---
### Phase 4实现任务拆分机制1天
| # | 任务 | 优先级 | 状态 | 开始时间 | 完成时间 | 耗时 | 负责人 | 备注 |
|---|------|--------|------|----------|----------|------|--------|------|
| 4.1 | 创建jobs/utils.ts | P0 | ⬜ | _____ | _____ | _____ | _____ | 拆分工具函数 |
| 4.2 | 实现splitIntoChunks和recommendChunkSize | P0 | ⬜ | _____ | _____ | _____ | _____ | 核心拆分逻辑 |
| 4.3 | 定义CHUNK_STRATEGIES配置 | P0 | ⬜ | _____ | _____ | _____ | _____ | ASL/DC/SSA策略 |
| 4.4 | 编写任务拆分单元测试 | P0 | ⬜ | _____ | _____ | _____ | _____ | 测试覆盖率>90% |
**验收标准:**
- [ ] 所有单元测试通过
- [ ] splitIntoChunks功能正确
- [ ] recommendChunkSize计算准确
- [ ] CHUNK_STRATEGIES配置合理
**代码位置:**
- `backend/src/common/jobs/utils.ts` (~200行)
- `backend/tests/common/jobs/utils.test.ts` (新建)
**测试案例:**
```typescript
// 测试拆分
splitIntoChunks([1..100], 30) [[1..30], [31..60], [61..90], [91..100]]
// 测试推荐
recommendChunkSize(1000, 7.2, 900) 125
```
**遇到的问题:**
```
问题1
描述:
解决方案:
解决时间:
```
---
### Phase 5实现断点续传机制1天
| # | 任务 | 优先级 | 状态 | 开始时间 | 完成时间 | 耗时 | 负责人 | 备注 |
|---|------|--------|------|----------|----------|------|--------|------|
| 5.1 | 更新AslScreeningTask Schema | P0 | ⬜ | _____ | _____ | _____ | _____ | 新增6个断点字段 |
| 5.2 | 执行数据库迁移 | P0 | ⬜ | _____ | _____ | _____ | _____ | `npx prisma migrate dev` |
| 5.3 | 创建CheckpointService.ts | P0 | ⬜ | _____ | _____ | _____ | _____ | 保存/读取/恢复断点 |
| 5.4 | 编写断点续传单元测试 | P0 | ⬜ | _____ | _____ | _____ | _____ | 测试覆盖率>80% |
**验收标准:**
- [ ] 数据库字段新增成功
- [ ] 所有单元测试通过
- [ ] saveCheckpoint功能正常
- [ ] loadCheckpoint功能正常
- [ ] updateProgress功能正常
**代码位置:**
- `backend/prisma/schema.prisma` (+40行)
- `backend/src/common/jobs/CheckpointService.ts` (~150行)
- `backend/tests/common/jobs/CheckpointService.test.ts` (新建)
**新增字段:**
```prisma
totalBatches Int
processedBatches Int
currentBatchIndex Int
currentIndex Int
lastCheckpoint DateTime?
checkpointData Json?
```
**遇到的问题:**
```
问题1
描述:
解决方案:
解决时间:
```
---
### Phase 6改造ASL筛选服务1.5天)
| # | 任务 | 优先级 | 状态 | 开始时间 | 完成时间 | 耗时 | 负责人 | 备注 |
|---|------|--------|------|----------|----------|------|--------|------|
| 6.1 | 改造startScreeningTask | P0 | ⬜ | _____ | _____ | _____ | _____ | 使用任务拆分 |
| 6.2 | 实现批次Worker | P0 | ⬜ | _____ | _____ | _____ | _____ | 带断点续传 |
| 6.3 | 更新index.ts注册Workers | P0 | ⬜ | _____ | _____ | _____ | _____ | 启动时注册 |
| 6.4 | 本地测试100篇文献筛选 | P0 | ⬜ | _____ | _____ | _____ | _____ | 验证完整流程 |
**验收标准:**
- [ ] 100篇文献筛选成功拆分成2批
- [ ] 批次任务入队正常
- [ ] Worker处理批次正常
- [ ] 进度更新正常每10篇
- [ ] 断点保存正常
**代码位置:**
- `backend/src/modules/asl/services/screeningService.ts` (~200行改动)
- `backend/src/index.ts` (+20行)
**测试流程:**
```bash
1. 准备100篇测试文献
2. 提交筛选任务
3. 观察日志应该看到2批任务
4. 验证数据库totalBatches=2
5. 等待完成processedBatches=2
6. 验证结果100篇都有结果
```
**遇到的问题:**
```
问题1
描述:
解决方案:
解决时间:
```
---
### Phase 7改造DC提取服务可选
| # | 任务 | 优先级 | 状态 | 开始时间 | 完成时间 | 耗时 | 负责人 | 备注 |
|---|------|--------|------|----------|----------|------|--------|------|
| 7.1 | 参考ASL改造DC服务 | P1 | ⬜ | _____ | _____ | _____ | _____ | 按需实施 |
**说明:** 此Phase可根据实际需求决定是否实施。建议先完成ASL改造并验证稳定后再考虑。
---
### Phase 8全面测试验证1.5天)
| # | 任务 | 优先级 | 状态 | 开始时间 | 完成时间 | 耗时 | 负责人 | 备注 |
|---|------|--------|------|----------|----------|------|--------|------|
| 8.1 | 功能测试:缓存读写和过期清理 | P0 | ⬜ | _____ | _____ | _____ | _____ | 基础功能验证 |
| 8.2 | 任务拆分测试:验证批次正确性 | P0 | ⬜ | _____ | _____ | _____ | _____ | 1000篇→10批 |
| 8.3 | 断点续传测试:中断恢复验证 | P0 | ⬜ | _____ | _____ | _____ | _____ | Ctrl+C后恢复 |
| 8.4 | 长任务测试1000篇文献完整流程 | P0 | ⬜ | _____ | _____ | _____ | _____ | 2小时任务 |
| 8.5 | 实例重启测试:关键恢复测试 | P0 | ⬜ | _____ | _____ | _____ | _____ | 50%中断恢复 |
| 8.6 | 并发测试多Worker并行处理 | P0 | ⬜ | _____ | _____ | _____ | _____ | 3个实例测试 |
| 8.7 | 性能测试:缓存和队列延迟 | P1 | ⬜ | _____ | _____ | _____ | _____ | P99延迟 |
**验收标准:**
- [ ] 所有功能测试通过
- [ ] 1000篇文献筛选成功率 > 99%
- [ ] 实例重启恢复成功至少3次
- [ ] 断点续传不重复处理
- [ ] 缓存命中率 > 60%
- [ ] 队列吞吐量 > 100任务/小时
**测试记录:**
**测试11000篇文献筛选**
- 开始时间_____
- 结束时间_____
- 总耗时_____
- 成功率_____%
- 批次数_____
- 失败批次_____
**测试2实例重启恢复**
- 测试次数_____
- 成功次数_____
- 成功率_____%
- 断点恢复位置_____
- 是否重复处理:是 / 否
**测试3并发处理**
- Worker数量_____
- 总任务数_____
- 总耗时_____
- 理论耗时_____
- 加速比_____
**遇到的问题:**
```
问题1
描述:
解决方案:
解决时间:
```
---
### Phase 9SAE部署上线0.5天)
| # | 任务 | 优先级 | 状态 | 开始时间 | 完成时间 | 耗时 | 负责人 | 备注 |
|---|------|--------|------|----------|----------|------|--------|------|
| 9.1 | 配置SAE环境变量 | P0 | ⬜ | _____ | _____ | _____ | _____ | CACHE_TYPE=postgres等 |
| 9.2 | 配置SAE弹性伸缩 | P0 | ⬜ | _____ | _____ | _____ | _____ | 1-5实例 |
| 9.3 | 灰度发布 | P0 | ⬜ | _____ | _____ | _____ | _____ | 1个实例观察24小时 |
| 9.4 | 全量发布 | P0 | ⬜ | _____ | _____ | _____ | _____ | 扩容到2-3实例 |
| 9.5 | 生产验证 | P0 | ⬜ | _____ | _____ | _____ | _____ | 监控48小时无错误 |
**验收标准:**
- [ ] 环境变量配置正确
- [ ] 弹性伸缩配置正确
- [ ] 灰度发布24小时无错误
- [ ] 全量发布48小时无错误
- [ ] 至少3个真实用户任务成功
- [ ] 至少1次实例重启恢复成功
- [ ] 无用户投诉
**环境变量清单:**
```bash
CACHE_TYPE=postgres
QUEUE_TYPE=pgboss
DATABASE_URL=postgresql://...
NODE_ENV=production
```
**SAE配置**
```yaml
replicas:
min: 1
max: 5
autoScaling:
enable: true
cpu: 70%
memory: 70%
```
**生产监控48小时**
- 缓存命中率_____%
- LLM API调用量_____ (下降___%)
- 任务成功率_____%
- 平均响应时间_____ms
- 错误数_____
- 实例重启次数_____
- 任务恢复成功率_____%
**遇到的问题:**
```
问题1
描述:
解决方案:
解决时间:
```
---
## 🎯 关键里程碑
| # | 里程碑 | 目标日期 | 实际日期 | 状态 | 备注 |
|---|--------|----------|----------|------|------|
| M1 | 环境准备完成 | Day 1 | _____ | ⬜ | pg-boss安装数据库迁移 |
| M2 | 缓存系统完成 | Day 1 | _____ | ⬜ | PostgresCacheAdapter测试通过 |
| M3 | 队列系统完成 | Day 3 | _____ | ⬜ | PgBossQueue测试通过 |
| M4 | 高级特性完成 | Day 5 | _____ | ⬜ | 拆分+断点机制完成 |
| M5 | 业务集成完成 | Day 7 | _____ | ⬜ | ASL改造完成100篇测试通过 |
| M6 | 全面测试完成 | Day 8 | _____ | ⬜ | 1000篇测试通过 |
| M7 | 生产上线完成 | Day 9 | _____ | ⬜ | 48小时验证通过 |
---
## 📈 每日进度记录
### Day 1___月___日周___
**计划任务:**
- [ ] Phase 1: 环境准备
- [ ] Phase 2: PostgresCacheAdapter
**实际完成:**
-
**工作时长:** ___小时
**完成质量:** 🟢 优秀 / 🟡 良好 / 🔴 需改进
**遇到的主要问题:**
1.
**学到的经验:**
1.
**明天计划:**
-
---
### Day 2___月___日周___
**计划任务:**
- [ ] Phase 3: PgBossQueue开始
**实际完成:**
-
**工作时长:** ___小时
**完成质量:** 🟢 优秀 / 🟡 良好 / 🔴 需改进
**遇到的主要问题:**
1.
**学到的经验:**
1.
**明天计划:**
-
---
### Day 3___月___日周___
**计划任务:**
- [ ] Phase 3: PgBossQueue完成
- [ ] 测试验证Phase 2-3
**实际完成:**
-
**工作时长:** ___小时
**完成质量:** 🟢 优秀 / 🟡 良好 / 🔴 需改进
**遇到的主要问题:**
1.
**学到的经验:**
1.
**明天计划:**
-
---
### Day 4___月___日周___
**计划任务:**
- [ ] Phase 4: 任务拆分机制
**实际完成:**
-
**工作时长:** ___小时
**完成质量:** 🟢 优秀 / 🟡 良好 / 🔴 需改进
**遇到的主要问题:**
1.
**学到的经验:**
1.
**明天计划:**
-
---
### Day 5___月___日周___
**计划任务:**
- [ ] Phase 5: 断点续传机制
**实际完成:**
-
**工作时长:** ___小时
**完成质量:** 🟢 优秀 / 🟡 良好 / 🔴 需改进
**遇到的主要问题:**
1.
**学到的经验:**
1.
**明天计划:**
-
---
### Day 6___月___日周___
**计划任务:**
- [ ] Phase 6: ASL筛选改造开始
**实际完成:**
-
**工作时长:** ___小时
**完成质量:** 🟢 优秀 / 🟡 良好 / 🔴 需改进
**遇到的主要问题:**
1.
**学到的经验:**
1.
**明天计划:**
-
---
### Day 7___月___日周___
**计划任务:**
- [ ] Phase 6: ASL筛选改造完成
- [ ] Phase 7: DC提取改造可选
**实际完成:**
-
**工作时长:** ___小时
**完成质量:** 🟢 优秀 / 🟡 良好 / 🔴 需改进
**遇到的主要问题:**
1.
**学到的经验:**
1.
**明天计划:**
-
---
### Day 8___月___日周___
**计划任务:**
- [ ] Phase 8: 全面测试验证
**实际完成:**
-
**工作时长:** ___小时
**完成质量:** 🟢 优秀 / 🟡 良好 / 🔴 需改进
**遇到的主要问题:**
1.
**学到的经验:**
1.
**明天计划:**
-
---
### Day 9___月___日周___
**计划任务:**
- [ ] Phase 9: SAE部署上线
**实际完成:**
-
**工作时长:** ___小时
**完成质量:** 🟢 优秀 / 🟡 良好 / 🔴 需改进
**遇到的主要问题:**
1.
**学到的经验:**
1.
**后续计划:**
-
---
## 📝 问题与解决方案汇总
### 问题列表
| # | 发现日期 | Phase | 问题描述 | 严重程度 | 状态 | 解决方案 | 解决日期 |
|---|---------|-------|---------|---------|------|---------|---------|
| 1 | _____ | Phase ___ | | 🔴高/🟡中/🟢低 | ⬜未解决/✅已解决 | | _____ |
| 2 | _____ | Phase ___ | | 🔴高/🟡中/🟢低 | ⬜未解决/✅已解决 | | _____ |
| 3 | _____ | Phase ___ | | 🔴高/🟡中/🟢低 | ⬜未解决/✅已解决 | | _____ |
### 重要问题详细记录
**问题1**
- **发现时间:**
- **问题描述:**
- **影响范围:**
- **根本原因:**
- **解决方案:**
- **预防措施:**
- **解决时间:**
---
## 📚 学习笔记与最佳实践
### pg-boss 使用心得
```
1.
2.
3.
```
### Prisma 迁移注意事项
```
1.
2.
3.
```
### 测试技巧
```
1.
2.
3.
```
### 调试经验
```
1.
2.
3.
```
---
## 🎉 项目总结
### 最终成果
**代码统计:**
- 新增代码_____ 行
- 修改代码_____ 行
- 测试代码_____ 行
- 总代码量_____ 行
**测试结果:**
- 单元测试_____ / _____ 通过
- 集成测试_____ / _____ 通过
- 功能测试_____ / _____ 通过
- 性能测试_____ / _____ 通过
**性能指标:**
- 缓存命中率_____%(目标 > 60%
- LLM API调用量下降_____%(目标 > 40%
- 长任务成功率_____%(目标 > 99%
- 实例重启恢复_____%(目标 100%
### 项目亮点
1.
2.
3.
### 待改进事项
1.
2.
3.
### 后续优化计划
1.
2.
3.
---
## 🔗 相关文档链接
- [Postgres-Only改造实施计划完整版](./09-Postgres-Only改造实施计划(完整版).md)
- [Postgres-Only全能架构解决方案](./08-Postgres-Only 全能架构解决方案.md)
- [长时间任务可靠性分析](./06-长时间任务可靠性分析.md)
- [SAE部署完全指南](../05-部署文档/02-SAE部署完全指南(产品经理版).md)
---
## 🎉 Phase 1-7 完成总结2025-12-13
### ✅ 完成情况
| 完成项 | 数量 | 说明 |
|--------|------|------|
| **完成阶段** | 7个 | Phase 1-7 全部完成 |
| **完成任务** | 31个 | 总任务数45个完成69% |
| **代码量** | ~1750行 | 新增核心代码 |
| **测试** | 10个 | 全部通过 |
| **文档** | 4个 | 全部更新 |
### 🏆 核心成果
1. **Platform-Only 架构重构**
- 统一使用 `platform_schema.job.data` 存储任务管理信息
- 业务表保持简洁,只存储业务信息
- CheckpointService 所有模块通用
- 符合 3 层架构原则
2. **智能双模式处理**
- 小任务(<50条直接处理快速响应
- 大任务≥50条队列处理可靠性高
- 性能与可靠性的完美平衡
3. **零额外成本**
- 使用 Postgres不需要 Redis
- 年省 ¥8400
- 运维成本零增加
### 📊 工作量统计
```
实际用时6.5天
预计用时9天
提前完成2.5天 ✅
代码量:~1750行目标~1900行
测试覆盖100%
Linter错误0个
```
### 🎯 下一步
- **Phase 8**全面测试验证预计5天
- **Phase 9**SAE部署上线预计5.5天)
---
**版本历史:**
- V1.02025-12-07初始版本
- V1.12025-12-13Phase 1-7 完成,添加 Platform-Only 架构重构记录