cache

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 15, 2024 License: MIT Imports: 12 Imported by: 0

README

Rueidis Cache

Rueidis Cache is a library for caching/persisting arbitrary data in Redis. Rueidis Cache is built on Rueidis and is an abstraction layer over that library that provides serialization, compression, along with tracing and metrics via OpenTelemetry. Rueidis Cache is focused on the developer experience making it easy to store and retrieve any arbitrary data structure. Under the hood Rueidis Cache stores everything as the string data type in Redis by serializing to represent that data as bytes. By default, msgpack is used to serialize and deserialize data without compression but that behavior can be customized by providing Option when initializing.

Rueidis Cache is very similar to another library I maintain redis-cache. That library uses go-redis under the hood but provides near identical features. The main motivating factor for creating Rueidis Cache was Rueidis slight performance edge, and it's built in support for service-assisted client-side caching.

Features

  • Cache/Persist any data structure that can be represented as bytes
  • Built-in serialization with msgpack but can be swapped out for JSON, Protobuf, etc. or even a custom representation.
  • Built-in support for compression with built-in support for lz4, gzip, and brotli
  • Instrumentation/Metrics via OpenTelemetry
  • Support for read-through and write-through cache via helper functions like Cacheable

Requirements

  • Go 1.22
  • Redis 7+
  • Only compatible with Rueidis Redis client

This library may work on earlier versions of Redis, but I did my testing with Redis 7.4

Getting Redis Cache

go get github.com/jkratz55/rueidis-cache

Usage

Since Rueidis Cache is a wrapper and abstraction of Rueidis it requires a valid reference to a rueidis.Client prior to initializing and using the Cache type.

func main() {

    // Initialize rueidis redis client
    client, err := rueidis.NewClient(rueidis.ClientOption{
        InitAddress: []string{"localhost:6379"},
    })
    if err != nil {
        panic(err)
    }
    defer client.Close()
    
    // Ping Redis to ensure its reachable
    func () {
        ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
        defer cancel()
    
        err := client.Do(ctx, client.B().Ping().Build()).Error()
        if err != nil {
            panic(err)
        }
    }()

    // Initialize the Cache. This will use the default configuration but optionally
    // the serialization and compression can be swapped out and features like NearCaching
    // can be enabled.
    rdb := cache.New(client)
	
    // todo: remaining code goes here ...
}

Note that the rueidis.ClientOption type provides many configuration options. The above example is using the defaults but the client can be tuned and configured for specific workloads and requirements. Please refer to the rueidis documentation for further details configuring the client.

Expanding on the example above here is a very simple use case of setting, getting, and deleting entries.

package main

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"os"
	"time"

	"github.com/redis/rueidis"

	cache "github.com/jkratz55/rueidis-cache"
)

type Person struct {
	FirstName string
	LastName  string
	Age       int
}

func main() {

	// Initialize a logger. You can use any logger you wish but in this example
	// we are sticking to the standard library using slog.
	logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
		AddSource: true,
	}))

	// Initialize rueidis redis client
	client, err := rueidis.NewClient(rueidis.ClientOption{
		InitAddress: []string{"localhost:6379"},
	})
	if err != nil {
		logger.Error("error creating redis client", slog.String("err", err.Error()))
		panic(err)
	}
	defer client.Close()

	// Ping Redis to ensure its reachable
	func() {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()

		err := client.Do(ctx, client.B().Ping().Build()).Error()
		if err != nil {
			logger.Error("error pinging redis", slog.String("err", err.Error()))
			panic(err)
		}
	}()

	// Initialize the Cache. This will use the default configuration but optionally
	// the serialization and compression can be swapped out and features like NearCaching
	// can be enabled.
	rdb := cache.New(client)

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

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

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

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

This library also supports MGET but because of limitations in GO's implementation of generics MGet is a function instead of a method on the Cache type. The MGet function accepts the Cache type as an argument to leverage the same marshaller and unmarshaller. In some rare use cases where thousands of keys are being fetched you may need to enable batching to break up into multiple MGET calls to redis to prevent latency issues with Redis due to Redis single threaded nature and MGET being O(n) time complexity.

