DEV Community

Saatwik kumar yadav
Saatwik kumar yadav

Posted on

Designing a strongly-consistent event exchange on Amazon Aurora DSQL

I created this article as part of my submission to the H0 Hackathon. #H0Hackathon

Every exchange — equities, prediction markets, ticketing — reduces to one hard problem: a correct, always-on, globally consistent ledger. Traditionally that demands three separate things before a single order is matched:

  1. a clearinghouse to guarantee trades,
  2. a distributed ledger to record them, and
  3. an infrastructure team for replication, failover, and reconciliation.

Parity is an experiment in collapsing all three into one primitive using Amazon Aurora DSQL — a serverless, distributed SQL database with strong consistency and optimistic concurrency. Orders match and money settles in the same transaction, with zero operational overhead.

The interesting part is the design.

Design with optimistic concurrency, not against it

Aurora DSQL uses optimistic concurrency control (OCC) with snapshot isolation: a transaction reads a consistent snapshot and commits only if no one else modified its rows. That makes it excellent when transactions touch distinct rows, and poor under hot-row contention.

A naïve exchange — one shared order-book row everyone mutates — is the worst case. Parity is built around the grain instead:

  • Matching is scoped per market. Each market is its own contention domain; unrelated markets never conflict.
  • Every fill is a double-entry across distinct rows — debit the buyer's account, credit the seller's, update both positions, append two ledger rows. Distinct counterparties are the OCC sweet spot.
  • Conflicts retry the whole transaction on SQLSTATE 40001.

The matching transaction

A fill moves cash from buyer to seller and a contract the other way, so cash is conserved on every trade. The retry wrapper is the backbone:

const SERIALIZATION_FAILURE = "40001";

export async function withTransaction<T>(fn: (c: PoolClient) => Promise<T>): Promise<T> {
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
    const client = await getPool().connect();
    try {
      await client.query("BEGIN");
      const result = await fn(client);
      await client.query("COMMIT");
      return result;
    } catch (err) {
      await client.query("ROLLBACK").catch(() => {});
      if ((err as { code?: string }).code !== SERIALIZATION_FAILURE) throw err; // a real error
      // OCC conflict → exponential backoff + full jitter, then retry the whole block
      await sleep(Math.random() * BASE_DELAY_MS * 2 ** (attempt - 1));
    } finally {
      client.release();
    }
  }
  throw new ConflictRetryExhausted();
}
Enter fullscreen mode Exit fullscreen mode

Keeping the transaction body short narrows the conflict window, which keeps retries rare.

No double-spend, enforced by the database

The debit is conditional:

UPDATE accounts
   SET balance = balance - $cost, updated_at = now()
 WHERE user_id = $buyer
   AND balance >= $cost;     -- 0 rows affected ⇒ insufficient funds, fill is skipped
Enter fullscreen mode Exit fullscreen mode

If it affects zero rows, the buyer can't fund the fill, so it doesn't happen. "No negative balance, no double-spend" becomes a property enforced by the database, not a convention. Under a race, two concurrent debits cannot both commit — one hits the OCC conflict and retries against the updated balance.

Settlement in one transaction

Resolving a market pays every winning contract 100¢ and losers 0¢ in one consistent transaction set — no overnight batch, no reconciliation pass. Because contracts are conserved (+qty to the buyer, −qty to the seller on every fill), total payout nets to exactly zero across all accounts.

Verifiable consistency

A stress test fires 200+ concurrent, conflicting trades across markets, then checks the books on the live cluster:

=== consistency proof ===
orders fired      : 216 concurrent across 3 markets
trade ledger sum  : 0      (must be 0)
cash before/after : equal  (conserved)
negative balances : 0      (must be 0)
PASS — strong consistency under contention, not eventual.
Enter fullscreen mode Exit fullscreen mode

Because every fill is a balanced double-entry, those invariants are queryable in real time. A /api/proof endpoint computes them directly from Aurora DSQL on each request:

{
  "backend": "Aurora DSQL",
  "trades": 23,
  "ledgerImbalance": 0,
  "negativeBalances": 0,
  "cashConserved": true,
  "consistent": true
}
Enter fullscreen mode Exit fullscreen mode

Working with DSQL's constraints

Aurora DSQL is PostgreSQL-compatible but not full Postgres. The schema is designed around it:

  • No sequences / SERIAL → UUID primary keys.
  • No foreign keys → relationships enforced in application logic.
  • Asynchronous index creationCREATE INDEX ASYNC.
  • IAM-token authentication, no static secrets → the database password is a short-lived token minted on demand by @aws-sdk/dsql-signer; on Vercel, OIDC federates to IAM.

DSQL's Postgres compatibility also enabled a dual-mode data layer: the identical application runs on local Postgres in development and Aurora DSQL in production with a single environment-variable change.

Takeaways

  • Schema design determines whether OCC is your best case or your worst case — an exchange is a precise test of that boundary.
  • Financial invariants can be enforced in the database rather than in application discipline.
  • "Strong consistency under contention" stops being a claim and becomes something you can verify with a single request.

Stack

Amazon Aurora DSQL · Next.js (App Router) on Vercel · TypeScript · node-postgres · @aws-sdk/dsql-signer · React Three Fiber.


I created this article as part of my submission to the H0 Hackathon. #H0Hackathon

Top comments (0)