A drop-in wrapper for pgxpool that adds automatic retry with exponential backoff for transient PostgreSQL errors.
go get github.com/tinybluerobots/retrypoolpackage main
import (
"context"
"log"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/tinybluerobots/retrypool"
)
func main() {
ctx := context.Background()
// Create the underlying pgxpool
pool, err := pgxpool.New(ctx, "postgres://localhost/mydb")
if err != nil {
log.Fatal(err)
}
defer pool.Close()
// Wrap with retry logic
rp := retrypool.New(pool)
// Use like a normal pool - transient errors are automatically retried
var count int
err = rp.QueryRow(ctx, "SELECT COUNT(*) FROM users").Scan(&count)
if err != nil {
log.Fatal(err)
}
}retrypool implements the DBTX interface, making it compatible with sqlc-generated code:
pool, _ := pgxpool.New(ctx, connString)
rp := retrypool.New(pool)
queries := db.New(rp) // Pass directly to sqlc-generated code
users, err := queries.ListUsers(ctx)rp := retrypool.New(pool,
retrypool.WithMaxRetries(5), // Default: 3
retrypool.WithBaseDelay(200*time.Millisecond), // Default: 100ms
)For operations that don't need retry (transactions, batch operations):
rp.Pool().BeginTx(ctx, pgx.TxOptions{})This library is designed primarily for read operations. While it uses pgconn.SafeToRetry() to avoid retrying queries that were already sent to the server, there are edge cases where a write operation may succeed on the server but the connection fails before the client receives confirmation.
Safe to use with:
SELECTqueries (always safe)- Idempotent writes:
UPDATE users SET name = 'value' WHERE id = 1 - Writes with unique constraints that prevent duplicates
Use caution with:
- Non-idempotent
INSERTstatements without unique constraints - Incremental updates:
UPDATE accounts SET balance = balance + 100 - Any operation where duplicate execution would cause incorrect results
For non-idempotent writes in critical paths, consider using the underlying pool directly via rp.Pool() or wrapping the operation in a transaction with appropriate isolation.
The library retries errors that are safe to retry without risking duplicate operations:
| Error Type | Examples |
|---|---|
| Connection errors | Connection refused, reset, broken pipe |
| Network timeouts | Dial timeout, I/O timeout |
| PostgreSQL 08xxx | Connection exception class |
| PostgreSQL 57Pxx | Admin shutdown, crash shutdown, cannot connect now |
| Safe-to-retry | Any error where pgconn.SafeToRetry() returns true |
Never retried:
- Context cancellation or deadline exceeded
- SQL syntax errors
- Constraint violations
- Any error after the query was sent to the server
- Exponential backoff: delay doubles each attempt (
baseDelay * 2^attempt) - Jitter: 25% random jitter to prevent thundering herd
- Maximum delay: Capped at 5 seconds
- Context-aware: Retries stop immediately if context is cancelled
Unlicense — public domain