Created
July 27, 2023 03:34
-
-
Save hden/f361ec9d5133d21c58187e08a0264251 to your computer and use it in GitHub Desktop.
BayesianCircuitBreaker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class BayesianCircuitBreaker { | |
constructor(windowSize, thresholdHealthy, thresholdStruggling, credibleLevel, gcInterval = 100) { | |
this.windowSize = windowSize | |
this.thresholdHealthy = thresholdHealthy | |
this.thresholdStruggling = thresholdStruggling | |
this.credibleLevel = credibleLevel | |
this.successCount = 0 | |
this.failureCount = 0 | |
this.samples = [] | |
this.gcTimer = setInterval((self) => { | |
const t = Date.now() | |
// Remove events that are outside the window | |
while (self.samples.length > 0) { | |
const oldestEventTime = self.samples[0].timestamp | |
if (t - oldestEventTime <= gcInterval) { | |
break | |
} | |
// Remove the oldest event | |
self.shift() | |
} | |
}, gcInterval, this) | |
} | |
// Method to record the result of a request (success: true, failure: false) | |
push(success) { | |
if (success) { | |
this.successCount++ | |
} else { | |
this.failureCount++ | |
} | |
this.samples.push(success) | |
if (this.samples.length > this.windowSize) { | |
// Remove the oldest event | |
this.shift() | |
} | |
} | |
shift() { | |
const removedEvent = this.samples.shift() | |
if (removedEvent) { | |
this.successCount-- | |
} else { | |
this.failureCount-- | |
} | |
return removedEvent | |
} | |
// Method to estimate the Highest Posterior Density (HPD) interval of the posteriors (Beta distribution) | |
estimateHPDInterval(alpha, beta) { | |
alpha += 5 | |
beta += 1 | |
// Calculate the quantiles for the Beta distribution using percentiles approach | |
const lowerQuantile = jStat.beta.inv(this.credibleLevel / 2, alpha, beta) | |
const upperQuantile = jStat.beta.inv(1 - this.credibleLevel / 2, alpha, beta) | |
return [lowerQuantile, upperQuantile] | |
} | |
// Method to get the current circuit status ('open', 'closed', or 'half-open') | |
getStatus() { | |
const [lowerBoundHealthy, upperBoundHealthy] = this.estimateHPDInterval(this.successCount, this.failureCount) | |
const [lowerBoundStruggling, upperBoundStruggling] = this.estimateHPDInterval(this.failureCount, this.successCount) | |
if (upperBoundHealthy < this.thresholdHealthy && upperBoundStruggling < this.thresholdStruggling) { | |
return 'half-open' // Both posteriors are below the HPD interval, HALF-OPEN state | |
} else if (lowerBoundHealthy >= this.thresholdHealthy) { | |
return 'closed' // Server is healthy, CLOSE the circuit | |
} else if (upperBoundStruggling >= this.thresholdStruggling) { | |
return 'open' // Server is struggling, OPEN the circuit | |
} else { | |
return 'half-open' // Otherwise, HALF-OPEN the circuit to explore more data | |
} | |
} | |
} | |
// Example usage: | |
const cb = new BayesianCircuitBreaker(100, 0.85, 0.4, 0.95) | |
const range = (n) => Array.from(Array(n), (_, k) => k + 1) | |
console.log('Circuit Status 0:', cb.getStatus()) | |
// Simulate some requests and record results (~1% error rate) | |
range(10).forEach((n) => { | |
const event = Math.random() >= 0.01 | |
cb.push(event) | |
// Get the current circuit status | |
console.log(`Circuit Status ${n} (${event}): ${cb.getStatus()}`) | |
}) | |
range(10).forEach((n) => { | |
const event = true | |
cb.push(event) | |
// Get the current circuit status | |
console.log(`Circuit Status ${10 + n} (${event}): ${cb.getStatus()}`) | |
}) | |
range(10).forEach((n) => { | |
const event = false | |
cb.push(event) | |
// Get the current circuit status | |
console.log(`Circuit Status ${20 + n} (${event}): ${cb.getStatus()}`) | |
}) | |
setTimeout(() => { | |
// Get the current circuit status | |
console.log(`Circuit Status (1 sec later):`, cb.getStatus()) | |
}, 1000) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment