diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d4b245b2a..60bc7c199 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,5 +1,27 @@ # Release Notes +# 9.17.1 (2025-11-25) + +## 🐛 Bug Fixes + +- add wait to keyless commands list ([#3615](https://github.com/redis/go-redis/pull/3615)) by [@marcoferrer](https://github.com/marcoferrer) +- fix(time): remove cached time optimization ([#3611](https://github.com/redis/go-redis/pull/3611)) by [@ndyakov](https://github.com/ndyakov) + +## 🧰 Maintenance + +- chore(deps): bump golangci/golangci-lint-action from 9.0.0 to 9.1.0 ([#3609](https://github.com/redis/go-redis/pull/3609)) +- chore(deps): bump actions/checkout from 5 to 6 ([#3610](https://github.com/redis/go-redis/pull/3610)) +- chore(script): fix help call in tag.sh ([#3606](https://github.com/redis/go-redis/pull/3606)) by [@ndyakov](https://github.com/ndyakov) + +## Contributors +We'd like to thank all the contributors who worked on this release! + +[@marcoferrer](https://github.com/marcoferrer) and [@ndyakov](https://github.com/ndyakov) + +--- + +**Full Changelog**: https://github.com/redis/go-redis/compare/v9.17.0...v9.17.1 + # 9.17.0 (2025-11-19) ## 🚀 Highlights diff --git a/example/cluster-mget/README.md b/example/cluster-mget/README.md new file mode 100644 index 000000000..8a2f952bd --- /dev/null +++ b/example/cluster-mget/README.md @@ -0,0 +1,144 @@ +# Redis Cluster MGET Example + +This example demonstrates how to use the Redis Cluster client with the `MGET` command to retrieve multiple keys efficiently. + +## Overview + +The example shows: +- Creating a Redis Cluster client +- Setting 10 keys with individual `SET` commands +- Retrieving all 10 keys in a single operation using `MGET` +- Validating that the retrieved values match the expected values +- Cleaning up by deleting the test keys + +## Prerequisites + +You need a running Redis Cluster. The example expects cluster nodes at: +- `localhost:7000` +- `localhost:7001` +- `localhost:7002` + +### Setting up a Redis Cluster (using Docker) + +If you don't have a Redis Cluster running, you can use the docker-compose setup from the repository root: + +```bash +# From the go-redis repository root +docker compose --profile cluster up -d +``` + +This will start a Redis Cluster with nodes on ports 16600-16605. + +If using the docker-compose cluster, update the `Addrs` in `main.go` to: +```go +Addrs: []string{ + "localhost:16600", + "localhost:16601", + "localhost:16602", +}, +``` + +## Running the Example + +```bash +go run main.go +``` + +## Expected Output + +``` +✓ Connected to Redis cluster + +=== Setting 10 keys === +✓ SET key0 = value0 +✓ SET key1 = value1 +✓ SET key2 = value2 +✓ SET key3 = value3 +✓ SET key4 = value4 +✓ SET key5 = value5 +✓ SET key6 = value6 +✓ SET key7 = value7 +✓ SET key8 = value8 +✓ SET key9 = value9 + +=== Retrieving keys with MGET === + +=== Validating MGET results === +✓ key0: value0 +✓ key1: value1 +✓ key2: value2 +✓ key3: value3 +✓ key4: value4 +✓ key5: value5 +✓ key6: value6 +✓ key7: value7 +✓ key8: value8 +✓ key9: value9 + +=== Summary === +✓ All values retrieved successfully and match expected values! + +=== Cleaning up === +✓ Cleanup complete +``` + +## Key Concepts + +### MGET Command + +`MGET` (Multiple GET) is a Redis command that retrieves the values of multiple keys in a single operation. This is more efficient than executing multiple individual `GET` commands. + +**Syntax:** +```go +result, err := rdb.MGet(ctx, key1, key2, key3, ...).Result() +``` + +**Returns:** +- A slice of `interface{}` values +- Each value corresponds to a key in the same order +- `nil` is returned for keys that don't exist + +### Cluster Client + +The `ClusterClient` automatically handles: +- Distributing keys across cluster nodes based on hash slots +- Following cluster redirects +- Maintaining connections to all cluster nodes +- Retrying operations on cluster topology changes + +For `MGET` operations in a cluster, the client may need to split the request across multiple nodes if the keys map to different hash slots. + +## Code Highlights + +```go +// Create cluster client +rdb := redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: []string{ + "localhost:7000", + "localhost:7001", + "localhost:7002", + }, +}) + +// Set individual keys +for i := 0; i < 10; i++ { + err := rdb.Set(ctx, fmt.Sprintf("key%d", i), fmt.Sprintf("value%d", i), 0).Err() + // handle error +} + +// Retrieve all keys with MGET +result, err := rdb.MGet(ctx, keys...).Result() + +// Validate results +for i, val := range result { + actualValue, ok := val.(string) + // validate actualValue matches expected +} +``` + +## Learn More + +- [Redis MGET Documentation](https://redis.io/commands/mget/) +- [Redis Cluster Specification](https://redis.io/topics/cluster-spec) +- [go-redis Documentation](https://redis.uptrace.dev/) + diff --git a/example/cluster-mget/go.mod b/example/cluster-mget/go.mod new file mode 100644 index 000000000..77b47b30d --- /dev/null +++ b/example/cluster-mget/go.mod @@ -0,0 +1,12 @@ +module github.com/redis/go-redis/example/cluster-mget + +go 1.18 + +replace github.com/redis/go-redis/v9 => ../.. + +require github.com/redis/go-redis/v9 v9.16.0 + +require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect +) diff --git a/example/cluster-mget/go.sum b/example/cluster-mget/go.sum new file mode 100644 index 000000000..d64ea0303 --- /dev/null +++ b/example/cluster-mget/go.sum @@ -0,0 +1,6 @@ +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= diff --git a/example/cluster-mget/main.go b/example/cluster-mget/main.go new file mode 100644 index 000000000..19565fc9b --- /dev/null +++ b/example/cluster-mget/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "context" + "fmt" + + "github.com/redis/go-redis/v9" +) + +func main() { + ctx := context.Background() + + // Create a cluster client + rdb := redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: []string{ + "localhost:16600", + "localhost:16601", + "localhost:16602", + "localhost:16603", + "localhost:16604", + "localhost:16605", + }, + }) + defer rdb.Close() + + // Test connection + if err := rdb.Ping(ctx).Err(); err != nil { + panic(fmt.Sprintf("Failed to connect to Redis cluster: %v", err)) + } + + fmt.Println("✓ Connected to Redis cluster") + + // Define 10 keys and values + keys := make([]string, 10) + values := make([]string, 10) + for i := 0; i < 10; i++ { + keys[i] = fmt.Sprintf("key%d", i) + values[i] = fmt.Sprintf("value%d", i) + } + + // Set all 10 keys + fmt.Println("\n=== Setting 10 keys ===") + for i := 0; i < 10; i++ { + err := rdb.Set(ctx, keys[i], values[i], 0).Err() + if err != nil { + panic(fmt.Sprintf("Failed to set %s: %v", keys[i], err)) + } + fmt.Printf("✓ SET %s = %s\n", keys[i], values[i]) + } + + /* + // Retrieve all keys using MGET + fmt.Println("\n=== Retrieving keys with MGET ===") + result, err := rdb.MGet(ctx, keys...).Result() + if err != nil { + panic(fmt.Sprintf("Failed to execute MGET: %v", err)) + } + */ + + /* + // Validate the results + fmt.Println("\n=== Validating MGET results ===") + allValid := true + for i, val := range result { + expectedValue := values[i] + actualValue, ok := val.(string) + + if !ok { + fmt.Printf("✗ %s: expected string, got %T\n", keys[i], val) + allValid = false + continue + } + + if actualValue != expectedValue { + fmt.Printf("✗ %s: expected '%s', got '%s'\n", keys[i], expectedValue, actualValue) + allValid = false + } else { + fmt.Printf("✓ %s: %s\n", keys[i], actualValue) + } + } + + // Print summary + fmt.Println("\n=== Summary ===") + if allValid { + fmt.Println("✓ All values retrieved successfully and match expected values!") + } else { + fmt.Println("✗ Some values did not match expected values") + } + */ + + // Clean up - delete the keys + fmt.Println("\n=== Cleaning up ===") + for _, key := range keys { + if err := rdb.Del(ctx, key).Err(); err != nil { + fmt.Printf("Warning: Failed to delete %s: %v\n", key, err) + } + } + fmt.Println("✓ Cleanup complete") + + err := rdb.Set(ctx, "{tag}exists", "asdf",0).Err() + if err != nil { + panic(err) + } + val, err := rdb.Get(ctx, "{tag}nilkeykey1").Result() + fmt.Printf("\nval: %+v err: %+v\n", val, err) + valm, err := rdb.MGet(ctx, "{tag}nilkeykey1", "{tag}exists").Result() + fmt.Printf("\nval: %+v err: %+v\n", valm, err) +} diff --git a/example/del-keys-without-ttl/go.mod b/example/del-keys-without-ttl/go.mod index 4314a54bb..a79e6676b 100644 --- a/example/del-keys-without-ttl/go.mod +++ b/example/del-keys-without-ttl/go.mod @@ -5,7 +5,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. require ( - github.com/redis/go-redis/v9 v9.17.0 + github.com/redis/go-redis/v9 v9.17.1 go.uber.org/zap v1.24.0 ) diff --git a/example/digest-optimistic-locking/go.mod b/example/digest-optimistic-locking/go.mod index b34b90823..dcda3ab87 100644 --- a/example/digest-optimistic-locking/go.mod +++ b/example/digest-optimistic-locking/go.mod @@ -5,7 +5,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. require ( - github.com/redis/go-redis/v9 v9.17.0 + github.com/redis/go-redis/v9 v9.17.1 github.com/zeebo/xxh3 v1.0.2 ) diff --git a/example/hll/go.mod b/example/hll/go.mod index d17d180bb..a1b37590e 100644 --- a/example/hll/go.mod +++ b/example/hll/go.mod @@ -4,7 +4,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. -require github.com/redis/go-redis/v9 v9.17.0 +require github.com/redis/go-redis/v9 v9.17.1 require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/example/hset-struct/go.mod b/example/hset-struct/go.mod index 23fe0b4dd..9f0db8bd3 100644 --- a/example/hset-struct/go.mod +++ b/example/hset-struct/go.mod @@ -6,7 +6,7 @@ replace github.com/redis/go-redis/v9 => ../.. require ( github.com/davecgh/go-spew v1.1.1 - github.com/redis/go-redis/v9 v9.17.0 + github.com/redis/go-redis/v9 v9.17.1 ) require ( diff --git a/example/lua-scripting/go.mod b/example/lua-scripting/go.mod index 17097a6f9..0a8fa1088 100644 --- a/example/lua-scripting/go.mod +++ b/example/lua-scripting/go.mod @@ -4,7 +4,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. -require github.com/redis/go-redis/v9 v9.17.0 +require github.com/redis/go-redis/v9 v9.17.1 require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/example/otel/go.mod b/example/otel/go.mod index 718fb0903..d32cd9f9d 100644 --- a/example/otel/go.mod +++ b/example/otel/go.mod @@ -11,8 +11,8 @@ replace github.com/redis/go-redis/extra/redisotel/v9 => ../../extra/redisotel replace github.com/redis/go-redis/extra/rediscmd/v9 => ../../extra/rediscmd require ( - github.com/redis/go-redis/extra/redisotel/v9 v9.17.0 - github.com/redis/go-redis/v9 v9.17.0 + github.com/redis/go-redis/extra/redisotel/v9 v9.17.1 + github.com/redis/go-redis/v9 v9.17.1 github.com/uptrace/uptrace-go v1.21.0 go.opentelemetry.io/otel v1.22.0 ) @@ -25,7 +25,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect - github.com/redis/go-redis/extra/rediscmd/v9 v9.17.0 // indirect + github.com/redis/go-redis/extra/rediscmd/v9 v9.17.1 // indirect go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect diff --git a/example/redis-bloom/go.mod b/example/redis-bloom/go.mod index 0888f9db0..bce8c344b 100644 --- a/example/redis-bloom/go.mod +++ b/example/redis-bloom/go.mod @@ -4,7 +4,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. -require github.com/redis/go-redis/v9 v9.17.0 +require github.com/redis/go-redis/v9 v9.17.1 require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/example/scan-struct/go.mod b/example/scan-struct/go.mod index 23fe0b4dd..9f0db8bd3 100644 --- a/example/scan-struct/go.mod +++ b/example/scan-struct/go.mod @@ -6,7 +6,7 @@ replace github.com/redis/go-redis/v9 => ../.. require ( github.com/davecgh/go-spew v1.1.1 - github.com/redis/go-redis/v9 v9.17.0 + github.com/redis/go-redis/v9 v9.17.1 ) require ( diff --git a/extra/rediscensus/go.mod b/extra/rediscensus/go.mod index 09a298f23..e13a6bb83 100644 --- a/extra/rediscensus/go.mod +++ b/extra/rediscensus/go.mod @@ -7,8 +7,8 @@ replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/extra/rediscmd/v9 => ../rediscmd require ( - github.com/redis/go-redis/extra/rediscmd/v9 v9.17.0 - github.com/redis/go-redis/v9 v9.17.0 + github.com/redis/go-redis/extra/rediscmd/v9 v9.17.1 + github.com/redis/go-redis/v9 v9.17.1 go.opencensus.io v0.24.0 ) diff --git a/extra/rediscmd/go.mod b/extra/rediscmd/go.mod index 9894c9b45..9010c505e 100644 --- a/extra/rediscmd/go.mod +++ b/extra/rediscmd/go.mod @@ -7,7 +7,7 @@ replace github.com/redis/go-redis/v9 => ../.. require ( github.com/bsm/ginkgo/v2 v2.12.0 github.com/bsm/gomega v1.27.10 - github.com/redis/go-redis/v9 v9.17.0 + github.com/redis/go-redis/v9 v9.17.1 ) require ( diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod index 245cf4c60..4e061fd86 100644 --- a/extra/redisotel/go.mod +++ b/extra/redisotel/go.mod @@ -7,8 +7,8 @@ replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/extra/rediscmd/v9 => ../rediscmd require ( - github.com/redis/go-redis/extra/rediscmd/v9 v9.17.0 - github.com/redis/go-redis/v9 v9.17.0 + github.com/redis/go-redis/extra/rediscmd/v9 v9.17.1 + github.com/redis/go-redis/v9 v9.17.1 go.opentelemetry.io/otel v1.22.0 go.opentelemetry.io/otel/metric v1.22.0 go.opentelemetry.io/otel/sdk v1.22.0 diff --git a/extra/redisprometheus/go.mod b/extra/redisprometheus/go.mod index ec4c00382..ec7fce994 100644 --- a/extra/redisprometheus/go.mod +++ b/extra/redisprometheus/go.mod @@ -6,7 +6,7 @@ replace github.com/redis/go-redis/v9 => ../.. require ( github.com/prometheus/client_golang v1.14.0 - github.com/redis/go-redis/v9 v9.17.0 + github.com/redis/go-redis/v9 v9.17.1 ) require ( diff --git a/version.go b/version.go index a8b92ff53..6718e8659 100644 --- a/version.go +++ b/version.go @@ -2,5 +2,5 @@ package redis // Version is the current release version. func Version() string { - return "9.17.0" + return "9.17.1" }