cache

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Jan 13, 2023 License: MIT Imports: 6 Imported by: 0

README

Redis Cache

Redis Cache is a very simple abstraction over Redis for basic caching functionality. It can be thought of as a simple Cache backed by Redis. Redis Cache provides the following functionality: Set, Get, and Delete. In addition, there is a SetWithTTL method to set entries that will automatically be removed once the TTL has expired.

Requirements

  • Go 1.18+ (might work on older versions of Go but untested)
  • Redis 6
  • go-redis/redis (this is the backing Redis library/client)

Getting Redis Cache

go get github.com/jkratz/redis-cache

Usage

Under the hood Redis Cache was designed to be used with https://github.com/go-redis/redis. However, it can work with any type that implements the RedisClient interface.

type RedisClient interface {
    Get(ctx context.Context, key string) *redis.StringCmd
    MGet(ctx context.Context, keys ...string) *redis.SliceCmd
    Set(ctx context.Context, key string, val any, ttl time.Duration) *redis.StatusCmd
    SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.BoolCmd
    SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.BoolCmd
    Del(ctx context.Context, keys ...string) *redis.IntCmd
}

This means that Cache type can work with the following types.

  • redis.Client
  • redis.ClusterClient
  • redis.Ring

You'll need to choose the right type based on your particular use cases. More information can be found here

For the example below we are going to assume a single standalone Redis node and use Client. Out of the box Cache uses msgpack to marshall and unmarshall the value. However, if you want to use a different serialization method you can provide it as an option when creating a Cache instance using NewCache.

package main

import (
	"context"
	"fmt"

	"github.com/go-redis/redis/v9"

	rcache "github.com/jkratz55/redis-cache"
)

type Person struct {
	FirstName string
	LastName  string
	Age       int
}

func main() {
	client := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})

	cache := rcache.NewCache(client)

	if err := cache.Set(context.Background(), "person", Person{
		FirstName: "Biily",
		LastName:  "Bob",
		Age:       45,
	}); err != nil {
		panic("ohhhhh snap!")
	}

	var p Person
	if err := cache.Get(context.Background(), "person", &p); err != nil {
		panic("ohhhhh snap")
	}
	fmt.Printf("%v\n", p)

	if err := cache.Delete(context.Background(), "person"); err != nil {
		panic("ohhh snap!")
	}

	if err := cache.Get(context.Background(), "person", &p); err != rcache.ErrKeyNotFound {
		panic("ohhhhh snap, this key should be gone!")
	}
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrKeyNotFound is an error value that signals the key requested does not
	// exist in the cache.
	ErrKeyNotFound = errors.New("key not found")
)

Functions

func IsRetryable added in v0.5.0

func IsRetryable(err error) bool

IsRetryable accepts an error and returns a boolean indicating if the operation that generated the error is retryable.

func Upsert added in v0.5.0

func Upsert[T any](ctx context.Context, c *Cache, key string, val T, cb UpsertCallback[T]) error

Upsert retrieves the existing value for a given key and invokes the UpsertCallback. The UpsertCallback function is responsible for determining the value to be stored. The value returned from the UpsertCallback is what is set in Redis.

Upsert allows for atomic updates of existing records, or simply inserting new entries when the key doesn't exist.

Redis uses an optimistic locking model. If the key changes during the transaction Redis will fail the transaction and return an error. However, these errors are retryable. To determine if the error is retryable use the IsRetryable function with the returned error.

cb := rcache.UpsertCallback[Person](func(found bool, oldValue Person, newValue Person) Person {
	fmt.Println(found)
	fmt.Println(oldValue)
	fmt.Println(newValue)
	return newValue
})
retries := 3
for i := 0; i < retries; i++ {
	err := rcache.Upsert[Person](context.Background(), c, "BillyBob", p, cb)
	if rcache.IsRetryable(err) {
		continue
	}
	// do something useful ...
	break
}

func UpsertTTL added in v0.5.0

func UpsertTTL[T any](ctx context.Context, c *Cache, key string, val T, cb UpsertCallback[T], ttl time.Duration) error

UpsertTTL retrieves the existing value for a given key and invokes the UpsertCallback. The UpsertCallback function is responsible for determining the value to be stored. The value returned from the UpsertCallback is what is set in Redis.

Upsert allows for atomic updates of existing records, or simply inserting new entries when the key doesn't exist.

Redis uses an optimistic locking model. If the key changes during the transaction Redis will fail the transaction and return an error. However, these errors are retryable. To determine if the error is retryable use the IsRetryable function with the returned error.

cb := rcache.UpsertCallback[Person](func(found bool, oldValue Person, newValue Person) Person {
	fmt.Println(found)
	fmt.Println(oldValue)
	fmt.Println(newValue)
	return newValue
})
retries := 3
for i := 0; i < retries; i++ {
	err := rcache.UpsertTTL[Person](context.Background(), c, "BillyBob", p, cb, time.Minute * 1)
	if rcache.IsRetryable(err) {
		continue
	}
	// do something useful ...
	break
}

Types

type Cache

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

Cache is a simple type that provides basic caching functionality: store, retrieve, and delete. It is backed by Redis and supports storing entries with a TTL.

The zero-value is not usable, and this type should be instantiated using the NewCache function.

func NewCache

func NewCache(redis RedisClient, opts ...Option) *Cache

NewCache creates and initializes a new Cache instance.

