redis

package module
v1.5.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 13 Imported by: 3

README

go-redis 🧰

🧷 Resilient Redis wiring for Go in one call — sensible pool, timeouts, and retries by default, with sentinel failover, cluster, and a health check built in, plus an optional toolkit for the patterns every service rebuilds.

go-redis wraps redis/go-redis/v9 so a service stops re-deriving the same pool sizes, timeouts, and retry policy in every codebase. Connect returns a client that has already Pinged the server, so a successful call means you are ready to serve.

✨ Highlights

  • 🛟 Resilient by default — pool, timeouts, retries with backoff, sentinel failover and cluster from one Connect.
  • ⚡ Fails fast when you ask — FastFail() gives health probes and fail-open paths a view that gives up in milliseconds while Redis is down.
  • 🧰 A toolkit on top — Lua scripts, caching, streams, pub/sub, locks, idempotency keys and rate limiting, each in its own package.
  • 🪶 Pay for what you import — the core has no logging or telemetry dependency; the toolkit and otel live in subpackages.
  • 🔇 Quiet unless asked — every tool takes a go-log logger for generous diagnostics and discards everything by default.
  • 🧪 Tested against real Redis — unit tests on miniredis, integration tests on redis:7 in CI.

📦 Install

go get github.com/Bugs5382/go-redis

🚀 Usage

Connect applies resilient defaults, dials, and verifies readiness with a Ping. Redis() hands you the full go-redis command API.

client, err := redis.Connect(ctx, redis.WithAddr("localhost:6379"))
if err != nil {
	log.Fatal(err)
}
defer client.Close()

rdb := client.Redis()
rdb.Set(ctx, "session:123", "alice", time.Minute)
name, _ := rdb.Get(ctx, "session:123").Result()

🧩 Depend on go-redis only

Nil, Cmdable, and UniversalClient re-export the handful of go-redis names a caller needs in order to name the value Redis() returns, so a wrapper library can depend on go-redis alone and never import github.com/redis/go-redis/v9 directly:

var cmd redis.Cmdable = client.Redis() // or redis.UniversalClient

_, err := cmd.Get(ctx, "session:missing").Result()
if errors.Is(err, redis.Nil) {
	// cache miss
}

Transactions, pipelines and streams get the same treatment. TxFailedErr, Tx, Pipeliner, Cmder, StringCmd, XAddArgs, XMessage and XMessageSliceCmd are aliases of the go-redis names, so an optimistic WATCH loop needs nothing else:

err := client.Redis().Watch(ctx, func(tx *redis.Tx) error {
	n, err := tx.Get(ctx, "audit:head").Result()
	if err != nil && !errors.Is(err, redis.Nil) {
		return err
	}
	_, err = tx.TxPipelined(ctx, func(p redis.Pipeliner) error {
		p.XAdd(ctx, &redis.XAddArgs{Stream: "audit", ID: "*", Values: map[string]any{"prev": n}})
		p.Set(ctx, "audit:head", "next", 0)
		return nil
	})
	return err
}, "audit:head")
if errors.Is(err, redis.TxFailedErr) {
	// another writer got there first: retry
}

🛟 High availability

Point at Redis Sentinel for automatic failover — the client discovers the current master through the sentinels and re-resolves it after a switchover. Redis Cluster is one option away.

// Sentinel-managed failover master.
client, _ := redis.Connect(ctx,
	redis.WithSentinel("mymaster", "localhost:26379", "localhost:26380"),
)

// Redis Cluster.
client, _ := redis.Connect(ctx,
	redis.WithCluster("localhost:7000", "localhost:7001", "localhost:7002"),
)

🎛 Options

Every knob is a functional option, and every one has a resilient default.

client, _ := redis.Connect(ctx,
	redis.WithAddr("localhost:6379"),
	redis.WithUsername("orders-svc"), // an ACL user; leave unset for the default user
	redis.WithPassword("s3cret"),
	redis.WithDB(0),
	redis.WithTLS(&tls.Config{}),
	redis.WithPool(50),
	redis.WithTimeouts(5*time.Second, 3*time.Second, 3*time.Second),
	redis.WithRetry(3, 8*time.Millisecond, 512*time.Millisecond),
	redis.WithDialRetry(5, 100*time.Millisecond),
	redis.WithFastFail(200*time.Millisecond),
)

