How to Use Connection Retry and Circuit Breaker for Databases in Go

Go's standard library does not include built-in connection retry or circuit breaker logic for databases; you must implement these patterns manually or use a third-party library. For a basic retry mechanism, wrap your database call in a loop with exponential backoff to handle transient failures. For

How to Use Connection Retry and Circuit Breaker for Databases in Go

Go's standard library does not include built-in connection retry or circuit breaker logic for databases; you must implement these patterns manually or use a third-party library. For a basic retry mechanism, wrap your database call in a loop with exponential backoff to handle transient failures. For a circuit breaker, track failure counts and stop attempting connections after a threshold is reached to prevent cascading failures.

package main

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	_ "github.com/lib/pq"
)

func connectWithRetry(ctx context.Context, dsn string, maxRetries int) (*sql.DB, error) {
	var db *sql.DB
	var err error
	for i := 0; i < maxRetries; i++ {
		db, err = sql.Open("postgres", dsn)
		if err == nil {
			if err = db.PingContext(ctx); err == nil {
				return db, nil
			}
			// Close the DB if Ping fails to avoid leaking connections
			db.Close()
		}
		time.Sleep(time.Duration(i+1) * time.Second)
	}
	return nil, fmt.Errorf("failed to connect after %d retries: %w", maxRetries, err)
}

func main() {
	ctx := context.Background()
	dsn := "host=localhost user=postgres password=secret dbname=test sslmode=disable"
	db, err := connectWithRetry(ctx, dsn, 5)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer db.Close()
	fmt.Println("Connected successfully")
}

To implement a circuit breaker, maintain a state (closed, open, half-open) and a failure counter. If the counter exceeds a limit, switch to the "open" state and reject new requests immediately. After a timeout, switch to "half-open" to test the connection; if it succeeds, reset to "closed".