rdb := cache.New(client, BatchMultiGets(500)) // Break keys up into multiple MGET commands each with no more than 500 keys

This library also supports atomic updates of existing keys by using the Upsert function. If the key was modified while the upsert is in progress it will return RetryableError signaling the operation can be retried and the UpsertCallback can decide how to handle merging the changes.

As mentioned previously there are several options that can be passed into the New function when initializing the Cache. These options can change the serialization, enable compression with specific Codec or enable server assisted client side caching.

Serialization

By default, msgpack is used to serialize and deserialize data stored in Redis. However, you can serialize the data with encoding you choose as long as it adheres to the Marshaller and Unmarshaller types.

// Marshaller is a function type that marshals the value of a cache entry for
// storage.
type Marshaller func(v any) ([]byte, error)

// Unmarshaller is a function type that unmarshalls the value retrieved from the
// cache into the target type.
type Unmarshaller func(b []byte, v any) error

This allows data to be stored using msgpack, JSON, Protobuf, or a custom binary representation. Since JSON is a popular choice the library provide a convenient Option already called JSON

rdb := cache.New(client, cache.JSON()) // Configures the cache to use JSON for serialization

If you want to use a different encoding for serialization you can use the Serialization option. In the example below we are still using JSON, but you could use protobuf or whatever you wish.

marshaller := func(v any) ([]byte, error) {
    return json.Marshal(v)
}
unmarshaller := func(data []byte, v any) error {
    return json.Unmarshal(data, v)
rdb := cache.New(client, cache.Serialization(marshaller, unmarshaller)) // Configures the cache to use our custom serialization
}
Compression

In some cases compressing values stored in Redis can have tremendous benefits, particularly when storing large volumes of data, large values per key, or both. Compression reduces the size of the cache, significantly decreases bandwidth and latency but at the cost of additional CPU consumption on the application/client.

By default, compression is not enabled. However, compression can be enabled through the Serialization Option when initializing the Cache. Serialization accepts a Codec which enables bringing or implementing your own compression. For developer convenience there are several implementations available out of the box including gzip, flate, lz4 and brotli.

The following example uses lz4.

rdb := cache.New(client, cache.JSON(), cache.LZ4()) // cache.JSON is here to demonstrate multiple Options call be passed
Server Assisted Client Caching

Rueidis supports server assisted client side caching which utilizing a feature in Redis where it notifies the client if a key it's interesting in has be updated and invalidates the local cache. Rueidis cache supports this feature as well, but it is not enabled by default. To enable it, an Option needs to be passed to New when initializing the Cache.

rdb := cache.New(client, cache.NearCache(time.Minute * 10)) // This will keep entry in client side cache for no longer than 10 minutes but it can be evicted sooner if Redis notifies the client a key has changed.

Instrumentation & Tracing

Rueidis Cache and Rueidis supports metrics and tracing using OpenTelemetry. However, there are a couple to be aware of:

  • Because the way rueidis handles DoMulti and DoMultiCache instrumenting errors and client side cache hits is not tracked for performance reasons. This is due to the API of the Hook from the rueidishook package. In order to capture that level of detail we'd need to iterate over the results in the hot path and check for errors and cache hits. Since performance is a major goal of this library we only capture the overall execution time.

The following example demonstrates how to set up OpenTelemetry for tracing and metrics, exposing those metrics via Prometheus.

package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/redis/rueidis"
	"github.com/redis/rueidis/rueidisotel"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/sdk/resource"
	"go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.26.0"

	cache "github.com/jkratz55/rueidis-cache"
	"github.com/jkratz55/rueidis-cache/cacheotel"

	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	"go.opentelemetry.io/otel/exporters/prometheus"
	"go.opentelemetry.io/otel/sdk/metric"
)

