cache

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 18, 2026 License: MIT Imports: 13 Imported by: 0

README

go-cache

GitHub release (latest SemVer) Go Version Go Report Card GoDoc MIT License

A Laravel-inspired caching library for Go with support for multiple backends, cache tags, and built-in stampede prevention.

c, _ := cache.New()

users, _ := cache.Remember(ctx, c, "users.all", time.Hour, func() ([]User, error) {
    return db.FindAllUsers() // called once, result cached for 1 hour
})

Installation

go get github.com/hymns/go-cache

Requires Go 1.21+.

Drivers

Driver Description Persistence
memory In-process store using a sync'd map No — lost on restart
file JSON files on disk, organised in subdirectories Yes
redis Redis via go-redis/v9 Yes

Configuration

Via environment variables
CACHE_DRIVER=memory        # memory | file | redis  (default: memory)
CACHE_PREFIX=myapp         # key prefix, e.g. "myapp:users.all"
CACHE_TTL=3600             # default TTL in seconds  (default: 3600)

# Redis
CACHE_REDIS_ADDR=127.0.0.1:6379
CACHE_REDIS_PASSWORD=secret
CACHE_REDIS_DB=0

# File driver
CACHE_FILE_PATH=/var/cache/myapp
c, err := cache.New()
Via struct
c, err := cache.NewConfig(cache.Config{
    Driver: cache.DriverRedis,
    Prefix: "myapp",
    TTL:    30 * time.Minute,

    RedisAddr:     "127.0.0.1:6379",
    RedisPassword: "",
    RedisDB:       0,
})

API

Get / Put / Forever
// Store a value with TTL
c.Put(ctx, "key", value, 5*time.Minute)

// Retrieve into a typed pointer — returns (found bool, err error)
var user User
found, err := c.Get(ctx, "key", &user)

// Store without expiry
c.Forever(ctx, "app.version", "v2.0.0")
Remember

Returns the cached value if present; otherwise calls the function, stores the result, and returns it. The function is only called once even under concurrent load (stampede-safe).

// Type-safe via generics
users, err := cache.Remember(ctx, c, "users.all", time.Hour, func() ([]User, error) {
    return db.FindAllUsers()
})

// Without expiry
config, err := cache.RememberForever(ctx, c, "app.config", func() (Config, error) {
    return loadConfigFromDB()
})
Has / Forget / Flush
c.Has(ctx, "key")          // bool
c.Forget(ctx, "key")       // remove one entry
c.Flush(ctx)               // remove all entries
Pull

Retrieve and immediately remove in one operation — useful for one-time tokens.

var otp string
found, err := c.Pull(ctx, "otp:12345", &otp)
// key is gone after this call
Add

Store only if the key does not already exist. Returns true if stored.

ok, err := c.Add(ctx, "lock:job", true, 30*time.Second)
if !ok {
    // another process already holds the lock
}
Increment / Decrement

Atomic integer counters. Creates the key at 0 if it does not exist.

views, _ := c.Increment(ctx, "page.views", 1)
stock, _ := c.Decrement(ctx, "product.stock", 1)

// Read back
n := c.GetInt(ctx, "page.views", 0)
Typed helpers
name := c.GetString(ctx, "app.name", "default")

Cache Tags

Group related entries under one or more tags. Flushing a tag instantly invalidates every entry stored under it without touching the rest of the cache.

// Write under a tag scope
c.Tags("users").Put(ctx, "1", user, time.Hour)
c.Tags("users").Put(ctx, "2", user, time.Hour)
c.Tags("posts").Put(ctx, "latest", posts, time.Hour)

// Flush only "users" — posts are untouched
c.Tags("users").Flush(ctx)

// Multi-tag entries require ALL tags to be valid
c.Tags("users", "posts").Put(ctx, "feed", feed, time.Hour)
c.Tags("users").Flush(ctx) // ← invalidates the feed entry too
TagRemember
users, err := cache.TagRemember(ctx, c.Tags("users"), "all", time.Hour, func() ([]User, error) {
    return db.FindAllUsers()
})

// After c.Tags("users").Flush(ctx), the next call re-fetches from the DB
cfg, err := cache.TagRememberForever(ctx, c.Tags("config"), "settings", func() (Settings, error) {
    return loadSettings()
})

How it works: each tag holds a random version token in the store. Flushing a tag rotates its token — all previously stored keys become unreachable immediately. TTL entries expire naturally; forever entries are orphaned until the next full Flush.


Stampede Prevention

Remember and RememberForever use singleflight internally. If many goroutines concurrently miss the same key, only one executes the callback — the rest wait and receive the same result.

// 100 goroutines, cold cache — DB is called exactly once
for range 100 {
    go func() {
        cache.Remember(ctx, c, "heavy.query", time.Hour, func() (Result, error) {
            return db.ExpensiveQuery() // called once
        })
    }()
}

This applies automatically to Remember, RememberForever, TagRemember, and TagRememberForever.


File Driver — Storage Layout

Files are organised in a two-level subdirectory structure based on the first two hex characters of the SHA-256 key hash:

