gocache

package module
v1.2.4 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2021 License: MIT Imports: 13 Imported by: 2

README

gocache

build Go Report Card codecov Go version Go Reference Follow TwiN

gocache is an easy-to-use, high-performance, lightweight and thread-safe (goroutine-safe) in-memory key-value cache with support for LRU and FIFO eviction policies as well as expiration, bulk operations and even persistence to file.

Table of Contents

Features

gocache supports the following cache eviction policies:

  • First in first out (FIFO)
  • Least recently used (LRU)

It also supports cache entry TTL, which is both active and passive. Active expiration means that if you attempt to retrieve a cache key that has already expired, it will delete it on the spot and the behavior will be as if the cache key didn't exist. As for passive expiration, there's a background task that will take care of deleting expired keys.

It also includes what you'd expect from a cache, like bulk operations, persistence and patterns.

While meant to be used as a library, there's a Redis-compatible cache server included. See the Server section. It may also serve as a good reference to use in order to implement gocache in your own applications.

Usage

go get -u github.com/TwiN/gocache

If you're interested in using gocache as a server rather than an embedded library, see Server

Initializing the cache
cache := gocache.NewCache().WithMaxSize(1000).WithEvictionPolicy(gocache.LeastRecentlyUsed)

If you're planning on using expiration (SetWithTTL or Expire) and you want expired entries to be automatically deleted in the background, make sure to start the janitor when you instantiate the cache:

cache.StartJanitor()
Functions
Function Description
WithMaxSize Sets the max size of the cache. gocache.NoMaxSize means there is no limit. If not set, the default max size is gocache.DefaultMaxSize.
WithMaxMemoryUsage Sets the max memory usage of the cache. gocache.NoMaxMemoryUsage means there is no limit. The default behavior is to not evict based on memory usage.
WithEvictionPolicy Sets the eviction algorithm to be used when the cache reaches the max size. If not set, the default eviction policy is gocache.FirstInFirstOut (FIFO).
WithForceNilInterfaceOnNilPointer Configures whether values with a nil pointer passed to write functions should be forcefully set to nil. Defaults to true.
StartJanitor Starts the janitor, which is in charge of deleting expired cache entries in the background.
StopJanitor Stops the janitor.
Set Same as SetWithTTL, but with no expiration (gocache.NoExpiration)
SetAll Same as Set, but in bulk
SetWithTTL Creates or updates a cache entry with the given key, value and expiration time. If the max size after the aforementioned operation is above the configured max size, the tail will be evicted. Depending on the eviction policy, the tail is defined as the oldest
Get Gets a cache entry by its key.
GetByKeys Gets a map of entries by their keys. The resulting map will contain all keys, even if some of the keys in the slice passed as parameter were not present in the cache.
GetAll Gets all cache entries.
GetKeysByPattern Retrieves a slice of keys that matches a given pattern.
Delete Removes a key from the cache.
DeleteAll Removes multiple keys from the cache.
Count Gets the size of the cache. This includes cache keys which may have already expired, but have not been removed yet.
Clear Wipes the cache.
TTL Gets the time until a cache key expires.
Expire Sets the expiration time of an existing cache key.
SaveToFile Stores the content of the cache to a file so that it can be read using ReadFromFile. See persistence.
ReadFromFile Populates the cache using a file created using SaveToFile. See persistence.

For further documentation, please refer to Go Reference

Examples
Creating or updating an entry
cache.Set("key", "value") 
cache.Set("key", 1)
cache.Set("key", struct{ Text string }{Test: "value"})
cache.SetWithTTL("key", []byte("value"), 24*time.Hour)
Getting an entry
value, exists := cache.Get("key")

You can also get multiple entries by using cache.GetByKeys([]string{"key1", "key2"})

Deleting an entry
cache.Delete("key")

You can also delete multiple entries by using cache.DeleteAll([]string{"key1", "key2"})

Complex example
package main

import (
    "fmt"
    "time"

    "github.com/TwiN/gocache"
)