func main() {

	var (
		enableNearCache = flag.Bool("near-cache", false, "Enables Rueidis Near Cache/Client Cache feature")
	)
	flag.Parse()

	// Initialize a logger. You can use any logger you wish but in this example
	// we are sticking to the standard library using slog.
	logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
		AddSource: true,
	}))

	// Setup OpenTelemetry trace exporter
	traceExporter, err := otlptracehttp.New(context.Background())
	if err != nil {
		logger.Error("error creating trace exporter", slog.String("err", err.Error()))
		panic(err)
	}

	// Configure OpenTelemetry resource and TracerProvider
	otelResource := resource.NewWithAttributes(
		semconv.SchemaURL,
		semconv.ServiceNameKey.String("cacheotel-example"),
		semconv.ServiceVersionKey.String("1.0.0"))
	traceProvider := trace.NewTracerProvider(
		trace.WithBatcher(traceExporter),
		trace.WithResource(otelResource))
	defer func() {
		err := traceProvider.Shutdown(context.Background())
		if err != nil {
			logger.Error("error shutting down trace provider", slog.String("err", err.Error()))
		}
	}()

	// Set the TraceProvider and TextMapPropagator globally
	otel.SetTracerProvider(traceProvider)
	otel.SetTextMapPropagator(propagation.TraceContext{})

	// Setup OpenTelemetry metric exporter
	exporter, err := prometheus.New()
	if err != nil {
		logger.Error("error creating prometheus exporter", slog.String("err", err.Error()))
		panic(err)
	}
	provider := metric.NewMeterProvider(metric.WithReader(exporter))
	otel.SetMeterProvider(provider)

	// Initialize the Rueidis Redis client with OpenTelemetry tracing
	// Note you can customize the client parameters here as needed
	redisClient, err := rueidisotel.NewClient(rueidis.ClientOption{
		InitAddress: []string{"localhost:6379"},
	},
		rueidisotel.WithMeterProvider(provider),
		rueidisotel.WithTracerProvider(traceProvider),
		rueidisotel.WithDBStatement(func(cmdTokens []string) string {
			// This is an example displaying logging the command sent to Redis.
			// In some cases this may not be optimal because the values are
			// compressed or in an unreadable format to humans, or perhaps its
			// just too much data.
			var builder strings.Builder
			for _, token := range cmdTokens {
				if utf8.ValidString(token) {
					builder.WriteString(token + " ")
				} else {
					// Because this example is using lz4 compression the resulting
					// command contains invalid utf8 strings which makes OTEL unhappy
					// so we need to do some formatting to ensure all the strings are
					// valid
					builder.WriteString(fmt.Sprintf("0x%x ", token))
				}
			}
			return builder.String()
		}),
	)
	if err != nil {
		logger.Error("error creating redis client", slog.String("err", err.Error()))
		panic(err)
	}
	defer redisClient.Close()

	// Ping Redis to ensure its reachable
	func() {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()

		err := redisClient.Do(ctx, redisClient.B().Ping().Build()).Error()
		if err != nil {
			logger.Error("error pinging redis", slog.String("err", err.Error()))
			panic(err)
		}
	}()

	// Enable instrumentation for the Redis client
	redisClient, err = cacheotel.InstrumentClient(redisClient,
		cacheotel.WithMeterProvider(provider),
		cacheotel.WithExplicitBucketBoundaries(cacheotel.ExponentialBuckets(0.001, 2, 6)))

	// Configure the Cache options to use compression and JSON serialization.
	// Typically, it is recommended to use the default encoding which is msgpack
	// but for this example, we will use JSON as it makes viewing the data in
	// Redis easier.
	opts := []cache.Option{
		cache.LZ4(),
		cache.JSON(),
	}
	if *enableNearCache {
		opts = append(opts, cache.NearCache(time.Minute*10))
	}

	// Initialize the Cache and enable instrumentation of the cache
	rdb := cache.New(redisClient, opts...)
	err = cacheotel.InstrumentMetrics(rdb, cacheotel.WithMeterProvider(provider),
		cacheotel.WithExplicitBucketBoundaries(cacheotel.ExponentialBuckets(0.001, 2, 6)))

	// Example HTTP handlers to demonstrate using the cache
	http.Handle("/metrics", promhttp.Handler())
	http.Handle("/get", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		key := r.URL.Query().Get("key")

		var res string
		err := rdb.Get(r.Context(), key, &res)
		if errors.Is(err, cache.ErrKeyNotFound) {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		w.Write([]byte(res))
	}))
	http.Handle("/set", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		key := r.URL.Query().Get("key")
		value := r.URL.Query().Get("value")

		err := rdb.Set(r.Context(), key, value, time.Minute*10)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
	}))

	_ = http.ListenAndServe(":8080", nil)
}

