Inspiration

Every software product begins with a database schema. Before the first line of application code is written, someone draws boxes representing tables, arrows representing relationships, and column names representing the contract the entire team will build against for months — sometimes years.

Today, that critical design session happens in one of three broken ways: one engineer owns the schema and shares a screenshot in Slack; teams use Notion docs and text tables that don't validate types or generate SQL; or they use a tool like dbdiagram.io where only one person can drive at a time.

We are a distributed team. Our backend engineer is in Bengaluru. Our database person is reviewing schema PRs at midnight. The "one person shares their screen" approach wasn't just inconvenient — it was structurally broken. Schema decisions made asynchronously drift apart from implementation the moment the call ends.

The gap isn't just a UX problem. It's an infrastructure problem. When two engineers in different regions simultaneously design a shared schema, the backend storing that schema must guarantee they both see the same state. Traditional databases either force a primary-region write bottleneck, or tolerate eventual consistency — which means two users can briefly see different schemas. For a design tool where every entity name is a contract, that's catastrophic.

Then Aurora DSQL reached general availability. The first serverless distributed SQL database with genuine multi-region active-active writes and synchronous consistency at commit time. We realized: this isn't just a database we could use for the project. It's the only database that makes the project's core guarantee possible.

That's VersionZero.


What it does

VersionZero is a real-time collaborative database schema designer — think Figma for database schemas — where multiple engineers simultaneously design tables, define columns, draw relationships, and export production-ready PostgreSQL SQL.

Here's what happens when you open a session:

  • Live multi-user canvas: Multiple engineers see each other's colored cursors moving in real time. You can add a users table while a colleague in a different timezone is adding columns to orders. No turn-taking. No locking.
  • Schema entity editor: Create tables with properly typed columns — TEXT, INTEGER, UUID, TIMESTAMPTZ, BOOLEAN, NUMERIC. Mark primary keys. Toggle nullability. Drag cards to reposition on the canvas.
  • Relationship drawer: Connect tables visually to define one-to-many, many-to-many, and one-to-one relationships. Relationships are first-class objects in the data model, rendered as SVG lines on the canvas behind the entity cards.
  • Conflict resolution toasts: When two users edit the same entity simultaneously, Aurora DSQL's Optimistic Concurrency Control detects the conflict at commit time. One write succeeds. The other retries automatically. The UI shows a non-blocking amber toast: "Edit conflict detected — DSQL resolved it automatically." The schema is always consistent. This is not an error. It is the architecture working as designed.
  • SQL export: One button generates valid PostgreSQL CREATE TABLE statements from the live DSQL schema state, displayed in a syntax-highlighted modal with a copy button. The design becomes executable SQL. This is the proof of shippability.
  • Audit trail (change log): Every schema change is logged atomically — in the same DSQL transaction as the mutation itself — capturing who changed what, when, and what the before-state was. Accessible as a session history panel. This is enterprise-ready compliance by construction.
  • Live presence: Avatar dots and colored cursors show who is in the session right now. When a user closes the tab, they vanish automatically within 30 seconds — zero application code required, courtesy of DynamoDB's TTL.
  • Session sharing: Share a URL. Teammates join immediately. No account required to view; authenticate with Google or magic link to edit.

How we built it

The two-database thesis

The most important architectural decision in VersionZero isn't a library choice or a framework preference. It's recognizing that the application handles two fundamentally different categories of data with different consistency requirements — and choosing a different database for each.

Schema state → Aurora DSQL (strong consistency required)

Tables, columns, relationships, sessions, audit logs. When engineer A creates a table named users, every other user must see it immediately and with certainty. This requires: strong global consistency, multi-region durable writes, OCC conflict detection, and atomic audit trails.

