cache

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 23, 2024 License: MIT Imports: 10 Imported by: 0

README

Simple cache package

This package is a simple in-memory key-value storage based on golang map type. Check docs for more details.

Data is stored as any interface inside a unit struct, which carries additional metadata.

Import

import "github.com/emar-kar/cache"

Cache options

  • WithDefaultLifetime - sets default lifetime of every key added into the cache
  • WithCleanupInteval - sets janitor run interval
  • WithMaxUnits - sets max number of keys in cache
  • WithMaxSize - sets max size of stored data in bytes
  • WithOnEvictionFn - sets custom function which is triggered on key removal by janitor or manually
  • WithoutJanitorEviction - defines janitor behavior if it should trigger on eviction function on clean up
  • WithDataSizeFn - sets custom function to define size of the data

Keys set options

  • WithLifetime - sets custom lifetime for cache key
  • WithSize - sets custom size for key data. If set, cache will ignore size calculation and use passed value. Adds default metadata size

In addition to global cache options, user can set individual lifetime per key or set data size to omit auto calculations.

Available functionality

Func What it does
Get Returns data of the given key
Scan Scans current snapshot of the cache data and returns key-value map if key contains given sub-string
ScanFunc Scans current snapshot of the cache and returns key-value map if given func returns true for a key
Set Saves data in cache with given key and options. If key already exists it will be replaced without warnings
Add Sets data in cache if given key does not exist
Replace Replaces data of the given key only if this key exists in cache and is not expired
Rename Renames old key with a new name only if given key exists in cache and is not expired
Remove Removes key with given name from cache. Do nothing if key does not exist. Does not apply on eviction function even if it was set
RemoveAll Removes all keys from cache. Does not apply on eviction function even if it was set. Runs GC to collect released memory
RemoveExpired Removes only expired keys. Does not apply on eviction function even if it was set
Delete Removes key with given name from cache. Do nothing if key does not exist. Applies on eviction function if it was set
DeleteAll Removes all keys from cache applying on eviction function if it was set
DeleteExpired Removes only expired keys applying on eviction function if it was set
Alive Creates copy of the cache with not expired keys data
Snapshot Creates copy of the cache with all keys data
Revive Prolongs lifetime of the key with default value from cache options
ReviveUntil Prolongs lifetime of the key with specified value
Length Returns number of keys in cache
Size Returns current size of the cache in bytes
ChangeMaxSize Updates cache default options with new cache max size in bytes
ChangeMaxLength Updates cache default options with new max number of keys
StopCleaning Stops current janitor if it was set. This function waits until janitor is unlocked if it is in cleaning progress
ChangeDefaultLifeTime Updates cache default options with new default lifetime for key
ChangeSizeFn Updates cache default options with new function to define data size
ChangeOnEvictionFn Updates cache default options with new function which runs when key is being cleaned after expiration. If janitor is cleaning cache, this function will wait until it finishes, before changing on eviction function
OrderCleaning Stops current janitor if it was set and starts a new one with cache default cleanup interval
RescheduleCleaning Stops current janitor if it was set, updates cache default cleanup interval with given duration and starts a new janitor
ChangeJanitorOnEviction Updates cache default options with new janitor expiried keys removal behavior. Allows to control if janitor should apply on eviction function even if it was set. Restart janitor if it's currently running

Increment/decrement values

Package has two exported functions, which recive Cache as first argument and support increment and decrement of the stored values by given N. It was done this way to support generics with Integer type:

type Integer interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
func Increment[T Integer](c *Cache, k string, n T) (T, error)

func Decrement[T Integer](c *Cache, k string, n T) (T, error)

Those functions will return error if key does not exist, was expired or it's data type assertion failed.

Eviction with goroutines

Since it was decided to remove explicit goroutine call for eviction functions in Delete* methods, here is the workaround how to implement this anyway:

goEviction := func() func(string, any) {
    return func(str string, a any) {
        go func() {
            // Do something with key and value...
        }()
    }
}

