processcache

package module
v0.1.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: 2 Imported by: 0

README

ProcessCache

CI pkg.go.dev Go Report Card

ProcessCache is a bounded, thread-safe, in-process LRU cache for Go applications that need fast local caching without Redis, Memcached, a database, an HTTP server, or any runtime sidecar.

import processcache "github.com/tonmoytalukder/process-cache"

Install

Requires Go 1.24 or newer.

go get github.com/tonmoytalukder/process-cache

Stability

ProcessCache follows semantic versioning.

  • v0.x means the API is usable but may still evolve based on early adoption feedback.
  • v1.0.0 will mark the start of stable compatibility expectations for the public API.

Quick Start

package main

import (
	"fmt"
	"time"

	processcache "github.com/tonmoytalukder/process-cache"
)

func main() {
	cache, err := processcache.NewMemoryCache(
		processcache.WithMaxSize(100*processcache.MB),
		processcache.WithCleanupInterval(5*time.Minute),
		processcache.WithTypeLimit("username:", 1*processcache.MB),
		processcache.WithTypeLimit("session:", 50*processcache.MB),
	)
	if err != nil {
		panic(err)
	}
	defer cache.Close()

	if !cache.Set("username:tonmoy", true, 5*time.Minute) {
		panic("cache rejected username entry")
	}

	exists, ok := processcache.GetAs[bool](cache, "username:tonmoy")
	fmt.Println(exists, ok)
}

Features

  • Bounded by an approximate global memory cap.
  • Optional per-prefix quotas such as username: or session:.
  • O(1) average key lookup with map[string]*item.
  • O(1) global LRU eviction using one doubly-linked list.
  • O(1) type-scoped LRU eviction using one doubly-linked list per configured prefix.
  • Exact LRU promotion on successful reads.
  • Lazy expiration on Get and Exists.
  • Background expiration cleanup.
  • Idempotent Close.
  • Stats snapshots with copied type-size maps.
  • Configurable Sizer and Clock.
  • Zero runtime external dependencies.

API

type Cache interface {
	Get(key string) (any, bool)
	Set(key string, value any, ttl ...time.Duration) bool
	Delete(key string) bool
	Exists(key string) bool
	Clear()
	Len() int
	Stats() Stats
	Close() error
}

Create a cache with:

cache, err := processcache.NewMemoryCache()

Or start from the exported config defaults:

cfg := processcache.DefaultConfig()
cfg.MaxSize = 32 * processcache.MB
cfg.CleanupDisabled = true

cache, err := processcache.NewMemoryCacheFromConfig(cfg)

Use GetAs for typed reads:

value, ok := processcache.GetAs[string](cache, "key")

Cached nil values are visible through Get, but GetAs treats them like a typed miss.

Options

  • WithMaxSize(bytes int64)
  • WithCleanupInterval(interval time.Duration)
  • WithCleanupDisabled()
  • WithTypeLimit(prefix string, bytes int64)
  • WithTypeLimits(limits ...TypeLimit)
  • WithSizer(sizer Sizer)
  • WithClock(clock Clock)
  • WithMetrics(enabled bool)

Defaults:

  • Max size: 100 * processcache.MB
  • Cleanup interval: 5 * time.Minute
  • Metrics: enabled
  • Type limits: none

Set accepts at most one meaningful TTL value. If the TTL is omitted or <= 0, the entry does not expire. If more than one TTL is passed, only the first value is used.

The cache preserves exact LRU ordering, so operations synchronize through one internal mutex rather than a read-optimized approximate policy.

Size Accounting

ProcessCache size accounting is approximate. The default sizer counts key length, common scalar sizes, strings, byte slices, and a conservative fixed overhead for unknown values. Go heap metadata, interface boxing, map growth, and GC behavior mean process memory can exceed the cache's internal byte count.

The built-in estimator is most accurate for strings, byte slices, booleans, numeric scalars, time.Time, and time.Duration. If you cache richer structs, slices, or maps and want tighter limits, provide WithSizer.

Stats

stats := cache.Stats()
fmt.Println(stats.Hits, stats.Misses, stats.CurrentSize)

Stats.TypeSizes and Stats.TypeLimits are copied before return, so callers cannot mutate internal cache state.

Configured prefixes remain present in Stats.TypeSizes even when their current size is zero.

Explicit Delete calls increment Stats.Deletes, including when the removed item is already expired. Overwrites increment Stats.Sets but not Stats.Deletes or Stats.Evictions.

Optional Service Integration

Application services can depend directly on processcache.Cache and treat nil as "cache disabled":

type UserService struct {
	cache processcache.Cache
}

func (s *UserService) IsUsernameTaken(username string) (bool, error) {
	key := "username:" + username
	if s.cache != nil {
		if value, ok := processcache.GetAs[bool](s.cache, key); ok {
			return value, nil
		}
	}

	// Query your source of truth here.
	exists := false

	if s.cache != nil {
		s.cache.Set(key, exists, 5*time.Minute)
	}
	return exists, nil
}

Shutdown

Always close the cache when your application shuts down:

defer cache.Close()

Close is idempotent and waits for the background sweeper to exit.

After Close, cache operations still work, but only lazy expiration runs; background cleanup is stopped.

All cache methods are safe for concurrent use.

Redis Comparison