func main() {
    cache := gocache.NewCache().WithEvictionPolicy(gocache.LeastRecentlyUsed).WithMaxSize(10000)
    cache.StartJanitor() // Passively manages expired entries

    cache.Set("key", "value")
    cache.SetWithTTL("key-with-ttl", "value", 60*time.Minute)
    cache.SetAll(map[string]interface{}{"k1": "v1", "k2": "v2", "k3": "v3"})

    value, exists := cache.Get("key")
    fmt.Printf("[Get] key=key; value=%s; exists=%v\n", value, exists)
    for key, value := range cache.GetByKeys([]string{"k1", "k2", "k3"}) {
        fmt.Printf("[GetByKeys] key=%s; value=%s\n", key, value)
    }
    for _, key := range cache.GetKeysByPattern("key*", 0) {
        fmt.Printf("[GetKeysByPattern] key=%s\n", key)
    }

    fmt.Println("Cache size before persisting cache to file:", cache.Count())
    err := cache.SaveToFile("cache.bak")
    if err != nil {
        panic(fmt.Sprintf("failed to persist cache to file: %s", err.Error()))
    }

    cache.Expire("key", time.Hour)
    time.Sleep(500*time.Millisecond)
    timeUntilExpiration, _ := cache.TTL("key")
    fmt.Println("Number of minutes before 'key' expires:", int(timeUntilExpiration.Seconds()))

    cache.Delete("key")
    cache.DeleteAll([]string{"k1", "k2", "k3"})

    fmt.Println("Cache size before restoring cache from file:", cache.Count())
    _, err = cache.ReadFromFile("cache.bak")
    if err != nil {
        panic(fmt.Sprintf("failed to restore cache from file: %s", err.Error()))
    }

    fmt.Println("Cache size after restoring cache from file:", cache.Count())
    cache.Clear()
    fmt.Println("Cache size after clearing the cache:", cache.Count())
}
Output
[Get] key=key; value=value; exists=true
[GetByKeys] key=k2; value=v2
[GetByKeys] key=k3; value=v3
[GetByKeys] key=k1; value=v1
[GetKeysByPattern] key=key
[GetKeysByPattern] key=key-with-ttl
Cache size before persisting cache to file: 5
Number of minutes before 'key' expires: 3599
Cache size before restoring cache from file: 1
Cache size after restoring cache from file: 5
Cache size after clearing the cache: 0

Persistence

While gocache is an in-memory cache, you can still save the content of the cache in a file and vice versa.

To save the content of the cache to a file:

err := cache.SaveToFile(TestCacheFile)

To retrieve the content of the cache from a file:

numberOfEntriesEvicted, err := newCache.ReadFromFile(TestCacheFile)

The numberOfEntriesEvicted will be non-zero only if the number of entries in the file is higher than the cache's configured MaxSize.

Limitations

While you can cache structs in memory out of the box, persisting structs to a file requires you to register the custom interfaces that your application uses with the gob package.

type YourCustomStruct struct {
    A string
    B int
}

// ...
cache.Set("key", YourCustomStruct{A: "test", B: 123})

To persist your custom struct properly:

gob.Register(YourCustomStruct{})
cache.SaveToFile("gocache.bak")

The same applies for restoring the cache from a file:

cache := NewCache()
gob.Register(YourCustomStruct{})
cache.ReadFromFile(TestCacheFile)
value, _ := cache.Get("key")
fmt.Println(value.(YourCustomStruct))

You only need to persist the struct once, so adding the following function in a file would suffice:

func init() {
    gob.Register(YourCustomStruct{})
}

Failure to register your custom structs will prevent gocache from persisting and/or parsing the value of each keys that use said custom structs.

That being said, assuming that you're using gocache as a cache, this shouldn't create any bugs on your end, because every key that cannot be parsed are not populated into the cache by ReadFromFile.

In other words, if you're falling back to a database or something similar when the cache doesn't have the key requested, you'll be fine.

Note that if you need to modify the type of a variable in a struct, you should change the name of that variable as well. For instance, if the struct has a CreatedAt variable with the type time.Time and that variable type is later modified to uint64, decoding the struct would fail, however, if you rename the variable to CreatedAtUnixTimeInMs, there won't be any decoding issues other than the loss of data for that field. You could also obviously handle the migration gracefully by keeping both variables, populating the CreatedAtUnixTimeInMs variable with the CreatedAt value and then removing the CreatedAt field.

Eviction

MaxSize

Eviction by MaxSize is the default behavior, and is also the most efficient.

The code below will create a cache that has a maximum size of 1000:

cache := gocache.NewCache().WithMaxSize(1000)

This means that whenever an operation causes the total size of the cache to go above 1000, the tail will be evicted.

MaxMemoryUsage

Eviction by MaxMemoryUsage is disabled by default, and is in alpha.

The code below will create a cache that has a maximum memory usage of 50MB:

