Caching
go.putnami.dev/cache provides a generic caching layer with in-memory, disk, and layered implementations. All caches are safe for concurrent use.
Cache interface
All implementations share the same interface:
type Cache interface {
Get(ctx context.Context, key string) ([]byte, bool)
Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
Delete(ctx context.Context, key string) error
Has(ctx context.Context, key string) bool
Clear(ctx context.Context) error
Close() error
}Memory cache
In-memory cache with optional FIFO eviction:
import "go.putnami.dev/cache"
c := cache.NewMemoryCache(cache.MemoryConfig{
MaxEntries: 1000, // 0 = unlimited
CleanupInterval: 5 * time.Minute,
})
defer c.Close()Configuration
| Field | Type | Default | Description |
|---|---|---|---|
MaxEntries |
int |
0 |
Maximum entries (0 = unlimited). FIFO eviction when exceeded |
CleanupInterval |
time.Duration |
— | Interval for removing expired entries |
Disk cache
Filesystem-backed persistent cache that survives process restarts:
c, err := cache.NewDiskCache(cache.DiskConfig{
Dir: "./cache",
CleanupInterval: 10 * time.Minute,
})
if err != nil {
log.Fatal(err)
}
defer c.Close()Objects are stored as files with SHA-256 hashed filenames. The directory is created automatically if it doesn't exist.
Configuration
| Field | Type | Default | Description |
|---|---|---|---|
Dir |
string |
— | Cache directory (created if needed) |
CleanupInterval |
time.Duration |
— | Interval for removing expired entries |
Layered cache
Compose an L1/L2 cache hierarchy. On a cache miss in L1, the value is fetched from L2 and promoted to L1:
memory := cache.NewMemoryCache(cache.MemoryConfig{MaxEntries: 100})
disk, _ := cache.NewDiskCache(cache.DiskConfig{Dir: "./cache"})
c := cache.NewLayeredCache(memory, disk)
defer c.Close()Behavior
| Operation | L1 (memory) | L2 (disk) |
|---|---|---|
Get |
Check first; on miss, fetch from L2 and promote | Checked on L1 miss |
Set |
Write | Write |
Delete |
Delete | Delete |
Clear |
Clear | Clear |
Operations
Set and get
// Store with TTL
err := c.Set(ctx, "user:123", []byte(`{"name":"Jane"}`), 5*time.Minute)
// Store without expiration
err := c.Set(ctx, "config", data, 0)
// Retrieve
value, found := c.Get(ctx, "user:123")
if !found {
// cache miss or expired
}Check and delete
if c.Has(ctx, "user:123") {
// key exists and is not expired
}
err := c.Delete(ctx, "user:123")
// Clear all entries
err := c.Clear(ctx)Patterns
Cache-aside
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
key := "user:" + id
// Check cache
if data, found := s.cache.Get(ctx, key); found {
var user User
json.Unmarshal(data, &user)
return &user, nil
}
// Cache miss — fetch from database
user, err := s.repo.FindByID(ctx, "id", id)
if err != nil {
return nil, err
}
// Store in cache
data, _ := json.Marshal(user)
s.cache.Set(ctx, key, data, 10*time.Minute)
return &user, nil
}Cache invalidation
func (s *UserService) UpdateUser(ctx context.Context, id string, input UpdateInput) error {
if err := s.repo.Update(ctx, id, input); err != nil {
return err
}
// Invalidate cached entry
return s.cache.Delete(ctx, "user:"+id)
}Related guides
- Persistence — database queries to cache
- Configuration — cache configuration