Skip to content

Commit

Permalink
Play2: Use thread-pool-executor for all database benchmarks
Browse files Browse the repository at this point in the history
  • Loading branch information
mkurz committed Mar 18, 2019
1 parent 22e3c0c commit 0fcdb35
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 165 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,41 @@
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ThreadLocalRandom;

import javax.inject.Inject;

import models.Fortune;
import models.World;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Result;
import utils.DatabaseExecutionContext;

public class Application extends Controller {

private final DatabaseExecutionContext dbEc;

@Inject
public Application(final DatabaseExecutionContext dbEc) {
this.dbEc = dbEc;
}

public CompletionStage<Result> db() {
return getRandomWorlds(1).thenApply(worlds -> ok(Json.toJson(worlds.get(0))));
public Result db() {
return ok(Json.toJson(getRandomWorlds(1).get(0)));
}

public CompletionStage<Result> queries(final String queries) {
return getRandomWorlds(queryCount(queries)).thenApply(worlds -> ok(Json.toJson(worlds)));
public Result queries(final String queries) {
return ok(Json.toJson(getRandomWorlds(queryCount(queries))));
}

public CompletionStage<Result> fortunes() {
return CompletableFuture.supplyAsync(() -> {
final List<Fortune> fortunes = Fortune.findAll();
fortunes.add(new Fortune("Additional fortune added at request time."));
Collections.sort(fortunes, (f1, f2) -> f1.message.compareTo(f2.message));
public Result fortunes() {
final List<Fortune> fortunes = Fortune.findAll();
fortunes.add(new Fortune("Additional fortune added at request time."));
Collections.sort(fortunes, (f1, f2) -> f1.message.compareTo(f2.message));

return ok(views.html.fortunes.render(fortunes));
}, dbEc);
return ok(views.html.fortunes.render(fortunes));
}

public CompletionStage<Result> update(final String queries) {
return getRandomWorlds(queryCount(queries)).thenApplyAsync(worlds -> {
final Random random = ThreadLocalRandom.current();
for (final World world : worlds) {
world.randomNumber = (long) (random.nextInt(10000) + 1);
}
public Result update(final String queries) {
final List<World> worlds = getRandomWorlds(queryCount(queries));
final Random random = ThreadLocalRandom.current();
for (final World world : worlds) {
world.randomNumber = (long) (random.nextInt(10000) + 1);
}

final List<World> updatedWorlds = World.save(worlds);
return ok(Json.toJson(updatedWorlds));
}, dbEc);
final List<World> updatedWorlds = World.save(worlds);
return ok(Json.toJson(updatedWorlds));
}

private int queryCount(final String queryCountString) {
Expand All @@ -72,17 +57,15 @@ private int queryCount(final String queryCountString) {
return queryCount;
}

private CompletionStage<List<World>> getRandomWorlds(final int n) {
return CompletableFuture.supplyAsync(() -> {
final Random random = ThreadLocalRandom.current();
final List<World> worlds = new ArrayList<>(n);
for (int i = 0; i < n; ++i) {
long randomId = random.nextInt(10000) + 1;
final World world = World.find(randomId);
worlds.add(world);
}
return worlds;
}, dbEc);
private List<World> getRandomWorlds(final int n) {
final Random random = ThreadLocalRandom.current();
final List<World> worlds = new ArrayList<>(n);
for (int i = 0; i < n; ++i) {
long randomId = random.nextInt(10000) + 1;
final World world = World.find(randomId);
worlds.add(world);
}
return worlds;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ play.server {
akka {
actor {
default-dispatcher {
executor = "thread-pool-executor"

fork-join-executor {
# one thread per core is enough
# https://github.com/playframework/playframework/issues/7242#issuecomment-295215448
Expand All @@ -63,11 +65,16 @@ akka {
task-peeking-mode="LIFO" # based on https://www.playframework.com/documentation/2.7.x/Migration24#Thread-pool-configuration
}

# https://www.playframework.com/documentation/2.7.x/ThreadPools#Highly-synchronous
thread-pool-executor {
fixed-pool-size = 44 # db conn pool (29) + number of cores (14) + housekeeping (1)
}

# https://doc.akka.io/docs/akka/2.5.11/dispatchers.html#looking-up-a-dispatcher
# Throughput defines the maximum number of messages to be
# processed per actor before the thread jumps to the next actor.
# Set to 1 for as fair as possible.
throughput = 64
throughput = 1
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ThreadLocalRandom;

import javax.inject.Inject;
Expand All @@ -25,61 +23,56 @@
import play.db.Database;
import play.mvc.Controller;
import play.mvc.Result;
import utils.DatabaseExecutionContext;

public class Application extends Controller {

private static final SQLDialect DIALECT = SQLDialect.MYSQL_5_7;
private static final JSONFormat JSON_FORMAT = JSONFormat.DEFAULT_FOR_RECORDS.recordFormat(RecordFormat.OBJECT);

private final Database db;
private final DatabaseExecutionContext dbEc;

@Inject
public Application(final Database db, final DatabaseExecutionContext dbEc) {
public Application(final Database db) {
this.db = db;
this.dbEc = dbEc;
}

public CompletionStage<Result> db() {
return getRandomWorlds(1).thenApply(worlds -> ok(worlds.get(0).formatJSON(JSON_FORMAT)).as(JSON));
public Result db() {
return ok(getRandomWorlds(1).get(0).formatJSON(JSON_FORMAT)).as(JSON);
}

public CompletionStage<Result> queries(final String queries) {
return getRandomWorlds(queryCount(queries)).thenApply(worlds -> ok(worlds.formatJSON(JSON_FORMAT)).as(JSON));
public Result queries(final String queries) {
return ok(getRandomWorlds(queryCount(queries)).formatJSON(JSON_FORMAT)).as(JSON);
}

public CompletionStage<Result> fortunes() {
return CompletableFuture.supplyAsync(() -> {
final List<FortuneRecord> fortunes = this.db.withConnection(connection -> {
return DSL.using(connection, DIALECT).select(FORTUNE.ID, FORTUNE.MESSAGE).from(FORTUNE).fetchInto(FortuneRecord.class);
});
fortunes.add(new FortuneRecord(UInteger.valueOf(0), "Additional fortune added at request time."));
Collections.sort(fortunes, (f1, f2) -> f1.getMessage().compareTo(f2.getMessage()));
public Result fortunes() {
final List<FortuneRecord> fortunes = this.db.withConnection(connection -> {
return DSL.using(connection, DIALECT).select(FORTUNE.ID, FORTUNE.MESSAGE).from(FORTUNE).fetchInto(FortuneRecord.class);
});
fortunes.add(new FortuneRecord(UInteger.valueOf(0), "Additional fortune added at request time."));
Collections.sort(fortunes, (f1, f2) -> f1.getMessage().compareTo(f2.getMessage()));

return ok(views.html.fortunes.render(fortunes));
}, dbEc);
return ok(views.html.fortunes.render(fortunes));
}

public CompletionStage<Result> update(final String queries) {
return getRandomWorlds(queryCount(queries)).thenApplyAsync(worlds -> {
final Random random = ThreadLocalRandom.current();
for (final WorldRecord world : worlds) {
world.setRandomnumber((random.nextInt(10000) + 1));
}
public Result update(final String queries) {
final org.jooq.Result<WorldRecord> worlds = getRandomWorlds(queryCount(queries));

final int batchSize = 25;
final int batches = ((worlds.size() / batchSize) + 1);
this.db.withConnection(connection -> {
final DSLContext sql = DSL.using(connection, DIALECT);
for ( int i = 0 ; i < batches ; ++i ) {
sql.batchUpdate(worlds.subList(i * batchSize, Math.min((i + 1) * batchSize, worlds.size()))).execute();
}
return null;
});
final Random random = ThreadLocalRandom.current();
for (final WorldRecord world : worlds) {
world.setRandomnumber((random.nextInt(10000) + 1));
}

return ok(worlds.formatJSON(JSON_FORMAT)).as(JSON);
}, dbEc);
final int batchSize = 25;
final int batches = ((worlds.size() / batchSize) + 1);
this.db.withConnection(connection -> {
final DSLContext sql = DSL.using(connection, DIALECT);
for ( int i = 0 ; i < batches ; ++i ) {
sql.batchUpdate(worlds.subList(i * batchSize, Math.min((i + 1) * batchSize, worlds.size()))).execute();
}
return null;
});

return ok(worlds.formatJSON(JSON_FORMAT)).as(JSON);
}

private int queryCount(final String queryCountString) {
Expand All @@ -98,24 +91,22 @@ private int queryCount(final String queryCountString) {
return queryCount;
}

private CompletionStage<org.jooq.Result<WorldRecord>> getRandomWorlds(final int n) {
return CompletableFuture.supplyAsync(() -> {
final Random random = ThreadLocalRandom.current();
org.jooq.Result<WorldRecord> worlds = null;
for (int i = 0; i < n; ++i) {
long randomId = random.nextInt(10000) + 1;
final org.jooq.Result<WorldRecord> world = this.db.withConnection(connection -> {
return DSL.using(connection, DIALECT).selectFrom(WORLD).where(WORLD.ID.eq(UInteger.valueOf(randomId))).fetch();
});

if(worlds == null) {
worlds = world;
} else {
worlds.add(world.get(0));
}
private org.jooq.Result<WorldRecord> getRandomWorlds(final int n) {
final Random random = ThreadLocalRandom.current();
org.jooq.Result<WorldRecord> worlds = null;
for (int i = 0; i < n; ++i) {
long randomId = random.nextInt(10000) + 1;
final org.jooq.Result<WorldRecord> world = this.db.withConnection(connection -> {
return DSL.using(connection, DIALECT).selectFrom(WORLD).where(WORLD.ID.eq(UInteger.valueOf(randomId))).fetch();
});

if(worlds == null) {
worlds = world;
} else {
worlds.add(world.get(0));
}
return worlds;
}, dbEc);
}
return worlds;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ play.server {
akka {
actor {
default-dispatcher {
executor = "thread-pool-executor"

fork-join-executor {
# one thread per core is enough
# https://github.com/playframework/playframework/issues/7242#issuecomment-295215448
Expand All @@ -63,11 +65,16 @@ akka {
task-peeking-mode="LIFO" # based on https://www.playframework.com/documentation/2.7.x/Migration24#Thread-pool-configuration
}

# https://www.playframework.com/documentation/2.7.x/ThreadPools#Highly-synchronous
thread-pool-executor {
fixed-pool-size = 44 # db conn pool (29) + number of cores (14) + housekeeping (1)
}

# https://doc.akka.io/docs/akka/2.5.11/dispatchers.html#looking-up-a-dispatcher
# Throughput defines the maximum number of messages to be
# processed per actor before the thread jumps to the next actor.
# Set to 1 for as fair as possible.
throughput = 64
throughput = 1
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ThreadLocalRandom;

import javax.inject.Inject;
Expand All @@ -16,47 +14,41 @@
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Result;
import utils.DatabaseExecutionContext;

public class Application extends Controller {

private final JPAApi jpa;
private final DatabaseExecutionContext dbEc;

@Inject
public Application(final JPAApi jpa, final DatabaseExecutionContext dbEc) {
public Application(final JPAApi jpa) {
this.jpa = jpa;
this.dbEc = dbEc;
}

public CompletionStage<Result> db() {
return getRandomWorlds(1).thenApply(worlds -> ok(Json.toJson(worlds.get(0))));
public Result db() {
return ok(Json.toJson(getRandomWorlds(1).get(0)));
}

public CompletionStage<Result> queries(final String queries) {
return getRandomWorlds(queryCount(queries)).thenApply(worlds -> ok(Json.toJson(worlds)));
public Result queries(final String queries) {
return ok(Json.toJson(getRandomWorlds(queryCount(queries))));
}

public CompletionStage<Result> fortunes() {
return CompletableFuture.supplyAsync(() -> {
final List<Fortune> fortunes = Fortune.findAll(this.jpa);
fortunes.add(new Fortune("Additional fortune added at request time."));
Collections.sort(fortunes, (f1, f2) -> f1.message.compareTo(f2.message));
public Result fortunes() {
final List<Fortune> fortunes = Fortune.findAll(this.jpa);
fortunes.add(new Fortune("Additional fortune added at request time."));
Collections.sort(fortunes, (f1, f2) -> f1.message.compareTo(f2.message));

return ok(views.html.fortunes.render(fortunes));
}, dbEc);
return ok(views.html.fortunes.render(fortunes));
}

public CompletionStage<Result> update(final String queries) {
return getRandomWorlds(queryCount(queries)).thenApplyAsync(worlds -> {
final Random random = ThreadLocalRandom.current();
for (final World world : worlds) {
world.randomNumber = (long) (random.nextInt(10000) + 1);
}
public Result update(final String queries) {
final List<World> worlds = getRandomWorlds(queryCount(queries));
final Random random = ThreadLocalRandom.current();
for (final World world : worlds) {
world.randomNumber = (long) (random.nextInt(10000) + 1);
}

final List<World> updatedWorlds = World.save(worlds, this.jpa);
return ok(Json.toJson(updatedWorlds));
}, dbEc);
final List<World> updatedWorlds = World.save(worlds, this.jpa);
return ok(Json.toJson(updatedWorlds));
}

private int queryCount(final String queryCountString) {
Expand All @@ -75,17 +67,15 @@ private int queryCount(final String queryCountString) {
return queryCount;
}

private CompletionStage<List<World>> getRandomWorlds(final int n) {
return CompletableFuture.supplyAsync(() -> {
final Random random = ThreadLocalRandom.current();
final List<World> worlds = new ArrayList<>(n);
for (int i = 0; i < n; ++i) {
long randomId = random.nextInt(10000) + 1;
final World world = World.findById(randomId, this.jpa);
worlds.add(world);
}
return worlds;
}, dbEc);
private List<World> getRandomWorlds(final int n) {
final Random random = ThreadLocalRandom.current();
final List<World> worlds = new ArrayList<>(n);
for (int i = 0; i < n; ++i) {
long randomId = random.nextInt(10000) + 1;
final World world = World.findById(randomId, this.jpa);
worlds.add(world);
}
return worlds;
}

}
Loading

0 comments on commit 0fcdb35

Please sign in to comment.