Documentation
¶
Overview ¶
Package postgres is a PostgreSQL-backed idempotency.Store for the BabelQueue Go runtime: a shared, persistent record of processed message ids so a fleet of consumers dedupes on the envelope's meta.id (ADR-0022) instead of every worker keeping its own in-memory set.
store, _ := postgres.New(ctx, "postgres://user:pass@localhost:5432/app?sslmode=disable") defer store.Close() app.Handle(urn, idempotency.Wrap(store, handler))
It satisfies the dependency-free idempotency.Store interface from the core (Seen / Remember / Forget) with the SAME contract the reference in-memory store guarantees, and adds atomic-claim semantics on top so concurrent consumers serialize:
- Remember is an ATOMIC claim — INSERT ... ON CONFLICT DO NOTHING. Exactly one concurrent caller inserts the row (and is the "winner"); a duplicate delivery hits the conflict and is a no-op. This is race-safe under any number of concurrent workers and connections (the unique primary key is the lock).
- Claim exposes that race result directly: it returns true iff THIS caller won the insert (first delivery), false if the row already existed (duplicate / in-flight "parked" delivery). Remember delegates to Claim and discards the bool, so the idempotency.Store contract is preserved exactly.
- Seen reports whether a live (non-expired) row exists.
- TTL/expiry is honored in SQL: an expired row is treated as absent by Seen and re-claimable by Claim/Remember (an expired key may be re-processed once its window lapses, the documented at-least-once behaviour).
The GR-7 zero-dependency core stays dependency-free: this driver lives in its own submodule (its own go.mod) and is the only place a database driver is pulled in. It talks to Postgres through the standard library database/sql over the pgx stdlib driver, so any *sql.DB wired to a Postgres-compatible database also works via NewWithDB.
Index ¶
- Constants
- type Option
- type Store
- func (s *Store) Claim(ctx context.Context, messageID string) (bool, error)
- func (s *Store) Close() error
- func (s *Store) Forget(ctx context.Context, messageID string) error
- func (s *Store) Migrate(ctx context.Context) error
- func (s *Store) Remember(ctx context.Context, messageID string) error
- func (s *Store) Seen(ctx context.Context, messageID string) (bool, error)
Constants ¶
const DefaultTable = "babelqueue_idempotency"
DefaultTable is the table name used when WithTable is not given.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Option ¶
type Option func(*Store)
Option customizes a Store.
func WithTTL ¶
WithTTL sets how long a remembered id stays "seen" before it expires and may be re-processed. Zero (the default) means ids never expire on their own — evict with Forget or an external job. A negative TTL is treated as zero.
func WithTable ¶
WithTable overrides the table name (default DefaultTable). The name is validated as a plain SQL identifier — it is interpolated into DDL/DML, never a query parameter, so it must not come from untrusted input.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a PostgreSQL-backed idempotency.Store. Construct it with New (owns the pool) or NewWithDB (bring your own *sql.DB).
func New ¶
New opens a pgx-backed connection pool to dsn (a postgres:// URL or a key=value DSN) and returns a Store. The caller owns the lifetime: call Store.Close to release the pool. It pings once so a bad DSN fails fast.
func NewWithDB ¶
NewWithDB wraps an existing *sql.DB (bring your own pool / driver / config). The caller keeps ownership of db; Store.Close is a no-op for a borrowed pool.
func (*Store) Claim ¶
Claim atomically records messageID and reports whether THIS call won the race — true on the first delivery (the row was inserted), false when the id was already claimed by a live (non-expired) row (a duplicate / in-flight "parked" delivery).
The race-safety comes from INSERT ... ON CONFLICT DO NOTHING on the primary key: under any number of concurrent consumers exactly one INSERT inserts a row and reports a claim; the rest hit the conflict and report a duplicate. An expired row is reclaimed in the same statement (DO UPDATE when the existing row has lapsed), so a claim succeeds again once the TTL window passes.
This is the atomic primitive [Remember] is built on; the idempotency.Store interface does not expose it, so it is available only on a *Store.
func (*Store) Close ¶
Close releases the connection pool when this Store opened it (New). For a borrowed pool (NewWithDB) it is a no-op, leaving the caller's *sql.DB intact.
func (*Store) Forget ¶
Forget drops messageID from the store (manual eviction; the idempotency.Store contract). Removing an absent id is a no-op.
func (*Store) Migrate ¶
Migrate creates the idempotency table if it does not exist. It is idempotent and safe to run on every boot; production deployments may instead apply the shipped DDL (see schema.sql) through their own migration tool.
func (*Store) Remember ¶
Remember records messageID as processed (atomic claim; the idempotency.Store write side). It delegates to Store.Claim and discards whether this caller won the race — the seen-set contract only needs the id to be durable afterwards.