The retry defaults favour riding out a blip over failing quickly. Each new connection is dialed up to 5 times with 100ms between tries (the go-redis dial loop, made explicit by WithDialRetry), and each command is retried up to 3 times on top of that. While the server is down, one call through the default client can take well over a second to fail (about 1.7s against a refused port in the tests). That is the right trade for work that needs Redis, and the wrong one for work that can do without it; see Failing open for the per-call answer instead of lowering the defaults for everyone.

🔐 ACL users

On Redis 6+ or Valkey, give each service its own ACL user with only the keys and commands it needs, and connect as it. WithUsername sends the user with the WithPassword password on every connection: standalone, cluster, the sentinel-managed master and replicas, and the FastFail view. The sentinels themselves are not authenticated.

// ACL SETUSER orders-svc on >s3cret ~orders:* +@all -@dangerous
client, err := redis.Connect(ctx,
	redis.WithAddr("cache.example.org:6379"),
	redis.WithUsername("orders-svc"),
	redis.WithPassword("s3cret"),
)

Without a username the password authenticates the server's default user, as before. A wrong user or password fails Connect with WRONGPASS, which WaitFor returns at once instead of retrying.

💓 Health

Healthy is a Ping. On the client from Connect it carries the normal retries, so while the server is down it can take a second or more to answer false. For a readiness probe, ask the fail-fast view, which answers within its budget:

if !client.FastFail().Healthy(ctx) {
	// fail the probe
}

⚡ Failing open

Some calls should skip Redis when it is down rather than wait for it: a cache read that can go to the source, a rate-limit check that can let the request through, a feature flag with a safe default. FastFail() returns a view of the same server with no command retries, a single dial try, and a short budget (200ms by default, set with WithFastFail) on its dial, read, write and pool-wait timeouts. While the server refuses connections, a call through the view fails in well under a millisecond.

ff := client.FastFail()

v, err := ff.Redis().Get(ctx, "flags:new-checkout").Result()
switch {
case err == nil:
	return v == "on"
case errors.Is(err, redis.Nil):
	return false // not set
default:
	logger.Warn("flag store unavailable, using the default", "err", err)
	return false // fail open
}

The view is a *redis.Client, so the toolkit takes it as is:

users := cache.New[User](client.FastFail()) // a down Redis falls through to the loader at once
limiter := ratelimit.New(client.FastFail())  // decide what a limiter error means: allow or deny

A few things to know:

  • 🔁 One view per client — the view has its own connection pool, built on the first FastFail() call and opened only when used. Calling it again returns the same view.
  • 🧹 The parent owns it — Close on the view does nothing; closing the client closes both.
  • 🎯 Pick the budget above your normal latency — a slow but healthy server that needs longer than the budget fails on the view too, and the caller takes its fallback.
  • 🚫 Not for writes that must land — keep those on the retrying client, where a blip is ridden out instead of reported.
  • 🔍 Telling down from broken — Transient(err) is true for the errors that mean the server is unreachable, if a fallback should only cover those.

⏳ Waiting for the server at startup

Connect pings once, so a service that starts before Redis or Valkey answers (after a reboot, say) gets an error. WaitFor retries while the error is Transient (network failures, and the LOADING, MASTERDOWN, TRYAGAIN and CLUSTERDOWN replies), with capped exponential backoff and jitter. Anything else, such as WRONGPASS, is returned at once.

var client *redis.Client
err := redis.WaitFor(ctx, func(ctx context.Context) error {
	c, err := redis.Connect(ctx, redis.WithAddr("cache.example.org:6379"))
	client = c
	return err
}, redis.WithRetryHook(func(attempt int, err error, delay time.Duration) {
	logger.Warn("redis not reachable yet, retrying", "attempt", attempt, "in", delay, "err", err)
}))

WithWaitBackoff(base, max) tunes the waits (default 500ms doubling to 15s).

🔌 Logging & metrics

A minimal Logger (dial diagnostics) and Observer (per-command and per-dial signals) are pluggable and default to no-ops, so the core drags in no logging or telemetry dependency.

client, _ := redis.Connect(ctx,
	redis.WithAddr("localhost:6379"),
	redis.WithObserver(myObserver),
)

📊 OpenTelemetry