-- Aurora DSQL schema (6 normalized tables)
CREATE TABLE design_sessions (
  id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name       TEXT NOT NULL,
  owner_id   UUID NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE schema_entities (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  session_id  UUID NOT NULL,  -- denormalized for efficient session-scoped queries
  entity_name TEXT NOT NULL,
  position_x  NUMERIC(10,2) NOT NULL DEFAULT 100,
  position_y  NUMERIC(10,2) NOT NULL DEFAULT 100,
  color_tag   TEXT NOT NULL DEFAULT 'blue',
  created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- + entity_attributes, entity_relationships, change_log, session_members

Presence state → Amazon DynamoDB (eventual consistency is correct)

Cursor positions, who's online, which entity is being hovered. These are updated every 3 seconds per user. Seeing a cursor 200ms out of date is fine. Auto-expiry when a user disconnects is essential. High write throughput matters.

Table: vz_presence
PK: session_id (String)
SK: user_id (String)
TTL: ttl (Number) — Unix timestamp: NOW + 30 seconds

DynamoDB's TTL attribute handles disconnected users vanishing automatically. DSQL has no equivalent. This is the right tool for exactly this purpose.

The OCC retry pattern — the technical centrepiece

Aurora DSQL uses Optimistic Concurrency Control. No locks are held during transaction execution. At commit time, DSQL checks whether any concurrent transaction modified the same rows. If yes: one succeeds, the other receives error code 40001 (serialization_failure) and must retry.

Every mutation route in VersionZero wraps its DSQL writes in withDSQLRetry():

// lib/dsql.ts
export async function withDSQLRetry<T>(
  fn: (client: PoolClient) => Promise<T>,
  maxRetries = 3
): Promise<T> {
  let attempt = 0;

  while (attempt < maxRetries) {
    const client = await pool.connect();
    try {
      await client.query('BEGIN');
      const result = await fn(client);
      await client.query('COMMIT');
      return result;

    } catch (err: any) {
      await client.query('ROLLBACK');

      if (err.code === '40001' && attempt < maxRetries - 1) {
        attempt++;
        await new Promise(r => setTimeout(r, 50 * attempt)); // 50ms, 100ms, 150ms
        continue;
      }
      throw err;

    } finally {
      client.release();
    }
  }
  throw new Error(`DSQL: max retries (${maxRetries}) exceeded`);
}

Every mutation — create table, rename column, add relationship, delete entity — runs inside this wrapper. The schema mutation and its change_log audit entry are written in the same transaction. Either both succeed or both rollback. The audit trail is atomic by construction.

Stack

Layer Technology Reasoning
Frontend + deployment Next.js 15 (App Router) on Vercel Native Vercel integration; Server Actions keep DB credentials server-side
Auth Clerk Magic link + Google OAuth; one env var to get userId for audit trail
Schema DB Aurora DSQL (multi-region: us-east-1 + us-west-2) Strong consistency for schema state; OCC conflict detection
Presence DB Amazon DynamoDB TTL auto-expiry; high-frequency writes; eventual consistency is correct
DSQL driver node-postgres (pg) PostgreSQL wire protocol; TLS enforced (DSQL requires it)
DynamoDB client @aws-sdk/client-dynamodb + @aws-sdk/lib-dynamodb Standard AWS SDK v3
Canvas Plain React + absolute positioning + SVG Deliberately avoided canvas libraries — fully debuggable, zero framework debt

Polling runs every 3 seconds for both schema state and presence. Vercel serverless functions don't support persistent connections, and 3-second convergence is sufficient for the collaboration guarantee. We implement optimistic UI updates — changes apply to local React state immediately, then commit to DSQL async. Conflicts revert the optimistic state and show the toast.


Challenges we ran into

Aurora DSQL is not PostgreSQL — and that's the point, but it surprises you.

DSQL does not support REFERENCES (foreign key constraints). It does not support JSONB. It does not support SERIAL/BIGSERIAL, triggers, views, or PL/pgSQL. Every one of these limitations exists because of the distributed architecture — foreign key constraints cannot be enforced globally without coordination that would undermine the OCC model. Understanding why each constraint exists forced us to design our schema correctly for a distributed environment, not just translate a PostgreSQL schema.

We enforce referential integrity at the API layer. We use TEXT with JSON.stringify/JSON.parse instead of JSONB. We use gen_random_uuid() instead of sequences — UUIDs are safer in distributed environments where two regions could generate the same integer sequence value.

Making OCC conflicts reliably demoable.

OCC conflicts require exact timing to trigger — two concurrent writes to the same rows at the same millisecond. In practice, most schema edits target different entities and never conflict. We built a controlled demo scenario: two browser windows, one editing a column while the other renames it, timed with a countdown. We recorded 5 takes and used the cleanest one. The conflict toast appearing at exactly the right moment is the 10-second clip that explains everything about DSQL.

Keeping the canvas performant without a library.

We deliberately avoided React Flow, Konva, and any canvas library. They add 200KB+ of dependency and interaction models we couldn't debug under hackathon time pressure. The canvas is plain React: entity cards are absolutely-positioned div elements, relationships are SVG lines rendered behind them on an inset-0 SVG layer with pointer-events-none. It's 40 lines of code that performs well and looks clean in a demo video.

The presence auto-expiry edge case.

DynamoDB's TTL doesn't delete entries immediately at expiry — it deletes within approximately 15 minutes. For presence, this meant a disconnected user's cursor could persist on screen for up to 15 minutes. We solved this client-side: the presence query filters out any entry where ttl < Date.now() / 1000 before rendering cursor positions. DynamoDB cleans up eventually; the UI is correct immediately.


Accomplishments that we're proud of

The dual-database architecture is principled, not accidental.

We didn't use two databases because we ran out of features in one. We used two databases because the application has two categories of data with different consistency requirements, and the correct database for each category is different. This is a distributed systems design pattern — the consistency model split — visible in the architecture diagram, the code comments, and the live demo.

The OCC retry wrapper is production-grade.

withDSQLRetry() correctly handles exponential backoff, client pool release in all code paths, and differentiates serialization failures (retryable) from all other errors (re-thrown). It is not boilerplate. It is the pattern DSQL expects, implemented correctly.

The audit trail is atomic.

Every schema mutation and its change_log entry are written in the same DSQL transaction. This is a subtle but critical design decision. An audit trail that writes in a separate transaction can miss changes if the process crashes between the mutation commit and the log write. Ours cannot.

The SQL export generates valid, executable PostgreSQL.

We tested every possible column type combination against a local PostgreSQL instance. The CREATE TABLE statements VersionZero generates are not illustrative. They run.

The self-referential premise is real, not a pitch.

VersionZero is a database schema designer. Its own schema — design_sessions, schema_entities, entity_attributes, entity_relationships, change_log, session_members — is stored in Aurora DSQL. The same database you might choose after designing your schema here. This isn't wordplay. The schema in the DSQL cluster is the schema you see in the plan.


What we learned

Consistency model selection is product design.

Before this project, database choice was infrastructure. After it, database choice is product architecture. Choosing DSQL wasn't about performance or cost. It was about what guarantee we could make to users — "every user in this session sees the same schema, always" — and then choosing the only database that makes that guarantee possible at global scale without operational overhead.

Optimistic concurrency is better than pessimistic locking for collaborative tools.

Traditional databases use pessimistic locking: when you begin editing a row, the DB locks it. Other writers wait. This prevents conflicts but creates deadlocks and kills throughput. DSQL's OCC runs every transaction against a consistent snapshot with no locks held. At commit time, if there's a conflict, one succeeds and one retries. In a collaborative tool, this model is strictly better — high-frequency concurrent edits to different entities always succeed without coordination. Only edits to the same entity at the same instant conflict, and conflicts resolve in under 100ms.

DynamoDB TTL is underrated.

The 30-second presence auto-expiry is zero lines of application code. No cleanup cron, no heartbeat endpoint that tracks last-seen and marks users offline. A PUT with a TTL attribute, and DynamoDB handles the rest. For ephemeral, high-frequency, low-stakes data, this pattern is elegant.

Ship the diagram on Day 1.

The architecture diagram is both a Stage 1 submission requirement and a forcing function for architectural clarity. Building it on Day 1 — before writing API routes — revealed ambiguities in the data flow that we would have discovered later as bugs. Draw the diagram first. Code second.


What's next for VersionZero

WebSocket presence. The 3-second polling model works and was the right call for the hackathon timeline. Real-time cursor tracking requires sub-100ms updates. The next version replaces polling with WebSocket connections via a separate presence service.

Schema versioning and migration diffs. The change_log table is an append-only audit trail with before-state snapshots. The next version adds a migration diff engine: given two change_log snapshots, generate the ALTER TABLE statements to migrate between them. This turns VersionZero from a design tool into a full schema lifecycle tool.

DynamoDB and MongoDB schema support. The current SQL export targets PostgreSQL. Future export modes would generate DynamoDB table specs (PK, SK, GSI definitions) and MongoDB collection schemas (JSON Schema validators). Schema design is not a relational-only problem.

AI schema suggestions. Given a natural language description of a product ("an e-commerce platform for handmade goods with sellers, buyers, and reviews"), VersionZero generates a normalized starting schema on the canvas. The user refines it collaboratively.

Enterprise tier. SSO via SAML/OIDC, dedicated DSQL cluster per organization, role-based access control beyond owner/editor/viewer, and SLA-backed uptime.

The free tier covers hobby teams and open-source projects. The Team tier ($29/month per organization) matches the exact price point of the closest competitor, DrawSQL — validating the market exists and the pricing is proven. The enterprise tier is the long-term business.

VersionZero is the first tool in your database workflow. The schema you design here becomes the SQL you run. Every other tool in the stack builds on top of it.

Built With

Share this project:

Updates