Browse Source

Undertow tuning (#2748)

* Undertow: remove unproven minimum size settings for connection pools

These settings don't seem to affect the final results.

* Undertow: remove two useless mysql connection string settings

We only have a few prepared statements and they're all short, so we can
use the default values for prepStmtCacheSize and prepStmtCacheSqlLimit.

* Undertow: adjust connection pool sizes for best ServerCentral results

I varied the SQL connection pool sizes at pretty small increments.  48
was consistently better than 32, 40, 56, and 64.  All of those were much,
much better than 256.  I'm not sure why other frameworks get better
performance out of bigger connection pools.

I didn't try as small increments for MongoDB.  256 was better than 128
and 512.  I didn't bother to narrow it down further.

* Undertow: avoid streams in mongo async handlers for simplicity

These handlers are already difficult to understand because of
CompletableFuture mechanics.  Piling streams and collectors on top of
that does not help.
Michael Hixson 8 years ago
parent
commit
644ffa7a3b

+ 0 - 3
frameworks/Java/undertow/src/main/java/hello/HelloWebServer.java

@@ -166,7 +166,6 @@ public final class HelloWebServer {
       config.setJdbcUrl(jdbcUrl);
       config.setUsername(username);
       config.setPassword(password);
-      config.setMinimumIdle(connections);
       config.setMaximumPoolSize(connections);
       return new HikariDataSource(config);
     }
@@ -178,7 +177,6 @@ public final class HelloWebServer {
                                           String databaseName,
                                           int connections) {
       MongoClientOptions.Builder options = MongoClientOptions.builder();
-      options.minConnectionsPerHost(connections);
       options.connectionsPerHost(connections);
       options.threadsAllowedToBlockForConnectionMultiplier(
           (int) Math.ceil((double) MAX_DB_REQUEST_CONCURRENCY / connections));
@@ -203,7 +201,6 @@ public final class HelloWebServer {
       ConnectionPoolSettings connectionPoolSettings =
           ConnectionPoolSettings
               .builder()
-              .minSize(connections)
               .maxSize(connections)
               .maxWaitQueueSize(
                   MAX_DB_REQUEST_CONCURRENCY * MAX_DB_QUERIES_PER_REQUEST)

+ 0 - 39
frameworks/Java/undertow/src/main/java/hello/Helper.java

@@ -10,15 +10,8 @@ import io.undertow.server.HttpServerExchange;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.Deque;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ThreadLocalRandom;
-import java.util.stream.Collector;
-import java.util.stream.Collectors;
 import org.bson.Document;
 
 /**
@@ -143,36 +136,4 @@ final class Helper {
   private static int mongoGetInt(Document document, String key) {
     return ((Number) document.get(key)).intValue();
   }
-
-  /**
-   * Transforms a stream of futures ({@code Stream<CompletableFuture<T>>}) into
-   * a single future ({@code CompletableFuture<List<T>>}) containing all the
-   * values of the input futures.
-   */
-  static <T> Collector<
-      CompletableFuture<? extends T>,
-      ?,
-      CompletableFuture<List<T>>>
-  toCompletableFuture() {
-    return Collectors.collectingAndThen(
-        Collectors.toList(),
-        Helper::futureValuesOf);
-  }
-
-  private static <T> CompletableFuture<List<T>> futureValuesOf(
-      Collection<CompletableFuture<? extends T>> futures) {
-    CompletableFuture<?>[] futuresArray =
-        futures.toArray(new CompletableFuture<?>[0]);
-    CompletableFuture<Void> allComplete = CompletableFuture.allOf(futuresArray);
-    return allComplete.thenApply(
-        nil -> {
-          List<T> values = new ArrayList<>(futuresArray.length);
-          for (CompletableFuture<?> future : futuresArray) {
-            @SuppressWarnings("unchecked")
-            T value = (T) future.join();
-            values.add(value);
-          }
-          return Collections.unmodifiableList(values);
-        });
-  }
 }

+ 37 - 25
frameworks/Java/undertow/src/main/java/hello/QueriesMongoAsyncHandler.java

@@ -4,7 +4,6 @@ import static hello.Helper.getQueries;
 import static hello.Helper.randomWorld;
 import static hello.Helper.sendException;
 import static hello.Helper.sendJson;
-import static hello.Helper.toCompletableFuture;
 
 import com.mongodb.async.client.MongoCollection;
 import com.mongodb.async.client.MongoDatabase;
@@ -12,7 +11,6 @@ import com.mongodb.client.model.Filters;
 import io.undertow.server.HttpHandler;
 import io.undertow.server.HttpServerExchange;
 import java.util.concurrent.CompletableFuture;
-import java.util.stream.IntStream;
 import org.bson.Document;
 
 /**
@@ -27,32 +25,46 @@ final class QueriesMongoAsyncHandler implements HttpHandler {
 
   @Override
   public void handleRequest(HttpServerExchange exchange) {
-    IntStream
-        .range(0, getQueries(exchange))
-        .mapToObj(
-            i -> {
-              CompletableFuture<World> future = new CompletableFuture<>();
-              worldCollection
-                  .find(Filters.eq(randomWorld()))
-                  .map(Helper::mongoDocumentToWorld)
-                  .first(
-                      (world, exception) -> {
-                        if (exception != null) {
-                          future.completeExceptionally(exception);
-                        } else {
-                          future.complete(world);
-                        }
-                      });
-              return future;
-            })
-        .collect(toCompletableFuture())
-        .whenComplete(
-            (worlds, exception) -> {
+    int queries = getQueries(exchange);
+    nWorlds(queries).whenComplete(
+        (worlds, exception) -> {
+          if (exception != null) {
+            sendException(exchange, exception);
+          } else {
+            sendJson(exchange, worlds);
+          }
+        });
+  }
+
+  private CompletableFuture<World[]> nWorlds(int n) {
+    @SuppressWarnings("unchecked")
+    CompletableFuture<World>[] futures = new CompletableFuture[n];
+    for (int i = 0; i < futures.length; i++) {
+      futures[i] = oneWorld();
+    }
+    return CompletableFuture.allOf(futures).thenApply(
+        nil -> {
+          World[] worlds = new World[futures.length];
+          for (int i = 0; i < futures.length; i++) {
+            worlds[i] = futures[i].join();
+          }
+          return worlds;
+        });
+  }
+
+  private CompletableFuture<World> oneWorld() {
+    CompletableFuture<World> future = new CompletableFuture<>();
+    worldCollection
+        .find(Filters.eq(randomWorld()))
+        .map(Helper::mongoDocumentToWorld)
+        .first(
+            (world, exception) -> {
               if (exception != null) {
-                sendException(exchange, exception);
+                future.completeExceptionally(exception);
               } else {
-                sendJson(exchange, worlds);
+                future.complete(world);
               }
             });
+    return future;
   }
 }

+ 61 - 46
frameworks/Java/undertow/src/main/java/hello/UpdatesMongoAsyncHandler.java

@@ -4,7 +4,6 @@ import static hello.Helper.getQueries;
 import static hello.Helper.randomWorld;
 import static hello.Helper.sendException;
 import static hello.Helper.sendJson;
-import static hello.Helper.toCompletableFuture;
 
 import com.mongodb.async.client.MongoCollection;
 import com.mongodb.async.client.MongoDatabase;
@@ -17,7 +16,6 @@ import io.undertow.server.HttpServerExchange;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
-import java.util.stream.IntStream;
 import org.bson.Document;
 import org.bson.conversions.Bson;
 
@@ -33,53 +31,70 @@ final class UpdatesMongoAsyncHandler implements HttpHandler {
 
   @Override
   public void handleRequest(HttpServerExchange exchange) {
-    IntStream
-        .range(0, getQueries(exchange))
-        .mapToObj(
-            i -> {
-              CompletableFuture<World> future = new CompletableFuture<>();
-              worldCollection
-                  .find(Filters.eq(randomWorld()))
-                  .map(Helper::mongoDocumentToWorld)
-                  .first(
-                      (world, exception) -> {
-                        if (exception != null) {
-                          future.completeExceptionally(exception);
-                        } else {
-                          future.complete(world);
-                        }
-                      });
-              return future;
-            })
-        .collect(toCompletableFuture())
-        .thenCompose(
-            worlds -> {
-              List<WriteModel<Document>> writes = new ArrayList<>(worlds.size());
-              for (World world : worlds) {
-                world.randomNumber = randomWorld();
-                Bson filter = Filters.eq(world.id);
-                Bson update = Updates.set("randomNumber", world.randomNumber);
-                writes.add(new UpdateOneModel<>(filter, update));
-              }
-              CompletableFuture<List<World>> next = new CompletableFuture<>();
-              worldCollection.bulkWrite(
-                  writes,
-                  (result, exception) -> {
-                    if (exception != null) {
-                      next.completeExceptionally(exception);
-                    } else {
-                      next.complete(worlds);
-                    }
-                  });
-              return next;
-            })
-        .whenComplete(
-            (worlds, exception) -> {
+    int queries = getQueries(exchange);
+    nUpdatedWorlds(queries).whenComplete(
+        (worlds, exception) -> {
+          if (exception != null) {
+            sendException(exchange, exception);
+          } else {
+            sendJson(exchange, worlds);
+          }
+        });
+  }
+
+  private CompletableFuture<World[]> nUpdatedWorlds(int n) {
+    return nWorlds(n).thenCompose(
+        worlds -> {
+          List<WriteModel<Document>> writes = new ArrayList<>(worlds.length);
+          for (World world : worlds) {
+            world.randomNumber = randomWorld();
+            Bson filter = Filters.eq(world.id);
+            Bson update = Updates.set("randomNumber", world.randomNumber);
+            writes.add(new UpdateOneModel<>(filter, update));
+          }
+          CompletableFuture<World[]> next = new CompletableFuture<>();
+          worldCollection.bulkWrite(
+              writes,
+              (result, exception) -> {
+                if (exception != null) {
+                  next.completeExceptionally(exception);
+                } else {
+                  next.complete(worlds);
+                }
+              });
+          return next;
+        });
+  }
+
+  private CompletableFuture<World[]> nWorlds(int n) {
+    @SuppressWarnings("unchecked")
+    CompletableFuture<World>[] futures = new CompletableFuture[n];
+    for (int i = 0; i < futures.length; i++) {
+      futures[i] = oneWorld();
+    }
+    return CompletableFuture.allOf(futures).thenApply(
+        nil -> {
+          World[] worlds = new World[futures.length];
+          for (int i = 0; i < futures.length; i++) {
+            worlds[i] = futures[i].join();
+          }
+          return worlds;
+        });
+  }
+
+  private CompletableFuture<World> oneWorld() {
+    CompletableFuture<World> future = new CompletableFuture<>();
+    worldCollection
+        .find(Filters.eq(randomWorld()))
+        .map(Helper::mongoDocumentToWorld)
+        .first(
+            (world, exception) -> {
               if (exception != null) {
-                sendException(exchange, exception);
+                future.completeExceptionally(exception);
               } else {
-                sendJson(exchange, worlds);
+                future.complete(world);
               }
             });
+    return future;
   }
 }

+ 4 - 6
frameworks/Java/undertow/src/main/resources/hello/server.properties

@@ -1,18 +1,16 @@
 undertow.port = 8080
 undertow.host = 0.0.0.0
 
-# We use the MySQL configuration recommended in HikariCP's documentation:
-# https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration
-mysql.jdbcUrl = jdbc:mysql://TFB-database:3306/hello_world?useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=250&prepStmtCacheSqlLimit=2048
+mysql.jdbcUrl = jdbc:mysql://TFB-database:3306/hello_world?useServerPrepStmts=true&cachePrepStmts=true
 mysql.username = benchmarkdbuser
 mysql.password = benchmarkdbpass
-mysql.connections = 32
+mysql.connections = 48
 
 postgresql.jdbcUrl = jdbc:postgresql://TFB-database:5432/hello_world
 postgresql.username = benchmarkdbuser
 postgresql.password = benchmarkdbpass
-postgresql.connections = 32
+postgresql.connections = 48
 
 mongodb.host = TFB-database:27017
 mongodb.databaseName = hello_world
-mongodb.connections = 32
+mongodb.connections = 256