The optional otel subpackage turns on tracing and metrics in one call against the global providers, keeping the dependency out of the core.

import redisotel "github.com/Bugs5382/go-redis/otel"

client, _ := redis.Connect(ctx, redis.WithAddr("localhost:6379"))
redisotel.Instrument(client) // spans + metrics per command

🧰 Toolkit

Each tool is optional, builds only on *redis.Client, and lives in its own package, so importing one pulls in nothing else. They all take a context.Context, log through a go-log Logger passed with WithLogger (keys and IDs only, never values; silent by default), and return plain wrapped errors you match with errors.Is.

Package For
script Lua scripts with EVALSHA and a transparent EVAL fallback
cache Typed get-or-set with codecs, jitter, negative caching and stampede protection
streams A resilient consumer-group consumer with retries and dead letters
pubsub A subscriber that re-subscribes after reconnects
lock A lock with token-checked release and auto-extend
idempotency Run a function once per key and replay its result
ratelimit Token-bucket and sliding-window rate limiting

📜 Lua scripts

script.New hashes the source once; Run sends EVALSHA and, when the server answers NOSCRIPT (first use, after a failover or SCRIPT FLUSH), falls back to EVAL, which caches it again. The lock, idempotency and rate-limit tools are built on it.

import "github.com/Bugs5382/go-redis/script"

incr := script.New(`return redis.call('INCRBY', KEYS[1], ARGV[1])`)
n, err := incr.Run(ctx, client, []string{"counter"}, 5).Int64()

Pass every key the script touches in keys. On a cluster client, keys that hash to different slots fail with ErrCrossSlot before anything is sent, so use a hash tag such as {order:7}:items to keep related keys together.

🗄️ Cache

A typed Cache[T] with Get, Set, GetOrSet and Delete. A miss is ErrMiss, never confused with a failure.

import "github.com/Bugs5382/go-redis/cache"

users := cache.New[User](client,
	cache.WithPrefix("users:"),
	cache.WithJitter(0.1),              // spread expiries
	cache.WithNegativeTTL(time.Minute), // remember "not found"
)
u, err := users.GetOrSet(ctx, id, 5*time.Minute, func(ctx context.Context) (User, error) {
	return db.LoadUser(ctx, id) // return cache.ErrNotFound when there is no such user
})

Concurrent GetOrSet calls for one key share a single load, within one process. If Redis is down, GetOrSet logs a warning and serves from the loader. Values are JSON unless you pass another Codec.

🌊 Streams

streams.Consume runs one consumer in a consumer group: it creates the group, reads with XREADGROUP, acks what the handler accepts, reclaims entries stuck with dead consumers (XAUTOCLAIM), and moves messages that keep failing to a dead-letter stream.

import "github.com/Bugs5382/go-redis/streams"

_, _ = streams.Publish(ctx, client, "orders", map[string]any{"order_id": "A-1"}, streams.WithMaxLen(100000))

err := streams.Consume(ctx, client, streams.Config{
	Stream:        "orders",
	Group:         "billing",
	Consumer:      podName,
	MaxDeliveries: 5, // then to "orders:dead"
}, func(ctx context.Context, msg streams.Message) error {
	return bill(ctx, msg.Values["order_id"]) // an error retries after ClaimMinIdle
})

Delivery is at least once: a message is handled again if its ack is lost, so handlers must be idempotent (the idempotency tool below pairs well). Redis errors back off and retry, and cancelling the context drains in-flight handlers before Consume returns.

📣 Pub/sub

pubsub.Subscribe returns once every channel and pattern is confirmed, then re-subscribes after a dropped connection or failover. Healthy backs a readiness probe.

import "github.com/Bugs5382/go-redis/pubsub"

sub, err := pubsub.Subscribe(ctx, client, pubsub.Topics{
	Channels: []string{"config.reload"},
	Patterns: []string{"orders.*"},
}, func(ctx context.Context, msg pubsub.Message) {
	route(ctx, msg.Channel, msg.Payload)
})
if err != nil {
	return err
}
defer sub.Close()

Redis pub/sub is at most once: messages published while the subscriber is disconnected are lost. Use streams when every message must arrive. The handler runs one message at a time, so hand slow work to your own goroutines.

🔒 Lock

lock.Acquire stores a random token with SET NX PX; Release and Extend only act while the key still holds that token, so an expired holder can never free the next holder's lock.