cache := gocache.NewCache().WithMaxSize(0).WithMaxMemoryUsage(50*gocache.Megabyte)

This means that whenever an operation causes the total memory usage of the cache to go above 50MB, one or more tails will be evicted.

Unlike evictions caused by reaching the MaxSize, evictions triggered by MaxMemoryUsage may lead to multiple entries being evicted in a row. The reason for this is that if, for instance, you had 100 entries of 0.1MB each and you suddenly added a single entry of 10MB, 100 entries would need to be evicted to make enough space for that new big entry.

It's very important to keep in mind that eviction by MaxMemoryUsage is approximate.

The only memory taken into consideration is the size of the cache, not the size of the entire application. If you pass along 100MB worth of data in a matter of seconds, even though the cache's memory usage will remain under 50MB (or whatever you configure the MaxMemoryUsage to), the memory footprint generated by that 100MB will still exist until the next GC cycle.

As previously mentioned, this is a work in progress, and here's a list of the things you should keep in mind:

  • The memory usage of structs are a gross estimation and may not reflect the actual memory usage.
  • Native types (string, int, bool, []byte, etc.) are the most accurate for calculating the memory usage.
  • Adding an entry bigger than the configured MaxMemoryUsage will work, but it will evict all other entries.

Expiration

There are two ways that the deletion of expired keys can take place:

  • Active
  • Passive

Active deletion of expired keys happens when an attempt is made to access the value of a cache entry that expired. Get, GetByKeys and GetAll are the only functions that can trigger active deletion of expired keys.

Passive deletion of expired keys runs in the background and is managed by the janitor. If you do not start the janitor, there will be no passive deletion of expired keys.

Server

For the sake of convenience, a ready-to-go cache server is available through the server package.

As an application
package main

import (
    "github.com/TwiN/gocache"
    gocacheserver "github.com/TwiN/gocache/server"
)

func main() {
    cache := gocache.NewCache().WithEvictionPolicy(gocache.LeastRecentlyUsed).WithMaxSize(100000)
    server := gocacheserver.NewServer(cache).WithPort(6379)
    // This is a blocking function, therefore, you are expected to run this on a goroutine
    server.Start()
}

The reason why the server is in a different package is because gocache limit its external dependencies to the strict minimum (e.g. boltdb for persistence), however, rather than re-inventing the wheel, the server implementation uses redcon, which is a very good Redis server framework for Go.

That way, those who desire to use gocache without the server will not add any extra dependencies as long as they don't import the server package.

If you'd like to run it through the CLI:

go run cmd/server/main.go

Any Redis client should be able to interact with the server, though only the following instructions are supported:

  • GET
  • SET
  • DEL
  • PING
  • QUIT
  • INFO
  • EXPIRE
  • SETEX
  • TTL
  • FLUSHDB
  • EXISTS
  • ECHO
  • MGET
  • MSET
  • SCAN (kind of - cursor is not currently supported)
  • KEYS

Running the server with Docker

Docker pulls

docker run --name gocache-server -p 6379:6379 twinproduction/gocache-server

To build it locally, refer to the Makefile's docker-build and docker-run steps.

Performance

Summary
  • Set: Both map and gocache have the same performance.
  • Get: Map is faster than gocache.

This is because gocache keeps track of the head and the tail for eviction and expiration/TTL.

Ultimately, the difference is negligible.

We could add a way to disable eviction or disable expiration altogether just to match the map's performance, but if you're looking into using a library like gocache, odds are, you want more than just a map.