Documentation

Overview

Package cache implements a cache backed by Redis and the Rueidis Redis Go client.

Rueidis Cache provides an implementation of a Cache backed by Redis that handles serialization, compression, and metrics with little to no effort from the user.

Index

Constants

View Source
const (
	// InfiniteTTL indicates a key will never expire.
	//
	// Depending on Redis configuration keys may still be evicted if Redis is
	// under memory pressure in accordance to the eviction policy configured.
	InfiniteTTL time.Duration = -3
)

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 Cacheable

func Cacheable[T any](
	ctx context.Context,
	c *Cache,
	key string,
	readTimeout time.Duration,
	ttl time.Duration,
	fn func(ctx context.Context) (T, error)) (T, error)

Cacheable attempts to read a value from the cache for a given key. On a cache miss or read error, it executes the provided function (fn) to retrieve or compute the value. If successful, the value is then asynchronously stored in the cache with the specified TTL (time-to-live) for future requests.

This function implements a read-through cache pattern, where the cache is updated after a cache miss. Cacheable only returns an error if the value cannot be retrieved or computed by the provided function.

Errors encountered while storing the value in the cache are logged, but not returned to the caller, and the cache set operation occurs in a non-blocking goroutine.

The cache read operation is subject to a readTimeout, which defines the maximum duration for waiting on a cache response. If the cache read exceeds this timeout or fails, the provided function is called to compute the value.

func Delete

func Delete[T any](
	ctx context.Context,
	c *Cache,
	key string,
	val T,
	fn func(ctx context.Context, val T) error) error

Delete removes the value associated with the given key from both the source of truth and the cache. It first attempts to delete the value from the source of truth using the provided function. If this operation is successful, it then removes the corresponding entry from the cache.

Delete is designed to ensure that both the source of truth and the cache remain in sync. If the source of truth is updated but the cache is not, the system may end up in an inconsistent state. Therefore, it is essential to call this function whenever a value needs to be deleted from both the source of truth and the cache.

If either the delete operation from the source of truth or the cache fails, an error is returned, providing information on where the failure occurred.

func IsRetryable

func IsRetryable(err error) bool

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

func MGetValues

func MGetValues[T any](ctx context.Context, c *Cache, keys ...string) ([]T, error)

MGetValues fetches multiple keys from Redis and returns only the values. If the relationship between key -> value is required use MGet instead.

MGetValues is useful when you only want to values and want to avoid the overhead of allocating a slice from a MultiResult.

func Upsert

func Upsert[T any](ctx context.Context, c *Cache, key string, val T, cb UpsertCallback[T], ttl time.Duration) 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, time.Minute * 1)
	if rcache.IsRetryable(err) {
		continue
	}
	// do something useful ...
	break
}

func Version

func Version() string

Version returns the current version of rueidis-cache.

func Write

func Write[T any](
	ctx context.Context,
	c *Cache,
	key string,
	val T,
	ttl time.Duration,
	fn func(ctx context.Context, v T) error) error

Write first invokes the provided function to write the value to the source of truth. If the write operation is successful, the value is then synchronously stored in the cache with the specified TTL (time-to-live) for future requests.