import "github.com/Bugs5382/go-redis/lock"

l, err := lock.Acquire(ctx, client, "jobs:nightly-report", 30*time.Second,
	lock.WithWait(5*time.Second),        // retry with backoff instead of failing fast
	lock.WithAutoExtend(10*time.Second), // keep it alive while the job runs
)
if errors.Is(err, lock.ErrNotAcquired) {
	return nil // another worker has it
}
defer l.Release(ctx)
// stop work if <-l.Lost() fires

This is a single-instance lock, not Redlock. Redis replicates asynchronously, so a failover can hand the same lock to a second client, and a long pause can outlive the TTL. Use it freely to avoid duplicate work; when overlapping holders would corrupt data, add a fencing token or make the downstream idempotent.

🔁 Idempotency

Store[T].Do runs a function at most once per key within a TTL and replays the stored result to duplicates, which is what a retried request or a redelivered message needs.

import "github.com/Bugs5382/go-redis/idempotency"

payments := idempotency.New[Receipt](client, idempotency.WithLease(30*time.Second))
r, err := payments.Do(ctx, requestID, 24*time.Hour, func(ctx context.Context) (Receipt, error) {
	return charge(ctx, order)
})
// a duplicate while charge runs gets idempotency.ErrInProgress (or waits, with WithWait)

On failure the key is released for a retry by default; KeepOnError stores the failure instead. The lease frees keys left by a caller that crashed mid-run, so keep it above the function's longest run. Like the lock, the guarantee is as strong as the Redis primary holding the key.

🚦 Rate limiting

ratelimit decides each request with one atomic Lua script per key, timed by the Redis server clock, so every app server agrees. It works on cluster.

import "github.com/Bugs5382/go-redis/ratelimit"

limiter := ratelimit.New(client) // token bucket; ratelimit.WithStrategy(ratelimit.SlidingWindow) for an exact window
r, err := limiter.Allow(ctx, "api:"+userID, ratelimit.Limit{Rate: 100, Period: time.Minute, Burst: 20})
if err != nil {
	return err
}
if !r.Allowed {
	return tooManyRequests(r.RetryAfter)
}

The token bucket allows bursts up to Burst and refills at Rate per Period; the sliding window never admits more than Rate in any Period. It needs Redis 5 or newer (the scripts call TIME).

🛠 Develop

task build    # go build ./...
task test     # go test ./...
task ci       # build + vet + race tests + linters
task license  # verify MIT headers (golic)

Integration tests run against a live server and are excluded from the default build:

REDIS_ADDR=localhost:6379 task test-integration

⚖️ License

MIT © 2026 Shane

Documentation

Overview

Package redis wraps github.com/redis/go-redis/v9 with resilient defaults so a service gets a correctly configured Redis client from a single Connect call: a sensible pool, bounded timeouts, a few retries with backoff, and a startup Ping that verifies readiness. Functional options select a standalone server, a sentinel-managed failover master, or a cluster, and tune pool, timeouts, retries, TLS, and pluggable Logger/Observer hooks. The returned Client exposes the underlying go-redis UniversalClient for the full command API.

Client.FastFail returns a view of the same server for health probes and fail-open paths: no command retries, a single dial attempt and a short budget (WithFastFail), so a call fails in milliseconds while the server is down instead of paying the default retries. WithDialRetry makes the go-redis dial-retry defaults explicit and tunable.

Nil, Cmdable, and UniversalClient re-export the handful of go-redis names a caller needs to work with Client.Redis's return value -- the miss sentinel and the command/client interfaces -- so a consumer of this package never needs to import github.com/redis/go-redis/v9 directly.

Index

Examples

Constants

This section is empty.

Variables

View Source
var Nil = goredis.Nil

Nil is the sentinel error go-redis returns when a command finds no result (for example GET on a missing key). It is re-exported here so a caller can write errors.Is(err, redis.Nil) without importing github.com/redis/go-redis/v9 directly.

View Source
var TxFailedErr = goredis.TxFailedErr

TxFailedErr is the error a WATCH transaction returns when a watched key changed before EXEC, so nothing was written and the caller may retry. It is re-exported so a caller can write errors.Is(err, redis.TxFailedErr) without importing github.com/redis/go-redis/v9 directly.

