Files
AIclinicalresearch/docs/03-业务模块/ASL-AI智能文献/06-技术文档/工具3 V2.0(散装架构)深度审查与断层修复报告.md
HaHafeng 371fa53956 docs(asl): Upgrade Tool 3 architecture from Fan-out to Scatter+Aggregator (v2.0)
Architecture transformation:
- Replace Fan-out (Manager->Child->Last Child Wins) with Scatter+Aggregator pattern
- API layer directly dispatches N independent jobs (no Manager)
- Worker only writes its own Result row, never touches Task table (zero row-lock)
- Aggregator polls groupBy for completion + zombie cleanup (replaces Sweeper)
- Reduce red lines from 13 to 9, eliminate distributed complexity

Documents updated (10 files):
- 08-Tool3 main architecture doc: v2.0 rewrite (schema, Task 2.3/2.4, red lines, risks)
- 08d-Code patterns: rewrite sections 4.1-4.6 (API dispatch, SingleWorker, Aggregator)
- 08a-M1 sprint: rewrite M1-3 core (Worker+Aggregator), red lines, acceptance criteria
- 08b-M2 sprint: simplify SSE (NOTIFY/LISTEN downgraded to P2 optional)
- 08c-M3 sprint: milestone table wording update
- New: Scatter+Polling Aggregator pattern guide v1.1 (Level 2 cookbook)
- New: V2.0 architecture deep review and gap-fix report
- Updated: ASL module status, system status, capability layer index

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 22:11:09 +08:00