Write implements a write-through cache pattern, where the cache is updated after the source of truth is updated. Write is NOT atomic. It is possible that the source of truth is updated but the cache is not. This is a trade-off for the performance benefits of write-through caching. If either write operation fails, an error is returned.

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 New function.

func New

func New(client rueidis.Client, opts ...Option) *Cache

New 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) AddHook

func (hs *Cache) AddHook(hook Hook)

AddHook adds a Hook to the processing chain.

func (*Cache) Client

func (c *Cache) Client() rueidis.Client

Client returns the underlying Redis client the Cache is wrapping/using.

This can be useful when there are operations that are not supported by Cache that are required, but you don't want to have to pass the Redis client around your application as well. This allows for the Redis client to be accessed from the Cache. However, it is important to understand that when using the Redis client directly it will not have the same behavior as the Cache. You will have to handle marshalling, unmarshalling, and compression yourself. You will also not have the same hooks behavior as the Cache and some metrics will not be tracked.

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) Expire

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

Expire sets a TTL on the given key.

If the key doesn't exist ErrKeyNotFound will be returned for the error value. If the key already has a TTL it will be overridden with ttl value provided.

Calling Expire with a non-positive ttl will result in the key being deleted.

func (*Cache) ExtendTTL

func (c *Cache) ExtendTTL(ctx context.Context, key string, dur time.Duration) error

ExtendTTL extends the TTL for the key by the given duration.

ExtendTTL retrieves the TTL remaining for the key, adds the duration, and then executes the EXPIRE command to set a new TTL.

If the key doesn't exist ErrKeyNotFound will be returned for the error value.

func (*Cache) Flush

func (c *Cache) Flush(ctx context.Context) error

Flush flushes the cache deleting all keys/entries.

func (*Cache) FlushAsync

func (c *Cache) FlushAsync(ctx context.Context) error

FlushAsync flushes the cache deleting all keys/entries asynchronously. Only keys that were present when FLUSH ASYNC command was received by Redis will be deleted. Any keys created during asynchronous flush will be unaffected.

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 v.

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) GetAndUpdateTTL

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

GetAndUpdateTTL retrieves a value from the Cache for the given key, decompresses it if applicable, unmarshalls the value to v, and updates the TTL for the key.

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.

The TTL/expiration for the key is updated to the provided key if it exists, even if the key did not have a TTL previously. If the ttl value is <= 0 the key will be persisted indefinitely.

func (*Cache) Healthy

func (c *Cache) Healthy(ctx context.Context) bool

Healthy pings Redis to ensure it is reachable and responding. Healthy returns true if Redis successfully responds to the ping, otherwise false.

Note that Healthy does not guarantee that Redis is fully operational, only that it is reachable and responding to pings.

func (*Cache) Keys

func (c *Cache) Keys(ctx context.Context) ([]string, error)

Keys retrieves all the keys in Redis/Cache

func (*Cache) MSet

func (c *Cache) MSet(ctx context.Context, keyvalues map[string]any) error

MSet performs multiple SET operations. Entries are added to the cache or overridden if they already exists.

MSet is atomic, either all keyvalues are set or none are set. Since MSet operates using a single atomic command it is the fastest way to bulk write entries to the Cache. It greatly reduces network overhead and latency when compared to calling SET sequentially.

func (*Cache) ScanKeys

func (c *Cache) ScanKeys(ctx context.Context, pattern string) ([]string, error)

ScanKeys allows for scanning keys in Redis using a pattern.

func (*Cache) Set

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

Set adds an entry into the cache, or overwrites an entry if the key already existed. If the ttl value is <= 0 the key will be persisted indefinitely.

func (*Cache) SetClient

func (c *Cache) SetClient(client rueidis.Client)

SetClient sets the underlying Redis client the Cache is wrapping/using.

Passing nil is not permitted and will result in a panic.

func (*Cache) SetIfAbsent

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

SetIfAbsent adds an entry into the cache only if the key doesn't already exist. The entry is set with the provided TTL and automatically removed from the cache once the TTL is expired. If the ttl value is <= 0 the key will be persisted indefinitely.

