Commit e287643cbf352cd115101b5ec9a3aff8e65e9ee3

Authored by yibo
1 parent 15e3db57

perf: fix critical I/O bottlenecks - cache /api/agent/list (5s TTL), buffer log …

…writes (4KB/1s), remove messages.log fallback in loadHistory
Showing 1 changed file with 54 additions and 23 deletions
server/routes/agent-chat.js
@@ -493,28 +493,10 @@ function saveMeta(agentId, convId, meta) { @@ -493,28 +493,10 @@ function saveMeta(agentId, convId, meta) {
493 function loadHistory(agentId, convId) { 493 function loadHistory(agentId, convId) {
494 try { 494 try {
495 const historyPath = path.join(getAgentDir(agentId), 'conversations', convId, 'history.json'); 495 const historyPath = path.join(getAgentDir(agentId), 'conversations', convId, 'history.json');
496 - const logPath = path.join(getAgentDir(agentId), 'conversations', convId, 'messages.log');  
497 -  
498 - let history = [];  
499 if (fs.existsSync(historyPath)) { 496 if (fs.existsSync(historyPath)) {
500 - history = JSON.parse(fs.readFileSync(historyPath, 'utf-8'));  
501 - }  
502 -  
503 - // 如果 messages.log 存在且比 history.json 更丰富, 从 log 里解析完整消息  
504 - if (fs.existsSync(logPath)) {  
505 - const logContent = fs.readFileSync(logPath, 'utf-8');  
506 - const parsed = parseMessagesLog(logContent);  
507 - // 只有当 log 解析出的消息更丰富时才使用 (例如包含更多内容)  
508 - if (parsed.length > 0) {  
509 - const historyTotalLen = history.reduce((sum, m) => sum + (m.content || '').length, 0);  
510 - const logTotalLen = parsed.reduce((sum, m) => sum + (m.content || '').length, 0);  
511 - if (logTotalLen > historyTotalLen * 1.5) {  
512 - return parsed;  
513 - }  
514 - } 497 + return JSON.parse(fs.readFileSync(historyPath, 'utf-8'));
515 } 498 }
516 -  
517 - return history; 499 + return [];
518 } catch { return []; } 500 } catch { return []; }
519 } 501 }
520 502
@@ -552,10 +534,43 @@ function appendHistory(agentId, convId, msg) { @@ -552,10 +534,43 @@ function appendHistory(agentId, convId, msg) {
552 fs.writeFileSync(path.join(dir, 'history.json'), JSON.stringify(history, null, 2)); 534 fs.writeFileSync(path.join(dir, 'history.json'), JSON.stringify(history, null, 2));
553 } 535 }
554 536
555 -/** 写日志(流式或单次) */ 537 +/** 写日志(流式,带缓冲) */
  538 +const _logBuffers = new Map();
  539 +const LOG_BUFFER_SIZE = 4096; // 4KB
  540 +const LOG_FLUSH_INTERVAL = 1000; // 1s
  541 +
556 function logMessageChunk(agentId, convId, content) { 542 function logMessageChunk(agentId, convId, content) {
557 - const dir = getConvDir(agentId, convId);  
558 - fs.appendFileSync(path.join(dir, 'messages.log'), content, 'utf-8'); 543 + const key = `${agentId}::${convId}`;
  544 + if (!_logBuffers.has(key)) {
  545 + _logBuffers.set(key, { buf: '', timer: null, agentId, convId });
  546 + }
  547 + const entry = _logBuffers.get(key);
  548 + entry.buf += content;
  549 + if (entry.buf.length >= LOG_BUFFER_SIZE) {
  550 + _flushLogBuffer(key);
  551 + } else if (!entry.timer) {
  552 + entry.timer = setTimeout(() => _flushLogBuffer(key), LOG_FLUSH_INTERVAL);
  553 + }
  554 +}
  555 +
  556 +function _flushLogBuffer(key) {
  557 + const entry = _logBuffers.get(key);
  558 + if (!entry || !entry.buf) return;
  559 + try {
  560 + const dir = getConvDir(entry.agentId, entry.convId);
  561 + fs.appendFileSync(path.join(dir, 'messages.log'), entry.buf, 'utf-8');
  562 + } catch (err) {
  563 + console.error(`[LogBuffer] ⚠️ flush 失败: ${key}`, err.message);
  564 + }
  565 + entry.buf = '';
  566 + if (entry.timer) { clearTimeout(entry.timer); entry.timer = null; }
  567 +}
  568 +
  569 +/** 强制刷出指定对话的日志缓冲 */
  570 +function flushLogBufferForConv(agentId, convId) {
  571 + const key = `${agentId}::${convId}`;
  572 + _flushLogBuffer(key);
  573 + _logBuffers.delete(key);
559 } 574 }
560 575
561 /** 传统单次写日志 (为了兼容) */ 576 /** 传统单次写日志 (为了兼容) */
@@ -818,8 +833,18 @@ router.get('/quota', (req, res) => { @@ -818,8 +833,18 @@ router.get('/quota', (req, res) => {
818 } 833 }
819 }); 834 });
820 835
  836 +// ── Agent List 缓存 (5秒 TTL,避免每次请求全量扫描磁盘) ──
  837 +let _agentListCache = null;
  838 +let _agentListCacheTime = 0;
  839 +const AGENT_LIST_CACHE_TTL = 5000; // 5 秒
  840 +
