go-redis Documentation

Go go & golang repository by @redis. AI agent rules & system prompts with 22k stars.

22,221 stars Go Go 1 file ยท ~11,197 tokens #go#golang#redis#redis-client#redis-cluster
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy go-redis on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name go-redis -p 8123:8123 --restart always redis
Configured Services & Ports
โš™๏ธ clickhouse
image: clickhouse/clickhouse-server:25.3.5
๐Ÿ”Œ Ports: 8123:8123, 9000:9000
โš™๏ธ postgres
image: postgres:17-alpine
๐Ÿ”Œ Ports: 5432:5432
โš™๏ธ uptrace
image: uptrace/uptrace:2.0.0
๐Ÿ”Œ Ports: 14317:4317, 14318:80
โš™๏ธ otelcol
image: otel/opentelemetry-collector-contrib:0.123.0
๐Ÿ”Œ Ports: 4317:4317, 4318:4318
โš™๏ธ vector
image: timberio/vector:0.28.X-alpine
โš™๏ธ mailpit
image: axllent/mailpit
๐Ÿ”Œ Ports: 1025:1025, 8025:8025
โš™๏ธ redis-server
image: redis
๐Ÿ”Œ Ports: 6379:6379
โš™๏ธ redis-cli
image: redis
docker-compose.yml
yaml (docker-compose.yml)
services:
  clickhouse:
    image: clickhouse/clickhouse-server:25.3.5
    restart: on-failure
    environment:
      CLICKHOUSE_USER: uptrace
      CLICKHOUSE_PASSWORD: uptrace
      CLICKHOUSE_DB: uptrace
    healthcheck:
      test: ['CMD', 'wget', '--spider', '-q', 'localhost:8123/ping']
      interval: 1s
      timeout: 1s
      retries: 30
    volumes:
      - ch_data:/var/lib/clickhouse
    ports:
      - '8123:8123'
      - '9000:9000'

  postgres:
    image: postgres:17-alpine
    restart: on-failure
    environment:
      PGDATA: /var/lib/postgresql/data/pgdata
      POSTGRES_USER: uptrace
      POSTGRES_PASSWORD: uptrace
      POSTGRES_DB: uptrace
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready', '-U', 'uptrace', '-d', 'uptrace']
      interval: 1s
      timeout: 1s
      retries: 30
    volumes:
      - 'pg_data:/var/lib/postgresql/data/pgdata'
    ports:
      - '5432:5432'

  uptrace:
    image: 'uptrace/uptrace:2.0.0'
    #image: 'uptrace/uptrace-dev:latest'
    restart: on-failure
    volumes:
      - ./uptrace.yml:/etc/uptrace/config.yml
    #environment:
    #  - DEBUG=2
    ports:
      - '14317:4317'
      - '14318:80'
    depends_on:
      clickhouse:
        condition: service_healthy

  otelcol:
    image: otel/opentelemetry-collector-contrib:0.123.0
    restart: on-failure
    volumes:
      - ./config/otel-collector.yaml:/etc/otelcol-contrib/config.yaml
    ports:
      - '4317:4317'
      - '4318:4318'

  vector:
    image: timberio/vector:0.28.X-alpine
    volumes:
      - ./config/vector.toml:/etc/vector/vector.toml:ro

  mailpit:
    image: axllent/mailpit
    restart: always
    ports:
      - 1025:1025
      - 8025:8025
    environment:
      MP_MAX_MESSAGES: 5000
      MP_DATA_FILE: /data/mailpit.db
      MP_SMTP_AUTH_ACCEPT_ANY: 1
      MP_SMTP_AUTH_ALLOW_INSECURE: 1
    volumes:
      - mailpit_data:/data

  redis-server:
    image: redis
    ports:
      - '6379:6379'
  redis-cli:
    image: redis

volumes:
  ch_data:
  pg_data:
  mailpit_data:

1. Project Overview & Quickstart (redis/go-redis)

File: README.md

Redis client for Go









go-redis is the official Redis client library for the Go programming language. It offers a straightforward interface for interacting with Redis servers.

Supported versions

In go-redis we are aiming to support the last three releases of Redis. Currently, this means we do support:

Although the go.mod states it requires at minimum go 1.24, our CI is configured to run the tests against all supported
versions of Redis and multiple versions of Go (1.24, oldstable, and stable). We observe that some modules related test may not pass with
Redis Stack 7.2 and some commands are changed with Redis CE 8.0.
Although it is not officially supported, go-redis/v9 should be able to work with any Redis 7.0+.
Please do refer to the documentation and the tests if you experience any issues.

Array data type (Redis 8.8+)

