1package redis_test
2
3import (
4	"bytes"
5	"context"
6	"fmt"
7	"strings"
8	"testing"
9	"time"
10
11	"github.com/go-redis/redis/v8"
12)
13
14func benchmarkRedisClient(ctx context.Context, poolSize int) *redis.Client {
15	client := redis.NewClient(&redis.Options{
16		Addr:         ":6379",
17		DialTimeout:  time.Second,
18		ReadTimeout:  time.Second,
19		WriteTimeout: time.Second,
20		PoolSize:     poolSize,
21	})
22	if err := client.FlushDB(ctx).Err(); err != nil {
23		panic(err)
24	}
25	return client
26}
27
28func BenchmarkRedisPing(b *testing.B) {
29	ctx := context.Background()
30	client := benchmarkRedisClient(ctx, 10)
31	defer client.Close()
32
33	b.ResetTimer()
34
35	b.RunParallel(func(pb *testing.PB) {
36		for pb.Next() {
37			if err := client.Ping(ctx).Err(); err != nil {
38				b.Fatal(err)
39			}
40		}
41	})
42}
43
44func BenchmarkRedisGetNil(b *testing.B) {
45	ctx := context.Background()
46	client := benchmarkRedisClient(ctx, 10)
47	defer client.Close()
48
49	b.ResetTimer()
50
51	b.RunParallel(func(pb *testing.PB) {
52		for pb.Next() {
53			if err := client.Get(ctx, "key").Err(); err != redis.Nil {
54				b.Fatal(err)
55			}
56		}
57	})
58}
59
60type setStringBenchmark struct {
61	poolSize  int
62	valueSize int
63}
64
65func (bm setStringBenchmark) String() string {
66	return fmt.Sprintf("pool=%d value=%d", bm.poolSize, bm.valueSize)
67}
68
69func BenchmarkRedisSetString(b *testing.B) {
70	benchmarks := []setStringBenchmark{
71		{10, 64},
72		{10, 1024},
73		{10, 64 * 1024},
74		{10, 1024 * 1024},
75		{10, 10 * 1024 * 1024},
76
77		{100, 64},
78		{100, 1024},
79		{100, 64 * 1024},
80		{100, 1024 * 1024},
81		{100, 10 * 1024 * 1024},
82	}
83	for _, bm := range benchmarks {
84		b.Run(bm.String(), func(b *testing.B) {
85			ctx := context.Background()
86			client := benchmarkRedisClient(ctx, bm.poolSize)
87			defer client.Close()
88
89			value := strings.Repeat("1", bm.valueSize)
90
91			b.ResetTimer()
92
93			b.RunParallel(func(pb *testing.PB) {
94				for pb.Next() {
95					err := client.Set(ctx, "key", value, 0).Err()
96					if err != nil {
97						b.Fatal(err)
98					}
99				}
100			})
101		})
102	}
103}
104
105func BenchmarkRedisSetGetBytes(b *testing.B) {
106	ctx := context.Background()
107	client := benchmarkRedisClient(ctx, 10)
108	defer client.Close()
109
110	value := bytes.Repeat([]byte{'1'}, 10000)
111
112	b.ResetTimer()
113
114	b.RunParallel(func(pb *testing.PB) {
115		for pb.Next() {
116			if err := client.Set(ctx, "key", value, 0).Err(); err != nil {
117				b.Fatal(err)
118			}
119
120			got, err := client.Get(ctx, "key").Bytes()
121			if err != nil {
122				b.Fatal(err)
123			}
124			if !bytes.Equal(got, value) {
125				b.Fatalf("got != value")
126			}
127		}
128	})
129}
130
131func BenchmarkRedisMGet(b *testing.B) {
132	ctx := context.Background()
133	client := benchmarkRedisClient(ctx, 10)
134	defer client.Close()
135
136	if err := client.MSet(ctx, "key1", "hello1", "key2", "hello2").Err(); err != nil {
137		b.Fatal(err)
138	}
139
140	b.ResetTimer()
141
142	b.RunParallel(func(pb *testing.PB) {
143		for pb.Next() {
144			if err := client.MGet(ctx, "key1", "key2").Err(); err != nil {
145				b.Fatal(err)
146			}
147		}
148	})
149}
150
151func BenchmarkSetExpire(b *testing.B) {
152	ctx := context.Background()
153	client := benchmarkRedisClient(ctx, 10)
154	defer client.Close()
155
156	b.ResetTimer()
157
158	b.RunParallel(func(pb *testing.PB) {
159		for pb.Next() {
160			if err := client.Set(ctx, "key", "hello", 0).Err(); err != nil {
161				b.Fatal(err)
162			}
163			if err := client.Expire(ctx, "key", time.Second).Err(); err != nil {
164				b.Fatal(err)
165			}
166		}
167	})
168}
169
170func BenchmarkPipeline(b *testing.B) {
171	ctx := context.Background()
172	client := benchmarkRedisClient(ctx, 10)
173	defer client.Close()
174
175	b.ResetTimer()
176
177	b.RunParallel(func(pb *testing.PB) {
178		for pb.Next() {
179			_, err := client.Pipelined(ctx, func(pipe redis.Pipeliner) error {
180				pipe.Set(ctx, "key", "hello", 0)
181				pipe.Expire(ctx, "key", time.Second)
182				return nil
183			})
184			if err != nil {
185				b.Fatal(err)
186			}
187		}
188	})
189}
190
191func BenchmarkZAdd(b *testing.B) {
192	ctx := context.Background()
193	client := benchmarkRedisClient(ctx, 10)
194	defer client.Close()
195
196	b.ResetTimer()
197
198	b.RunParallel(func(pb *testing.PB) {
199		for pb.Next() {
200			err := client.ZAdd(ctx, "key", &redis.Z{
201				Score:  float64(1),
202				Member: "hello",
203			}).Err()
204			if err != nil {
205				b.Fatal(err)
206			}
207		}
208	})
209}
210
211var clientSink *redis.Client
212
213func BenchmarkWithContext(b *testing.B) {
214	ctx := context.Background()
215	rdb := benchmarkRedisClient(ctx, 10)
216	defer rdb.Close()
217
218	b.ResetTimer()
219	b.ReportAllocs()
220
221	for i := 0; i < b.N; i++ {
222		clientSink = rdb.WithContext(ctx)
223	}
224}
225
226var ringSink *redis.Ring
227
228func BenchmarkRingWithContext(b *testing.B) {
229	ctx := context.Background()
230	rdb := redis.NewRing(&redis.RingOptions{})
231	defer rdb.Close()
232
233	b.ResetTimer()
234	b.ReportAllocs()
235
236	for i := 0; i < b.N; i++ {
237		ringSink = rdb.WithContext(ctx)
238	}
239}
240
241//------------------------------------------------------------------------------
242
243func newClusterScenario() *clusterScenario {
244	return &clusterScenario{
245		ports:     []string{"8220", "8221", "8222", "8223", "8224", "8225"},
246		nodeIDs:   make([]string, 6),
247		processes: make(map[string]*redisProcess, 6),
248		clients:   make(map[string]*redis.Client, 6),
249	}
250}
251
252func BenchmarkClusterPing(b *testing.B) {
253	if testing.Short() {
254		b.Skip("skipping in short mode")
255	}
256
257	ctx := context.Background()
258	cluster := newClusterScenario()
259	if err := startCluster(ctx, cluster); err != nil {
260		b.Fatal(err)
261	}
262	defer cluster.Close()
263
264	client := cluster.newClusterClient(ctx, redisClusterOptions())
265	defer client.Close()
266
267	b.ResetTimer()
268
269	b.RunParallel(func(pb *testing.PB) {
270		for pb.Next() {
271			err := client.Ping(ctx).Err()
272			if err != nil {
273				b.Fatal(err)
274			}
275		}
276	})
277}
278
279func BenchmarkClusterSetString(b *testing.B) {
280	if testing.Short() {
281		b.Skip("skipping in short mode")
282	}
283
284	ctx := context.Background()
285	cluster := newClusterScenario()
286	if err := startCluster(ctx, cluster); err != nil {
287		b.Fatal(err)
288	}
289	defer cluster.Close()
290
291	client := cluster.newClusterClient(ctx, redisClusterOptions())
292	defer client.Close()
293
294	value := string(bytes.Repeat([]byte{'1'}, 10000))
295
296	b.ResetTimer()
297
298	b.RunParallel(func(pb *testing.PB) {
299		for pb.Next() {
300			err := client.Set(ctx, "key", value, 0).Err()
301			if err != nil {
302				b.Fatal(err)
303			}
304		}
305	})
306}
307
308var clusterSink *redis.ClusterClient
309
310func BenchmarkClusterWithContext(b *testing.B) {
311	ctx := context.Background()
312	rdb := redis.NewClusterClient(&redis.ClusterOptions{})
313	defer rdb.Close()
314
315	b.ResetTimer()
316	b.ReportAllocs()
317
318	for i := 0; i < b.N; i++ {
319		clusterSink = rdb.WithContext(ctx)
320	}
321}
322