func (*Cache) SetIfPresent

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

SetIfPresent updates an entry into the cache if they key already exists in the cache. The entry is set with the provided TTL and automatically removed from the cache once the TTL is expired. If the ttl value is <= 0 the key will be persisted indefinitely.

func (*Cache) TTL

func (c *Cache) TTL(ctx context.Context, key string) (time.Duration, error)

TTL returns the time to live for a particular key.

If the key doesn't exist ErrKeyNotFound will be returned for the error value. If the key doesn't have a TTL InfiniteTTL will be returned.

type Codec

type Codec interface {
	Flate(data []byte) ([]byte, error)
	Deflate(data []byte) ([]byte, error)
}

Codec is an interface type that defines the behavior for compressing and decompressing data.

type CompressionHook

type CompressionHook func(data []byte) ([]byte, error)

CompressionHook is a function type that is invoked prior to compressing or decompressing data.

type Hook

type Hook interface {
	MarshalHook(next Marshaller) Marshaller
	UnmarshallHook(next Unmarshaller) Unmarshaller
	CompressHook(next CompressionHook) CompressionHook
	DecompressHook(next CompressionHook) CompressionHook
}

Hook is an interface type defining the operations that can be intercepted and potentially allow for their behavior to be modified.

The primary intention of Hook is to allow for observability: instrumentation, logging, tracing, etc.

It is important implementations of Hook call next or the execution pipeline will terminate.

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

type MultiResult[T any] map[string]T

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

func MGet

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.

If a key doesn't exist in Redis it will not be included in the MultiResult returned. If all keys are not found the MultiResult will be empty.

func Scan

func Scan[T any](ctx context.Context, c *Cache, pattern string) (MultiResult[T], error)

Scan retrieves all the keys and values from Redis matching the given pattern.

Scan works similar to MGet, but allows a pattern to be specified rather than providing keys.

func (MultiResult[T]) Get

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

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

IsEmpty returns a boolean indicating if the results are empty.

func (MultiResult[T]) Keys

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

Keys returns all the keys found.

func (MultiResult[T]) Values

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 BatchMultiGets

func BatchMultiGets(batchSize int) Option

BatchMultiGets configures the Cache to use pipelining and split keys up into multiple MGET commands for increased throughput and lower latency when dealing with MGet operations with very large sets of keys.

func Brotli

func Brotli() Option

Brotli configures the Cache to use Brotli for compressing and decompressing values stored in Redis. The default Brotli configuration uses a balanced approach between speed and compression level.

func Compression

func Compression(codec Codec) Option

Compression allows for the values to be flated and deflated to conserve bandwidth and memory at the cost of higher CPU time. Compression accepts a Codec to handle compressing and decompressing the data to/from Redis.

func Flate

func Flate() Option

Flate configures the Cache to use Flate Codec for compressing and decompressing values stored in Redis. Flate uses a default configuration favoring compression over speed.

func GZip

func GZip() Option

GZip configures the Cache to use gzip for compressing and decompressing values stored in Redis. GZip uses a default configuration favoring compression size over speed

func JSON

func JSON() Option

JSON is a convenient Option for configuring Cache to use JSON for serializing data stored in the cache.

JSON is the equivalent of using Serialization passing it a Marshaller and Unmarshaller using json.

func LZ4

func LZ4() Option

LZ4 configures the Cache to use lz4 for compressing and decompressing values stored in Redis.

func NearCache

func NearCache(ttl time.Duration) Option

NearCache enables a local in-memory cache to reduce the load on Redis and improve the latency.

Providing a TTL <=0 is a no-op.

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 RetryableError

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

func (RetryableError) Error

func (e RetryableError) Error() string

func (RetryableError) IsRetryable

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 an Unmarshaller using msgpack to unmarshall values.

type UpsertCallback

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
compression
lz4
examples

Jump to

Keyboard shortcuts

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