Results
key value
goos windows
goarch amd64
cpu i7-9700K
mem 32G DDR4
// Normal map
BenchmarkMap_Get
BenchmarkMap_Get-8                                                              46087372  26.7 ns/op
BenchmarkMap_Set                                                                   
BenchmarkMap_Set/small_value-8                                                   3841911   389 ns/op
BenchmarkMap_Set/medium_value-8                                                  3887074   391 ns/op
BenchmarkMap_Set/large_value-8                                                   3921956   393 ns/op
// Gocache                                                                         
BenchmarkCache_Get                                                                 
BenchmarkCache_Get/FirstInFirstOut-8                                            27273036  46.4 ns/op
BenchmarkCache_Get/LeastRecentlyUsed-8                                          26648248  46.3 ns/op
BenchmarkCache_Set                                                              
BenchmarkCache_Set/FirstInFirstOut_small_value-8                                 2919584   405 ns/op
BenchmarkCache_Set/FirstInFirstOut_medium_value-8                                2990841   391 ns/op
BenchmarkCache_Set/FirstInFirstOut_large_value-8                                 2970513   391 ns/op
BenchmarkCache_Set/LeastRecentlyUsed_small_value-8                               2962939   402 ns/op
BenchmarkCache_Set/LeastRecentlyUsed_medium_value-8                              2962963   390 ns/op
BenchmarkCache_Set/LeastRecentlyUsed_large_value-8                               2962928   394 ns/op
BenchmarkCache_SetUsingMaxMemoryUsage                                           
BenchmarkCache_SetUsingMaxMemoryUsage/small_value-8                              2683356   447 ns/op
BenchmarkCache_SetUsingMaxMemoryUsage/medium_value-8                             2637578   441 ns/op
BenchmarkCache_SetUsingMaxMemoryUsage/large_value-8                              2672434   443 ns/op
BenchmarkCache_SetWithMaxSize                                                   
BenchmarkCache_SetWithMaxSize/100_small_value-8                                  4782966   252 ns/op
BenchmarkCache_SetWithMaxSize/10000_small_value-8                                4067967   296 ns/op
BenchmarkCache_SetWithMaxSize/100000_small_value-8                               3762055   328 ns/op
BenchmarkCache_SetWithMaxSize/100_medium_value-8                                 4760479   252 ns/op
BenchmarkCache_SetWithMaxSize/10000_medium_value-8                               4081050   295 ns/op
BenchmarkCache_SetWithMaxSize/100000_medium_value-8                              3785050   330 ns/op
BenchmarkCache_SetWithMaxSize/100_large_value-8                                  4732909   254 ns/op
BenchmarkCache_SetWithMaxSize/10000_large_value-8                                4079533   297 ns/op
BenchmarkCache_SetWithMaxSize/100000_large_value-8                               3712820   331 ns/op
BenchmarkCache_SetWithMaxSizeAndLRU                                             
BenchmarkCache_SetWithMaxSizeAndLRU/100_small_value-8                            4761732   254 ns/op
BenchmarkCache_SetWithMaxSizeAndLRU/10000_small_value-8                          4084474   296 ns/op
BenchmarkCache_SetWithMaxSizeAndLRU/100000_small_value-8                         3761402   329 ns/op
BenchmarkCache_SetWithMaxSizeAndLRU/100_medium_value-8                           4783075   254 ns/op
BenchmarkCache_SetWithMaxSizeAndLRU/10000_medium_value-8                         4103980   296 ns/op
BenchmarkCache_SetWithMaxSizeAndLRU/100000_medium_value-8                        3646023   331 ns/op
BenchmarkCache_SetWithMaxSizeAndLRU/100_large_value-8                            4779025   254 ns/op
BenchmarkCache_SetWithMaxSizeAndLRU/10000_large_value-8                          4096192   296 ns/op
BenchmarkCache_SetWithMaxSizeAndLRU/100000_large_value-8                         3726823   331 ns/op
BenchmarkCache_GetSetMultipleConcurrent                                         
BenchmarkCache_GetSetMultipleConcurrent-8                                         707142  1698 ns/op
BenchmarkCache_GetSetConcurrentWithFrequentEviction
BenchmarkCache_GetSetConcurrentWithFrequentEviction/FirstInFirstOut-8            3616256   334 ns/op
BenchmarkCache_GetSetConcurrentWithFrequentEviction/LeastRecentlyUsed-8          3636367   331 ns/op
BenchmarkCache_GetConcurrentWithLRU                                              
BenchmarkCache_GetConcurrentWithLRU/FirstInFirstOut-8                            4405557   268 ns/op
BenchmarkCache_GetConcurrentWithLRU/LeastRecentlyUsed-8                          4445475   269 ns/op
BenchmarkCache_WithForceNilInterfaceOnNilPointer
BenchmarkCache_WithForceNilInterfaceOnNilPointer/true_with_nil_struct_pointer-8  6184591   191 ns/op
BenchmarkCache_WithForceNilInterfaceOnNilPointer/true-8                          6090482   191 ns/op
BenchmarkCache_WithForceNilInterfaceOnNilPointer/false_with_nil_struct_pointer-8 6184629   187 ns/op
BenchmarkCache_WithForceNilInterfaceOnNilPointer/false-8                         6281781   186 ns/op
(Trimmed "BenchmarkCache_" for readability)
WithForceNilInterfaceOnNilPointerWithConcurrency
WithForceNilInterfaceOnNilPointerWithConcurrency/true_with_nil_struct_pointer-8  4379564   268 ns/op
WithForceNilInterfaceOnNilPointerWithConcurrency/true-8                          4379558   265 ns/op
WithForceNilInterfaceOnNilPointerWithConcurrency/false_with_nil_struct_pointer-8 4444456   261 ns/op
WithForceNilInterfaceOnNilPointerWithConcurrency/false-8                         4493896   262 ns/op

