Compendium.Adapters.PostgreSQL
PostgreSQL-backed event store and projection store for Compendium. Designed for high-throughput SaaS workloads with proper connection pooling and bulk-insert support.
Install
dotnet add package Compendium.Adapters.PostgreSQL
You also need a PostgreSQL 14+ instance reachable from your service.
Configuration
{
"Compendium": {
"EventStore": {
"ConnectionString": "Host=localhost;Database=compendium;Username=app;Password=secret",
"AutoCreateSchema": false,
"BatchSize": 1000
}
}
}
builder.Services.Configure<PostgreSqlOptions>(
builder.Configuration.GetSection(PostgreSqlOptions.SectionName));
Options (PostgreSqlOptions):
| Option | Default | Description |
|---|---|---|
ConnectionString |
required | Standard Npgsql connection string |
MaxPoolSize |
200 |
Application-level concurrency cap (SemaphoreSlim) |
CommandTimeout |
60s |
Single-query timeout |
TableName |
event_store |
Event store table |
AutoCreateSchema |
false |
Whether to auto-create tables on startup (dev only) |
BatchSize |
1000 |
Bulk-write batch size |
MinimumPoolSize |
50 |
Npgsql min pool — prewarmed connections |
MaximumPoolSize |
200 |
Npgsql max pool (must be ≤ Postgres max_connections) |
ConnectionIdleLifetime |
900s |
Idle connection close timeout |
ConnectionLifetime |
3600s |
Hard connection recycle |
ConnectionTimeout |
30s |
Pool wait timeout |
Keepalive |
30s |
TCP keepalive (0 to disable) |
EnablePooling |
true |
Disable only for debugging |
Usage
Once registered, the event store is available via the IEventStore port (from Compendium.Abstractions):
public sealed class PlaceOrderHandler(IEventStore eventStore)
: ICommandHandler<PlaceOrderCommand>
{
public async Task<Result> Handle(PlaceOrderCommand cmd, CancellationToken ct)
{
var orderResult = OrderAggregate.Create(cmd.CustomerId, cmd.Amount);
if (orderResult.IsFailure) return orderResult.Error;
await eventStore.AppendAsync(
orderResult.Value.Id.ToString(),
orderResult.Value.DomainEvents,
expectedVersion: 0,
ct);
return Result.Success();
}
}
For schema bootstrapping in a non-dev environment, run the migrations under tests/Integration/.../Database/ as a reference, or write your own migrations.
Gotchas
AutoCreateSchemais false by default — on purpose. In production you want migrations under version control, not implicit DDL on startup.MaximumPoolSizemust be smaller than the server'smax_connections. A microservice deployed in 10 replicas withMaximumPoolSize: 200is asking for 2000 connections; many Postgres deployments cap at 100–200.BatchSize: 1000is tuned for Postgres on SSD. On constrained hardware (small RDS), drop it to 200–500.- Connection idle lifetime under proxies. If you front Postgres with PgBouncer in transaction mode, keep
ConnectionIdleLifetimeshorter than the proxy's idle timeout to avoid stale-handle errors.