A high-performance, thread-safe Go cache library with Time-To-Live (TTL) support and configurable eviction policies (LRU, LFU, ARC, NOOP). Built with generics for type safety and optimal performance.
- Features
- Technologies & Stack
- Installation
- Quick Start
- Usage Examples
- Eviction Policies
- API Reference
- Project Structure
- Development
- Benchmarks
- License
- Author
- β±οΈ TTL Support - Automatic expiration of cached items with configurable granularity
- π Multiple Eviction Policies - LRU (Least Recently Used), LFU (Least Frequently Used), ARC (Adaptive Replacement Cache), and NOOP (no eviction)
- π Thread-Safe - Full concurrency support with mutex-based synchronization
- π― Type-Safe - Built with Go generics for compile-time type checking
- β‘ High Performance - Optimized data structures and efficient memory management
- ποΈ Configurable - Flexible capacity limits and TTL epoch granularity
- π§Ή Automatic Cleanup - Background goroutine handles expired item removal
- π¦ Zero External Dependencies - Only depends on
github.com/moeryomenko/synxfor synchronization primitives
Language: Go 1.25.1
Core Dependencies:
github.com/moeryomenko/synxv0.14.0 - Synchronization utilities
Development Tools:
golangci-lintv2.5.0 - Comprehensive Go lintergotestsumv1.13.0 - Enhanced test runnergoimports- Code formatting and import management
Build System:
- GNU Make for task automation
- Standard Go toolchain
go get github.com/moeryomenko/ttlcacheRequirements: Go 1.25.1 or higher
package main
import (
"context"
"fmt"
"time"
"github.com/moeryomenko/ttlcache"
)
func main() {
ctx := context.Background()
// Create a new cache with capacity of 100 items and LRU eviction policy
cache := cache.NewCache[string, string](ctx, 100, cache.WithEvictionPolicy(cache.LRU))
// Set a value without expiration (will only be evicted by policy)
cache.Set("key1", "value1")
// Set a value with 5 second TTL
cache.SetNX("key2", "value2", 5*time.Second)
// Get a value
if value, ok := cache.Get("key1"); ok {
fmt.Println("Found:", value)
}
}ctx := context.Background()
cache := cache.NewCache[string, int](ctx, 100)
// Set permanent value (evicted only by policy)
cache.Set("counter", 42)
// Set value with TTL
cache.SetNX("session", 12345, 10*time.Minute)
// Get value
if value, ok := cache.Get("counter"); ok {
fmt.Println("Value:", value)
}
// Remove value
cache.Remove("counter")
// Get cache size
size := cache.Len()// LRU - Least Recently Used (default)
lruCache := cache.NewCache[string, string](
ctx,
100,
cache.WithEvictionPolicy(cache.LRU),
)
// LFU - Least Frequently Used
lfuCache := cache.NewCache[string, string](
ctx,
100,
cache.WithEvictionPolicy(cache.LFU),
)
// ARC - Adaptive Replacement Cache
arcCache := cache.NewCache[string, string](
ctx,
100,
cache.WithEvictionPolicy(cache.ARC),
)
// NOOP - No eviction (only TTL-based expiration)
noopCache := cache.NewCache[string, string](
ctx,
100,
cache.WithEvictionPolicy(cache.NOOP),
)By default, the TTL cleanup runs every second. You can customize this:
cache := cache.NewCache[string, string](
ctx,
100,
cache.WithEvictionPolicy(cache.LRU),
cache.WithTTLEpochGranularity(100*time.Millisecond), // Check every 100ms
)type User struct {
ID int
Username string
Email string
}
cache := cache.NewCache[int, *User](ctx, 1000)
user := &User{
ID: 1,
Username: "john_doe",
Email: "john@example.com",
}
cache.SetNX(user.ID, user, 1*time.Hour)
if foundUser, ok := cache.Get(1); ok {
fmt.Printf("User: %s (%s)\n", foundUser.Username, foundUser.Email)
}type Session struct {
UserID string
Token string
CreatedAt time.Time
}
sessionCache := cache.NewCache[string, *Session](
ctx,
10000,
cache.WithEvictionPolicy(cache.LRU),
cache.WithTTLEpochGranularity(1*time.Second),
)
// Create new session with 30 minute expiry
session := &Session{
UserID: "user123",
Token: "abc-def-ghi",
CreatedAt: time.Now(),
}
sessionCache.SetNX(session.Token, session, 30*time.Minute)
// Validate session
if session, ok := sessionCache.Get("abc-def-ghi"); ok {
fmt.Println("Valid session for user:", session.UserID)
} else {
fmt.Println("Session expired or not found")
}Evicts the items that haven't been accessed for the longest time. Best for general-purpose caching where recent access patterns predict future access.
cache.WithEvictionPolicy(cache.LRU)Evicts items with the lowest access frequency. Best when certain items are accessed much more frequently than others.
cache.WithEvictionPolicy(cache.LFU)Adaptive policy that balances between recency and frequency. Automatically adjusts to access patterns. Best for workloads with changing patterns.
cache.WithEvictionPolicy(cache.ARC)No automatic eviction based on access patterns - items are only removed when they expire via TTL or are manually removed. Best when you have strict memory limits and control expiration via TTL.
cache.WithEvictionPolicy(cache.NOOP)type Cache[K comparable, V any]Generic cache supporting any comparable key type and any value type.
func NewCache[K comparable, V any](
ctx context.Context,
capacity int,
opts ...Option,
) *Cache[K, V]Creates a new cache instance. The cache runs a background cleanup goroutine that stops when ctx is canceled.
Sets a value that persists until evicted by the replacement policy. Won't be removed by TTL expiration.
Sets a value with TTL. The item will be automatically removed after expiry duration.
Retrieves a value by key. Returns the value and true if found, zero value and false otherwise.
Manually removes an item from the cache.
Returns the current number of items in the cache.
Sets the eviction policy. Available policies: LRU, LFU, ARC, NOOP. Default is LRU.
Sets how often the background cleanup goroutine checks for expired items. Default is 1 * time.Second.
ttlcache/
βββ cache.go # Main cache implementation
βββ cache_test.go # Comprehensive test suite
βββ config.go # Configuration structures
βββ interfaces.go # Internal interfaces
βββ options.go # Functional options API
βββ policies.go # Eviction policy constants
βββ internal/
β βββ policies/ # Eviction policy implementations
β βββ lru.go # Least Recently Used
β βββ lfu.go # Least Frequently Used
β βββ arc.go # Adaptive Replacement Cache
β βββ noop.go # No eviction policy
βββ go.mod # Go module definition
βββ Makefile # Build and development tasks
βββ LICENSE-MIT # MIT License
βββ LICENSE-APACHE # Apache 2.0 License
- Go 1.25.1 or higher
- Make (optional, for convenience commands)
make lint # Run linter with auto-fix
make test # Run tests with coverage
make cover # View coverage in browser
make fmt # Format code
make mod # Update dependencies
make check # Run lint + test
make clean # Clean build artifacts
make help # Show all available commands# Run all tests with coverage
make test
# Or using go directly
go test -v -race -coverprofile=coverage.out ./...
# View coverage
make coverThe project uses golangci-lint with a comprehensive configuration. Run linting with:
make lintThe cache is designed for high-performance scenarios. All operations are O(1) average case for get/set operations, with eviction policies implemented using efficient data structures.
Run benchmarks with:
go test -bench=. -benchmemThis project is dual-licensed under:
You may choose either license to govern your use of this software.
Maxim Eryomenko (@moeryomenko)
- GitHub: github.com/moeryomenko/ttlcache
- Package Documentation: pkg.go.dev/github.com/moeryomenko/ttlcache
β If you find this project useful, please consider giving it a star on GitHub!
When you add a new item to a full cache, the eviction policy determines which existing item to remove. The cache will first try to remove any expired items, then apply the configured eviction policy if still over capacity.
Yes, the cache is fully thread-safe and can be safely used from multiple goroutines simultaneously.
Yes, use the Set() method instead of SetNX(). Items added with Set() will only be evicted by the replacement policy when the cache is full, not by TTL expiration.
- Use LRU for general-purpose caching (default choice)
- Use LFU when some items are accessed much more frequently than others
- Use ARC for workloads with changing access patterns
- Use NOOP when you want to manage expiration purely via TTL
Memory usage depends on your stored values and the number of items. The cache itself has minimal overhead - mainly the map structures and tracking metadata for the eviction policy.
Yes, call SetNX() again with the same key and the new expiration time.
The background cleanup goroutine will stop, but the cache remains usable. No new automatic TTL cleanup will occur, but you can still manually use the cache.