diff --git a/Makefile b/Makefile index bded315..5fbf4d4 100644 --- a/Makefile +++ b/Makefile @@ -27,3 +27,5 @@ test-win: cover: go tool cover -html=./coverage/coverage.out +benchmark: + go run redis_benchmark.go $(if $(commands),-commands="$(commands)") $(if $(use_local_server),-use_local_server) diff --git a/README.md b/README.md index 488c97b..5d0d33e 100644 --- a/README.md +++ b/README.md @@ -155,31 +155,17 @@ Redis clients. # Benchmarks -The following benchmark only applies to the TCP client-server mode. +To compare command performance with Redis, benchmarks can be run with: -Hardware: MacBook Pro 14in, M1 chip, 16GB RAM, 8 Cores
-Command: `redis-benchmark -h localhost -p 7480 -q -t ping,set,get,incr,lpush,rpush,lpop,rpop,sadd,hset,zpopmin,lrange,mset`
-Result: -``` -PING_INLINE: 89285.71 requests per second, p50=0.247 msec -PING_MBULK: 85543.20 requests per second, p50=0.239 msec -SET: 65573.77 requests per second, p50=0.455 msec -GET: 79176.56 requests per second, p50=0.295 msec -INCR: 68870.52 requests per second, p50=0.439 msec -LPUSH: 27601.44 requests per second, p50=1.567 msec -RPUSH: 61842.92 requests per second, p50=0.519 msec -LPOP: 58548.01 requests per second, p50=0.567 msec -RPOP: 68681.32 requests per second, p50=0.439 msec -SADD: 67613.25 requests per second, p50=0.479 msec -HSET: 56561.09 requests per second, p50=0.599 msec -ZPOPMIN: 70972.32 requests per second, p50=0.359 msec -LPUSH (needed to benchmark LRANGE): 26434.05 requests per second, p50=1.623 msec -LRANGE_100 (first 100 elements): 26939.66 requests per second, p50=1.263 msec -LRANGE_300 (first 300 elements): 5081.82 requests per second, p50=9.095 msec -LRANGE_500 (first 500 elements): 2554.87 requests per second, p50=18.191 msec -LRANGE_600 (first 600 elements): 1903.96 requests per second, p50=24.607 msec -MSET (10 keys): 56022.41 requests per second, p50=0.463 msec -``` +`make benchmark` + +Prerequisites: +- `brew install redis` to run the Redis server and benchmark script +- `brew tap echovault/sugardb` & `brew install echovault/echovault/sugardb` to run the SugarDB Client-Server + +Benchmark script options: +- `make benchmark use_local_server=true` runs on your local SugarDB Client-Server +- `make benchmark commands=ping,set,get...` runs the benchmark script on the specified commands # Supported Commands diff --git a/docs/docs/intro.md b/docs/docs/intro.md index 7f5373c..69f76d7 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -70,8 +70,8 @@ if err != nil { ### Homebrew To install via homebrew, run: -1) `brew tap echovault/echovault` -2) `brew install echovault/echovault/echovault` +1) `brew tap echovault/sugardb` +2) `brew install echovault/echovault/sugardb` Once installed, you can run the server with the following command: `echovault --bind-addr=localhost --data-dir="path/to/persistence/directory"` diff --git a/redis_benchmark.go b/redis_benchmark.go new file mode 100644 index 0000000..4454f07 --- /dev/null +++ b/redis_benchmark.go @@ -0,0 +1,141 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/exec" + "strings" + "text/tabwriter" + "time" +) + +const ( + Host = "localhost" + SugarDBPort = "7480" + RedisPort = "6379" +) + +type Metrics struct { + CommandName string + RequestsPerSecond string + P50Latency string +} + +func getCommandArgs() (string, bool) { + defaultCommands := "ping,set,get,incr,lpush,rpush,lpop,rpop,sadd,hset,zpopmin,lrange,mset" + commands := flag.String("commands", defaultCommands, "Commands to run") + useLocal := flag.Bool("use_local_server", false, "Run benchamark using local SugarDB server") + flag.Parse() + fmt.Printf("Provided commands: %s\n", *commands) + if *useLocal { + fmt.Println("Using local running SugarDB server") + } + return *commands, *useLocal +} + +func runBenchmark(port string, commands string) ([]Metrics, error) { + var results []Metrics + + // Run redis-benchmark + cmd := exec.Command("redis-benchmark", "-h", Host, "-p", port, "-q", "-t", commands) + output, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + + strOutput := string(output) + fmt.Println(strOutput) + lines := strings.Split(strOutput, "\n") + for _, line := range lines { + if !strings.HasPrefix(line, "WARNING") && line != "" { + // Get command name + colonIndex := strings.Index(line, ":") + commandName := line[:colonIndex] + + // Get requests per second + reqSecIndex := strings.Index(line, " requests per second") + spaceIndex := strings.LastIndex(line[:reqSecIndex], " ") + requestsPerSecond := line[spaceIndex+1 : reqSecIndex] + + // Get p50 latency + p50Index := strings.Index(line, "p50=") + spaceAfterP50 := strings.Index(line[p50Index:], " ") + p50Latency := line[p50Index+4 : p50Index+spaceAfterP50] + + results = append(results, Metrics{ + CommandName: commandName, + RequestsPerSecond: requestsPerSecond, + P50Latency: p50Latency, + }) + } + } + + return results, nil +} + +func createDisplayTable(redisResults []Metrics, sugarDBResults []Metrics) { + if len(sugarDBResults) != len(redisResults) { + fmt.Println("Error: Number of commands in Redis and SugarDB do not match") + } + + fmt.Println("Benchmark Performance Results:") + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + fmt.Fprint(w, "Command\tRedis (req/sec)\tRedis p50 Latency (msec)\tSugarDB (req/sec)\tSugarDB p50 Latency (msec)\t\n") + for i := 0; i < len(redisResults); i++ { + command := redisResults[i].CommandName + redisReqSec := redisResults[i].RequestsPerSecond + redisLatency := redisResults[i].P50Latency + sugarDBReqSec := sugarDBResults[i].RequestsPerSecond + sugarDBLatency := sugarDBResults[i].P50Latency + + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t\n", command, redisReqSec, redisLatency, sugarDBReqSec, sugarDBLatency) + } + w.Flush() +} + +func main() { + + commands, useLocal := getCommandArgs() + + // Start a local Redis server, wait a few seconds for it to start + exec.Command("redis-server", "--port", RedisPort).Start() + time.Sleep(2 * time.Second) + + // Run benchmark on local Redis server + fmt.Println("-------Running Redis Benchmarks------") + redisResults, err := runBenchmark(RedisPort, commands) + if err != nil { + fmt.Println("Error running benchmark on Redis server:", err) + return + } + + if !useLocal { + // Run the packaged SugarDB server, wait a few seconds for it to start + exec.Command("echovault", "--bind-addr=localhost", "--data-dir=persistence").Start() + time.Sleep(5 * time.Second) + } + + // Run benchmark on SugarDB server + fmt.Println("-------Running SugarDB Benchmarks------") + sugarDBResults, err := runBenchmark(SugarDBPort, commands) + if err != nil { + fmt.Println("Error running benchmark on SugarDB server:", err) + fmt.Println("Check that the SugarDB server is running") + return + } + + // Display results in a table format + createDisplayTable(redisResults, sugarDBResults) + + // Kill the local Redis server + exec.Command("pkill", "-f", "redis-server").Run() + + if !useLocal { + // Kill the packaged SugarDB server + exec.Command("pkill", "-f", "echovault").Run() + if err := os.RemoveAll("persistence"); err != nil { // Remove persistence directory + fmt.Println("Error removing persistence directory:", err) + } + } +}