samber/hot
samber/hot — dự án backend — đang có 269 sao GitHub trong nhóm Backend. 🌶️ In-memory caching library for read-intensive Go applications
Tóm tắt dựng từ metadata GitHub của chính dự án — chưa có bài review TopGit. Trang sẽ tự động cập nhật khi bài review đầy đủ được xuất bản.
TopGit viết bài đầy đủ cho repo có nhiều sao nhất và được yêu cầu nhiều nhất. Trang này là snapshot trong thời gian chờ — xem README gốc ở tab READ ME.
Snapshot
Cộng tác viên hàng đầu
Xem cộng tác viên hàng đầu
HOT - Blazing Fast In-Memory Caching for Go
HOT stands for Hot Object Tracker - a feature-complete, blazing-fast caching library for Go applications.
🚀 Features
- ⚡ High Performance: Optimized for speed
- 🔄 Multiple Eviction Policies: LRU, LFU, TinyLFU, W-TinyLFU, S3FIFO, ARC, 2Q, SIEVE, and FIFO algorithms
- ⏰ TTL with Jitter: Prevent cache stampedes with exponential distribution
- 🔄 Stale-While-Revalidate: Serve stale data while refreshing in background
- ❌ Missing Key Caching: Cache negative results to avoid repeated lookups
- 🍕 Sharded Cache: Scale horizontally with multiple cache shards
- 🔒 Thread Safety: Optional locking with zero-cost when disabled
- 🔗 Loader Chains: Chain multiple data sources with in-flight deduplication
- 🌶️ Cache Warmup: Preload frequently accessed data
- 📦 Batch Operations: Efficient bulk operations for better performance
- 🧩 Composable Design: Mix and match caching strategies
- 📝 Copy-on-Read/Write: Optional value copying for thread safety
- 📊 Metrics Collection: Built-in statistics and monitoring
- 💫 Go Generics: Type-safe caching with compile-time guarantees
- 🏡 Bring your own cache: Pluggable APIs and highly customizable
📋 Table of Contents
- 🏎️ Performance
- 📦 Installation
- 🤠 Getting Started
- 🍱 API Reference
- 🏛️ Architecture
- 🪄 Examples
- 👀 Observability
- 🤝 Contributing
🏎️ Performance
HOT is optimized for high-performance scenarios:
- Zero-allocation operations where possible
- Lock-free operations when thread safety is disabled
- Batch operations for better throughput
- Sharded architecture for high concurrency
- Monotonic clock lookup (2.5x faster)
📦 Installation
go get github.com/samber/hot
# AI Agent Skill:
npx skills add https://github.com/samber/cc-skills-golang --skill golang-samber-hot
This library is v0 and follows SemVer strictly.
Some breaking changes might be made to exported APIs before v1.0.0.
🤠 Getting started
Let's start with a simple LRU cache and 10 minutes TTL:
import "github.com/samber/hot"
cache := hot.NewHotCache[string, int](hot.LRU, 1_000_000).
WithTTL(10*time.Minute).
Build()
cache.Set("hello", 42)
values, missing := cache.GetMany([]string{"bar", "baz", "hello"})
// values: {"hello": 42}
// missing: ["baz", "bar"]
🍱 API Reference
GoDoc: https://godoc.org/github.com/samber/hot
Configuration Options
TTL and expiration settings:
// Set default time-to-live for all cache entries
WithTTL(ttl time.Duration)
// Add random jitter to TTL to prevent cache stampedes
WithJitter(lambda float64, upperBound time.Duration)
// Enable background cleanup of expired items
WithJanitor()
Background cache revalidation (stale-while-revalidate pattern):
// Keep serving stale data while refreshing in background
WithRevalidation(stale time.Duration, loaders ...hot.Loader[K, V])
// Control behavior when revalidation fails (KeepOnError/DropOnError)
WithRevalidationErrorPolicy(policy hot.RevalidationErrorPolicy)
Missing key caching - prevents repeated lookups for non-existent keys:
// Use separate cache for missing keys (prevents main cache pollution)
WithMissingCache(algorithm hot.EvictionAlgorithm, capacity int)
// Share missing key cache with main cache (good for low missing rate)
WithMissingSharedCache()
Data source integration:
// Set chain of loaders for cache misses (primary, fallback, etc.)
WithLoaders(loaders ...hot.Loader[K, V])
Thread safety configuration:
// Disable mutex for single-threaded applications (performance boost)
WithoutLocking()
// Copy values when reading (prevents external modification)
WithCopyOnRead(copier func(V) V)
// Copy values when writing (ensures cache owns the data)
WithCopyOnWrite(copier func(V) V)
Sharding for high concurrency scenarios:
// Split cache into multiple shards to reduce lock contention
WithSharding(shards uint64, hasher sharded.Hasher[K])
Event callbacks and hooks:
// Called when items are evicted (LRU/LFU/TinyLFU/W-TinyLFU/S3FIFO/expiration)
WithEvictionCallback(callback func(key K, value V))
// Preload cache on startup with data from loader
WithWarmUp(loader func() (map[K]V, []K, error))
// Preload with timeout protection for slow data sources
WithWarmUpWithTimeout(timeout time.Duration, loader func() (map[K]V, []K, error))
Monitoring and metrics:
// Enable Prometheus metrics collection with the specified cache name
WithPrometheusMetrics(cacheName string)
Eviction algorithms:
hot.LRU
hot.LFU
hot.TinyLFU
hot.WTinyLFU
hot.S3FIFO
hot.TwoQueue
hot.ARC
hot.SIEVE
hot.FIFO
Revalidation policies:
hot.KeepOnError
hot.DropOnError
Core Methods
Basic operations:
// Store a key-value pair in the cache
cache.Set(key K, value V)
// Store with custom time-to-live (overrides default TTL)
cache.SetWithTTL(key K, value V, ttl time.Duration)
// Retrieve value by key, returns found status and any error
cache.Get(key K) -> (value V, found bool, error error)
// Check if key exists in cache (no side effects)
cache.Has(key K) -> bool
// Remove key from cache, returns true if key existed
cache.Delete(key K) -> bool
Batch operations (more efficient for multiple items):
// Store multiple key-value pairs atomically
cache.SetMany(items map[K]V)
// Retrieve multiple values, returns found items and missing keys
cache.GetMany(keys []K) -> (found map[K]V, missing []K)
// Check existence of multiple keys, returns map of key->exists
cache.HasMany(keys []K) -> map[K]bool
// Remove multiple keys, returns map of key->was_deleted
cache.DeleteMany(keys []K) -> map[K]bool
Inspection methods (no side effects on cache state):
// Get value without updating access time or LRU position
cache.Peek(key K) -> (value V, found bool)
// Get multiple values without side effects
cache.PeekMany(keys []K) -> (found map[K]V, missing []K)
// Get all keys in cache (order not guaranteed)
cache.Keys() -> []K
// Get all values in cache (order not guaranteed)
cache.Values() -> []V
// Get all keys and values in cache (order not guaranteed)
cache.All() -> map[K]V
// Iterate over all key-value pairs, return false to stop
cache.Range(fn func(key K, value V) bool)
// Get current number of items in cache
cache.Len() -> int
// Get (current_size, max_capacity) of cache
cache.Capacity() -> (current int, max int)
// Get (algorithm_name, algorithm_version) info
cache.Algorithm() -> (name string, version string)
Cache management and lifecycle:
// Remove all items from cache immediately
cache.Purge()
// Preload cache with data from loader function
cache.WarmUp(loader hot.Loader[K, V]) -> error
// Start background cleanup of expired items
cache.Janitor()
// Stop background janitor process
cache.StopJanitor()
Loader Interface
// Loader function signature for fetching data from external sources
// Called when cache misses occur, with automatic deduplication of concurrent requests
type Loader[K comparable, V any] func(keys []K) (found map[K]V, err error)
// Example:
func userLoader(keys []string) (found map[string]*User, err error) {
// Fetch users from database
// Return map of found users (key -> user object)
// Return empty map if no users found (not an error)
// Return error if database query fails
return users, nil
}
Shard partitioner
// Hasher is responsible for generating unsigned, 16 bit hash of provided key.
// Hasher should minimize collisions. For great performance, a fast function is preferable.
type Hasher[K any] func(key K) uint64
// Example:
func hash(key string) uint64 {
hasher := fnv.New64a()
hasher.Write([]byte(s))
return hasher.Sum64()
}
🏛️ Architecture
This project has been split into multiple layers to respect the separation of concern.
Each cache layer implements the pkg/base.InMemoryCache[K, V] interface. Combining multiple encapsulation has a small cost (~1ns per call), but offers great customization.
We highly recommend using hot.HotCache[K, V] instead of lower layers.
Example:
┌─────────────────────────────────────────────────────────────┐
│ hot.HotCache[K, V] │
│ (High-level, feature-complete) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ pkg/sharded.ShardedInMemoryCache │
│ (Sharding layer) │
└─────────────────────────────────────────────────────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ pkg/metrics.InstrumentedCache[K, V] │
│ (Metric collection layer) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ pkg/safe.SafeInMemoryCache[K, V] │
│ (Thread safety layer) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ pkg/lru.LRUCache[K, V] │
│ pkg/lfu.LFUCache[K, V] │
│ pkg/lfu.TinyLFUCache[K, V] │
│ pkg/lfu.WTinyLFUCache[K, V] │
│ pkg/lfu.S3FIFOCache[K, V] │
│ pkg/arc.ARCCache[K, V] │
│ pkg/sieve.SIEVECache[K, V] │
│ pkg/fifo.FIFOCache[K, V] │
│ pkg/twoqueue.TwoQueueCache[K, V] │
│ (Eviction policies) │
└─────────────────────────────────────────────────────────────┘
Eviction policies
This project provides multiple eviction policies. Each implements the pkg/base.InMemoryCache[K, V] interface.
They are not protected against concurrent access. If safety is required, encapsulate it into pkg/safe.SafeInMemoryCache[K comparable, V any].
Packages:
pkg/lrupkg/lfupkg/tinylfupkg/wtinylfupkg/s3fifopkg/twoqueuepkg/arcpkg/sievepkg/fifo
Example:
cache := lru.NewLRUCache[string, *User](100_000)
Concurrent access
The hot.HotCache[K, V] offers protection against concurrent access by default. But in some cases, unnecessary locking might just slow down a program.
Low-level cache layers are not protected by default. Use the following encapsulation to bring safety:
import (
"github.com/samber/hot/pkg/lfu"
"github.com/samber/hot/pkg/safe"
)
cache := safe.NewSafeInMemoryCache(
lru.NewLRUCache[string, *User](100_000),
)
Sharded cache
A sharded cache might be useful in two scenarios:
- highly concurrent application slowed down by cache locking -> 1 lock per shard instead of 1 global lock
- highly parallel application with no concurrency -> no lock
The sharding key must not be too costly to compute and must offer a nice balance between shards. The hashing function must have func(k K) uint64 signature.
A sharded cache can be created via hot.HotCache[K, V] or using a low-level layer:
import (
"hash/fnv"
"github.com/samber/hot/pkg/lfu"
"github.com/samber/hot/pkg/safe"
"github.com/samber/hot/pkg/sharded"
)
cache := sharded.NewShardedInMemoryCache(
100, // Number of shards
func() base.InMemoryCache[K, *item[V]] {
// Cache builder for each shard
return safe.NewSafeInMemoryCache(
lru.NewLRUCache[string, *User](100_000),
)
},
func(key string) uint64 {
// Hash function
h := fnv.New64a()
h.Write([]byte(key))
return h.Sum64()
},
)
Missing key caching
Instead of calling the loader chain every time an invalid key is requested, a "missing cache" can be enabled. Note that it won't protect your app against a DDoS attack with high cardinality keys.
If the missing keys are infrequent, sharing the missing cache with the main cache might be reasonable:
import "github.com/samber/hot"
cache := hot.NewHotCache[string, int](hot.LRU, 100_000).
WithMissingSharedCache().
Build()
If the missing keys are frequent, use a dedicated cache to prevent pollution of the main cache:
import "github.com/samber/hot"
cache := hot.NewHotCache[string, int](hot.LRU, 100_000).
WithMissingCache(hot.LFU, 50_000).
Build()
🪄 Examples
Simple LRU cache
import "github.com/samber/hot"
// Available eviction policies: hot.LRU, hot.LFU, hot.TinyLFU, hot.WTinyLFU, hot.S3FIFO, hot.TwoQueue, hot.ARC, hot.SIEVE, hot.FIFO
// Capacity: 100k keys/values
cache := hot.NewHotCache[string, int](hot.LRU, 100_000).
Build()
cache.Set("hello", 42)
cache.SetMany(map[string]int{"foo": 1, "bar": 2})
values, missing := cache.GetMany([]string{"bar", "baz", "hello"})
// values: {"bar": 2, "hello": 42}
// missing: ["baz"]
value, found, _ := cache.Get("foo")
// value: 1
// found: true
Error Handling Patterns
// Handle cache operations with proper error checking
value, found, err := cache.Get("key")
if err != nil {
// Handle loader errors (database connection, network issues, etc.)
log.Printf("Cache get error: %v", err)
return
}
if !found {
// Key doesn't exist in cache and wasn't found by loaders
log.Printf("Key not found: %s", "key")
return
}
// Use value safely
fmt.Printf("Value: %v", value)
// Batch operations with error handling
values, missing := cache.GetMany([]string{"key1", "key2", "key3"})
if len(missing) > 0 {
log.Printf("Missing keys: %v", missing)
}
// Process found values
for key, value := range values {
fmt.Printf("%s: %v", key, value)
}
Cache with remote data source
If a value is not available in the in-memory cache, it will be fetched from a database or any data source.
Concurrent calls to loaders are deduplicated by key.
import "github.com/samber/hot"
cache := hot.NewHotCache[string, *User](hot.LRU, 100_000).
WithLoaders(func(keys []string) (found map[string]*User, err error) {
rows, err := db.Query("SELECT * FROM users WHERE id IN (?)", keys)
// ...
return users, err
}).
Build()
user, found, err := cache.Get("user-123")
// might fail if "user-123" is not in cache and loader returns error
// get or create
user, found, err := cache.GetWithLoaders(
"user-123",
func(keys []string) (found map[string]*User, err error) {
rows, err := db.Query("SELECT * FROM users WHERE id IN (?)", keys)
// ...
return users, err
},
func(keys []string) (found map[string]*User, err error) {
rows, err := db.Query("INSERT INTO users (id, email) VALUES (?, ?)", id, email)
// ...
return users, err
},
)
// either `err` is not nil, or `found` is true
// missing value vs nil value
user, found, err := cache.GetWithLoaders(
"user-123",
func(keys []string) (found map[string]*User, err error) {
// value could not be found
return map[string]*User{}, nil
// or
// value exists but is nil
return map[string]*User{"user-123": nil}, nil
},
)
Cache with expiration
import "github.com/samber/hot"
cache := hot.NewHotCache[string, int](hot.LRU, 100_000).
WithTTL(1 * time.Minute). // items will expire after 1 minute
WithJitter(2, 30*time.Second). // optional: randomizes the TTL with an exponential distribution in the range [0, +30s)
WithJanitor(1 * time.Minute). // optional: background job will purge expired keys every minutes
Build()
cache.SetWithTTL("foo", 42, 10*time.Second) // shorter TTL for "foo" key
With cache revalidation:
loader := func(keys []string) (found map[string]*User, err error) {
rows, err := db.Query("SELECT * FROM users WHERE id IN (?)", keys)
// ...
return users, err
}
cache := hot.NewHotCache[string, *User](hot.LRU, 100_000).
WithTTL(1 * time.Minute).
// Keep delivering cache 5 more second, but refresh value in background.
// Keys that are not fetched during the interval will be dropped anyway.
// A timeout or error in loader will drop keys.
WithRevalidation(5 * time.Second, loader).
// On revalidation error, the cache entries are either kept or dropped.
// Optional (default: drop)
WithRevalidationErrorPolicy(hot.KeepOnError).
Build()
If WithRevalidation is used without loaders, the one provided in WithRevalidation() or GetWithLoaders() is used.
👀 Observability
HOT provides comprehensive Prometheus metrics for monitoring cache performance and behavior. Enable metrics by calling WithPrometheusMetrics() with a cache name:
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/samber/hot"
"net/http"
)
// Create cache with Prometheus metrics
cache := hot.NewHotCache[string, string](hot.LRU, 1000).
WithTTL(5*time.Minute).
WithJitter(0.5, 10*time.Second).
WithRevalidation(10*time.Second).
WithRevalidationErrorPolicy(hot.KeepOnError).
WithPrometheusMetrics("users-by-id").
WithMissingCache(hot.ARC, 1000).
Build()
// Register the cache metrics with Prometheus
err := prometheus.Register(cache)
if err != nil {
log.Fatalf("Failed to register metrics: %v", err)
}
defer prometheus.Unregister(cache)
// Set up HTTP server to expose metrics
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
Available Metrics
Counters:
hot_insertion_total- Total number of items inserted into the cachehot_eviction_total{reason}- Total number of items evicted from the cache (by reason)hot_hit_total- Total number of cache hitshot_miss_total- Total number of cache misses
Gauges:
hot_size_bytes- Current size of the cache in bytes (including keys and values)hot_length- Current number of items in the cache
Configuration Gauges:
hot_settings_capacity- Maximum number of items the cache can holdhot_settings_algorithm- Eviction algorithm type (0=lru, 1=lfu, 2=arc, 3=2q, 4=fifo, 5=tinylfu, 6=wtinylfu, 7=s3fifo, 8=sieve)hot_settings_ttl_seconds- Time-to-live duration in seconds (if set)hot_settings_jitter_lambda- Jitter lambda parameter for TTL randomization (if set)hot_settings_jitter_upper_bound_seconds- Jitter upper bound duration in seconds (if set)hot_settings_stale_seconds- Stale duration in seconds (if set)hot_settings_missing_capacity- Maximum number of missing keys the cache can hold (if set)
Example Prometheus Queries
# Cache hit ratio
rate(hot_hit_total[5m]) / (rate(hot_hit_total[5m]) + rate(hot_miss_total[5m]))
# Eviction rate by reason
rate(hot_eviction_total[5m])
# Cache size in MB
hot_size_bytes / 1024 / 1024
# Cache utilization percentage
hot_length / hot_settings_capacity * 100
# Insertion rate
rate(hot_insertion_total[5m])
🏎️ Benchmark
TODO
🤝 Contributing
- Ping me on Twitter @samuelberthe (DMs, mentions, whatever :))
- Fork the project
- Fix open issues or request new features
Don't hesitate ;)
# Install some dev dependencies
make tools
# Run tests
make test
# or
make watch-test
👤 Contributors
💫 Show your support
Give a ⭐️ if this project helped you!
📝 License
Copyright © 2024 Samuel Berthe.
This project is MIT licensed.
Repo liên quan
Awesome Go, maintained at avelino/awesome-go, groups Go frameworks, libraries, and software into topic categories including Actor Model, Artificial Intelligence, Audio and Music, Authentication and Authorization, Blockchain, Bot Building, and Build Automation. Every linked entry gets a short description instead of a bare URL. The repository carries an MIT license and takes new entries through pull requests reviewed against its own contribution guidelines, which makes it a common bookmark for day-to-day Go development.
golang/go is the open source repository behind The Go Programming Language, holding the compiler, runtime, and standard library source that Go's official binaries are built from. The README frames Go's purpose as building software that is simple, reliable, and efficient, credits the project to thousands of contributors, and notes that this GitHub repo mirrors the canonical Git repository at go.googlesource.com/go.
The Gin Go web framework offers a high-performance, minimalist approach to building web applications and REST APIs in Go. It delivers exceptional speed through its use of httprouter and provides an Express.js-like API for rapid development.
Caddy is an HTTP/1-2-3 web server, written in Go, which provides automatic HTTPS by default, utilizing services like ZeroSSL and Let's Encrypt for public names or a local CA for internal ones. It supports flexible configuration through its native JSON API, a Caddyfile, or various other formats via config adapters. The project highlights a modular architecture for extensibility, runs on multiple platforms without external dependencies, and benefits from Go's memory safety features. The README indicates it has served trillions of requests and managed millions of TLS certificates, scaling to hundreds of thousands of sites in production. Caddy began development in 2014 and was an early adopter of automatic HTTPS by default.
Trả lời nhanh
Cùng nhóm Backend còn repo nào?
samber/hot thuộc nhóm Backend trên TopGit, cùng 18 topic GitHub. Trang Trending và Topics liệt kê các repo cùng số sao và cùng ngôn ngữ để so sánh.
Đọc thêm về samber/hot ở đâu?
Trang TopGit này là một snapshot — tab "Readme" hiển thị nguyên văn README của repo (đã bỏ link, giữ ảnh). Repo GitHub ở github.com/samber/hot là nguồn chính thức.
samber/hot có phải mã nguồn mở không?
Có — samber/hot phát hành theo license MIT, nghĩa là mã nguồn mở để đọc, fork và (tùy license) tái sử dụng. Mã: github.com/samber/hot.
samber/hot là gì?
samber/hot (samber/hot) là dự án Go trên GitHub. Theo mô tả gốc: 🌶️ In-memory caching library for read-intensive Go applications
samber/hot so với các dự án Backend khác thế nào?
samber/hot được TopGit xếp vào nhóm Backend, với 269 sao GitHub và viết bằng Go. Xem trang chủ đề Backend trên TopGit để so sánh với các dự án tương tự theo số sao và mức độ hoạt động.
Vì sao samber/hot được xếp vào nhóm Backend?
TopGit xếp samber/hot vào nhóm Backend dựa trên GitHub topics và mô tả của repo (gắn thẻ: "2q", "arc", "cache"). Việc phân loại dựa trên metadata thật của repo, không phải đoán theo cảm tính biên tập.
Đọc đầy đủ README ở tab phía trên.
Vẫn đang phân vân về hot?
Một cú bấm sẽ gửi câu hỏi kèm trang này cho AI — xem AI nói gì về hot.