Database Circuit Breaker — Complete Implementation Guide
In this tutorial, you will learn about Database Circuit Breaker. We cover key concepts, practical examples, and best practices to help you master this topic.
Database circuit breaker protects application databases by monitoring query failures and opening the circuit when the database becomes unresponsive, preventing Connection Pool exhaustion.
What You'll Learn
By the end of this tutorial, you will implement circuit breakers around database queries, integrate with connection pools, and provide fallback read queries.
Real-World Use
DodaTech's services use database circuit breakers that open after 5 query timeouts, preventing connection pool exhaustion and allowing the database time to recover.
Database Circuit Breaker Implementation
const { Pool } = require("pg");
class DatabaseCircuitBreaker {
constructor(options = {}) {
this.threshold = options.threshold || 5;
this.resetTimeout = options.resetTimeout || 30000;
this.timeout = options.timeout || 10000;
this.state = "closed";
this.failureCount = 0;
this.nextAttempt = Date.now();
}
async query(pool, text, params) {
if (this.state === "open") {
if (Date.now() < this.nextAttempt) {
throw new Error("Database circuit open");
}
this.state = "half-open";
}
try {
const result = await Promise.race([
pool.query(text, params),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Query timeout")), this.timeout)
)
]);
this.onSuccess();
return result;
} catch (err) {
this.onFailure(err);
throw err;
}
}
onSuccess() {
this.failureCount = 0;
if (this.state === "half-open") this.state = "closed";
}
onFailure(err) {
const isTransient = err.message.includes("timeout") ||
err.code === "08006" || err.code === "08003";
if (!isTransient) return;
this.failureCount++;
if (this.state === "half-open" || this.failureCount >= this.threshold) {
this.state = "open";
this.nextAttempt = Date.now() + this.resetTimeout;
}
}
}
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const dbCircuitBreaker = new DatabaseCircuitBreaker();
async function queryWithBreaker(text, params) {
try {
return await dbCircuitBreaker.query(pool, text, params);
} catch (err) {
if (err.message === "Database circuit open") {
return fallbackQuery(text, params);
}
throw err;
}
}
async function fallbackQuery(text, params) {
if (text.trim().toUpperCase().startsWith("SELECT")) {
return { rows: [], fallback: true };
}
throw new Error("Database unavailable");
}
Connection Pool Protection
Circuit breakers protect the connection pool by rejecting queries early, preventing connection acquisition attempts that would exhaust the pool.
class PoolProtectingBreaker {
constructor(pool, options = {}) {
this.pool = pool;
this.state = "closed";
this.failures = 0;
this.threshold = options.threshold || 5;
this.resetTimeout = options.resetTimeout || 30000;
}
async query(text, params) {
if (this.state === "open") {
if (Date.now() < this.nextAttempt) {
return { error: "Circuit open", rows: [] };
}
this.state = "half-open";
}
const client = await this.pool.connect();
try {
const result = await client.query(text, params);
this.onSuccess();
return result;
} catch (err) {
this.onFailure(err);
throw err;
} finally {
client.release();
}
}
onSuccess() {
this.failures = 0;
if (this.state === "half-open") this.state = "closed";
}
onFailure(err) {
if (err.code === "08006" || err.code === "08003" || err.message.includes("timeout")) {
this.failures++;
if (this.state === "half-open" || this.failures >= this.threshold) {
this.state = "open";
this.nextAttempt = Date.now() + this.resetTimeout;
}
}
}
async healthCheck() {
try {
await this.pool.query("SELECT 1");
return true;
} catch {
return false;
}
}
}
Common Mistakes
Counting non-transient errors -- Syntax errors and constraint violations should not open the circuit. Only connection and timeout errors.
Not releasing connections on error -- When a query fails, the client must be released back to the pool. Use try/finally.
Opening circuit on slow queries -- Slow queries need optimization, not circuit breaking. Set separate thresholds for timeouts.
No fallback for read queries -- Read queries can often return cached or empty data. Write queries may need to fail.
Not monitoring database circuit state -- An open database circuit is a critical event. Always alert when it happens.
Practice Questions
Which database errors should trigger the circuit breaker? Connection failures (08006, 08003) and query timeouts. Syntax errors and constraint violations should not.
How does the circuit breaker protect the connection pool? By rejecting queries early without acquiring a connection from the pool, preserving connections for healthy queries.
What fallback should read queries use during an open circuit? Cached data, an empty result set, or a read replica if available.
Challenge: Implement a circuit breaker that uses a read replica as fallback.
class ReadReplicaBreaker {
async query(text, params) {
try {
return await primary.query(text, params);
} catch (err) {
if (circuit.state === "open") {
return await replica.query(text, params);
}
throw err;
}
}
}
FAQ
Mini Project
Build a database circuit breaker with read replica fallback, connection pool protection, and health checking.
class DatabaseProtector {
constructor(primary, replica = null) {
this.primary = primary;
this.replica = replica;
this.state = "closed";
this.failures = 0;
this.threshold = 5;
this.resetTimeout = 30000;
this.nextAttempt = Date.now();
}
async query(text, params) {
if (this.state === "open") {
if (Date.now() < this.nextAttempt) {
return this.fallback(text, params);
}
this.state = "half-open";
}
try {
const result = await this.primary.query(text, params);
this.failures = 0;
if (this.state === "half-open") this.state = "closed";
return result;
} catch (err) {
this.failures++;
if (this.state === "half-open" || this.failures >= this.threshold) {
this.state = "open";
this.nextAttempt = Date.now() + this.resetTimeout;
}
if (this.isRetryable(err) && this.replica) {
return this.replica.query(text, params);
}
throw err;
}
}
isRetryable(err) {
return err.code === "08006" || err.code === "08003" || err.message.includes("timeout");
}
fallback(text, params) {
if (this.replica && text.trim().toUpperCase().startsWith("SELECT")) {
return this.replica.query(text, params);
}
return { rows: [] };
}
async healthCheck() {
try {
await this.primary.query("SELECT 1");
return "healthy";
} catch {
return "unhealthy";
}
}
}
What's Next
Now that you understand database circuit breakers, explore circuit breakers in microservice architectures. Then learn about advanced circuit breaker patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro