pgtx

Transaction management for Go + PostgreSQL, built on
pgx/v5 and
avito-tech/go-transaction-manager v2.
A transaction whose context ends is rolled back and its connection returned
to the pool (v1 left it acquired forever).
A Trm manager owns transaction boundaries; a context-aware DB executor
transparently routes queries to the active transaction (or the pool when there
is none), so repositories stay unaware of whether they run inside a
transaction. Generic Do* helpers wrap a unit of work in a chosen isolation
level and return its result.
pgtx/ NewTxManager / NewTxDB constructors (single pgtx.go)
pkg/tx/ Trm, DB, executor, settings, Do* helpers, tx concurrency guard
contrib/otel/ pgxpool → OpenTelemetry metrics (its own go.mod)
Install
go get github.com/gopherex/pgtx
Usage
pool, _ := pgxpool.New(ctx, dsn)
mgr, err := pgtx.NewTxManager(pool, tx.Serializable())
if err != nil {
return err
}
// context-aware executor: uses the in-context transaction when present
db := pgtx.NewTxDB(trmpgx.DefaultCtxGetter)
// run a unit of work in a transaction, get a typed result back
user, err := tx.DoSerializableRet(ctx, mgr, func(ctx context.Context) (User, error) {
return repo.FindAndUpdate(ctx, db, id)
})
Isolation-level helpers: tx.Serializable, tx.RepeatableRead,
tx.ReadCommitted, tx.ReadUncommitted, plus the Do* / Do*Ret runners.
Transaction concurrency guard
pgx.Tx does not allow two statements on one transaction at the same time:
a goroutine spawned with the transaction context (errgroup and friends)
would otherwise hit a busy-conn or protocol error that does not name the
cause. Every transaction begun by NewTxManager is therefore guarded:
while a call still holds the transaction — its Rows are not drained or
closed, its Row is not scanned, its BatchResults are not closed — every
other call routed into it, at any savepoint level, is refused with
tx.ErrTxInUse before reaching pgx. A refusal never damages the
transaction: it stays usable. Sequential code, nested savepoints and the
pool path outside transactions are untouched — pool access stays fully
concurrent, the guard costs ~15 ns per statement.
OpenTelemetry metrics
The contrib/otel module exposes pgxpool statistics as OpenTelemetry
metrics. It is a separate module so the core stays free of the OTel dependency:
go get github.com/gopherex/pgtx/contrib/otel
import pgtxotel "github.com/gopherex/pgtx/contrib/otel"
if err := pgtxotel.RegisterMetrics(pool); err != nil {
return err
}
License
MIT — see LICENSE.