Starting with Redis 8.8, go-redis exposes the new array data type via the AR* command family
(ARSET, ARGET, ARGETRANGE, ARMSET, ARMGET, ARINSERT, ARDEL, ARDELRANGE,
ARLEN, ARCOUNT, ARNEXT, ARSEEK, ARSCAN, ARGREP, ARRING, ARLASTITEMS,
ARINFO/ARINFOFULL, and the AROP* reducers). See array_commands.go for the full
surface. The API is experimental and may change in a future release.

How do I Redis?

Learn for free at Redis University

Build faster with the Redis Launchpad

Try the Redis Cloud

Dive in developer tutorials

Join the Redis community

Work at Redis

Resources

old documentation

Ecosystem

Features

Installation

go-redis supports 2 last Go versions and requires a Go version with
modules support. So make sure to initialize a Go
module:

shell
go mod init github.com/my/repo

Then install go-redis/v9:

shell
go get github.com/redis/go-redis/v9

Quickstart

go
import (
    "context"
    "fmt"

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

var ctx = context.Background()

func ExampleClient() {
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })
    defer rdb.Close()

    err := rdb.Set(ctx, "key", "value", 0).Err()
    if err != nil {
        panic(err)
    }

    val, err := rdb.Get(ctx, "key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println("key", val)

    val2, err := rdb.Get(ctx, "key2").Result()
    if err == redis.Nil {
        fmt.Println("key2 does not exist")
    } else if err != nil {
        panic(err)
    } else {
        fmt.Println("key2", val2)
    }
    // Output: key value
    // key2 does not exist
}

Dial retries and backoff

Connection establishment can be retried by the connection pool when dialing fails.

  • DialerRetries: maximum number of dial attempts (default: 5).
  • DialerRetryTimeout: default delay between attempts when no custom backoff is provided (default: 100ms).
  • DialerRetryBackoff: optional function hook to control the delay between attempts.

Example:

go
rdb := redis.NewClient(&redis.Options{
	Addr: "localhost:6379",

	DialerRetries:      5,
	DialerRetryTimeout: 100 * time.Millisecond, // used when DialerRetryBackoff is nil

	// Optional: exponential backoff with jitter and a cap.
	DialerRetryBackoff: redis.DialRetryBackoffExponential(100*time.Millisecond, 2*time.Second),
})
defer rdb.Close()

Authentication

The Redis client supports multiple ways to provide authentication credentials, with a clear priority order. Here are the available options:

1. Streaming Credentials Provider (Highest Priority) - Experimental feature

The streaming credentials provider allows for dynamic credential updates during the connection lifetime. This is particularly useful for managed identity services and token-based authentication.

go
type StreamingCredentialsProvider interface {
    Subscribe(listener CredentialsListener) (Credentials, UnsubscribeFunc, error)
}

type CredentialsListener interface {
    OnNext(credentials Credentials)  // Called when credentials are updated
    OnError(err error)              // Called when an error occurs
}

type Credentials interface {
    BasicAuth() (username string, password string)
    RawCredentials() string
}

Example usage:

go
rdb := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
    StreamingCredentialsProvider: &MyCredentialsProvider{},
})

Note: The streaming credentials provider can be used with go-redis-entraid to enable Entra ID (formerly Azure AD) authentication. This allows for seamless integration with Azure's managed identity services and token-based authentication.

Example with Entra ID:

go
import (
    "github.com/redis/go-redis/v9"
    "github.com/redis/go-redis-entraid"
)

// Create an Entra ID credentials provider
provider := entraid.NewDefaultAzureIdentityProvider()

// Configure Redis client with Entra ID authentication
rdb := redis.NewClient(&redis.Options{
    Addr: "your-redis-server.redis.cache.windows.net:6380",
    StreamingCredentialsProvider: provider,
    TLSConfig: &tls.Config{
        MinVersion: tls.VersionTLS12,
    },
})

2. Context-based Credentials Provider

The context-based provider allows credentials to be determined at the time of each operation, using the context.

go
rdb := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
    CredentialsProviderContext: func(ctx context.Context) (string, string, error) {
        // Return username, password, and any error
        return "user", "pass", nil
    },
})

3. Regular Credentials Provider

A simple function-based provider that returns static credentials.

go
rdb := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
    CredentialsProvider: func() (string, string) {
        // Return username and password
        return "user", "pass"
    },
})

4. Username/Password Fields (Lowest Priority)

The most basic way to provide credentials is through the Username and Password fields in the options.

go
rdb := redis.NewClient(&redis.Options{
    Addr:     "localhost:6379",
    Username: "user",
    Password: "pass",
})

Priority Order

The client will use credentials in the following priority order:

  1. Streaming Credentials Provider (if set)
  2. Context-based Credentials Provider (if set)
  3. Regular Credentials Provider (if set)
  4. Username/Password fields (if set)

If none of these are set, the client will attempt to connect without authentication.

Protocol Version

The client supports both RESP2 and RESP3 protocols. You can specify the protocol version in the options:

