Summary
PostgreSQL serves as a capable event store for moderate event volumes, leveraging JSONB payloads, UNIQUE constraints for optimistic concurrency, and WAL-based replication as a natural CDC feed for downstream projections.
Evidence
- ·UNIQUE(aggregate_id, sequence_number) enforces optimistic concurrency natively via INSERT conflict
- ·JSONB payload column supports schema-flexible event payloads without table migrations
- ·Logical replication (wal2json) can stream the events table to downstream consumers as a CDC feed
- ·Point-in-time recovery and PITR snapshots provide event log durability guarantees
- ·PostgreSQL's ACID transactions enable atomic writes across the events table and outbox in one transaction
Operational Context
- ·Index on (aggregate_id, sequence_number) is required for efficient aggregate reconstruction
- ·Index on (aggregate_type, occurred_at) enables time-range projection queries across aggregates
- ·At >50K events/second, the events table becomes a write bottleneck: Kafka is appropriate at that scale
Tradeoffs
- ·Single-table events at high insert rates creates WAL pressure and index bloat
- ·Replaying a single aggregate requires filtering by aggregate_id: efficient with the right index but not a log seek like Kafka
- ·Connection pool saturation is a risk when many projection consumers open long-lived connections
Evidence grounding
Grounded, 5 supporting itemsPostgreSQL's JSONB, transactional writes, and UNIQUE constraint on (aggregate_id, sequence_number) satisfy event store requirements. Used in production at multiple SaaS companies for event-sourced domains at <50K events/second.