FAQ

How can I persist the data on application termination?

Because this library doesn't persist immediately after every write operations, persistence is instead expected to be done on a schedule, like for instance, every 10 minutes.

While this prevents you from losing all of your data, you may still lose some data if the application stopped 9 minutes after the previous "auto save".

To increase your odds of not losing any data, you can use Go's signal package, more specifically its Notify function which allows listening for termination signals like SIGTERM and SIGINT. Once a termination signal is caught, you can add the necessary logic for a graceful shutdown.

In the following example, the code that would usually be present in the main function is moved to a different function named Start which is launched on a different goroutine so that listening for a termination signals is what blocks the main goroutine instead:

package main

import (
    "log"
    "os"
    "os/signal"
    "syscall"

    "github.com/TwiN/gocache"
)

const CacheFile = "gocache.data"

var cache = gocache.NewCache()

func main() {
    // Load persisted data from file
    cache.ReadFromFile(CacheFile)
    // Start everything else on another goroutine to prevent blocking the main goroutine
    go Start()
    // Wait for termination signal
    sig := make(chan os.Signal, 1)
    done := make(chan bool, 1)
    signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-sig
        log.Println("Received termination signal, attempting to gracefully shut down")
        err := cache.SaveToFile(CacheFile)
        if err != nil {
            log.Println("Failed to save storage provider:", err.Error())
        }
        done <- true
    }()
    <-done
    log.Println("Shutting down")
}

Note that this won't protect you from a SIGKILL, as this signal cannot be caught.

How can I automatically save the cache to a file every 5 minutes?

Beside using the suggestion above, automatically persisting the cache on an interval will protect your application from sudden terminations triggered by signals that cannot be caught, such as the force kill signal received by an application being OOMKilled.

The simplest implementation could be something like this:

const CacheFile = "gocache.data"

func main() {
    cache := gocache.NewCache()
    cache.ReadFromFile(CacheFile)
    go autoSave(10*time.Minute)
    // ...
}

func autoSave(interval time.Duration) {
    for {
        err := cache.SaveToFile(CacheFile)
        if err != nil {
            log.Println("Failed to persist cache to file:", err.Error())
        }
        time.Sleep(interval)
    }
}
Why does the memory usage not go down?

NOTE: As of Go 1.16, this no longer applies. See golang/go#42330

By default, Go uses MADV_FREE if the kernel supports it to release memory, which is significantly more efficient than using MADV_DONTNEED. Unfortunately, this means that RSS doesn't go down unless the OS actually needs the memory.

Technically, the memory is available to the kernel, even if it shows a high memory usage, but the OS will only use that memory if it needs to. In the case that the OS does need the freed memory, the RSS will go down and you'll notice the memory usage lowering.

reference

You can reproduce this by following the steps below:

  • Start the server
  • Note the memory usage
  • Create 500k keys
  • Note the memory usage
  • Flush the cache
  • Note that the memory usage has not decreased, despite the cache being empty.

Substituting gocache for a normal map will yield the same result.

If the released memory still appearing as used is a problem for you, you can set the environment variable GODEBUG to madvdontneed=1.

Documentation

Index

Constants

View Source
const (
	// NoMaxSize means that the cache has no maximum number of entries in the cache
	// Setting Cache.maxSize to this value also means there will be no eviction
	NoMaxSize = 0

	// NoMaxMemoryUsage means that the cache has no maximum number of entries in the cache
	NoMaxMemoryUsage = 0

	// DefaultMaxSize is the max size set if no max size is specified
	DefaultMaxSize = 100000

	// NoExpiration is the value that must be used as TTL to specify that the given key should never expire
	NoExpiration = -1

	Kilobyte = 1024
	Megabyte = 1024 * Kilobyte
	Gigabyte = 1024 * Megabyte
)
View Source
const (
	// JanitorShiftTarget is the target number of expired keys to find during passive clean up duty
	// before pausing the passive expired keys eviction process
	JanitorShiftTarget = 25

	// JanitorMaxIterationsPerShift is the maximum number of nodes to traverse before pausing
	JanitorMaxIterationsPerShift = 1000

	// JanitorMinShiftBackOff is the minimum interval between each iteration of steps
	// defined by JanitorMaxIterationsPerShift
	JanitorMinShiftBackOff = time.Millisecond * 50

	// JanitorMaxShiftBackOff is the maximum interval between each iteration of steps
	// defined by JanitorMaxIterationsPerShift
	JanitorMaxShiftBackOff = time.Millisecond * 500
)

