A Redis-inspired distributed cache project built to learn real backend engineering: cache design, TTL semantics, HTTP APIs, persistence, hashing for sharding, and production packaging with Docker.
This repository is designed as a learning-to-production journey:
- Start with core cache operations (
GET,SET) and LRU eviction. - Add reliability features (TTL + save/load persistence).
- Add distribution primitives (consistent hashing + replica forwarding).
- Ship it like a real service using Docker and Compose.
- In-memory LRU cache with
O(1)average read/write operations - Per-key TTL support
- HTTP API server (
/get,/set,/save,/load) - Snapshot persistence to disk (
cache.json) - Consistent hash client for key-to-node routing
- Dockerized deployment (non-root container + healthcheck)
graph TD
Client[Client Layer<br>curl / app / SDK client] --> HTTP[HTTP Server<br>server.go]
HTTP --> HashRing[HashRing<br>consistent_hash.go]
HashRing -.->|Determine Owner| HTTP
HTTP --> Cache[Cache Facade<br>cache.go]
Cache --> LRU[LRU Core + TTL<br>lru.go]
Cache --> Persist[Persistence<br>persistence.go]
LRU -.->|list + map| LRU
Persist -.->|JSON save/load| Persist
sequenceDiagram
participant Client
participant Primary as Primary Node
participant Replica as Replica Node
Client->>Primary: SET /set?key=user&value=john
Primary->>Primary: Calculate HashRing Owner (Self)
Primary->>Primary: cache.SetWithTTL(key, value)
Primary-->>Client: 200 OK
Primary-)Replica: Async GET /set?key=user... (X-Is-Replica: true)
Replica->>Replica: Check Header (Is Replica)
Replica->>Replica: cache.SetWithTTL(key, value)
LRU is implemented using:
- Hash map: key -> list node pointer (fast lookup)
- Doubly linked list: recency order (fast move/eviction)
Complexity summary:
GET:O(1)averageSET:O(1)average- Eviction:
O(1)
| Endpoint | Method | Query Params | Purpose |
|---|---|---|---|
/ |
GET | - | Health/info text |
/set |
GET | key, value, optional ttl |
Write key/value |
/get |
GET | key |
Read value |
/save |
GET | optional file |
Snapshot to disk |
/load |
GET | optional file |
Restore snapshot |
Quick examples:
curl "http://localhost:8000/set?key=user&value=john"
curl "http://localhost:8000/get?key=user"
curl "http://localhost:8000/set?key=temp&value=123&ttl=60"
curl "http://localhost:8000/save?file=cache.json"
curl "http://localhost:8000/load?file=cache.json"Build image:
docker build -t distributed-cache:latest .Run container:
docker run --rm -p 8000:8000 distributed-cache:latestCompose:
docker compose up --buildProduction-minded container choices already applied:
- Non-root runtime user
- Healthcheck configured
- Restart policy in Compose (
unless-stopped) - Alpine 3.21 runtime base
From your running container screenshot:
- CPU usage: near
0%at idle - Memory usage: about
2.07 MB - Disk I/O: low and bursty (expected for light traffic)
- Network I/O: tiny, request-driven traffic
Interpretation:
- Current implementation is lightweight and efficient for small workloads.
- Real stress testing is still needed for throughput/latency confidence.
- Why list + map is the practical LRU pattern for
O(1)operations - TTL is not just storage, it affects read-path correctness and eviction behavior
- Persistence introduces state lifecycle concerns (startup, crash recovery)
- Consistent hashing simplifies horizontal scaling strategy
- Docker hardening matters even for small projects (user, healthchecks, base image hygiene)
Concurrency safety (Completed)
- Added synchronization (
sync.RWMutex) around shared cache state. - System is now fully hardened for concurrent writes.
- Better HTTP semantics
- Use
POSTfor writes (/set,/save,/load) instead ofGET. - Return consistent JSON response format with status/message fields.
- Distributed behavior maturity
- Add real node membership + dynamic replica list.
- Add retry/backoff and timeout handling for replica forwarding.
- Observability
- Add
/metricsendpoint (Prometheus format). - Add structured logging (request id, path, latency, status).
- Performance confidence
- Add benchmark scripts (
hey,wrk, or Go benchmarks). - Track p50/p95/p99 latency under load.
- Data durability
- Add periodic snapshots.
- Add write-ahead log (WAL) for crash recovery guarantees.
.
|-- main.go
|-- Dockerfile
|-- docker-compose.yml
|-- cache/
| |-- cache.go
| |-- lru.go
| `-- persistence.go
|-- server/
| `-- server.go
|-- hash/
| `-- consistent_hash.go
`-- client/
`-- client.go
Prerequisite:
- Go 1.26+
Run:
go run main.go -port 8000 -capacity 100- Keys without
ttlare treated as non-expiring. GET /getreturns404for missing or expired keys.- Persistence file defaults to
cache.json.