| Copyright | (c) 2024 |
|---|---|
| License | BSD3 |
| Maintainer | maintainer@example.com |
| Stability | experimental |
| Safe Haskell | None |
| Language | GHC2021 |
Hindsight.Store.PostgreSQL
Contents
Description
Overview
PostgreSQL-backed event store providing full ACID guarantees, scalability, and production-ready durability. Supports both synchronous and asynchronous projections.
Recommended for production systems, distributed deployments, and applications requiring strong consistency guarantees.
Quick Start
import Hindsight.Store.PostgreSQL main :: IO () main = do -- Create store let connStr = "postgresql:/localhostevents" store <- newSQLStore connStr -- Initialize schema (run once) Pool.use (getPool store) createSQLSchema -- Insert events (see Hindsight.Store for details) streamId <- StreamId <$> UUID.nextRandom let event = mkEvent MyEvent myData result <- insertEvents store Nothing $ singleEvent streamId NoStream event -- Subscribe to events handle <- subscribe store matcher (EventSelector AllStreams FromBeginning) -- ... process events ... -- Cleanup when done shutdownSQLStore store
Configuration
Connection via PostgreSQL connection string. Pool size defaults to 300 connections.
For custom configuration, use newSQLStoreWithProjections to register synchronous projections
that execute within event insertion transactions.
Use Cases
When to use PostgreSQL store:
- Production systems requiring durability and ACID guarantees
- Distributed multi-node deployments
- High event throughput scenarios
- Large event volumes (millions+ events)
- Applications needing advanced SQL querying
- Systems requiring synchronous projections (strong consistency)
When NOT to use PostgreSQL store:
- Simple testing (use Memory store)
- Single-node apps without database (use Filesystem store)
- Environments where PostgreSQL can't be deployed
- Prototypes and development (unless testing PostgreSQL-specific features)
Trade-offs
Advantages:
- Full ACID guarantees (PostgreSQL transactions)
- Scales to millions of events
- Distributed multi-node support
- Advanced querying via SQL
- Synchronous projections (consistency within single transaction)
- LISTEN/NOTIFY for efficient subscriptions
- Battle-tested PostgreSQL reliability
Limitations:
- Requires PostgreSQL database
- More complex deployment than Memory/Filesystem
- Higher resource requirements
- Network latency for remote databases
Sync vs Async Projections
Synchronous Projections: Execute within event insertion transaction. Changes to events
and projection state are atomic. Use registerSyncProjection and newSQLStoreWithProjections.
Guarantees strong consistency but adds latency to writes.
Asynchronous Projections: Run separately using runProjection from hindsight-postgresql-projections.
Process events after they're committed. Better write performance but eventual consistency.
Implementation
Events stored in events table with compound key (transaction_no, seq_no) for total ordering.
Stream metadata in stream_heads table. Projection state in projections table.
LISTEN/NOTIFY used for efficient subscription updates.
See Schema for complete schema DDL.
Synopsis
- newSQLStore :: ByteString -> IO SQLStoreHandle
- newSQLStoreWithProjections :: ByteString -> SyncProjectionRegistry -> IO SQLStoreHandle
- shutdownSQLStore :: SQLStoreHandle -> IO ()
- data SQLStoreHandle
- getPool :: SQLStoreHandle -> Pool
- getConnectionString :: SQLStoreHandle -> ByteString
- data SQLStore
- data SQLCursor = SQLCursor {
- transactionNo :: !Int64
- sequenceNo :: !Int32
- data SyncProjectionRegistry
- emptySyncProjectionRegistry :: SyncProjectionRegistry
- registerSyncProjection :: forall (ts :: [Symbol]). ProjectionId -> ProjectionHandlers ts SQLStore -> SyncProjectionRegistry -> SyncProjectionRegistry
- insertEventsWithSyncProjections :: forall (t :: Type -> Type) m. (Traversable t, MonadUnliftIO m) => SQLStoreHandle -> SyncProjectionRegistry -> Maybe CorrelationId -> Transaction t SQLStore -> m (InsertionResult SQLStore)
- createSQLSchema :: Session ()
- module Hindsight.Store
Documentation
newSQLStore :: ByteString -> IO SQLStoreHandle Source #
Create a PostgreSQL event store with default configuration.
Uses a connection pool size of 10 and an empty sync projection registry.
newSQLStoreWithProjections :: ByteString -> SyncProjectionRegistry -> IO SQLStoreHandle Source #
Create a PostgreSQL event store with custom sync projections.
Sync projections run within the same transaction as event insertion, providing guaranteed consistency between events and their projections.
On startup, this function will: 1. Register all sync projections in the database 2. Catch up any projections that are behind 3. Fail with an exception if catch-up fails
shutdownSQLStore :: SQLStoreHandle -> IO () Source #
Shutdown the SQL store gracefully
This function should be called before releasing the connection pool. It shuts down the event dispatcher used by all subscriptions.
data SQLStoreHandle Source #
Handle for interacting with PostgreSQL event store.
getPool :: SQLStoreHandle -> Pool Source #
Get the connection pool from a store handle.
Useful for running custom queries or projections that need direct database access.
getConnectionString :: SQLStoreHandle -> ByteString Source #
Get the connection string from a store handle.
Useful for creating additional store instances with the same configuration.
Phantom type representing the PostgreSQL storage backend.
Instances
Position cursor for PostgreSQL event store.
Uses a compound key of (transactionNo, sequenceNo) to provide total ordering across all events. The Ord instance uses lexicographical ordering, so events are ordered first by transaction number, then by sequence within transaction.
Constructors
| SQLCursor | |
Fields
| |
Instances
| FromJSON SQLCursor Source # | |||||
Defined in Hindsight.Store.PostgreSQL.Core.Types | |||||
| ToJSON SQLCursor Source # | |||||
| Generic SQLCursor Source # | |||||
Defined in Hindsight.Store.PostgreSQL.Core.Types Associated Types
| |||||
| Show SQLCursor Source # | |||||
| Eq SQLCursor Source # | |||||
| Ord SQLCursor Source # | |||||
Defined in Hindsight.Store.PostgreSQL.Core.Types | |||||
| type Rep SQLCursor Source # | |||||
Defined in Hindsight.Store.PostgreSQL.Core.Types type Rep SQLCursor = D1 ('MetaData "SQLCursor" "Hindsight.Store.PostgreSQL.Core.Types" "hindsight-postgresql-store-0.1.0.0-inplace" 'False) (C1 ('MetaCons "SQLCursor" 'PrefixI 'True) (S1 ('MetaSel ('Just "transactionNo") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Int64) :*: S1 ('MetaSel ('Just "sequenceNo") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Int32))) | |||||
data SyncProjectionRegistry Source #
Registry of synchronous projections
emptySyncProjectionRegistry :: SyncProjectionRegistry Source #
Create an empty registry
registerSyncProjection :: forall (ts :: [Symbol]). ProjectionId -> ProjectionHandlers ts SQLStore -> SyncProjectionRegistry -> SyncProjectionRegistry Source #
Register a synchronous projection
insertEventsWithSyncProjections Source #
Arguments
| :: forall (t :: Type -> Type) m. (Traversable t, MonadUnliftIO m) | |
| => SQLStoreHandle | Store handle with connection pool |
| -> SyncProjectionRegistry | Registry of sync projections to execute |
| -> Maybe CorrelationId | Optional correlation ID for event tracking |
| -> Transaction t SQLStore | Transaction containing events to insert |
| -> m (InsertionResult SQLStore) | Result indicating success or failure |
Insert events with synchronous projections using a custom registry.
Events and projections are committed atomically in a single PostgreSQL transaction. If any projection fails, the entire transaction is rolled back, ensuring consistency.
This function allows you to override the sync projection registry from
the handle. In most cases, you should use the generic insertEvents
function instead, which automatically uses the registry from the handle.
This function is useful for testing or scenarios where you need to use a different registry than the one configured in the handle.
createSQLSchema :: Session () Source #
Re-export createSchema with a more specific name to avoid conflicts
module Hindsight.Store
Orphan instances
| EventStore SQLStore Source # | PostgreSQL This instance automatically uses the sync projections configured in the handle. All insertEvents calls will execute the sync projections that were registered when the store was created. | ||||
Associated Types
Methods insertEvents :: forall (t :: Type -> Type) m. (Traversable t, StoreConstraints SQLStore m) => BackendHandle SQLStore -> Maybe CorrelationId -> Transaction t SQLStore -> m (InsertionResult SQLStore) # subscribe :: forall m (ts :: [Symbol]). StoreConstraints SQLStore m => BackendHandle SQLStore -> EventMatcher ts SQLStore m -> EventSelector SQLStore -> m (SubscriptionHandle SQLStore) # | |||||