Shared, multi-node Ankusa.WAL backed by Postgres. This is the adapter that
turns "many ingest servers" into a real fleet: every BEAM node runs its own
local Postgrex connection pool (registered under the same
Ankusa.via(instance, :wal) name DiskLog would use), and coordination between
nodes happens entirely through the shared database — no BEAM distribution,
no RPC between edge nodes, matching the framework's existing rule that
components hand off through durable state, never through each other.
Group commit, translated to SQL
append/2 runs inside one Postgrex.transaction/2 (still exactly one
COMMIT, i.e. one fsync, per batch — matching the core invariant):
- Claim dedup keys. A single
INSERT ... ON CONFLICT DO NOTHINGagainstankusa_wal_dedup, batched viaunnest/1. Postgres takes a row lock on the conflicting index entry and blocks until the other writer's transaction resolves, so two nodes racing the same dedup key never double-claim it — the loser reliably sees the winner's committed row afterward. - Insert winners. Rows that either had no dedup key or won their
claim are inserted into
ankusa_wal(again batched viaunnest/1),RETURNING event_id, seq. A record whose dedup key collided is never written here — the same "duplicate absorbed, nothing extra stored" contract asWAL.DiskLog. - Resolve losers' seq. For rows that lost their dedup claim, one
lookup joins
ankusa_wal_dedupback toankusa_walbyevent_idto find the seq of the row that already owns that key.
Every row is correlated by the envelope's own id (a UUIDv7, always unique
per envelope regardless of dedup key), never by array position — positional
matching against RETURNING is not guaranteed to preserve input order.
Config
config :ankusa,
wal: {Ankusa.WAL.Postgres, hostname: "localhost", port: 5433,
username: "ankusa", password: "ankusa", database: "ankusa_dev",
pool_size: 10}Any Postgrex.start_link/1 option is accepted and passed through verbatim
(:migrate false skips the DDL bootstrap, e.g. if you run migrations
separately in CI).