DEV Community

Cover image for Building Sovereign: A Dual-Database Creator Liquidity Exchange on AWS Aurora DSQL + DynamoDB
Aman
Aman

Posted on • Edited on

Building Sovereign: A Dual-Database Creator Liquidity Exchange on AWS Aurora DSQL + DynamoDB

Digital creators hold billions of dollars in illiquid assets - from newsletters and YouTube channels to SaaS repositories and Notion templates. How do you fractionalize ownership with instant, atomic trading settlements and absolute data integrity under heavy concurrent load?

Enter Sovereign: a real-time Creator Liquidity Exchange built on a dual-database architecture - AWS Aurora DSQL for transactional correctness and Amazon DynamoDB for high-throughput telemetry.


The Problem: Distributed Contention on the Order Book

When multiple buyers attempt to match against the same resting sell orders concurrently, they generate a high-contention race condition:

  • The Over-Allocation Threat: If two buy transactions read the same sell order's remaining balance simultaneously, both believe they can buy it. This creates phantom shares - double spending where total ledger allocation exceeds asset supply.
  • The Latency Trap: Solving this with distributed locks slows execution significantly across regions, raising latency to hundreds of milliseconds.

The Solution: Optimistic Concurrency Control with DSQL

Instead of locking rows, Sovereign uses SERIALIZABLE isolation on Aurora DSQL:

  • Every matching event executes in a single database transaction. The engine reads counter-orders, computes fill quantities, and updates buyer and seller ledger rows atomically.
  • If another transaction modified the order concurrently, DSQL throws a 40001 serialization conflict. The engine catches it, applies jittered exponential backoff, and retries:
const isOCCCollision = (err: unknown): boolean => {
  if (err instanceof Error) {
    const cause = (err as any).cause;
    return (
      cause?.code === "40001" ||
      cause?.message?.includes("serialization failure")
    );
  }
  return false;
};

// Retry loop
while (attempt <= MAX_RETRIES) {
  try {
    const result = await attemptMatch(orderId);
    return result;
  } catch (err) {
    if (isOCCCollision(err)) {
      const delay = Math.pow(2, attempt) * 10 + Math.random() * 50;
      await sleep(delay);
      attempt++;
      continue;
    }
    throw err;
  }
}
Enter fullscreen mode Exit fullscreen mode

Dual-Database Architecture

The most important architectural decision: two databases, each doing what it's best at.

Aurora DSQL (Stockholm, eu-north-1) - Transactional Core

  • Orders, trades, ownership ledger, assets
  • Requires SERIALIZABLE isolation and global consistency
  • OCC collision resolution IS the product mechanic
  • Flat schema, no FK constraints (DSQL active-active requirement)
  • CREATE INDEX ASYNC (synchronous indexes not supported on distributed clusters)

Amazon DynamoDB (eu-north-1) - Activity Firehose

  • Trade events, OCC collision logs, stampede telemetry
  • Single-table design with 3 GSIs
  • 30-day TTL auto-expiry
  • PAY_PER_REQUEST billing
  • No transactions needed - pure append-only throughput DSQL for correctness. DynamoDB for throughput.

Every trade fires a fire-and-forget write to DynamoDB:

// After successful DSQL match - never blocks critical path
writeFirehose({
  eventType: "TRADE",
  assetId: order.asset_id,
  payload: { sharesTraded, priceExecuted, latencyMs },
  timestamp: new Date().toISOString(),
}).catch(() => {}); // silent fail - DynamoDB is best-effort
Enter fullscreen mode Exit fullscreen mode

System Architecture Flow

              ┌───────────────────────────────┐
              │      Client Web App (UI)      │
              └───────────────┬───────────────┘
                              │ HTTPS / SSE
                              ▼
              ┌───────────────────────────────┐
              │     Next.js Server Actions    │
              └───────┬───────────────┬───────┘
                      │               │
  Serializable Tx     │               │ Async Telemetry Write
 (Postgres pg client) │               │ (AWS SDK PutCommand)
                      ▼               ▼
              ┌───────────────┐┌───────────────┐
              │  OCC Engine   ││  DynamoDB     │
              │  (Aurora DSQL)││  Firehose     │
              └───────────────┘└───────────────┘
Enter fullscreen mode Exit fullscreen mode

Stress Testing: The Stampede Load Simulator

To prove integrity under load, we built a stampede simulator:

  1. Seed 100 sell orders from market-maker
  2. Fire 100 concurrent buy orders simultaneously
  3. DSQL generates serialization failures - engine retries with jitter
  4. Audit: SUM(shares_owned) must equal total_shares exactly

Recent Run Results:

  • Settled: 100/100 orders
  • OCC Collisions Resolved: 432
  • Time elapsed: 1,450ms
  • Balance Delta: 0
  • Audit Verdict: ✅ PASSED - ZERO OVER-ALLOCATION

The mathematical proof: if two buyers both got share #47, delta would be nonzero. Delta is always 0. DSQL OCC makes this impossible.


Key Implementation Challenges

  • Drizzle wraps native errors: Drizzle puts the real Postgres error in err.cause, not err directly. Our retry loop initially missed all 40001 codes. Fix: inspect err.cause?.code.
  • DSQL DDL restrictions: No SERIAL types, no synchronous indexes. Required a custom migration runner using CREATE INDEX ASYNC.
  • Vercel timeout: 300 concurrent orders to Stockholm DSQL exceeded Vercel's 60s function timeout. Restructured to 3 batches of 20 with timeout escape hatch.

Live Demo & Code

  • Live Demo: sovereign-dex.vercel.app - click DEMO LOGIN, no signup needed.
  • Run Stampede: Hit RUN STAMPEDE in the telemetry console to see OCC collisions resolve live. Watch the DynamoDB activity feed populate on the market home page.
  • GitHub Repository: AmanM006/sovereign

I created this article for the H0: Hack the Zero Stack hackathon.

Top comments (0)