@claude-vector/cli
Version:
CLI for Claude-integrated vector search
545 lines (448 loc) • 14 kB
JavaScript
/**
* AdaptiveThrottler - パラメトリック思考によるAPI制限・パフォーマンス適応制御
*
* 多次元制御要因:
* - API レート制限(OpenAI: 3 RPM for free tier, 500+ for paid)
* - 同時リクエスト数制限
* - エラー率に基づく動的調整
* - システム負荷(CPU、メモリ)
* - ネットワーク応答時間
*
* 適応的制御アルゴリズム:
* - バックプレッシャー制御
* - 指数バックオフ
* - 動的バッチサイズ調整
*/
import { EventEmitter } from 'events';
/**
* API制限・パフォーマンス制御のデフォルト設定
*/
const THROTTLE_DEFAULTS = {
// OpenAI API 制限設定
baseDelay: 1000, // 基本待機時間(ms)
maxDelay: 30000, // 最大待機時間(ms)
maxConcurrent: 3, // 最大同時実行数
// 適応制御設定
errorThreshold: 0.1, // エラー率閾値(10%)
successThreshold: 0.95, // 成功率閾値(95%)
adaptationFactor: 1.5, // 適応係数
// バックオフ設定
exponentialBase: 2, // 指数バックオフ基数
maxRetries: 3, // 最大リトライ回数
jitterFactor: 0.1, // ジッター係数
// システム負荷制御
memoryThreshold: 500, // メモリ閾値(MB)
cpuThreshold: 80, // CPU使用率閾値(%)
// 統計収集設定
statisticsWindow: 100, // 統計計算ウィンドウサイズ
cleanupInterval: 300000 // 統計クリーンアップ間隔(5分)
};
export class AdaptiveThrottler extends EventEmitter {
constructor(config = {}) {
super();
this.config = { ...THROTTLE_DEFAULTS, ...config };
// 制御状態
this.state = {
// 実行制御
activeCalls: 0,
queuedCalls: 0,
// 統計情報
totalCalls: 0,
successfulCalls: 0,
failedCalls: 0,
// 動的制御
currentDelay: this.config.baseDelay,
errorRate: 0,
successRate: 1,
avgResponseTime: 0,
// システム状態
lastSystemCheck: 0,
systemLoad: {
memory: 0,
cpu: 0
}
};
// 統計履歴
this.statistics = {
callHistory: [],
responseTimeHistory: [],
errorHistory: []
};
// タイマー
this.cleanupTimer = setInterval(() => {
this.cleanupStatistics();
}, this.config.cleanupInterval);
// 待機キュー
this.waitQueue = [];
this.startTime = Date.now();
}
/**
* API呼び出しの制御実行(パラメトリック制御)
*/
async throttleApiCall(operation, metadata = {}) {
const callId = this.generateCallId();
const startTime = Date.now();
try {
// 制御前チェック
await this.preCallControl(callId, metadata);
// API呼び出し実行
const result = await this.executeWithTracking(operation, callId);
// 成功時の統計更新
this.recordSuccess(callId, startTime, result);
return result;
} catch (error) {
// 失敗時の統計更新
this.recordFailure(callId, startTime, error);
throw error;
}
}
/**
* 呼び出し前制御(多次元要因考慮)
*/
async preCallControl(callId, metadata) {
// 1. 同時実行数制御
await this.waitForSlot();
// 2. 適応的遅延制御
const delay = this.calculateAdaptiveDelay(metadata);
if (delay > 0) {
await this.wait(delay);
}
// 3. システム負荷チェック
await this.checkSystemLoad();
// 4. API レート制限チェック
await this.checkRateLimit();
this.state.activeCalls++;
this.state.queuedCalls = Math.max(0, this.state.queuedCalls - 1);
}
/**
* 実行スロット待機
*/
async waitForSlot() {
if (this.state.activeCalls >= this.config.maxConcurrent) {
this.state.queuedCalls++;
return new Promise((resolve) => {
this.waitQueue.push(resolve);
});
}
}
/**
* 適応的遅延計算(パラメトリック思考)
*/
calculateAdaptiveDelay(metadata = {}) {
let delay = this.state.currentDelay;
// 要因1: エラー率ベース調整
if (this.state.errorRate > this.config.errorThreshold) {
const errorMultiplier = 1 + (this.state.errorRate * this.config.adaptationFactor);
delay *= errorMultiplier;
}
// 要因2: システム負荷ベース調整
const systemLoad = this.state.systemLoad;
if (systemLoad.memory > this.config.memoryThreshold) {
delay *= 1.2; // メモリ負荷補正
}
if (systemLoad.cpu > this.config.cpuThreshold) {
delay *= 1.3; // CPU負荷補正
}
// 要因3: 応答時間ベース調整
if (this.state.avgResponseTime > 5000) { // 5秒以上
delay *= 1.1; // 応答時間補正
}
// 要因4: メタデータベース調整
if (metadata.priority === 'low') {
delay *= 1.5;
} else if (metadata.priority === 'high') {
delay *= 0.8;
}
// 要因5: ジッター追加(同期呼び出し回避)
const jitter = Math.random() * this.config.jitterFactor * delay;
delay += jitter;
// 制限範囲内に調整
return Math.max(0, Math.min(delay, this.config.maxDelay));
}
/**
* 待機処理
*/
async wait(ms) {
if (ms <= 0) return;
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
/**
* システム負荷チェック
*/
async checkSystemLoad() {
const now = Date.now();
// 1秒間隔でシステム負荷をチェック
if (now - this.state.lastSystemCheck > 1000) {
const memUsage = process.memoryUsage();
this.state.systemLoad.memory = Math.round(memUsage.heapUsed / 1024 / 1024);
// CPU使用率は簡易的な実装(実際の測定は複雑)
this.state.systemLoad.cpu = Math.min(50, this.state.activeCalls * 10);
this.state.lastSystemCheck = now;
}
}
/**
* API レート制限チェック
*/
async checkRateLimit() {
// OpenAI の RPM 制限を考慮した制御
// 基本的には delay で制御されるが、追加チェック
const recentCalls = this.getRecentCalls(60000); // 過去1分
const rpm = recentCalls.length;
// 無料tier: 3 RPM, 有料tier: 500+ RPM
const maxRpm = process.env.OPENAI_TIER === 'free' ? 3 : 100;
if (rpm >= maxRpm) {
const waitTime = 60000 / maxRpm; // 分あたりの間隔
await this.wait(waitTime);
}
}
/**
* 実行・追跡
*/
async executeWithTracking(operation, callId) {
const startTime = Date.now();
try {
this.emit('call-start', { callId, startTime });
const result = await operation();
const endTime = Date.now();
const responseTime = endTime - startTime;
this.emit('call-success', { callId, responseTime, result });
return result;
} catch (error) {
const endTime = Date.now();
const responseTime = endTime - startTime;
this.emit('call-error', { callId, responseTime, error });
throw error;
} finally {
this.state.activeCalls--;
this.releaseWaitingCall();
}
}
/**
* 成功記録
*/
recordSuccess(callId, startTime, result) {
const responseTime = Date.now() - startTime;
this.state.totalCalls++;
this.state.successfulCalls++;
// 統計履歴に追加
this.statistics.callHistory.push({
callId,
startTime,
responseTime,
success: true,
timestamp: Date.now()
});
this.statistics.responseTimeHistory.push(responseTime);
// 動的統計更新
this.updateStatistics();
// 成功時の制御調整
this.adjustControlOnSuccess();
}
/**
* 失敗記録
*/
recordFailure(callId, startTime, error) {
const responseTime = Date.now() - startTime;
this.state.totalCalls++;
this.state.failedCalls++;
// 統計履歴に追加
this.statistics.callHistory.push({
callId,
startTime,
responseTime,
success: false,
error: error.message,
timestamp: Date.now()
});
this.statistics.errorHistory.push({
timestamp: Date.now(),
error: error.message,
type: this.categorizeError(error)
});
// 動的統計更新
this.updateStatistics();
// 失敗時の制御調整
this.adjustControlOnFailure(error);
}
/**
* 統計更新
*/
updateStatistics() {
if (this.state.totalCalls === 0) return;
// エラー率計算
this.state.errorRate = this.state.failedCalls / this.state.totalCalls;
this.state.successRate = this.state.successfulCalls / this.state.totalCalls;
// 平均応答時間計算
const recentResponseTimes = this.statistics.responseTimeHistory.slice(-50);
if (recentResponseTimes.length > 0) {
this.state.avgResponseTime = recentResponseTimes.reduce((sum, time) => sum + time, 0) / recentResponseTimes.length;
}
}
/**
* 成功時の制御調整
*/
adjustControlOnSuccess() {
// 連続成功時は遅延を減少
if (this.state.successRate > this.config.successThreshold) {
this.state.currentDelay = Math.max(
this.config.baseDelay,
this.state.currentDelay * 0.9
);
}
}
/**
* 失敗時の制御調整
*/
adjustControlOnFailure(error) {
const errorType = this.categorizeError(error);
// エラータイプに応じた調整
switch (errorType) {
case 'rate_limit':
this.state.currentDelay *= 2; // レート制限時は倍化
break;
case 'timeout':
this.state.currentDelay *= 1.3; // タイムアウト時は1.3倍
break;
case 'server_error':
this.state.currentDelay *= 1.5; // サーバーエラー時は1.5倍
break;
default:
this.state.currentDelay *= 1.2; // その他は1.2倍
}
// 最大値制限
this.state.currentDelay = Math.min(this.state.currentDelay, this.config.maxDelay);
}
/**
* エラー分類
*/
categorizeError(error) {
const message = error.message.toLowerCase();
if (message.includes('rate limit') || message.includes('429')) {
return 'rate_limit';
} else if (message.includes('timeout') || message.includes('ECONNRESET')) {
return 'timeout';
} else if (message.includes('500') || message.includes('502') || message.includes('503')) {
return 'server_error';
} else if (message.includes('network') || message.includes('ENOTFOUND')) {
return 'network_error';
} else {
return 'unknown';
}
}
/**
* 待機中呼び出しの解放
*/
releaseWaitingCall() {
if (this.waitQueue.length > 0) {
const resolve = this.waitQueue.shift();
resolve();
}
}
/**
* 最近の呼び出し取得
*/
getRecentCalls(timeWindowMs) {
const cutoff = Date.now() - timeWindowMs;
return this.statistics.callHistory.filter(call => call.timestamp > cutoff);
}
/**
* 統計クリーンアップ
*/
cleanupStatistics() {
const cutoff = Date.now() - (3600000); // 1時間前
// 古い履歴を削除
this.statistics.callHistory = this.statistics.callHistory.filter(
call => call.timestamp > cutoff
);
this.statistics.errorHistory = this.statistics.errorHistory.filter(
error => error.timestamp > cutoff
);
// 応答時間履歴は最新200件のみ保持
if (this.statistics.responseTimeHistory.length > 200) {
this.statistics.responseTimeHistory = this.statistics.responseTimeHistory.slice(-200);
}
}
/**
* 統計情報取得
*/
getStatistics() {
const uptime = Date.now() - this.startTime;
const rpm = this.getRecentCalls(60000).length;
return {
// 基本統計
totalCalls: this.state.totalCalls,
successfulCalls: this.state.successfulCalls,
failedCalls: this.state.failedCalls,
successRate: this.state.successRate,
errorRate: this.state.errorRate,
// パフォーマンス
avgResponseTime: Math.round(this.state.avgResponseTime),
currentDelay: this.state.currentDelay,
activeCalls: this.state.activeCalls,
queuedCalls: this.state.queuedCalls,
rpm,
// システム負荷
systemLoad: this.state.systemLoad,
// 実行時間
uptime,
// 設定
config: this.config
};
}
/**
* コール ID 生成
*/
generateCallId() {
return `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 設定更新
*/
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
this.emit('config-updated', this.config);
}
/**
* 状態リセット
*/
reset() {
this.state = {
activeCalls: 0,
queuedCalls: 0,
totalCalls: 0,
successfulCalls: 0,
failedCalls: 0,
currentDelay: this.config.baseDelay,
errorRate: 0,
successRate: 1,
avgResponseTime: 0,
lastSystemCheck: 0,
systemLoad: { memory: 0, cpu: 0 }
};
this.statistics = {
callHistory: [],
responseTimeHistory: [],
errorHistory: []
};
this.waitQueue = [];
this.startTime = Date.now();
this.emit('reset');
}
/**
* クリーンアップ
*/
destroy() {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
// 待機中の呼び出しをキャンセル
this.waitQueue.forEach(resolve => {
resolve(); // または reject with cancellation error
});
this.waitQueue = [];
this.emit('destroyed');
}
}