Variables

View Source
var (
	ErrKeyDoesNotExist       = errors.New("key does not exist")         // Returned when a cache key does not exist
	ErrKeyHasNoExpiration    = errors.New("key has no expiration")      // Returned when a cache key has no expiration
	ErrJanitorAlreadyRunning = errors.New("janitor is already running") // Returned when the janitor has already been started
)
View Source
var (
	Debug = false
)

Functions

func MatchPattern

func MatchPattern(pattern, s string) bool

MatchPattern checks whether a string matches a pattern

Types

type Cache

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

Cache is the core struct of gocache which contains the data as well as all relevant configuration fields

func NewCache

func NewCache() *Cache

NewCache creates a new Cache

Should be used in conjunction with Cache.WithMaxSize, Cache.WithMaxMemoryUsage and/or Cache.WithEvictionPolicy

gocache.NewCache().WithMaxSize(10000).WithEvictionPolicy(gocache.LeastRecentlyUsed)

func (*Cache) Clear

func (cache *Cache) Clear()

Clear deletes all entries from the cache

func (*Cache) Count

func (cache *Cache) Count() int

Count returns the total amount of entries in the cache, regardless of whether they're expired or not

func (*Cache) Delete

func (cache *Cache) Delete(key string) bool

Delete removes a key from the cache

Returns false if the key did not exist.

func (*Cache) DeleteAll

func (cache *Cache) DeleteAll(keys []string) int

DeleteAll deletes multiple entries based on the keys passed as parameter

Returns the number of keys deleted

func (*Cache) EvictionPolicy

func (cache *Cache) EvictionPolicy() EvictionPolicy

EvictionPolicy returns the EvictionPolicy of the Cache

func (*Cache) Expire

func (cache *Cache) Expire(key string, ttl time.Duration) bool

Expire sets a key's expiration time

A TTL of -1 means that the key will never expire A TTL of 0 means that the key will expire immediately If using LRU, note that this does not reset the position of the key

Returns true if the cache key exists and has had its expiration time altered

func (*Cache) Get

func (cache *Cache) Get(key string) (interface{}, bool)

Get retrieves an entry using the key passed as parameter If there is no such entry, the value returned will be nil and the boolean will be false If there is an entry, the value returned will be the value cached and the boolean will be true

func (*Cache) GetAll

func (cache *Cache) GetAll() map[string]interface{}

GetAll retrieves all cache entries

If the eviction policy is LeastRecentlyUsed, note that unlike Get and GetByKeys, this does not update the last access timestamp. The reason for this is that since all cache entries will be accessed, updating the last access timestamp would provide very little benefit while harming the ability to accurately determine the next key that will be evicted

You should probably avoid using this if you have a lot of entries.

GetKeysByPattern is a good alternative if you want to retrieve entries that you do not have the key for, as it only retrieves the keys and does not trigger active eviction and has a parameter for setting a limit to the number of keys you wish to retrieve.

func (*Cache) GetByKeys

func (cache *Cache) GetByKeys(keys []string) map[string]interface{}

GetByKeys retrieves multiple entries using the keys passed as parameter All keys are returned in the map, regardless of whether they exist or not, however, entries that do not exist in the cache will return nil, meaning that there is no way of determining whether a key genuinely has the value nil, or whether it doesn't exist in the cache using only this function.

func (*Cache) GetKeysByPattern

func (cache *Cache) GetKeysByPattern(pattern string, limit int) []string

GetKeysByPattern retrieves a slice of keys that match a given pattern If the limit is set to 0, the entire cache will be searched for matching keys. If the limit is above 0, the search will stop once the specified number of matching keys have been found.

e.g.

cache.GetKeysByPattern("*some*", 0) will return all keys containing "some" in them
cache.GetKeysByPattern("*some*", 5) will return 5 keys (or less) containing "some" in them