c := New(WithOnEvictionFn(goEviction()))

if err := c.Set("foo", "simple string"); err != nil {
    // Process error...
}

c.Delete("foo")

// Wait until goroutine finish onEviction...

Save/Load

Experimentally there is a support of marshal/unmarshal data to json format. Since data is stored as any interface, it can dump custom formats as well. Those features are experimental and might change in future. Check examples for more details.

c := New(
    WithDefaultLifetime(uint64(time.Hour)),
    WithMaxSize(1028),
)

if err := c.Set("foo", "simple string"); err != nil {
    // Process error...
}

type test struct {
    Str string `json:"str"`
}

testStruct := &test{"string in struct"}

if err := c.Set("foo2", testStruct); err != nil {
    // Process error...
}

if err := c.Set("foo3", []string{"string in slice"}); err != nil {
    // Process error...
}

if err := c.Set("foo4", map[string]string{"bar": "string in map"}); err != nil {
    // Process error...
}

dumpFile := "dump.json"

f, err := os.Create(dumpFile)
if err != nil {
    // Process error...
}

if err := c.Save(f); err != nil {
    f.Close()
    // Process error...
}

f.Close()
c.RemoveAll()

f, err = os.Open(dumpFile)
if err != nil {
    // Process error...
}
defer f.Close()

if err := c.Load(f); err != nil {
    // Process error...
}

str, err := c.Get("foo")
if err != nil {
    // Process error...
}

fmt.Println(str) // Prints: "simple string"

if str, err := c.Get("foo2"); err != nil {
    // Process error...
} else {
    jsonData, err := json.Marshal(str)
    if err != nil {
        // Process error...
    }

    var structData test
    if err := json.Unmarshal(jsonData, &structData); err != nil {
        // Process error...
    }

    // structData.Str == "string in struct".
}

if str, err := c.Get("foo3"); err != nil {
    // Process error...
} else {
    sl := make([]string, len(str.([]any)))
    for i, el := range str.([]any) {
        sl[i] = el.(string)
    }

    // sl[0] == "string in slice".
}

if str, err := c.Get("foo4"); err != nil {
    // Process error...
} else {
    m := make(map[string]string, len(str.(map[string]any)))
    for k, v := range str.(map[string]any) {
        m[k] = v.(string)
    }

    // m["bar"] == "string in map".
}

Some productivity tests:

BenchmarkCacheGetDataWithLifetime-10            30645175                37.39 ns/op            0 B/op          0 allocs/op
BenchmarkCacheGetData-10                        66297579                18.03 ns/op            0 B/op          0 allocs/op
BenchmarkCacheGetExpiredConcurrent-10            8271970               135.1 ns/op             0 B/op          0 allocs/op
BenchmarkCacheGetDataConcurrent-10               9178712               135.5 ns/op             0 B/op          0 allocs/op
BenchmarkCacheSetWithOpts-10                    21413689                56.22 ns/op            0 B/op          0 allocs/op
BenchmarkCacheSetData-10                        35918688                30.92 ns/op            0 B/op          0 allocs/op
BenchmarkCacheIncrement-10                      25929056                45.62 ns/op            7 B/op          0 allocs/op
BenchmarkCacheDecrement-10                      25951790                45.72 ns/op            7 B/op          0 allocs/op

Documentation

Overview

Package cache is a simple implementation of in-memory key-value storage based on golang map type. This package allows to setup various options, such as values expiration time (default, individual), max number of entries in cache, max byte size of data which can be stored in cache. Data can be dumped into json file and restored from it with all saved metadata. Cache usage is thread safe and it can be accessed from multiple goroutines.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrDuration  = errors.New("non-positive duration")
	ErrExists    = errors.New("already exists")
	ErrExpired   = errors.New("is expired")
	ErrNotExists = errors.New("does not exist")
	ErrMaxSize   = errors.New("max size limit")
	ErrMaxLength = errors.New("max data limit")
	ErrNotInt    = errors.New("data type is not integer")
)