145 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# **🔍 深度审查与断层修复报告:工具 3 全文提取 (V2.0 散装架构版)**
**审查人:** 资深架构师
**审查对象:** 08 总纲及 08a/08b/08c/08d 拆分计划文档
**审查基准:** 《散装派发与轮询收口任务模式指南 v1.0》
**总体评估:** 战略方向极其正确,但**文档间存在严重的自相矛盾**。旧版 Fan-out 的代码Manager、原子递增、独立清道夫未被彻底清除导致架构撕裂。必须立即修复 08a、08b 和 08d 文档。
## **🚨 致命断层 1Worker 仍在修改父任务(行锁地雷未除)**
### **❌ 发现的问题 (位于 08d 代码手册 §4.3)**
在 08 总纲中您明确写道“Worker 绝不碰 Task 表,进度由 Aggregator groupBy 聚合”。
但是!在 08d 的 ExtractionChildWorker 代码示例中,依然保留了旧版 Fan-out 的致命逻辑:
// 08d 错误残留代码:
const \[\_resultUpdate, taskAfterUpdate\] \= await prisma.$transaction(\[
prisma.aslExtractionResult.update(...),
prisma.aslExtractionTask.update({ ... data: { successCount: { increment: 1 } } }) // ❌ 严重违规!
\]);
// ...
if (taskAfterUpdate.successCount \+ ... \>= totalCount) { ... } // ❌ 严重违规 (Last Child Wins 残留)
**危害:** 如果开发照抄这段代码,散装架构最核心的优势“无锁并发”将彻底丧失,依然会引发 Lock wait timeout 和死锁!
### **✅ 修复指令 (修改 08d §4.3)**
彻底删除 $transaction、删除 increment删除 Last Child Wins 判定。Worker 的代码必须极简:
// 修正后的 08d §4.3:纯粹的散装 Worker
// ... 幽灵重试守卫保持不变 ...
const extractResult \= await this.extractionService.extractOne(resultId, taskId);
// ✅ 核心:只管更新自己的 Result绝不碰 Task
await prisma.aslExtractionResult.update({
where: { id: resultId },
data: { status: 'completed', extractedData: extractResult.data, processedAt: new Date() }
});
// ✅ SSE 广播 (可选)
await broadcastLog(taskId, { source: 'system', message: \`✅ ${extractResult.filename} extracted\` });
// 结束!没有任何收口逻辑!
## **🚨 致命断层 2Manager 的幽灵依然存在**
### **❌ 发现的问题 (位于 08a M1-3 和 08d §4.2)**
* 08 总纲明确声明“消灭的组件ExtractionManagerWorker”“API 层直接散装派发 N 个独立 Job”。
* 然而08a (M1-3) 依然安排了 1 天工期去开发 ExtractionManagerWorker.ts。
* 08d (§4.2) 依然详细保留了 Manager Worker 的完整代码。
**危害:** 架构撕裂。如果 API 层发了一个 Manager JobManager 又去发 Child Job这就退回到了极其笨重的 Fan-out 模式,且徒增了一次重试崩溃的风险。
### **✅ 修复指令**
1. **修改 08a M1-3**:划掉 ExtractionManagerWorker.ts 的开发任务。
2. **修改 08d §4.2**:将此节更名为 **§4.2 API 层散装派发 (ExtractionController)**,并替换为以下代码:
// 修正后的 08d §4.2API 直接派发
async function createTask(req, reply) {
// ... DB 幂等拦截 (idempotencyKey) ...
// 1\. 获取 PKB 快照元数据并冻结到 DB (移至 API 层执行)
const pkbDocs \= await pkbBridge.getDocumentsDetail(documentIds);
const resultsData \= pkbDocs.map(doc \=\> ({
taskId: task.id,
pkbDocumentId: doc.documentId,
snapshotStorageKey: doc.storageKey,
status: 'pending'
}));
await prisma.aslExtractionResult.createMany({ data: resultsData });
const createdResults \= await prisma.aslExtractionResult.findMany({ where: { taskId: task.id } });
// 2\. 🚀 极速散装派发
const jobs \= createdResults.map(result \=\> ({
name: 'asl\_extract\_single',
data: { resultId: result.id, taskId: task.id, pkbDocumentId: result.pkbDocumentId },
options: { singletonKey: \`extract-${result.id}\`, expireInMinutes: 30 }
}));
await jobQueue.insert(jobs); // 批量压入 pg-boss
return reply.send({ taskId: task.id });
}
## **🚨 致命断层 3Sweeper(清道夫) 与 Aggregator(收口器) 未合并**
### **❌ 发现的问题 (位于 08a M1-3 和 08d §4.6)**
在《散装模式指南》中你们团队提出了一个绝妙的优化“Aggregator 兼职 Sweeper一个组件两个职责”。
但在 08a 中,依然要求单独注册 asl\_extraction\_sweeper在 08d 中也保留了独立的 Sweeper 代码。
### **✅ 修复指令 (修改 08a 和 08d)**
1. **修改 08a**:删除 Step D (Sweeper 注册)。新增任务:“开发 ExtractionAggregator.ts实现僵尸清理与轮询收口”。
2. **修改 08d**:删除 §4.6 的独立 Sweeper。补充 ExtractionAggregator 的标准代码:
// 修正后的 08d §4.6ExtractionAggregator 轮询包工头
jobQueue.work('asl\_extraction\_aggregator', async () \=\> {
const tasks \= await prisma.aslExtractionTask.findMany({ where: { status: 'processing' } });
for (const task of tasks) {
// 1\. 顺手清理僵尸 (Sweeper 职责)
await prisma.aslExtractionResult.updateMany({
where: { taskId: task.id, status: 'extracting', updatedAt: { lt: new Date(Date.now() \- 30 \* 60 \* 1000\) } },
data: { status: 'error', errorMessage: '\[Aggregator\] Timeout, likely worker crash.' }
});
// 2\. 聚合统计
const stats \= await prisma.aslExtractionResult.groupBy({ by: \['status'\], where: { taskId: task.id }, \_count: true });
const pending \= stats.find(s \=\> s.status \=== 'pending')?.\_count || 0;
const extracting \= stats.find(s \=\> s.status \=== 'extracting')?.\_count || 0;
// 3\. 收口
if (pending \=== 0 && extracting \=== 0\) {
await prisma.aslExtractionTask.update({ where: { id: task.id }, data: { status: 'completed' } });
}
}
});
## **⚠️ 体验断层 4M2-3 对 NOTIFY/LISTEN 的执念**
### **❌ 发现的问题 (位于 08b M2-3)**
08 总纲明确将 NOTIFY/LISTEN 跨 Pod 广播降级为“已被 V2.0 纯轮询替代/可选增强”。但在 M2 冲刺清单08b M2-3依然将 SseNotifyBridge.ts 和独立 PgClient 的开发列为了“必须做的额外任务”。
### **✅ 修复指令 (修改 08b)**
为了保证 M2 的按时交付,请将 08b 中的 **"🆕 v1.5 额外任务SSE 跨 Pod 广播"** 直接**删除**或标注为 **\[P2 可选\]**。
在散装架构下,既然进度条和跳页全靠 React Query 轮询,终端日志由于没有严重的业务关联,前期完全可以用“本 Pod 内存事件”将就,没必要在 M2 阶段去死磕复杂的 PG 独立监听连接。
## **🏁 架构师总结建议**
这个断层非常典型。因为我们前期对 Fan-out 模式进行了多达 6 轮的极限推演和打磨那些代码乐观锁、原子递增、Manager已经深入人心导致在重写 V2.0 文档时,大家潜意识里舍不得删掉它们。
**请项目经理做最后一步:**
打开 08a、08b 和 08d。凡是带了 Manager、successCount: { increment: 1 }、Last Child Wins、asl\_extraction\_sweeper 字眼的代码和任务,**全部无情删掉**,替换为上述的散装代码。
删完之后,你们的系统代码量将减少 30%,但稳定性将提升 200%!恭喜团队,你们即将拥有一套极其优雅的底层流水线,放心开干吧!