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(); + } +}