A high-performance distributed cache for Go. Embed it as a library or run it as a standalone server.
- Sub-microsecond reads — 76ns Get, 441ns Set (benchmarked)
- Distributed — Gossip-based cluster with consistent hashing
- Three cache modes — Partitioned, Replicated, NearCache
- Distributed locks — With fencing tokens and auto-release
- Rate limiting — Token bucket and sliding window algorithms
- Pub/Sub — Subscribe to cache events with glob patterns
- Tag-based invalidation — Group keys by tags, invalidate in bulk
- Namespaces — Multi-tenant key isolation
- HTTP middleware — Rate limiting and response caching for
net/http - Standalone server — REST API with Docker and Kubernetes support
- Prometheus metrics —
/metricsendpoint out of the box - Zero external dependencies at runtime — No Redis, no Memcached
go get github.com/skshohagmiah/cachegridpackage main
import (
"fmt"
"time"
"github.com/skshohagmiah/cachegrid"
)
func main() {
cache, _ := cachegrid.New(cachegrid.Config{
MaxMemoryMB: 256,
DefaultTTL: 5 * time.Minute,
})
defer cache.Shutdown()
// Set and Get
cache.Set("user:123", map[string]string{"name": "Alice"}, 5*time.Minute)
var user map[string]string
if cache.Get("user:123", &user) {
fmt.Println(user["name"]) // Alice
}
}cache, _ := cachegrid.New(cachegrid.Config{
ListenAddr: ":7946",
Peers: []string{"10.0.0.2:7946", "10.0.0.3:7946"},
Mode: cachegrid.Partitioned,
MaxMemoryMB: 256,
})
defer cache.Shutdown()Nodes discover each other via gossip and automatically partition keys across the cluster.
docker run -d -p 6380:6380 -p 7946:7946 cachegrid
curl -X PUT localhost:6380/cache/user:123 \
-H "X-TTL: 5m" -d '{"name": "Alice"}'
curl localhost:6380/cache/user:123// Basic CRUD
cache.Set("key", value, 5*time.Minute)
cache.Get("key", &dest)
cache.Delete("key")
cache.Exists("key")
cache.TTL("key")
// Cache-aside pattern
cache.GetOrSet("key", &dest, ttl, func() (interface{}, error) {
return fetchFromDB()
})
// Bulk operations
cache.MSet(map[string]cachegrid.Item{
"a": {Value: 1, TTL: time.Minute},
"b": {Value: 2, TTL: time.Minute},
})
results := cache.MGet("a", "b")
// Atomic counters
cache.Incr("views", 1)
cache.Decr("stock", 1)// Tag-based invalidation
cache.SetWithTags("user:1:profile", profile, ttl, []string{"user:1"})
cache.SetWithTags("user:1:settings", settings, ttl, []string{"user:1"})
cache.InvalidateTag("user:1") // deletes both keys
// Namespaces
tenant := cache.WithNamespace("acme")
tenant.Set("config", val, time.Hour) // stored as "acme:config"
tenant.Get("config", &dest)lock, err := cache.Lock("order:456", cachegrid.LockOptions{
TTL: 30 * time.Second,
RetryCount: 10,
RetryDelay: 100 * time.Millisecond,
})
if err != nil {
return err
}
defer lock.Release()
// Extend for long operations
lock.Extend(60 * time.Second)
// Fencing token for safe writes
token := lock.Token()
// Non-blocking attempt
lock, ok := cache.TryLock("resource", 30*time.Second)// Token bucket
allowed, state := cache.RateLimit("api:user:1", cachegrid.RateLimitOptions{
Limit: 100,
Window: time.Minute,
})
// state.Remaining, state.Limit, state.ResetsAt
// Sliding window
allowed, state := cache.RateLimitSliding("api:user:1", cachegrid.SlidingWindowOptions{
Limit: 100,
Window: time.Minute,
})sub := cache.Subscribe("user:*")
defer sub.Close()
go func() {
for event := range sub.Events() {
fmt.Printf("%s %s\n", event.Type, event.Key)
}
}()
// Event hooks
cache.OnHit(func(key string) { /* metrics */ })
cache.OnMiss(func(key string) { /* metrics */ })
cache.OnEvict(func(key string, value interface{}) { /* cleanup */ })mux := http.NewServeMux()
// Rate limit by client IP (100 req/min)
handler := cachegrid.HTTPRateLimit(cache, 100, time.Minute)(mux)
// Cache GET responses
handler = cachegrid.HTTPCache(cache, 30*time.Second)(mux)| Method | Endpoint | Description |
|---|---|---|
GET |
/cache/{key} |
Get value |
PUT |
/cache/{key} |
Set value (X-TTL, X-Tags headers) |
DELETE |
/cache/{key} |
Delete key |
HEAD |
/cache/{key} |
Check existence |
POST |
/cache/_mget |
Bulk get ({"keys": [...]}) |
POST |
/locks/{key} |
Acquire lock ({"ttl": "30s"}) |
DELETE |
/locks/{key} |
Release lock (X-Lock-Token header) |
PUT |
/locks/{key} |
Extend lock |
POST |
/ratelimit/{key} |
Check rate limit |
GET |
/subscribe/{pattern} |
SSE event stream |
GET |
/health |
Health check |
GET |
/metrics |
Prometheus metrics |
GET |
/cluster/nodes |
Cluster state |
cachegrid.Config{
NumShards: 256, // must be power of 2
MaxMemoryMB: 256, // 0 = unlimited
DefaultTTL: 5*time.Minute, // 0 = no expiry
SweeperInterval: time.Second, // expired key cleanup interval
// Cluster (optional — omit for local-only mode)
ListenAddr: ":7946",
Peers: []string{"10.0.0.2:7946"},
Mode: cachegrid.Partitioned, // or Replicated, NearCache
NodeName: "node-1", // defaults to hostname
GRPCPort: 7947, // inter-node RPC
HTTPPort: 6380, // standalone HTTP API
VirtualNodes: 150, // hash ring granularity
}| Variable | Default | Description |
|---|---|---|
CACHEGRID_SHARDS |
256 |
Number of shards |
CACHEGRID_MAX_MEMORY_MB |
0 |
Memory limit |
CACHEGRID_DEFAULT_TTL |
0 |
Default TTL (e.g., 5m) |
CACHEGRID_NODE_NAME |
hostname | Node identifier |
CACHEGRID_LISTEN_ADDR |
`` | Gossip bind address |
CACHEGRID_SEEDS |
`` | Comma-separated seed nodes |
CACHEGRID_MODE |
partitioned |
partitioned, replicated, nearcache |
CACHEGRID_RPC_PORT |
7947 |
Inter-node RPC port |
CACHEGRID_HTTP_PORT |
6380 |
HTTP API port |
docker compose up -d # starts 3-node clustercurl localhost:6380/health
curl localhost:6381/health
curl localhost:6382/healthkubectl apply -f kubernetes.yamlCreates a 3-replica StatefulSet with headless service for gossip discovery.
| Mode | Read | Write | Use Case |
|---|---|---|---|
| Partitioned | From owner | To owner | Large datasets, even distribution |
| Replicated | Local (fast) | Fan-out to all | Read-heavy, small datasets |
| NearCache | Local first, then owner | Local + owner | Hot keys, read-heavy |
BenchmarkGetHit-10 15,733,113 75.85 ns/op 69 B/op 3 allocs/op
BenchmarkGetMiss-10 51,526,194 23.16 ns/op 16 B/op 1 allocs/op
BenchmarkSet-10 2,504,900 441.30 ns/op 353 B/op 6 allocs/op
BenchmarkExists-10 17,432,041 68.02 ns/op 15 B/op 1 allocs/op
BenchmarkIncr-10 7,588,982 156.50 ns/op 304 B/op 7 allocs/op
BenchmarkConcurrentRead-10 10,314,302 122.10 ns/op 87 B/op 4 allocs/op
BenchmarkConcurrentMixed-10 8,313,844 147.90 ns/op 138 B/op 5 allocs/op
Run locally: go test -bench=. -benchmem
┌─────────────────────────────────────────────────────────────┐
│ Public API (cachegrid) │
│ Set · Get · Delete · Lock · RateLimit · Subscribe · ... │
├──────────────┬──────────────┬───────────────┬───────────────┤
│ internal/ │ internal/ │ internal/ │ internal/ │
│ cache │ cluster │ transport │ lock │
│ (shards, │ (hashring, │ (TCP+msgpack │ (fencing, │
│ LRU, │ gossip, │ RPC) │ auto- │
│ eviction) │ state) │ │ release) │
├──────────────┼──────────────┼───────────────┼───────────────┤
│ internal/ │ internal/ │ internal/ │ │
│ ratelimit │ pubsub │ server │ │
│ (token │ (broker, │ (HTTP REST, │ │
│ bucket, │ events, │ SSE, │ │
│ sliding │ hooks) │ metrics) │ │
│ window) │ │ │ │
└──────────────┴──────────────┴───────────────┴───────────────┘
cachegrid/
├── cache.go, config.go, errors.go # Core cache + configuration
├── distributed.go # Cluster routing logic
├── lock.go, ratelimit.go # Distributed locks + rate limiting
├── pubsub.go, tags.go, namespace.go # Events, tags, namespaces
├── middleware.go # HTTP middleware helpers
├── *_test.go # 121 tests across all features
├── internal/
│ ├── cache/ # Sharded store, LRU eviction, serialization
│ ├── cluster/ # Hash ring, gossip membership, cluster state
│ ├── transport/ # TCP+msgpack inter-node RPC
│ ├── lock/ # Lock engine with fencing tokens
│ ├── ratelimit/ # Token bucket + sliding window
│ ├── pubsub/ # Event broker + subscriptions
│ └── server/ # HTTP server, handlers, middleware
├── cmd/cachegrid/ # Standalone server binary
├── Dockerfile
├── docker-compose.yml
└── kubernetes.yaml
See CONTRIBUTING.md for guidelines.
MIT License. See LICENSE for details.