Functions

func Decrement

func Decrement[T Integer](c *Cache, k string, n T) (T, error)

Decrement decrements data of the given key with n. Returns error if key does not exist, was expired or if type assertion to Integer has failed.

func Increment

func Increment[T Integer](c *Cache, k string, n T) (T, error)

Increment increments data of the given key with n. Returns error if key does not exist, was expired or if type assertion to Integer has failed.

func WithCleanupInteval

func WithCleanupInteval(d time.Duration) cacheOptFn

WithCleanupInteval sets interval for janitor to clean expired keys.

func WithDataSize deprecated

func WithDataSize(fn func(string, any) (uint64, error)) cacheOptFn

WithDataSize sets function which defines data size.

Deprecated: use WithDataSizeFn instead.

func WithDataSizeFn added in v1.0.1

func WithDataSizeFn(fn func(string, any) (uint64, error)) cacheOptFn

WithDataSizeFn sets function which defines data size.

func WithDefaultLifetime

func WithDefaultLifetime(lt uint64) cacheOptFn

WithDefaultLifetime sets default lifetime for key in cache.

func WithLifetime

func WithLifetime(lt uint64) unitOptFn

WithLifetime sets custom lifetime for cache key.

func WithMaxSize

func WithMaxSize(ms uint64) cacheOptFn

WithMaxSize sets maximum cache data size in bytes.

func WithMaxUnits

func WithMaxUnits(mu uint64) cacheOptFn

WithMaxUnits sets maxixum number of keys, which can be stored in cache.

func WithOnEviction deprecated

func WithOnEviction(fn func(string, any)) cacheOptFn

WithOnEviction sets custom function which is applied when key is being deleted from cache.

Deprecated: use WithOnEvictionFn instead.

func WithOnEvictionFn added in v1.0.1

func WithOnEvictionFn(fn func(string, any)) cacheOptFn

WithOnEvictionFn sets custom function which is applied when key is being deleted from cache.

func WithSize

func WithSize(s uint64) unitOptFn

WithSize sets custom size for key data. If set, cache will ignore size calculation and use passed value. Adds default metadata size.

func WithoutJanitorEviction

func WithoutJanitorEviction(co *cacheOpts)

WithoutJanitorEviction sets janitor to clean expired keys without applying on eviction function even if it was set.

Types

type Cache

type Cache struct {
	// contains filtered or unexported fields
}
Example
c := New()

if err := c.Set("foo", "bar"); err != nil {
	// Process error...
}

data, err := c.Get("foo")
if err != nil {
	// Process error...
}

fmt.Println(data) // Prints: "bar".
Output:

Example (SaveLoad)
c := New(
	WithDefaultLifetime(uint64(time.Hour)),
	WithMaxSize(1028),
)

if err := c.Set("foo", "simple string"); err != nil {
	// Process error...
}

type test struct {
	Str string `json:"str"`
}

testStruct := &test{"string in struct"}

if err := c.Set("foo2", testStruct); err != nil {
	// Process error...
}

if err := c.Set("foo3", []string{"string in slice"}); err != nil {
	// Process error...
}

if err := c.Set("foo4", map[string]string{"bar": "string in map"}); err != nil {
	// Process error...
}

dumpFile := "dump.json"

f, err := os.Create(dumpFile)
if err != nil {
	// Process error...
}

if err := c.Save(f); err != nil {
	f.Close()
	// Process error...
}

f.Close()
c.RemoveAll()

f, err = os.Open(dumpFile)
if err != nil {
	// Process error...
}
defer f.Close()

if err := c.Load(f); err != nil {
	// Process error...
}

str, err := c.Get("foo")
if err != nil {
	// Process error...
}

fmt.Println(str) // Prints: "simple string".

