eventcore-postgres
PostgreSQL adapter for EventCore - production-ready event store with ACID guarantees.
Features
- Multi-stream atomicity via PostgreSQL transactions
- Optimistic concurrency control with version checking
- Type-safe serialization - your events, not JSON blobs
- Connection pooling with sqlx
- Automatic retries on transient failures
- Concurrent schema initialization with advisory locks
Installation
[]
= "0.1"
= "0.1"
Quick Start
use ;
use CommandExecutor;
use Duration;
// Configure with basic settings
let config = new;
// Or use the builder for advanced configuration
let config = new
.database_url
.max_connections
.min_connections
.connect_timeout
.idle_timeout
.max_lifetime
.build;
// Initialize
let store = new.await?;
store.initialize.await?; // Creates schema (safe to call multiple times)
// Use with commands
let executor = new;
Configuration
Connection Pool Options
PostgresConfig provides comprehensive connection pool configuration:
max_connections(default: 20) - Maximum connections in the poolmin_connections(default: 2) - Minimum idle connections to maintainconnect_timeout(default: 10s) - Connection acquisition timeoutidle_timeout(default: 600s) - How long connections can remain idlemax_lifetime(default: 1800s) - Maximum lifetime of a connectionquery_timeout(default: 30s) - Timeout for individual queriestest_before_acquire(default: false) - Test connections before use
Code Configuration
use ;
use Duration;
// Basic configuration
let config = new;
// Advanced configuration with builder
let config = new
.database_url
.max_connections
.min_connections
.connect_timeout
.idle_timeout
.max_lifetime
.query_timeout
.test_before_acquire // Skip for better performance
.build;
// Performance-optimized preset
let config = new
.database_url
.performance_optimized // Applies optimized settings
.build;
Environment Variables
DATABASE_URL=postgres://localhost/eventcore
# Note: Connection pool settings are configured in code, not via environment variables
Schema
EventCore uses two tables with optimized indexes:
-- Event streams with version tracking
(
stream_id VARCHAR(255) PRIMARY KEY,
version BIGINT NOT NULL DEFAULT 0,
updated_at TIMESTAMPTZ DEFAULT NOW
);
-- Events with efficient ordering
(
event_id UUID PRIMARY KEY,
stream_id VARCHAR(255) NOT NULL REFERENCES event_streams(stream_id),
event_type VARCHAR(255) NOT NULL,
event_data JSONB NOT NULL,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW
);
-- Indexes for performance
(stream_id, created_at);
(created_at);
Production Considerations
Connection Pool Sizing
// For high-throughput systems
let config = new
.database_url
.max_connections // Rule of thumb: 2x CPU cores
.min_connections // Keep connections warm
.idle_timeout // 5 minutes
.max_lifetime // 1 hour
.build;
// For bursty workloads
let config = new
.database_url
.max_connections // Higher max for bursts
.min_connections // Lower minimum
.connect_timeout // Fast timeout
.idle_timeout // Aggressive cleanup
.build;
// For long-running operations
let config = new
.database_url
.max_connections
.query_timeout // 5 minute queries
.max_lifetime // No lifetime limit
.build;
Transaction Isolation
EventCore uses SERIALIZABLE isolation for multi-stream writes, ensuring:
- No phantom reads
- No write skew
- Full ACID compliance
Performance Tuning
-- Increase shared_buffers for better caching
ALTER SYSTEM SET shared_buffers = '256MB';
-- Enable parallel queries
ALTER SYSTEM SET max_parallel_workers_per_gather = 4;
-- Tune for SSDs
ALTER SYSTEM SET random_page_cost = 1.1;
For information about how EventCore leverages PostgreSQL prepared statements for optimal performance, see Prepared Statement Performance.
Monitoring
The adapter provides comprehensive connection pool monitoring:
// Get current pool metrics
let metrics = store.get_pool_metrics;
println!;
println!;
println!;
// Start background monitoring
let = store.start_pool_monitoring;
// Access detailed health information
let health = store.health_check.await?;
println!;
println!;
// Enable detailed tracing
fmt
.with_env_filter
.init;
Key metrics to monitor:
current_connections- Active connections in poolidle_connections- Available connectionsutilization_percent- Pool usage (0-100%)connection_timeouts- Failed connection attemptsavg_acquisition_time- Time to get a connectionpeak_connections- Historical maximum
Testing
For integration tests, use Docker:
# docker-compose.yml
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: eventcore_test
POSTGRES_PASSWORD: postgres
ports:
- "5433:5432"
async
Migration from Other Event Stores
From EventStoreDB
// EventCore handles projections differently
// Instead of catch-up subscriptions, use:
let projection = new;
let runner = new;
runner.run_continuous.await?;
From Axon
// No more aggregate classes!
// Commands handle their own state:
Troubleshooting
"Version conflict" errors
This is optimistic concurrency control working correctly. EventCore will automatically retry with exponential backoff.
"Connection pool timeout"
This indicates all connections are busy. Solutions:
// 1. Increase pool size
let config = new
.database_url
.max_connections // Increase from default 20
.connect_timeout // Give more time
.build;
// 2. Check pool health
let metrics = store.get_pool_metrics;
if metrics.utilization_percent > 80.0
// 3. Enable connection recovery
let config = PostgresConfig ;
Schema initialization hangs
Another process might be initializing. This is normal - EventCore uses advisory locks to prevent race conditions.
See Also
- Connection Tuning Guide - Detailed connection pool tuning
- EventCore Core - Core library documentation
- Examples - Complete applications
- Memory Adapter - For testing