rueidisaside

package
v1.0.53 Latest Latest
Warning

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

Go to latest
Published: Jan 13, 2025 License: Apache-2.0 Imports: 10 Imported by: 3

README

rueidisaside

A Cache-Aside pattern implementation enhanced by Client Side Caching.

Features backed by the Redis Client Side Caching

Cache-Aside is a widely used pattern to cache other data sources into Redis. However, there are many issues to be considered when implementing it.

For example, an implementation without locking or versioning may cause a fresh cache be overridden by a stale one. And if using a locking mechanism, how to get notified when a lock is released? If using versioning mechanism, how to version an empty value?

Thankfully, the above issues can be addressed better with the client-side caching along with the following additional benefits:

  • Avoiding unnecessary network round trips. Redis will proactively invalidate the client-side cache.
  • Avoiding Cache Stampede by locking keys with the client-side caching, the same technique used in rueidislock. Only the first cache missed call can update the cache and others will wait for notifications.

Example

package main

import (
	"context"
	"database/sql"
	"time"

	"github.com/redis/rueidis"
	"github.com/redis/rueidis/rueidisaside"
)

func main() {
	var db sql.DB
	client, err := rueidisaside.NewClient(rueidisaside.ClientOption{
		ClientOption: rueidis.ClientOption{InitAddress: []string{"127.0.0.1:6379"}},
	})
	if err != nil {
		panic(err)
	}
	val, err := client.Get(context.Background(), time.Minute, "mykey", func(ctx context.Context, key string) (val string, err error) {
		if err = db.QueryRowContext(ctx, "SELECT val FROM mytab WHERE id = ?", key).Scan(&val); err == sql.ErrNoRows {
			val = "_nil_" // cache nil to avoid penetration.
			err = nil     // clear err in case of sql.ErrNoRows.
		}
		return
	})
	if err != nil {
		panic(err)
	} else if val == "_nil_" {
		val = ""
		err = sql.ErrNoRows
	} else {
		// ...
	}
}

If you want to use cache typed value, not string, you can use rueidisaside.TypedCacheAsideClient.

package main

import (
	"context"
	"database/sql"
	"encoding/json"
	"time"

	"github.com/redis/rueidis"
	"github.com/redis/rueidis/rueidisaside"
)

type MyValue struct {
	Val string `json:"val"`
}

func main() {
	var db sql.DB
	client, err := rueidisaside.NewClient(rueidisaside.ClientOption{
		ClientOption: rueidis.ClientOption{InitAddress: []string{"127.0.0.1:6379"}},
	})
	if err != nil {
		panic(err)
	}

	serializer := func(val *MyValue) (string, error) {
		b, err := json.Marshal(val)
		return string(b), err
	}
	deserializer := func(s string) (*MyValue, error) {
		var val *MyValue
		if err := json.Unmarshal([]byte(s), &val); err != nil {
			return nil, err
		}
		return val, nil
	}

	typedClient := rueidisaside.NewTypedCacheAsideClient(client, serializer, deserializer)
	val, err := typedClient.Get(context.Background(), time.Minute, "myKey", func(ctx context.Context, key string) (*MyValue, error) {
		var val MyValue
		if err := db.QueryRowContext(ctx, "SELECT val FROM mytab WHERE id = ?", key).Scan(&val.Val); err == sql.ErrNoRows {
			return nil, nil
		} else if err != nil {
			return nil, err
		}
		return &val, nil
	})
	// ...
}

Limitation

Currently, requires Redis >= 7.0.

Documentation

Index

Constants

View Source
const PlaceholderPrefix = "rueidisid:"

Variables

This section is empty.

Functions

This section is empty.

Types

type CacheAsideClient

type CacheAsideClient interface {
	Get(ctx context.Context, ttl time.Duration, key string, fn func(ctx context.Context, key string) (val string, err error)) (val string, err error)
	Del(ctx context.Context, key string) error
	Client() rueidis.Client
	Close()
}

func NewClient

func NewClient(option ClientOption) (cc CacheAsideClient, err error)

type Client

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

func (*Client) Client added in v1.0.23

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

Client exports the underlying rueidis.Client

func (*Client) Close

func (c *Client) Close()

func (*Client) Del

func (c *Client) Del(ctx context.Context, key string) error

func (*Client) Get

func (c *Client) Get(ctx context.Context, ttl time.Duration, key string, fn func(ctx context.Context, key string) (val string, err error)) (string, error)

type ClientOption

type ClientOption struct {
	// ClientBuilder can be used to modify rueidis.Client used by Locker
	ClientBuilder func(option rueidis.ClientOption) (rueidis.Client, error)
	ClientOption  rueidis.ClientOption
	ClientTTL     time.Duration // TTL for the client marker, refreshed every 1/2 TTL. Defaults to 10s. The marker allows other client to know if this client is still alive.
}

type TypedCacheAsideClient added in v1.0.53

type TypedCacheAsideClient[T any] interface {
	Get(ctx context.Context, ttl time.Duration, key string, fn func(ctx context.Context, key string) (val *T, err error)) (val *T, err error)
	Del(ctx context.Context, key string) error
	Client() CacheAsideClient
}

TypedCacheAsideClient is an interface that provides a typed cache-aside client. It allows you to cache and retrieve values of a specific type T.

func NewTypedCacheAsideClient added in v1.0.53

func NewTypedCacheAsideClient[T any](
	client CacheAsideClient,
	serializer func(*T) (string, error),
	deserializer func(string) (*T, error),
) TypedCacheAsideClient[T]

NewTypedCacheAsideClient creates a new TypedCacheAsideClient instance that provides a typed cache-aside client. The client, serializer, and deserializer functions are used to interact with the underlying cache. The serializer function is used to convert the provided value of type T to a string, and the deserializer function is used to convert the cached string value back to the original type T.

Jump to

Keyboard shortcuts

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