loadingcache

package module
v0.0.0-...-2d674cb Latest Latest
Warning

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

Go to latest
Published: Mar 27, 2024 License: MIT Imports: 8 Imported by: 0

README

Loading Cache

At its core, loading cache is a rather simple cache implementation. It is heavily inspired by Guava.

image

Features

  1. 2024年03月25日: 增加从 Redis/DB 中异步加载缓存值的示例
  2. 2024年03月25日: 支持 key 过期时,异步加载,异步加载未完成时,访问对应的 Key 返回过期值.

Basics

You can use it as a simple, no fuss cache.

package main

import (
	"fmt"
	"time"

	"github.com/goldstd/loadingcache"
)

type sharedLoader struct{}

func (b *sharedLoader) Load(a any) (any, error) {
	// 从 redis / mysql 中加载数据
	return "abc", nil
}

func main() {
	cache := loadingcache.Config{
		Load:             &sharedLoader{},
		ExpireAfterWrite: 10 * time.Minute,
		AsyncLoad:        true, // 支持 key 过期时,异步加载
	}.Build()

	// Adding some values and reading them
	cache.Put("a", 1)
	cache.Put("b", 2)
	cache.Put("c", 3)
	val1, _ := cache.Get("a") // Don't forget to check for errors
	fmt.Printf("%v\n", val1)
	val2, _ := cache.Get("b") // Don't forget to check for errors
	fmt.Printf("%v\n", val2)

	// Getting a value that does not exist
	_, err := cache.Get("d")
	if errors.Is(err, loadingcache.ErrKeyNotFound) {
		fmt.Println("That key does not exist")
	}

	// Evicting
	cache.Invalidate("a")
	cache.Invalidate("b", "c")
	cache.InvalidateAll()

	// Output: 1
	// 2
	// That key does not exist
}

Advanced

You can also use more advanced options.

package main

import (
	"github.com/goldstd/loadingcache"
)

func main() {
	cache := loadingcache.Config{
		MaxSize:          2,
		ExpireAfterRead:  2 * time.Minute,
		ExpireAfterWrite: time.Minute,
		EvictListeners: []loadingcache.EvictListener{
			func(notification loadingcache.EvictNotification) {
				fmt.Printf("Entry removed due to %s\n", notification.Reason)
			},
		},
		ShardHashFunc: func(key any) (any, error) {
			fmt.Printf("Loading key %v\n", key)
			return fmt.Sprint(key), nil
		},
	}.Build()

	cache.Put(1, "1")
	val1, _ := cache.Get(1)
	fmt.Printf("%v\n", val1)

	val2, _ := cache.Get(2)
	fmt.Printf("%v\n", val2)

	val3, _ := cache.Get(3)
	fmt.Printf("%v\n", val3)

	// Output: 1
	// Loading key 2
	// 2
	// Loading key 3
	// Entry removed due to SIZE
	// 3
}

Benchmarks

Although preliminary, below you can find some benchmarks (included in the repo).

