diff --git a/pom.xml b/pom.xml
index 5e03472..5db239a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -137,6 +137,14 @@
1.1.1
+
+
+
+ org.ehcache
+ ehcache
+ 3.5.2
+
+
diff --git a/src/main/java/io/rudin/minetest/tileserver/TileRenderer.java b/src/main/java/io/rudin/minetest/tileserver/TileRenderer.java
index 8e3d3f6..e8577ac 100644
--- a/src/main/java/io/rudin/minetest/tileserver/TileRenderer.java
+++ b/src/main/java/io/rudin/minetest/tileserver/TileRenderer.java
@@ -101,17 +101,17 @@ public class TileRenderer {
long start = System.currentTimeMillis();
- Integer count = ctx
- .select(DSL.count())
- .from(BLOCKS)
- .where(
- BLOCKS.POSX.ge(Math.min(x1, x2))
- .and(BLOCKS.POSX.le(Math.max(x1, x2)))
- .and(BLOCKS.POSZ.ge(Math.min(z1, z2)))
- .and(BLOCKS.POSZ.le(Math.max(z1, z2)))
- .and(yCondition)
- )
- .fetchOne(DSL.count());
+ Result firstResult = ctx
+ .selectFrom(BLOCKS)
+ .where(
+ BLOCKS.POSX.ge(Math.min(x1, x2))
+ .and(BLOCKS.POSX.le(Math.max(x1, x2)))
+ .and(BLOCKS.POSZ.ge(Math.min(z1, z2)))
+ .and(BLOCKS.POSZ.le(Math.max(z1, z2)))
+ .and(yCondition)
+ )
+ .limit(1)
+ .fetch();
long diff = System.currentTimeMillis() - start;
@@ -119,7 +119,7 @@ public class TileRenderer {
logger.warn("white-count-query took {} ms", diff);
}
- if (count == 0) {
+ if (firstResult.isEmpty()) {
logger.debug("Fail-fast, got zero mapblock count for x={}-{} z={}-{}", x1,x2, z1,z2);
byte[] data = WhiteTile.getPNG();
@@ -293,15 +293,15 @@ public class TileRenderer {
start = now;
- Integer count = ctx
- .select(DSL.count())
- .from(BLOCKS)
+ Result countList = ctx
+ .selectFrom(BLOCKS)
.where(
BLOCKS.POSX.eq(mapblockX)
.and(BLOCKS.POSZ.eq(mapblockZ))
.and(yCondition)
)
- .fetchOne(DSL.count());
+ .limit(1)
+ .fetch();
now = System.currentTimeMillis();
long timingZeroCountCheck = now - start;
@@ -314,7 +314,7 @@ public class TileRenderer {
long timingRender = 0;
- if (count > 0) {
+ if (!countList.isEmpty()) {
blockRenderer.render(mapblockX, mapblockZ, graphics, 16);
diff --git a/src/main/java/io/rudin/minetest/tileserver/config/TileServerConfig.java b/src/main/java/io/rudin/minetest/tileserver/config/TileServerConfig.java
index 2f2c60b..6fdacf4 100644
--- a/src/main/java/io/rudin/minetest/tileserver/config/TileServerConfig.java
+++ b/src/main/java/io/rudin/minetest/tileserver/config/TileServerConfig.java
@@ -42,6 +42,10 @@ public interface TileServerConfig extends Config {
@DefaultValue("2")
int playerUpdateInterval();
+ @Key("tile.route.reentrycount")
+ @DefaultValue("5000")
+ int tileRouteReentryCount();
+
/*
Logging stuff
*/
@@ -67,12 +71,13 @@ public interface TileServerConfig extends Config {
*/
@Key("tile.cache.impl")
- @DefaultValue("FILE")
+ @DefaultValue("EHCACHE")
CacheType tileCacheType();
enum CacheType {
DATABASE,
- FILE
+ FILE,
+ EHCACHE
}
@Key("tiles.directory")
diff --git a/src/main/java/io/rudin/minetest/tileserver/job/UpdateChangedTilesJob.java b/src/main/java/io/rudin/minetest/tileserver/job/UpdateChangedTilesJob.java
index 4d62d03..7a9a17e 100644
--- a/src/main/java/io/rudin/minetest/tileserver/job/UpdateChangedTilesJob.java
+++ b/src/main/java/io/rudin/minetest/tileserver/job/UpdateChangedTilesJob.java
@@ -119,6 +119,12 @@ public class UpdateChangedTilesJob implements Runnable {
int count = blocks.size();
int invalidatedTiles = 0;
+ long diff = start - System.currentTimeMillis();
+
+ if (diff > 1000 && cfg.logQueryPerformance()){
+ logger.warn("updated-tiles-query took {} ms", diff);
+ }
+
if (blocks.size() == LIMIT) {
logger.warn("Got max-blocks ({}) from update-queue", LIMIT);
}
diff --git a/src/main/java/io/rudin/minetest/tileserver/module/DBModule.java b/src/main/java/io/rudin/minetest/tileserver/module/DBModule.java
index 6e16fa0..2ddf02f 100644
--- a/src/main/java/io/rudin/minetest/tileserver/module/DBModule.java
+++ b/src/main/java/io/rudin/minetest/tileserver/module/DBModule.java
@@ -12,7 +12,7 @@ import com.zaxxer.hikari.HikariDataSource;
public class DBModule extends AbstractModule {
- public DBModule(TileServerConfig cfg) throws Exception {
+ public DBModule(TileServerConfig cfg) {
this.cfg = cfg;
}
diff --git a/src/main/java/io/rudin/minetest/tileserver/module/LoggingExecuteListener.java b/src/main/java/io/rudin/minetest/tileserver/module/LoggingExecuteListener.java
index 28c28e0..7797704 100644
--- a/src/main/java/io/rudin/minetest/tileserver/module/LoggingExecuteListener.java
+++ b/src/main/java/io/rudin/minetest/tileserver/module/LoggingExecuteListener.java
@@ -25,7 +25,7 @@ public class LoggingExecuteListener extends DefaultExecuteListener {
super.executeEnd(ctx);
if (watch.split() > 5_000_000_000L)
logger.warn(
- "Slow SQL",
+ "Slow SQL: " + ctx.sql(),
new SQLPerformanceWarning());
}
}
diff --git a/src/main/java/io/rudin/minetest/tileserver/module/ServiceModule.java b/src/main/java/io/rudin/minetest/tileserver/module/ServiceModule.java
index 757215c..ded51ba 100644
--- a/src/main/java/io/rudin/minetest/tileserver/module/ServiceModule.java
+++ b/src/main/java/io/rudin/minetest/tileserver/module/ServiceModule.java
@@ -12,13 +12,14 @@ import io.rudin.minetest.tileserver.provider.ExecutorProvider;
import io.rudin.minetest.tileserver.service.EventBus;
import io.rudin.minetest.tileserver.service.TileCache;
import io.rudin.minetest.tileserver.service.impl.DatabaseTileCache;
+import io.rudin.minetest.tileserver.service.impl.EHTileCache;
import io.rudin.minetest.tileserver.service.impl.EventBusImpl;
import io.rudin.minetest.tileserver.service.impl.FileTileCache;
import org.jooq.util.jaxb.Database;
public class ServiceModule extends AbstractModule {
- public ServiceModule(TileServerConfig cfg) throws Exception {
+ public ServiceModule(TileServerConfig cfg) {
this.cfg = cfg;
}
@@ -30,10 +31,13 @@ public class ServiceModule extends AbstractModule {
if (cfg.tileCacheType() == TileServerConfig.CacheType.DATABASE)
bind(TileCache.class).to(DatabaseTileCache.class);
+
+ else if (cfg.tileCacheType() == TileServerConfig.CacheType.EHCACHE)
+ bind(TileCache.class).to(EHTileCache.class);
+
else
bind(TileCache.class).to(FileTileCache.class);
- //bind(TileCache.class).to(FileTileCache.class);
bind(EventBus.class).to(EventBusImpl.class);
bind(ColorTable.class).toProvider(ColorTableProvider.class);
bind(ExecutorService.class).toProvider(ExecutorProvider.class);
diff --git a/src/main/java/io/rudin/minetest/tileserver/route/TileRoute.java b/src/main/java/io/rudin/minetest/tileserver/route/TileRoute.java
index 8ee6186..bd8b51a 100644
--- a/src/main/java/io/rudin/minetest/tileserver/route/TileRoute.java
+++ b/src/main/java/io/rudin/minetest/tileserver/route/TileRoute.java
@@ -15,6 +15,7 @@ import spark.Route;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
@Singleton
public class TileRoute implements Route {
@@ -25,12 +26,18 @@ public class TileRoute implements Route {
public TileRoute(TileRenderer renderer, TileServerConfig cfg, TileCache cache) {
this.renderer = renderer;
this.cache = cache;
+ this.cfg = cfg;
}
private final TileRenderer renderer;
private final TileCache cache;
+ private final TileServerConfig cfg;
+
+ //TODO: proper rate limit
+ private final AtomicInteger entryCount = new AtomicInteger();
+
@Override
public Object handle(Request req, Response res) throws Exception {
res.header("Content-Type", "image/png");
@@ -46,7 +53,17 @@ public class TileRoute implements Route {
logger.debug("Rendering tile @ {}/{} zoom: {}", x,y,z);
- return renderer.render(x, y, z);
+ try {
+ while (entryCount.get() > cfg.tileRouteReentryCount()){
+ Thread.sleep(50);
+ }
+
+ entryCount.incrementAndGet();
+ return renderer.render(x, y, z);
+
+ } finally {
+ entryCount.decrementAndGet();
+ }
}
}
diff --git a/src/main/java/io/rudin/minetest/tileserver/service/impl/EHTileCache.java b/src/main/java/io/rudin/minetest/tileserver/service/impl/EHTileCache.java
new file mode 100644
index 0000000..7eb0ec8
--- /dev/null
+++ b/src/main/java/io/rudin/minetest/tileserver/service/impl/EHTileCache.java
@@ -0,0 +1,92 @@
+package io.rudin.minetest.tileserver.service.impl;
+
+import io.rudin.minetest.tileserver.config.TileServerConfig;
+import io.rudin.minetest.tileserver.service.TileCache;
+import org.ehcache.Cache;
+import org.ehcache.PersistentCacheManager;
+import org.ehcache.config.builders.CacheConfigurationBuilder;
+import org.ehcache.config.builders.CacheManagerBuilder;
+import org.ehcache.config.builders.ResourcePoolsBuilder;
+import org.ehcache.config.units.EntryUnit;
+import org.ehcache.config.units.MemoryUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+@Singleton
+public class EHTileCache implements TileCache {
+
+ @Inject
+ public EHTileCache(TileServerConfig cfg){
+ this.cfg = cfg;
+ this.timestampMarker = new File(cfg.tileDirectory(), "timestampmarker-ehcache");
+
+ if (!timestampMarker.isFile()){
+ try (OutputStream output = new FileOutputStream(timestampMarker)){
+ output.write(0x00);
+ } catch (Exception e){
+ throw new IllegalArgumentException("could not create timestamp marker!", e);
+ }
+
+ timestampMarker.setLastModified(0);
+ }
+
+
+ PersistentCacheManager persistentCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
+ .with(CacheManagerBuilder.persistence(new File(cfg.tileDirectory(), "ehcache")))
+ .withCache("cache",
+ CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, byte[].class,
+ ResourcePoolsBuilder.newResourcePoolsBuilder()
+ .heap(10, MemoryUnit.MB)
+ .offheap(50, MemoryUnit.MB)
+ .disk(10, MemoryUnit.GB, true)
+ )
+ ).build(true);
+
+ cache = persistentCacheManager.getCache("cache", String.class, byte[].class);
+
+ Runtime.getRuntime().addShutdownHook(new Thread(persistentCacheManager::close));
+ }
+
+ private final File timestampMarker;
+
+ private final TileServerConfig cfg;
+
+ private final Cache cache;
+
+ private String getKey(int x, int y, int z){
+ //TODO: long key
+ return x + "/" + y + "/" + z;
+ }
+
+
+ @Override
+ public void put(int x, int y, int z, byte[] data) throws IOException {
+ cache.put(getKey(x,y,z), data);
+ timestampMarker.setLastModified(System.currentTimeMillis());
+ }
+
+ @Override
+ public byte[] get(int x, int y, int z) throws IOException {
+ return cache.get(getKey(x,y,z));
+ }
+
+ @Override
+ public boolean has(int x, int y, int z) {
+ return cache.containsKey(getKey(x,y,z));
+ }
+
+ @Override
+ public void remove(int x, int y, int z) {
+ cache.remove(getKey(x,y,z));
+ }
+
+ @Override
+ public long getLatestTimestamp() {
+ return timestampMarker.lastModified();
+ }
+}