go
rdb := redis.NewClient(&redis.Options{
    Addr:     "localhost:6379",
    Password: "", // no password set
    DB:       0,  // use default DB
    Protocol: 3,  // specify 2 for RESP 2 or 3 for RESP 3
})

Client-side caching

go-redis supports server-assisted client-side caching for standalone clients.
Eligible read replies are stored in the application's memory, so repeated reads
can avoid a Redis round trip. Redis tracks which keys each connection has read
and sends RESP3 invalidation notifications when those keys change. go-redis
uses those notifications to evict affected entries automatically.

Experimental: The client-side caching API may change in a minor release.

Enable the built-in bounded cache with ClientSideCacheConfig:

go
rdb := redis.NewClient(&redis.Options{
    Addr:     "localhost:6379",
    Protocol: 3,
    DB:       0,
    ClientSideCacheConfig: &redis.ClientSideCacheConfig{
        MaxEntries: 10_000,
    },
})
defer rdb.Close()

Client-side caching currently requires RESP3, a standalone client, and database
0. Fixed Username and Password values are supported. It is disabled when a
dynamic credential provider is configured, because cached data must never be
reused after the client's ACL identity changes. Only deterministic read
commands supported by the cache are stored; writes and streaming responses
bypass it.

While client-side caching is enabled, go-redis rejects SELECT, AUTH,
HELLO with arguments, RESET, CLIENT TRACKING, and raw SUBSCRIBE,
PSUBSCRIBE, or SSUBSCRIBE commands because they would change connection
state that the cache relies on. A guarded command also fails its whole
pipeline. The typed Subscribe, PSubscribe, and SSubscribe APIs remain
supported because they use dedicated connections.

Invalidations are processed asynchronously. DrainInterval controls how often
idle connections are checked for them, while MaxStaleness can provide an
optional upper bound on an entry's lifetime. See the
client-side caching example for a working
demonstration.

Connecting via a redis url

go-redis also supports connecting via the
redis uri specification.
The example below demonstrates how the connection can easily be configured using a string, adhering
to this specification.

go
import (
    "github.com/redis/go-redis/v9"
)

func ExampleClient() *redis.Client {
    url := "redis://user:password@localhost:6379/0?protocol=3"
    opts, err := redis.ParseURL(url)
    if err != nil {
        panic(err)
    }

    return redis.NewClient(opts)
}

Instrument with OpenTelemetry

go
import (
    "github.com/redis/go-redis/v9"
    "github.com/redis/go-redis/extra/redisotel/v9"
    "errors"
)

func main() {
    ...
    rdb := redis.NewClient(&redis.Options{...})

    if err := errors.Join(redisotel.InstrumentTracing(rdb), redisotel.InstrumentMetrics(rdb)); err != nil {
        log.Fatal(err)
    }

Buffer Size Configuration

go-redis uses 32KiB read and write buffers by default for optimal performance. For high-throughput applications or large pipelines, you can customize buffer sizes:

go
rdb := redis.NewClient(&redis.Options{
    Addr:            "localhost:6379",
    ReadBufferSize:  1024 * 1024, // 1MiB read buffer
    WriteBufferSize: 1024 * 1024, // 1MiB write buffer
})

Automatic pipelining

Experimental โ€” the API may still change. Reach for autopipelining in
high-throughput / high-load / scale scenarios; at low concurrency a plain
client is simpler and just as fast. A runnable usage tour and throughput
comparison live in example/autopipeline.

EXPERIMENTAL: the autopipelining API is subject to change in a future
release as we gather feedback โ€” pin your go-redis version if you adopt it.

When many goroutines issue commands concurrently, autopipelining batches them
into Redis pipelines automatically โ€” without you writing any pipeline code. It
comes in two faces:

  • AutoPipeline() โ€” blocking, drop-in. Each command call blocks until it
    executes and returns its own value/error, exactly like a normal client, so
    existing code keeps working unchanged. Under concurrency the engine coalesces
    commands from all goroutines into deep, back-to-back pipelines (a single
    ordered batch stream by default), reaching several times a plain client's
    executed commands per second in the same environment โ€” roughly an order of
    magnitude with a parallel-batch config (MaxConcurrentBatches > 1 with
    Unordered). Per-goroutine ordering is preserved.
  • AsyncAutoPipeline() โ€” deferred, highest throughput. Command calls return
    immediately; you submit a window of commands and read their results afterward,
    which keeps each pipeline deep โ€” tens of times a plain client's throughput.
    Ordered by default. Absolute numbers depend heavily on the machine, network
    path and server; see autopipeline_bench_README.md for the benchmark
    methodology and multipliers.
go
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
defer rdb.Close()
ctx := context.Background()

// Blocking face: drop-in for a normal client, batched under the hood.
ap, err := rdb.AutoPipeline()
if err != nil { // invalid AutoPipelineOptions, or the client is closed
    log.Fatal(err)
}
defer ap.Close()

var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
    wg.Add(1)
    go func(i int) {
        defer wg.Done()
        key := fmt.Sprintf("key:%d", i)
        if err := ap.Set(ctx, key, i, 0).Err(); err != nil { // blocks until executed
            log.Printf("set %s: %v", key, err)
        }
    }(i)
}
wg.Wait()