821 router.get('/list', (req, res) => { 841 router.get('/list', (req, res) => {
822 try { 842 try {
  843 + // 缓存命中:5秒内直接返回
  844 + if (_agentListCache && Date.now() - _agentListCacheTime < AGENT_LIST_CACHE_TTL) {
  845 + return res.json(_agentListCache);
  846 + }
  847 +
823 const allAgents = [] 848 const allAgents = []
824 const DATA_ROOT = path.resolve(__dirname, '../../data') 849 const DATA_ROOT = path.resolve(__dirname, '../../data')
825 850
@@ -927,6 +952,10 @@ router.get(&#39;/list&#39;, (req, res) =&gt; { @@ -927,6 +952,10 @@ router.get(&#39;/list&#39;, (req, res) =&gt; {
927 return tb - ta 952 return tb - ta
928 }) 953 })
929 954
  955 + // 更新缓存
  956 + _agentListCache = allAgents;
  957 + _agentListCacheTime = Date.now();
  958 +
930 res.json(allAgents) 959 res.json(allAgents)
931 } catch (err) { res.status(500).json({ error: err.message }) } 960 } catch (err) { res.status(500).json({ error: err.message }) }
932 }); 961 });
@@ -2361,6 +2390,7 @@ ${resultsText.join(&#39;\n\n&#39;)} @@ -2361,6 +2390,7 @@ ${resultsText.join(&#39;\n\n&#39;)}
2361 } 2390 }
2362 2391
2363 meta.status = 'completed'; 2392 meta.status = 'completed';
  2393 + flushLogBufferForConv(agentId, convId); // 刷出日志缓冲
2364 saveMeta(agentId, convId, meta); 2394 saveMeta(agentId, convId, meta);
2365 2395
2366 // ── Phase 1 MVP: 自动保存 Session 摘要到记忆 ── 2396 // ── Phase 1 MVP: 自动保存 Session 摘要到记忆 ──
@@ -2382,6 +2412,7 @@ ${resultsText.join(&#39;\n\n&#39;)} @@ -2382,6 +2412,7 @@ ${resultsText.join(&#39;\n\n&#39;)}
2382 } catch (err) { 2412 } catch (err) {
2383 console.error('[AgentChat] Error:', err.message); 2413 console.error('[AgentChat] Error:', err.message);
2384 logMessageChunk(agentId, convId, `\n\n[ERROR]\n${err.message}\n\n`); 2414 logMessageChunk(agentId, convId, `\n\n[ERROR]\n${err.message}\n\n`);
  2415 + flushLogBufferForConv(agentId, convId); // 刷出日志缓冲
2385 meta.status = 'failed'; 2416 meta.status = 'failed';
2386 meta.error = err.message; 2417 meta.error = err.message;
2387 saveMeta(agentId, convId, meta); 2418 saveMeta(agentId, convId, meta);