Functions

func Transient added in v1.3.0

func Transient(err error) bool

Transient reports whether err means the server is not reachable or not ready yet, as opposed to a failure that retrying will not fix. It is true for network failures (refused or reset connections, failed DNS lookups, timeouts, a cut connection) and for the LOADING, MASTERDOWN, TRYAGAIN and CLUSTERDOWN replies. Any other reply, such as WRONGPASS or NOAUTH, is not transient, and neither is a closed client. It unwraps the chain, so the errors Connect returns qualify.

func WaitFor added in v1.3.0

func WaitFor(ctx context.Context, fn func(context.Context) error, opts ...WaitOption) error

WaitFor calls fn until it succeeds, retrying while its error is Transient, so a service can start before its Redis or Valkey answers instead of exiting. A non-transient error is returned at once. When ctx ends first, the error wraps both ctx's error and the last attempt's. Each wait is a capped exponential backoff with jitter. fn is typically a Connect call.

Types

type Client

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

Client is a thin, resilient wrapper over a go-redis UniversalClient. Build one with Connect; use Redis to reach the full go-redis command API, Healthy for a liveness probe, and Close to release the pool. A Client is safe for concurrent use by multiple goroutines.

func Connect

func Connect(ctx context.Context, opts ...Option) (*Client, error)

Connect builds a Redis client from the given options, applies the resilient defaults for anything left unset, and verifies readiness with a Ping before returning. The mode (standalone, sentinel, or cluster) is chosen by the WithAddr, WithSentinel, or WithCluster option; standalone localhost:6379 is the default. On a failed Ping the underlying client is closed and the error is returned, so a returned Client is always usable. The ctx bounds both the dial and the readiness Ping.

Example

Connect to a standalone server with the resilient defaults, then read and write through the underlying go-redis client.

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	redis "github.com/Bugs5382/go-redis"
)

func main() {
	ctx := context.Background()

	client, err := redis.Connect(ctx, redis.WithAddr("localhost:6379"))
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	rdb := client.Redis()
	if err := rdb.Set(ctx, "session:123", "alice", time.Minute).Err(); err != nil {
		log.Fatal(err)
	}
	name, err := rdb.Get(ctx, "session:123").Result()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(name)
}
Example (Sentinel)

Connect through Redis Sentinel for high availability: the client discovers the current master through the sentinels and re-resolves it after a failover.

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	redis "github.com/Bugs5382/go-redis"
)

func main() {
	ctx := context.Background()

	client, err := redis.Connect(ctx,
		redis.WithSentinel("mymaster", "localhost:26379", "localhost:26380"),
		redis.WithPassword("s3cret"),
		redis.WithPool(50),
		redis.WithTimeouts(5*time.Second, 3*time.Second, 3*time.Second),
		redis.WithRetry(3, 8*time.Millisecond, 512*time.Millisecond),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	if client.Healthy(ctx) {
		fmt.Println("ready")
	}
}

func (*Client) Close

func (c *Client) Close() error

Close releases the connection pool, and the FastFail view's pool when one was built. It is safe to call once; further use of the Client after Close returns errors from go-redis. On the FastFail view, Close does nothing: the parent owns the view's pool.

func (*Client) FastFail added in v1.4.0

func (c *Client) FastFail() *Client

FastFail returns a view of the same server that gives up quickly instead of retrying: no command retries, a single dial attempt, and the WithFastFail budget (default 200ms) as its dial, read, write and pool-wait timeouts. While the server is down, a call through the view fails in about the time it takes to be refused (or the budget, for a host that does not answer) instead of the seconds the retrying path can spend.

Use it where Redis is optional and the caller has a fallback: a cache read that falls back to the source, a rate-limit check that lets the request through, a readiness probe. The returned value is a *Client, so it plugs into the toolkit packages (cache, ratelimit and the rest) unchanged. Errors from the view are the usual go-redis errors; Transient reports true for the ones that mean the server is unreachable.

The view has its own small connection pool, built on the first call and shared by every later call; it opens connections only when used. It inherits the Logger and Observer given to Connect. The parent owns it: Close on the view is a no-op, and closing the parent closes the view. FastFail on the view returns the view.

Example

Name the command surface with this package's own alias -- Cmdable -- and check a miss against the re-exported Nil sentinel. Neither line requires importing github.com/redis/go-redis/v9. Read through the fail-fast view and fall back when Redis is unavailable, instead of waiting out the default retries.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"time"

	redis "github.com/Bugs5382/go-redis"
)