By default, msgpack is used for marshalling and unmarshalling the entry values. The behavior of Cache can be configured by passing Options.

func (*Cache) Delete

func (c *Cache) Delete(ctx context.Context, keys ...string) error

Delete removes entries from the cache for a given set of keys.

func (*Cache) Get

func (c *Cache) Get(ctx context.Context, key string, v any) error

Get retrieves an entry from the Cache for the given key, and if found will unmarshall the value into the provided type.

If the key does not exist ErrKeyNotFound will be returned as the error value. A non-nil error value will be returned if the operation on the backing Redis fails, or if the value cannot be unmarshalled into the target type.

func (*Cache) MGet added in v0.2.0

func (c *Cache) MGet(ctx context.Context, keys []string, callback func(key string, val []byte, um Unmarshaller)) error

MGet retrieves multiple entries from the cache for the given keys.

Because Go doesn't support type parameters on methods there is no way to handle unmarshalling into the destination type. For this reason each value returned from Redis will be passed to a callback as []byte along with they key and Unmarshaller func. The caller can unmarshall the result from Redis and process the results as they wish.

It is recommended to use the MGet function instead unless there is a specific need to handle building the results on your own.

func (*Cache) Set

func (c *Cache) Set(ctx context.Context, key string, v any) error

Set adds an entry into the cache, or overwrites an entry if the key already existed. The entry is set without an expiration.

func (*Cache) SetIfAbsent added in v0.3.0

func (c *Cache) SetIfAbsent(ctx context.Context, key string, v any, ttl time.Duration) (bool, error)

func (*Cache) SetIfPresent added in v0.3.0

func (c *Cache) SetIfPresent(ctx context.Context, key string, v any, ttl time.Duration) (bool, error)

func (*Cache) SetTTL added in v0.3.0

func (c *Cache) SetTTL(ctx context.Context, key string, v any, ttl time.Duration) error

SetTTL adds an entry into the cache, or overwrites an entry if the key already existed. The entry is set with the provided TTL and automatically removed from the cache once the TTL is expired.

type Marshaller

type Marshaller func(v any) ([]byte, error)

Marshaller is a function type that marshals the value of a cache entry for storage.

func DefaultMarshaller

func DefaultMarshaller() Marshaller

DefaultMarshaller returns a Marshaller using msgpack to marshall values.

type MultiResult added in v0.2.0

type MultiResult[T any] map[string]T

MultiResult is a type representing returning multiple entries from the Cache.

func MGet added in v0.2.0

func MGet[R any](ctx context.Context, c *Cache, keys ...string) (MultiResult[R], error)

MGet uses the provided Cache to retrieve multiple keys from Redis and returns a MultiResult.

The Cache type has a MGet method but because Go doesn't support type parameters on methods it relies on callbacks for the caller to unmarshall into the destination type. For that reason it is highly recommended to use this variant of MGet over the method on the Cache type.

func (MultiResult[T]) Get added in v0.2.0

func (mr MultiResult[T]) Get(key string) (T, bool)

Get returns the value and a boolean indicating if the key exists. If the key doesn't exist the value will be the default zero value.

func (MultiResult[T]) IsEmpty added in v0.2.0

func (mr MultiResult[T]) IsEmpty() bool

IsEmpty returns a boolean indicating if the results are empty.

func (MultiResult[T]) Keys added in v0.2.0

func (mr MultiResult[T]) Keys() []string

Keys returns all the keys found.

func (MultiResult[T]) Values added in v0.2.0

func (mr MultiResult[T]) Values() []T

Values returns all the values found.

type Option

type Option func(c *Cache)

Option allows for the Cache behavior/configuration to be customized.

func Serialization

func Serialization(mar Marshaller, unmar Unmarshaller) Option

Serialization allows for the marshalling and unmarshalling behavior to be customized for the Cache.

A valid Marshaller and Unmarshaller must be provided. Providing nil for either will immediately panic.

type RedisClient added in v0.3.0

type RedisClient interface {
	Get(ctx context.Context, key string) *redis.StringCmd
	MGet(ctx context.Context, keys ...string) *redis.SliceCmd
	Set(ctx context.Context, key string, val any, ttl time.Duration) *redis.StatusCmd
	SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.BoolCmd
	SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.BoolCmd
	Del(ctx context.Context, keys ...string) *redis.IntCmd
	Watch(ctx context.Context, fn func(*redis.Tx) error, keys ...string) error
}

RedisClient is an interface type that defines the Redis functionality this package requires to use Redis as a cache.

type RetryableError added in v0.5.0

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

func (RetryableError) Error added in v0.5.0

func (e RetryableError) Error() string

func (RetryableError) IsRetryable added in v0.5.0

func (e RetryableError) IsRetryable() bool

type Unmarshaller

type Unmarshaller func(b []byte, v any) error

Unmarshaller is a function type that unmarshalls the value retrieved from the cache into the target type.

func DefaultUnmarshaller

func DefaultUnmarshaller() Unmarshaller

DefaultUnmarshaller returns a Unmarshaller using msgpack to unmarshall values.

type UpsertCallback added in v0.5.0

type UpsertCallback[T any] func(found bool, oldValue T, newValue T) T

UpsertCallback is a callback function that is invoked by Upsert. An UpsertCallback is passed if a key was found, the old value (or zero-value if the key wasn't found) and the new value. An UpsertCallback is responsible for determining what value should be set for a given key in the cache. The value returned from UpsertCallback is the value set.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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