package com.lambdaworks.redis; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.junit.*; import com.lambdaworks.redis.api.StatefulRedisConnection; import com.lambdaworks.redis.api.rx.RedisReactiveCommands; import rx.Observable; /** * @author Mark Paluch */ @Ignore public class LettucePerformanceTest { private static RedisClient redisClient = new RedisClient(TestSettings.host(), TestSettings.port()); private ExecutorService executor; private CountDownLatch latch = new CountDownLatch(1); @Before public void before() throws Exception { LoggerContext ctx = (LoggerContext) LogManager.getContext(); Configuration config = ctx.getConfiguration(); config.getLoggerConfig("com.lambdaworks.redis").setLevel(Level.OFF); config.getLoggerConfig("com.lambdaworks.redis.protocol").setLevel(Level.OFF); } @After public void after() throws Exception { LoggerContext ctx = (LoggerContext) LogManager.getContext(); ctx.reconfigure(); executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES); } @AfterClass public static void afterClass() throws Exception { redisClient.shutdown(); } /** * Multi-threaded performance test. * * Uses a {@link ThreadPoolExecutor} with thread and connection preheating. Execution tasks are submitted and synchronized * with a {@link CountDownLatch} * * @throws Exception */ @Test public void testSyncAsyncPerformance() throws Exception { // TWEAK ME int threads = 4; int totalCalls = 250000; boolean waitForFutureCompletion = true; boolean connectionPerThread = false; // Keep in mind, that the size of the event loop threads is CPU count * 4 unless you // set -Dio.netty.eventLoopThreads=... // END OF TWEAK ME executor = new ThreadPoolExecutor(threads, threads, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(totalCalls)); List<Future<List<CompletableFuture<String>>>> futurama = new ArrayList<>(); preheat(threads); final int callsPerThread = totalCalls / threads; submitExecutionTasks(threads, futurama, callsPerThread, connectionPerThread); Thread.sleep(800); long start = System.currentTimeMillis(); latch.countDown(); for (Future<List<CompletableFuture<String>>> listFuture : futurama) { for (CompletableFuture<String> future : listFuture.get()) { if (waitForFutureCompletion) { future.get(); } } } long end = System.currentTimeMillis(); long duration = end - start; double durationSeconds = duration / 1000d; double opsPerSecond = totalCalls / durationSeconds; System.out.println(String.format("Sync/Async: Duration: %d ms (%.2f sec), operations: %d, %.2f ops/sec ", duration, durationSeconds, totalCalls, opsPerSecond)); for (Future<List<CompletableFuture<String>>> listFuture : futurama) { for (CompletableFuture<String> future : listFuture.get()) { future.get(); } } } protected void submitExecutionTasks(int threads, List<Future<List<CompletableFuture<String>>>> futurama, final int callsPerThread, final boolean connectionPerThread) { final RedisAsyncConnection<String, String> sharedConnection; if (!connectionPerThread) { sharedConnection = redisClient.connectAsync(); } else { sharedConnection = null; } for (int i = 0; i < threads; i++) { Future<List<CompletableFuture<String>>> submit = executor.submit(() -> { RedisAsyncConnection<String, String> connection = sharedConnection; if (connectionPerThread) { connection = redisClient.connectAsync(); } connection.ping().get(); List<CompletableFuture<String>> futures = new ArrayList<>(callsPerThread); latch.await(); for (int i1 = 0; i1 < callsPerThread; i1++) { futures.add(connection.ping().toCompletableFuture()); } return futures; }); futurama.add(submit); } } /** * Multi-threaded performance using reactive commands. * * Uses a {@link ThreadPoolExecutor} with thread and connection preheating. Execution tasks are submitted and synchronized * with a {@link CountDownLatch} * * @throws Exception */ @Test public void testObservablePerformance() throws Exception { // TWEAK ME int threads = 4; int totalCalls = 25000; boolean waitForCompletion = true; boolean connectionPerThread = false; // Keep in mind, that the size of the event loop threads is CPU count * 4 unless you // set -Dio.netty.eventLoopThreads=... // END OF TWEAK ME executor = new ThreadPoolExecutor(threads, threads, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(totalCalls)); List<Future<List<Observable<String>>>> futurama = new ArrayList<>(); preheat(threads); final int callsPerThread = totalCalls / threads; submitObservableTasks(threads, futurama, callsPerThread, connectionPerThread); Thread.sleep(800); long start = System.currentTimeMillis(); latch.countDown(); for (Future<List<Observable<String>>> listFuture : futurama) { for (Observable<String> future : listFuture.get()) { if (waitForCompletion) { future.toBlocking().last(); } else { future.subscribe(); } } } long end = System.currentTimeMillis(); long duration = end - start; double durationSeconds = duration / 1000d; double opsPerSecond = totalCalls / durationSeconds; System.out.println(String.format("Reactive Duration: %d ms (%.2f sec), operations: %d, %.2f ops/sec ", duration, durationSeconds, totalCalls, opsPerSecond)); } protected void submitObservableTasks(int threads, List<Future<List<Observable<String>>>> futurama, final int callsPerThread, final boolean connectionPerThread) { final StatefulRedisConnection<String, String> sharedConnection; if (!connectionPerThread) { sharedConnection = redisClient.connectAsync().getStatefulConnection(); } else { sharedConnection = null; } for (int i = 0; i < threads; i++) { Future<List<Observable<String>>> submit = executor.submit(() -> { StatefulRedisConnection<String, String> connection = sharedConnection; if (connectionPerThread) { connection = redisClient.connectAsync().getStatefulConnection(); } RedisReactiveCommands<String, String> reactive = connection.reactive(); connection.sync().ping(); List<Observable<String>> observables = new ArrayList<>(callsPerThread); latch.await(); for (int i1 = 0; i1 < callsPerThread; i1++) { observables.add(reactive.ping()); } return observables; }); futurama.add(submit); } } protected void preheat(int threads) throws Exception { List<Future<?>> futures = new ArrayList<>(); for (int i = 0; i < threads; i++) { futures.add(executor.submit(new Runnable() { @Override public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } })); } for (Future<?> future : futures) { future.get(); } } }