/var/cache/myapp/
├── a3/
│   └── a3f9bc...json
├── b1/
│   └── b1d042...json
└── e1/
    └── e10a8b...json

Each file stores the serialised value, expiry time, and a forever flag.


Sharing a Store

Multiple Cache instances can share the same underlying store — useful for applying different prefixes to the same backend:

store := drivers.NewMemory()

users := cache.NewWithStore(store, cache.Config{Prefix: "users"})
posts := cache.NewWithStore(store, cache.Config{Prefix: "posts"})

Custom Driver

Implement the Store interface to add your own backend:

type Store interface {
    Get(ctx context.Context, key string) ([]byte, bool, error)
    Put(ctx context.Context, key string, value []byte, ttl time.Duration) error
    Forever(ctx context.Context, key string, value []byte) error
    Add(ctx context.Context, key string, value []byte, ttl time.Duration) (bool, error)
    Increment(ctx context.Context, key string, amount int64) (int64, error)
    Decrement(ctx context.Context, key string, amount int64) (int64, error)
    Forget(ctx context.Context, key string) error
    Flush(ctx context.Context) error
    Has(ctx context.Context, key string) (bool, error)
}
c := cache.NewWithStore(myCustomStore, cache.Config{Prefix: "app"})


Web frameworks

This library uses standard context.Context and works with any Go web framework. Pass the request context as usual:

  • Ginc.Request.Context()
  • Chir.Context()
  • Fiberc.UserContext() ⚠️ not c.Context() (fasthttp context is not a context.Context)

Testing

# Run all tests (Redis skipped if not available)
go test ./tests/...

# Verbose
go test ./tests/... -v

# Run with Redis
CACHE_REDIS_ADDR=127.0.0.1:6379 go test ./tests/...

License

MIT © Muhammad Hamizi Jaminan

Documentation

Overview

Package cache provides a Laravel-inspired caching library for Go with support for multiple backends (memory, file, Redis) configured via env vars.

Quick start:

c, _ := cache.New()

c.Put(ctx, "key", "value", time.Minute)

result, _ := cache.Remember(ctx, c, "users", time.Hour, func() ([]User, error) {
    return db.FindAllUsers()
})

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Remember

func Remember[T any](ctx context.Context, c *Cache, key string, ttl time.Duration, fn func() (T, error)) (T, error)

Remember returns the cached value for key. If the key is missing it calls fn, stores the result with ttl, and returns it.

Concurrent calls for the same key on a cache miss are coalesced: only one goroutine executes fn and all others receive the same result, preventing cache stampedes.

users, err := cache.Remember(ctx, c, "users.all", time.Hour, func() ([]User, error) {
    return db.FindAllUsers()
})

func RememberForever

func RememberForever[T any](ctx context.Context, c *Cache, key string, fn func() (T, error)) (T, error)

RememberForever is like Remember but stores without expiry. Concurrent misses for the same key are coalesced (stampede-safe).

func TagRemember

func TagRemember[T any](ctx context.Context, t *Tagged, key string, ttl time.Duration, fn func() (T, error)) (T, error)

TagRemember returns the cached value for key under this tag scope. On miss it calls fn, stores the result with ttl, and returns it.

users, err := cache.TagRemember(ctx, c.Tags("users"), "all", time.Hour, func() ([]User, error) {
    return db.FindAllUsers()
})

func TagRememberForever

func TagRememberForever[T any](ctx context.Context, t *Tagged, key string, fn func() (T, error)) (T, error)

TagRememberForever is like TagRemember but stores without expiry.

Types

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache wraps a Store with a Laravel-like API.

func New

func New() (*Cache, error)

New creates a Cache using ConfigFromEnv. This is the standard entry point — configure via environment variables.

func NewConfig

func NewConfig(cfg Config) (*Cache, error)

NewConfig creates a Cache from an explicit Config struct.

func NewWithStore

func NewWithStore(store Store, cfg Config) *Cache

NewWithStore creates a Cache around an existing Store. Useful for testing or when you need to share a store across multiple Cache instances.

func (*Cache) Add

func (c *Cache) Add(ctx context.Context, key string, value any, ttl time.Duration) (bool, error)

Add stores value only if the key does not already exist. Returns true if the value was stored.

func (*Cache) Decrement

func (c *Cache) Decrement(ctx context.Context, key string, amount int64) (int64, error)

Decrement atomically decreases an integer counter by amount.

func (*Cache) DefaultTTL

func (c *Cache) DefaultTTL() time.Duration

DefaultTTL returns the configured default TTL.

func (*Cache) Flush

func (c *Cache) Flush(ctx context.Context) error

Flush clears all cache entries (respects the store scope, e.g. Redis DB).

func (*Cache) Forever

func (c *Cache) Forever(ctx context.Context, key string, value any) error

Forever stores value without an expiry.

func (*Cache) Forget

func (c *Cache) Forget(ctx context.Context, key string) error

Forget removes a single cache entry.

func (*Cache) Get

func (c *Cache) Get(ctx context.Context, key string, dest any) (bool, error)

Get retrieves a cached value into dest (must be a pointer). Returns false if the key does not exist or has expired.