ProcessCache is not a distributed cache. It is intentionally local to one Go process. Use Redis or Memcached when you need shared cache state across processes, persistence, cross-service invalidation, or centralized memory management. Use ProcessCache when you need a zero-infrastructure in-process cache with predictable local LRU behavior.

Development

./scripts/test.sh
./scripts/race.sh
./scripts/bench.sh

Docker:

./scripts/docker-test.sh
docker compose run --rm race
docker compose run --rm bench

Project Docs

Documentation

Overview

Package processcache provides a bounded, thread-safe, in-process LRU cache for Go applications that need fast local caching without Redis, Memcached, a database, an HTTP server, or any runtime sidecar.

Example usage:

cache, err := processcache.NewMemoryCache(
	processcache.WithCleanupDisabled(),
	processcache.WithMaxSize(10*processcache.MB),
)
if err != nil {
	panic(err)
}
defer cache.Close()

cache.Set("user:1", "Tonmoy", time.Minute)

name, ok := processcache.GetAs[string](cache, "user:1")
fmt.Println(name, ok)

Index

Constants

View Source
const (
	// KB is one kibibyte in bytes.
	KB = internal.KB
	// MB is one mebibyte in bytes.
	MB = internal.MB
	// GB is one gibibyte in bytes.
	GB = internal.GB
)

Variables

View Source
var (
	// ErrInvalidMaxSize reports a non-positive cache size limit.
	ErrInvalidMaxSize = internal.ErrInvalidMaxSize
	// ErrInvalidCleanupInterval reports a non-positive cleanup interval when cleanup is enabled.
	ErrInvalidCleanupInterval = internal.ErrInvalidCleanupInterval
	// ErrInvalidTypePrefix reports an empty type-prefix quota key.
	ErrInvalidTypePrefix = internal.ErrInvalidTypePrefix
	// ErrInvalidTypeLimit reports a non-positive type quota.
	ErrInvalidTypeLimit = internal.ErrInvalidTypeLimit
	// ErrDuplicateTypePrefix reports duplicate configured type prefixes.
	ErrDuplicateTypePrefix = internal.ErrDuplicateTypePrefix
	// ErrOverlappingTypePrefix reports prefixes whose match ranges overlap.
	ErrOverlappingTypePrefix = internal.ErrOverlappingTypePrefix
	// ErrNilSizer reports a nil sizer implementation.
	ErrNilSizer = internal.ErrNilSizer
	// ErrNilClock reports a nil clock implementation.
	ErrNilClock = internal.ErrNilClock
)

Functions

func GetAs

func GetAs[T any](c Cache, key string) (T, bool)

GetAs returns a typed cache value when the key exists and matches T.

Cached nil values are observable through Get, but GetAs returns false for them because a nil dynamic value cannot satisfy a concrete type assertion.

Types

type Cache

type Cache interface {
	Get(key string) (any, bool)
	Set(key string, value any, ttl ...time.Duration) bool
	Delete(key string) bool
	Exists(key string) bool
	Clear()
	Len() int
	Stats() Stats
	Close() error
}

Cache is the minimal concurrent cache contract exposed by this package.

type Clock

type Clock = internal.Clock

Clock provides time to the cache for expiration logic.

Implementations must not call back into the cache; doing so may deadlock.

type Config

type Config = internal.Config

Config configures a MemoryCache instance.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the package defaults for a new MemoryCache.

type MemoryCache

type MemoryCache = internal.MemoryCache

MemoryCache is the in-memory LRU cache implementation.

func NewMemoryCache

func NewMemoryCache(opts ...Option) (*MemoryCache, error)

NewMemoryCache constructs a MemoryCache from functional options.

func NewMemoryCacheFromConfig

func NewMemoryCacheFromConfig(cfg Config) (*MemoryCache, error)

NewMemoryCacheFromConfig constructs a MemoryCache from an explicit Config.

Callers typically start from DefaultConfig and then override selected fields.

type Option

type Option = internal.Option

Option mutates a Config during construction.

func WithCleanupDisabled

func WithCleanupDisabled() Option

WithCleanupDisabled disables the background expiration sweeper.

func WithCleanupInterval

func WithCleanupInterval(interval time.Duration) Option

WithCleanupInterval sets the background expiration sweep interval.

func WithClock

func WithClock(clock Clock) Option

WithClock overrides the time source used for expiration.

func WithMaxSize

func WithMaxSize(bytes int64) Option

WithMaxSize sets the global cache size limit in bytes.

func WithMetrics

func WithMetrics(enabled bool) Option

WithMetrics enables or disables atomic stats counters.

func WithSizer

func WithSizer(sizer Sizer) Option

WithSizer overrides the default size estimator.

func WithTypeLimit

func WithTypeLimit(prefix string, bytes int64) Option

WithTypeLimit adds one prefix-scoped quota.

func WithTypeLimits

func WithTypeLimits(limits ...TypeLimit) Option

WithTypeLimits adds multiple prefix-scoped quotas.

type Sizer

type Sizer = internal.Sizer

Sizer estimates the cache cost of one entry.

Implementations must not call back into the cache; doing so may deadlock.

type Stats

type Stats = internal.Stats

Stats is a point-in-time view of cache counters and capacity usage.

type TypeLimit

type TypeLimit = internal.TypeLimit

TypeLimit reserves part of the cache budget for keys with a given prefix.

Directories

Path Synopsis
cmd
bench command
example command
internal

Jump to

Keyboard shortcuts

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