For maximum throughput, submit a window on the async face and read later:

go
ctx := context.Background()
ap, err := rdb.AsyncAutoPipeline() // ordered by default
if err != nil {
    log.Fatal(err)
}
defer ap.Close()

cmds := make([]*redis.StatusCmd, 0, 200)
for i := 0; i < 200; i++ {
    cmds = append(cmds, ap.Set(ctx, fmt.Sprintf("key:%d", i), i, 0)) // returns immediately
}
for _, cmd := range cmds {
    if err := cmd.Err(); err != nil { // blocks until executed
        log.Printf("set: %v", err)
    }
}

Each face has a no-argument form that uses Options.AutoPipelineOptions (or the
built-in default) and a WithOptions form that takes an explicit
*AutoPipelineOptions; both return (*AutoPipeliner, error) โ€” the error is
non-nil for an invalid config or a closed client (e.g.
ap, err := rdb.AsyncAutoPipelineWithOptions(&redis.AutoPipelineOptions{MaxConcurrentBatches: 8, Unordered: true}));
a handful of parallel batches saturates the link โ€” more permits only add
overlapping batches without deepening them.
They work on ClusterClient too: commands are routed to the correct shard per
key, so a single batch may span many slots; ordering across nodes is per key
(same-key commands stay in order, different nodes' sub-pipelines run
concurrently). Because batches share a few pipeline connections, autopipelining
also needs far fewer connections than a plain client at the same concurrency
(see PipelinePoolSize). Autopipelining is only a win under concurrency (or
windowed submission) โ€” a single goroutine issuing one blocking command at a
time sees little benefit, and a hand-written Pipeline() is still fastest when
you can batch by hand.

Caveats: a command's context is not honored once it is queued (batches execute
on the autopipeliner's own context) โ€” use a plain client for per-command
deadlines. Blocking commands (BLPOP, WAIT, ...) are never batched and run
directly on your context โ€” as are SHUTDOWN and MONITOR, which would
poison a shared pipeline connection โ€” and Do also bypasses batching with plain
Client.Do semantics โ€” prefer the typed methods (ap.Set, ap.Get, ...). On
a dropped connection a batch is retried whole (up to MaxRetries), so
non-idempotent commands may execute twice. Both faces return a cached,
client-shared instance: the first call's config wins and Close stops it for
all callers. Hooks may read command results (the engine hands a hook running
on the dispatch goroutine the same view a plain pipeline hook gets), but a
hook must never issue a command on the same autopipeliner and wait for it โ€”
the nested command needs the very dispatch slot the hook is holding, and the
engine only recovers by failing that flush after its 30s permit backstops.
Options.Limiter is consulted once per batch dispatch (as with a manual
pipeline), not once per command. An autopipeliner created on a
WithTimeout/WithReadTimeout clone is not stopped by the parent's Close โ€”
close it explicitly.

Advanced Configuration

go-redis supports extending the client identification phase to allow projects to send their own custom client identification.

Default Client Identification

By default, go-redis automatically sends the client library name and version during the connection process. This feature is available in redis-server as of version 7.2. As a result, the command is "fire and forget", meaning it should fail silently, in the case that the redis server does not support this feature.

Disabling Identity Verification

When connection identity verification is not required or needs to be explicitly disabled, a DisableIdentity configuration option exists.
Initially there was a typo and the option was named DisableIndentity instead of DisableIdentity. The misspelled option is marked as Deprecated and will be removed in V10 of this library.
Although both options will work at the moment, the correct option is DisableIdentity. The deprecated option will be removed in V10 of this library, so please use the correct option name to avoid any issues.

To disable verification, set the DisableIdentity option to true in the Redis client options:

go
rdb := redis.NewClient(&redis.Options{
    Addr:            "localhost:6379",
    Password:        "",
    DB:              0,
    DisableIdentity: true, // Disable set-info on connect
})

RESP3 for RediSearch Commands (`UnstableResp3` is deprecated)

As of v9.20, FT.SEARCH, FT.AGGREGATE, FT.INFO, FT.SPELLCHECK, and FT.SYNDUMP
parse RESP3 (map) responses into the same typed result objects as RESP2. No flag
is required โ€” Val() / Result() work uniformly on both protocols.

The legacy UnstableResp3 option is now a no-op and is retained on every
options struct only for backwards compatibility. It will be removed in a future
release; new code should not set it.

RawResult() / RawVal() continue to work for callers that prefer the raw RESP
payload directly:

go
res1, err := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{}).RawResult()
val1 := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{}).RawVal()

