We do not use Redis. The client was a go-redis fork and still said so
everywhere: `package redis`, `redis.NewClient`, `redis://`, `extra/redisotel`.
Consumers wrote `redis.Client` against a Valkey-backed store called KV.
package redis -> package kv (93 decls)
redis.X -> kv.X (2,794 call sites)
"redis: …" errors -> "kv: …" (206)
extra/redis{otel,cmd,prometheus,census} -> extra/kv{otel,cmd,prometheus,census}
redis:// , rediss:// -> kv:// , kvs:// (ParseURL)
Redis*/*Redis* idents -> KV*/*KV*
REDIS_VERSION, REDIS_ENDPOINTS_CONFIG_PATH -> KV_*
I previously argued this rename was unsafe because much of it was "wire
contract". That was wrong, and the categorisation shows why: RESP command names
are GET/SET/HSET — they never contain the word. Of 5,771 occurrences only two
kinds were server-visible at all: the ParseURL scheme (we choose what we pass;
our configs already use kv://) and the CLIENT SETINFO lib-name label, which the
server merely records. Nothing here changes a byte of protocol.
Two things deliberately keep the old spelling:
RediSearch — names a specific external module the FT.* commands map to.
StoreDist — contains the letters by accident (sto-reDis-t), not the word.
Breaking for consumers by design: every import site must move redis.X -> kv.X.
Minor bump, never a major — this module stays v9 forever.
KV Cluster MGET Example
This example demonstrates how to use the KV Cluster client with the MGET command to retrieve multiple keys efficiently.
Overview
The example shows:
- Creating a KV Cluster client
- Setting 10 keys with individual
SETcommands - 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 KV Cluster. The example expects cluster nodes at:
localhost:7000localhost:7001localhost:7002
Setting up a KV Cluster (using Docker)
If you don't have a KV Cluster running, you can use the docker-compose setup from the repository root:
# From the go-kv repository root
docker compose --profile cluster up -d
This will start a KV Cluster with nodes on ports 16600-16605.
If using the docker-compose cluster, update the Addrs in main.go to:
Addrs: []string{
"localhost:16600",
"localhost:16601",
"localhost:16602",
},
Running the Example
go run main.go
Expected Output
✓ Connected to KV 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 KV command that retrieves the values of multiple keys in a single operation. This is more efficient than executing multiple individual GET commands.
Syntax:
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
nilis 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
// Create cluster client
rdb := kv.NewClusterClient(&kv.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
}