"use strict"; /** * Load Balancer - Intelligent request routing and traffic management * * Features: * - Circuit breaker pattern * - Rate limiting per client * - Regional routing * - Request prioritization * - Health-based routing */ Object.defineProperty(exports, "__esModule", { value: true }); exports.LoadBalancer = void 0; const events_1 = require("events"); const api_1 = require("@opentelemetry/api"); const prom_client_1 = require("prom-client"); // Metrics const metrics = { routedRequests: new prom_client_1.Counter({ name: 'load_balancer_routed_requests_total', help: 'Total number of routed requests', labelNames: ['backend', 'status'], }), rejectedRequests: new prom_client_1.Counter({ name: 'load_balancer_rejected_requests_total', help: 'Total number of rejected requests', labelNames: ['reason'], }), circuitBreakerState: new prom_client_1.Gauge({ name: 'circuit_breaker_state', help: 'Circuit breaker state (0=closed, 1=open, 2=half-open)', labelNames: ['backend'], }), rateLimitActive: new prom_client_1.Gauge({ name: 'rate_limit_active_clients', help: 'Number of clients currently rate limited', }), requestLatency: new prom_client_1.Histogram({ name: 'load_balancer_request_latency_seconds', help: 'Request latency in seconds', labelNames: ['backend'], buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1], }), }; const tracer = api_1.trace.getTracer('load-balancer', '1.0.0'); // Circuit breaker states var CircuitState; (function (CircuitState) { CircuitState[CircuitState["CLOSED"] = 0] = "CLOSED"; CircuitState[CircuitState["OPEN"] = 1] = "OPEN"; CircuitState[CircuitState["HALF_OPEN"] = 2] = "HALF_OPEN"; })(CircuitState || (CircuitState = {})); // Request priority var RequestPriority; (function (RequestPriority) { RequestPriority[RequestPriority["LOW"] = 0] = "LOW"; RequestPriority[RequestPriority["NORMAL"] = 1] = "NORMAL"; RequestPriority[RequestPriority["HIGH"] = 2] = "HIGH"; RequestPriority[RequestPriority["CRITICAL"] = 3] = "CRITICAL"; })(RequestPriority || (RequestPriority = {})); /** * Token Bucket Rate Limiter */ class RateLimiter { constructor(requestsPerSecond) { this.buckets = new Map(); this.capacity = requestsPerSecond; this.refillRate = requestsPerSecond; } tryAcquire(clientId, tokens = 1) { const now = Date.now(); let bucket = this.buckets.get(clientId); if (!bucket) { bucket = { tokens: this.capacity, lastRefill: now }; this.buckets.set(clientId, bucket); } // Refill tokens based on time passed const timePassed = (now - bucket.lastRefill) / 1000; const tokensToAdd = timePassed * this.refillRate; bucket.tokens = Math.min(this.capacity, bucket.tokens + tokensToAdd); bucket.lastRefill = now; // Try to consume tokens if (bucket.tokens >= tokens) { bucket.tokens -= tokens; return true; } return false; } reset(clientId) { this.buckets.delete(clientId); } getStats() { let limitedClients = 0; for (const [_, bucket] of this.buckets) { if (bucket.tokens < 1) { limitedClients++; } } return { totalClients: this.buckets.size, limitedClients, }; } } /** * Circuit Breaker */ class CircuitBreaker { constructor(backendId, threshold, timeout, halfOpenMaxRequests) { this.backendId = backendId; this.threshold = threshold; this.timeout = timeout; this.halfOpenMaxRequests = halfOpenMaxRequests; this.state = CircuitState.CLOSED; this.failures = 0; this.successes = 0; this.lastFailureTime = 0; this.halfOpenRequests = 0; this.updateMetrics(); } async execute(fn) { if (this.state === CircuitState.OPEN) { // Check if timeout has passed if (Date.now() - this.lastFailureTime >= this.timeout) { this.state = CircuitState.HALF_OPEN; this.halfOpenRequests = 0; this.updateMetrics(); } else { throw new Error(`Circuit breaker open for backend ${this.backendId}`); } } if (this.state === CircuitState.HALF_OPEN) { if (this.halfOpenRequests >= this.halfOpenMaxRequests) { throw new Error(`Circuit breaker half-open limit reached for backend ${this.backendId}`); } this.halfOpenRequests++; } const startTime = Date.now(); try { const result = await fn(); this.onSuccess(); const duration = (Date.now() - startTime) / 1000; metrics.requestLatency.observe({ backend: this.backendId }, duration); metrics.routedRequests.inc({ backend: this.backendId, status: 'success' }); return result; } catch (error) { this.onFailure(); metrics.routedRequests.inc({ backend: this.backendId, status: 'failure' }); throw error; } } onSuccess() { this.failures = 0; this.successes++; if (this.state === CircuitState.HALF_OPEN) { if (this.successes >= this.halfOpenMaxRequests) { this.state = CircuitState.CLOSED; this.successes = 0; this.updateMetrics(); } } } onFailure() { this.failures++; this.lastFailureTime = Date.now(); const failureRate = this.failures / (this.failures + this.successes); if (failureRate >= this.threshold) { this.state = CircuitState.OPEN; this.updateMetrics(); } } updateMetrics() { metrics.circuitBreakerState.set({ backend: this.backendId }, this.state); } getState() { return this.state; } reset() { this.state = CircuitState.CLOSED; this.failures = 0; this.successes = 0; this.lastFailureTime = 0; this.halfOpenRequests = 0; this.updateMetrics(); } } /** * Backend Manager */ class BackendManager { constructor(backends, circuitBreakerThreshold, circuitBreakerTimeout, halfOpenMaxRequests) { this.backends = new Map(); for (const backend of backends) { this.backends.set(backend.id, { config: backend, circuitBreaker: new CircuitBreaker(backend.id, circuitBreakerThreshold, circuitBreakerTimeout, halfOpenMaxRequests), activeRequests: 0, healthScore: 1.0, }); } } selectBackend(region) { const available = Array.from(this.backends.entries()) .filter(([_, backend]) => { // Filter by region if specified if (region && backend.config.region !== region) { return false; } // Filter by circuit breaker state if (backend.circuitBreaker.getState() === CircuitState.OPEN) { return false; } // Filter by concurrency limit if (backend.config.maxConcurrency && backend.activeRequests >= backend.config.maxConcurrency) { return false; } return true; }) .map(([id, backend]) => ({ id, score: this.calculateScore(backend), })) .sort((a, b) => b.score - a.score); return available.length > 0 ? available[0].id : null; } calculateScore(backend) { const weight = backend.config.weight || 1; const loadFactor = backend.config.maxConcurrency ? 1 - (backend.activeRequests / backend.config.maxConcurrency) : 1; return weight * loadFactor * backend.healthScore; } async executeOnBackend(backendId, fn) { const backend = this.backends.get(backendId); if (!backend) { throw new Error(`Backend ${backendId} not found`); } backend.activeRequests++; try { return await backend.circuitBreaker.execute(fn); } finally { backend.activeRequests--; } } updateHealth(backendId, healthScore) { const backend = this.backends.get(backendId); if (backend) { backend.healthScore = Math.max(0, Math.min(1, healthScore)); } } getStats() { const stats = {}; for (const [id, backend] of this.backends) { stats[id] = { activeRequests: backend.activeRequests, healthScore: backend.healthScore, circuitState: backend.circuitBreaker.getState(), region: backend.config.region, }; } return stats; } } /** * Priority Queue for request scheduling */ class PriorityQueue { constructor() { this.queues = new Map([ [RequestPriority.CRITICAL, []], [RequestPriority.HIGH, []], [RequestPriority.NORMAL, []], [RequestPriority.LOW, []], ]); } enqueue(item, priority) { const queue = this.queues.get(priority); queue.push(item); } dequeue() { // Process by priority for (const priority of [ RequestPriority.CRITICAL, RequestPriority.HIGH, RequestPriority.NORMAL, RequestPriority.LOW, ]) { const queue = this.queues.get(priority); if (queue.length > 0) { return queue.shift(); } } return undefined; } size() { return Array.from(this.queues.values()).reduce((sum, q) => sum + q.length, 0); } clear() { for (const queue of this.queues.values()) { queue.length = 0; } } } /** * Load Balancer */ class LoadBalancer extends events_1.EventEmitter { constructor(config) { super(); this.config = { maxRequestsPerSecond: config.maxRequestsPerSecond || 10000, circuitBreakerThreshold: config.circuitBreakerThreshold || 0.5, circuitBreakerTimeout: config.circuitBreakerTimeout || 30000, halfOpenMaxRequests: config.halfOpenMaxRequests || 5, backends: config.backends || [{ id: 'default', host: 'localhost' }], enableRegionalRouting: config.enableRegionalRouting !== false, priorityQueueSize: config.priorityQueueSize || 1000, }; this.rateLimiter = new RateLimiter(this.config.maxRequestsPerSecond); this.backendManager = new BackendManager(this.config.backends, this.config.circuitBreakerThreshold, this.config.circuitBreakerTimeout, this.config.halfOpenMaxRequests); this.requestQueue = new PriorityQueue(); this.updateMetrics(); } async route(collection, query, clientId = 'default', priority = RequestPriority.NORMAL) { const span = tracer.startSpan('load-balancer-route', { attributes: { collection, clientId, priority }, }); try { // Rate limiting check if (!this.rateLimiter.tryAcquire(clientId)) { metrics.rejectedRequests.inc({ reason: 'rate_limit' }); span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: 'Rate limit exceeded' }); return false; } // Queue size check if (this.requestQueue.size() >= this.config.priorityQueueSize) { metrics.rejectedRequests.inc({ reason: 'queue_full' }); span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: 'Queue full' }); return false; } // Select backend const region = query.region; const backendId = this.backendManager.selectBackend(this.config.enableRegionalRouting ? region : undefined); if (!backendId) { metrics.rejectedRequests.inc({ reason: 'no_backend' }); span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: 'No backend available' }); return false; } span.setStatus({ code: api_1.SpanStatusCode.OK }); return true; } catch (error) { span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error.message }); return false; } finally { span.end(); } } async executeWithLoadBalancing(fn, region, priority = RequestPriority.NORMAL) { const backendId = this.backendManager.selectBackend(this.config.enableRegionalRouting ? region : undefined); if (!backendId) { throw new Error('No backend available'); } return this.backendManager.executeOnBackend(backendId, fn); } updateBackendHealth(backendId, healthScore) { this.backendManager.updateHealth(backendId, healthScore); } updateMetrics() { setInterval(() => { const rateLimitStats = this.rateLimiter.getStats(); metrics.rateLimitActive.set(rateLimitStats.limitedClients); }, 5000); } getStats() { return { rateLimit: this.rateLimiter.getStats(), backends: this.backendManager.getStats(), queueSize: this.requestQueue.size(), }; } reset() { this.requestQueue.clear(); } } exports.LoadBalancer = LoadBalancer; //# sourceMappingURL=load-balancer.js.map