func main() {
	ctx := context.Background()

	client, err := redis.Connect(ctx,
		redis.WithAddr("localhost:6379"),
		redis.WithFastFail(100*time.Millisecond),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	ff := client.FastFail()
	if !ff.Healthy(ctx) {
		fmt.Println("not ready")
	}

	flag, err := ff.Redis().Get(ctx, "flags:new-checkout").Result()
	switch {
	case err == nil:
		fmt.Println("flag:", flag)
	case errors.Is(err, redis.Nil):
		fmt.Println("flag not set")
	default:
		fmt.Println("flag store unavailable, using the default")
	}
}

func (*Client) Healthy

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

Healthy reports whether the server answers a Ping within ctx. On a Client from Connect the Ping carries the normal retries, so while the server is down it can take a second or more to say false; for a readiness probe use c.FastFail().Healthy(ctx), which answers within the fast-fail budget.

func (*Client) Redis

func (c *Client) Redis() UniversalClient

Redis returns the underlying client as the UniversalClient alias so callers can use the full command API (Get, Set, pipelines, pub/sub, scripts, and so on) while naming only this package's exported types. The returned value is owned by this Client; do not Close it directly -- use Client.Close.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	redis "github.com/Bugs5382/go-redis"
)

func main() {
	ctx := context.Background()

	client, err := redis.Connect(ctx, redis.WithAddr("localhost:6379"))
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	var cmd redis.Cmdable = client.Redis()
	_, err = cmd.Get(ctx, "session:missing").Result()
	if errors.Is(err, redis.Nil) {
		fmt.Println("cache miss")
	}
}

type Cmdable added in v1.1.0

type Cmdable = goredis.Cmdable

Cmdable is the full go-redis command surface (Get, Set, pipelines, pub/sub, scripts, and so on), re-exported as a type alias so a caller can name it -- for example to store the result of Client.Redis in a typed field or accept it as a function parameter -- without importing github.com/redis/go-redis/v9 directly.

type Cmder added in v1.5.0

type Cmder = goredis.Cmder

Cmder is any queued or executed command, as Pipelined and TxPipelined return them. Re-exported as a type alias.

type Logger

type Logger interface {
	// Printf logs a formatted message. ctx carries the call's context, which
	// may hold trace information.
	Printf(ctx context.Context, format string, args ...any)
}

Logger receives connection-level diagnostic messages. Implementations must be safe for concurrent use. The default is a no-op, so the core carries no logging dependency.

type Observer

type Observer interface {
	// ObserveCommand is called after each command completes. name is the Redis
	// command name, dur is how long it took, and err is its result (nil on
	// success; note goredis.Nil signals a missing key, not a failure).
	ObserveCommand(ctx context.Context, name string, dur time.Duration, err error)
	// ObserveDial is called after each attempt to open a new connection.
	ObserveDial(ctx context.Context, network, addr string, dur time.Duration, err error)
}

Observer receives lightweight per-command and per-dial signals, suitable for driving metrics without pulling a telemetry library into the core. Implementations must be safe for concurrent use and must not block. The default is a no-op. For full OpenTelemetry tracing and metrics, use the otel subpackage instead.

type Option

type Option func(*config)

Option configures a Client. Options are applied in order by Connect; a later Option wins over an earlier one.

func WithAddr

func WithAddr(addrs ...string) Option

WithAddr targets a standalone Redis server. The first address is used; extra addresses are ignored (use WithCluster for multiple nodes). Overrides the default localhost:6379.

func WithCluster

func WithCluster(addrs ...string) Option

WithCluster targets a Redis Cluster. addrs are seed node endpoints; the client discovers the rest of the topology and reshards transparently. Note: cluster mode ignores WithDB (Redis Cluster supports only database 0).

func WithDB

func WithDB(db int) Option

WithDB selects the logical database index. Ignored in cluster mode.

func WithDialRetry added in v1.4.0

func WithDialRetry(attempts int, backoff time.Duration) Option

