Documentation
¶
Overview ¶
Package db provides Postgres connection bootstrapping and schema migration for the Wardyn control plane. Postgres is the ONLY required dependency.
Index ¶
- Constants
- func AuditDDLProtected(ctx context.Context, pool *pgxpool.Pool) (bool, error)
- func Connect(ctx context.Context, dsn string) (*pgxpool.Pool, error)
- func Migrate(ctx context.Context, pool *pgxpool.Pool) error
- func TryAdvisoryLock(ctx context.Context, pool *pgxpool.Pool, key int64) (release func(), ok bool, err error)
Constants ¶
const GroundTruthRotatorLockKey int64 = 0x5741524459_475452 // ASCII "WARDYGTR"
GroundTruthRotatorLockKey elects the ground-truth token rotator's leader across control planes (S2). Unlike ReaperAdvisoryLockKey (re-tried every tick via TryAdvisoryLock, release()d at the end of each one), this key is acquired ONCE before the rotator's loop starts and held for the process lifetime: only the holder mints/writes the shared token file, and every other replica parks on a backoff and retries, taking over automatically when the holder's Postgres session ends.
It buys AT MOST ONE STEADY-STATE leader, NOT mutual exclusion. An advisory lock dies with its SESSION, not with the process, and the holder never re-verifies it: a Postgres restart, a failover, pg_terminate_backend or an idle-session timeout releases it under a still-running leader, and a standby takes over within one backoff — two rotators, neither aware. Harmless for THIS workload only, because every write is an atomic rename of a stateless token (cmd/wardynd/gt_rotator.go). Work that needs genuine fencing must not reuse this key. Any stable value works, as long as it differs from every other key in this file.
const ReaperAdvisoryLockKey int64 = 0x5741524459_524541 // ASCII "WARDYREA"
ReaperAdvisoryLockKey makes the lifecycle reap tick single-flight across control planes. Unlike migrateAdvisoryLockKey (a BLOCKING lock — the second boot must still see the migrations applied), this one is only ever taken with TryAdvisoryLock: a replica that loses SKIPS the tick, because a queued second reap of the same runs is pure duplicate work and a duplicate run.autostop.
Variables ¶
This section is empty.
Functions ¶
func AuditDDLProtected ¶
AuditDDLProtected reports whether the given (application) pool's role is UNABLE to bypass the audit_events append-only triggers via DDL — i.e. it is neither a superuser nor a MEMBER of the table's owner role (membership, not just direct ownership: a role GRANTed the owner role inherits DROP TRIGGER / ALTER ... DISABLE TRIGGER rights). The N4 role-separation only protects the append-only guarantee when this is true, so the two-DSN deploy must be VERIFIED here rather than assumed (honesty: never log a protection claim stronger than the enforcing role setup). Fails safe: any ambiguity (missing table, error) reports NOT protected.
func Connect ¶
Connect opens a pgxpool to dsn and performs a lightweight liveness check. Returns the pool; caller owns Close().
func Migrate ¶
Migrate applies all migrations in internal/db/migrations/*.sql in lexical order. Each migration runs inside its own transaction; already-applied filenames (tracked in schema_migrations) are skipped. Idempotent.
func TryAdvisoryLock ¶ added in v0.5.0
func TryAdvisoryLock(ctx context.Context, pool *pgxpool.Pool, key int64) (release func(), ok bool, err error)
TryAdvisoryLock takes session-level advisory lock key on a connection borrowed from pool WITHOUT waiting, reporting ok=false when another session already holds it. Call the returned release (deferred) to unlock and hand the connection back — skipping it strands a pooled conn for the life of the process. Only for work of BOUNDED duration: the borrowed conn is unavailable to everyone else until release — which also means the caller's own queries need a SECOND conn, so this requires pool_max_conns >= 2 (a 1-conn pool would self-deadlock: the lock holds the only conn while the guarded work blocks on Acquire; the reaper's per-tick deadline turns that into a failed tick, not a hang, but the lock is still wasted).
Types ¶
This section is empty.