An in-memory string-interface{} map with various expiration options for golang

TTLCache - an in-memory cache with expiration

Documentation Release

TTLCache is a simple key/value cache in golang with the following functions:

  1. Expiration of items based on time, or custom function
  2. Loader function to retrieve missing keys can be provided. Additional Get calls on the same key block while fetching is in progress (groupcache style).
  3. Individual expiring time or global expiring time, you can choose
  4. Auto-Extending expiration on Get -or- DNS style TTL, see SkipTTLExtensionOnHit(bool)
  5. Can trigger callback on key expiration
  6. Cleanup resources by calling Close() at end of lifecycle.
  7. Thread-safe with comprehensive testing suite. This code is in production at bol.com on critical systems.

Note (issue #25): by default, due to historic reasons, the TTL will be reset on each cache hit and you need to explicitly configure the cache to use a TTL that will not get extended.

Build Status Go Report Card Coverage Status GitHub issues license

Usage

You can copy it as a full standalone demo program.

package main

import (
	"fmt"
	"time"

	"github.com/ReneKroon/ttlcache/v2"
)

var (
	notFound = ttlcache.ErrNotFound
	isClosed = ttlcache.ErrClosed
)

func main() {
	newItemCallback := func(key string, value interface{}) {
		fmt.Printf("New key(%s) added\n", key)
	}
	checkExpirationCallback := func(key string, value interface{}) bool {
		if key == "key1" {
			// if the key equals "key1", the value
			// will not be allowed to expire
			return false
		}
		// all other values are allowed to expire
		return true
	}
	expirationCallback := func(key string, value interface{}) {
		fmt.Printf("This key(%s) has expired\n", key)
	}

	loaderFunction := func(key string) (data interface{}, ttl time.Duration, err error) {
		ttl = time.Second * 300
		data, err = getFromNetwork(key)

		return data, ttl, err
	}

	cache := ttlcache.NewCache()
	defer cache.Close()
	cache.SetTTL(time.Duration(10 * time.Second))
	cache.SetExpirationCallback(expirationCallback)
	cache.SetLoaderFunction(loaderFunction)
	cache.SetNewItemCallback(newItemCallback)
	cache.SetCheckExpirationCallback(checkExpirationCallback)

	cache.Set("key", "value")
	cache.SetWithTTL("keyWithTTL", "value", 10*time.Second)

	if value, exists := cache.Get("key"); exists == nil {
		fmt.Printf("Got value: %v\n", value)
	}
	count := cache.Count()
	if result := cache.Remove("keyNNN"); result == notFound {
		fmt.Printf("Not found, %d items left\n", count)
	}
}

func getFromNetwork(key string) (string, error) {
	time.Sleep(time.Millisecond * 30)
	return "value", nil
}

TTLCache - Some design considerations

  1. The complexity of the current cache is already quite high. Therefore not all requests can be implemented in a straight-forward manner.
  2. The locking should be done only in the exported functions and startExpirationProcessing of the Cache struct. Else data races can occur or recursive locks are needed, which are both unwanted.
  3. I prefer correct functionality over fast tests. It's ok for new tests to take seconds to proof something.

Original Project

TTLCache was forked from wunderlist/ttlcache to add extra functions not avaiable in the original scope. The main differences are:

  1. A item can store any kind of object, previously, only strings could be saved
  2. Optionally, you can add callbacks too: check if a value should expire, be notified if a value expires, and be notified when new values are added to the cache
  3. The expiration can be either global or per item
  4. Items can exist without expiration time (time.Zero)
  5. Expirations and callbacks are realtime. Don't have a pooling time to check anymore, now it's done with a heap.
  6. A cache count limiter
Comments
  • Limit on no. of keys stored in ttlcache?

    Limit on no. of keys stored in ttlcache?

    is there a limit on no. of keys that can be stored using ttlcache library? In our service we recently faced increase in response time when ttlcache was being used.

  • Returning loader errors

    Returning loader errors

    With new version v3 there is currently no way to return loader (either default or getter) error as Get returns only item and there is no way to propagate errors back to caller.

  • SuppressedLoader should allow setting `group`

    SuppressedLoader should allow setting `group`

    SuppressedLoader looks useful! Unfortunately, group is private. So if I just create a SuppressedLoader from an existing Loader and try to use it, I get an "invalid memory address or nil pointer dereference" error.

    I have had to copy SuppressedLoader into my own code where I can set group myself in order to workaround this.

    Ideas

    • ttlcache should expose a method for constructing SuppressedLoaders with non-nil group?
    • SuppressedLoader should lazily set group to non-nil?
  • Upgrade to v3

    Upgrade to v3

    Before the stable release of v3, some additional, non-backwards compatible changes could be made. Here is a list of my proposed changes:

    • [x] Update README.md
    • [x] Allow TTL extension to be enabled/disabled per Get() call, rather than per cache instance. Having them both could also work: the global cache value would be used as the default one.
    • [x] Use functional options to set cache options.
    • [x] Remove the SimpleCache interface, since it serves no purpose, at least in this package. The idiomatic approach is to allow the client/user of the package to create their own interface, with their own limited set of methods. However, if there are clear and useful examples of where this is may be needed, this interface should be at least renamed to something more appropriate.
    • [x] Rename the event methods and make them follow On[Noun](fn) naming pattern (e.g. OnExpiration(fn)). The provided functions should also be stored in a slice rather than a simple field. A good example of this is bolt's OnCommit.
    • [x] Separate expiration queue and LRU item list.
    • [x] Rename existing methods.
    • [x] Remove evictionreason_enumer.go since it seems to add very little value yet creates quite a bit of unidiomatic code noise.
    • [x] Clean up some of the code/add proper docs.
    • [x] Add Loader interface and its helper types/funcs.
    • [x] Improve mutex usage.
    • [x] Make auto cleaner process optional. It should also be possible to stop and then restart it without closing the cache instance.
    • [x] Remove Close(), replace it with Start() and Stop()
    • [x] Remove error return values (most of them return only ErrClosed).
    • [x] Add a method for manual expired item deletion.
    • [x] Return/pass the whole Item type rather than its fields (applies to Get/Set/event functions).
    • [x] Rewrite/improve tests.

    I may have some more ideas later on.

    What do you think @ReneKroon?

  • call expirationCallback automatically on cache.Close()

    call expirationCallback automatically on cache.Close()

    Is it possible to call expirationCallback on each remaining keys in the cache, when calling cache.Close()?

    The purpose is to keep the overall expirationCallback behavior the same?

  • func (cache *Cache) startExpirationProcessing() - Never Exits and Leaves residual Goroutines.

    func (cache *Cache) startExpirationProcessing() - Never Exits and Leaves residual Goroutines.

    Hey, I noticed there is no exit case in your Goroutine here: https://github.com/ReneKroon/ttlcache/blob/49e7d8e56413a8ee1f826b064e7268f6e10ca2d3/cache.go#L52

    I was developing an application that start multiple caches as child-struct. But when the parent is removed this function - func (cache *Cache) startExpirationProcessing() - keeps running forever.

    Do you perhaps have a way of killing it somewhere in your code ? I like your package and I would love to keep using it, but in the current state I can not :(

  • feat(disable_overwrite_on_set): Add ability to disable overwrites when setting an item in the cache

    feat(disable_overwrite_on_set): Add ability to disable overwrites when setting an item in the cache

    Add ability to disable overwrites when setting an item in the cache

    • Added new option & associated test
    • Added functionality in cache.set() to return the item if overwrite is disabled. Could be done as an else.. but wanted to make explicit up front at top of func.

    Not sure if more tests need additional testing. I didn't a chance to review all existing cases / structure of testing framework

    Addresses issue / enhancement request https://github.com/jellydator/ttlcache/issues/78

  • add GetItems method

    add GetItems method

    Hi! @ReneKroon ! Awesome cache! Want to switch to yours from https://github.com/patrickmn/go-cache as yours has a size limit.

    Can you accept a small PR which adds the GetItems method?

  • Loaderfunction key not found

    Loaderfunction key not found

    Fixes #44

    I ran into some trouble where TestCache_Limit didn't work for me locally but that was the same before and after the change (length was 10 but keys were incorrect - this might be due to shitty windows timers).

    Let me know any thoughts.

  • PurgeCallback to be called on Purge()

    PurgeCallback to be called on Purge()

    I needed a callback to be called on Purge. If I just wrapped it in an internal package, I'd invent a possible race condition:

    thread1 purges 
    thread2 adds
    add callback
    purge callback
    

    Thus this PR.

  • Use type parameters

    Use type parameters

    This PR adds type parameters to all the relevant types and functions. The logic of the functions and their tests has not been changed.

    Other changes worth mentioning:

    • The top-level documentation comments have been reformatted (mainly missing periods have been added). The content of those comments has not been changed.
    • For the sake of consistency, some fields/variables have been renamed from 'data' to 'value'.
    • The go version specified in go.mod has been changed to 1.18.
    • Readme.md has been renamed to README.md.

    Closes: #63

  • Add MGet or Multiple Get to improve read performance

    Add MGet or Multiple Get to improve read performance

    I've performance issues when my cache has heavy read and write load in the same time. I propose an approch like Reduce mutex blocking while get from multiple keys (like redis MGET ) Or use RW.Mutex while Get() instead.

  • use atomics for metrics

    use atomics for metrics

    AFAIK metricsMu is not really performing any useful function over and above the likely more efficient atomic integer operations available on most modern CPUs. In the Metric() return function, the metricsMu served to ensure all the counters are returned as of an instant in time when no other counter is incremented relative to other counters, but since the write locks are only held for the duration of an increment of a single element of the structure, even that has no utility (i.e., there are never increments of two counters at once while holding the lock across both increments, which is the only thing the read lock would protect). Finally, the atomic loads probably collapse to simple reads on many CPUs, with the atomic load possibly only forcing cpu cache coherency (i.e., you can probably just write Metrics(){return c.metrics} and be just fine).

  • Provides similar implementation as sync.Map (LoadOrStore, LoadAndDelete)

    Provides similar implementation as sync.Map (LoadOrStore, LoadAndDelete)

    https://pkg.go.dev/sync#Map.LoadOrStore https://pkg.go.dev/sync#Map.LoadAndDelete

    https://dev.to/sreramk/go-loadanddelete-and-loadorstore-in-sync-map-why-are-they-needed-30f7

  • Add Version method to item type

    Add Version method to item type

    It should return a uint64 that determines how many times an item has been updated. This method may be useful in situations when a task's execution depends on whether the item has changed or not and when due to the duration of another task a transaction/mutex isn't acceptible.

    item := cache.Get("key")
    version := item.Version()
    // long task
    if item.Version() == version {
         // execute only if the item hasn't been updated during the long task's execution
    }
    
Recursively searches a map[string]interface{} structure for another map[string]interface{} structure

msirecurse Recursively searches a map[string]interface{} structure for existence of a map[string]interface{} structure Motivation I wrote this package

Mar 3, 2022
Decode / encode XML to/from map[string]interface{} (or JSON); extract values with dot-notation paths and wildcards. Replaces x2j and j2x packages.

mxj - to/from maps, XML and JSON Decode/encode XML to/from map[string]interface{} (or JSON) values, and extract/modify values from maps by key or key-

Dec 22, 2022
skipmap is a high-performance concurrent sorted map based on skip list. Up to 3x ~ 10x faster than sync.Map in the typical pattern.
skipmap is a high-performance concurrent sorted map based on skip list. Up to 3x ~ 10x faster than sync.Map in the typical pattern.

Introduction skipmap is a high-performance concurrent map based on skip list. In typical pattern(one million operations, 90%LOAD 9%STORE 1%DELETE), th

Jan 8, 2023
A fast (5x) string keyed read-only map for Go - particularly good for keys using a small set of nearby runes.

faststringmap faststringmap is a fast read-only string keyed map for Go (golang). For our use case it is approximately 5 times faster than using Go's

Jan 8, 2023
A memory-efficient trie for testing the existence/prefixes of string only(for now).

Succinct Trie A memory-efficient trie for testing the existence/prefixes of string only(for now). Install go get -u github.com/nobekanai/sutrie Docume

Mar 10, 2022
When storing a value in a Go interface allocates memory on the heap.

Go interface values This repository deep dives Go interface values, what they are, how they work, and when storing a value in a Go interface allocates

Dec 16, 2022
Golang string comparison and edit distance algorithms library, featuring : Levenshtein, LCS, Hamming, Damerau levenshtein (OSA and Adjacent transpositions algorithms), Jaro-Winkler, Cosine, etc...

Go-edlib : Edit distance and string comparison library Golang string comparison and edit distance algorithms library featuring : Levenshtein, LCS, Ham

Dec 20, 2022
Convert json string to Golang struct

json-to-go-cli Convert json string to Golang struct How to install git clone https://github.com/tiancheng92/json-to-go-cli.git cd json-to-go-cli go bu

May 10, 2022
A thread safe map which has expiring key-value pairs

~ timedmap ~ A map which has expiring key-value pairs. go get github.com/zekroTJA/timedmap Intro This package allows to set values to a map which will

Dec 29, 2022
A prefix-enhanced map in Go

PrefixMap PrefixMap is a prefix-enhanced map that eases the retrieval of values based on key prefixes. Quick Start Creating a PrefixMap // creates the

Jun 13, 2022
A typed implementation of the Go sync.Map using code generation

syncmap A typed implementation of the Go sync.Map using code generation. Install go get -u github.com/a8m/syncmap@master Examples: Using CLI $ syncma

Dec 26, 2022
💯 Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving

Package validator implements value validations for structs and individual fields based on tags.

Nov 9, 2022
A Go library to iterate over potentially nested map keys using the visitor pattern

A Go library to iterate over potentially nested map keys using the visitor pattern

Mar 15, 2022
Go library for encoding native Go structures into generic map values.

wstructs origin: github.com/things-go/structs Go library for encoding native Go structures into generic map values. Installation Use go get. go ge

Jan 10, 2022
Multi-String Pattern Matching Algorithm Using TrieHashNode

Multi-String Pattern Matching algorithm. This implementation is inspired from Aho-Corasick algorithm Getting Started modelA = mspm.NewModel("mspm_mode

Dec 9, 2022
Data structure and relevant algorithms for extremely fast prefix/fuzzy string searching.

Trie Data structure and relevant algorithms for extremely fast prefix/fuzzy string searching. Usage Create a Trie with: t := trie.New() Add Keys with:

Dec 27, 2022
Implementation of Boyer-Moore fast string search algorithm in Go

boyermoore Implementation of Boyer-Moore fast string search algorithm in Go

Oct 7, 2022
Algorithms for various integer sequences from the OEIS site.

OEIS The ongoing quest to program every sequence in the OEIS database (in Golang) Content sequences -- The folder containing the seq package, which co

Dec 23, 2021
Implementation of various data structures and algorithms in Go
Implementation of various data structures and algorithms in Go

GoDS (Go Data Structures) Implementation of various data structures and algorithms in Go. Data Structures Containers Lists ArrayList SinglyLinkedList

Jan 25, 2022