func (*Cache) GetInt

func (c *Cache) GetInt(ctx context.Context, key string, def int64) int64

GetInt returns the cached int64 counter value, or def if missing. Only works with keys written via Increment/Decrement.

func (*Cache) GetString

func (c *Cache) GetString(ctx context.Context, key, def string) string

GetString returns the cached string value, or def if missing.

func (*Cache) Has

func (c *Cache) Has(ctx context.Context, key string) bool

Has reports whether a non-expired entry exists for key.

func (*Cache) Increment

func (c *Cache) Increment(ctx context.Context, key string, amount int64) (int64, error)

Increment atomically increases an integer counter by amount. Creates the key with value amount if it does not exist.

func (*Cache) Pull

func (c *Cache) Pull(ctx context.Context, key string, dest any) (bool, error)

Pull retrieves a cached value into dest and immediately removes it.

func (*Cache) Put

func (c *Cache) Put(ctx context.Context, key string, value any, ttl time.Duration) error

Put stores value with the given TTL.

func (*Cache) Store

func (c *Cache) Store() Store

Store returns the underlying Store for driver-specific operations.

func (*Cache) Tags

func (c *Cache) Tags(tags ...string) *Tagged

Tags returns a Tagged cache scoped to the given tags.

type Config

type Config struct {
	Driver Driver
	Prefix string
	TTL    time.Duration

	// Redis
	RedisAddr     string
	RedisPassword string
	RedisDB       int

	// File
	FilePath string
}

func ConfigFromEnv

func ConfigFromEnv() Config

ConfigFromEnv loads cache config from environment variables.

CACHE_DRIVER   = memory | redis | file  (default: memory)
CACHE_PREFIX   = key prefix             (default: "")
CACHE_TTL      = seconds                (default: 3600)

CACHE_REDIS_ADDR     = host:port        (default: 127.0.0.1:6379)
CACHE_REDIS_PASSWORD =                  (default: "")
CACHE_REDIS_DB       = 0               (default: 0)

CACHE_FILE_PATH = /path/to/cache  (default: OS cache dir + "/go-cache")

type Driver

type Driver string
const (
	DriverMemory Driver = "memory"
	DriverRedis  Driver = "redis"
	DriverFile   Driver = "file"
)

type Store

type Store interface {
	Get(ctx context.Context, key string) ([]byte, bool, error)
	Put(ctx context.Context, key string, value []byte, ttl time.Duration) error
	Forever(ctx context.Context, key string, value []byte) error
	Add(ctx context.Context, key string, value []byte, ttl time.Duration) (bool, error)
	Increment(ctx context.Context, key string, amount int64) (int64, error)
	Decrement(ctx context.Context, key string, amount int64) (int64, error)
	Forget(ctx context.Context, key string) error
	Flush(ctx context.Context) error
	Has(ctx context.Context, key string) (bool, error)
}

Store is the low-level interface every driver must satisfy.

type Tagged

type Tagged struct {
	// contains filtered or unexported fields
}

Tagged is a cache scope tied to one or more tags. Calling Flush on a Tagged invalidates every entry stored under those tags without touching the rest of the cache.

tc := c.Tags("users", "roles")
tc.Put(ctx, "admin", user, time.Hour)
tc.Flush(ctx) // only invalidates "users"+"roles" entries

func (*Tagged) Add

func (t *Tagged) Add(ctx context.Context, key string, value any, ttl time.Duration) (bool, error)

Add stores a tagged entry only if the key does not already exist.

func (*Tagged) Decrement

func (t *Tagged) Decrement(ctx context.Context, key string, amount int64) (int64, error)

Decrement atomically decreases an integer counter under this tag scope.

func (*Tagged) Flush

func (t *Tagged) Flush(ctx context.Context) error

Flush invalidates all entries under these tags by rotating each tag's version token. Old entries become unreachable immediately and will either expire (TTL entries) or be orphaned (forever entries, cleaned up on next full Flush of the underlying store).

func (*Tagged) Forever

func (t *Tagged) Forever(ctx context.Context, key string, value any) error

Forever stores a tagged entry without expiry.

func (*Tagged) Forget

func (t *Tagged) Forget(ctx context.Context, key string) error

Forget removes a single tagged cache entry.

func (*Tagged) Get

func (t *Tagged) Get(ctx context.Context, key string, dest any) (bool, error)

Get retrieves a tagged cache entry into dest.

func (*Tagged) Has

func (t *Tagged) Has(ctx context.Context, key string) bool

Has reports whether a non-expired tagged entry exists.

func (*Tagged) Increment

func (t *Tagged) Increment(ctx context.Context, key string, amount int64) (int64, error)

Increment atomically increases an integer counter under this tag scope.

func (*Tagged) Pull

func (t *Tagged) Pull(ctx context.Context, key string, dest any) (bool, error)

Pull retrieves and atomically removes a tagged cache entry.

func (*Tagged) Put

func (t *Tagged) Put(ctx context.Context, key string, value any, ttl time.Duration) error

Put stores a tagged cache entry with the given TTL.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL