| Copyright | (c) 2024 |
|---|---|
| License | BSD3 |
| Maintainer | maintainer@example.com |
| Stability | experimental |
| Safe Haskell | None |
| Language | GHC2021 |
Hindsight.Store.Filesystem
Description
Overview
File-based event store persisting events as JSON on disk. Provides durability without requiring a database. Suitable for single-node deployments with moderate event volumes.
Events stored in append-only events.log file. Multi-process support via file locking
and fsnotify change detection.
Quick Start
import Hindsight.Store.Filesystem main :: IO () main = do -- Create store with default config config <- mkDefaultConfig "./events" store <- newFilesystemStore config -- 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 cleanupFilesystemStore store
Configuration
FilesystemStoreConfig has three parameters:
storePath- Directory for event log and lock filesyncInterval- Disk sync frequency (microseconds, 0 = sync every write)lockTimeout- Max time to wait for file lock (microseconds)
Use mkDefaultConfig for sensible defaults or construct manually for custom settings.
Use Cases
When to use Filesystem store:
- Single-node applications requiring durability
- Development/staging environments
- Embedded systems or edge deployments
- Apps that can't run PostgreSQL (resource constraints, deployment complexity)
- Multi-process applications on same host
When NOT to use Filesystem store:
- Distributed multi-node systems (use PostgreSQL)
- Very high event throughput (PostgreSQL performs better)
- Large event volumes (startup replay becomes slow)
Trade-offs
Advantages:
- Events survive process restarts (durable)
- No database dependency
- Multi-process support on same host
- Simple deployment (just a directory)
- ACID guarantees via file locking
Limitations:
- Startup time grows with event count (linear log replay)
- All indices must fit in memory
- Single-node only (no distributed support)
- Performance limited by disk I/O
- Not suitable for very large datasets
Implementation
Persistence layer over Memory store infrastructure. Events written to disk then loaded into in-memory STM structures. File locking serializes writes. fsnotify detects changes from other processes for incremental reloading.
Storage format: Append-only JSON log (events.log), one transaction per line.
Stream indices rebuilt on startup by replaying log.
Synopsis
- data FilesystemStore
- data FilesystemStoreHandle
- newtype FilesystemCursor = FilesystemCursor {}
- data FilesystemStoreConfig = FilesystemStoreConfig {
- storePath :: FilePath
- syncInterval :: Int
- lockTimeout :: Int
- mkDefaultConfig :: FilePath -> FilesystemStoreConfig
- getStoreConfig :: FilesystemStoreHandle -> FilesystemStoreConfig
- newFilesystemStore :: FilesystemStoreConfig -> IO FilesystemStoreHandle
- cleanupFilesystemStore :: FilesystemStoreHandle -> IO ()
- data StoreException
- data EventLogEntry = EventLogEntry {
- transactionId :: UUID
- events :: [StoredEvent]
- timestamp :: UTCTime
- data StorePaths = StorePaths {}
- getPaths :: FilePath -> StorePaths
Store Types
data FilesystemStore Source #
Store type marker
Instances
data FilesystemStoreHandle Source #
Handle for filesystem store operations.
newtype FilesystemCursor Source #
Cursor for filesystem store.
Constructors
| FilesystemCursor | |
Fields
| |
Instances
| FromJSON FilesystemCursor Source # | |||||
Defined in Hindsight.Store.Filesystem Methods parseJSON :: Value -> Parser FilesystemCursor # parseJSONList :: Value -> Parser [FilesystemCursor] # | |||||
| ToJSON FilesystemCursor Source # | |||||
Defined in Hindsight.Store.Filesystem Methods toJSON :: FilesystemCursor -> Value # toEncoding :: FilesystemCursor -> Encoding # toJSONList :: [FilesystemCursor] -> Value # toEncodingList :: [FilesystemCursor] -> Encoding # omitField :: FilesystemCursor -> Bool # | |||||
| Generic FilesystemCursor Source # | |||||
Defined in Hindsight.Store.Filesystem Associated Types
Methods from :: FilesystemCursor -> Rep FilesystemCursor x # to :: Rep FilesystemCursor x -> FilesystemCursor # | |||||
| Show FilesystemCursor Source # | |||||
Defined in Hindsight.Store.Filesystem Methods showsPrec :: Int -> FilesystemCursor -> ShowS # show :: FilesystemCursor -> String # showList :: [FilesystemCursor] -> ShowS # | |||||
| Eq FilesystemCursor Source # | |||||
Defined in Hindsight.Store.Filesystem Methods (==) :: FilesystemCursor -> FilesystemCursor -> Bool # (/=) :: FilesystemCursor -> FilesystemCursor -> Bool # | |||||
| Ord FilesystemCursor Source # | |||||
Defined in Hindsight.Store.Filesystem Methods compare :: FilesystemCursor -> FilesystemCursor -> Ordering # (<) :: FilesystemCursor -> FilesystemCursor -> Bool # (<=) :: FilesystemCursor -> FilesystemCursor -> Bool # (>) :: FilesystemCursor -> FilesystemCursor -> Bool # (>=) :: FilesystemCursor -> FilesystemCursor -> Bool # max :: FilesystemCursor -> FilesystemCursor -> FilesystemCursor # min :: FilesystemCursor -> FilesystemCursor -> FilesystemCursor # | |||||
| type Rep FilesystemCursor Source # | |||||
Defined in Hindsight.Store.Filesystem type Rep FilesystemCursor = D1 ('MetaData "FilesystemCursor" "Hindsight.Store.Filesystem" "hindsight-filesystem-store-0.1.0.0-inplace" 'True) (C1 ('MetaCons "FilesystemCursor" 'PrefixI 'True) (S1 ('MetaSel ('Just "getSequenceNo") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Integer))) | |||||
Configuration
data FilesystemStoreConfig Source #
Configuration for filesystem store
Constructors
| FilesystemStoreConfig | |
Fields
| |
Instances
| FromJSON FilesystemStoreConfig Source # | |||||
Defined in Hindsight.Store.Filesystem Methods parseJSON :: Value -> Parser FilesystemStoreConfig # parseJSONList :: Value -> Parser [FilesystemStoreConfig] # | |||||
| ToJSON FilesystemStoreConfig Source # | |||||
Defined in Hindsight.Store.Filesystem Methods toJSON :: FilesystemStoreConfig -> Value # toEncoding :: FilesystemStoreConfig -> Encoding # toJSONList :: [FilesystemStoreConfig] -> Value # toEncodingList :: [FilesystemStoreConfig] -> Encoding # omitField :: FilesystemStoreConfig -> Bool # | |||||
| Generic FilesystemStoreConfig Source # | |||||
Defined in Hindsight.Store.Filesystem Associated Types
Methods from :: FilesystemStoreConfig -> Rep FilesystemStoreConfig x # to :: Rep FilesystemStoreConfig x -> FilesystemStoreConfig # | |||||
| Show FilesystemStoreConfig Source # | |||||
Defined in Hindsight.Store.Filesystem Methods showsPrec :: Int -> FilesystemStoreConfig -> ShowS # show :: FilesystemStoreConfig -> String # showList :: [FilesystemStoreConfig] -> ShowS # | |||||
| Eq FilesystemStoreConfig Source # | |||||
Defined in Hindsight.Store.Filesystem Methods (==) :: FilesystemStoreConfig -> FilesystemStoreConfig -> Bool # (/=) :: FilesystemStoreConfig -> FilesystemStoreConfig -> Bool # | |||||
| type Rep FilesystemStoreConfig Source # | |||||
Defined in Hindsight.Store.Filesystem type Rep FilesystemStoreConfig = D1 ('MetaData "FilesystemStoreConfig" "Hindsight.Store.Filesystem" "hindsight-filesystem-store-0.1.0.0-inplace" 'False) (C1 ('MetaCons "FilesystemStoreConfig" 'PrefixI 'True) (S1 ('MetaSel ('Just "storePath") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 FilePath) :*: (S1 ('MetaSel ('Just "syncInterval") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 Int) :*: S1 ('MetaSel ('Just "lockTimeout") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 Int)))) | |||||
Arguments
| :: FilePath | Base directory for store files |
| -> FilesystemStoreConfig | Configuration with defaults |
Create a default configuration for a filesystem store.
Defaults: sync every write, 5-second lock timeout.
Arguments
| :: FilesystemStoreHandle | Store handle |
| -> FilesystemStoreConfig | Store configuration |
Get the configuration from a store handle.
Useful for accessing the store path during cleanup or creating additional instances.
Store Operations
Arguments
| :: FilesystemStoreConfig | Store configuration |
| -> IO FilesystemStoreHandle | Initialized store handle |
Initialize or open a filesystem store.
Creates the store directory and event log file if they don't exist. If the event log exists and contains events, automatically reloads them into memory. Starts file watchers for cross-process event notifications.
This function handles both fresh stores and reopening existing stores, making it suitable for process restarts and multi-instance deployments.
cleanupFilesystemStore Source #
Arguments
| :: FilesystemStoreHandle | Store handle to clean up |
| -> IO () |
Cleanup store resources and shut down background threads.
Stops file watchers and removes the lock file. Call this before application shutdown to ensure clean termination.
Exceptions
data StoreException Source #
Custom exceptions
Constructors
| LockTimeout FilePath | |
| CorruptEventLog FilePath String |
Instances
| Exception StoreException Source # | |
Defined in Hindsight.Store.Filesystem Methods toException :: StoreException -> SomeException # fromException :: SomeException -> Maybe StoreException # displayException :: StoreException -> String # backtraceDesired :: StoreException -> Bool # | |
| Show StoreException Source # | |
Defined in Hindsight.Store.Filesystem Methods showsPrec :: Int -> StoreException -> ShowS # show :: StoreException -> String # showList :: [StoreException] -> ShowS # | |
Testing Support
data EventLogEntry Source #
An entry in our event log represents a transaction state change.
Constructors
| EventLogEntry | |
Fields
| |
Instances
| FromJSON EventLogEntry Source # | |||||
Defined in Hindsight.Store.Filesystem Methods parseJSON :: Value -> Parser EventLogEntry # parseJSONList :: Value -> Parser [EventLogEntry] # | |||||
| ToJSON EventLogEntry Source # | |||||
Defined in Hindsight.Store.Filesystem Methods toJSON :: EventLogEntry -> Value # toEncoding :: EventLogEntry -> Encoding # toJSONList :: [EventLogEntry] -> Value # toEncodingList :: [EventLogEntry] -> Encoding # omitField :: EventLogEntry -> Bool # | |||||
| Generic EventLogEntry Source # | |||||
Defined in Hindsight.Store.Filesystem Associated Types
| |||||
| Show EventLogEntry Source # | |||||
Defined in Hindsight.Store.Filesystem Methods showsPrec :: Int -> EventLogEntry -> ShowS # show :: EventLogEntry -> String # showList :: [EventLogEntry] -> ShowS # | |||||
| Eq EventLogEntry Source # | |||||
Defined in Hindsight.Store.Filesystem Methods (==) :: EventLogEntry -> EventLogEntry -> Bool # (/=) :: EventLogEntry -> EventLogEntry -> Bool # | |||||
| type Rep EventLogEntry Source # | |||||
Defined in Hindsight.Store.Filesystem type Rep EventLogEntry = D1 ('MetaData "EventLogEntry" "Hindsight.Store.Filesystem" "hindsight-filesystem-store-0.1.0.0-inplace" 'False) (C1 ('MetaCons "EventLogEntry" 'PrefixI 'True) (S1 ('MetaSel ('Just "transactionId") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 UUID) :*: (S1 ('MetaSel ('Just "events") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 [StoredEvent]) :*: S1 ('MetaSel ('Just "timestamp") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict) (Rec0 UTCTime)))) | |||||
data StorePaths Source #
File paths used by the store.
Constructors
| StorePaths | |
Fields
| |
Arguments
| :: FilePath | Base directory for the store |
| -> StorePaths | Computed file paths |
Compute file paths for store files within a base directory.