Skip to content

pgxpool wrapper with automatic retry for transient database errors

License

Notifications You must be signed in to change notification settings

tinybluerobots/retrypool

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

retrypool

A drop-in wrapper for pgxpool that adds automatic retry with exponential backoff for transient PostgreSQL errors.

Installation

go get github.com/tinybluerobots/retrypool

Usage

package 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)
    }
}

With sqlc

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)

Configuration

rp := retrypool.New(pool,
    retrypool.WithMaxRetries(5),              // Default: 3
    retrypool.WithBaseDelay(200*time.Millisecond), // Default: 100ms
)

Accessing the Underlying Pool

For operations that don't need retry (transactions, batch operations):

rp.Pool().BeginTx(ctx, pgx.TxOptions{})

⚠️ Write Query Safety

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:

  • SELECT queries (always safe)
  • Idempotent writes: UPDATE users SET name = 'value' WHERE id = 1
  • Writes with unique constraints that prevent duplicates

Use caution with:

  • Non-idempotent INSERT statements 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.

What Gets Retried

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

Retry Strategy

  • 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

License

Unlicense — public domain

About

pgxpool wrapper with automatic retry for transient database errors

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages