BlogPricing
PostgreSQL

Event sourcing database schema

An event sourcing schema: aggregates, an append-only event_log partitioned by stream, snapshots for replay performance, and projection state for fast reads.

Start for free
Database schema previewEvent sourcing database schema
Recreate this in Raltey

Copy this prompt

Paste this into Raltey's AI agent to generate the same diagram, then tweak it on the canvas.

Prompt
Design a PostgreSQL database schema for: Event sourcing database schema.

An event sourcing schema: aggregates, an append-only event_log partitioned by stream, snapshots for replay performance, and projection state for fast reads.

Include these tables: aggregate, event_log, snapshot, projection_offset.

Use proper primary keys, foreign keys, and indexes. Export SQL DDL for PostgreSQL.

Context

  • - Single ordered event_log per stream (one row per event).
  • - Snapshots periodically materialise an aggregate to reduce replay cost.
  • - Read-side projections live in their own tables, kept in sync by a worker.

Patterns

  • - event_log primary key (stream_id, version) enforces ordering.
  • - Snapshots every N events shorten replay.
  • - Projections track their own offset for retry safety.
Tables

The schema, table by table

aggregate

DBML - aggregate
Table aggregate {
  id   uuid pk
  type text not null   // order, account, ...
  created_at timestamptz default `now()`
}

event_log

Append-only ordered event log per aggregate.

DBML - event_log
Table event_log {
  stream_id    uuid [ref: > aggregate.id, not null]
  version      int not null
  event_type   text not null
  payload      jsonb not null
  recorded_at  timestamptz default `now()`
  pk (stream_id, version)
}

snapshot

DBML - snapshot
Table snapshot {
  stream_id    uuid [ref: > aggregate.id]
  version      int not null
  state        jsonb not null
  taken_at     timestamptz default `now()`
  pk (stream_id, version)
}

projection_offset

Per-projection cursor into the event log.

DBML - projection_offset
Table projection_offset {
  projection_name text pk
  last_position   bigint not null
  updated_at      timestamptz default `now()`
}

Relationships

FromToType
aggregateevent_log1:N
aggregatesnapshot1:N

Notes from the field

Frequently asked

How often should I snapshot?

A common rule is every 100-1000 events per aggregate, but tune to replay budget. Snapshot if replaying the tail takes more than ~50 ms.

How do I rebuild a projection?

Reset projection_offset.last_position to 0 (or a known checkpoint) and let the worker re-consume from there.

More templates

Open this template in Raltey

Edit it on the canvas, ask the AI agent to extend it, then share or export.

Start for free