Redis-Search Default Dialect

In the Redis-Search module, the default dialect is 2. If needed, you can explicitly specify a different dialect using the appropriate configuration in your queries.

Important: Be aware that the query dialect may impact the results returned. If needed, you can revert to a different dialect version by passing the desired dialect in the arguments of the command you want to execute.
For example:

text
res2, err := rdb.FTSearchWithArgs(ctx,
		"idx:bicycle",
		"@pickup_zone:[CONTAINS $bike]",
		&redis.FTSearchOptions{
			Params: map[string]interface{}{
				"bike": "POINT(-0.1278 51.5074)",
			},
			DialectVersion: 3,
		},
	).Result()

You can find further details in the query dialect documentation.

Custom buffer sizes

Prior to v9.12, the buffer size was the default go value of 4096 bytes. Starting from v9.12,
go-redis uses 32KiB read and write buffers by default for optimal performance.
For high-throughput applications or large pipelines, you can customize buffer sizes:

go
rdb := redis.NewClient(&redis.Options{
    Addr:            "localhost:6379",
    ReadBufferSize:  1024 * 1024, // 1MiB read buffer
    WriteBufferSize: 1024 * 1024, // 1MiB write buffer
})

Important: If you experience any issues with the default buffer sizes, please try setting them to the go default of 4096 bytes.

Contributing

We welcome contributions to the go-redis library! If you have a bug fix, feature request, or improvement, please open an issue or pull request on GitHub.
We appreciate your help in making go-redis better for everyone.
If you are interested in contributing to the go-redis library, please check out our contributing guidelines for more information on how to get started.

Look and feel

Some corner cases:

go
// SET key value EX 10 NX
set, err := rdb.SetNX(ctx, "key", "value", 10*time.Second).Result()

// SET key value keepttl NX
set, err := rdb.SetNX(ctx, "key", "value", redis.KeepTTL).Result()