WithDialRetry tunes how hard each new connection is dialed: attempts is the total number of dial tries (1 means a single try with no retry) and backoff is the pause between them. The defaults, 5 attempts and 100ms, are the go-redis values; together with WithRetry they decide how long a call takes to fail while the server is down (at least (attempts-1) x backoff per dial). Lowering them makes every call fail sooner but gives up on a server that is only restarting; for a per-call choice, use Client.FastFail instead. Non-positive values keep the defaults.

func WithFastFail added in v1.4.0

func WithFastFail(timeout time.Duration) Option

WithFastFail sets the per-operation budget of the Client.FastFail view: its dial, read, write and pool-wait timeouts (default 200ms). Pick a value above the server's normal worst-case latency; a call that needs longer fails on the view and should take the caller's fallback. A non-positive value keeps the default.

func WithLogger

func WithLogger(l Logger) Option

WithLogger installs a Logger for connection-level diagnostics (dial failures). The default is a no-op; the core imposes no logging dependency.

func WithObserver

func WithObserver(o Observer) Option

WithObserver installs an Observer for lightweight command and dial metrics. The default is a no-op; the core imposes no telemetry dependency.

func WithPassword

func WithPassword(password string) Option

WithPassword sets the AUTH password. Leave unset for an unauthenticated server.

func WithPool

func WithPool(size int) Option

WithPool sets the maximum number of socket connections in the pool. A non-positive size is ignored, keeping the resilient default.

func WithRetry

func WithRetry(attempts int, min, max time.Duration) Option

WithRetry tunes command retries: attempts is the maximum number of retries after the first try (mapped to go-redis MaxRetries), and min/max bound the exponential backoff between them. A negative attempts count is ignored; pass 0 to disable retries. Non-positive backoff bounds are left at their defaults.

func WithSentinel

func WithSentinel(masterName string, addrs ...string) Option

WithSentinel targets a Redis Sentinel deployment: masterName is the monitored master's name (commonly "mymaster") and addrs are the sentinel endpoints. This is the high-availability path -- go-redis discovers the current master through the sentinels and re-resolves it after a failover.

func WithTLS

func WithTLS(cfg *tls.Config) Option