Note that GetKeysByPattern does not trigger active evictions, nor does it count as accessing the entry, the latter only applying if the cache uses the LeastRecentlyUsed eviction policy. The reason for that behavior is that these two (active eviction and access) only applies when you access the value of the cache entry, and this function only returns the keys.

func (*Cache) GetValue

func (cache *Cache) GetValue(key string) interface{}

GetValue retrieves an entry using the key passed as parameter Unlike Get, this function only returns the value

func (*Cache) MaxMemoryUsage

func (cache *Cache) MaxMemoryUsage() int

MaxMemoryUsage returns the configured maxMemoryUsage of the cache

func (*Cache) MaxSize

func (cache *Cache) MaxSize() int

MaxSize returns the maximum amount of keys that can be present in the cache before new entries trigger the eviction of the tail

func (*Cache) MemoryUsage

func (cache *Cache) MemoryUsage() int

MemoryUsage returns the current memory usage of the cache's dataset in bytes If MaxMemoryUsage is set to NoMaxMemoryUsage, this will return 0

func (*Cache) ReadFromFile

func (cache *Cache) ReadFromFile(path string) (int, error)

ReadFromFile populates the cache using a file created using cache.SaveToFile(path)

Note that if the number of entries retrieved from the file exceed the configured maxSize, the extra entries will be automatically evicted according to the EvictionPolicy configured. This function returns the number of entries evicted, and because this function only reads from a file and does not modify it, you can safely retry this function after configuring the cache with the appropriate maxSize, should you desire to.

func (*Cache) SaveToFile

func (cache *Cache) SaveToFile(path string) error

SaveToFile stores the content of the cache to a file so that it can be read using the ReadFromFile function

func (*Cache) Set

func (cache *Cache) Set(key string, value interface{})

Set creates or updates a key with a given value

func (*Cache) SetAll

func (cache *Cache) SetAll(entries map[string]interface{})

SetAll creates or updates multiple values

func (*Cache) SetWithTTL

func (cache *Cache) SetWithTTL(key string, value interface{}, ttl time.Duration)

SetWithTTL creates or updates a key with a given value and sets an expiration time (-1 is NoExpiration)

The TTL provided must be greater than 0, or NoExpiration (-1). If a negative value that isn't -1 (NoExpiration) is provided, the entry will not be created if the key doesn't exist

func (*Cache) StartJanitor

func (cache *Cache) StartJanitor() error

StartJanitor starts the janitor on a different goroutine The janitor's job is to delete expired keys in the background, in other words, it takes care of passive eviction. It can be stopped by calling Cache.StopJanitor. If you do not start the janitor, expired keys will only be deleted when they are accessed through Get, GetByKeys, or GetAll.

func (*Cache) Stats

func (cache *Cache) Stats() Statistics

Stats returns statistics from the cache

func (*Cache) StopJanitor

func (cache *Cache) StopJanitor()

StopJanitor stops the janitor

func (*Cache) TTL

func (cache *Cache) TTL(key string) (time.Duration, error)

TTL returns the time until the cache entry specified by the key passed as parameter will be deleted.

func (*Cache) WithEvictionPolicy

func (cache *Cache) WithEvictionPolicy(policy EvictionPolicy) *Cache

WithEvictionPolicy sets eviction algorithm. Defaults to FirstInFirstOut (FIFO)

func (*Cache) WithForceNilInterfaceOnNilPointer

func (cache *Cache) WithForceNilInterfaceOnNilPointer(forceNilInterfaceOnNilPointer bool) *Cache

WithForceNilInterfaceOnNilPointer sets whether all Set-like functions should set a value as nil if the interface passed has a nil value but not a nil type.

In Go, an interface is only nil if both its type and value are nil, which means that a nil pointer (e.g. (*Struct)(nil)) will retain its attribution to the type, and the unmodified value returned from Cache.Get, for instance, would return false when compared with nil if this option is set to false.

We can bypass this by detecting if the interface's value is nil and setting it to nil rather than a nil pointer, which will make the value returned from Cache.Get return true when compared with nil. This is exactly what passing true to WithForceNilInterfaceOnNilPointer does, and it's also the default behavior.

Alternatively, you may pass false to WithForceNilInterfaceOnNilPointer, which will mean that you'll have to cast the value returned from Cache.Get to its original type to check for whether the pointer returned is nil or not.

If set to true:

cache := gocache.NewCache().WithForceNilInterfaceOnNilPointer(true)
cache.Set("key", (*Struct)(nil))
value, _ := cache.Get("key")
// the following returns true, because the interface{} was forcefully set to nil
if value == nil {}
// the following will panic, because the value has been casted to its type (which is nil)
if value.(*Struct) == nil {}