// SORT list LIMIT 0 2 ASC
vals, err := rdb.Sort(ctx, "list", &redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result()

// ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2
vals, err := rdb.ZRangeByScoreWithScores(ctx, "zset", &redis.ZRangeBy{
    Min: "-inf",
    Max: "+inf",
    Offset: 0,
    Count: 2,
}).Result()

// ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM
vals, err := rdb.ZInterStore(ctx, "out", &redis.ZStore{
    Keys: []string{"zset1", "zset2"},
    Weights: []int64{2, 3}
}).Result()

// EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello"
vals, err := rdb.Eval(ctx, "return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result()

// custom command
res, err := rdb.Do(ctx, "set", "key", "value").Result()

Raw commands and connection state

Do sends the command verbatim on whichever pooled connection happens to be
free. For keyspace commands that is all you need. It is the wrong tool for
any command that alters connection session state โ€” SELECT,
CLIENT SETNAME, CLIENT TRACKING, RESET, HIMPORT PREPARE/DISCARD,
and similar: the state lands on (or is wiped from) a single arbitrary
connection, later commands are served by other connections that don't share
it, and the affected connection eventually returns to the pool and serves
unrelated callers. The result is nondeterministic behavior that typed APIs
manage for you โ€” for example, the typed HImport* methods keep a
client-side registry and replay fieldsets onto every connection that needs
them, while a raw Do(ctx, "himport", "prepare", ...) bypasses that
entirely, with no replay, recovery, or discard propagation.

For session-scoped work without a typed API, hold a dedicated connection
(client.Conn()) for its whole lifetime and close it afterwards.

Typed Errors

go-redis provides typed error checking functions for common Redis errors:

go
// Cluster and replication errors
redis.IsLoadingError(err)        // Redis is loading the dataset
redis.IsReadOnlyError(err)       // Write to read-only replica
redis.IsClusterDownError(err)    // Cluster is down
redis.IsTryAgainError(err)       // Command should be retried
redis.IsMasterDownError(err)     // Master is down
redis.IsMovedError(err)          // Returns (address, true) if key moved
redis.IsAskError(err)            // Returns (address, true) if key being migrated

// Connection and resource errors
redis.IsMaxClientsError(err)     // Maximum clients reached
redis.IsAuthError(err)           // Authentication failed (NOAUTH, WRONGPASS, unauthenticated)
redis.IsPermissionError(err)     // Permission denied (NOPERM)
redis.IsOOMError(err)            // Out of memory (OOM)

// Transaction errors
redis.IsExecAbortError(err)      // Transaction aborted (EXECABORT)

Error Wrapping in Hooks

When wrapping errors in hooks, use custom error types with Unwrap() method (preferred) or fmt.Errorf with %w. Always call cmd.SetErr() to preserve error type information:

go
// Custom error type (preferred)
type AppError struct {
    Code      string
    RequestID string
    Err       error
}

func (e *AppError) Error() string {
    return fmt.Sprintf("[%s] request_id=%s: %v", e.Code, e.RequestID, e.Err)
}

func (e *AppError) Unwrap() error {
    return e.Err
}

// Hook implementation
func (h MyHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
    return func(ctx context.Context, cmd redis.Cmder) error {
        err := next(ctx, cmd)
        if err != nil {
            // Wrap with custom error type
            wrappedErr := &AppError{
                Code:      "REDIS_ERROR",
                RequestID: getRequestID(ctx),
                Err:       err,
            }
            cmd.SetErr(wrappedErr)
            return wrappedErr  // Return wrapped error to preserve it
        }
        return nil
    }
}

// Typed error detection works through wrappers
if redis.IsLoadingError(err) {
    // Retry logic
}

// Extract custom error if needed
var appErr *AppError
if errors.As(err, &appErr) {
    log.Printf("Request: %s", appErr.RequestID)
}

Alternatively, use fmt.Errorf with %w:

go
wrappedErr := fmt.Errorf("context: %w", err)
cmd.SetErr(wrappedErr)

Pipeline Hook Example

For pipeline operations, use ProcessPipelineHook:

text
/* Detailed source-code truncated for AI context efficiency. */

Run the test

Recommended to use Docker, just need to run:

shell
make test

See also

Contributors

The go-redis project was originally initiated by :star: uptrace/uptrace.
Uptrace is an open-source APM tool that supports distributed tracing, metrics, and logs. You can
use it to monitor applications and set up automatic alerts to receive notifications via email,
Slack, Telegram, and others.

See OpenTelemetry example which
demonstrates how you can use Uptrace to monitor go-redis.

Thanks to all the people who already contributed!


File: doctests/README.md

Command examples for redis.io

These examples appear on the Redis documentation site as part of the tabbed examples interface.

How to add examples

  • Create a Go test file with a meaningful name in the current folder.
  • Create a single method prefixed with Example and write your test in it.
  • Determine the id for the example you're creating and add it as the first line of the file: // EXAMPLE: set_and_get.
  • We're using the Testable Examples feature of Go to test the desired output has been written to stdout.

Special markup

See https://github.com/redis-stack/redis-stack-website#readme for more details.

How to test the examples

  • Start a Redis server locally on port 6379
  • CD into the doctests directory
  • Run go test to test all examples in the directory.
  • Run go test filename.go to test a single file

File: example/scan-struct/README.md

Example for scanning hash fields into a struct

To run this example:

shell
go run .

See
Redis: Scanning hash fields into a struct
for details.


File: example/redis-bloom/README.md

RedisBloom example for go-redis

This is an example for
Bloom, Cuckoo, Count-Min, Top-K
article.

To run it, you need to compile and install
RedisBloom module:

shell
go run .

File: example/otel/README.md

go-redis OpenTelemetry Monitoring with Uptrace

This example demonstrates how to instrument and monitor Redis operations in Go applications using
OpenTelemetry and Uptrace, providing comprehensive
observability into your Redis performance and operations.

Overview

This integration provides:

  • Distributed tracing for Redis operations
  • Performance monitoring with latency and throughput metrics
  • Error tracking and debugging capabilities
  • Visual dashboards for Redis health monitoring
  • Production-ready observability stack with Docker

Prerequisites

  • Go 1.19+
  • Docker and Docker Compose
  • Basic understanding of Redis and OpenTelemetry

Quick Start

1. Clone and Navigate

bash
git clone https://github.com/redis/go-redis.git
cd example/otel

2. Start the Monitoring Stack

Launch Redis and Uptrace services:

bash
docker compose up -d

This starts:

  • Redis server on localhost:6379
  • Uptrace APM on http://localhost:14318

3. Verify Services

Check that Uptrace is running properly:

bash
docker compose logs uptrace

Look for successful startup messages without errors.

4. Run the Example

Execute the instrumented Redis client:

bash
go run client.go

You should see output similar to:

text
trace: http://localhost:14318/traces/ee029d8782242c8ed38b16d961093b35

Click the trace URL to view detailed operation traces in Uptrace.

5. Explore the Dashboard

Open the Uptrace UI at http://localhost:14318 to explore:

  • Traces: Individual Redis operation details
  • Metrics: Performance statistics and trends
  • Logs: Application and system logs
  • Service Map: Visual representation of dependencies

Advanced Monitoring Setup

Redis Performance Metrics

For production environments, enable comprehensive Redis monitoring by installing the OpenTelemetry
Collector:

The OpenTelemetry Collector acts as a telemetry agent
that:

  • Pulls performance metrics directly from Redis
  • Collects system-level statistics
  • Forwards data to Uptrace via OTLP protocol

When configured, Uptrace automatically generates a Redis dashboard:

Key Metrics Monitored

  • Connection Statistics: Active connections, connection pool utilization
  • Command Performance: Operation latency, throughput, error rates
  • Memory Usage: Memory consumption, key distribution
  • Replication Health: Master-slave sync status and lag

Logs and Debugging

View service logs:

bash
# All services
docker compose logs

# Specific service
docker compose logs redis
docker compose logs uptrace

Additional Resources


File: example/lua-scripting/README.md

Redis Lua scripting example

This is an example for Redis Lua scripting
article. To run it:

shell
go run .

File: example/hset-struct/README.md

Example for setting struct fields as hash fields

To run this example:

shell
go run .

File: example/hll/README.md

Redis HyperLogLog example

To run this example:

shell
go run .

See Using HyperLogLog command with go-redis for
details.


File: example/del-keys-without-ttl/README.md

Delete keys without a ttl

This example demonstrates how to use SCAN and pipelines to efficiently delete keys without a TTL.

To run this example:

shell
go run .

See documentation for more details.


File: extra/redisprometheus/README.md

Prometheus Metric Collector

This package implements a prometheus.Collector
for collecting metrics about the connection pool used by the various redis clients.
Supported clients are redis.Client, redis.ClusterClient, redis.Ring and redis.UniversalClient.

Example

go
client := redis.NewClient(options)
collector := redisprometheus.NewCollector(namespace, subsystem, client)
prometheus.MustRegister(collector)

Metrics

Name Type Description
pool_hit_total Counter metric number of times a connection was found in the pool
pool_miss_total Counter metric number of times a connection was not found in the pool
pool_timeout_total Counter metric number of times a timeout occurred when getting a connection from the pool
pool_conn_total_current Gauge metric current number of connections in the pool
pool_conn_idle_current Gauge metric current number of idle connections in the pool
pool_conn_stale_total Counter metric number of times a connection was removed from the pool because it was stale

2. Official Technical Reference & Guides (redis/docs)

Redis Docs

Note: if you are an AI agent, please see AI_AGENT_DEVELOPER_GUIDE.md for more information.

PRs are merged to the main branch of this repo. Merging to main automatically builds and publishes your changes to the live docs site (under /latest) โ€” there is no separate manual publish step, so allow a little time for the build and CDN cache to refresh after your PR is merged.
Before your PR is merged, you can preview your changes on a per-branch staging URL; the docs team's bot posts the staging links as a comment on your PR.

Site template files and folders

  • /archetypes: A Markdown file needs to have some front matter. An archetype defines which front matter is used when using hugo new content. Right now, the only supported archetype is the default one. Note: We might want to add additional archetypes in the future because most of our pages contain additional meta data properties like linkTitle.
  • /content: This folder contains the markdown files. We will have the subfolders like /develop, /integrate, and /operate
  • /assets: CSS files, site-wide icons, and images.
  • /data: Data files that are accessed by Hugo and rendered with the help of short codes or partials.
  • /layouts/partials: HTML templates that are used across sites. Examples are TOCs, breadcrumbs, or headers.
  • /layouts/$type: Each page type has at least the following templates to implement single.html and list.html. The single template is used to render a discrete page. The list template is used to render a collection of related pages (e.g., all sub-pages).
  • /layouts/home.html: The home page of the site, that is, the page that is displayed when you open the root path.
  • /layouts/404.html: The default 404 page.
  • /layouts/shortcodes HTML template snippets that you want to leverage within Markdown files. Examples are tabbed code examples or notification blocks.
  • /public: This is the folder that is generated by Hugo. This content needs to be published by site maintainers.
  • /static: Any static files that need to be accessed by the site, e.g., CSS or JavaScript.
  • /package.json: Node.js dependencies. Tailwind, for example, is installed via the Node package manager (npm).
  • /config.toml: Hugo's site configuration, like the root path and menu items. Hugo can access configuration elements when rendering the site. So you can define custom configuration settings here.
  • /Makefile: We use make to wrap some Hugo commands and to add additional build steps.
  • /tailwind.config.js: This is the Tailwind CSS framwork's configuration file.
  • /postcss.config.js: Needed to make Tailwind statically accessible to the site.

Requirements

You will need the following tools to build the site locally:

  • python3: Python >= 3.8.
  • node and npm: Node.js >= 19.7.0, and the Node.js package manager >= 9.5.0.
  • hugo: Hugo site generator, v0.143.0; the extended edition.
  • make: To run the makefile.
  • git: To manage the source code of this repo.

Build script and data files

The site can be built using the command make all. Here is what's executed:

  1. Clean the current folder by deleting the generated output of a previous build.
  2. Install the necessary Node.js and Python dependencies.
  3. Build the components that were fetched from external Github repositories (e.g., client examples). This fetches source code files and generates data files.
  4. Execute the hugo command to generate the HTML pages. The build result can be found within the folder /public.

The command make serve performs the same steps, but serves the web pages on the local host for development purposes.

The Makefile contains details about the executed commands.

N.B. once you've built the site once, you can continuing working without rebuilding every time you make a change.
Use hugo serve to serve the docs locally.
The only time you really need to rebuild the site locally is when new code samples need to be pulled from the client sites.

Build pipeline

The build pipeline that is defined within .github/workflows/main.yml builds the documentation and uploads it into a GCS bucket:

  1. Install Hugo
  2. Check the branch out
  3. Perform the build as stated before by using make all
  4. Install the Google Cloud CLI
  5. Authenticate to the GCS bucket
  6. Validate the branch name
  7. Sync the branch with a GCS folder

Hugo specifics

We are using the following syntax for Hugo relrefs:

text
[Link title]({{< relref "link relative to the site's base url" >}})

Here is an example:

text
[Data structure store]({{< relref "/develop/get-started/data-store" >}})

It's strongly advised to use relref because it provides the following advantages:

  1. Links are checked at build time. Any broken references within the site are reported, stopping the build.
  2. References are prefixed with the site's base URL, which means that they work in builds with a different base URL.

The following needs to be taken into account when using relref: The reference /develop/get-started/data-store and /develop/get-started/data-store/ aren't the same. You must use the trailing slash if the referenced article is an _index.md file within a folder (e.g., .../data-store/ for .../data-store/_index.md). Otherwise, you should not use the trailing slash (e.g., .../get-started/data-store.md).

Images

The image shortcode doesn't need to be closed anymore. Here is an example;

text
{{< image filename="/images/rc/icon-database-update-status-pending.png" alt="Database update" >}}

The filename property value can be any file name path which is relative to the site's base path.

We added a new property class which allows you to override the CSS class of the image. Images have by default a block display in Tailwind. You can change this by setting the class property to inline. The following example shows two images that are in a single line:

text
{{< image filename="/images/rc/icon-database-update-status-pending.png#no-click" alt="Pending database status" class="inline" >}} &nbsp; {{< image filename="/images/rc/icon-database-status-active.png#no-click" alt="Active database status" class="inline" >}}

Templating

Variables are scoped in the context of their block. Overriding a variable within an if block doesn't change the variable value as soon as you leave that block. This is a specific limitation of the Go templating language.

The following will give you the / outside of the condition block if the path was initially set to /:

text
{{ $path := $parsed.Path }}
{{ if (eq $path "/") }}
	{{ $path = "" }}
{{ end }}

{{- $path -}}

The solution is to use a scratchpad for storing the 'global' state:

text
{{ $path := $parsed.Path }}
{{ if (eq $path "/") }}
	{{ .Scratch.Set "path" ""}}
{{ else }}
    {{ .Scratch.Set "path" $path}}
{{ end }}

{{- .Scratch.Get "path" -}}

Child pages

The redis.io template adds a list of links to child pages at the bottom of each section page. You can prevent this by adding the following front matter property:

text
hideListLinks: true

Tailwind specifics

Fonts

The fonts are located in the folder static/fonts. This folder contains typically file of the formats ttf, woff, or woff2. You can perform the following steps to add a font:

  • Copy your font files to the fonts folder static/fonts.

  • Ensure that the font is loaded by adding it to the partial layouts/partials/fonts.html. This partial defines the font using a style sheet and ensures that the font resources are pre-loaded.

    Here is an example that shows how to define the font face 'Geist Mono' with a regular font weight:

    text
    @font-face {
        font-family: 'Geist Mono';
        src: url('{{ $basePath }}/fonts/GeistMono-Regular.woff2') format('woff2');
        font-weight: 400;
        font-style: normal;
        font-display: swap;
    }
  • Make your font available via Tailwind by adding it to the tailwind.config.js file.

    text
    module.exports = {
      content: ["content/**/*.md", "layouts/**/*.html", "./tailwindcss.whitelist.txt"],
      theme: {
        extend: {
          fontFamily: {
                                    sans: [ 'Space Grotesk', ...defaultTheme.fontFamily.sans ],
                                    mono: [ 'Space Mono', 'SF Mono', ...defaultTheme.fontFamily.mono ],
                                    monogeist: ['Geist Mono', ...defaultTheme.fontFamily.mono ],
          },
          ...
  • Use a font-* CSS class within HTML or your style sheets, whereby the * needs to be substituted with one of the Tailwind fonts that you defined in the Tailwind configuration file (tailwind.config.js).

    Here is an example how to use font-monogeist in the global CSS file assets/css/index.css to change the font of code blocks:

    text
    .prose pre > code {
      @apply bg-none font-monogeist;
    }

Troubleshooting

NIL pointer error when rendering a section

You might sometimes see the following error message:

text
nil pointer evaluating page.Page.Params

This happened for us when Hugo executed the bannerText logic, which inspected all parent pages by checking if they have a banner text set. The problem was that one of the parent folders was missing an _index.md file, which meant the folder couldn't be identified as a page. The solution was to add an _index.md file to the folder that didn't have it yet.