Skip to content

Commit 397fcb1

Browse files
authored
Cache: support redis cache backend (#4888)
* Cache: support redis cache backend Signed-off-by: Jimmie Han <[email protected]> * Cache: add get gate make linter happy Signed-off-by: Jimmie Han <[email protected]> * add concurrent config control Signed-off-by: Jimmie Han <[email protected]> * add redis cache doc Signed-off-by: Jimmie Han <[email protected]> * use doWithBatch to optmize concurrent batch code. Signed-off-by: Jimmie Han <[email protected]> * support db select Signed-off-by: Jimmie Han <[email protected]> * fix query-frontend cache config Signed-off-by: Jimmie Han <[email protected]>
1 parent c0a3f14 commit 397fcb1

File tree

18 files changed

+835
-46
lines changed

18 files changed

+835
-46
lines changed

docs/components/query-frontend.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,35 @@ config:
9595
expiration: 24h
9696
```
9797
98+
#### Redis
99+
100+
The default redis config is:
101+
102+
```yaml mdox-exec="go run scripts/cfggen/main.go --name=queryfrontend.RedisResponseCacheConfig"
103+
type: REDIS
104+
config:
105+
addr: ""
106+
username: ""
107+
password: ""
108+
db: 0
109+
dial_timeout: 5s
110+
read_timeout: 3s
111+
write_timeout: 3s
112+
pool_size: 100
113+
min_idle_conns: 10
114+
idle_timeout: 5m0s
115+
max_conn_age: 0s
116+
max_get_multi_concurrency: 100
117+
get_multi_batch_size: 100
118+
max_set_multi_concurrency: 100
119+
set_multi_batch_size: 100
120+
expiration: 24h0m0s
121+
```
122+
123+
`expiration` specifies redis cache valid time. If set to 0s, so using a default of 24 hours expiration time.
124+
125+
Other cache configuration parameters, you can refer to [redis-index-cache](store.md#redis-index-cache).
126+
98127
### Slow Query Log
99128

100129
Query Frontend supports `--query-frontend.log-queries-longer-than` flag to log queries running longer than some duration.

docs/components/store.md

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,11 +304,56 @@ While the remaining settings are **optional**:
304304
- `dns_provider_update_interval`: the DNS discovery update interval.
305305
- `auto_discovery`: whether to use the auto-discovery mechanism for memcached.
306306

307+
### Redis index cache
308+
309+
The `redis` index cache allows to use [Redis](https://redis.io) as cache backend. This cache type is configured using `--index-cache.config-file` to reference the configuration file or `--index-cache.config` to put yaml config directly:
310+
311+
```yaml mdox-exec="go run scripts/cfggen/main.go --name=cacheutil.RedisClientConfig"
312+
type: REDIS
313+
config:
314+
addr: ""
315+
username: ""
316+
password: ""
317+
db: 0
318+
dial_timeout: 5s
319+
read_timeout: 3s
320+
write_timeout: 3s
321+
pool_size: 100
322+
min_idle_conns: 10
323+
idle_timeout: 5m0s
324+
max_conn_age: 0s
325+
max_get_multi_concurrency: 100
326+
get_multi_batch_size: 100
327+
max_set_multi_concurrency: 100
328+
set_multi_batch_size: 100
329+
```
330+
331+
The **required** settings are:
332+
333+
- `addr`: redis server address.
334+
335+
While the remaining settings are **optional**:
336+
337+
- `username`: the username to connect redis, only redis 6.0 and grater need this field.
338+
- `password`: the password to connect redis.
339+
- `db`: the database to be selected after connecting to the server.
340+
- `dial_timeout`: the redis dial timeout.
341+
- `read_timeout`: the redis read timeout.
342+
- `write_timeout`: the redis write timeout.
343+
- `pool_size`: maximum number of socket connections.
344+
- `min_idle_conns`: specifies the minimum number of idle connections which is useful when establishing new connection is slow.
345+
- `idle_timeout`: amount of time after which client closes idle connections. Should be less than server's timeout.
346+
- `max_conn_age`: connection age at which client retires (closes) the connection.
347+
- `max_get_multi_concurrency`: specifies the maximum number of concurrent GetMulti() operations.
348+
- `get_multi_batch_size`: specifies the maximum size per batch for mget.
349+
- `max_set_multi_concurrency`: specifies the maximum number of concurrent SetMulti() operations.
350+
- `set_multi_batch_size`: specifies the maximum size per batch for pipeline set.
351+
307352
## Caching Bucket
308353

309354
Thanos Store Gateway supports a "caching bucket" with [chunks](../design.md#chunk) and metadata caching to speed up loading of [chunks](../design.md#chunk) from TSDB blocks. To configure caching, one needs to use `--store.caching-bucket.config=<yaml content>` or `--store.caching-bucket.config-file=<file.yaml>`.
310355

311-
Both memcached and in-memory cache "backend"s are supported:
356+
memcached/in-memory/redis cache "backend"s are supported:
312357

313358
```yaml
314359
type: MEMCACHED # Case-insensitive
@@ -333,7 +378,8 @@ metafile_content_ttl: 24h
333378
metafile_max_size: 1MiB
334379
```
335380
336-
`config` field for memcached supports all the same configuration as memcached for [index cache](#memcached-index-cache). `addresses` in the config field is a **required** setting
381+
- `config` field for memcached supports all the same configuration as memcached for [index cache](#memcached-index-cache). `addresses` in the config field is a **required** setting
382+
- `config` field for redis supports all the same configuration as redis for [index cache](#redis-index-cache).
337383

338384
Additional options to configure various aspects of [chunks](../design.md#chunk) cache are available:
339385

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8
1010
github.com/NYTimes/gziphandler v1.1.1
1111
github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a
12+
github.com/alicebob/miniredis/v2 v2.14.3
1213
github.com/aliyun/aliyun-oss-go-sdk v2.0.4+incompatible
1314
github.com/baidubce/bce-sdk-go v0.9.81
1415
github.com/blang/semver/v4 v4.0.0
@@ -29,11 +30,14 @@ require (
2930
github.com/fsnotify/fsnotify v1.5.1
3031
github.com/go-kit/log v0.2.0
3132
github.com/go-openapi/strfmt v0.21.0
33+
github.com/go-redis/redis/v8 v8.11.4
3234
github.com/gogo/protobuf v1.3.2
3335
github.com/gogo/status v1.1.0
3436
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
3537
github.com/golang/snappy v0.0.4
3638
github.com/googleapis/gax-go v2.0.2+incompatible
39+
github.com/grafana/dskit v0.0.0-20211021180445-3bd016e9d7f1
40+
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
3741
github.com/grpc-ecosystem/go-grpc-middleware/providers/kit/v2 v2.0.0-20201002093600-73cf2ae9d891
3842
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.2.0.20201207153454-9f6bf00c00a7
3943
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0

go.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -946,8 +946,9 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb
946946
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
947947
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
948948
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
949-
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 h1:FlFbCRLd5Jr4iYXZufAvgWN6Ao0JrI5chLINnUXDDr0=
950949
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
950+
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
951+
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
951952
github.com/grpc-ecosystem/go-grpc-middleware/providers/kit/v2 v2.0.0-20201002093600-73cf2ae9d891 h1:RhOqTAECcPnehv3hKlYy1fAnpQ7rnZu58l3mpq6gT1k=
952953
github.com/grpc-ecosystem/go-grpc-middleware/providers/kit/v2 v2.0.0-20201002093600-73cf2ae9d891/go.mod h1:516cTXxZzi4NBUBbKcwmO4Eqbb6GHAEd3o4N+GYyCBY=
953954
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-20200501113911-9a95f0fdbfea/go.mod h1:GugMBs30ZSAkckqXEAIEGyYdDH6EgqowG8ppA3Zt+AY=

pkg/cache/memcached.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
// MemcachedCache is a memcached-based cache.
1919
type MemcachedCache struct {
2020
logger log.Logger
21-
memcached cacheutil.MemcachedClient
21+
memcached cacheutil.RemoteCacheClient
2222
name string
2323

2424
// Metrics.
@@ -27,7 +27,7 @@ type MemcachedCache struct {
2727
}
2828

2929
// NewMemcachedCache makes a new MemcachedCache.
30-
func NewMemcachedCache(name string, logger log.Logger, memcached cacheutil.MemcachedClient, reg prometheus.Registerer) *MemcachedCache {
30+
func NewMemcachedCache(name string, logger log.Logger, memcached cacheutil.RemoteCacheClient, reg prometheus.Registerer) *MemcachedCache {
3131
c := &MemcachedCache{
3232
logger: logger,
3333
memcached: memcached,

pkg/cache/redis.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) The Thanos Authors.
2+
// Licensed under the Apache License 2.0.
3+
4+
package cache
5+
6+
import (
7+
"context"
8+
"time"
9+
10+
"github.com/go-kit/log"
11+
"github.com/go-kit/log/level"
12+
"github.com/prometheus/client_golang/prometheus"
13+
"github.com/prometheus/client_golang/prometheus/promauto"
14+
"github.com/thanos-io/thanos/pkg/cacheutil"
15+
)
16+
17+
// RedisCache is a redis cache.
18+
type RedisCache struct {
19+
logger log.Logger
20+
redisClient *cacheutil.RedisClient
21+
name string
22+
23+
// Metrics.
24+
requests prometheus.Counter
25+
hits prometheus.Counter
26+
}
27+
28+
// NewRedisCache makes a new RedisCache.
29+
func NewRedisCache(name string, logger log.Logger, redisClient *cacheutil.RedisClient, reg prometheus.Registerer) *RedisCache {
30+
c := &RedisCache{
31+
logger: logger,
32+
redisClient: redisClient,
33+
name: name,
34+
}
35+
36+
c.requests = promauto.With(reg).NewCounter(prometheus.CounterOpts{
37+
Name: "thanos_cache_redis_requests_total",
38+
Help: "Total number of items requests to redis.",
39+
ConstLabels: prometheus.Labels{"name": name},
40+
})
41+
42+
c.hits = promauto.With(reg).NewCounter(prometheus.CounterOpts{
43+
Name: "thanos_cache_redis_hits_total",
44+
Help: "Total number of items requests to the cache that were a hit.",
45+
ConstLabels: prometheus.Labels{"name": name},
46+
})
47+
48+
level.Info(logger).Log("msg", "created redis cache")
49+
50+
return c
51+
}
52+
53+
// Store data identified by keys.
54+
func (c *RedisCache) Store(ctx context.Context, data map[string][]byte, ttl time.Duration) {
55+
c.redisClient.SetMulti(ctx, data, ttl)
56+
}
57+
58+
// Fetch fetches multiple keys and returns a map containing cache hits, along with a list of missing keys.
59+
// In case of error, it logs and return an empty cache hits map.
60+
func (c *RedisCache) Fetch(ctx context.Context, keys []string) map[string][]byte {
61+
c.requests.Add(float64(len(keys)))
62+
results := c.redisClient.GetMulti(ctx, keys)
63+
c.hits.Add(float64(len(results)))
64+
return results
65+
}
66+
67+
func (c *RedisCache) Name() string {
68+
return c.name
69+
}

pkg/cache/redis_test.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright (c) The Thanos Authors.
2+
// Licensed under the Apache License 2.0.
3+
4+
package cache
5+
6+
import (
7+
"context"
8+
"os"
9+
"testing"
10+
"time"
11+
12+
"github.com/alicebob/miniredis/v2"
13+
"github.com/go-kit/log"
14+
"github.com/prometheus/client_golang/prometheus"
15+
prom_testutil "github.com/prometheus/client_golang/prometheus/testutil"
16+
"github.com/thanos-io/thanos/pkg/cacheutil"
17+
"github.com/thanos-io/thanos/pkg/testutil"
18+
)
19+
20+
func TestRedisCache(t *testing.T) {
21+
// Init some data to conveniently define test cases later one.
22+
key1 := "key1"
23+
key2 := "key2"
24+
key3 := "key3"
25+
value1 := []byte{1}
26+
value2 := []byte{2}
27+
value3 := []byte{3}
28+
29+
type args struct {
30+
data map[string][]byte
31+
fetchKeys []string
32+
}
33+
type want struct {
34+
hits map[string][]byte
35+
}
36+
tests := []struct {
37+
name string
38+
args args
39+
want want
40+
}{
41+
{
42+
name: "all hit",
43+
args: args{
44+
data: map[string][]byte{
45+
key1: value1,
46+
key2: value2,
47+
key3: value3,
48+
},
49+
fetchKeys: []string{key1, key2, key3},
50+
},
51+
want: want{
52+
hits: map[string][]byte{
53+
key1: value1,
54+
key2: value2,
55+
key3: value3,
56+
},
57+
},
58+
},
59+
{
60+
name: "partial hit",
61+
args: args{
62+
data: map[string][]byte{
63+
key1: value1,
64+
key2: value2,
65+
},
66+
fetchKeys: []string{key1, key2, key3},
67+
},
68+
want: want{
69+
hits: map[string][]byte{
70+
key1: value1,
71+
key2: value2,
72+
},
73+
},
74+
},
75+
{
76+
name: "not hit",
77+
args: args{
78+
data: map[string][]byte{},
79+
fetchKeys: []string{key1, key2, key3},
80+
},
81+
want: want{
82+
hits: map[string][]byte{},
83+
},
84+
},
85+
}
86+
s, err := miniredis.Run()
87+
if err != nil {
88+
testutil.Ok(t, err)
89+
}
90+
defer s.Close()
91+
logger := log.NewLogfmtLogger(os.Stderr)
92+
reg := prometheus.NewRegistry()
93+
cfg := cacheutil.DefaultRedisClientConfig
94+
cfg.Addr = s.Addr()
95+
c, err := cacheutil.NewRedisClientWithConfig(logger, t.Name(), cfg, reg)
96+
if err != nil {
97+
testutil.Ok(t, err)
98+
}
99+
for _, tt := range tests {
100+
t.Run(tt.name, func(t *testing.T) {
101+
defer s.FlushAll()
102+
c := NewRedisCache(tt.name, logger, c, reg)
103+
// Store the cache expected before running the test.
104+
ctx := context.Background()
105+
c.Store(ctx, tt.args.data, time.Hour)
106+
107+
// Fetch postings from cached and assert on it.
108+
hits := c.Fetch(ctx, tt.args.fetchKeys)
109+
testutil.Equals(t, tt.want.hits, hits)
110+
111+
// Assert on metrics.
112+
testutil.Equals(t, float64(len(tt.args.fetchKeys)), prom_testutil.ToFloat64(c.requests))
113+
testutil.Equals(t, float64(len(tt.want.hits)), prom_testutil.ToFloat64(c.hits))
114+
})
115+
}
116+
}

0 commit comments

Comments
 (0)