if str, err := c.Get("foo2"); err != nil {
	// Process error...
} else {
	jsonData, err := json.Marshal(str)
	if err != nil {
		// Process error...
	}

	var structData test
	if err := json.Unmarshal(jsonData, &structData); err != nil {
		// Process error...
	}

	// structData.Str == "string in struct".
}

if str, err := c.Get("foo3"); err != nil {
	// Process error...
} else {
	sl := make([]string, len(str.([]any)))
	for i, el := range str.([]any) {
		sl[i] = el.(string)
	}

	// sl[0] == "string in slice".
}

if str, err := c.Get("foo4"); err != nil {
	// Process error...
} else {
	m := make(map[string]string, len(str.(map[string]any)))
	for k, v := range str.(map[string]any) {
		m[k] = v.(string)
	}

	// m["bar"] == "string in map".
}
Output:

Example (WithOptions)
c := New(
	WithCleanupInteval(time.Minute),
	WithDefaultLifetime(uint64(500*time.Millisecond)),
)

if err := c.Set("foo", "bar"); err != nil {
	// Process error...
}

time.Sleep(1 * time.Second)

data, err := c.Get("foo")
fmt.Println(data) // Prints: "bar".
fmt.Println(err)  // Prints: key "foo": is expired.

c.RemoveExpired()

_, err = c.Get("foo")
fmt.Println(err) // Prints: key "foo": does not exist.

if err := c.Set("foo", "bar", WithLifetime(uint64(1*time.Second))); err != nil {
	// Process error...
}

time.Sleep(500 * time.Millisecond)

m := c.Alive()
if v, ok := m["foo"]; ok {
	fmt.Println(v) // Prints: "bar".
}
Output:

func New

func New(opts ...cacheOptFn) *Cache

New creates new Cache instance with given options.

func (*Cache) Add

func (c *Cache) Add(k string, a any, opts ...unitOptFn) error

Add sets data in cache only if given key does not exist.

func (*Cache) Alive

func (c *Cache) Alive() map[string]any

Alive creates copy of the cache with not expired keys data.

func (*Cache) ChangeDefaultLifeTime

func (c *Cache) ChangeDefaultLifeTime(lt uint64)

ChangeDefaultLifeTime updates cache default options with new default lifetime for key. Does not affect keys already in cache.

func (*Cache) ChangeJanitorOnEviction

func (c *Cache) ChangeJanitorOnEviction(b bool)

ChangeJanitorOnEviction updates cache default options with new janitor expiried keys removal behavior. Allows to control if janitor should apply on eviction function even if it was set. Restart janitor if it's currently running.

func (*Cache) ChangeMaxLength

func (c *Cache) ChangeMaxLength(ml uint64) error

ChangeMaxLength updates cache default options with new max number of keys. Returns ErrMaxLength if new value is lower than number of keys already in cache.

func (*Cache) ChangeMaxSize

func (c *Cache) ChangeMaxSize(i uint64) error

ChangeMaxSize updates cache default options with new cache max size in bytes.

func (*Cache) ChangeOnEviction

func (c *Cache) ChangeOnEviction(fn func(string, any))

ChangeOnEviction updates cache default options with new function which runs when key is being cleaned after expiration. If janitor is cleaning cache, this function will wait until it finishes, before changing on eviction function. Deprecated: use [ChangeOnEvictionFn] instead.

func (*Cache) ChangeOnEvictionFn added in v1.0.1

func (c *Cache) ChangeOnEvictionFn(fn func(string, any))

ChangeOnEvictionFn updates cache default options with new function which runs when key is being cleaned after expiration. If janitor is cleaning cache, this function will wait until it finishes, before changing on eviction function.

func (*Cache) ChangeSizeFn

func (c *Cache) ChangeSizeFn(fn func(string, any) (uint64, error))

ChangeSizeFn updates cache default options with new function to define data size. Does not affect keys already in cache.

func (*Cache) Delete

func (c *Cache) Delete(k string)

