diff --git a/java/jabushka/benchmarks/src/main/java/javabushka/client/AsyncClient.java b/java/jabushka/benchmarks/src/main/java/javabushka/client/AsyncClient.java new file mode 100644 index 0000000000..da06e6b234 --- /dev/null +++ b/java/jabushka/benchmarks/src/main/java/javabushka/client/AsyncClient.java @@ -0,0 +1,16 @@ +package javabushka.client; + +import java.util.concurrent.Future; + +public interface AsyncClient extends Client { + + long DEFAULT_TIMEOUT = 1000; + + Future asyncSet(String key, String value); + + Future asyncGet(String key); + + T waitForResult(Future future); + + T waitForResult(Future future, long timeout); +} diff --git a/java/jabushka/benchmarks/src/main/java/javabushka/client/BenchmarkingApp.java b/java/jabushka/benchmarks/src/main/java/javabushka/client/BenchmarkingApp.java index c0341c8da3..36fc5df715 100644 --- a/java/jabushka/benchmarks/src/main/java/javabushka/client/BenchmarkingApp.java +++ b/java/jabushka/benchmarks/src/main/java/javabushka/client/BenchmarkingApp.java @@ -3,15 +3,14 @@ import java.io.FileWriter; import java.io.IOException; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import javabushka.client.jedis.JedisClient; +import javabushka.client.jedis.JedisPseudoAsyncClient; import javabushka.client.lettuce.LettuceAsyncClient; +import javabushka.client.lettuce.LettuceClient; import javabushka.client.utils.Benchmarking; -import javabushka.client.utils.ChosenAction; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -37,26 +36,41 @@ public static void main(String[] args) { System.err.println("Parsing failed. Reason: " + exp.getMessage()); } - try { - switch (runConfiguration.clients) { + for (ClientName client : runConfiguration.clients) { + switch (client) { case ALL: - testJedisClientResourceSetGet(runConfiguration); - testLettuceClientResourceSetGet(runConfiguration); + testClientSetGet(new JedisClient(), runConfiguration); + testClientSetGet(new LettuceClient(), runConfiguration); + testAsyncClientSetGet(new JedisPseudoAsyncClient(), runConfiguration); + testAsyncClientSetGet(new LettuceAsyncClient(), runConfiguration); + System.out.println("Babushka not yet configured"); + break; + case ALL_ASYNC: + testAsyncClientSetGet(new JedisPseudoAsyncClient(), runConfiguration); + testAsyncClientSetGet(new LettuceAsyncClient(), runConfiguration); + System.out.println("Babushka not yet configured"); + break; + case ALL_SYNC: + testClientSetGet(new JedisClient(), runConfiguration); + testClientSetGet(new LettuceClient(), runConfiguration); System.out.println("Babushka not yet configured"); break; case JEDIS: - testJedisClientResourceSetGet(runConfiguration); + testClientSetGet(new JedisClient(), runConfiguration); + break; + case JEDIS_ASYNC: + testAsyncClientSetGet(new JedisPseudoAsyncClient(), runConfiguration); break; case LETTUCE: - testLettuceClientResourceSetGet(runConfiguration); + testClientSetGet(new LettuceClient(), runConfiguration); + break; + case LETTUCE_ASYNC: + testAsyncClientSetGet(new LettuceAsyncClient(), runConfiguration); break; case BABUSHKA: System.out.println("Babushka not yet configured"); break; } - } catch (IOException ioException) { - System.out.println("Error writing to results file"); - ioException.printStackTrace(); } if (runConfiguration.resultsFile.isPresent()) { @@ -123,18 +137,11 @@ private static RunConfiguration verifyOptions(CommandLine line) throws ParseExce } if (line.hasOption("clients")) { - String clients = line.getOptionValue("clients"); - if (ClientName.ALL.isEqual(clients)) { - runConfiguration.clients = ClientName.ALL; - } else if (ClientName.JEDIS.isEqual(clients)) { - runConfiguration.clients = ClientName.JEDIS; - } else if (ClientName.LETTUCE.isEqual(clients)) { - runConfiguration.clients = ClientName.LETTUCE; - } else if (ClientName.BABUSHKA.isEqual(clients)) { - runConfiguration.clients = ClientName.BABUSHKA; - } else { - throw new ParseException("Invalid clients option: all|jedis|lettuce|babushka"); - } + String[] clients = line.getOptionValue("clients").split(","); + runConfiguration.clients = + Arrays.stream(clients) + .map(c -> Enum.valueOf(ClientName.class, c.toUpperCase())) + .toArray(ClientName[]::new); } if (line.hasOption("host")) { @@ -152,66 +159,27 @@ private static RunConfiguration verifyOptions(CommandLine line) throws ParseExce return runConfiguration; } - private static void testJedisClientResourceSetGet(RunConfiguration runConfiguration) - throws IOException { - JedisClient jedisClient = new JedisClient(); - jedisClient.connectToRedis(runConfiguration.host, runConfiguration.port); - - int iterations = 100000; - String value = "my-value"; - - if (runConfiguration.resultsFile.isPresent()) { - runConfiguration.resultsFile.get().write("JEDIS client Benchmarking: "); - } else { - System.out.println("JEDIS client Benchmarking: "); - } - - Map actions = new HashMap<>(); - actions.put(ChosenAction.GET_EXISTING, () -> jedisClient.get(Benchmarking.generateKeySet())); - actions.put( - ChosenAction.GET_NON_EXISTING, () -> jedisClient.get(Benchmarking.generateKeyGet())); - actions.put(ChosenAction.SET, () -> jedisClient.set(Benchmarking.generateKeySet(), value)); - - Benchmarking.printResults( - Benchmarking.calculateResults(Benchmarking.getLatencies(iterations, actions)), - runConfiguration.resultsFile); + private static void testClientSetGet(Client client, RunConfiguration runConfiguration) { + System.out.printf("%n =====> %s <===== %n%n", client.getName()); + Benchmarking.printResults(Benchmarking.measurePerformance(client, runConfiguration, false)); + System.out.println(); } - private static LettuceAsyncClient initializeLettuceClient() { - LettuceAsyncClient lettuceClient = new LettuceAsyncClient(); - lettuceClient.connectToRedis(); - return lettuceClient; - } - - private static void testLettuceClientResourceSetGet(RunConfiguration runConfiguration) - throws IOException { - LettuceAsyncClient lettuceClient = initializeLettuceClient(); - - int iterations = 100000; - String value = "my-value"; - - if (runConfiguration.resultsFile.isPresent()) { - runConfiguration.resultsFile.get().write("LETTUCE client Benchmarking: "); - } else { - System.out.println("LETTUCE client Benchmarking: "); - } - - HashMap actions = new HashMap<>(); - actions.put(ChosenAction.GET_EXISTING, () -> lettuceClient.get(Benchmarking.generateKeySet())); - actions.put( - ChosenAction.GET_NON_EXISTING, () -> lettuceClient.get(Benchmarking.generateKeyGet())); - actions.put(ChosenAction.SET, () -> lettuceClient.set(Benchmarking.generateKeySet(), value)); - - Benchmarking.printResults( - Benchmarking.calculateResults(Benchmarking.getLatencies(iterations, actions)), - runConfiguration.resultsFile); + private static void testAsyncClientSetGet(AsyncClient client, RunConfiguration runConfiguration) { + System.out.printf("%n =====> %s <===== %n%n", client.getName()); + Benchmarking.printResults(Benchmarking.measurePerformance(client, runConfiguration, true)); + System.out.println(); } public enum ClientName { JEDIS("Jedis"), + JEDIS_ASYNC("Jedis async"), LETTUCE("Lettuce"), + LETTUCE_ASYNC("Lettuce async"), BABUSHKA("Babushka"), - ALL("All"); + ALL("All"), + ALL_SYNC("All sync"), + ALL_ASYNC("All async"); private String name; @@ -233,7 +201,7 @@ public static class RunConfiguration { public String configuration; public Optional resultsFile; public List concurrentTasks; - public ClientName clients; + public ClientName[] clients; public String host; public int port; public int clientCount; @@ -243,11 +211,11 @@ public RunConfiguration() { configuration = "Release"; resultsFile = Optional.empty(); concurrentTasks = List.of(1, 10, 100); - clients = ClientName.ALL; + clients = new ClientName[] {ClientName.ALL}; host = "localhost"; port = 6379; clientCount = 1; - tls = true; + tls = false; } } } diff --git a/java/jabushka/benchmarks/src/main/java/javabushka/client/Client.java b/java/jabushka/benchmarks/src/main/java/javabushka/client/Client.java new file mode 100644 index 0000000000..91b97b56cd --- /dev/null +++ b/java/jabushka/benchmarks/src/main/java/javabushka/client/Client.java @@ -0,0 +1,13 @@ +package javabushka.client; + +import javabushka.client.utils.ConnectionSettings; + +public interface Client { + void connectToRedis(); + + void connectToRedis(ConnectionSettings connectionSettings); + + default void closeConnection() {} + + String getName(); +} diff --git a/java/jabushka/benchmarks/src/main/java/javabushka/client/SyncClient.java b/java/jabushka/benchmarks/src/main/java/javabushka/client/SyncClient.java new file mode 100644 index 0000000000..f14bdce5e1 --- /dev/null +++ b/java/jabushka/benchmarks/src/main/java/javabushka/client/SyncClient.java @@ -0,0 +1,7 @@ +package javabushka.client; + +public interface SyncClient extends Client { + void set(String key, String value); + + String get(String key); +} diff --git a/java/jabushka/benchmarks/src/main/java/javabushka/client/jedis/JedisClient.java b/java/jabushka/benchmarks/src/main/java/javabushka/client/jedis/JedisClient.java index b3f9e3ee69..30ba659b87 100644 --- a/java/jabushka/benchmarks/src/main/java/javabushka/client/jedis/JedisClient.java +++ b/java/jabushka/benchmarks/src/main/java/javabushka/client/jedis/JedisClient.java @@ -3,27 +3,46 @@ */ package javabushka.client.jedis; +import javabushka.client.SyncClient; +import javabushka.client.utils.ConnectionSettings; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; -public class JedisClient { +public class JedisClient implements SyncClient { public static final String DEFAULT_HOST = "localhost"; public static final int DEFAULT_PORT = 6379; - Jedis jedisResource; + protected Jedis jedisResource; public boolean someLibraryMethod() { return true; } - public void connectToRedis(String host, int port) { - JedisPool pool = new JedisPool(host, port); + @Override + public void connectToRedis() { + JedisPool pool = new JedisPool(DEFAULT_HOST, DEFAULT_PORT); jedisResource = pool.getResource(); } - public void connectToRedis() { - connectToRedis(DEFAULT_HOST, DEFAULT_PORT); + @Override + public void closeConnection() { + try { + jedisResource.close(); + } catch (Exception ignored) { + } + } + + @Override + public String getName() { + return "Jedis"; + } + + @Override + public void connectToRedis(ConnectionSettings connectionSettings) { + jedisResource = + new Jedis(connectionSettings.host, connectionSettings.port, connectionSettings.useSsl); + jedisResource.connect(); } public String info() { @@ -34,10 +53,12 @@ public String info(String section) { return jedisResource.info(section); } + @Override public void set(String key, String value) { jedisResource.set(key, value); } + @Override public String get(String key) { return jedisResource.get(key); } diff --git a/java/jabushka/benchmarks/src/main/java/javabushka/client/jedis/JedisPseudoAsyncClient.java b/java/jabushka/benchmarks/src/main/java/javabushka/client/jedis/JedisPseudoAsyncClient.java new file mode 100644 index 0000000000..2f6bcd7611 --- /dev/null +++ b/java/jabushka/benchmarks/src/main/java/javabushka/client/jedis/JedisPseudoAsyncClient.java @@ -0,0 +1,39 @@ +package javabushka.client.jedis; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import javabushka.client.AsyncClient; + +// Jedis doesn't provide async API +// https://github.com/redis/jedis/issues/241 +public class JedisPseudoAsyncClient extends JedisClient implements AsyncClient { + @Override + public Future asyncSet(String key, String value) { + return CompletableFuture.runAsync(() -> super.set(key, value)); + } + + @Override + public Future asyncGet(String key) { + return CompletableFuture.supplyAsync(() -> super.get(key)); + } + + @Override + public T waitForResult(Future future) { + return waitForResult(future, DEFAULT_TIMEOUT); + } + + @Override + public T waitForResult(Future future, long timeout) { + try { + return future.get(timeout, TimeUnit.MILLISECONDS); + } catch (Exception ignored) { + return null; + } + } + + @Override + public String getName() { + return "Jedis pseudo-async"; + } +} diff --git a/java/jabushka/benchmarks/src/main/java/javabushka/client/lettuce/LettuceAsyncClient.java b/java/jabushka/benchmarks/src/main/java/javabushka/client/lettuce/LettuceAsyncClient.java index 6c0c8f5083..f93ae104d1 100644 --- a/java/jabushka/benchmarks/src/main/java/javabushka/client/lettuce/LettuceAsyncClient.java +++ b/java/jabushka/benchmarks/src/main/java/javabushka/client/lettuce/LettuceAsyncClient.java @@ -7,46 +7,67 @@ import io.lettuce.core.RedisFuture; import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.async.RedisAsyncCommands; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import javabushka.client.AsyncClient; +import javabushka.client.utils.ConnectionSettings; -public class LettuceAsyncClient { +public class LettuceAsyncClient implements AsyncClient { RedisClient client; - RedisAsyncCommands lettuceSync; + RedisAsyncCommands asyncCommands; StatefulRedisConnection connection; - public final long MAX_TIMEOUT_MS = 1000; - + @Override public void connectToRedis() { - client = RedisClient.create("redis://localhost:6379"); + connectToRedis(new ConnectionSettings("localhost", 6379, false)); + } + + @Override + public void connectToRedis(ConnectionSettings connectionSettings) { + client = + RedisClient.create( + String.format( + "%s://%s:%d", + connectionSettings.useSsl ? "rediss" : "redis", + connectionSettings.host, + connectionSettings.port)); connection = client.connect(); - lettuceSync = connection.async(); + asyncCommands = connection.async(); } - public RedisFuture set(String key, String value) { - RedisFuture future = lettuceSync.set(key, value); - return future; + @Override + public RedisFuture asyncSet(String key, String value) { + return asyncCommands.set(key, value); } - public RedisFuture get(String key) { - RedisFuture future = lettuceSync.get(key); - return future; + @Override + public RedisFuture asyncGet(String key) { + return asyncCommands.get(key); } - public Object waitForResult(RedisFuture future) - throws ExecutionException, InterruptedException, TimeoutException { - return this.waitForResult(future, MAX_TIMEOUT_MS); + @Override + public Object waitForResult(Future future) { + return waitForResult(future, DEFAULT_TIMEOUT); } - public Object waitForResult(RedisFuture future, long timeoutMS) - throws ExecutionException, InterruptedException, TimeoutException { - return future.get(timeoutMS, TimeUnit.MILLISECONDS); + @Override + public Object waitForResult(Future future, long timeoutMS) { + try { + return future.get(timeoutMS, TimeUnit.MILLISECONDS); + } catch (Exception ignored) { + return null; + } } + @Override public void closeConnection() { connection.close(); client.shutdown(); } + + @Override + public String getName() { + return "Lettuce Async"; + } } diff --git a/java/jabushka/benchmarks/src/main/java/javabushka/client/lettuce/LettuceClient.java b/java/jabushka/benchmarks/src/main/java/javabushka/client/lettuce/LettuceClient.java index c0dc6a38f0..6fa237666d 100644 --- a/java/jabushka/benchmarks/src/main/java/javabushka/client/lettuce/LettuceClient.java +++ b/java/jabushka/benchmarks/src/main/java/javabushka/client/lettuce/LettuceClient.java @@ -6,29 +6,51 @@ import io.lettuce.core.RedisClient; import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.sync.RedisStringCommands; +import javabushka.client.SyncClient; +import javabushka.client.utils.ConnectionSettings; -public class LettuceClient { +public class LettuceClient implements SyncClient { RedisClient client; - RedisStringCommands lettuceSync; + RedisStringCommands syncCommands; StatefulRedisConnection connection; + @Override public void connectToRedis() { - client = RedisClient.create("redis://localhost:6379"); + connectToRedis(new ConnectionSettings("localhost", 6379, false)); + } + + @Override + public void connectToRedis(ConnectionSettings connectionSettings) { + client = + RedisClient.create( + String.format( + "%s://%s:%d", + connectionSettings.useSsl ? "rediss" : "redis", + connectionSettings.host, + connectionSettings.port)); connection = client.connect(); - lettuceSync = connection.sync(); + syncCommands = connection.sync(); } + @Override public void set(String key, String value) { - lettuceSync.set(key, value); + syncCommands.set(key, value); } + @Override public String get(String key) { - return (String) lettuceSync.get(key); + return (String) syncCommands.get(key); } + @Override public void closeConnection() { connection.close(); client.shutdown(); } + + @Override + public String getName() { + return "Lettuce"; + } } diff --git a/java/jabushka/benchmarks/src/main/java/javabushka/client/utils/Benchmarking.java b/java/jabushka/benchmarks/src/main/java/javabushka/client/utils/Benchmarking.java index 182b062c99..9f8744720e 100644 --- a/java/jabushka/benchmarks/src/main/java/javabushka/client/utils/Benchmarking.java +++ b/java/jabushka/benchmarks/src/main/java/javabushka/client/utils/Benchmarking.java @@ -8,6 +8,10 @@ import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; +import javabushka.client.AsyncClient; +import javabushka.client.BenchmarkingApp; +import javabushka.client.Client; +import javabushka.client.SyncClient; public class Benchmarking { static final double PROB_GET = 0.8; @@ -64,7 +68,13 @@ private static void addLatency(Operation op, ArrayList latencies) { // Assumption: latencies is sorted in ascending order private static Long percentile(ArrayList latencies, int percentile) { - return latencies.get((int) Math.ceil((percentile / 100.0) * latencies.size())); + int N = latencies.size(); + double n = (N - 1) * percentile / 100. + 1; + if (n == 1d) return latencies.get(0); + else if (n == N) return latencies.get(N - 1); + int k = (int) n; + double d = n - k; + return Math.round(latencies.get(k - 1) + d * (latencies.get(k) - latencies.get(k - 1))); } private static double stdDeviation(ArrayList latencies, Double avgLatency) { @@ -138,4 +148,42 @@ public static void printResults(Map resultsMap) { System.out.println(action + " std dev in ms: " + results.stdDeviation / 1000000.0); } } + + public static Map measurePerformance( + Client client, BenchmarkingApp.RunConfiguration config, boolean async) { + client.connectToRedis(new ConnectionSettings(config.host, config.port, config.tls)); + + int iterations = 10000; + String value = "my-value"; + + if (config.resultsFile.isPresent()) { + try { + config.resultsFile.get().write(client.getName() + " client Benchmarking: "); + } catch (Exception ignored) { + } + } else { + System.out.printf("%s client Benchmarking: %n", client.getName()); + } + + Map actions = new HashMap<>(); + actions.put( + ChosenAction.GET_EXISTING, + async + ? () -> ((AsyncClient) client).asyncGet(Benchmarking.generateKeySet()) + : () -> ((SyncClient) client).get(Benchmarking.generateKeySet())); + actions.put( + ChosenAction.GET_NON_EXISTING, + async + ? () -> ((AsyncClient) client).asyncGet(Benchmarking.generateKeyGet()) + : () -> ((SyncClient) client).get(Benchmarking.generateKeyGet())); + actions.put( + ChosenAction.SET, + async + ? () -> ((AsyncClient) client).asyncSet(Benchmarking.generateKeySet(), value) + : () -> ((SyncClient) client).set(Benchmarking.generateKeySet(), value)); + + var results = Benchmarking.calculateResults(Benchmarking.getLatencies(iterations, actions)); + client.closeConnection(); + return results; + } } diff --git a/java/jabushka/benchmarks/src/main/java/javabushka/client/utils/ConnectionSettings.java b/java/jabushka/benchmarks/src/main/java/javabushka/client/utils/ConnectionSettings.java new file mode 100644 index 0000000000..725f604ce7 --- /dev/null +++ b/java/jabushka/benchmarks/src/main/java/javabushka/client/utils/ConnectionSettings.java @@ -0,0 +1,13 @@ +package javabushka.client.utils; + +public class ConnectionSettings { + public String host; + public int port; + public boolean useSsl; + + public ConnectionSettings(String host, int port, boolean useSsl) { + this.host = host; + this.port = port; + this.useSsl = useSsl; + } +} diff --git a/java/jabushka/benchmarks/src/test/java/javabushka/client/lettuce/LettuceAsyncClientIT.java b/java/jabushka/benchmarks/src/test/java/javabushka/client/lettuce/LettuceAsyncClientIT.java index ddc03c77a2..336f1f92ec 100644 --- a/java/jabushka/benchmarks/src/test/java/javabushka/client/lettuce/LettuceAsyncClientIT.java +++ b/java/jabushka/benchmarks/src/test/java/javabushka/client/lettuce/LettuceAsyncClientIT.java @@ -40,8 +40,8 @@ public void testResourceSetGet() { String otherKey = "key2"; String otherValue = "my-value-2"; - RedisFuture setResult = lettuceClient.set(key, value); - RedisFuture otherSetResult = otherLettuceClient.set(otherKey, otherValue); + RedisFuture setResult = lettuceClient.asyncSet(key, value); + RedisFuture otherSetResult = otherLettuceClient.asyncSet(otherKey, otherValue); // and wait for both clients try { @@ -55,8 +55,8 @@ public void testResourceSetGet() { fail("Can SET other redis result without Exception"); } - RedisFuture getResult = lettuceClient.get(key); - RedisFuture otherGetResult = otherLettuceClient.get(otherKey); + RedisFuture getResult = lettuceClient.asyncGet(key); + RedisFuture otherGetResult = otherLettuceClient.asyncGet(otherKey); String result = "invalid"; String otherResult = "invalid"; try {