$ go test -benchmem -bench .
goos: darwin
goarch: amd64
pkg: github.com/goldstd/loadingcache
cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
BenchmarkGetMiss/Sharded_(2)-12           400610              2631 ns/op            1016 B/op         12 allocs/op
BenchmarkGetMiss/Sharded_(3)-12           451284              2618 ns/op            1016 B/op         12 allocs/op
BenchmarkGetMiss/Sharded_(16)-12                  447225              2709 ns/op            1016 B/op         12 allocs/op
BenchmarkGetMiss/Sharded_(32)-12                  441804              2649 ns/op            1015 B/op         12 allocs/op
BenchmarkGetMiss/Simple-12                        737130              1582 ns/op             680 B/op          8 allocs/op
BenchmarkGetHit/Simple-12                        9312319               126.5 ns/op             0 B/op          0 allocs/op
BenchmarkGetHit/Sharded_(2)-12                   8891150               137.2 ns/op             0 B/op          0 allocs/op
BenchmarkGetHit/Sharded_(3)-12                   8845831               137.3 ns/op             0 B/op          0 allocs/op
BenchmarkGetHit/Sharded_(16)-12                  8769058               136.0 ns/op             0 B/op          0 allocs/op
BenchmarkGetHit/Sharded_(32)-12                  8685018               136.1 ns/op             0 B/op          0 allocs/op
BenchmarkPutNew/Sharded_(2)-12                     12978            175136 ns/op              75 B/op          1 allocs/op
BenchmarkPutNew/Sharded_(3)-12                     20353            156025 ns/op             143 B/op          2 allocs/op
BenchmarkPutNew/Sharded_(16)-12                    93429            129674 ns/op             146 B/op          2 allocs/op
BenchmarkPutNew/Sharded_(32)-12                   145015            105135 ns/op             172 B/op          2 allocs/op
BenchmarkPutNew/Simple-12                          10000            181544 ns/op             168 B/op          2 allocs/op
BenchmarkPutNewNoPreWrite/Simple-12              1920939               627.5 ns/op           136 B/op          2 allocs/op
BenchmarkPutNewNoPreWrite/Sharded_(2)-12         2016948               725.2 ns/op           133 B/op          2 allocs/op
BenchmarkPutNewNoPreWrite/Sharded_(3)-12         2041030               594.4 ns/op           118 B/op          2 allocs/op
BenchmarkPutNewNoPreWrite/Sharded_(16)-12                2113516               627.2 ns/op           130 B/op          2 allocs/op
BenchmarkPutNewNoPreWrite/Sharded_(32)-12                1934784               684.8 ns/op           136 B/op          2 allocs/op
BenchmarkPutReplace-12                                   3397014               341.8 ns/op            64 B/op          1 allocs/op
BenchmarkPutAtMaxSize-12                                 3076584               380.7 ns/op            71 B/op          1 allocs/op
PASS
ok      github.com/goldstd/loadingcache 74.447s

Documentation

Overview

Package loadingcache provides a way for clients to create a cache capable of loading values on demand, should they get cache misses.

You can configure the cache to expire entries after a certain amount elapses since the last write and/or read.