If set to false:

cache := gocache.NewCache().WithForceNilInterfaceOnNilPointer(false)
cache.Set("key", (*Struct)(nil))
value, _ := cache.Get("key")
// the following returns false, because the interface{} returned has a non-nil type (*Struct)
if value == nil {}
// the following returns true, because the value has been casted to its type
if value.(*Struct) == nil {}

In other words, if set to true, you do not need to cast the value returned from the cache to to check if the value is nil.

Defaults to true

func (*Cache) WithMaxMemoryUsage

func (cache *Cache) WithMaxMemoryUsage(maxMemoryUsageInBytes int) *Cache

WithMaxMemoryUsage sets the maximum amount of memory that can be used by the cache at any given time

NOTE: This is approximate.

Setting this to NoMaxMemoryUsage will disable eviction by memory usage

func (*Cache) WithMaxSize

func (cache *Cache) WithMaxSize(maxSize int) *Cache

WithMaxSize sets the maximum amount of entries that can be in the cache at any given time A maxSize of 0 or less means infinite

type Entry

type Entry struct {
	// Key is the name of the cache entry
	Key string

	// Value is the value of the cache entry
	Value interface{}

	// RelevantTimestamp is the variable used to store either:
	// - creation timestamp, if the Cache's EvictionPolicy is FirstInFirstOut
	// - last access timestamp, if the Cache's EvictionPolicy is LeastRecentlyUsed
	//
	// Note that updating an existing entry will also update this value
	RelevantTimestamp time.Time

	// Expiration is the unix time in nanoseconds at which the entry will expire (-1 means no expiration)
	Expiration int64
	// contains filtered or unexported fields
}

Entry is a cache entry

func (*Entry) Accessed

func (entry *Entry) Accessed()

Accessed updates the Entry's RelevantTimestamp to now

func (Entry) Expired

func (entry Entry) Expired() bool

Expired returns whether the Entry has expired

func (*Entry) SizeInBytes

func (entry *Entry) SizeInBytes() int

SizeInBytes returns the size of an entry in bytes, approximately.

type EvictionPolicy

type EvictionPolicy string

EvictionPolicy is what dictates how evictions are handled

var (
	// LeastRecentlyUsed is an eviction policy that causes the most recently accessed cache entry to be moved to the
	// head of the cache. Effectively, this causes the cache entries that have not been accessed for some time to
	// gradually move closer and closer to the tail, and since the tail is the entry that gets deleted when an eviction
	// is required, it allows less used cache entries to be evicted while keeping recently accessed entries at or close
	// to the head.
	//
	// For instance, creating a Cache with a Cache.MaxSize of 3 and creating the entries 1, 2 and 3 in that order would
	// put 3 at the head and 1 at the tail:
	//     3 (head) -> 2 -> 1 (tail)
	// If the cache entry 1 was then accessed, 1 would become the head and 2 the tail:
	//     1 (head) -> 3 -> 2 (tail)
	// If a cache entry 4 was then created, because the Cache.MaxSize is 3, the tail (2) would then be evicted:
	//     4 (head) -> 1 -> 3 (tail)
	LeastRecentlyUsed EvictionPolicy = "LeastRecentlyUsed"

	// FirstInFirstOut is an eviction policy that causes cache entries to be evicted in the same order that they are
	// created.
	//
	// For instance, creating a Cache with a Cache.MaxSize of 3 and creating the entries 1, 2 and 3 in that order would
	// put 3 at the head and 1 at the tail:
	//     3 (head) -> 2 -> 1 (tail)
	// If the cache entry 1 was then accessed, unlike with LeastRecentlyUsed, nothing would change:
	//     3 (head) -> 2 -> 1 (tail)
	// If a cache entry 4 was then created, because the Cache.MaxSize is 3, the tail (1) would then be evicted:
	//     4 (head) -> 3 -> 2 (tail)
	FirstInFirstOut EvictionPolicy = "FirstInFirstOut"
)

type Statistics

type Statistics struct {
	// EvictedKeys is the number of keys that were evicted
	EvictedKeys uint64

	// ExpiredKeys is the number of keys that were automatically deleted as a result of expiring
	ExpiredKeys uint64

	// Hits is the number of cache hits
	Hits uint64

	// Misses is the number of cache misses
	Misses uint64
}

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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