memento-mcp-server 1.13.1 → 1.14.0-a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/database/migration/migrations/007-procedural-memory-enhancement.sql +66 -0
- package/dist/database/schema.sql +9 -2
- package/dist/domains/memory/tools/recall-tool.d.ts +13 -0
- package/dist/domains/memory/tools/recall-tool.d.ts.map +1 -1
- package/dist/domains/memory/tools/recall-tool.js +160 -12
- package/dist/domains/memory/tools/recall-tool.js.map +1 -1
- package/dist/domains/memory/tools/remember-tool.d.ts +10 -0
- package/dist/domains/memory/tools/remember-tool.d.ts.map +1 -1
- package/dist/domains/memory/tools/remember-tool.js +264 -35
- package/dist/domains/memory/tools/remember-tool.js.map +1 -1
- package/dist/domains/relation/services/relation-quality-validator.d.ts.map +1 -1
- package/dist/domains/relation/services/relation-quality-validator.js +15 -10
- package/dist/domains/relation/services/relation-quality-validator.js.map +1 -1
- package/dist/domains/relation/services/rule-based-relation-extractor.d.ts.map +1 -1
- package/dist/domains/relation/services/rule-based-relation-extractor.js +18 -0
- package/dist/domains/relation/services/rule-based-relation-extractor.js.map +1 -1
- package/dist/domains/relation/tools/add-relation-tool.d.ts +3 -3
- package/dist/domains/relation/tools/add-relation-tool.js +2 -2
- package/dist/domains/relation/tools/add-relation-tool.js.map +1 -1
- package/dist/domains/relation/tools/get-relations-tool.d.ts +3 -3
- package/dist/domains/relation/tools/get-relations-tool.js +2 -2
- package/dist/domains/relation/tools/get-relations-tool.js.map +1 -1
- package/dist/domains/search/algorithms/hybrid-search-engine.d.ts +20 -0
- package/dist/domains/search/algorithms/hybrid-search-engine.d.ts.map +1 -1
- package/dist/domains/search/algorithms/hybrid-search-engine.js +168 -5
- package/dist/domains/search/algorithms/hybrid-search-engine.js.map +1 -1
- package/dist/domains/search/algorithms/search-engine.d.ts.map +1 -1
- package/dist/domains/search/algorithms/search-engine.js +37 -17
- package/dist/domains/search/algorithms/search-engine.js.map +1 -1
- package/dist/domains/search/algorithms/search-ranking.d.ts +15 -2
- package/dist/domains/search/algorithms/search-ranking.d.ts.map +1 -1
- package/dist/domains/search/algorithms/search-ranking.js +46 -15
- package/dist/domains/search/algorithms/search-ranking.js.map +1 -1
- package/dist/domains/search/repositories/vector-search.repository.d.ts.map +1 -1
- package/dist/domains/search/repositories/vector-search.repository.js +180 -89
- package/dist/domains/search/repositories/vector-search.repository.js.map +1 -1
- package/dist/infrastructure/database/database/migration/migrations/007-procedural-memory-enhancement.d.ts +63 -0
- package/dist/infrastructure/database/database/migration/migrations/007-procedural-memory-enhancement.d.ts.map +1 -0
- package/dist/infrastructure/database/database/migration/migrations/007-procedural-memory-enhancement.js +257 -0
- package/dist/infrastructure/database/database/migration/migrations/007-procedural-memory-enhancement.js.map +1 -0
- package/dist/infrastructure/reflexion-worker.d.ts +18 -0
- package/dist/infrastructure/reflexion-worker.d.ts.map +1 -1
- package/dist/infrastructure/reflexion-worker.js +216 -0
- package/dist/infrastructure/reflexion-worker.js.map +1 -1
- package/dist/infrastructure/scheduler/batch-scheduler.d.ts +51 -8
- package/dist/infrastructure/scheduler/batch-scheduler.d.ts.map +1 -1
- package/dist/infrastructure/scheduler/batch-scheduler.js +299 -205
- package/dist/infrastructure/scheduler/batch-scheduler.js.map +1 -1
- package/dist/infrastructure/scheduler/file-logger.d.ts +82 -0
- package/dist/infrastructure/scheduler/file-logger.d.ts.map +1 -0
- package/dist/infrastructure/scheduler/file-logger.js +133 -0
- package/dist/infrastructure/scheduler/file-logger.js.map +1 -0
- package/dist/infrastructure/scheduler/health-checker.d.ts +54 -0
- package/dist/infrastructure/scheduler/health-checker.d.ts.map +1 -0
- package/dist/infrastructure/scheduler/health-checker.js +96 -0
- package/dist/infrastructure/scheduler/health-checker.js.map +1 -0
- package/dist/infrastructure/scheduler/job-queue.d.ts +85 -0
- package/dist/infrastructure/scheduler/job-queue.d.ts.map +1 -0
- package/dist/infrastructure/scheduler/job-queue.js +125 -0
- package/dist/infrastructure/scheduler/job-queue.js.map +1 -0
- package/dist/infrastructure/scheduler/relation-validator-executor.d.ts +37 -0
- package/dist/infrastructure/scheduler/relation-validator-executor.d.ts.map +1 -0
- package/dist/infrastructure/scheduler/relation-validator-executor.js +120 -0
- package/dist/infrastructure/scheduler/relation-validator-executor.js.map +1 -0
- package/dist/infrastructure/scheduler/retry-manager.d.ts +62 -0
- package/dist/infrastructure/scheduler/retry-manager.d.ts.map +1 -0
- package/dist/infrastructure/scheduler/retry-manager.js +91 -0
- package/dist/infrastructure/scheduler/retry-manager.js.map +1 -0
- package/dist/npm-client/utils.d.ts.map +1 -1
- package/dist/npm-client/utils.js +2 -1
- package/dist/npm-client/utils.js.map +1 -1
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/http-server.js +15 -17
- package/dist/server/http-server.js.map +1 -1
- package/dist/services/anchor-manager.d.ts.map +1 -1
- package/dist/services/anchor-manager.js.map +1 -1
- package/dist/shared/types/index.d.ts +36 -0
- package/dist/shared/types/index.d.ts.map +1 -1
- package/dist/shared/types/index.js.map +1 -1
- package/dist/shared/types/relation.d.ts +1 -1
- package/dist/shared/types/relation.d.ts.map +1 -1
- package/dist/shared/types/relation.js +7 -4
- package/dist/shared/types/relation.js.map +1 -1
- package/dist/shared/utils/database.d.ts.map +1 -1
- package/dist/shared/utils/database.js +9 -2
- package/dist/shared/utils/database.js.map +1 -1
- package/dist/shared/utils/procedural-memory-extractor.d.ts +108 -0
- package/dist/shared/utils/procedural-memory-extractor.d.ts.map +1 -0
- package/dist/shared/utils/procedural-memory-extractor.js +581 -0
- package/dist/shared/utils/procedural-memory-extractor.js.map +1 -0
- package/dist/shared/utils/relation-type-converter.d.ts +52 -0
- package/dist/shared/utils/relation-type-converter.d.ts.map +1 -0
- package/dist/shared/utils/relation-type-converter.js +106 -0
- package/dist/shared/utils/relation-type-converter.js.map +1 -0
- package/dist/shared/utils/type-param-validator.d.ts +31 -0
- package/dist/shared/utils/type-param-validator.d.ts.map +1 -1
- package/dist/shared/utils/type-param-validator.js +90 -2
- package/dist/shared/utils/type-param-validator.js.map +1 -1
- package/dist/tools/base-tool.d.ts.map +1 -1
- package/dist/tools/types.d.ts +4 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js +5 -0
- package/dist/tools/types.js.map +1 -1
- package/dist/workers/consolidation-score-worker.d.ts.map +1 -1
- package/dist/workers/consolidation-score-worker.js +0 -2
- package/dist/workers/consolidation-score-worker.js.map +1 -1
- package/package.json +3 -3
- package/scripts/auto-setup.js +1 -1
|
@@ -6,14 +6,15 @@
|
|
|
6
6
|
import { ForgettingPolicyService } from '../../domains/forgetting/services/forgetting-policy-service.js';
|
|
7
7
|
import { getPerformanceMonitor } from '../../domains/monitoring/services/performance-monitor.js';
|
|
8
8
|
import Database from 'better-sqlite3';
|
|
9
|
-
import fs from 'fs';
|
|
10
|
-
import path from 'path';
|
|
11
9
|
import { ConsolidationScoreWorker } from '../../workers/consolidation-score-worker.js';
|
|
12
10
|
import { ReflexionWorker } from '../reflexion-worker.js';
|
|
13
11
|
import { mementoConfig } from '../../shared/config/index.js';
|
|
14
12
|
import { mcpLogger } from '../../server/mcp-logger.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
13
|
+
import { JobQueue } from './job-queue.js';
|
|
14
|
+
import { RetryManager } from './retry-manager.js';
|
|
15
|
+
import { HealthChecker } from './health-checker.js';
|
|
16
|
+
import { FileLogger } from './file-logger.js';
|
|
17
|
+
import { RelationValidatorExecutor } from './relation-validator-executor.js';
|
|
17
18
|
export class BatchScheduler {
|
|
18
19
|
config;
|
|
19
20
|
forgettingService;
|
|
@@ -26,11 +27,14 @@ export class BatchScheduler {
|
|
|
26
27
|
startTime = null;
|
|
27
28
|
lastExecution = new Map();
|
|
28
29
|
totalExecutions = new Map();
|
|
29
|
-
errorCount = new Map();
|
|
30
|
-
runningJobs = new Set();
|
|
31
|
-
jobQueue = [];
|
|
32
30
|
jobProcessorInterval = null;
|
|
33
|
-
|
|
31
|
+
// 분리된 모듈들 (DI)
|
|
32
|
+
jobQueue;
|
|
33
|
+
retryManager;
|
|
34
|
+
healthChecker;
|
|
35
|
+
fileLogger;
|
|
36
|
+
relationValidatorExecutor;
|
|
37
|
+
constructor(config, dependencies) {
|
|
34
38
|
this.config = {
|
|
35
39
|
cleanupInterval: 60 * 60 * 1000, // 1시간
|
|
36
40
|
monitoringInterval: 5 * 60 * 1000, // 5분
|
|
@@ -49,6 +53,7 @@ export class BatchScheduler {
|
|
|
49
53
|
jobTimeout: 5 * 60 * 1000, // 5분
|
|
50
54
|
retryAttempts: 3,
|
|
51
55
|
retryDelay: 1000, // 1초
|
|
56
|
+
weeklyRelationValidationTimeout: undefined, // 기본값: jobTimeout 사용
|
|
52
57
|
...config
|
|
53
58
|
};
|
|
54
59
|
// 생성자에서 설정 검증
|
|
@@ -59,6 +64,20 @@ export class BatchScheduler {
|
|
|
59
64
|
if (mementoConfig.consolidationScoreEnabled) {
|
|
60
65
|
this.consolidationScoreWorker = new ConsolidationScoreWorker();
|
|
61
66
|
}
|
|
67
|
+
// 분리된 모듈들 초기화 (DI 또는 기본 생성)
|
|
68
|
+
this.jobQueue = dependencies?.jobQueue ?? new JobQueue();
|
|
69
|
+
this.retryManager = dependencies?.retryManager ?? new RetryManager({
|
|
70
|
+
maxAttempts: this.config.retryAttempts,
|
|
71
|
+
baseDelay: this.config.retryDelay,
|
|
72
|
+
maxErrorCount: this.config.retryAttempts * 3
|
|
73
|
+
});
|
|
74
|
+
this.healthChecker = dependencies?.healthChecker ?? new HealthChecker();
|
|
75
|
+
this.fileLogger = dependencies?.fileLogger ?? new FileLogger({
|
|
76
|
+
enabled: this.config.enableLogging
|
|
77
|
+
});
|
|
78
|
+
this.relationValidatorExecutor = dependencies?.relationValidatorExecutor ?? new RelationValidatorExecutor({
|
|
79
|
+
timeout: this.config.weeklyRelationValidationTimeout ?? this.config.jobTimeout
|
|
80
|
+
});
|
|
62
81
|
}
|
|
63
82
|
/**
|
|
64
83
|
* 스케줄러 시작
|
|
@@ -73,6 +92,15 @@ export class BatchScheduler {
|
|
|
73
92
|
this.db = db;
|
|
74
93
|
this.isRunning = true;
|
|
75
94
|
this.startTime = new Date();
|
|
95
|
+
// 재시작 시 큐 초기화 (이전 세션의 작업이 남아있을 수 있음)
|
|
96
|
+
if (this.jobQueue.size > 0) {
|
|
97
|
+
this.log(`Clearing ${this.jobQueue.size} leftover jobs from previous session`, {
|
|
98
|
+
leftoverJobs: this.jobQueue.size
|
|
99
|
+
});
|
|
100
|
+
this.jobQueue.clear();
|
|
101
|
+
}
|
|
102
|
+
// 헬스체크 시작 시간 설정
|
|
103
|
+
this.healthChecker.setStartTime(this.startTime);
|
|
76
104
|
// 성능 모니터 초기화
|
|
77
105
|
this.performanceMonitor.initialize(db);
|
|
78
106
|
// Reflexion Worker 통합 (Phase 2)
|
|
@@ -107,6 +135,7 @@ export class BatchScheduler {
|
|
|
107
135
|
}
|
|
108
136
|
/**
|
|
109
137
|
* 스케줄러 중지
|
|
138
|
+
* 재시작 시 의도하지 않은 배치 실행과 상태 오염을 방지하기 위해 큐를 비움
|
|
110
139
|
*/
|
|
111
140
|
async stop() {
|
|
112
141
|
if (!this.isRunning) {
|
|
@@ -127,8 +156,17 @@ export class BatchScheduler {
|
|
|
127
156
|
}
|
|
128
157
|
// 실행 중인 작업 완료 대기
|
|
129
158
|
await this.waitForRunningJobs();
|
|
159
|
+
// 큐에 남아있는 작업 제거 (재시작 시 의도하지 않은 실행 방지)
|
|
160
|
+
const queuedJobsCount = this.jobQueue.size;
|
|
161
|
+
if (queuedJobsCount > 0) {
|
|
162
|
+
this.log(`Clearing ${queuedJobsCount} queued jobs to prevent unintended execution on restart`, {
|
|
163
|
+
queuedJobs: queuedJobsCount
|
|
164
|
+
});
|
|
165
|
+
this.jobQueue.clear(); // 큐 비우기
|
|
166
|
+
}
|
|
130
167
|
this.log('BatchScheduler stopped', {
|
|
131
|
-
uptime: this.startTime ? Date.now() - this.startTime.getTime() : 0
|
|
168
|
+
uptime: this.startTime ? Date.now() - this.startTime.getTime() : 0,
|
|
169
|
+
clearedQueuedJobs: queuedJobsCount
|
|
132
170
|
});
|
|
133
171
|
}
|
|
134
172
|
/**
|
|
@@ -150,95 +188,149 @@ export class BatchScheduler {
|
|
|
150
188
|
if (this.config.jobTimeout < 1000) {
|
|
151
189
|
throw new Error('jobTimeout must be at least 1 second');
|
|
152
190
|
}
|
|
191
|
+
// weeklyRelationValidationTimeout 검증 (설정된 경우에만)
|
|
192
|
+
if (this.config.weeklyRelationValidationTimeout !== undefined) {
|
|
193
|
+
if (typeof this.config.weeklyRelationValidationTimeout !== 'number' ||
|
|
194
|
+
isNaN(this.config.weeklyRelationValidationTimeout) ||
|
|
195
|
+
this.config.weeklyRelationValidationTimeout <= 0) {
|
|
196
|
+
throw new Error('weeklyRelationValidationTimeout must be a positive number (at least 1 second)');
|
|
197
|
+
}
|
|
198
|
+
if (this.config.weeklyRelationValidationTimeout < 1000) {
|
|
199
|
+
throw new Error('weeklyRelationValidationTimeout must be at least 1 second');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
153
202
|
}
|
|
154
203
|
/**
|
|
155
|
-
* 작업
|
|
204
|
+
* 작업 실행 래퍼 (타임아웃, 상태 관리, 재시도 포함)
|
|
205
|
+
* 재시도 큐에서도 동일한 래퍼를 사용하여 타임아웃/상태 관리가 적용되도록 함
|
|
206
|
+
*
|
|
207
|
+
* @param name 작업 이름
|
|
208
|
+
* @param job 실행할 작업 함수
|
|
209
|
+
* @param priority 우선순위 (재시도 시 사용)
|
|
210
|
+
* @param initialRetryCount 초기 재시도 횟수 (기본값: 0)
|
|
211
|
+
* @returns 실행 결과
|
|
156
212
|
*/
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
213
|
+
async executeJobWithRetry(name, job, priority, initialRetryCount = 0) {
|
|
214
|
+
// 이미 실행 중인 작업은 큐에 남겨두어 다음 턴에 실행되도록 함
|
|
215
|
+
// (스킵하면 주기적 실행이 누락될 수 있음)
|
|
216
|
+
// 단, 중복 방지를 위해 동일 이름의 잡이 이미 큐에 있으면 추가하지 않음
|
|
217
|
+
if (this.jobQueue.isRunning(name)) {
|
|
218
|
+
const added = this.addJobToQueue(name, job, priority, initialRetryCount);
|
|
219
|
+
if (!added) {
|
|
220
|
+
// 이미 큐에 있으면 스킵
|
|
161
221
|
return;
|
|
162
222
|
}
|
|
163
|
-
this.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
223
|
+
this.log(`Job ${name} is already running, will retry after completion`, { level: 'debug' });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this.jobQueue.markRunning(name);
|
|
227
|
+
const startTime = Date.now();
|
|
228
|
+
let retryCount = initialRetryCount;
|
|
229
|
+
try {
|
|
230
|
+
await this.executeWithTimeout(job, this.config.jobTimeout);
|
|
231
|
+
this.lastExecution.set(name, new Date());
|
|
232
|
+
this.totalExecutions.set(name, (this.totalExecutions.get(name) || 0) + 1);
|
|
233
|
+
// 성공시 에러 카운트 리셋 (RetryManager 사용)
|
|
234
|
+
this.retryManager.resetErrorCount(name);
|
|
235
|
+
this.log(`Job ${name} completed successfully`, {
|
|
236
|
+
duration: Date.now() - startTime,
|
|
237
|
+
totalExecutions: this.totalExecutions.get(name),
|
|
238
|
+
retryCount
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
retryCount++;
|
|
243
|
+
const totalErrorCount = this.retryManager.incrementErrorCount(name);
|
|
244
|
+
const errorInfo = {
|
|
245
|
+
error: error instanceof Error ? error.message : String(error),
|
|
246
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
247
|
+
errorCount: totalErrorCount,
|
|
248
|
+
retryCount,
|
|
249
|
+
duration: Date.now() - startTime
|
|
250
|
+
};
|
|
251
|
+
this.log(`Job ${name} failed`, errorInfo, 'error');
|
|
252
|
+
// RetryManager를 사용하여 재시도 여부 결정
|
|
253
|
+
const retryResult = this.retryManager.shouldRetry(name, retryCount, totalErrorCount);
|
|
254
|
+
if (retryResult.exceededMaxErrors) {
|
|
255
|
+
this.log(`Job ${name} exceeded maximum error count (${totalErrorCount}), stopping retries`, {
|
|
256
|
+
totalErrorCount,
|
|
257
|
+
finalError: errorInfo
|
|
258
|
+
}, 'error');
|
|
259
|
+
// 심각한 에러의 경우 스케줄러 상태 확인
|
|
260
|
+
this.log(`Job ${name} has too many consecutive failures, checking scheduler health`, { level: 'warn' });
|
|
261
|
+
await this.checkSchedulerHealth();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
// 재시도 로직
|
|
265
|
+
if (retryResult.shouldRetry) {
|
|
266
|
+
this.log(`Retrying job ${name} in ${retryResult.nextRetryDelay}ms`, {
|
|
267
|
+
attempt: retryResult.retryCount,
|
|
268
|
+
totalAttempts: this.config.retryAttempts,
|
|
269
|
+
nextRetryDelay: retryResult.nextRetryDelay,
|
|
270
|
+
totalErrorCount
|
|
271
|
+
});
|
|
272
|
+
setTimeout(() => {
|
|
273
|
+
if (this.isRunning) { // 스케줄러가 여전히 실행 중인지 확인
|
|
274
|
+
// 재시도 시 retryCount를 큐 항목에 저장하여 다음 실행 시 전달 (중복 방지 포함)
|
|
275
|
+
this.addJobToQueue(name, job, priority, retryResult.retryCount);
|
|
215
276
|
}
|
|
277
|
+
}, retryResult.nextRetryDelay);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
this.log(`Job ${name} failed permanently after ${retryCount} attempts`, {
|
|
281
|
+
totalErrorCount,
|
|
282
|
+
finalError: errorInfo
|
|
283
|
+
}, 'error');
|
|
284
|
+
// 심각한 에러의 경우 스케줄러 상태 확인
|
|
285
|
+
if (totalErrorCount > this.config.retryAttempts * 2) {
|
|
286
|
+
this.log(`Job ${name} has too many consecutive failures, checking scheduler health`, { level: 'warn' });
|
|
287
|
+
await this.checkSchedulerHealth();
|
|
216
288
|
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
this.jobQueue.markCompleted(name);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* 큐에 작업 추가 (중복 방지 포함)
|
|
297
|
+
* 동일 이름의 잡이 이미 큐에 있거나 실행 중이면 추가하지 않음
|
|
298
|
+
*/
|
|
299
|
+
addJobToQueue(name, job, priority, retryCount = 0) {
|
|
300
|
+
return this.jobQueue.add(name, job, priority, retryCount);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* 작업 스케줄링
|
|
304
|
+
* 시작 시 maxConcurrentJobs를 보장하기 위해 무조건 큐를 통해 실행
|
|
305
|
+
* 여러 작업이 동시에 시작될 때 race condition을 방지하기 위함
|
|
306
|
+
*/
|
|
307
|
+
scheduleJob(name, interval, job, priority) {
|
|
308
|
+
const wrappedJob = async () => {
|
|
309
|
+
// 주기적 실행도 큐를 통해 실행하여 maxConcurrentJobs 보장 (중복 방지 포함)
|
|
310
|
+
this.addJobToQueue(name, job, priority, 0);
|
|
222
311
|
};
|
|
223
|
-
// 즉시
|
|
224
|
-
|
|
225
|
-
|
|
312
|
+
// 즉시 실행도 큐를 통해 실행 (maxConcurrentJobs 보장, 중복 방지 포함)
|
|
313
|
+
// 여러 작업이 동시에 시작될 때 race condition 방지
|
|
314
|
+
this.addJobToQueue(name, job, priority, 0);
|
|
315
|
+
// 주기적 실행도 큐를 통해 실행
|
|
226
316
|
const intervalId = setInterval(wrappedJob, interval);
|
|
227
317
|
this.intervals.set(name, intervalId);
|
|
228
318
|
}
|
|
229
319
|
/**
|
|
230
320
|
* 작업 큐 처리기 시작
|
|
321
|
+
* 재시도 큐에서도 동일한 래퍼(타임아웃+상태 관리)를 사용하도록 수정
|
|
231
322
|
*/
|
|
232
323
|
startJobProcessor() {
|
|
233
324
|
const processQueue = async () => {
|
|
234
|
-
if (this.jobQueue.
|
|
325
|
+
if (this.jobQueue.isEmpty || this.jobQueue.runningCount >= this.config.maxConcurrentJobs) {
|
|
235
326
|
return;
|
|
236
327
|
}
|
|
237
|
-
|
|
238
|
-
this.jobQueue.sort((a, b) => a.priority - b.priority);
|
|
239
|
-
const nextJob = this.jobQueue.shift();
|
|
328
|
+
const nextJob = this.jobQueue.getNext();
|
|
240
329
|
if (nextJob) {
|
|
241
|
-
|
|
330
|
+
// 재시도 큐에서도 동일한 래퍼를 사용하여 타임아웃/상태 관리가 적용되도록 함
|
|
331
|
+
// 재시도 시 저장된 retryCount를 사용하여 무한 재시도 방지
|
|
332
|
+
const retryCount = nextJob.retryCount ?? 0;
|
|
333
|
+
await this.executeJobWithRetry(nextJob.name, nextJob.job, nextJob.priority, retryCount);
|
|
242
334
|
}
|
|
243
335
|
};
|
|
244
336
|
// 큐 처리 인터벌 (ID 저장)
|
|
@@ -261,11 +353,12 @@ export class BatchScheduler {
|
|
|
261
353
|
async waitForRunningJobs() {
|
|
262
354
|
const maxWaitTime = 30000; // 30초
|
|
263
355
|
const startTime = Date.now();
|
|
264
|
-
while (this.
|
|
356
|
+
while (this.jobQueue.runningCount > 0 && (Date.now() - startTime) < maxWaitTime) {
|
|
357
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
265
358
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
266
359
|
}
|
|
267
|
-
if (this.
|
|
268
|
-
this.log(`Warning: ${this.
|
|
360
|
+
if (this.jobQueue.runningCount > 0) {
|
|
361
|
+
this.log(`Warning: ${this.jobQueue.runningCount} jobs still running after timeout`, { level: 'warn' });
|
|
269
362
|
}
|
|
270
363
|
}
|
|
271
364
|
/**
|
|
@@ -391,27 +484,17 @@ export class BatchScheduler {
|
|
|
391
484
|
warnings: []
|
|
392
485
|
};
|
|
393
486
|
try {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
// 데이터베이스 연결 확인
|
|
398
|
-
await this.db.prepare('SELECT 1').get();
|
|
399
|
-
// 메모리 사용량 확인
|
|
400
|
-
const memUsage = process.memoryUsage();
|
|
401
|
-
const memUsagePercent = (memUsage.heapUsed / memUsage.heapTotal) * 100;
|
|
402
|
-
if (memUsagePercent > 90) {
|
|
403
|
-
result.warnings.push(`High memory usage: ${memUsagePercent.toFixed(1)}%`);
|
|
404
|
-
}
|
|
405
|
-
// 실행 중인 작업 수 확인
|
|
406
|
-
if (this.runningJobs.size > this.config.maxConcurrentJobs * 0.8) {
|
|
407
|
-
result.warnings.push(`High job concurrency: ${this.runningJobs.size}/${this.config.maxConcurrentJobs}`);
|
|
408
|
-
}
|
|
409
|
-
result.success = true;
|
|
487
|
+
// HealthChecker를 사용하여 헬스체크 실행
|
|
488
|
+
const healthResult = await this.healthChecker.check(this.db, this.jobQueue.runningCount, this.jobQueue.size, this.config.maxConcurrentJobs);
|
|
489
|
+
result.success = healthResult.isHealthy;
|
|
410
490
|
result.processed = 1;
|
|
491
|
+
result.warnings = healthResult.warnings;
|
|
492
|
+
result.errors = healthResult.errors;
|
|
411
493
|
result.details = {
|
|
412
|
-
memoryUsage:
|
|
413
|
-
runningJobs:
|
|
414
|
-
|
|
494
|
+
memoryUsage: healthResult.memoryUsage,
|
|
495
|
+
runningJobs: healthResult.runningJobs,
|
|
496
|
+
queueSize: healthResult.queueSize,
|
|
497
|
+
uptime: healthResult.uptime
|
|
415
498
|
};
|
|
416
499
|
}
|
|
417
500
|
catch (error) {
|
|
@@ -477,9 +560,10 @@ export class BatchScheduler {
|
|
|
477
560
|
/**
|
|
478
561
|
* Consolidation Score 전체 스윕 작업 스케줄링
|
|
479
562
|
* 지정된 시간에 하루 1회 실행
|
|
563
|
+
* 큐를 통해 실행하여 maxConcurrentJobs, 타임아웃, 재시도, lastExecution 기록이 적용되도록 함
|
|
480
564
|
*/
|
|
481
565
|
scheduleConsolidationScoreFullSweep() {
|
|
482
|
-
const checkAndRun =
|
|
566
|
+
const checkAndRun = () => {
|
|
483
567
|
const now = new Date();
|
|
484
568
|
const currentHour = now.getHours();
|
|
485
569
|
// 지정된 시간에 실행
|
|
@@ -488,7 +572,9 @@ export class BatchScheduler {
|
|
|
488
572
|
const lastExecution = this.lastExecution.get('consolidation_score_full_sweep');
|
|
489
573
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
490
574
|
if (!lastExecution || lastExecution < today) {
|
|
491
|
-
|
|
575
|
+
// 큐를 통해 실행하여 maxConcurrentJobs, 타임아웃, 재시도, lastExecution 기록이 적용되도록 함 (중복 방지 포함)
|
|
576
|
+
this.addJobToQueue('consolidation_score_full_sweep', async () => { await this.runConsolidationScoreFullSweep(); }, 4, // consolidation_score_incremental과 동일한 우선순위
|
|
577
|
+
0);
|
|
492
578
|
}
|
|
493
579
|
}
|
|
494
580
|
};
|
|
@@ -502,9 +588,10 @@ export class BatchScheduler {
|
|
|
502
588
|
}
|
|
503
589
|
/**
|
|
504
590
|
* 주간 관계 추출 품질 검증 작업 스케줄링
|
|
591
|
+
* 큐를 통해 실행하여 maxConcurrentJobs, 타임아웃, 재시도, lastExecution 기록이 적용되도록 함
|
|
505
592
|
*/
|
|
506
593
|
scheduleWeeklyRelationValidation() {
|
|
507
|
-
const checkAndRun =
|
|
594
|
+
const checkAndRun = () => {
|
|
508
595
|
const now = new Date();
|
|
509
596
|
const currentDayOfWeek = now.getDay(); // 0=일요일, 6=토요일
|
|
510
597
|
const currentHour = now.getHours();
|
|
@@ -515,7 +602,9 @@ export class BatchScheduler {
|
|
|
515
602
|
const lastExecution = this.lastExecution.get('weekly_relation_validation');
|
|
516
603
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
517
604
|
if (!lastExecution || lastExecution < today) {
|
|
518
|
-
|
|
605
|
+
// 큐를 통해 실행하여 maxConcurrentJobs, 타임아웃, 재시도, lastExecution 기록이 적용되도록 함 (중복 방지 포함)
|
|
606
|
+
this.addJobToQueue('weekly_relation_validation', async () => { await this.runWeeklyRelationValidation(); }, 5, // 다른 작업보다 낮은 우선순위
|
|
607
|
+
0);
|
|
519
608
|
}
|
|
520
609
|
}
|
|
521
610
|
};
|
|
@@ -528,6 +617,7 @@ export class BatchScheduler {
|
|
|
528
617
|
}
|
|
529
618
|
/**
|
|
530
619
|
* 주간 관계 추출 품질 검증 실행
|
|
620
|
+
* 타임아웃 및 강제 종료 로직 포함
|
|
531
621
|
*/
|
|
532
622
|
async runWeeklyRelationValidation() {
|
|
533
623
|
const startTime = new Date();
|
|
@@ -543,43 +633,29 @@ export class BatchScheduler {
|
|
|
543
633
|
};
|
|
544
634
|
try {
|
|
545
635
|
this.log('Starting weekly relation validation...');
|
|
546
|
-
//
|
|
547
|
-
const
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
cwd: process.cwd(),
|
|
551
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
552
|
-
env: { ...process.env }
|
|
553
|
-
});
|
|
554
|
-
let stdout = '';
|
|
555
|
-
let stderr = '';
|
|
556
|
-
childProcess.stdout?.on('data', (data) => {
|
|
557
|
-
stdout += data.toString();
|
|
558
|
-
});
|
|
559
|
-
childProcess.stderr?.on('data', (data) => {
|
|
560
|
-
stderr += data.toString();
|
|
561
|
-
});
|
|
562
|
-
await new Promise((resolve, reject) => {
|
|
563
|
-
childProcess.on('close', (code) => {
|
|
564
|
-
if (code === 0) {
|
|
565
|
-
resolve();
|
|
566
|
-
}
|
|
567
|
-
else {
|
|
568
|
-
reject(new Error(`Script exited with code ${code}\n${stderr}`));
|
|
569
|
-
}
|
|
570
|
-
});
|
|
571
|
-
childProcess.on('error', (error) => {
|
|
572
|
-
reject(error);
|
|
573
|
-
});
|
|
574
|
-
});
|
|
575
|
-
result.success = true;
|
|
636
|
+
// RelationValidatorExecutor를 사용하여 스크립트 실행
|
|
637
|
+
const timeout = this.config.weeklyRelationValidationTimeout ?? this.config.jobTimeout;
|
|
638
|
+
const executorResult = await this.relationValidatorExecutor.execute([], timeout);
|
|
639
|
+
result.success = executorResult.success;
|
|
576
640
|
result.endTime = new Date();
|
|
577
|
-
result.duration =
|
|
641
|
+
result.duration = executorResult.duration;
|
|
578
642
|
result.processed = 1;
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
643
|
+
if (executorResult.error) {
|
|
644
|
+
result.errors.push(executorResult.error);
|
|
645
|
+
}
|
|
646
|
+
if (executorResult.success) {
|
|
647
|
+
this.log('Weekly relation validation completed successfully', {
|
|
648
|
+
duration: result.duration,
|
|
649
|
+
stdout: executorResult.stdout.substring(0, 500) // 처음 500자만 로그
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
this.log('Weekly relation validation failed', {
|
|
654
|
+
error: executorResult.error,
|
|
655
|
+
duration: result.duration,
|
|
656
|
+
stderr: executorResult.stderr.substring(0, 500)
|
|
657
|
+
}, 'error');
|
|
658
|
+
}
|
|
583
659
|
}
|
|
584
660
|
catch (error) {
|
|
585
661
|
result.endTime = new Date();
|
|
@@ -668,6 +744,7 @@ export class BatchScheduler {
|
|
|
668
744
|
totalMemories: totalMemories.count,
|
|
669
745
|
estimatedSize: dbSize.page_count * pageSize.page_size
|
|
670
746
|
};
|
|
747
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
|
|
671
748
|
}
|
|
672
749
|
catch (error) {
|
|
673
750
|
this.log('Failed to collect database stats:', error, 'warn');
|
|
@@ -676,6 +753,7 @@ export class BatchScheduler {
|
|
|
676
753
|
}
|
|
677
754
|
/**
|
|
678
755
|
* 로깅
|
|
756
|
+
* data 객체에 level 속성이 있으면 이를 우선적으로 사용하여 호출부의 편의성을 높임
|
|
679
757
|
*/
|
|
680
758
|
log(message, data, level = 'info') {
|
|
681
759
|
if (!this.config.enableLogging)
|
|
@@ -683,6 +761,7 @@ export class BatchScheduler {
|
|
|
683
761
|
// 배치 작업 컨텍스트 정보 추가
|
|
684
762
|
// Error 객체는 non-enumerable 속성을 가지므로 명시적으로 처리 필요
|
|
685
763
|
let safeData;
|
|
764
|
+
let actualLevel = level;
|
|
686
765
|
if (data instanceof Error) {
|
|
687
766
|
// Error 객체의 속성을 명시적으로 추출 (non-enumerable 속성 포함)
|
|
688
767
|
safeData = {
|
|
@@ -693,7 +772,17 @@ export class BatchScheduler {
|
|
|
693
772
|
}
|
|
694
773
|
else if (typeof data === 'object' && data !== null && !Array.isArray(data)) {
|
|
695
774
|
// 일반 객체는 spread 가능
|
|
696
|
-
safeData = data;
|
|
775
|
+
safeData = { ...data };
|
|
776
|
+
// data.level이 있으면 이를 우선적으로 사용 (호출부 편의성)
|
|
777
|
+
if ('level' in safeData && typeof safeData.level === 'string') {
|
|
778
|
+
const dataLevel = safeData.level.toLowerCase();
|
|
779
|
+
// 'debug'는 'info'로 변환 (mcpLogger가 debug를 지원하지 않을 수 있음)
|
|
780
|
+
if (dataLevel === 'debug' || dataLevel === 'info' || dataLevel === 'warn' || dataLevel === 'error') {
|
|
781
|
+
actualLevel = dataLevel === 'debug' ? 'info' : dataLevel;
|
|
782
|
+
}
|
|
783
|
+
// level 속성은 제거 (중복 방지)
|
|
784
|
+
delete safeData.level;
|
|
785
|
+
}
|
|
697
786
|
}
|
|
698
787
|
else {
|
|
699
788
|
// 원시 타입이나 배열은 빈 객체로 처리
|
|
@@ -702,43 +791,34 @@ export class BatchScheduler {
|
|
|
702
791
|
const batchContext = {
|
|
703
792
|
...safeData,
|
|
704
793
|
uptime: this.startTime ? Date.now() - this.startTime.getTime() : 0,
|
|
705
|
-
activeJobs: this.
|
|
706
|
-
queueSize: this.jobQueue.
|
|
794
|
+
activeJobs: this.jobQueue.runningCount,
|
|
795
|
+
queueSize: this.jobQueue.size
|
|
707
796
|
};
|
|
708
797
|
// MCP 로거 사용
|
|
709
|
-
mcpLogger.logBatch(
|
|
710
|
-
//
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
level,
|
|
716
|
-
message,
|
|
717
|
-
data: batchContext,
|
|
798
|
+
mcpLogger.logBatch(actualLevel, message, batchContext);
|
|
799
|
+
// 에러/경고 로그는 파일에도 저장 (FileLogger 사용, 비동기이므로 await 없이 fire-and-forget)
|
|
800
|
+
// warn과 error를 구분하여 원본 레벨을 보존
|
|
801
|
+
if (actualLevel === 'warn') {
|
|
802
|
+
// 비동기 로깅이지만 await하지 않음 (로깅 실패가 작업 실패로 이어지지 않도록)
|
|
803
|
+
this.fileLogger.logWarn(message, batchContext, {
|
|
718
804
|
uptime: batchContext.uptime,
|
|
719
805
|
activeJobs: batchContext.activeJobs,
|
|
720
806
|
queueSize: batchContext.queueSize
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* 파일 로깅 (에러 로그)
|
|
727
|
-
*/
|
|
728
|
-
logToFile(logEntry) {
|
|
729
|
-
try {
|
|
730
|
-
const logDir = path.join(process.cwd(), 'logs');
|
|
731
|
-
// 로그 디렉토리 생성
|
|
732
|
-
if (!fs.existsSync(logDir)) {
|
|
733
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
734
|
-
}
|
|
735
|
-
const logFile = path.join(logDir, 'batch-scheduler.log');
|
|
736
|
-
const logLine = JSON.stringify(logEntry) + '\n';
|
|
737
|
-
fs.appendFileSync(logFile, logLine);
|
|
807
|
+
}).catch((error) => {
|
|
808
|
+
// 파일 로깅 실패는 콘솔에만 기록 (무한 루프 방지)
|
|
809
|
+
console.error('File logging failed:', error);
|
|
810
|
+
});
|
|
738
811
|
}
|
|
739
|
-
|
|
740
|
-
//
|
|
741
|
-
|
|
812
|
+
else if (actualLevel === 'error') {
|
|
813
|
+
// 비동기 로깅이지만 await하지 않음 (로깅 실패가 작업 실패로 이어지지 않도록)
|
|
814
|
+
this.fileLogger.logError(message, batchContext, {
|
|
815
|
+
uptime: batchContext.uptime,
|
|
816
|
+
activeJobs: batchContext.activeJobs,
|
|
817
|
+
queueSize: batchContext.queueSize
|
|
818
|
+
}).catch((error) => {
|
|
819
|
+
// 파일 로깅 실패는 콘솔에만 기록 (무한 루프 방지)
|
|
820
|
+
console.error('File logging failed:', error);
|
|
821
|
+
});
|
|
742
822
|
}
|
|
743
823
|
}
|
|
744
824
|
/**
|
|
@@ -771,29 +851,47 @@ export class BatchScheduler {
|
|
|
771
851
|
}
|
|
772
852
|
/**
|
|
773
853
|
* 수동으로 작업 실행
|
|
854
|
+
* 직접 실행하되 lastExecution과 totalExecutions을 기록함
|
|
774
855
|
*/
|
|
775
856
|
async runJob(jobType) {
|
|
857
|
+
let result;
|
|
776
858
|
switch (jobType) {
|
|
777
859
|
case 'cleanup':
|
|
778
|
-
|
|
860
|
+
result = await this.runMemoryCleanup();
|
|
861
|
+
break;
|
|
779
862
|
case 'monitoring':
|
|
780
|
-
|
|
863
|
+
result = await this.runMonitoring();
|
|
864
|
+
break;
|
|
781
865
|
case 'healthcheck':
|
|
782
|
-
|
|
866
|
+
result = await this.runHealthCheck();
|
|
867
|
+
break;
|
|
783
868
|
default:
|
|
784
869
|
throw new Error(`Unknown job type: ${jobType}`);
|
|
785
870
|
}
|
|
871
|
+
// lastExecution과 totalExecutions 업데이트 (큐를 통한 실행과 일관성 유지)
|
|
872
|
+
this.lastExecution.set(jobType, new Date());
|
|
873
|
+
this.totalExecutions.set(jobType, (this.totalExecutions.get(jobType) || 0) + 1);
|
|
874
|
+
return result;
|
|
786
875
|
}
|
|
787
876
|
/**
|
|
788
877
|
* 스케줄러 상태 확인
|
|
789
878
|
*/
|
|
790
879
|
getStatus() {
|
|
880
|
+
// RetryManager에서 errorCount를 가져와서 Map으로 변환
|
|
881
|
+
const errorCountMap = new Map();
|
|
882
|
+
// 모든 작업 이름에 대해 errorCount 조회 (intervals의 키 사용)
|
|
883
|
+
for (const jobName of this.intervals.keys()) {
|
|
884
|
+
const errorCount = this.retryManager.getErrorCount(jobName);
|
|
885
|
+
if (errorCount > 0) {
|
|
886
|
+
errorCountMap.set(jobName, errorCount);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
791
889
|
return {
|
|
792
890
|
isRunning: this.isRunning,
|
|
793
891
|
activeJobs: Array.from(this.intervals.keys()),
|
|
794
892
|
lastExecution: new Map(this.lastExecution),
|
|
795
893
|
totalExecutions: new Map(this.totalExecutions),
|
|
796
|
-
errorCount:
|
|
894
|
+
errorCount: errorCountMap,
|
|
797
895
|
uptime: this.startTime ? Date.now() - this.startTime.getTime() : 0,
|
|
798
896
|
config: { ...this.config }
|
|
799
897
|
};
|
|
@@ -846,34 +944,27 @@ export class BatchScheduler {
|
|
|
846
944
|
async checkSchedulerHealth() {
|
|
847
945
|
try {
|
|
848
946
|
this.log('Performing scheduler health check...');
|
|
849
|
-
//
|
|
850
|
-
|
|
851
|
-
|
|
947
|
+
// HealthChecker를 사용하여 헬스체크 실행
|
|
948
|
+
const healthResult = await this.healthChecker.check(this.db, this.jobQueue.runningCount, this.jobQueue.size, this.config.maxConcurrentJobs);
|
|
949
|
+
// 경고가 있으면 로깅
|
|
950
|
+
if (healthResult.warnings.length > 0) {
|
|
951
|
+
healthResult.warnings.forEach(warning => {
|
|
952
|
+
this.log(warning, { level: 'warn' });
|
|
953
|
+
});
|
|
852
954
|
}
|
|
853
|
-
// 메모리
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
if (memUsagePercent > 90) {
|
|
857
|
-
this.log(`High memory usage detected: ${memUsagePercent.toFixed(1)}%`, { level: 'warn' });
|
|
858
|
-
// 메모리 정리 시도
|
|
859
|
-
if (global.gc) {
|
|
860
|
-
global.gc();
|
|
955
|
+
// 메모리 사용량이 높으면 가비지 컬렉션 시도
|
|
956
|
+
if (healthResult.memoryUsage > 90) {
|
|
957
|
+
if (this.healthChecker.triggerGarbageCollection()) {
|
|
861
958
|
this.log('Garbage collection triggered');
|
|
862
959
|
}
|
|
863
960
|
}
|
|
864
|
-
// 실행 중인 작업 수 확인
|
|
865
|
-
if (this.runningJobs.size > this.config.maxConcurrentJobs) {
|
|
866
|
-
this.log(`Too many running jobs: ${this.runningJobs.size}/${this.config.maxConcurrentJobs}`, { level: 'warn' });
|
|
867
|
-
}
|
|
868
|
-
// 큐 크기 확인
|
|
869
|
-
if (this.jobQueue.length > 100) {
|
|
870
|
-
this.log(`Large job queue: ${this.jobQueue.length} items`, { level: 'warn' });
|
|
871
|
-
}
|
|
872
961
|
this.log('Scheduler health check completed', {
|
|
873
|
-
memoryUsage:
|
|
874
|
-
runningJobs:
|
|
875
|
-
queueSize:
|
|
876
|
-
uptime:
|
|
962
|
+
memoryUsage: healthResult.memoryUsage,
|
|
963
|
+
runningJobs: healthResult.runningJobs,
|
|
964
|
+
queueSize: healthResult.queueSize,
|
|
965
|
+
uptime: healthResult.uptime,
|
|
966
|
+
warnings: healthResult.warnings.length,
|
|
967
|
+
errors: healthResult.errors.length
|
|
877
968
|
});
|
|
878
969
|
}
|
|
879
970
|
catch (error) {
|
|
@@ -887,22 +978,25 @@ export class BatchScheduler {
|
|
|
887
978
|
const memUsage = process.memoryUsage();
|
|
888
979
|
const memUsagePercent = (memUsage.heapUsed / memUsage.heapTotal) * 100;
|
|
889
980
|
const totalExecutions = Array.from(this.totalExecutions.values()).reduce((sum, count) => sum + count, 0);
|
|
890
|
-
|
|
981
|
+
// RetryManager에서 총 에러 카운트 계산
|
|
982
|
+
const totalErrors = Array.from(this.intervals.keys()).reduce((sum, name) => {
|
|
983
|
+
return sum + this.retryManager.getErrorCount(name);
|
|
984
|
+
}, 0);
|
|
891
985
|
const errorRate = totalExecutions > 0 ? totalErrors / totalExecutions : 0;
|
|
892
986
|
const jobs = Array.from(this.intervals.keys()).map(name => ({
|
|
893
987
|
name,
|
|
894
988
|
lastExecution: this.lastExecution.get(name) || null,
|
|
895
989
|
totalExecutions: this.totalExecutions.get(name) || 0,
|
|
896
|
-
errorCount: this.
|
|
897
|
-
errorRate: (this.totalExecutions.get(name) || 0) > 0 ?
|
|
898
|
-
isRunning: this.
|
|
990
|
+
errorCount: this.retryManager.getErrorCount(name),
|
|
991
|
+
errorRate: (this.totalExecutions.get(name) || 0) > 0 ? this.retryManager.getErrorCount(name) / (this.totalExecutions.get(name) || 1) : 0,
|
|
992
|
+
isRunning: this.jobQueue.isRunning(name)
|
|
899
993
|
}));
|
|
900
994
|
return {
|
|
901
995
|
status: this.getStatus(),
|
|
902
996
|
health: {
|
|
903
997
|
memoryUsage: memUsagePercent,
|
|
904
|
-
runningJobs: this.
|
|
905
|
-
queueSize: this.jobQueue.
|
|
998
|
+
runningJobs: this.jobQueue.runningCount,
|
|
999
|
+
queueSize: this.jobQueue.size,
|
|
906
1000
|
errorRate,
|
|
907
1001
|
uptime: this.startTime ? Date.now() - this.startTime.getTime() : 0
|
|
908
1002
|
},
|