Skip to content

Fixed Cache fetch error on Redis Cluster #4056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 27, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
* [BUGFIX] Querier: fixed race condition causing queries to fail right after querier startup with the "empty ring" error. #4068
* [BUGFIX] Compactor: Increment `cortex_compactor_runs_failed_total` if compactor failed compact a single tenant. #4094
* [BUGFIX] Tracing: hot fix to avoid the Jaeger tracing client to indefinitely block the Cortex process shutdown in case the HTTP connection to the tracing backend is blocked. #4134
* [BUGFIX] Fixed cache fetch error on Redis Cluster. #4056
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase master and move this CHANGELOG entry to the top. We've cut 1.9 release in the meanwhile. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kamijin-fanta This commit is still valid. Could you address it, please?


## Blocksconvert

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/Masterminds/squirrel v0.0.0-20161115235646-20f192218cf5
github.com/NYTimes/gziphandler v1.1.1
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15
github.com/alicebob/miniredis v2.5.0+incompatible
github.com/alicebob/miniredis/v2 v2.14.3
github.com/armon/go-metrics v0.3.6
github.com/aws/aws-sdk-go v1.38.3
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZp
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/alicebob/miniredis/v2 v2.14.3 h1:QWoo2wchYmLgOB6ctlTt2dewQ1Vu6phl+iQbwT8SYGo=
github.com/alicebob/miniredis/v2 v2.14.3/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I=
github.com/aliyun/aliyun-oss-go-sdk v2.0.4+incompatible h1:EaK5256H3ELiyaq5O/Zwd6fnghD6DqmZDQmmzzJklUU=
github.com/aliyun/aliyun-oss-go-sdk v2.0.4+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
Expand Down Expand Up @@ -634,7 +636,6 @@ github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.8.4/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
Expand Down
2 changes: 1 addition & 1 deletion pkg/chunk/cache/redis_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"testing"
"time"

"github.com/alicebob/miniredis"
"github.com/alicebob/miniredis/v2"
"github.com/go-kit/kit/log"
"github.com/go-redis/redis/v8"
"github.com/stretchr/testify/require"
Expand Down
37 changes: 29 additions & 8 deletions pkg/chunk/cache/redis_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,38 @@ func (c *RedisClient) MGet(ctx context.Context, keys []string) ([][]byte, error)
defer cancel()
}

cmd := c.rdb.MGet(ctx, keys...)
if err := cmd.Err(); err != nil {
return nil, err
}

ret := make([][]byte, len(keys))
for i, val := range cmd.Val() {
if val != nil {
ret[i] = StringToBytes(val.(string))

// redis.UniversalClient can take redis.Client and redis.ClusterClient.
// if redis.Client is set, then Single node or sentinel configuration. mget is always supported.
// if redis.ClusterClient is set, then Redis Cluster configuration. mget may not be supported.
_, isCluster := c.rdb.(*redis.ClusterClient)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a question to better understand the fix (I'm not an expect on Redis or the Go bindings).

This is working around an issue/limitation in the bindings, correct? Or a limitation in a particular server version? I only ask because my first reaction to this is that checking the type of an interface feels wrong. If it's the only option, then we'll have to live with it of course.

Either way, it might be nice to put a comment here to explain why we have to check the type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the review!

I thought the same thing, but I think this method is the simplest. The Redis client is initialized with the NewUniversalClient .

https://github.com/cortexproject/cortex/blob/v1.8.0/pkg/chunk/cache/redis_client.go#L70

This is the implementation.

https://github.com/go-redis/redis/blob/v8.8.0/universal.go#L199-L206

UniversalClient can take Client and ClusterClient. Single node or sentinel if Client is set. mget is always supported. Redis Cluster configuration if ClusterClient is set. mget may not be supported.

The only way to check if the Universal Client is Redis Cluster is by type assertion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment.


if isCluster {
for i, key := range keys {
cmd := c.rdb.Get(ctx, key)
err := cmd.Err()
if err == redis.Nil {
// if key not found, response nil
continue
} else if err != nil {
return nil, err
}
ret[i] = StringToBytes(cmd.Val())
}
} else {
cmd := c.rdb.MGet(ctx, keys...)
if err := cmd.Err(); err != nil {
return nil, err
}

for i, val := range cmd.Val() {
if val != nil {
ret[i] = StringToBytes(val.(string))
}
}
}

return ret, nil
}

Expand Down
100 changes: 100 additions & 0 deletions pkg/chunk/cache/redis_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package cache

import (
"context"
"testing"
"time"

"github.com/alicebob/miniredis/v2"
"github.com/go-redis/redis/v8"
"github.com/stretchr/testify/require"
)

func TestRedisClient(t *testing.T) {
single, err := mockRedisClientSingle()
require.Nil(t, err)
defer single.Close()

cluster, err := mockRedisClientCluster()
require.Nil(t, err)
defer cluster.Close()

ctx := context.Background()

tests := []struct {
name string
client *RedisClient
}{
{
name: "single redis client",
client: single,
},
{
name: "cluster redis client",
client: cluster,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
keys := []string{"key1", "key2", "key3"}
bufs := [][]byte{[]byte("data1"), []byte("data2"), []byte("data3")}
miss := []string{"miss1", "miss2"}

// set values
err := tt.client.MSet(ctx, keys, bufs)
require.Nil(t, err)

// get keys
values, err := tt.client.MGet(ctx, keys)
require.Nil(t, err)
require.Len(t, values, len(keys))
for i, value := range values {
require.Equal(t, values[i], value)
}

// get missing keys
values, err = tt.client.MGet(ctx, miss)
require.Nil(t, err)
require.Len(t, values, len(miss))
for _, value := range values {
require.Nil(t, value)
}
})
}
}

func mockRedisClientSingle() (*RedisClient, error) {
redisServer, err := miniredis.Run()
if err != nil {
return nil, err
}
return &RedisClient{
expiration: time.Minute,
timeout: 100 * time.Millisecond,
rdb: redis.NewClient(&redis.Options{
Addr: redisServer.Addr(),
}),
}, nil
}

func mockRedisClientCluster() (*RedisClient, error) {
redisServer1, err := miniredis.Run()
if err != nil {
return nil, err
}
redisServer2, err := miniredis.Run()
if err != nil {
return nil, err
}
return &RedisClient{
expiration: time.Minute,
timeout: 100 * time.Millisecond,
rdb: redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{
redisServer1.Addr(),
redisServer2.Addr(),
},
}),
}, nil
}

This file was deleted.

This file was deleted.

Loading