Delete removes key with given name from cache. Do nothing if key does not exist. Applies on eviction function if it was set.

func (*Cache) DeleteAll

func (c *Cache) DeleteAll()

DeleteAll removes all keys from cache applying on eviction function if it was set.

func (*Cache) DeleteExpired

func (c *Cache) DeleteExpired()

DeleteExpired removes only expired keys applying on eviction function if it was set.

func (*Cache) Get

func (c *Cache) Get(k string) (any, error)

Get returns data of the given key. If key does not exist then ErrNotExists will be returned. If key is already expired, but was not yet cleaned, returns data and ErrExpired as error.

func (*Cache) Length

func (c *Cache) Length() int

Length returns number of keys in cache.

func (*Cache) Load

func (c *Cache) Load(r io.Reader) error

Load restore cache from the given io.Reader with json unmarshaller.

func (*Cache) OrderCleaning

func (c *Cache) OrderCleaning()

OrderCleaning stops current janitor if it was set and starts a new one with cache default cleanup interval.

func (*Cache) Remove

func (c *Cache) Remove(k string)

Remove removes key with given name from cache. Do nothing if key does not exist. Does not apply on eviction function even if it was set.

func (*Cache) RemoveAll

func (c *Cache) RemoveAll()

RemoveAll removes all keys from cache. Does not apply on eviction function even if it was set. Runs GC to collect released memory.

func (*Cache) RemoveExpired

func (c *Cache) RemoveExpired()

RemoveExpired removes only expired keys. Does not apply on eviction function even if it was set.

func (*Cache) Rename

func (c *Cache) Rename(oldKey, newKey string) error

Rename renames old key with a new name only if given key exists in cache and is not expired.

func (*Cache) Replace

func (c *Cache) Replace(k string, a any) error

Replace replaces data of the given key only if this key exists in cache and is not expired.

func (*Cache) RescheduleCleaning

func (c *Cache) RescheduleCleaning(d time.Duration) error

RescheduleCleaning stops current janitor if it was set, updates cache default cleanup interval with given duration and starts a new janitor.

func (*Cache) Revive

func (c *Cache) Revive(k string) error

Revive prolongs lifetime of the key with default value from cache options.

func (*Cache) ReviveUntil

func (c *Cache) ReviveUntil(k string, lt uint64) error

ReviveUntil prolongs lifetime of the key with specified value.

func (*Cache) Save

func (c *Cache) Save(w io.Writer) error

Save dumps cache into the given io.Writer with json marshaller.

func (*Cache) Scan

func (c *Cache) Scan(sub string) map[string]any

Scan scans current [Snapshot] of the cache data and returns key-value map if key contains given sub-string.

func (*Cache) ScanFunc

func (c *Cache) ScanFunc(fn func(string) bool) map[string]any

ScanFunc scans current [Snapshot] of the cache and returns key-value map if given func returns true for a key.

func (*Cache) Set

func (c *Cache) Set(k string, a any, opts ...unitOptFn) error

Set saves data in cache with given key and options. If key already exists it will be replaced without warnings.

func (*Cache) Size

func (c *Cache) Size() uint64

Size returns current size of the cache in bytes.

func (*Cache) Snapshot

func (c *Cache) Snapshot() map[string]any

Snapshot creates copy of the cache with all keys data.

func (*Cache) Stats

func (c *Cache) Stats() *Stats

Stats gets current cache state.

func (*Cache) StopCleaning

func (c *Cache) StopCleaning()

StopCleaning stops current janitor if it was set. This function waits until janitor is unlocked if it is in cleaning progress.

type Integer

type Integer interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}

type Stats

type Stats struct {
	CleanupInterval time.Duration
	DefaultLifetime uint64
	CurrentLength   int
	MaxLength       uint64
	MaxSize         uint64
	CurrentSize     uint64
}

Stats represents current cache statistics.

Jump to

Keyboard shortcuts

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