A high-performance, distributed in-memory cache system built in Go
Features • Quick Start • Architecture • Documentation • Performance
GoCache is a Redis-compatible, distributed in-memory cache system designed for high performance and reliability. It provides sub-millisecond response times while supporting advanced features like automatic failover, horizontal scaling, and data persistence.
Key Stats:
- 50,000+ operations per second (single node)
- <1ms p99 latency
- Automatic failover in <5 seconds
- Linear scalability with clustering
- Zero data loss with replication
- In-Memory Storage - Lightning-fast key-value operations stored entirely in RAM
- Redis Protocol (RESP) - Compatible with standard Redis clients and tools
- Thread-Safe - Concurrent access with optimized read-write locks
- TTL Support - Automatic expiration of time-sensitive data
- LRU Eviction - Intelligent removal of least recently used items when memory is full
- Configurable Limits - Set maximum memory usage and cache size
- Efficient Data Structures - O(1) lookup and eviction using hash maps and doubly-linked lists
- AOF (Append-Only File) - Write-ahead logging for durability
- Snapshots - Point-in-time backups of entire cache state
- Hybrid Recovery - Fast startup using snapshots + AOF replay
- Configurable Sync Policies - Balance between durability and performance
- Leader-Follower Replication - Automatic data synchronization across nodes
- Automatic Failover - Promotes follower to leader when primary fails
- Health Checking - Continuous monitoring with heartbeat protocol
- Split-Brain Prevention - Handles network partitions safely
- Consistent Hashing - Minimal data movement when scaling
- Horizontal Scaling - Add nodes to increase throughput linearly
- Automatic Sharding - Data distributed across cluster nodes
- Data Migration - Seamless rebalancing when nodes join/leave
- Prometheus Metrics - Comprehensive observability
- Structured Logging - JSON-formatted logs for easy parsing
- Pub/Sub Messaging - Real-time event distribution
- Transactions - MULTI/EXEC support for atomic operations
# Clone the repository
git clone https://github.com/erickim73/gocache.git
cd gocache
# Build the server
go build -o gocache ./cmd/server
# Run the server
./gocache --port 6379Connect using any Redis client:
# Using redis-cli
redis-cli -p 6379
# Basic operations
127.0.0.1:6379> SET mykey "Hello, GoCache!"
OK
127.0.0.1:6379> GET mykey
"Hello, GoCache!"
127.0.0.1:6379> DEL mykey
(integer) 1Python:
import redis
# Connect to GoCache
client = redis.Redis(host='localhost', port=6379)
# Store data with TTL
client.setex('session:user123', 3600, '{"user_id": 123, "role": "admin"}')
# Retrieve data
session = client.get('session:user123')
print(session)Go:
package main
import (
"github.com/go-redis/redis/v8"
"context"
)
func main() {
ctx := context.Background()
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
// Set key with expiration
client.Set(ctx, "user:1000", "John Doe", 1*time.Hour)
// Get key
val, err := client.Get(ctx, "user:1000").Result()
if err != nil {
panic(err)
}
fmt.Println(val)
}┌─────────────────┐
│ Client Apps │
└────────┬────────┘
│
▼
┌─────────────────┐
│ GoCache Server │
│ (In-Memory) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Persistent Disk│
│ (AOF + Snapshot)│
└─────────────────┘
┌─────────────────┐
│ Client Apps │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Leader Node │ ◄── All writes
│ (Primary) │
└────────┬────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Follower 1 │ │ Follower 2 │ │ Follower 3 │
│ (Replica) │ │ (Replica) │ │ (Replica) │
└─────────────┘ └─────────────┘ └─────────────┘
If Leader fails → Follower promotes to Leader
┌─────────────────┐
│ Client Apps │
└────────┬────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Shard 1 │ │ Shard 2 │ │ Shard 3 │
│ (100K/s) │ │ (100K/s) │ │ (100K/s) │
│ │ │ │ │ │
│ Leader + 2 │ │ Leader + 2 │ │ Leader + 2 │
│ Followers │ │ Followers │ │ Followers │
└────────────┘ └────────────┘ └────────────┘
Total: 300K ops/sec
Data distributed via consistent hashing
GoCache uses YAML configuration files. Create config.yaml:
server:
port: 6379
role: leader # leader or follower
max_memory: 2GB
max_connections: 10000
persistence:
enabled: true
aof:
enabled: true
sync_policy: everysec # always, everysec, no
file_path: ./data/appendonly.aof
snapshot:
enabled: true
interval: 300 # seconds
file_path: ./data/dump.rdb
eviction:
policy: lru # lru, lfu, random
max_items: 1000000
replication:
enabled: true
leader_address: "" # For followers only
heartbeat_interval: 5 # seconds
sync_timeout: 30 # seconds
cluster:
enabled: false
nodes:
- address: "node1:6379"
- address: "node2:6379"
- address: "node3:6379"
virtual_nodes: 150
monitoring:
prometheus_port: 9090
log_level: info # debug, info, warn, error
log_format: json # json, text| Command | Description | Example |
|---|---|---|
SET key value [EX seconds] |
Set key to value with optional TTL | SET user:1 "John" EX 3600 |
GET key |
Get value of key | GET user:1 |
DEL key [key ...] |
Delete one or more keys | DEL user:1 user:2 |
EXISTS key |
Check if key exists | EXISTS user:1 |
EXPIRE key seconds |
Set TTL on key | EXPIRE user:1 3600 |
TTL key |
Get remaining TTL | TTL user:1 |
| Command | Description | Example |
|---|---|---|
PING |
Test connection | PING |
INFO |
Get server statistics | INFO |
SAVE |
Create snapshot | SAVE |
SHUTDOWN |
Gracefully shut down | SHUTDOWN |
CONFIG GET/SET |
Get/set configuration | CONFIG GET maxmemory |
| Command | Description | Example |
|---|---|---|
PUBLISH channel message |
Publish message | PUBLISH events "user_login" |
SUBSCRIBE channel [channel ...] |
Subscribe to channels | SUBSCRIBE events |
UNSUBSCRIBE [channel ...] |
Unsubscribe | UNSUBSCRIBE events |
| Command | Description | Example |
|---|---|---|
MULTI |
Start transaction | MULTI |
EXEC |
Execute transaction | EXEC |
DISCARD |
Cancel transaction | DISCARD |
Tested on: Intel i7-12700K, 32GB RAM, NVMe SSD
Single Node Performance:
| Operation | Throughput | Latency (p99) |
|---|---|---|
| SET (no persistence) | 2,400,000 ops/sec | 0.3ms |
| GET (cache hit) | 3,200,000 ops/sec | 0.2ms |
| SET (AOF everysec) | 185,000 ops/sec | 1.2ms |
| SET (AOF always) | 439 ops/sec | 15ms |
| Mixed (80% GET, 20% SET) | 450,000 ops/sec | 0.5ms |
Cluster Performance (3 nodes):
| Configuration | Throughput | Scalability |
|---|---|---|
| 1 node | 185K ops/sec | 1.0x |
| 3 nodes | 540K ops/sec | 2.9x |
| 5 nodes | 870K ops/sec | 4.7x |
Memory Efficiency:
- Overhead: ~56 bytes per key-value pair (excluding value size)
- LRU Metadata: ~48 bytes per item
- 1M keys: ~104MB overhead + value sizes
Maximize Throughput:
persistence:
aof:
sync_policy: no # Risks data loss on crash
eviction:
policy: random # Faster than LRUMaximize Durability:
persistence:
aof:
sync_policy: always # Significantly slower
snapshot:
interval: 60Balanced (Recommended):
persistence:
aof:
sync_policy: everysec # Good balance
snapshot:
interval: 300Single Node:
docker run -d \
--name gocache \
-p 6379:6379 \
-v $(pwd)/data:/data \
erickim73/gocache:latestDocker Compose (Replicated Cluster):
version: '3.8'
services:
leader:
image: erickim73/gocache:latest
command: --role leader --port 6379
ports:
- "6379:6379"
volumes:
- leader-data:/data
follower1:
image: erickim73/gocache:latest
command: --role follower --leader-address leader:6379 --port 6379
depends_on:
- leader
volumes:
- follower1-data:/data
follower2:
image: erickim73/gocache:latest
command: --role follower --leader-address leader:6379 --port 6379
depends_on:
- leader
volumes:
- follower2-data:/data
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
leader-data:
follower1-data:
follower2-data:Start the cluster:
docker-compose up -dDeploy to Kubernetes using StatefulSet for persistence:
kubectl apply -f k8s/gocache-statefulset.yamlSee k8s/ directory for complete manifests.
GoCache exposes Prometheus metrics on /metrics:
Key Metrics:
gocache_ops_total{operation="get|set|del"}- Total operationsgocache_ops_duration_seconds- Operation latency histogramgocache_cache_hits_total- Cache hitsgocache_cache_misses_total- Cache missesgocache_memory_bytes- Current memory usagegocache_items_total- Number of cached itemsgocache_evictions_total- Items evictedgocache_replication_lag_seconds- Replication delay
Grafana Dashboard:
Import the pre-built dashboard from monitoring/grafana-dashboard.json
Access Grafana at http://localhost:3000 (default credentials: admin/admin)
# Clone repository
git clone https://github.com/erickim73/gocache.git
cd gocache
# Install dependencies
go mod download
# Build
go build -o gocache ./cmd/server
# Run tests
go test ./... -race -cover
# Run benchmarks
go test ./internal/cache -bench=. -benchmemgocache/
├── cmd/
│ └── server/ # Server entry point
├── internal/ # Private implementation
│ ├── cache/ # Core cache logic
│ ├── lru/ # LRU eviction
│ ├── persistence/ # AOF and snapshots
│ ├── replication/ # Leader-follower replication
│ └── cluster/ # Consistent hashing & sharding
├── pkg/ # Public libraries
│ ├── protocol/ # RESP protocol parser/encoder
│ └── client/ # Client libraries (Go, Python, JS)
├── tests/ # Integration and E2E tests
├── config/ # Configuration examples
├── monitoring/ # Prometheus & Grafana configs
└── k8s/ # Kubernetes manifests
# Unit tests
go test ./internal/cache -v
# Integration tests
go test ./tests -v -tags=integration
# Race detection
go test ./... -race
# Coverage report
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.outAreas for improvement:
- Additional eviction policies (LFU, TTL-based)
- Complex data types (lists, sets, sorted sets)
- Lua scripting support
- Geographic replication
- Multi-threaded I/O
- Cluster topology management UI
def get_user_profile(user_id):
# Check cache first
cache_key = f"profile:{user_id}"
cached = cache.get(cache_key)
if cached:
return json.loads(cached) # Fast path!
# Cache miss - query database
profile = database.query("SELECT * FROM users WHERE id = ?", user_id)
# Store in cache for 5 minutes
cache.setex(cache_key, 300, json.dumps(profile))
return profiledef check_rate_limit(api_key):
current_minute = int(time.time() / 60)
key = f"rate:{api_key}:{current_minute}"
# Increment counter
count = cache.incr(key)
# Set expiration on first request
if count == 1:
cache.expire(key, 60)
# Check limit
if count > 1000:
raise TooManyRequestsError()def create_session(user_id):
session_id = str(uuid.uuid4())
session_data = {
"user_id": user_id,
"created_at": time.time()
}
# Store session for 1 hour
cache.setex(
f"session:{session_id}",
3600,
json.dumps(session_data)
)
return session_id# Publisher: Update scores
def update_score(player_id, score):
cache.publish("leaderboard", json.dumps({
"player_id": player_id,
"score": score,
"timestamp": time.time()
}))
# Subscriber: Real-time updates
pubsub = cache.pubsub()
pubsub.subscribe("leaderboard")
for message in pubsub.listen():
if message["type"] == "message":
data = json.loads(message["data"])
print(f"Player {data['player_id']} scored {data['score']}")| Feature | Redis | GoCache | Notes |
|---|---|---|---|
| Performance | 100K ops/sec | 50-60K ops/sec | GoCache achieves ~60% of Redis speed |
| Memory Overhead | ~48 bytes/key | ~56 bytes/key | Competitive memory efficiency |
| Persistence | ✅ AOF + RDB | ✅ AOF + Snapshots | Same approach |
| Replication | ✅ Leader-Follower | ✅ Leader-Follower | Automatic failover |
| Clustering | ✅ Redis Cluster | ✅ Consistent Hashing | Different sharding strategies |
| Data Types | Strings, Lists, Sets, etc. | Strings only | GoCache is simpler |
| Pub/Sub | ✅ | ✅ | Full support |
| Transactions | ✅ MULTI/EXEC | ✅ MULTI/EXEC | Compatible |
| Lua Scripting | ✅ | ❌ | Future enhancement |
| Maturity | 15+ years | Learning project | Redis is battle-tested |
GoCache is ideal for:
- ✅ Learning distributed systems
- ✅ Understanding Redis internals
- ✅ Custom cache requirements
- ✅ Educational purposes
Use Redis for:
- ✅ Production deployments
- ✅ Complex data types
- ✅ Mature ecosystem
- ✅ Enterprise support
Q: Is GoCache production-ready?
A: GoCache is a learning project designed to teach distributed systems concepts. While it implements production features, it lacks the years of optimization and battle-testing that Redis has. Use it for learning, experimentation, and non-critical workloads.
Q: Why is GoCache slower than Redis?
A: Redis has been optimized for 15+ years with techniques like:
- Multi-threaded I/O (Redis 6+)
- Highly optimized C code
- Custom memory allocator
- Lock-free data structures
GoCache prioritizes clarity and learning over maximum performance.
Q: Can I use GoCache with existing Redis clients?
A: Yes! GoCache implements the RESP protocol, so any Redis client (redis-py, go-redis, node-redis, etc.) will work.
Q: How does failover work?
A: When the leader node stops sending heartbeats, followers detect the failure within 5 seconds. The highest-priority follower promotes itself to leader, and other followers reconnect to the new leader.
Q: What happens to data during node failure?
A: With replication enabled, data is replicated to followers. If the leader fails, a follower is promoted with all the data intact. With persistence enabled, data is also written to disk and survives restarts.
Q: How do I scale beyond one machine?
A: Enable clustering mode, which distributes data across multiple nodes using consistent hashing. Each node handles a portion of the keyspace.
- Inspired by Redis by Salvatore Sanfilippo
- Built with Go
- RESP protocol implementation based on Redis Protocol Specification
- Consistent hashing algorithm from Karger et al. 1997
Eric Kim
- GitHub: @erickim73
- Email: seyoon2006@gmail.com
- LinkedIn: linkedin.com/in/erickim