retrypool

package module
v0.0.0-...-c1ac856 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 30, 2026 License: Unlicense Imports: 10 Imported by: 0

README

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

Documentation

Overview

Package retrypool wraps pgxpool.Pool with automatic retry for transient errors.

Index

Constants

View Source
const (
	DefaultMaxRetries = 3
	DefaultBaseDelay  = 100 * time.Millisecond
)

Default retry configuration.

Variables

This section is empty.

Functions

func IsTransient

func IsTransient(err error) bool

IsTransient returns true if the error is safe to retry. Uses pgconn.SafeToRetry() plus additional network error checks.

Types

type Option

type Option func(*Pool)

Option configures a Pool.

func WithBaseDelay

func WithBaseDelay(d time.Duration) Option

WithBaseDelay sets the base delay for exponential backoff.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets the maximum number of retry attempts.

type Pool

type Pool struct {
	// contains filtered or unexported fields
}

Pool wraps *pgxpool.Pool with retry for transient errors. Implements the DBTX interface (Exec, Query, QueryRow) expected by sqlc-generated code.

func New

func New(pool *pgxpool.Pool, opts ...Option) *Pool

New wraps a pgxpool.Pool with default retry settings (3 attempts, 100ms base delay).

func (*Pool) Close

func (p *Pool) Close()

Close closes the underlying pool.

func (*Pool) Exec

func (p *Pool) Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)

Exec executes a query with retry.

func (*Pool) Ping

func (p *Pool) Ping(ctx context.Context) error

Ping verifies a connection to the database is still alive.

func (*Pool) Pool

func (p *Pool) Pool() *pgxpool.Pool

Pool returns the underlying *pgxpool.Pool for operations that don't need retry.

func (*Pool) Query

func (p *Pool) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)

Query executes a query returning rows, with retry.

func (*Pool) QueryRow

func (p *Pool) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row

QueryRow executes a query returning a single row. Returns a retryRow that retries on transient Scan errors.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL