diff --git a/.github/actions/run-tests/action.yml b/.github/actions/run-tests/action.yml index b8e576e7..0d6db09b 100644 --- a/.github/actions/run-tests/action.yml +++ b/.github/actions/run-tests/action.yml @@ -18,22 +18,20 @@ runs: - name: Setup Test environment env: REDIS_VERSION: ${{ inputs.redis-version }} - CLIENT_LIBS_TEST_IMAGE: "redislabs/client-libs-test:${{ inputs.redis-version }}" run: | set -e redis_version_np=$(echo "$REDIS_VERSION" | grep -oP '^\d+.\d+') - + # Mapping of redis version to redis testing containers declare -A redis_version_mapping=( + ["8.4.x"]="8.4-RC1-pre" ["8.2.x"]="8.2.1-pre" ["8.0.x"]="8.0.2" - ["7.4.x"]="rs-7.4.0-v5" - ["7.2.x"]="rs-7.2.0-v17" ) - + if [[ -v redis_version_mapping[$REDIS_VERSION] ]]; then echo "REDIS_VERSION=${redis_version_np}" >> $GITHUB_ENV - echo "REDIS_IMAGE=redis:${{ inputs.redis-version }}" >> $GITHUB_ENV + echo "REDIS_IMAGE=redis:${REDIS_VERSION}" >> $GITHUB_ENV echo "CLIENT_LIBS_TEST_IMAGE=redislabs/client-libs-test:${redis_version_mapping[$REDIS_VERSION]}" >> $GITHUB_ENV else echo "Version not found in the mapping." diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 075d603a..fa4ba024 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Go on: push: - branches: [master, v9, v9.7, v9.8, 'ndyakov/*', 'ofekshenawa/*', 'htemelski-redis/*', 'ce/*'] + branches: [master, v9, 'v9.*'] pull_request: branches: [master, v9, v9.7, v9.8, 'ndyakov/*', 'ofekshenawa/*', 'htemelski-redis/*', 'ce/*'] @@ -18,9 +18,9 @@ jobs: fail-fast: false matrix: redis-version: + - "8.4.x" # Redis CE 8.4 - "8.2.x" # Redis CE 8.2 - "8.0.x" # Redis CE 8.0 - - "7.4.x" # Redis stack 7.4 go-version: - "1.23.x" - "1.24.x" @@ -44,9 +44,9 @@ jobs: # Mapping of redis version to redis testing containers declare -A redis_version_mapping=( + ["8.4.x"]="8.4-RC1-pre" ["8.2.x"]="8.2.1-pre" ["8.0.x"]="8.0.2" - ["7.4.x"]="rs-7.4.0-v5" ) if [[ -v redis_version_mapping[$REDIS_VERSION] ]]; then echo "REDIS_VERSION=${redis_version_np}" >> $GITHUB_ENV @@ -74,10 +74,9 @@ jobs: fail-fast: false matrix: redis-version: + - "8.4.x" # Redis CE 8.4 - "8.2.x" # Redis CE 8.2 - "8.0.x" # Redis CE 8.0 - - "7.4.x" # Redis stack 7.4 - - "7.2.x" # Redis stack 7.2 go-version: - "1.23.x" - "1.24.x" diff --git a/Makefile b/Makefile index 0252a7e2..36902ec9 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort) -REDIS_VERSION ?= 8.2 +REDIS_VERSION ?= 8.4 RE_CLUSTER ?= false RCE_DOCKER ?= true -CLIENT_LIBS_TEST_IMAGE ?= redislabs/client-libs-test:8.2.1-pre +CLIENT_LIBS_TEST_IMAGE ?= redislabs/client-libs-test:8.4-RC1-pre docker.start: export RE_CLUSTER=$(RE_CLUSTER) && \ diff --git a/docker-compose.yml b/docker-compose.yml index cc864d85..384d0fb2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ services: redis: - image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.2.1-pre} + image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.4-RC1-pre} platform: linux/amd64 container_name: redis-standalone environment: @@ -23,7 +23,7 @@ services: - all osscluster: - image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.2.1-pre} + image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.4-RC1-pre} platform: linux/amd64 container_name: redis-osscluster environment: @@ -40,7 +40,7 @@ services: - all sentinel-cluster: - image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.2.1-pre} + image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.4-RC1-pre} platform: linux/amd64 container_name: redis-sentinel-cluster network_mode: "host" @@ -60,7 +60,7 @@ services: - all sentinel: - image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.2.1-pre} + image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.4-RC1-pre} platform: linux/amd64 container_name: redis-sentinel depends_on: @@ -84,7 +84,7 @@ services: - all ring-cluster: - image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.2.1-pre} + image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.4-RC1-pre} platform: linux/amd64 container_name: redis-ring-cluster environment: diff --git a/example/disable-maintnotifications/README.md b/example/disable-maintnotifications/README.md new file mode 100644 index 00000000..977c5b6a --- /dev/null +++ b/example/disable-maintnotifications/README.md @@ -0,0 +1,133 @@ +# Disable Maintenance Notifications Example + +This example demonstrates how to use the go-redis client with maintenance notifications **disabled**. + +## What are Maintenance Notifications? + +Maintenance notifications are a Redis Cloud feature that allows the server to notify clients about: +- Planned maintenance events +- Failover operations +- Node migrations +- Cluster topology changes + +The go-redis client supports three modes: +- **`ModeDisabled`**: Client doesn't send `CLIENT MAINT_NOTIFICATIONS ON` command +- **`ModeEnabled`**: Client forcefully sends the command, interrupts connection on error +- **`ModeAuto`** (default): Client tries to send the command, disables feature on error + +## When to Disable Maintenance Notifications + +You should disable maintenance notifications when: + +1. **Connecting to non-Redis Cloud / Redis Enterprise instances** - Standard Redis servers don't support this feature +2. **You want to handle failovers manually** - Your application has custom failover logic +3. **Minimizing client-side overhead** - You want the simplest possible client behavior +4. **The Redis server doesn't support the feature** - Older Redis versions or forks + +## Usage + +### Basic Example + +```go +import ( + "github.com/redis/go-redis/v9" + "github.com/redis/go-redis/v9/maintnotifications" +) + +rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + + // Explicitly disable maintenance notifications + MaintNotificationsConfig: &maintnotifications.Config{ + Mode: maintnotifications.ModeDisabled, + }, +}) +defer rdb.Close() +``` + +### Cluster Client Example + +```go +rdbCluster := redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: []string{"localhost:7000", "localhost:7001", "localhost:7002"}, + + // Disable maintenance notifications for cluster + MaintNotificationsConfig: &maintnotifications.Config{ + Mode: maintnotifications.ModeDisabled, + }, +}) +defer rdbCluster.Close() +``` + +### Default Behavior (ModeAuto) + +If you don't specify `MaintNotifications`, the client defaults to `ModeAuto`: + +```go +// This uses ModeAuto by default +rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + // MaintNotificationsConfig: nil means ModeAuto +}) +``` + +With `ModeAuto`, the client will: +1. Try to enable maintenance notifications +2. If the server doesn't support it, silently disable the feature +3. Continue normal operation + +## Running the Example + +1. Start a Redis server: + ```bash + redis-server --port 6379 + ``` + +2. Run the example: + ```bash + go run main.go + ``` + +## Expected Output + +``` +=== Example 1: Explicitly Disabled === +✓ Connected successfully (maintenance notifications disabled) +✓ SET operation successful +✓ GET operation successful: value1 + +=== Example 2: Default Behavior (ModeAuto) === +✓ Connected successfully (maintenance notifications auto-enabled) + +=== Example 3: Cluster Client with Disabled Notifications === +Cluster not available (expected): ... + +=== Example 4: Performance Comparison === +✓ 1000 SET operations (disabled): 45ms +✓ 1000 SET operations (auto): 46ms + +=== Cleanup === +✓ Database flushed + +=== Summary === +Maintenance notifications can be disabled by setting: + MaintNotificationsConfig: &maintnotifications.Config{ + Mode: maintnotifications.ModeDisabled, + } + +This is useful when: + - Connecting to non-Redis Cloud instances + - You want to handle failovers manually + - You want to minimize client-side overhead + - The Redis server doesn't support CLIENT MAINT_NOTIFICATIONS +``` + +## Performance Impact + +Disabling maintenance notifications has minimal performance impact. The main differences are: + +1. **Connection Setup**: One less command (`CLIENT MAINT_NOTIFICATIONS ON`) during connection initialization +2. **Runtime Overhead**: No background processing of maintenance notifications +3. **Memory Usage**: Slightly lower memory footprint (no notification handlers) + +In most cases, the performance difference is negligible (< 1%). \ No newline at end of file diff --git a/example/disable-maintnotifications/go.mod b/example/disable-maintnotifications/go.mod new file mode 100644 index 00000000..e342e2ab --- /dev/null +++ b/example/disable-maintnotifications/go.mod @@ -0,0 +1,12 @@ +module github.com/redis/go-redis/example/disable-maintnotifications + +go 1.23 + +replace github.com/redis/go-redis/v9 => ../.. + +require github.com/redis/go-redis/v9 v9.7.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/disable-maintnotifications/go.sum b/example/disable-maintnotifications/go.sum new file mode 100644 index 00000000..4db68f6d --- /dev/null +++ b/example/disable-maintnotifications/go.sum @@ -0,0 +1,8 @@ +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +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/disable-maintnotifications/main.go b/example/disable-maintnotifications/main.go new file mode 100644 index 00000000..babe4dc3 --- /dev/null +++ b/example/disable-maintnotifications/main.go @@ -0,0 +1,144 @@ +package main + +import ( + "context" + "fmt" + "time" + + "github.com/redis/go-redis/v9" + "github.com/redis/go-redis/v9/maintnotifications" +) + +func main() { + ctx := context.Background() + + // Example 0: Explicitly disable maintenance notifications + fmt.Println("=== Example 0: Explicitly Enabled ===") + rdb0 := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + + // Explicitly disable maintenance notifications + // This prevents the client from sending CLIENT MAINT_NOTIFICATIONS ON + MaintNotificationsConfig: &maintnotifications.Config{ + Mode: maintnotifications.ModeEnabled, + }, + }) + defer rdb0.Close() + + // Test the connection + if err := rdb0.Ping(ctx).Err(); err != nil { + fmt.Printf("Failed to connect: %v\n\n", err) + } + fmt.Println("When ModeEnabled, the client will return an error if the server doesn't support maintenance notifications.") + fmt.Printf("ModeAuto will silently disable the feature.\n\n") + + // Example 1: Explicitly disable maintenance notifications + fmt.Println("=== Example 1: Explicitly Disabled ===") + rdb1 := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + + // Explicitly disable maintenance notifications + // This prevents the client from sending CLIENT MAINT_NOTIFICATIONS ON + MaintNotificationsConfig: &maintnotifications.Config{ + Mode: maintnotifications.ModeDisabled, + }, + }) + defer rdb1.Close() + + // Test the connection + if err := rdb1.Ping(ctx).Err(); err != nil { + fmt.Printf("Failed to connect: %v\n\n", err) + return + } + fmt.Println("✓ Connected successfully (maintenance notifications disabled)") + + // Perform some operations + if err := rdb1.Set(ctx, "example:key1", "value1", 0).Err(); err != nil { + fmt.Printf("Failed to set key: %v\n\n", err) + return + } + fmt.Println("✓ SET operation successful") + + val, err := rdb1.Get(ctx, "example:key1").Result() + if err != nil { + fmt.Printf("Failed to get key: %v\n\n", err) + return + } + fmt.Printf("✓ GET operation successful: %s\n\n", val) + + // Example 2: Using nil config (defaults to ModeAuto) + fmt.Printf("\n=== Example 2: Default Behavior (ModeAuto) ===\n") + rdb2 := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + // MaintNotifications: nil means ModeAuto (enabled for Redis Cloud) + }) + defer rdb2.Close() + + if err := rdb2.Ping(ctx).Err(); err != nil { + fmt.Printf("Failed to connect: %v\n\n", err) + return + } + fmt.Println("✓ Connected successfully (maintenance notifications auto-enabled)") + + // Example 4: Comparing behavior with and without maintenance notifications + fmt.Printf("\n=== Example 4: Performance Comparison ===\n") + + // Client with auto-enabled notifications + startauto := time.Now() + for i := 0; i < 1000; i++ { + key := fmt.Sprintf("test:auto:%d", i) + if err := rdb2.Set(ctx, key, i, time.Minute).Err(); err != nil { + fmt.Printf("Failed to set key: %v\n", err) + return + } + } + autoDuration := time.Since(startauto) + fmt.Printf("✓ 1000 SET operations (auto): %v\n", autoDuration) + + // print pool stats + fmt.Printf("Pool stats (auto): %+v\n", rdb2.PoolStats()) + + // give the server a moment to take chill + fmt.Println("---") + time.Sleep(time.Second) + + // Client with disabled notifications + start := time.Now() + for i := 0; i < 1000; i++ { + key := fmt.Sprintf("test:disabled:%d", i) + if err := rdb1.Set(ctx, key, i, time.Minute).Err(); err != nil { + fmt.Printf("Failed to set key: %v\n", err) + return + } + } + disabledDuration := time.Since(start) + fmt.Printf("✓ 1000 SET operations (disabled): %v\n", disabledDuration) + fmt.Printf("Pool stats (disabled): %+v\n", rdb1.PoolStats()) + + // performance comparison note + fmt.Printf("\nNote: The pool stats and performance are identical because there is no background processing overhead.\n") + fmt.Println("Since the server doesn't support maintenance notifications, there is no difference in behavior.") + fmt.Printf("The only difference is that the \"ModeDisabled\" client doesn't send the CLIENT MAINT_NOTIFICATIONS ON command.\n\n") + fmt.Println("p.s. reordering the execution here makes it look like there is a small performance difference, but it's just noise.") + + // Cleanup + fmt.Printf("\n=== Cleanup ===\n") + if err := rdb1.FlushDB(ctx).Err(); err != nil { + fmt.Printf("Failed to flush DB: %v\n", err) + return + } + fmt.Println("✓ Database flushed") + + fmt.Printf("\n=== Summary ===\n") + fmt.Println("Maintenance notifications can be disabled by setting:") + fmt.Println(" MaintNotifications: &maintnotifications.Config{") + fmt.Println(" Mode: maintnotifications.ModeDisabled,") + fmt.Println(" }") + fmt.Printf("\nThis is useful when:\n") + fmt.Println(" - Connecting to non-Redis Cloud instances") + fmt.Println(" - You want to handle failovers manually") + fmt.Println(" - You want to minimize client-side overhead") + fmt.Println(" - The Redis server doesn't support CLIENT MAINT_NOTIFICATIONS") + fmt.Printf("\nFor more information, see:\n") + fmt.Println(" https://github.com/redis/go-redis/tree/master/maintnotifications") +} diff --git a/main_test.go b/main_test.go index 0d17767d..9d8efe3d 100644 --- a/main_test.go +++ b/main_test.go @@ -69,7 +69,7 @@ var RCEDocker = false // Notes version of redis we are executing tests against. // This can be used before we change the bsm fork of ginkgo for one, // which have support for label sets, so we can filter tests per redis version. -var RedisVersion float64 = 8.2 +var RedisVersion float64 = 8.4 func SkipBeforeRedisVersion(version float64, msg string) { if RedisVersion < version { @@ -96,7 +96,7 @@ var _ = BeforeSuite(func() { RedisVersion, _ = strconv.ParseFloat(strings.Trim(os.Getenv("REDIS_VERSION"), "\""), 64) if RedisVersion == 0 { - RedisVersion = 8.2 + RedisVersion = 8.4 } fmt.Printf("RECluster: %v\n", RECluster)