This project is heavily inspired by Guava Cache (https://github.com/google/guava/wiki/CachesExplained).

All errors are wrapped by github.com/pkg/errors.Wrap. If you which to check the type of it, please use github.com/pkg/errors.Is.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrAlreadySet = errors.New("Cache already set")

ErrAlreadySet can be returned by Loader to tell the caller that the cache had already been set by its self.

View Source
var ErrKeyNotFound = errors.New("Key not found")

ErrKeyNotFound represents an error indicating that the key was not found

View Source
var StringFnvHashCodeFunc = func(k any) int {
	h := fnv.New32a()
	if _, err := h.Write([]byte(k.(string))); err != nil {
		panic(err)
	}
	return int(h.Sum32())
}

StringFnvHashCodeFunc is a hash code function for strings which uses fnv.New32a

Functions

This section is empty.

Types

type Cache

type Cache interface {
	// Get returns the value associated with a given key. If no entry exists for
	// the provided key, loadingcache.ErrKeyNotFound is returned.
	Get(key any, options ...GetOption) (any, error)

	// Put adds a value to the cache identified by a key.
	// If a value already exists associated with that key, it
	// is replaced.
	Put(key, value any)

	// Invalidate removes keys from the cache. If a key does not exist it is a noop.
	Invalidate(keys ...any)

	// InvalidateAll invalidates all keys
	InvalidateAll()

	// Close cleans up any resources used by the cache
	Close()

	// Stats returns the current stats
	Stats() Stats

	// IsSharded tells the implementation is a sharded cache for testing.
	IsSharded() bool
}

Cache describe the base interface to interact with a generic cache.

This interface reduces all keys and values to a generic any.

Example (AdvancedUsage)
package main

import (
	"fmt"
	"time"

	"github.com/goldstd/loadingcache"
)

func main() {
	cache := loadingcache.Config{
		MaxSize:          2,
		ExpireAfterRead:  2 * time.Minute,
		ExpireAfterWrite: time.Minute,
		EvictListeners: []loadingcache.RemovalListener{
			func(notification loadingcache.EvictNotification) {
				fmt.Printf("Entry removed due to %s\n", notification.Reason)
			},
		},
		Load: loadingcache.LoadFunc(func(key any, _ loadingcache.Cache) (any, error) {
			fmt.Printf("Loading key %v\n", key)
			return fmt.Sprint(key), nil
		}),
	}.Build()

	cache.Put(1, "1")
	val1, _ := cache.Get(1)
	fmt.Printf("%v\n", val1)

	val2, _ := cache.Get(2)
	fmt.Printf("%v\n", val2)

	val3, _ := cache.Get(3)
	fmt.Printf("%v\n", val3)

}
Output:

1
Loading key 2
2
Loading key 3
Entry removed due to Size
3
Example (SimpleUsage)
package main

import (
	"fmt"

	"github.com/goldstd/loadingcache"
	"github.com/pkg/errors"
)

func main() {
	cache := loadingcache.Config{}.Build()

	// Adding some values and reading them
	cache.Put("a", 1)
	cache.Put("b", 2)
	cache.Put("c", 3)
	val1, _ := cache.Get("a") // Don't forget to check for errors
	fmt.Printf("%v\n", val1)
	val2, _ := cache.Get("b") // Don't forget to check for errors
	fmt.Printf("%v\n", val2)

	// Getting a value that does not exist
	_, err := cache.Get("d")
	if errors.Is(err, loadingcache.ErrKeyNotFound) {
		fmt.Println("That key does not exist")
	}

	// Evicting
	cache.Invalidate("a")
	cache.Invalidate("b", "c")
	cache.InvalidateAll()

}
Output:

1
2
That key does not exist

type CacheOption

type CacheOption func(Cache)

CacheOption describes an option that can configure the cache

type Config

type Config struct {
	// Clock allows passing a custom clock to be used with the cache.
	//
	// This is useful for testing, where controlling time is important.
	Clock clock.Clock

	// Load configures a loading function
	Load Loader

	// ShardHashFunc is a function that produces a hashcode of the key.
	//
	// See https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/Object.html#hashCode()
	// for best practices surrounding hash code functions.
	ShardHashFunc func(key any) int

	// EvictListeners configures a removal listeners
	EvictListeners []RemovalListener

	// ExpireAfterWrite configures the cache to expire entries after
	// a given duration after writing.
	ExpireAfterWrite time.Duration

	// ExpireAfterRead configures the cache to expire entries after
	// a given duration after reading.
	ExpireAfterRead time.Duration

	// EvictInterval controls if a background go routine should be created
	// which automatically evicts entries that have expired. If not specified,
	// no background goroutine will be created.
	//
	// The background go routine runs with the provided frequency.
	// To avoid go routine leaks, use the close function when you're done with the cache.
	EvictInterval time.Duration

	// MaxSize limits the number of entries allowed in the cache.
	// If the limit is achieved, an eviction process will take place,
	// this means that eviction policies will be executed such as write
	// time, read time or random entry if no eviction policy frees up
	// space.
	//
	// If the cache is sharded, MaxSize is applied to each shard,
	// meaning that the overall capacity will be MaxSize * ShardCount.
	MaxSize uint32

	// ShardCount indicates how many shards will be used by the cache.
	// This allows some degree of parallelism in read and writing to the cache.
	//
	// If the shard count is greater than 1, then ShardHashFunc must be provided
	// otherwise the constructor will panic.
	ShardCount uint32

	// AsyncLoad configures loading in async way after cache expired
	AsyncLoad bool
}

Config available options to initialize the cache

func (Config) Build

func (c Config) Build() Cache

Build instantiates a new cache

type EvictNotification

type EvictNotification struct {
	Key    any
	Value  any
	Reason EvictReason
}

EvictNotification is passed to listeners everytime an entry is removed

type EvictReason

type EvictReason int

EvictReason is an enum describing the causes for an entry to be removed from the cache.

const (
	// EvictReasonExplicit means the entry was explicitly invalidated
	EvictReasonExplicit EvictReason = iota

	// EvictReasonReplaced means the entry was replaced by a new one
	EvictReasonReplaced

	// EvictReasonReadExpired means the entry read expired, e.g. too much time
	// since last read/write.
	EvictReasonReadExpired

	// EvictReasonWriteExpired means the entry write expired, e.g. too much time
	// since last read/write.
	EvictReasonWriteExpired

	// EvictReasonSize means the entry was removed due to the cache size.
	EvictReasonSize
)

func (EvictReason) String

func (r EvictReason) String() string

type GetOption

type GetOption struct {
	Load Loader
}

GetOption describes the option for Get

type LoadFunc

type LoadFunc func(key any, cache Cache) (any, error)

LoadFunc represents a function that given a key, it returns a value or an error.

func (LoadFunc) Load

func (f LoadFunc) Load(key any, cache Cache) (any, error)

Load implements the Loader interface.

type Loader

type Loader interface {
	// Load loads the latest value by the key.
	// err can be ErrAlreadySet to indicate that the loader had been set the cache.
	Load(key any, cache Cache) (any, error)
}

Loader represents an interface for loading cache value.

type RemovalListener

type RemovalListener func(EvictNotification)

RemovalListener represents a removal listener

type Stats

type Stats interface {
	// EvictionCount is the number of times an entry has been evicted
	EvictionCount() int64

	// HitCount the number of times Cache lookup methods have returned a cached value
	HitCount() int64

	// HitRate is the ratio of cache requests which were hits. This is defined as
	// hitCount / requestCount, or 1.0 when requestCount == 0
	HitRate() float64

	// MissCount is the number of times Cache lookup methods have returned an uncached
	// (newly loaded) value
	MissCount() int64

	// MissRate is the ratio of cache requests which were misses. This is defined as
	// missCount / requestCount, or 0.0 when requestCount == 0
	MissRate() float64

	// RequestCount is the number of times Cache lookup methods have returned either a cached or
	// uncached value. This is defined as hitCount + missCount
	RequestCount() int64

	// LoadSuccessCount is the number of times Cache lookup methods have successfully
	// loaded a new value
	LoadSuccessCount() int64

	// LoadErrorCount is the number of times Cache lookup methods threw an exception while loading
	// a new value
	LoadErrorCount() int64

	// LoadErrorRate is the ratio of cache loading attempts which threw exceptions.
	// This is defined as loadExceptionCount / (loadSuccessCount + loadExceptionCount), or 0.0 when
	// loadSuccessCount + loadExceptionCount == 0
	LoadErrorRate() float64

	// LoadCount the total number of times that Cache lookup methods attempted to load new values.
	// This includes both successful load operations, and those that threw exceptions.
	// This is defined as loadSuccessCount + loadExceptionCount
	LoadCount() int64

	// LoadTotalTime is the total duration the cache has spent loading new values
	LoadTotalTime() time.Duration

	// AverageLoadPenalty is the average duration spent loading new values. This is defined as
	// totalLoadTime / (loadSuccessCount + loadExceptionCount).
	AverageLoadPenalty() time.Duration
}

Stats exposes cache relevant metrics.

Be aware that this interface may be exposing a live stats collector, and as such if you manually calculate rates, values may differ if calls to the cache have occurred between calls.

Directories

Path Synopsis
cmd
internal
loader

Jump to

Keyboard shortcuts

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