From c05af625f926834b526e96faf7bfba550868a94e Mon Sep 17 00:00:00 2001 From: filipe oliveira Date: Thu, 28 Sep 2023 15:24:43 +0100 Subject: [PATCH] Introduce client side caching benchmark capabilities. Enable rueidis/radix underlying vanilla client selection (#31) * First CSC POC using a fork of ruedis * Added replica test to CI. Enabled all features on the rueidis variation * Fixed CI service image --- .gitignore | 1 + Makefile | 2 +- commands.go | 91 ++++++++++++++++++++++ common.go | 2 + go.mod | 4 +- go.sum | 12 +-- redis-bechmark-go.go | 150 +++++++++++++++++++++++++++++++------ redis-bechmark-go_test.go | 7 +- standalone_conn.go | 2 +- utils/gen_read_only_map.py | 31 ++++++++ 10 files changed, 272 insertions(+), 30 deletions(-) create mode 100644 commands.go create mode 100644 utils/gen_read_only_map.py diff --git a/.gitignore b/.gitignore index 1ab3e8f..c42e6e8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist *.json dump.rdb .coverdata +.DS_Store diff --git a/Makefile b/Makefile index 7f9495c..8a6ee1b 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ GIT_DIRTY:=$(shell git diff --no-ext-diff 2> /dev/null | wc -l) endif .PHONY: all test coverage -all: test build +all: build build-coverage: $(GOBUILD) -cover \ diff --git a/commands.go b/commands.go new file mode 100644 index 0000000..1b6a4cf --- /dev/null +++ b/commands.go @@ -0,0 +1,91 @@ +package main + +var readOnlyCommands = map[string]bool{ + "LLEN": true, + "DUMP": true, + "PTTL": true, + "GEOHASH": true, + "HEXISTS": true, + "GROUPS": true, + "GET": true, + "SSCAN": true, + "TOUCH": true, + "CONSUMERS": true, + "BITCOUNT": true, + "MGET": true, + "SUBSTR": true, + "HVALS": true, + "TTL": true, + "FREQ": true, + "XREAD": true, + "PFCOUNT": true, + "ZREVRANGEBYSCORE": true, + "HMGET": true, + "LINDEX": true, + "XLEN": true, + "GEODIST": true, + "ZLEXCOUNT": true, + "LPOS": true, + "ZREVRANK": true, + "GEORADIUS_RO": true, + "GETRANGE": true, + "STRLEN": true, + "REFCOUNT": true, + "IDLETIME": true, + "SCARD": true, + "EXPIRETIME": true, + "ZRANGE": true, + "LOLWUT": true, + "SINTER": true, + "XREVRANGE": true, + "ZRANGEBYSCORE": true, + "SUNION": true, + "GETBIT": true, + "BITFIELD_RO": true, + "ZDIFF": true, + "HGETALL": true, + "ZSCORE": true, + "ZREVRANGE": true, + "ZREVRANGEBYLEX": true, + "SINTERCARD": true, + "SMISMEMBER": true, + "KEYS": true, + "LCS": true, + "SORT_RO": true, + "ZRANK": true, + "HSTRLEN": true, + "LRANGE": true, + "PEXPIRETIME": true, + "RANDOMKEY": true, + "SMEMBERS": true, + "ZMSCORE": true, + "HKEYS": true, + "ZCOUNT": true, + "ZSCAN": true, + "GEOSEARCH": true, + "ZUNION": true, + "XPENDING": true, + "HRANDFIELD": true, + "HLEN": true, + "ZRANDMEMBER": true, + "DBSIZE": true, + "SDIFF": true, + "STREAM": true, + "ZINTER": true, + "SCAN": true, + "TYPE": true, + "USAGE": true, + "ZINTERCARD": true, + "SISMEMBER": true, + "HGET": true, + "SRANDMEMBER": true, + "ENCODING": true, + "EXISTS": true, + "GEOPOS": true, + "XRANGE": true, + "ZRANGEBYLEX": true, + "GEORADIUSBYMEMBER_RO": true, + "BITPOS": true, + "ZCARD": true, + "HSCAN": true, +} diff --git a/common.go b/common.go index b2d00c5..c81d6a3 100644 --- a/common.go +++ b/common.go @@ -12,6 +12,7 @@ import ( ) var totalCommands uint64 +var totalCached uint64 var totalErrors uint64 var latencies *hdrhistogram.Histogram var benchmarkCommands arrayStringParameters @@ -23,6 +24,7 @@ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" type datapoint struct { success bool duration_ms int64 + cachedEntry bool } func stringWithCharset(length int, charset string) string { diff --git a/go.mod b/go.mod index 9dbdb2d..8a93757 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/HdrHistogram/hdrhistogram-go v1.1.0 github.com/mattn/go-shellwords v1.0.12 github.com/mediocregopher/radix/v4 v4.1.2 - github.com/rueian/rueidis v0.0.100 + github.com/redis/rueidis v1.0.19 golang.org/x/time v0.0.0-20191024005414-555d28b269f0 ) @@ -14,3 +14,5 @@ require ( github.com/tilinna/clock v1.0.2 // indirect golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 // indirect ) + +replace github.com/redis/rueidis => github.com/filipecosta90/rueidis v0.0.0-20230927221707-2d17d4ee82e3 diff --git a/go.sum b/go.sum index d531975..6cf39cc 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/filipecosta90/rueidis v0.0.0-20230927221707-2d17d4ee82e3 h1:slwoBsdbPe8JqOhlEaEZzkog/PLSwAQGuW3QtkIRsNM= +github.com/filipecosta90/rueidis v0.0.0-20230927221707-2d17d4ee82e3/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -22,11 +24,11 @@ github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lL github.com/mediocregopher/radix/v4 v4.1.2 h1:Pj7XnNK5WuzzFy63g98pnccainAePK+aZNQRvxSvj2I= github.com/mediocregopher/radix/v4 v4.1.2/go.mod h1:ajchozX/6ELmydxWeWM6xCFHVpZ4+67LXHOTOVR0nCE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/gomega v1.27.5 h1:T/X6I0RNFw/kTqgfkZPcQ5KU6vCnWNBGdtrIx2dpGeQ= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rueian/rueidis v0.0.100 h1:22yp/+8YHuWc/vcrp8bkjeE7baD3vygoh2gZ2+xu1KQ= -github.com/rueian/rueidis v0.0.100/go.mod h1:ivvsRYRtAUcf9OnheuKc5Gpa8IebrkLT1P45Lr2jlXE= +github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo= +github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= @@ -52,14 +54,14 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/redis-bechmark-go.go b/redis-bechmark-go.go index 72df233..13396e0 100644 --- a/redis-bechmark-go.go +++ b/redis-bechmark-go.go @@ -5,37 +5,101 @@ import ( "flag" "fmt" hdrhistogram "github.com/HdrHistogram/hdrhistogram-go" - "github.com/mediocregopher/radix/v4" + radix "github.com/mediocregopher/radix/v4" + "github.com/redis/rueidis" "golang.org/x/time/rate" "log" "math/rand" "net" "os" "os/signal" + "strings" "sync" "time" ) -func benchmarkRoutine(conn Client, enableMultiExec bool, datapointsChan chan datapoint, continueOnError bool, cmdS [][]string, commandsCDF []float32, keyspacelen, datasize, number_samples uint64, loop bool, debug_level int, wg *sync.WaitGroup, keyplace, dataplace []int, useLimiter bool, rateLimiter *rate.Limiter, waitReplicas, waitReplicasMs int) { +func benchmarkRoutine(radixClient Client, ruedisClient rueidis.Client, useRuedis, useCSC, enableMultiExec bool, datapointsChan chan datapoint, continueOnError bool, cmdS [][]string, commandsCDF []float32, keyspacelen, datasize, number_samples uint64, loop bool, debug_level int, wg *sync.WaitGroup, keyplace, dataplace []int, readOnly []bool, useLimiter bool, rateLimiter *rate.Limiter, waitReplicas, waitReplicasMs int, cscDuration time.Duration) { defer wg.Done() for i := 0; uint64(i) < number_samples || loop; i++ { cmdPos := sample(commandsCDF) kplace := keyplace[cmdPos] dplace := dataplace[cmdPos] + isReadOnly := readOnly[cmdPos] cmds := cmdS[cmdPos] newCmdS, key := keyBuildLogic(kplace, dplace, datasize, keyspacelen, cmds, charset) - rawCurrentCmd := radix.Cmd(nil, newCmdS[0], newCmdS[1:]...) - sendCmdLogic(conn, rawCurrentCmd, enableMultiExec, key, datapointsChan, continueOnError, debug_level, useLimiter, rateLimiter, waitReplicas, waitReplicasMs) + if useLimiter { + r := rateLimiter.ReserveN(time.Now(), int(1)) + time.Sleep(r.Delay()) + } + if useRuedis { + sendCmdLogicRuedis(ruedisClient, newCmdS, enableMultiExec, datapointsChan, continueOnError, debug_level, useCSC, isReadOnly, cscDuration, waitReplicas, waitReplicasMs) + } else { + sendCmdLogicRadix(radixClient, newCmdS, enableMultiExec, key, datapointsChan, continueOnError, debug_level, waitReplicas, waitReplicasMs) + + } } } -func sendCmdLogic(conn Client, cmd radix.Action, enableMultiExec bool, key string, datapointsChan chan datapoint, continueOnError bool, debug_level int, useRateLimiter bool, rateLimiter *rate.Limiter, waitReplicas, waitReplicasMs int) { +func sendCmdLogicRuedis(ruedisClient rueidis.Client, newCmdS []string, enableMultiExec bool, datapointsChan chan datapoint, continueOnError bool, debug_level int, useCSC, isReadOnly bool, cscDuration time.Duration, waitReplicas, waitReplicasMs int) { ctx := context.Background() + var startT time.Time + var endT time.Time + var redisResult rueidis.RedisResult + cacheHit := false + var err error + arbitrary := ruedisClient.B().Arbitrary(newCmdS[0]) + if len(newCmdS) > 1 { + arbitrary = arbitrary.Keys(newCmdS[1]) + if len(newCmdS) > 2 { + arbitrary = arbitrary.Args(newCmdS[2:]...) + } + } + if useCSC && isReadOnly { + startT = time.Now() + redisResult = ruedisClient.DoCache(ctx, arbitrary.Cache(), cscDuration) + endT = time.Now() + } else if enableMultiExec { + cmds := make(rueidis.Commands, 0, 3) + cmds = append(cmds, ruedisClient.B().Multi().Build()) + cmds = append(cmds, arbitrary.Build()) + cmds = append(cmds, ruedisClient.B().Exec().Build()) + startT = time.Now() + resMulti := ruedisClient.DoMulti(ctx, cmds...) + endT = time.Now() + redisResult = resMulti[1] + } else if waitReplicas > 0 { + cmds := make(rueidis.Commands, 0, 2) + cmds = append(cmds, arbitrary.Build()) + cmds = append(cmds, ruedisClient.B().Wait().Numreplicas(int64(waitReplicas)).Timeout(int64(waitReplicasMs)).Build()) + startT = time.Now() + resMulti := ruedisClient.DoMulti(ctx, cmds...) + endT = time.Now() + redisResult = resMulti[0] + } else { + startT = time.Now() + redisResult = ruedisClient.Do(ctx, arbitrary.Build()) + endT = time.Now() + } + err = redisResult.NonRedisError() + cacheHit = redisResult.IsCacheHit() - if useRateLimiter { - r := rateLimiter.ReserveN(time.Now(), int(1)) - time.Sleep(r.Delay()) + if err != nil { + if continueOnError { + if debug_level > 0 { + log.Println(fmt.Sprintf("Received an error with the following command(s): %v, error: %v", newCmdS, err)) + } + } else { + log.Fatalf("Received an error with the following command(s): %v, error: %v", newCmdS, err) + } } + duration := endT.Sub(startT) + datapointsChan <- datapoint{!(err != nil), duration.Microseconds(), cacheHit} +} + +func sendCmdLogicRadix(conn Client, newCmdS []string, enableMultiExec bool, key string, datapointsChan chan datapoint, continueOnError bool, debug_level int, waitReplicas, waitReplicasMs int) { + cmd := radix.Cmd(nil, newCmdS[0], newCmdS[1:]...) + ctx := context.Background() + cacheHit := false var err error startT := time.Now() if enableMultiExec { @@ -90,7 +154,7 @@ func sendCmdLogic(conn Client, cmd radix.Action, enableMultiExec bool, key strin } } duration := endT.Sub(startT) - datapointsChan <- datapoint{!(err != nil), duration.Microseconds()} + datapointsChan <- datapoint{!(err != nil), duration.Microseconds(), cacheHit} } func main() { @@ -113,6 +177,11 @@ func main() { betweenClientsDelay := flag.Duration("between-clients-duration", time.Millisecond*0, "Between each client creation, wait this time.") version := flag.Bool("v", false, "Output version and exit") verbose := flag.Bool("verbose", false, "Output verbose info") + cscEnabled := flag.Bool("csc", false, "Enable client side caching") + useRuedis := flag.Bool("rueidis", false, "Use rueidis as the vanilla underlying client.") + cscDuration := flag.Duration("csc-ttl", time.Minute, "Client side cache ttl for cached entries") + clientKeepAlive := flag.Duration("client-keepalive", time.Minute, "Client keepalive") + cscSizeBytes := flag.Int("csc-per-client-bytes", rueidis.DefaultCacheBytes, "client side cache size that bind to each TCP connection to a single redis instance") continueonerror := flag.Bool("continue-on-error", false, "Output verbose info") resp := flag.String("resp", "", "redis command response protocol (2 - RESP 2, 3 - RESP 3). If empty will not enforce it.") nameserver := flag.String("nameserver", "", "the IP address of the DNS name server. The IP address can be an IPv4 or an IPv6 address. If empty will use the default host namserver.") @@ -129,6 +198,7 @@ func main() { cmdRates := make([]float64, totalQueries) cmdKeyplaceHolderPos := make([]int, totalQueries) cmdDataplaceHolderPos := make([]int, totalQueries) + cmdReadOnly := make([]bool, totalQueries) git_sha := toolGitSHA1() git_dirty_str := "" if toolGitDirty() { @@ -156,6 +226,9 @@ func main() { for i := 0; i < len(cmds); i++ { cmdKeyplaceHolderPos[i], cmdDataplaceHolderPos[i] = getplaceholderpos(cmds[i], *verbose) + cmdAllCaps := strings.ToUpper(cmds[i][0]) + _, isReadOnly := readOnlyCommands[cmdAllCaps] + cmdReadOnly[i] = isReadOnly } var requestRate = Inf @@ -178,10 +251,13 @@ func main() { if *password != "" { opts.AuthPass = *password } + alwaysRESP2 := false if *resp == "2" { opts.Protocol = "2" + alwaysRESP2 = true } else if *resp == "3" { opts.Protocol = "3" + alwaysRESP2 = false } ips := make([]net.IP, 0) @@ -217,27 +293,49 @@ func main() { fmt.Printf("Using random seed: %d\n", *seed) rand.Seed(*seed) var cluster *radix.Cluster + var radixStandalone radix.Client + var ruedisClient rueidis.Client + var err error = nil datapointsChan := make(chan datapoint, *numberRequests) - for channel_id := 1; uint64(channel_id) <= *clients; channel_id++ { + for clientId := 1; uint64(clientId) <= *clients; clientId++ { wg.Add(1) connectionStr := fmt.Sprintf("%s:%d", ips[rand.Int63n(int64(len(ips)))], *port) - if *clusterMode { - cluster = getOSSClusterConn(connectionStr, opts, 1) - } if *verbose { - fmt.Printf("Using connection string %s for client %d\n", connectionStr, channel_id) + fmt.Printf("Using connection string %s for client %d\n", connectionStr, clientId) } cmd := make([]string, len(args)) copy(cmd, args) - if *clusterMode { - go benchmarkRoutine(cluster, *multi, datapointsChan, *continueonerror, cmds, cdf, *keyspacelen, *datasize, samplesPerClient, *loop, int(*debug), &wg, cmdKeyplaceHolderPos, cmdDataplaceHolderPos, useRateLimiter, rateLimiter, *waitReplicas, *waitReplicasMs) + if *cscEnabled || *useRuedis { + clientOptions := rueidis.ClientOption{ + InitAddress: []string{connectionStr}, + Password: *password, + AlwaysPipelining: false, + AlwaysRESP2: alwaysRESP2, + DisableCache: !*cscEnabled, + BlockingPoolSize: 0, + PipelineMultiplex: 0, + RingScaleEachConn: 1, + ReadBufferEachConn: 1024, + WriteBufferEachConn: 1024, + CacheSizeEachConn: *cscSizeBytes, + } + clientOptions.Dialer.KeepAlive = *clientKeepAlive + ruedisClient, err = rueidis.NewClient(clientOptions) + if err != nil { + panic(err) + } + go benchmarkRoutine(radixStandalone, ruedisClient, *useRuedis, *cscEnabled, *multi, datapointsChan, *continueonerror, cmds, cdf, *keyspacelen, *datasize, samplesPerClient, *loop, int(*debug), &wg, cmdKeyplaceHolderPos, cmdDataplaceHolderPos, cmdReadOnly, useRateLimiter, rateLimiter, *waitReplicas, *waitReplicasMs, *cscDuration) } else { - if *multi { - go benchmarkRoutine(getStandaloneConn(connectionStr, opts, 1), *multi, datapointsChan, *continueonerror, cmds, cdf, *keyspacelen, *datasize, samplesPerClient, *loop, int(*debug), &wg, cmdKeyplaceHolderPos, cmdDataplaceHolderPos, useRateLimiter, rateLimiter, *waitReplicas, *waitReplicasMs) + // legacy radix code + if *clusterMode { + cluster = getOSSClusterConn(connectionStr, opts, 1) + go benchmarkRoutine(cluster, ruedisClient, *useRuedis, *cscEnabled, *multi, datapointsChan, *continueonerror, cmds, cdf, *keyspacelen, *datasize, samplesPerClient, *loop, int(*debug), &wg, cmdKeyplaceHolderPos, cmdDataplaceHolderPos, cmdReadOnly, useRateLimiter, rateLimiter, *waitReplicas, *waitReplicasMs, *cscDuration) } else { - go benchmarkRoutine(getStandaloneConn(connectionStr, opts, 1), *multi, datapointsChan, *continueonerror, cmds, cdf, *keyspacelen, *datasize, samplesPerClient, *loop, int(*debug), &wg, cmdKeyplaceHolderPos, cmdDataplaceHolderPos, useRateLimiter, rateLimiter, *waitReplicas, *waitReplicasMs) + radixStandalone = getStandaloneConn(connectionStr, opts, 1) + go benchmarkRoutine(radixStandalone, ruedisClient, *useRuedis, *cscEnabled, *multi, datapointsChan, *continueonerror, cmds, cdf, *keyspacelen, *datasize, samplesPerClient, *loop, int(*debug), &wg, cmdKeyplaceHolderPos, cmdDataplaceHolderPos, cmdReadOnly, useRateLimiter, rateLimiter, *waitReplicas, *waitReplicasMs, *cscDuration) } } + // delay the creation for each additional client time.Sleep(*betweenClientsDelay) } @@ -278,12 +376,13 @@ func main() { func updateCLI(tick *time.Ticker, c chan os.Signal, message_limit uint64, loop bool, datapointsChan chan datapoint) (bool, time.Time, time.Duration, uint64, []float64) { var currentErr uint64 = 0 var currentCount uint64 = 0 + var currentCachedCount uint64 = 0 start := time.Now() prevTime := time.Now() prevMessageCount := uint64(0) messageRateTs := []float64{} var dp datapoint - fmt.Printf("%26s %7s %25s %25s %7s %25s %25s\n", "Test time", " ", "Total Commands", "Total Errors", "", "Command Rate", "p50 lat. (msec)") + fmt.Printf("%26s %7s %25s %25s %7s %25s %25s %7s %25s\n", "Test time", " ", "Total Commands", "Total Errors", "", "Command Rate", "Client Cache Hits", "", "p50 lat. (msec)") for { select { case dp = <-datapointsChan: @@ -292,14 +391,19 @@ func updateCLI(tick *time.Ticker, c chan os.Signal, message_limit uint64, loop b if !dp.success { currentErr++ } + if dp.cachedEntry { + currentCachedCount++ + } currentCount++ } case <-tick.C: { totalCommands += currentCount + totalCached += currentCachedCount totalErrors += currentErr currentErr = 0 currentCount = 0 + currentCachedCount = 0 now := time.Now() took := now.Sub(prevTime) messageRate := float64(totalCommands-prevMessageCount) / float64(took.Seconds()) @@ -309,6 +413,10 @@ func updateCLI(tick *time.Ticker, c chan os.Signal, message_limit uint64, loop b completionPercentStr = fmt.Sprintf("[%3.1f%%]", completionPercent) } errorPercent := float64(totalErrors) / float64(totalCommands) * 100.0 + cachedPercent := 0.0 + if totalCached > 0 { + cachedPercent = float64(totalCached) / float64(totalCommands) * 100.0 + } p50 := float64(latencies.ValueAtQuantile(50.0)) / 1000.0 @@ -321,7 +429,7 @@ func updateCLI(tick *time.Ticker, c chan os.Signal, message_limit uint64, loop b prevMessageCount = totalCommands prevTime = now - fmt.Printf("%25.0fs %s %25d %25d [%3.1f%%] %25.2f %25.2f\t", time.Since(start).Seconds(), completionPercentStr, totalCommands, totalErrors, errorPercent, messageRate, p50) + fmt.Printf("%25.0fs %s %25d %25d [%3.1f%%] %25.0f %25d [%3.1f%%] %25.3f\t", time.Since(start).Seconds(), completionPercentStr, totalCommands, totalErrors, errorPercent, messageRate, totalCached, cachedPercent, p50) fmt.Printf("\r") //w.Flush() if message_limit > 0 && totalCommands >= uint64(message_limit) && !loop { diff --git a/redis-bechmark-go_test.go b/redis-bechmark-go_test.go index 0bf72b7..78d588a 100644 --- a/redis-bechmark-go_test.go +++ b/redis-bechmark-go_test.go @@ -3,7 +3,7 @@ package main import ( "bytes" "context" - "github.com/rueian/rueidis" + "github.com/redis/rueidis" "os" "os/exec" "reflect" @@ -100,9 +100,14 @@ func TestGecko(t *testing.T) { args []string }{ {"simple run", 0, 1, 100, []string{"-p", "6379", "-c", "10", "-n", "100", "HSET", "hash:1", "field", "value"}}, + {"simple run rueidis", 0, 1, 100, []string{"-p", "6379", "-rueidis", "-c", "10", "-n", "100", "HSET", "hash:1", "field", "value"}}, + {"run with multi-exec rueidis", 0, 1, 100, []string{"-p", "6379", "-rueidis", "-multi", "-c", "10", "-n", "100", "-rps", "100", "HSET", "hash:1", "field", "value"}}, {"run with rps", 0, 1, 100, []string{"-p", "6379", "-c", "10", "-n", "100", "-rps", "100", "HSET", "hash:1", "field", "value"}}, + {"run with rps rueidis", 0, 1, 100, []string{"-p", "6379", "-rueidis", "-c", "10", "-n", "100", "-rps", "100", "HSET", "hash:1", "field", "value"}}, {"run with key placeholder", 0, 10, 1000, []string{"-p", "6379", "-c", "10", "-n", "1000", "-r", "10", "-rps", "10000", "HSET", "hash:__key__", "field", "value"}}, + {"run with key placeholder rueidis", 0, 10, 1000, []string{"-p", "6379", "-rueidis", "-c", "10", "-n", "1000", "-r", "10", "-rps", "10000", "HSET", "hash:__key__", "field", "value"}}, {"run with multiple commands", 0, 20, 1000, []string{"-p", "6379", "-c", "10", "-n", "1000", "-r", "10", "-rps", "10000", "-cmd", "HSET hash:__key__ field value", "-cmd-ratio", "0.5", "-cmd", "SET string:__key__ value", "-cmd-ratio", "0.5"}}, + {"run with multiple commands rueidis", 0, 20, 1000, []string{"-p", "6379", "-rueidis", "-c", "10", "-n", "1000", "-r", "10", "-rps", "10000", "-cmd", "HSET hash:__key__ field value", "-cmd-ratio", "0.5", "-cmd", "SET string:__key__ value", "-cmd-ratio", "0.5"}}, {"bad run", 2, 0, 0, []string{"-p", "xx"}}, } host, password := getTestConnectionDetails() diff --git a/standalone_conn.go b/standalone_conn.go index 7107cb1..38d0d2a 100644 --- a/standalone_conn.go +++ b/standalone_conn.go @@ -6,7 +6,7 @@ import ( "log" ) -func getStandaloneConn(addr string, opts radix.Dialer, clients uint64) Client { +func getStandaloneConn(addr string, opts radix.Dialer, clients uint64) radix.Client { var err error var size int = int(clients) network := "tcp" diff --git a/utils/gen_read_only_map.py b/utils/gen_read_only_map.py new file mode 100644 index 0000000..0e641d9 --- /dev/null +++ b/utils/gen_read_only_map.py @@ -0,0 +1,31 @@ +import json +import argparse +import os + +parser = argparse.ArgumentParser() +parser.add_argument("--redis-folder", default="../../redis/src/commands") +args = parser.parse_args() +directory = args.redis_folder + +go_file = """ +package main + +var readOnlyCommands = map[string]bool{ +""" + + +for command_filename in os.listdir(directory): + f = os.path.join(directory, command_filename) + # checking if it is a file + if os.path.isfile(f): + with open(f, "r") as json_fd: + command_json = json.load(json_fd) + for command_name, command_details in command_json.items(): + if "command_flags" in command_details: + command_flags = command_details["command_flags"] + if "READONLY" in command_flags and "NOSCRIPT" not in command_flags: + go_file_newline = f'"{command_name}":true,\n' + go_file += go_file_newline +go_file += "}" + +print(go_file)