"kee-oh-shoon" /kiːoʊʃuːn/
Thread-safe, sharded in-memory cache for Go - with an optional peer-to-peer cluster backend
- What is Kioshun?
- Cluster (Overview)
- Internals
- Installation
- Quick Start
- Configuration
- API
- HTTP Middleware
- Benchmark Results
Kioshun is a thread-safe (and fast!), in-memory cache for Go. You can run it as a local cache just like any other in-memory caches, or turn on the peer-to-peer cluster when you want replicas across hosts.
If you want to know more about Kioshun internals and how it works under the hood - see Kioshun Internals
Note
Clustering is fully optional. If you don’t enable the cluster, Kioshun runs as a standalone, in‑memory cache.
Kioshun’s cluster turns every service instance into a small, self-managing peer-to-peer cache. You just point each one at a few reachable Seeds and it discovers the rest, builds a weighted rendezvous and replicates writes with configurable RF/WC so hot data stays local. Gossip keeps the peer list fresh, hinted handoff plus backfill repair all gaps, and reads go straight to the primary owner while read-through uses single-flight leases.
┌─────────────┐ Gossip + Weights ┌─────────────┐
│ Service A │◀──────────────────────────▶│ Service B │
│ + Node │◀───────────▶◀───────────▶ │ + Node │
└──────┬──────┘ └──────┬──────┘
│ Owner‑routed Get/Set (RF) │
└──────────────▶◀──────────────────────────┘
Service C + Node
Small multinode example:
# on each server
CACHE_BIND=:4443
CACHE_PUBLIC=srv-a:4443 # srv-b / srv-c on others
CACHE_SEEDS=srv-a:4443,srv-b:4443,srv-c:4443
CACHE_AUTH=supersecret// in code
local := cache.NewWithDefaults[string, []byte]()
cfg := cluster.Default()
cfg.BindAddr = os.Getenv("CACHE_BIND")
cfg.PublicURL = os.Getenv("CACHE_PUBLIC")
cfg.Seeds = strings.Split(os.Getenv("CACHE_SEEDS"), ",")
cfg.ReplicationFactor = 3; cfg.WriteConcern = 2
cfg.Sec.AuthToken = os.Getenv("CACHE_AUTH")
node := cluster.NewNode[string, []byte](cfg, cluster.StringKeyCodec[string]{}, local, cluster.BytesCodec{})
if err := node.Start(); err != nil {
panic(err)
}
dc := cluster.NewDistributedCache[string, []byte](node)Only a subset of nodes need to appear in CACHE_SEEDS. The list is purely for bootstrap - include a few stable peers so new processes can reach at least one live seed, then gossip distributes the rest of the membership automatically, whether you run 3 caches or 20.
See CLUSTER.md for more details.
go get github.com/unkn0wn-root/kioshunpackage main
import (
"fmt"
"time"
cache "github.com/unkn0wn-root/kioshun"
)
func main() {
// Create cache with default configuration
c := cache.NewWithDefaults[string, string]()
defer c.Close()
// Set with default TTL (30 min)
c.Set("user:123", "David Nice 1", cache.DefaultExpiration)
// Set with no expiration
c.Set("user:123", "David Nice 2", cache.NoExpiration)
// Set value with custom TTL
c.Set("user:123", "David Nice 3", 5*time.Minute)
// Get value
if value, found := c.Get("user:123"); found {
fmt.Printf("User: %s\n", value)
}
// Get cache statistics
stats := c.Stats()
fmt.Printf("Hit ratio: %.2f%%\n", stats.HitRatio*100)
}config := cache.Config{
MaxSize: 100000, // Maximum number of items
ShardCount: 16, // Number of shards (0 = auto-detect)
CleanupInterval: 5 * time.Minute, // Cleanup frequency
DefaultTTL: 30 * time.Minute, // Default expiration time
EvictionPolicy: cache.AdmissionLFU, // Eviction algorithm (default)
StatsEnabled: true, // Enable statistics collection
}
cache := cache.New[string, any](config)cache.Set(key, value, ttl time.Duration) error
cache.SetWithCallback(key, value, ttl, callback func(key, value)) error
cache.Get(key) (value, found bool)
cache.GetWithTTL(key) (value, ttl time.Duration, found bool)
cache.Keys() []K
cache.Clear()
cache.Delete(key) bool
cache.Exists(key) bool
cache.Size() int64
cache.Stats() Stats
cache.TriggerCleanup()
cache.Close() errortype Stats struct {
Hits int64
Misses int64
Evictions int64
Expirations int64
Size int64
Capacity int64
HitRatio float64
Shards int
}Kioshun provides HTTP middleware out-of-the-box.
config := cache.DefaultMiddlewareConfig()
config.DefaultTTL = 5 * time.Minute
config.MaxSize = 100000
middleware := cache.NewHTTPCacheMiddleware(config)
defer middleware.Close()
http.Handle("/api/users", middleware.Middleware(usersHandler))See MIDDLEWARE.md for complete documentation, examples, and advanced configuration.
Latest benchmark run (Apple M4 Max, Go 1.24.7):
SET: 100,000,000 ops/sec · 75.55 ns/op · 41 B/op · 3 allocs/opGET: 231,967,180 ops/sec · 25.87 ns/op · 31 B/op · 2 allocs/opReal-World: 52,742,550 ops/sec · 65.25 ns/op · 48 B/op · 3 allocs/op
Full suite: _benchmarks/README.md