package io.vertx.example.web.mongo;
import com.mongodb.MongoClientOptions;
import io.vertx.core.*;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.mongo.MongoClient;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.StaticHandler;
import io.vertx.ext.web.templ.JadeTemplateEngine;
import org.jongo.Jongo;
import org.jongo.MongoCollection;
import org.jongo.MongoCursor;
public class ServerSync extends AbstractVerticle {
// Convenience method so you can run it in your IDE
public static void main(String[] args) {
Vertx.vertx().deployVerticle(ServerSync.class.getName(), new DeploymentOptions().setInstances(8));
}
@Override
public void start() throws Exception {
// Create a mongo client using all defaults (connect to localhost and default port) using the database name "demo".
final MongoClient mongo = MongoClient.createShared(vertx, new JsonObject().put("db_name", "demo"));
Jongo jongo = new Jongo(new com.mongodb.MongoClient("localhost", new MongoClientOptions.Builder().connectionsPerHost(250).cursorFinalizerEnabled(false).build()).getDB("demo"));
MongoCollection usersCollection = jongo.getCollection("users");
// In order to use a JADE template we first need to create an engine
final JadeTemplateEngine jade = JadeTemplateEngine.create();
// To simplify the development of the web components we use a Router to route all HTTP requests
// to organize our code in a reusable way.
final Router router = Router.router(vertx);
// Enable the body parser to we can get the form data and json documents in out context.
router.route().handler(BodyHandler.create());
// Entry point to the application, this will render a custom JADE template.
router.get("/").handler(ctx -> {
// we define a hardcoded title for our application
ctx.put("title", "Vert.x Web");
// and now delegate to the engine to render it.
jade.render(ctx, "templates/index", res -> {
if (res.succeeded()) {
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, "text/html").end(res.result());
} else {
ctx.fail(res.cause());
}
});
});
// and now we mount the handlers in their appropriate routes
// Read all users from the mongo collection.
router.get("/users").handler(ctx -> {
vertx.executeBlocking(event -> {
// issue a find command to mongo to fetch all documents from the "users" collection
MongoCursor<JsonObject> map = usersCollection.find().map(dbObject -> new JsonObject()
.put("username", (String) dbObject.get("username"))
.put("email", (String) dbObject.get("email"))
.put("fullname", (String) dbObject.get("fullname"))
.put("location", (String) dbObject.get("location"))
.put("age", (String) dbObject.get("age"))
.put("gender", (String) dbObject.get("gender")));
event.complete(map);
}, new Handler<AsyncResult<MongoCursor<JsonObject>>>() {
@Override
public void handle(AsyncResult<MongoCursor<JsonObject>> event) {
final JsonArray json = new JsonArray();
for (JsonObject entries : event.result()) {
json.add(entries);
}
// since we are producing json we should inform the browser of the correct content type.
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json");
// encode to json string
ctx.response().end(json.encode());
}
});
});
// Create a new document on mongo.
router.post("/users").handler(ctx -> {
// since jquery is sending data in multipart-form format to avoid preflight calls, we need to convert it to JSON.
JsonObject user = new JsonObject()
.put("username", ctx.request().getFormAttribute("username"))
.put("email", ctx.request().getFormAttribute("email"))
.put("fullname", ctx.request().getFormAttribute("fullname"))
.put("location", ctx.request().getFormAttribute("location"))
.put("age", ctx.request().getFormAttribute("age"))
.put("gender", ctx.request().getFormAttribute("gender"));
// insert into mongo
mongo.insert("users", user, lookup -> {
// error handling
if (lookup.failed()) {
ctx.fail(lookup.cause());
return;
}
// inform that the document was created
ctx.response().setStatusCode(201);
ctx.response().end();
});
});
// Remove a document from mongo.
router.delete("/users/:id").handler(ctx -> {
// catch the id to remove from the url /users/:id and transform it to a mongo query.
mongo.removeOne("users", new JsonObject().put("_id", ctx.request().getParam("id")), lookup -> {
// error handling
if (lookup.failed()) {
ctx.fail(lookup.cause());
return;
}
// inform the browser that there is nothing to return.
ctx.response().setStatusCode(204);
ctx.response().end();
});
});
// Serve the non private static pages
router.route().handler(StaticHandler.create());
// start a HTTP web server on port 8080
vertx.createHttpServer().requestHandler(router::accept).listen(8080);
}
}
When I play some benchmarks on the url localhost:8080/users for both Servers by default with 1 instance, I have far better results with the async driver. However, when I play the benchmarks with setInstances(8) on the verticle deployment in order to use all my laptop cores, I have basically the same results for both drivers, and even a bit better results for the sync one. And I can still observe that we are using more more connections with the async driver (101 connections) whereas the sync driver is doing the job with few connections only (17 connections). And I am testing with just 2 users inside my mongo users collection. It is a bit surprising that we are using so many connections for so light requests with the async driver.
So, I am a bit worry that if I would test with more heavy requests (with 1000 users for example or in a real life project), the use of so many connections could be the reason of the deterioration of results. What do you think ?
wrk -t16 -c1000 -d30s http://localhost:8080/users
Running 30s test @ http://localhost:8080/users
16 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 71.06ms 26.88ms 468.36ms 74.78%
Req/Sec 780.30 194.56 1.84k 80.17%
364887 requests in 30.10s, 120.05MB read
Socket errors: connect 0, read 975, write 0, timeout 0
Requests/sec: 12123.77
Transfer/sec: 3.99MB
wrk -t16 -c1000 -d30s http://localhost:8080/users
Running 30s test @ http://localhost:8080/users
16 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 59.28ms 15.91ms 265.25ms 73.07%
Req/Sec 0.87k 248.13 1.71k 78.64%
416229 requests in 30.09s, 110.75MB read
Socket errors: connect 0, read 1011, write 0, timeout 0
Requests/sec: 13833.76
Transfer/sec: 3.68MB