WithTLS enables TLS using the given configuration. Pass a non-nil *tls.Config (an empty &tls.Config{} uses the system roots and the server's hostname). A nil config leaves TLS disabled.

func WithTimeouts

func WithTimeouts(dial, read, write time.Duration) Option

WithTimeouts overrides the dial, read, and write timeouts. A non-positive duration for any parameter leaves that timeout at its default.

func WithUsername added in v1.5.0

func WithUsername(username string) Option

WithUsername sets the ACL user (Redis 6+, Valkey) that authenticates with the WithPassword password, so a service can run under its own least-privilege account instead of the server's default user. It applies to standalone, cluster and sentinel connections (the master and replicas the sentinels point to; the sentinels themselves are not authenticated). Leave it unset, or pass "", to AUTH with the password alone as the default user.

Example

Connect as a named ACL user instead of the server's default user.

package main

import (
	"context"
	"log"

	redis "github.com/Bugs5382/go-redis"
)

func main() {
	ctx := context.Background()

	client, err := redis.Connect(ctx,
		redis.WithAddr("cache.example.org:6379"),
		redis.WithUsername("orders-svc"),
		redis.WithPassword("s3cret"),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()
}

type Pipeliner added in v1.5.0

type Pipeliner = goredis.Pipeliner

Pipeliner is the queue a Pipelined or TxPipelined callback adds commands to. Re-exported as a type alias so the callback can be written without the upstream import.

type StringCmd added in v1.5.0

type StringCmd = goredis.StringCmd

StringCmd is the result of a command that replies with one string (GET and friends), for holding a pipelined result until the pipeline runs. Re-exported as a type alias.

type Tx added in v1.5.0

type Tx = goredis.Tx

Tx is the connection UniversalClient.Watch hands its callback: commands on it read the watched keys, and its TxPipelined queues the MULTI/EXEC writes. Re-exported as a type alias so the callback can be written without the upstream import.

Example

An optimistic WATCH transaction that appends to a stream, written with this package's aliases only.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	redis "github.com/Bugs5382/go-redis"
)

func main() {
	ctx := context.Background()

	client, err := redis.Connect(ctx, redis.WithAddr("localhost:6379"))
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	err = client.Redis().Watch(ctx, func(tx *redis.Tx) error {
		n, err := tx.Get(ctx, "audit:head").Result()
		if err != nil && !errors.Is(err, redis.Nil) {
			return err
		}
		_, err = tx.TxPipelined(ctx, func(p redis.Pipeliner) error {
			p.XAdd(ctx, &redis.XAddArgs{Stream: "audit", ID: "*", Values: map[string]any{"prev": n}})
			p.Set(ctx, "audit:head", "next", 0)
			return nil
		})
		return err
	}, "audit:head")
	if errors.Is(err, redis.TxFailedErr) {
		fmt.Println("another writer got there first: retry")
	}
}

type UniversalClient added in v1.1.0

type UniversalClient = goredis.UniversalClient

UniversalClient is the client-mode-agnostic go-redis interface (standalone, sentinel-failover, or cluster) that Client wraps, re-exported as a type alias for the same reason as Cmdable. It embeds Cmdable and additionally exposes hooks, transactions, pub/sub, and pool stats.

type WaitOption added in v1.3.0

type WaitOption func(*waitConfig)

WaitOption configures WaitFor.

func WithRetryHook added in v1.3.0

func WithRetryHook(hook func(attempt int, err error, delay time.Duration)) WaitOption

WithRetryHook calls hook after each transient failure, before the wait: attempt is 1-based, err is the failure and delay the wait that follows. Use it to log one line per attempt.

func WithWaitBackoff added in v1.3.0

func WithWaitBackoff(base, max time.Duration) WaitOption

WithWaitBackoff sets the delay before the first retry (base) and its cap (max); each wait doubles up to max, with jitter. Non-positive values keep the defaults (500ms and 15s).

type XAddArgs added in v1.5.0

type XAddArgs = goredis.XAddArgs

XAddArgs are the arguments to XADD (the stream, the entry ID or "*", the field values and the optional trimming). Re-exported as a type alias.

type XMessage added in v1.5.0

type XMessage = goredis.XMessage

XMessage is one stream entry, its ID and field values, as XRANGE and XREAD return them. Re-exported as a type alias.

type XMessageSliceCmd added in v1.5.0

type XMessageSliceCmd = goredis.XMessageSliceCmd

XMessageSliceCmd is the result of XRANGE and XREVRANGE, for holding a pipelined result until the pipeline runs. Re-exported as a type alias.

Directories

Path Synopsis
Package cache adds typed get-or-set caching on top of a go-redis Client, with pluggable codecs, key namespacing, TTL jitter, negative caching and stampede protection.
Package cache adds typed get-or-set caching on top of a go-redis Client, with pluggable codecs, key namespacing, TTL jitter, negative caching and stampede protection.
Package idempotency runs a function at most once per key within a TTL and replays its result, built on a go-redis Client and the script package.
Package idempotency runs a function at most once per key within a TTL and replays its result, built on a go-redis Client and the script package.
Package lock is a Redis lock with safe release and optional auto-extend, built on a go-redis Client and the script package.
Package lock is a Redis lock with safe release and optional auto-extend, built on a go-redis Client and the script package.
Package otel adds optional OpenTelemetry tracing and metrics to a go-redis Client in one call.
Package otel adds optional OpenTelemetry tracing and metrics to a go-redis Client in one call.
Package pubsub is a Redis pub/sub subscriber that re-subscribes to its channels and patterns after a reconnect or failover, with its health visible to readiness probes.
Package pubsub is a Redis pub/sub subscriber that re-subscribes to its channels and patterns after a reconnect or failover, with its health visible to readiness probes.
Package ratelimit is a distributed rate limiter backed by atomic Lua scripts, built on a go-redis Client and the script package.
Package ratelimit is a distributed rate limiter backed by atomic Lua scripts, built on a go-redis Client and the script package.
Package script runs Lua scripts on a go-redis Client with EVALSHA, falling back to EVAL when the server answers NOSCRIPT.
Package script runs Lua scripts on a go-redis Client with EVALSHA, falling back to EVAL when the server answers NOSCRIPT.
Package streams is a resilient Redis Streams consumer-group consumer, with a matching Publish, built on a go-redis Client.
Package streams is a resilient Redis Streams consumer-group consumer, with a matching Publish, built on a go-redis Client.

Jump to

Keyboard shortcuts

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