# Eventium SQLite
SQLite-based event store implementation for embedded and single-process applications.
## Overview
`eventium-sqlite` provides a lightweight, file-based event store implementation using SQLite. It's perfect for single-process applications, embedded systems, mobile apps, and scenarios where you want persistent storage without the complexity of a database server.
## Features
- ✅ **Zero Configuration** - No database server required
- ✅ **File-Based Storage** - Single database file for easy backup
- ✅ **ACID Transactions** - Full consistency guarantees from SQLite
- ✅ **Optimistic Concurrency** - Version-based conflict detection
- ✅ **Type-Safe Access** - Uses Persistent library
- ✅ **Cross-Platform** - Works on Linux, macOS, Windows
- ✅ **Embedded-Friendly** - Low resource footprint
- ✅ **Easy Deployment** - No separate database process
## When to Use SQLite
### ✅ Good Fit
- **Desktop Applications** - Local data storage
- **CLI Tools** - Persistent command-line applications
- **Mobile Apps** - Embedded event storage
- **Development/Testing** - Persistent data without server setup
- **Single-Process Systems** - No concurrent process access needed
- **Edge Computing** - Resource-constrained environments
### ⚠️ Consider Alternatives
- **Multi-Process Systems** - Use `eventium-postgresql` instead
- **High Write Concurrency** - PostgreSQL handles concurrent writes better
- **Distributed Systems** - Need a client-server database
- **Very Large Datasets** - PostgreSQL scales better for TB+ data
## Installation
Add to your `package.yaml`:
```yaml
dependencies:
- eventium-core
- eventium-sql-common
- eventium-sqlite
- persistent-sqlite # SQLite driver
```
## Usage
```haskell
import Eventium.Store.Sqlite
import Database.Persist.Sqlite
main :: IO ()
main = do
-- Use file-based storage
withSqlitePool "events.db" 1 $ \pool -> do
-- Initialize schema
flip runSqlPool pool $ do
runMigration migrateAll
-- Create event store
let store = makeSqliteEventStore pool
-- Use with command handlers
result <- applyCommandHandler
(eventStoreWriter store)
(eventStoreReader store)
commandHandler
aggregateId
command
```
## Database Location
### File-Based Storage
```haskell
-- Relative path
withSqlitePool "events.db" 1 $ \pool -> ...
-- Absolute path
withSqlitePool "/var/lib/myapp/events.db" 1 $ \pool -> ...
-- User-specific location
home <- getHomeDirectory
let dbPath = home </> ".myapp" </> "events.db"
withSqlitePool dbPath 1 $ \pool -> ...
```
### In-Memory Storage (Testing)
```haskell
-- Temporary in-memory database
withSqlitePool ":memory:" 1 $ \pool -> ...
```
## Configuration
### Connection Pool
SQLite works best with a single connection per process:
```haskell
-- Recommended for SQLite
withSqlitePool "events.db" 1 $ \pool -> ...
```
### WAL Mode (Recommended)
Enable Write-Ahead Logging for better concurrent read performance:
```haskell
withSqlitePool "events.db" 1 $ \pool -> do
flip runSqlPool pool $ do
rawExecute "PRAGMA journal_mode=WAL;" []
runMigration migrateAll
```
Benefits:
- Readers don't block writers
- Better performance for read-heavy workloads
- Safer concurrent access
## Performance
Typical SQLite event store performance:
- **Writes**: ~1000-3000 events/sec
- **Reads**: ~5000-20000 events/sec
- **Storage**: ~1KB per event (JSON serialized)
### Optimization Tips
1. **Use WAL Mode** - Better concurrent access
2. **Batch Writes** - Multiple events per transaction
3. **Index Strategy** - Default indexes cover common queries
4. **VACUUM Regularly** - Reclaim space from deleted data
5. **Synchronous Mode** - Balance durability vs speed
## Backup & Recovery
### Simple File Copy
```bash
# Stop application or ensure no writes
cp events.db events.db.backup
# Or use SQLite backup command
sqlite3 events.db ".backup events.db.backup"
```
### Continuous Backup
```bash
# With WAL mode, backup while app runs
sqlite3 events.db ".backup events.db.backup"
```
## Migration from In-Memory
```haskell
-- Development: in-memory
development :: IO ()
development = withSqlitePool ":memory:" 1 $ \pool -> ...
-- Production: file-based
production :: IO ()
production = withSqlitePool "events.db" 1 $ \pool -> ...
```
## Example: Complete CLI Application
```haskell
import Eventium.Store.Sqlite
import System.Directory (getAppUserDataDirectory)
main :: IO ()
main = do
-- Store in application data directory
dataDir <- getAppUserDataDirectory "myapp"
createDirectoryIfMissing True dataDir
let dbPath = dataDir </> "events.db"
withSqlitePool dbPath 1 $ \pool -> do
-- Initialize on first run
flip runSqlPool pool $ do
rawExecute "PRAGMA journal_mode=WAL;" []
runMigration migrateAll
-- Run application
runApp pool
```
## Tools
### SQLite CLI
```bash
# Open database
sqlite3 events.db
# Inspect schema
.schema
# Query events
SELECT * FROM events ORDER BY version DESC LIMIT 10;
# Check database size
.dbinfo
```
## Limitations
- **Single Writer** - Only one process should write at a time
- **File Locking** - May have issues on network filesystems
- **Database Size** - Practical limit around 100GB-1TB
- **Concurrent Writes** - Limited compared to PostgreSQL
## Documentation
- [Main README](../README.md) - Project overview
- [SQL Common](../eventium-sql-common/) - Shared SQL utilities
- [Design Documentation](../DESIGN.md) - Architecture details
- [Examples](../examples/) - Bank and cafe examples use SQLite
## License
MIT - see [LICENSE.md](LICENSE.md)