Idempotent Event Ingestion & Replay System
EventRail is an event ingestion and delivery system designed to safely ingest, persist, deliver, retry, and replay events under real-world failure conditions. It provides idempotency guarantees, durable storage, fan-out delivery, retry with backoff, dead-letter queues, and replay/backfill capabilities using a clean, API-first design.
Think of EventRail as a simplified, developer-friendly alternative to internal event pipelines.
- Docker Image
- Architecture Overview
- Features
- Tech Stack
- Getting Started
- Usage
- Configuration
- Design Guarantees
- License
EventRail is published as a Docker image via GitHub Container Registry.
docker pull ghcr.io/krishgondaliya/eventrail:latestClient
│
│ POST /events (Idempotency-Key)
▼
API (Go)
│
│ Durable write
▼
PostgreSQL ◄── source of truth
│
│ Publish (best-effort)
▼
Redis Streams
│
│ Consumer Groups
▼
Workers
│
├──► Retry (ZSET + backoff)
│
└──► Dead-Letter Queue
| Principle | Description |
|---|---|
| Durability first | DB commit before delivery |
| Idempotency at the boundary | Safe duplicate handling |
| At-least-once delivery | With exactly-once ingestion |
| Replayable by design | Full historical recovery |
- API-first REST interface
- Database-enforced idempotency using
Idempotency-Key - Safe under retries, crashes, and concurrent requests
- PostgreSQL as the authoritative event store
- Indexed for time-range queries and replay
- Redis Streams for append-only delivery
- Consumer groups for fan-out
- Per-consumer offsets tracked by Redis
- Automatic retries with exponential backoff
- Configurable retry limits
- Dead-letter queue for permanently failed events
- Replay events by time range
- Reset consumer offsets to reprocess history
- No downtime required
| Component | Technology |
|---|---|
| Language | Go |
| API | net/http (REST) |
| Database | PostgreSQL |
| Streaming | Redis Streams |
| Containerization | Docker & Docker Compose |
Make sure you have:
- Docker
- Docker Compose
- Git
No local Go or Postgres installation required.
git clone https://github.com/krishgondaliya/eventrail-ingestion.git
cd eventrail-ingestiondocker compose up --buildYou should see logs like:
EventRail API starting on :8080
stream worker started (group=eventrail.cg consumer=api-1)
curl http://localhost:8080/healthExpected response:
{
"status": "ok",
"postgres": "ok",
"redis": "ok"
}Invoke-RestMethod `
-Method Post `
-Uri http://localhost:8080/events `
-Headers @{ "Idempotency-Key" = "example-123" } `
-ContentType "application/json" `
-Body '{
"event_type": "user.created",
"source": "auth-service",
"payload": { "user_id": 42 }
}'Retrying this request with the same
Idempotency-Keywill return the same event ID without creating duplicates.
Invoke-RestMethod `
-Method Get `
-Uri http://localhost:8080/events/<EVENT_ID>Re-publish historical events from Postgres into Redis Streams:
$from = (Get-Date).AddDays(-1).ToString("o")
$to = (Get-Date).ToString("o")
Invoke-RestMethod `
-Method Post `
-Uri http://localhost:8080/replay `
-ContentType "application/json" `
-Body "{ `"from`": `"$from`", `"to`": `"$to`", `"limit`": 1000 }"Force consumers to reprocess events from the beginning:
Invoke-RestMethod `
-Method Post `
-Uri http://localhost:8080/consumer-groups/set-cursor `
-ContentType "application/json" `
-Body '{ "start_id": "0" }'docker exec -it eventrail-redis redis-cli
XRANGE eventrail.events.dlq - +| Variable | Description | Default |
|---|---|---|
CONSUMER_NAME |
Worker name | api-1 |
MAX_RETRIES |
Max delivery retries | 5 |
BASE_BACKOFF_MS |
Initial retry delay (ms) | 500 |
- Exactly-once ingestion
- At-least-once delivery
- Deterministic behavior under retries
- Safe recovery after crashes
- Replay without downtime
Every failure mode is explicitly handled.
MIT