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 493 function loadHistory(agentId, convId) {
494 494 try {
495 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 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 500 } catch { return []; }
519 501 }
520 502  
... ... @@ -552,10 +534,43 @@ function appendHistory(agentId, convId, msg) {
552 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 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 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 841 router.get('/list', (req, res) => {
822 842 try {
  843 + // 缓存命中:5秒内直接返回
  844 + if (_agentListCache && Date.now() - _agentListCacheTime < AGENT_LIST_CACHE_TTL) {
  845 + return res.json(_agentListCache);
  846 + }
  847 +
823 848 const allAgents = []
824 849 const DATA_ROOT = path.resolve(__dirname, '../../data')
825 850  
... ... @@ -927,6 +952,10 @@ router.get(&#39;/list&#39;, (req, res) =&gt; {
927 952 return tb - ta
928 953 })
929 954  
  955 + // 更新缓存
  956 + _agentListCache = allAgents;
  957 + _agentListCacheTime = Date.now();
  958 +
930 959 res.json(allAgents)
931 960 } catch (err) { res.status(500).json({ error: err.message }) }
932 961 });
... ... @@ -2361,6 +2390,7 @@ ${resultsText.join(&#39;\n\n&#39;)}
2361 2390 }
2362 2391  
2363 2392 meta.status = 'completed';
  2393 + flushLogBufferForConv(agentId, convId); // 刷出日志缓冲
2364 2394 saveMeta(agentId, convId, meta);
2365 2395  
2366 2396 // ── Phase 1 MVP: 自动保存 Session 摘要到记忆 ──
... ... @@ -2382,6 +2412,7 @@ ${resultsText.join(&#39;\n\n&#39;)}
2382 2412 } catch (err) {
2383 2413 console.error('[AgentChat] Error:', err.message);
2384 2414 logMessageChunk(agentId, convId, `\n\n[ERROR]\n${err.message}\n\n`);
  2415 + flushLogBufferForConv(agentId, convId); // 刷出日志缓冲
2385 2416 meta.status = 'failed';
2386 2417 meta.error = err.message;
2387 2418 saveMeta(agentId, convId, meta);
... ...