From 712a73aefd1cadbf5120813dc3ddc815bb088be7 Mon Sep 17 00:00:00 2001 From: lena Date: Sat, 2 Mar 2024 14:06:40 +0300 Subject: [PATCH 01/24] [stage-2] add report & fixes after review --- .../data/stage2/profile-get-130000-alloc.html | 509 +++ .../data/stage2/profile-get-130000-lock.html | 330 ++ .../data/stage2/profile-get-130000.html | 3106 +++++++++++++++++ 3 files changed, 3945 insertions(+) create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-lock.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000.html diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-alloc.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-alloc.html new file mode 100644 index 000000000..216022508 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-alloc.html @@ -0,0 +1,509 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-lock.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-lock.html new file mode 100644 index 000000000..2d782b1d3 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-lock.html @@ -0,0 +1,330 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000.html new file mode 100644 index 000000000..6040ef65a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000.html @@ -0,0 +1,3106 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ From 5cc32529ed146b6938151d9b65544cc7dcf487d0 Mon Sep 17 00:00:00 2001 From: lena Date: Fri, 15 Mar 2024 20:35:44 +0300 Subject: [PATCH 02/24] [stage-2] Fix report --- .../data/stage2/profile-get-130000-alloc.html | 509 --- .../data/stage2/profile-get-130000-lock.html | 330 -- .../data/stage2/profile-get-130000.html | 3106 ----------------- 3 files changed, 3945 deletions(-) delete mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-alloc.html delete mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-lock.html delete mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000.html diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-alloc.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-alloc.html deleted file mode 100644 index 216022508..000000000 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-alloc.html +++ /dev/null @@ -1,509 +0,0 @@ - - - - - - - -

Flame Graph

-
  
-
Produced by async-profiler
- -
-

-

Matched:

- diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-lock.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-lock.html deleted file mode 100644 index 2d782b1d3..000000000 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000-lock.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - - -

Flame Graph

-
  
-
Produced by async-profiler
- -
-

-

Matched:

- diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000.html deleted file mode 100644 index 6040ef65a..000000000 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage2/profile-get-130000.html +++ /dev/null @@ -1,3106 +0,0 @@ - - - - - - - -

Flame Graph

-
  
-
Produced by async-profiler
- -
-

-

Matched:

- From eb7647cb51c087a7716d4d24728d3de775fcd182 Mon Sep 17 00:00:00 2001 From: lena Date: Wed, 13 Mar 2024 22:38:51 +0300 Subject: [PATCH 03/24] [stage-3] Add sharding --- .../test/elenakhodosova/HttpServerImpl.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index fa47ab9c2..ef3bb3c3c 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -10,7 +10,6 @@ import one.nio.http.RequestMethod; import one.nio.http.Response; import one.nio.server.AcceptorConfig; -import one.nio.util.Hash; import one.nio.util.Utf8; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,7 +53,7 @@ public HttpServerImpl(ServiceConfig config, ReferenceDao dao, ExecutorService ex this.executorService = executorService; this.selfUrl = config.selfUrl(); this.nodes = config.clusterUrls(); - this.client = HttpClient.newHttpClient(); + client = HttpClient.newHttpClient(); } @Override @@ -101,9 +100,9 @@ private static HttpServerConfig createServerConfig(ServiceConfig serviceConfig) @RequestMethod(Request.METHOD_GET) public Response getEntity(@Param(value = "id", required = true) String id) { if (isParamIncorrect(id)) return new Response(Response.BAD_REQUEST, Response.EMPTY); - String clusterUrl = hash(id); - if (!Objects.equals(clusterUrl, selfUrl)) { - return redirectRequest(AllowedMethods.GET, id, clusterUrl, new byte[0]); + String targetNode = hash(id); + if (!Objects.equals(targetNode, selfUrl)) { + return proxyRequest(AllowedMethods.GET, id, targetNode, new byte[0]); } try { Entry value = dao.get(MemorySegment.ofArray(Utf8.toBytes(id))); @@ -121,9 +120,9 @@ public Response putEntity(@Param(value = "id", required = true) String id, Reque return new Response(Response.BAD_REQUEST, Response.EMPTY); } byte[] value = request.getBody(); - String clusterUrl = hash(id); - if (!Objects.equals(clusterUrl, selfUrl)) { - return redirectRequest(AllowedMethods.PUT, id, clusterUrl, value); + String targetNode = hash(id); + if (!Objects.equals(targetNode, selfUrl)) { + return proxyRequest(AllowedMethods.PUT, id, targetNode, value); } try { dao.upsert(new BaseEntry<>( @@ -139,9 +138,9 @@ public Response putEntity(@Param(value = "id", required = true) String id, Reque @RequestMethod(Request.METHOD_DELETE) public Response deleteEntity(@Param(value = "id", required = true) String id) { if (isParamIncorrect(id)) return new Response(Response.BAD_REQUEST, Response.EMPTY); - String clusterUrl = hash(id); - if (!Objects.equals(clusterUrl, selfUrl)) { - return redirectRequest(AllowedMethods.DELETE, id, clusterUrl, new byte[0]); + String targetNode = hash(id); + if (!Objects.equals(targetNode, selfUrl)) { + return proxyRequest(AllowedMethods.DELETE, id, targetNode, new byte[0]); } try { dao.upsert(new BaseEntry<>(MemorySegment.ofArray(Utf8.toBytes(id)), null)); @@ -162,10 +161,11 @@ public void handleDefault(Request request, HttpSession session) throws IOExcepti session.sendResponse(badRequest); } - private Response redirectRequest(AllowedMethods method, String id, String clusterUrl, byte[] body) { + private Response proxyRequest(AllowedMethods method, String id, String targetNode, byte[] body) { + String targetPath = targetNode + PATH_NAME + "?id=" + id; try { HttpResponse response = client.send(HttpRequest.newBuilder() - .uri(URI.create(clusterUrl + PATH_NAME + "?id=" + id)) + .uri(URI.create(targetPath)) .method(method.name(), HttpRequest.BodyPublishers.ofByteArray(body) ).build(), HttpResponse.BodyHandlers.ofByteArray()); return new Response(getResponseByCode(response.statusCode()), response.body()); @@ -181,16 +181,16 @@ private boolean isParamIncorrect(String param) { } private String hash(String id) { - int maxHash = Integer.MIN_VALUE; - String nodeUrl = null; + int maxValue = Integer.MIN_VALUE; + String maxHashNode = null; for (String node : nodes) { - int hash = Hash.murmur3(id + node); - if (hash > maxHash) { - maxHash = hash; - nodeUrl = node; + int hash = (id + node).hashCode(); + if (hash > maxValue) { + maxValue = hash; + maxHashNode = node; } } - return nodeUrl; + return maxHashNode; } private String getResponseByCode(int code) { From 239fcc7646515ce1fb5121a4926ea4dd0a99f83b Mon Sep 17 00:00:00 2001 From: lena Date: Fri, 15 Mar 2024 22:33:06 +0300 Subject: [PATCH 04/24] [stage-3] Add server --- .../test/elenakhodosova/HttpServerImpl.java | 18 +++++++------ .../vk/itmo/test/elenakhodosova/Server.java | 26 ++++++++----------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index ef3bb3c3c..37489b674 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -26,6 +26,8 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.time.Duration; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutorService; @@ -53,7 +55,7 @@ public HttpServerImpl(ServiceConfig config, ReferenceDao dao, ExecutorService ex this.executorService = executorService; this.selfUrl = config.selfUrl(); this.nodes = config.clusterUrls(); - client = HttpClient.newHttpClient(); + this.client = HttpClient.newHttpClient(); } @Override @@ -102,7 +104,7 @@ public Response getEntity(@Param(value = "id", required = true) String id) { if (isParamIncorrect(id)) return new Response(Response.BAD_REQUEST, Response.EMPTY); String targetNode = hash(id); if (!Objects.equals(targetNode, selfUrl)) { - return proxyRequest(AllowedMethods.GET, id, targetNode, new byte[0]); + return redirectRequest(AllowedMethods.GET, id, targetNode, new byte[0]); } try { Entry value = dao.get(MemorySegment.ofArray(Utf8.toBytes(id))); @@ -122,7 +124,7 @@ public Response putEntity(@Param(value = "id", required = true) String id, Reque byte[] value = request.getBody(); String targetNode = hash(id); if (!Objects.equals(targetNode, selfUrl)) { - return proxyRequest(AllowedMethods.PUT, id, targetNode, value); + return redirectRequest(AllowedMethods.PUT, id, targetNode, value); } try { dao.upsert(new BaseEntry<>( @@ -140,7 +142,7 @@ public Response deleteEntity(@Param(value = "id", required = true) String id) { if (isParamIncorrect(id)) return new Response(Response.BAD_REQUEST, Response.EMPTY); String targetNode = hash(id); if (!Objects.equals(targetNode, selfUrl)) { - return proxyRequest(AllowedMethods.DELETE, id, targetNode, new byte[0]); + return redirectRequest(AllowedMethods.DELETE, id, targetNode, new byte[0]); } try { dao.upsert(new BaseEntry<>(MemorySegment.ofArray(Utf8.toBytes(id)), null)); @@ -161,12 +163,12 @@ public void handleDefault(Request request, HttpSession session) throws IOExcepti session.sendResponse(badRequest); } - private Response proxyRequest(AllowedMethods method, String id, String targetNode, byte[] body) { - String targetPath = targetNode + PATH_NAME + "?id=" + id; + private Response redirectRequest(AllowedMethods method, String id, String targetNode, byte[] body) { try { HttpResponse response = client.send(HttpRequest.newBuilder() - .uri(URI.create(targetPath)) - .method(method.name(), HttpRequest.BodyPublishers.ofByteArray(body) + .uri(URI.create(targetNode + PATH_NAME + "?id=" + id)) + .method(method.name(), HttpRequest.BodyPublishers.ofByteArray(body)) + .timeout(Duration.of(500, ChronoUnit.MILLIS) ).build(), HttpResponse.BodyHandlers.ofByteArray()); return new Response(getResponseByCode(response.statusCode()), response.body()); } catch (InterruptedException | IOException e) { diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java index cfcbba2a0..9a7d16c67 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java @@ -1,8 +1,8 @@ package ru.vk.itmo.test.elenakhodosova; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ru.vk.itmo.ServiceConfig; +import ru.vk.itmo.dao.Config; +import ru.vk.itmo.test.elenakhodosova.dao.ReferenceDao; import java.io.IOException; import java.nio.file.Files; @@ -11,9 +11,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.Objects; +import java.util.concurrent.ExecutorService; +import java.util.stream.Collectors; public final class Server { @@ -21,13 +21,13 @@ public final class Server { public static final String LOCALHOST = "http://localhost:"; public static final String DIRECTORY_PREFIX = "tmp/"; public static final int NODES_COUNT = 3; - private static final Logger logger = LoggerFactory.getLogger(HttpServerImpl.class); private Server() { } public static void main(String[] args) throws IOException { + ExecutorService executorService = ExecutorServiceConfig.getExecutorService(); List nodesConfigs = new ArrayList<>(NODES_COUNT); Map nodes = new HashMap<>(); @@ -43,19 +43,15 @@ public static void main(String[] args) throws IOException { ServiceConfig config = new ServiceConfig( port, url, - nodes.values().stream().toList(), - Files.createDirectories(Paths.get(DIRECTORY_PREFIX + port)) + nodes.values().stream().filter(value -> !Objects.equals(value, url)).collect(Collectors.toList()), + Files.createDirectories(Paths.get(DIRECTORY_PREFIX + url)) ); nodesConfigs.add(config); } for (ServiceConfig config : nodesConfigs) { - ServiceImpl server = new ServiceImpl(config); - try { - server.start().get(1, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - logger.error("Unable to start service instance: ", e); - Thread.currentThread().interrupt(); - } + ReferenceDao dao = new ReferenceDao(new Config(config.workingDir(), FLUSH_THRESHOLD_BYTES)); + HttpServerImpl server = new HttpServerImpl(config, dao, executorService); + server.start(); } } } From 6931fe1060aff4792f492786f71c596a5e5f8f2e Mon Sep 17 00:00:00 2001 From: lena Date: Sat, 16 Mar 2024 18:28:00 +0300 Subject: [PATCH 05/24] [stage-3] Add hash analysis and profiling --- .../test/elenakhodosova/HttpServerImpl.java | 42 +++++++++---------- .../vk/itmo/test/elenakhodosova/Server.java | 26 +++++++----- .../test/elenakhodosova/reports/Stage3.md | 32 +------------- 3 files changed, 36 insertions(+), 64 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index 37489b674..fa47ab9c2 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -10,6 +10,7 @@ import one.nio.http.RequestMethod; import one.nio.http.Response; import one.nio.server.AcceptorConfig; +import one.nio.util.Hash; import one.nio.util.Utf8; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,8 +27,6 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.time.Duration; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutorService; @@ -102,9 +101,9 @@ private static HttpServerConfig createServerConfig(ServiceConfig serviceConfig) @RequestMethod(Request.METHOD_GET) public Response getEntity(@Param(value = "id", required = true) String id) { if (isParamIncorrect(id)) return new Response(Response.BAD_REQUEST, Response.EMPTY); - String targetNode = hash(id); - if (!Objects.equals(targetNode, selfUrl)) { - return redirectRequest(AllowedMethods.GET, id, targetNode, new byte[0]); + String clusterUrl = hash(id); + if (!Objects.equals(clusterUrl, selfUrl)) { + return redirectRequest(AllowedMethods.GET, id, clusterUrl, new byte[0]); } try { Entry value = dao.get(MemorySegment.ofArray(Utf8.toBytes(id))); @@ -122,9 +121,9 @@ public Response putEntity(@Param(value = "id", required = true) String id, Reque return new Response(Response.BAD_REQUEST, Response.EMPTY); } byte[] value = request.getBody(); - String targetNode = hash(id); - if (!Objects.equals(targetNode, selfUrl)) { - return redirectRequest(AllowedMethods.PUT, id, targetNode, value); + String clusterUrl = hash(id); + if (!Objects.equals(clusterUrl, selfUrl)) { + return redirectRequest(AllowedMethods.PUT, id, clusterUrl, value); } try { dao.upsert(new BaseEntry<>( @@ -140,9 +139,9 @@ public Response putEntity(@Param(value = "id", required = true) String id, Reque @RequestMethod(Request.METHOD_DELETE) public Response deleteEntity(@Param(value = "id", required = true) String id) { if (isParamIncorrect(id)) return new Response(Response.BAD_REQUEST, Response.EMPTY); - String targetNode = hash(id); - if (!Objects.equals(targetNode, selfUrl)) { - return redirectRequest(AllowedMethods.DELETE, id, targetNode, new byte[0]); + String clusterUrl = hash(id); + if (!Objects.equals(clusterUrl, selfUrl)) { + return redirectRequest(AllowedMethods.DELETE, id, clusterUrl, new byte[0]); } try { dao.upsert(new BaseEntry<>(MemorySegment.ofArray(Utf8.toBytes(id)), null)); @@ -163,12 +162,11 @@ public void handleDefault(Request request, HttpSession session) throws IOExcepti session.sendResponse(badRequest); } - private Response redirectRequest(AllowedMethods method, String id, String targetNode, byte[] body) { + private Response redirectRequest(AllowedMethods method, String id, String clusterUrl, byte[] body) { try { HttpResponse response = client.send(HttpRequest.newBuilder() - .uri(URI.create(targetNode + PATH_NAME + "?id=" + id)) - .method(method.name(), HttpRequest.BodyPublishers.ofByteArray(body)) - .timeout(Duration.of(500, ChronoUnit.MILLIS) + .uri(URI.create(clusterUrl + PATH_NAME + "?id=" + id)) + .method(method.name(), HttpRequest.BodyPublishers.ofByteArray(body) ).build(), HttpResponse.BodyHandlers.ofByteArray()); return new Response(getResponseByCode(response.statusCode()), response.body()); } catch (InterruptedException | IOException e) { @@ -183,16 +181,16 @@ private boolean isParamIncorrect(String param) { } private String hash(String id) { - int maxValue = Integer.MIN_VALUE; - String maxHashNode = null; + int maxHash = Integer.MIN_VALUE; + String nodeUrl = null; for (String node : nodes) { - int hash = (id + node).hashCode(); - if (hash > maxValue) { - maxValue = hash; - maxHashNode = node; + int hash = Hash.murmur3(id + node); + if (hash > maxHash) { + maxHash = hash; + nodeUrl = node; } } - return maxHashNode; + return nodeUrl; } private String getResponseByCode(int code) { diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java index 9a7d16c67..cfcbba2a0 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java @@ -1,8 +1,8 @@ package ru.vk.itmo.test.elenakhodosova; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ru.vk.itmo.ServiceConfig; -import ru.vk.itmo.dao.Config; -import ru.vk.itmo.test.elenakhodosova.dao.ReferenceDao; import java.io.IOException; import java.nio.file.Files; @@ -11,9 +11,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ExecutorService; -import java.util.stream.Collectors; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; public final class Server { @@ -21,13 +21,13 @@ public final class Server { public static final String LOCALHOST = "http://localhost:"; public static final String DIRECTORY_PREFIX = "tmp/"; public static final int NODES_COUNT = 3; + private static final Logger logger = LoggerFactory.getLogger(HttpServerImpl.class); private Server() { } public static void main(String[] args) throws IOException { - ExecutorService executorService = ExecutorServiceConfig.getExecutorService(); List nodesConfigs = new ArrayList<>(NODES_COUNT); Map nodes = new HashMap<>(); @@ -43,15 +43,19 @@ public static void main(String[] args) throws IOException { ServiceConfig config = new ServiceConfig( port, url, - nodes.values().stream().filter(value -> !Objects.equals(value, url)).collect(Collectors.toList()), - Files.createDirectories(Paths.get(DIRECTORY_PREFIX + url)) + nodes.values().stream().toList(), + Files.createDirectories(Paths.get(DIRECTORY_PREFIX + port)) ); nodesConfigs.add(config); } for (ServiceConfig config : nodesConfigs) { - ReferenceDao dao = new ReferenceDao(new Config(config.workingDir(), FLUSH_THRESHOLD_BYTES)); - HttpServerImpl server = new HttpServerImpl(config, dao, executorService); - server.start(); + ServiceImpl server = new ServiceImpl(config); + try { + server.start().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + logger.error("Unable to start service instance: ", e); + Thread.currentThread().interrupt(); + } } } } diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage3.md b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage3.md index f53a6e36c..909f7f3d1 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage3.md +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage3.md @@ -91,34 +91,4 @@ wrk2 -d 30 -t 1 -c 64 -R 25000 -L -s get.lua http://127.0.0.1:8080 ### Сравнение с этапом 2 -На предыдущем этапе с 64 потоками для put точка разладки была ~150000 rps, при 130000 rps средняя latency была 1.80ms. -С добавлением шардирования пропускная способность упала в 4,5 раза. Сервис выдерживает 33000 rps с latency 6.30ms, а дальше высокие -перцентили растут до 300ms+. - -| RPS | Avg Latency | -|-------|-------------| -| 35000 | 19.83ms | -| 33000 | 6.30ms | -| 32000 | 4.25ms | -| 28000 | 1.66ms | - -На втором этапе при увеличении потоков wrk с 1 до 64, точка разладки выросла примерно со 105000 rps до 150000 rps, -а теперь всего на ~1000. Но, средняя latency начинает расти после 28000, а не после 20000 как в измерениях выше. - - - - -Для get с 64 потоками точка разладки была ~155000 rps, при 100000 rps средняя latency была 1.52ms. -С добавлением шардирования пропускная способность упала в ~5 раз. Сервис выдерживает 34000 rps с latency 8.65ms, а дальше высокие -перцентили растут до 200ms+. - -| RPS | Avg Latency | -|-------|-------------| -| 35000 | 11.85ms | -| 34000 | 8.65ms | -| 32000 | 3.75ms | -| 29000 | 1.80ms | - -Соответственно, как и с put latency предыдущего этапа сохраняется при пропускной способности гораздо ниже, чем до -добавления шардирования. Как было видно из профилей, сетевое взаимодействие между нодами является узким местом, относительно которого -оптимизации бизнес-логики самого приложения практически не влияют на итоговый результат. \ No newline at end of file +//TODO From 74b864d71cbddf7b99568c821c6dcb7222946f0f Mon Sep 17 00:00:00 2001 From: lena Date: Sun, 17 Mar 2024 14:01:21 +0300 Subject: [PATCH 06/24] [stage-3] Add comparison to stage 2 --- .../test/elenakhodosova/reports/Stage3.md | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage3.md b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage3.md index 909f7f3d1..f53a6e36c 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage3.md +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage3.md @@ -91,4 +91,34 @@ wrk2 -d 30 -t 1 -c 64 -R 25000 -L -s get.lua http://127.0.0.1:8080 ### Сравнение с этапом 2 -//TODO +На предыдущем этапе с 64 потоками для put точка разладки была ~150000 rps, при 130000 rps средняя latency была 1.80ms. +С добавлением шардирования пропускная способность упала в 4,5 раза. Сервис выдерживает 33000 rps с latency 6.30ms, а дальше высокие +перцентили растут до 300ms+. + +| RPS | Avg Latency | +|-------|-------------| +| 35000 | 19.83ms | +| 33000 | 6.30ms | +| 32000 | 4.25ms | +| 28000 | 1.66ms | + +На втором этапе при увеличении потоков wrk с 1 до 64, точка разладки выросла примерно со 105000 rps до 150000 rps, +а теперь всего на ~1000. Но, средняя latency начинает расти после 28000, а не после 20000 как в измерениях выше. + + + + +Для get с 64 потоками точка разладки была ~155000 rps, при 100000 rps средняя latency была 1.52ms. +С добавлением шардирования пропускная способность упала в ~5 раз. Сервис выдерживает 34000 rps с latency 8.65ms, а дальше высокие +перцентили растут до 200ms+. + +| RPS | Avg Latency | +|-------|-------------| +| 35000 | 11.85ms | +| 34000 | 8.65ms | +| 32000 | 3.75ms | +| 29000 | 1.80ms | + +Соответственно, как и с put latency предыдущего этапа сохраняется при пропускной способности гораздо ниже, чем до +добавления шардирования. Как было видно из профилей, сетевое взаимодействие между нодами является узким местом, относительно которого +оптимизации бизнес-логики самого приложения практически не влияют на итоговый результат. \ No newline at end of file From edbc011a660fd755c7c2ac74df8b0809fe45be79 Mon Sep 17 00:00:00 2001 From: lena Date: Wed, 27 Mar 2024 08:12:25 +0300 Subject: [PATCH 07/24] [stage-4] add replication --- .../test/elenakhodosova/HttpServerImpl.java | 249 +++++++++++++----- .../itmo/test/elenakhodosova/ServiceImpl.java | 2 +- .../dao/BaseEntryWithTimestamp.java | 8 + .../dao/EntryWithTimestamp.java | 7 + .../dao/LiveFilteringIterator.java | 18 +- .../test/elenakhodosova/dao/MemTable.java | 10 +- .../dao/MergingEntryIterator.java | 10 +- .../test/elenakhodosova/dao/ReferenceDao.java | 34 +-- .../itmo/test/elenakhodosova/dao/SSTable.java | 34 ++- .../elenakhodosova/dao/SSTableWriter.java | 20 +- .../test/elenakhodosova/dao/SSTables.java | 38 +-- .../test/elenakhodosova/dao/TableSet.java | 40 ++- .../dao/WeightedPeekingEntryIterator.java | 16 +- 13 files changed, 300 insertions(+), 186 deletions(-) create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/dao/BaseEntryWithTimestamp.java create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/dao/EntryWithTimestamp.java diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index fa47ab9c2..e66907ef9 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -4,10 +4,7 @@ import one.nio.http.HttpServer; import one.nio.http.HttpServerConfig; import one.nio.http.HttpSession; -import one.nio.http.Param; -import one.nio.http.Path; import one.nio.http.Request; -import one.nio.http.RequestMethod; import one.nio.http.Response; import one.nio.server.AcceptorConfig; import one.nio.util.Hash; @@ -15,28 +12,33 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.vk.itmo.ServiceConfig; -import ru.vk.itmo.dao.BaseEntry; import ru.vk.itmo.dao.Dao; -import ru.vk.itmo.dao.Entry; +import ru.vk.itmo.test.elenakhodosova.dao.BaseEntryWithTimestamp; +import ru.vk.itmo.test.elenakhodosova.dao.EntryWithTimestamp; import ru.vk.itmo.test.elenakhodosova.dao.ReferenceDao; import java.io.IOException; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; +import java.net.HttpURLConnection; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.List; -import java.util.Objects; +import java.util.Map; +import java.util.TreeMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; public class HttpServerImpl extends HttpServer { - private final Dao> dao; + private final Dao> dao; private static final String PATH_NAME = "/v0/entity"; private static final String TOO_MANY_REQUESTS = "429 Too Many Requests"; + private static final String NOT_ENOUGH_REPLICAS = "504 Not Enough Replicas"; + private static final String TIMESTAMP_HEADER = "X-timestamp: "; + private static final String REDIRECTED_HEADER = "X-redirected"; private final ExecutorService executorService; private static final Logger logger = LoggerFactory.getLogger(HttpServerImpl.class); @@ -60,22 +62,116 @@ public HttpServerImpl(ServiceConfig config, ReferenceDao dao, ExecutorService ex @Override public void handleRequest(Request request, HttpSession session) throws IOException { try { - executorService.execute(() -> processRequest(request, session)); + String id = request.getParameter("id="); + if (isParamIncorrect(id)) { + session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); + return; + } + executorService.execute(() -> processRequest(request, session, id)); } catch (RejectedExecutionException e) { logger.error("Request rejected", e); session.sendResponse(new Response(TOO_MANY_REQUESTS, Response.EMPTY)); } } - private void processRequest(Request request, HttpSession session) { + private void processRequest(Request request, HttpSession session, String id) { try { - super.handleRequest(request, session); + AllowedMethods method = getMethod(request.getMethod()); + if (method == null) { + session.sendResponse(new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY)); + return; + } + + String isRedirected = request.getHeader(REDIRECTED_HEADER); + + if (isRedirected != null) { + session.sendResponse(handleLocalRequest(request, id)); + return; + } + + String fromStr = request.getParameter("from="); + String ackStr = request.getParameter("ack="); + int from = fromStr == null || fromStr.isEmpty() ? nodes.size() + : Integer.parseInt(fromStr); + int ack = ackStr == null || ackStr.isEmpty() ? from / 2 + 1 : Integer.parseInt(ackStr); //TODO move quorum to method + if (ack == 0 || ack > from || from > nodes.size()) { + session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); + return; + } + + List nodesHashes = getSortedNodes(id, from); + int success = 0; //todo rename + Response[] responses = new Response[ack]; //todo replace with list & check length + request.addHeader(TIMESTAMP_HEADER + System.currentTimeMillis()); //todo remove unused + + + for (int i = 0; i < from; i++) { + if (success == ack) { + break; + } + String node = nodesHashes.get(i); + try { + if (node.equals(selfUrl)) { + responses[success] = handleLocalRequest(request, id); + } else { + responses[success] = redirectRequest(method, id, node, request); + } + success++; + } catch (InterruptedException | IOException e) { + logger.error("Error during sending request", e); + Thread.currentThread().interrupt(); + } + + } + if (success < ack) { + session.sendResponse(new Response(NOT_ENOUGH_REPLICAS, Response.EMPTY)); + return; + } + + if (request.getMethod() == Request.METHOD_GET) { + Response response = validateGetRequests(ack, responses); + session.sendResponse(response); + return; + } + String responseStatusCode = getResponseByCode(responses[0].getStatus()); + Response response = new Response(responseStatusCode, responses[0].getBody()); + session.sendResponse(response); } catch (Exception e) { logger.error("Unexpected error when processing request", e); sendError(session, e); } } + private Response validateGetRequests(int ack, Response[] responses) { + int notFound = 0; + long latestTimestamp = Integer.MIN_VALUE; + Response latestResponse = null; + for (Response response : responses) { + long timestamp = response.getHeader(TIMESTAMP_HEADER) == null ? Integer.MIN_VALUE + : Long.parseLong(response.getHeader(TIMESTAMP_HEADER)); + if (response.getStatus() == HttpURLConnection.HTTP_NOT_FOUND) { + notFound++; + if (timestamp != Integer.MIN_VALUE && latestTimestamp < timestamp) { + latestTimestamp = timestamp; + latestResponse = null; + } + continue; + } + if (timestamp > latestTimestamp) { + latestTimestamp = timestamp; + latestResponse = response; + } + } + Response response; + if (notFound == ack || latestResponse == null) { //todo more checks? + response = new Response(Response.NOT_FOUND, Response.EMPTY); + } else { + response = new Response(Response.OK, latestResponse.getBody()); + } + return response; + } + + private void sendError(HttpSession session, Exception e) { try { String responseCode = e.getClass() == HttpException.class ? Response.BAD_REQUEST : Response.INTERNAL_ERROR; @@ -86,6 +182,16 @@ private void sendError(HttpSession session, Exception e) { } } + private List getSortedNodes(String key, Integer from) { + Map nodesHashes = new TreeMap<>(); + + for (String node : nodes) { + nodesHashes.put(Hash.murmur3(node + key), node); + } + return nodesHashes.values().stream().limit(from).toList(); + } + + private static HttpServerConfig createServerConfig(ServiceConfig serviceConfig) { HttpServerConfig httpServerConfig = new HttpServerConfig(); AcceptorConfig acceptorConfig = new AcceptorConfig(); @@ -97,82 +203,85 @@ private static HttpServerConfig createServerConfig(ServiceConfig serviceConfig) return httpServerConfig; } - @Path(PATH_NAME) - @RequestMethod(Request.METHOD_GET) - public Response getEntity(@Param(value = "id", required = true) String id) { - if (isParamIncorrect(id)) return new Response(Response.BAD_REQUEST, Response.EMPTY); - String clusterUrl = hash(id); - if (!Objects.equals(clusterUrl, selfUrl)) { - return redirectRequest(AllowedMethods.GET, id, clusterUrl, new byte[0]); - } + public Response getEntity(String id) { try { - Entry value = dao.get(MemorySegment.ofArray(Utf8.toBytes(id))); - return value == null ? new Response(Response.NOT_FOUND, Response.EMPTY) - : Response.ok(value.value().toArray(ValueLayout.JAVA_BYTE)); + EntryWithTimestamp value = dao.get(MemorySegment.ofArray(Utf8.toBytes(id))); + if (value == null) { + return new Response(Response.NOT_FOUND, Response.EMPTY); + } + + Response response; + if (value.value() == null) { + response = new Response(Response.NOT_FOUND, Response.EMPTY); + } else { + response = Response.ok(value.value().toArray(ValueLayout.JAVA_BYTE)); + } + response.addHeader(TIMESTAMP_HEADER + value.timestamp()); + return response; } catch (Exception ex) { + logger.error("GET: ", ex); return new Response(Response.INTERNAL_ERROR, Response.EMPTY); } } - @Path(PATH_NAME) - @RequestMethod(Request.METHOD_PUT) - public Response putEntity(@Param(value = "id", required = true) String id, Request request) { - if (isParamIncorrect(id) || request.getBody() == null) { + public Response putEntity(String id, Request request, long timestamp) { + if (request.getBody() == null) { return new Response(Response.BAD_REQUEST, Response.EMPTY); } byte[] value = request.getBody(); - String clusterUrl = hash(id); - if (!Objects.equals(clusterUrl, selfUrl)) { - return redirectRequest(AllowedMethods.PUT, id, clusterUrl, value); - } try { - dao.upsert(new BaseEntry<>( + dao.upsert(new BaseEntryWithTimestamp<>( MemorySegment.ofArray(Utf8.toBytes(id)), - MemorySegment.ofArray(value))); + MemorySegment.ofArray(value), + timestamp)); return new Response(Response.CREATED, Response.EMPTY); } catch (Exception ex) { + logger.error("PUT: ", ex); return new Response(Response.INTERNAL_ERROR, Response.EMPTY); } } - @Path(PATH_NAME) - @RequestMethod(Request.METHOD_DELETE) - public Response deleteEntity(@Param(value = "id", required = true) String id) { - if (isParamIncorrect(id)) return new Response(Response.BAD_REQUEST, Response.EMPTY); - String clusterUrl = hash(id); - if (!Objects.equals(clusterUrl, selfUrl)) { - return redirectRequest(AllowedMethods.DELETE, id, clusterUrl, new byte[0]); - } + public Response deleteEntity(String id, long timestamp) { try { - dao.upsert(new BaseEntry<>(MemorySegment.ofArray(Utf8.toBytes(id)), null)); + dao.upsert(new BaseEntryWithTimestamp<>(MemorySegment.ofArray(Utf8.toBytes(id)), null, timestamp)); return new Response(Response.ACCEPTED, Response.EMPTY); } catch (Exception ex) { + logger.error("DELETE: ", ex); return new Response(Response.INTERNAL_ERROR, Response.EMPTY); } } - @Path(PATH_NAME) - public Response methodNotSupported() { - return new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); - } - - @Override - public void handleDefault(Request request, HttpSession session) throws IOException { - Response badRequest = new Response(Response.BAD_REQUEST, Response.EMPTY); - session.sendResponse(badRequest); + private Response redirectRequest(AllowedMethods method, + String id, + String clusterUrl, + Request request) throws InterruptedException, IOException { + byte[] body = request.getBody(); + HttpResponse response = client.send(HttpRequest.newBuilder() + .uri(URI.create(clusterUrl + PATH_NAME + "?id=" + id)) + .method(method.name(), body == null + ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofByteArray(body)) + .header(REDIRECTED_HEADER, "true" + ).build(), HttpResponse.BodyHandlers.ofByteArray()); + return new Response(getResponseByCode(response.statusCode()), response.body()); } - private Response redirectRequest(AllowedMethods method, String id, String clusterUrl, byte[] body) { - try { - HttpResponse response = client.send(HttpRequest.newBuilder() - .uri(URI.create(clusterUrl + PATH_NAME + "?id=" + id)) - .method(method.name(), HttpRequest.BodyPublishers.ofByteArray(body) - ).build(), HttpResponse.BodyHandlers.ofByteArray()); - return new Response(getResponseByCode(response.statusCode()), response.body()); - } catch (InterruptedException | IOException e) { - logger.error("Error during sending request", e); - Thread.currentThread().interrupt(); - return new Response(Response.INTERNAL_ERROR, Response.EMPTY); + private Response handleLocalRequest(Request request, String id) { + String timestampHeader = request.getHeader(TIMESTAMP_HEADER); + long timestamp = timestampHeader == null ? System.currentTimeMillis() : Long.parseLong(timestampHeader); + switch (request.getMethod()) { + case Request.METHOD_GET -> { + return getEntity(id); + } + case Request.METHOD_PUT -> { + return putEntity(id, request, timestamp); + } + case Request.METHOD_DELETE -> { + return deleteEntity(id, timestamp); + } + default -> { + return new Response(Response.BAD_REQUEST, Response.EMPTY); + } } } @@ -180,19 +289,6 @@ private boolean isParamIncorrect(String param) { return param == null || param.isEmpty(); } - private String hash(String id) { - int maxHash = Integer.MIN_VALUE; - String nodeUrl = null; - for (String node : nodes) { - int hash = Hash.murmur3(id + node); - if (hash > maxHash) { - maxHash = hash; - nodeUrl = node; - } - } - return nodeUrl; - } - private String getResponseByCode(int code) { return switch (code) { case 200 -> Response.OK; @@ -205,4 +301,11 @@ private String getResponseByCode(int code) { default -> Response.INTERNAL_ERROR; }; } + + private AllowedMethods getMethod(int method) { + if (method == 1) return AllowedMethods.GET; + if (method == 5) return AllowedMethods.PUT; + if (method == 6) return AllowedMethods.DELETE; + return null; + } } diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java index c61e3783c..49b621adc 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java @@ -54,7 +54,7 @@ public CompletableFuture stop() throws IOException { return CompletableFuture.completedFuture(null); } - @ServiceFactory(stage = 3) + @ServiceFactory(stage = 4) public static class Factory implements ServiceFactory.Factory { @Override diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/BaseEntryWithTimestamp.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/BaseEntryWithTimestamp.java new file mode 100644 index 000000000..5e65caa2b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/BaseEntryWithTimestamp.java @@ -0,0 +1,8 @@ +package ru.vk.itmo.test.elenakhodosova.dao; + +public record BaseEntryWithTimestamp(Data key, Data value, long timestamp) implements EntryWithTimestamp { + @Override + public String toString() { + return "{" + key + ":" + value + ":" + timestamp + "}"; + } +} diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/EntryWithTimestamp.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/EntryWithTimestamp.java new file mode 100644 index 000000000..809bd7944 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/EntryWithTimestamp.java @@ -0,0 +1,7 @@ +package ru.vk.itmo.test.elenakhodosova.dao; + +import ru.vk.itmo.dao.Entry; + +public interface EntryWithTimestamp extends Entry { + long timestamp(); +} diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/LiveFilteringIterator.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/LiveFilteringIterator.java index ad1d4a0a3..570566157 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/LiveFilteringIterator.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/LiveFilteringIterator.java @@ -1,28 +1,26 @@ package ru.vk.itmo.test.elenakhodosova.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; import java.util.Iterator; import java.util.NoSuchElementException; /** - * Filters non tombstone {@link Entry}s. + * Filters non tombstone {@link EntryWithTimestamp}s. * * @author incubos */ -final class LiveFilteringIterator implements Iterator> { - private final Iterator> delegate; - private Entry next; +final class LiveFilteringIterator implements Iterator> { + private final Iterator> delegate; + private EntryWithTimestamp next; - LiveFilteringIterator(final Iterator> delegate) { + LiveFilteringIterator(final Iterator> delegate) { this.delegate = delegate; skipTombstones(); } private void skipTombstones() { while (delegate.hasNext()) { - final Entry entry = delegate.next(); + final EntryWithTimestamp entry = delegate.next(); if (entry.value() != null) { this.next = entry; break; @@ -36,13 +34,13 @@ public boolean hasNext() { } @Override - public Entry next() { + public EntryWithTimestamp next() { if (!hasNext()) { throw new NoSuchElementException(); } // Consume - final Entry result = next; + final EntryWithTimestamp result = next; next = null; skipTombstones(); diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/MemTable.java index 9109864e5..ef914df72 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/MemTable.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/MemTable.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.elenakhodosova.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; import java.util.Iterator; import java.util.NavigableMap; @@ -13,7 +11,7 @@ * @author incubos */ final class MemTable { - private final NavigableMap> map = + private final NavigableMap> map = new ConcurrentSkipListMap<>( MemorySegmentComparator.INSTANCE); @@ -21,7 +19,7 @@ boolean isEmpty() { return map.isEmpty(); } - Iterator> get( + Iterator> get( final MemorySegment from, final MemorySegment to) { if (from == null && to == null) { @@ -39,11 +37,11 @@ Iterator> get( } } - Entry get(final MemorySegment key) { + EntryWithTimestamp get(final MemorySegment key) { return map.get(key); } - Entry upsert(final Entry entry) { + EntryWithTimestamp upsert(final EntryWithTimestamp entry) { return map.put(entry.key(), entry); } } diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/MergingEntryIterator.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/MergingEntryIterator.java index d86bad383..f3cb34e83 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/MergingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/MergingEntryIterator.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.elenakhodosova.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; import java.util.Iterator; import java.util.List; @@ -14,7 +12,7 @@ * * @author incubos */ -final class MergingEntryIterator implements Iterator> { +final class MergingEntryIterator implements Iterator> { private final Queue iterators; MergingEntryIterator(final List iterators) { @@ -29,13 +27,13 @@ public boolean hasNext() { } @Override - public Entry next() { + public EntryWithTimestamp next() { if (!hasNext()) { throw new NoSuchElementException(); } final WeightedPeekingEntryIterator top = iterators.remove(); - final Entry result = top.next(); + final EntryWithTimestamp result = top.next(); if (top.hasNext()) { // Not exhausted @@ -51,7 +49,7 @@ public Entry next() { } // Skip entries with the same key - final Entry entry = iterator.peek(); + final EntryWithTimestamp entry = iterator.peek(); if (MemorySegmentComparator.INSTANCE.compare(result.key(), entry.key()) != 0) { // Reached another key break; diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/ReferenceDao.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/ReferenceDao.java index a0aba4d7b..02981770b 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/ReferenceDao.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/ReferenceDao.java @@ -2,14 +2,16 @@ import ru.vk.itmo.dao.Config; import ru.vk.itmo.dao.Dao; -import ru.vk.itmo.dao.Entry; import java.io.IOException; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -22,7 +24,7 @@ * * @author incubos */ -public class ReferenceDao implements Dao> { +public class ReferenceDao implements Dao> { private final Config config; private final Arena arena; @@ -63,7 +65,7 @@ public ReferenceDao(final Config config) throws IOException { } @Override - public Iterator> get( + public Iterator> get( final MemorySegment from, final MemorySegment to) { return new LiveFilteringIterator( @@ -73,13 +75,13 @@ public Iterator> get( } @Override - public Entry get(final MemorySegment key) { + public EntryWithTimestamp get(final MemorySegment key) { // Without lock, just snapshot of table set return tableSet.get(key); } @Override - public void upsert(final Entry entry) { + public void upsert(final EntryWithTimestamp entry) { final boolean autoFlush; lock.readLock().lock(); try { @@ -89,10 +91,10 @@ public void upsert(final Entry entry) { } // Upsert - final Entry previous = tableSet.upsert(entry); + final EntryWithTimestamp previous = tableSet.upsert(entry); // Update size estimate - final long size = tableSet.memTableSize.addAndGet(sizeOf(entry) - sizeOf(previous)); + final long size = tableSet.memTableSize.addAndGet(sizeOf(entry) + sizeOf(previous)); autoFlush = size > config.flushThresholdBytes(); } finally { lock.readLock().unlock(); @@ -103,16 +105,16 @@ public void upsert(final Entry entry) { } } - private static long sizeOf(final Entry entry) { + private static long sizeOf(final EntryWithTimestamp entry) { if (entry == null) { return 0L; } if (entry.value() == null) { - return entry.key().byteSize(); + return entry.key().byteSize() + ValueLayout.JAVA_LONG.byteSize(); } - return entry.key().byteSize() + entry.value().byteSize(); + return entry.key().byteSize() + entry.value().byteSize() + ValueLayout.JAVA_LONG.byteSize(); } private void initiateFlush(final boolean auto) { @@ -138,12 +140,13 @@ private void initiateFlush(final boolean auto) { } // Write - final int sequence = currentTableSet.nextSequence(); + final long timestamp = System.currentTimeMillis(); + // final int sequence = currentTableSet.nextSequence(); try { new SSTableWriter() .write( config.basePath(), - sequence, + timestamp, currentTableSet.flushingTable.get(null, null)); } catch (IOException e) { e.printStackTrace(); @@ -157,7 +160,7 @@ private void initiateFlush(final boolean auto) { flushed = SSTables.open( arena, config.basePath(), - sequence); + timestamp); } catch (IOException e) { e.printStackTrace(); Runtime.getRuntime().halt(-2); @@ -200,8 +203,7 @@ public void compact() throws IOException { .write( config.basePath(), 0, - new LiveFilteringIterator( - currentTableSet.allSSTableEntries())); + new LiveFilteringIterator(currentTableSet.allSSTableEntries())); } catch (IOException e) { e.printStackTrace(); Runtime.getRuntime().halt(-3); @@ -241,7 +243,7 @@ public void compact() throws IOException { try { SSTables.remove( config.basePath(), - ssTable.sequence); + ssTable.timestamp); } catch (IOException e) { e.printStackTrace(); Runtime.getRuntime().halt(-5); diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTable.java index 9c7e58b87..24792fda9 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTable.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTable.java @@ -1,8 +1,5 @@ package ru.vk.itmo.test.elenakhodosova.dao; -import ru.vk.itmo.dao.BaseEntry; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.util.Collections; @@ -16,17 +13,17 @@ * @see SSTables */ final class SSTable { - final int sequence; + final long timestamp; private final MemorySegment index; private final MemorySegment data; private final long size; SSTable( - final int sequence, + final long timestamp, final MemorySegment index, final MemorySegment data) { - this.sequence = sequence; + this.timestamp = timestamp; this.index = index; this.data = data; this.size = index.byteSize() / Long.BYTES; @@ -88,7 +85,7 @@ private long getLength(final long offset) { offset); } - Iterator> get( + Iterator> get( final MemorySegment from, final MemorySegment to) { assert from == null || to == null || MemorySegmentComparator.INSTANCE.compare(from, to) <= 0; @@ -134,7 +131,7 @@ Iterator> get( return new SliceIterator(fromOffset, toOffset); } - Entry get(final MemorySegment key) { + EntryWithTimestamp get(final MemorySegment key) { final long entry = entryBinarySearch(key); if (entry < 0) { return null; @@ -147,16 +144,20 @@ Entry get(final MemorySegment key) { final long valueLength = getLength(offset); if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { // Tombstone encountered - return new BaseEntry<>(key, null); + return new BaseEntryWithTimestamp<>(key, null, timestamp); } else { // Get value offset += Long.BYTES; final MemorySegment value = data.asSlice(offset, valueLength); - return new BaseEntry<>(key, value); + + // Get timestamp + offset += Long.BYTES; + final long timestamp = getLength(offset); + return new BaseEntryWithTimestamp<>(key, value, timestamp); } } - private final class SliceIterator implements Iterator> { + private final class SliceIterator implements Iterator> { private long offset; private final long toOffset; @@ -173,7 +174,7 @@ public boolean hasNext() { } @Override - public Entry next() { + public EntryWithTimestamp next() { if (!hasNext()) { throw new NoSuchElementException(); } @@ -192,12 +193,17 @@ public Entry next() { // Read value if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { + //add timestamp length + offset += Long.BYTES; // Tombstone encountered - return new BaseEntry<>(key, null); + return new BaseEntryWithTimestamp<>(key, null, timestamp); } else { final MemorySegment value = data.asSlice(offset, valueLength); offset += valueLength; - return new BaseEntry<>(key, value); + final long timestamp = getLength(offset); + //add timestamp length + offset += Long.BYTES; + return new BaseEntryWithTimestamp<>(key, value, timestamp); } } } diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTableWriter.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTableWriter.java index a232cc30a..5b6c69488 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTableWriter.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTableWriter.java @@ -39,11 +39,11 @@ final class SSTableWriter { void write( final Path baseDir, - final int sequence, - final Iterator> entries) throws IOException { + final long timestamp, + final Iterator> entries) throws IOException { // Write to temporary files - final Path tempIndexName = SSTables.tempIndexName(baseDir, sequence); - final Path tempDataName = SSTables.tempDataName(baseDir, sequence); + final Path tempIndexName = SSTables.tempIndexName(baseDir, timestamp); + final Path tempDataName = SSTables.tempDataName(baseDir, timestamp); // Delete temporary files to eliminate tails Files.deleteIfExists(tempIndexName); @@ -71,7 +71,7 @@ void write( writeLong(entryOffset, index); // Then write the entry - final Entry entry = entries.next(); + final EntryWithTimestamp entry = entries.next(); entryOffset += writeEntry(entry, data); } } @@ -81,7 +81,7 @@ void write( final Path indexName = SSTables.indexName( baseDir, - sequence); + timestamp); Files.move( tempIndexName, indexName, @@ -90,7 +90,7 @@ void write( final Path dataName = SSTables.dataName( baseDir, - sequence); + timestamp); Files.move( tempDataName, dataName, @@ -132,10 +132,11 @@ private void writeSegment( * @return written bytes */ private long writeEntry( - final Entry entry, + final EntryWithTimestamp entry, final OutputStream os) throws IOException { final MemorySegment key = entry.key(); final MemorySegment value = entry.value(); + final long timestamp = entry.timestamp(); long result = 0L; // Key size @@ -161,6 +162,9 @@ private long writeEntry( result += value.byteSize(); } + // Timestamp + writeLong(timestamp, os); + result += Long.BYTES; return result; } } diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTables.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTables.java index a5114de7a..0f33c358c 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTables.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTables.java @@ -35,26 +35,26 @@ private SSTables() { static Path indexName( final Path baseDir, - final int sequence) { - return baseDir.resolve(sequence + INDEX_SUFFIX); + final long timestamp) { + return baseDir.resolve(timestamp + INDEX_SUFFIX); } static Path dataName( final Path baseDir, - final int sequence) { - return baseDir.resolve(sequence + DATA_SUFFIX); + final long timestamp) { + return baseDir.resolve(timestamp + DATA_SUFFIX); } static Path tempIndexName( final Path baseDir, - final int sequence) { - return baseDir.resolve(sequence + INDEX_SUFFIX + TEMP_SUFFIX); + final long timestamp) { + return baseDir.resolve(timestamp + INDEX_SUFFIX + TEMP_SUFFIX); } static Path tempDataName( final Path baseDir, - final int sequence) { - return baseDir.resolve(sequence + DATA_SUFFIX + TEMP_SUFFIX); + final long timestamp) { + return baseDir.resolve(timestamp + DATA_SUFFIX + TEMP_SUFFIX); } /** @@ -76,15 +76,15 @@ static List discover( return; } - final int sequence = + final long timestamp = // .data -> N - Integer.parseInt( + Long.parseLong( fileName.substring( 0, fileName.length() - DATA_SUFFIX.length())); try { - result.add(open(arena, baseDir, sequence)); + result.add(open(arena, baseDir, timestamp)); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -92,7 +92,7 @@ static List discover( } // Sort from freshest to oldest - result.sort((o1, o2) -> Integer.compare(o2.sequence, o1.sequence)); + result.sort((o1, o2) -> Long.compare(o2.timestamp, o1.timestamp)); return Collections.unmodifiableList(result); } @@ -100,18 +100,18 @@ static List discover( static SSTable open( final Arena arena, final Path baseDir, - final int sequence) throws IOException { + final long timestamp) throws IOException { final MemorySegment index = mapReadOnly( arena, - indexName(baseDir, sequence)); + indexName(baseDir, timestamp)); final MemorySegment data = mapReadOnly( arena, - dataName(baseDir, sequence)); + dataName(baseDir, timestamp)); return new SSTable( - sequence, + timestamp, index, data); } @@ -133,10 +133,10 @@ private static MemorySegment mapReadOnly( static void remove( final Path baseDir, - final int sequence) throws IOException { + final long timestamp) throws IOException { // First delete data file to make SSTable invisible - Files.delete(dataName(baseDir, sequence)); - Files.delete(indexName(baseDir, sequence)); + Files.delete(dataName(baseDir, timestamp)); + Files.delete(indexName(baseDir, timestamp)); } static void promote( diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/TableSet.java index eb3a935f9..32af03d41 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/TableSet.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/TableSet.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.elenakhodosova.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; import java.util.ArrayList; import java.util.Collections; @@ -42,13 +40,6 @@ static TableSet from(final List ssTables) { ssTables); } - int nextSequence() { - return ssTables.stream() - .mapToInt(t -> t.sequence) - .max() - .orElse(0) + 1; - } - TableSet flushing() { if (memTable.isEmpty()) { throw new IllegalStateException("Nothing to flush"); @@ -98,14 +89,14 @@ TableSet compacted( newSsTables); } - Iterator> get( + Iterator> get( final MemorySegment from, final MemorySegment to) { final List iterators = new ArrayList<>(2 + ssTables.size()); // MemTable goes first - final Iterator> memTableIterator = + final Iterator> memTableIterator = memTable.get(from, to); if (memTableIterator.hasNext()) { iterators.add( @@ -116,7 +107,7 @@ Iterator> get( // Then goes flushing if (flushingTable != null) { - final Iterator> flushingIterator = + final Iterator> flushingIterator = flushingTable.get(from, to); if (flushingIterator.hasNext()) { iterators.add( @@ -129,7 +120,7 @@ Iterator> get( // Then go all the SSTables for (int i = 0; i < ssTables.size(); i++) { final SSTable ssTable = ssTables.get(i); - final Iterator> ssTableIterator = + final Iterator> ssTableIterator = ssTable.get(from, to); if (ssTableIterator.hasNext()) { iterators.add( @@ -146,11 +137,11 @@ Iterator> get( }; } - Entry get(final MemorySegment key) { + EntryWithTimestamp get(final MemorySegment key) { // Slightly optimized version not to pollute the heap // First check MemTable - Entry result = memTable.get(key); + EntryWithTimestamp result = memTable.get(key); if (result != null) { // Transform tombstone return swallowTombstone(result); @@ -167,10 +158,10 @@ Entry get(final MemorySegment key) { // At last check SSTables from freshest to oldest for (final SSTable ssTable : ssTables) { - result = ssTable.get(key); - if (result != null) { + EntryWithTimestamp resultFromSstable = ssTable.get(key); + if (resultFromSstable != null) { // Transform tombstone - return swallowTombstone(result); + return swallowTombstone(resultFromSstable); } } @@ -178,21 +169,22 @@ Entry get(final MemorySegment key) { return null; } - private static Entry swallowTombstone(final Entry entry) { - return entry.value() == null ? null : entry; + private static EntryWithTimestamp swallowTombstone(final EntryWithTimestamp entry) { + if (entry.value() == null) return null; + return new BaseEntryWithTimestamp<>(entry.key(), entry.value(), entry.timestamp()); } - Entry upsert(final Entry entry) { + EntryWithTimestamp upsert(final EntryWithTimestamp entry) { return memTable.upsert(entry); } - Iterator> allSSTableEntries() { + Iterator> allSSTableEntries() { final List iterators = new ArrayList<>(ssTables.size()); for (int i = 0; i < ssTables.size(); i++) { final SSTable ssTable = ssTables.get(i); - final Iterator> ssTableIterator = + final Iterator> ssTableIterator = ssTable.get(null, null); iterators.add( new WeightedPeekingEntryIterator( @@ -202,4 +194,4 @@ Iterator> allSSTableEntries() { return new MergingEntryIterator(iterators); } -} +} \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/WeightedPeekingEntryIterator.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/WeightedPeekingEntryIterator.java index e6b878ae2..af2a31da0 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/WeightedPeekingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/WeightedPeekingEntryIterator.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.elenakhodosova.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; import java.util.Iterator; import java.util.NoSuchElementException; @@ -12,15 +10,15 @@ * @author incubos */ final class WeightedPeekingEntryIterator - implements Iterator>, + implements Iterator>, Comparable { private final int weight; - private final Iterator> delegate; - private Entry next; + private final Iterator> delegate; + private EntryWithTimestamp next; WeightedPeekingEntryIterator( final int weight, - final Iterator> delegate) { + final Iterator> delegate) { this.weight = weight; this.delegate = delegate; this.next = delegate.hasNext() ? delegate.next() : null; @@ -32,17 +30,17 @@ public boolean hasNext() { } @Override - public Entry next() { + public EntryWithTimestamp next() { if (!hasNext()) { throw new NoSuchElementException(); } - final Entry result = next; + final EntryWithTimestamp result = next; next = delegate.hasNext() ? delegate.next() : null; return result; } - Entry peek() { + EntryWithTimestamp peek() { if (!hasNext()) { throw new NoSuchElementException(); } From f97e02a931c01bdbb1c4bba6778da37c1662f52c Mon Sep 17 00:00:00 2001 From: lena Date: Wed, 27 Mar 2024 08:12:25 +0300 Subject: [PATCH 08/24] [stage-4] add replication --- .../test/elenakhodosova/HttpServerImpl.java | 26 ++++++++++-------- .../test/elenakhodosova/dao/ReferenceDao.java | 18 +++++-------- .../itmo/test/elenakhodosova/dao/SSTable.java | 18 +++++++------ .../elenakhodosova/dao/SSTableWriter.java | 27 ++++++++++--------- .../test/elenakhodosova/dao/TableSet.java | 23 ++++++++++------ 5 files changed, 62 insertions(+), 50 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index e66907ef9..682e11413 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -27,6 +27,7 @@ import java.net.http.HttpResponse; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.TreeMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; @@ -93,16 +94,15 @@ private void processRequest(Request request, HttpSession session, String id) { String ackStr = request.getParameter("ack="); int from = fromStr == null || fromStr.isEmpty() ? nodes.size() : Integer.parseInt(fromStr); - int ack = ackStr == null || ackStr.isEmpty() ? from / 2 + 1 : Integer.parseInt(ackStr); //TODO move quorum to method + int ack = ackStr == null || ackStr.isEmpty() ? from / 2 + 1 : Integer.parseInt(ackStr); if (ack == 0 || ack > from || from > nodes.size()) { session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); return; } List nodesHashes = getSortedNodes(id, from); - int success = 0; //todo rename - Response[] responses = new Response[ack]; //todo replace with list & check length - request.addHeader(TIMESTAMP_HEADER + System.currentTimeMillis()); //todo remove unused + int success = 0; + Response[] responses = new Response[ack]; for (int i = 0; i < from; i++) { @@ -144,16 +144,16 @@ private void processRequest(Request request, HttpSession session, String id) { private Response validateGetRequests(int ack, Response[] responses) { int notFound = 0; - long latestTimestamp = Integer.MIN_VALUE; + long latestTimestamp = 0L; Response latestResponse = null; for (Response response : responses) { - long timestamp = response.getHeader(TIMESTAMP_HEADER) == null ? Integer.MIN_VALUE + long timestamp = response.getHeader(TIMESTAMP_HEADER) == null ? 0L : Long.parseLong(response.getHeader(TIMESTAMP_HEADER)); if (response.getStatus() == HttpURLConnection.HTTP_NOT_FOUND) { notFound++; - if (timestamp != Integer.MIN_VALUE && latestTimestamp < timestamp) { + if (timestamp != 0L && latestTimestamp < timestamp) { latestTimestamp = timestamp; - latestResponse = null; + latestResponse = response; } continue; } @@ -163,7 +163,7 @@ private Response validateGetRequests(int ack, Response[] responses) { } } Response response; - if (notFound == ack || latestResponse == null) { //todo more checks? + if (notFound == ack || latestResponse == null) { response = new Response(Response.NOT_FOUND, Response.EMPTY); } else { response = new Response(Response.OK, latestResponse.getBody()); @@ -261,9 +261,13 @@ private Response redirectRequest(AllowedMethods method, .method(method.name(), body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofByteArray(body)) - .header(REDIRECTED_HEADER, "true" + .header(REDIRECTED_HEADER, "true") + .header("X-timestamp", String.valueOf(System.currentTimeMillis()) ).build(), HttpResponse.BodyHandlers.ofByteArray()); - return new Response(getResponseByCode(response.statusCode()), response.body()); + Response result = new Response(getResponseByCode(response.statusCode()), response.body()); + Optional respTimestamp = response.headers().firstValue("X-timestamp"); + respTimestamp.ifPresent(v-> result.addHeader(TIMESTAMP_HEADER + v)); + return result; } private Response handleLocalRequest(Request request, String id) { diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/ReferenceDao.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/ReferenceDao.java index 02981770b..6fa95fb90 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/ReferenceDao.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/ReferenceDao.java @@ -6,12 +6,9 @@ import java.io.IOException; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; -import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -94,7 +91,7 @@ public void upsert(final EntryWithTimestamp entry) { final EntryWithTimestamp previous = tableSet.upsert(entry); // Update size estimate - final long size = tableSet.memTableSize.addAndGet(sizeOf(entry) + sizeOf(previous)); + final long size = tableSet.memTableSize.addAndGet(sizeOf(entry) - sizeOf(previous)); autoFlush = size > config.flushThresholdBytes(); } finally { lock.readLock().unlock(); @@ -111,10 +108,10 @@ private static long sizeOf(final EntryWithTimestamp entry) { } if (entry.value() == null) { - return entry.key().byteSize() + ValueLayout.JAVA_LONG.byteSize(); + return entry.key().byteSize() + Long.BYTES; } - return entry.key().byteSize() + entry.value().byteSize() + ValueLayout.JAVA_LONG.byteSize(); + return entry.key().byteSize() + entry.value().byteSize() + Long.BYTES; } private void initiateFlush(final boolean auto) { @@ -140,13 +137,12 @@ private void initiateFlush(final boolean auto) { } // Write - final long timestamp = System.currentTimeMillis(); - // final int sequence = currentTableSet.nextSequence(); + final int sequence = currentTableSet.nextSequence(); try { new SSTableWriter() .write( config.basePath(), - timestamp, + sequence, currentTableSet.flushingTable.get(null, null)); } catch (IOException e) { e.printStackTrace(); @@ -160,7 +156,7 @@ private void initiateFlush(final boolean auto) { flushed = SSTables.open( arena, config.basePath(), - timestamp); + sequence); } catch (IOException e) { e.printStackTrace(); Runtime.getRuntime().halt(-2); @@ -243,7 +239,7 @@ public void compact() throws IOException { try { SSTables.remove( config.basePath(), - ssTable.timestamp); + ssTable.sequence); } catch (IOException e) { e.printStackTrace(); Runtime.getRuntime().halt(-5); diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTable.java index 24792fda9..6d00834b7 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTable.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTable.java @@ -13,17 +13,17 @@ * @see SSTables */ final class SSTable { - final long timestamp; + final int sequence; private final MemorySegment index; private final MemorySegment data; private final long size; SSTable( - final long timestamp, + final int sequence, final MemorySegment index, final MemorySegment data) { - this.timestamp = timestamp; + this.sequence = sequence; this.index = index; this.data = data; this.size = index.byteSize() / Long.BYTES; @@ -143,15 +143,18 @@ EntryWithTimestamp get(final MemorySegment key) { // Extract value length final long valueLength = getLength(offset); if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { + // Get timestamp + offset += Long.BYTES; + final long timestamp = getLength(offset); // Tombstone encountered return new BaseEntryWithTimestamp<>(key, null, timestamp); } else { // Get value - offset += Long.BYTES; + // offset += Long.BYTES; final MemorySegment value = data.asSlice(offset, valueLength); // Get timestamp - offset += Long.BYTES; + offset += valueLength; final long timestamp = getLength(offset); return new BaseEntryWithTimestamp<>(key, value, timestamp); } @@ -193,15 +196,14 @@ public EntryWithTimestamp next() { // Read value if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { - //add timestamp length - offset += Long.BYTES; // Tombstone encountered + final long timestamp = getLength(offset); + offset += Long.BYTES; return new BaseEntryWithTimestamp<>(key, null, timestamp); } else { final MemorySegment value = data.asSlice(offset, valueLength); offset += valueLength; final long timestamp = getLength(offset); - //add timestamp length offset += Long.BYTES; return new BaseEntryWithTimestamp<>(key, value, timestamp); } diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTableWriter.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTableWriter.java index 5b6c69488..d10c033d7 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTableWriter.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTableWriter.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.elenakhodosova.dao; -import ru.vk.itmo.dao.Entry; - import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -15,7 +13,7 @@ import java.util.Iterator; /** - * Writes {@link Entry} {@link Iterator} to SSTable on disk. + * Writes {@link EntryWithTimestamp} {@link Iterator} to SSTable on disk. * *

Index file {@code .index} contains {@code long} offsets to entries in data file: * {@code [offset0, offset1, ...]} @@ -39,11 +37,11 @@ final class SSTableWriter { void write( final Path baseDir, - final long timestamp, + final int sequence, final Iterator> entries) throws IOException { // Write to temporary files - final Path tempIndexName = SSTables.tempIndexName(baseDir, timestamp); - final Path tempDataName = SSTables.tempDataName(baseDir, timestamp); + final Path tempIndexName = SSTables.tempIndexName(baseDir, sequence); + final Path tempDataName = SSTables.tempDataName(baseDir, sequence); // Delete temporary files to eliminate tails Files.deleteIfExists(tempIndexName); @@ -81,7 +79,7 @@ void write( final Path indexName = SSTables.indexName( baseDir, - timestamp); + sequence); Files.move( tempIndexName, indexName, @@ -90,7 +88,7 @@ void write( final Path dataName = SSTables.dataName( baseDir, - timestamp); + sequence); Files.move( tempDataName, dataName, @@ -127,7 +125,7 @@ private void writeSegment( } /** - * Writes {@link Entry} to {@link FileChannel}. + * Writes {@link EntryWithTimestamp} to {@link FileChannel}. * * @return written bytes */ @@ -152,6 +150,10 @@ private long writeEntry( // Tombstone writeLong(SSTables.TOMBSTONE_VALUE_LENGTH, os); result += Long.BYTES; + + // Timestamp + writeLong(timestamp, os); + result += Long.BYTES; } else { // Value length writeLong(value.byteSize(), os); @@ -160,11 +162,12 @@ private long writeEntry( // Value writeSegment(value, os); result += value.byteSize(); + + // Timestamp + writeLong(timestamp, os); + result += Long.BYTES; } - // Timestamp - writeLong(timestamp, os); - result += Long.BYTES; return result; } } diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/TableSet.java index 32af03d41..e6cd6994e 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/TableSet.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/TableSet.java @@ -40,6 +40,13 @@ static TableSet from(final List ssTables) { ssTables); } + int nextSequence() { + return ssTables.stream() + .mapToInt(t -> t.sequence) + .max() + .orElse(0) + 1; + } + TableSet flushing() { if (memTable.isEmpty()) { throw new IllegalStateException("Nothing to flush"); @@ -144,7 +151,7 @@ EntryWithTimestamp get(final MemorySegment key) { EntryWithTimestamp result = memTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } // Then check flushing @@ -152,16 +159,16 @@ EntryWithTimestamp get(final MemorySegment key) { result = flushingTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } } // At last check SSTables from freshest to oldest for (final SSTable ssTable : ssTables) { - EntryWithTimestamp resultFromSstable = ssTable.get(key); - if (resultFromSstable != null) { + result = ssTable.get(key); + if (result != null) { // Transform tombstone - return swallowTombstone(resultFromSstable); + return result; } } @@ -169,10 +176,10 @@ EntryWithTimestamp get(final MemorySegment key) { return null; } - private static EntryWithTimestamp swallowTombstone(final EntryWithTimestamp entry) { - if (entry.value() == null) return null; +/* private static EntryWithTimestamp swallowTombstone(final EntryWithTimestamp entry) { + if (entry.value() == null) return new BaseEntryWithTimestamp<>(entry.key(), null, entry.timestamp());; return new BaseEntryWithTimestamp<>(entry.key(), entry.value(), entry.timestamp()); - } + }*/ EntryWithTimestamp upsert(final EntryWithTimestamp entry) { return memTable.upsert(entry); From 67233176f5ee02cc37b66dbace9af901f8534dd5 Mon Sep 17 00:00:00 2001 From: lena Date: Sun, 31 Mar 2024 19:30:45 +0300 Subject: [PATCH 09/24] [stage-4] add replication --- .../java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index 682e11413..7c008cb78 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -146,6 +146,7 @@ private Response validateGetRequests(int ack, Response[] responses) { int notFound = 0; long latestTimestamp = 0L; Response latestResponse = null; + boolean isLatestFailed = false; for (Response response : responses) { long timestamp = response.getHeader(TIMESTAMP_HEADER) == null ? 0L : Long.parseLong(response.getHeader(TIMESTAMP_HEADER)); @@ -153,17 +154,18 @@ private Response validateGetRequests(int ack, Response[] responses) { notFound++; if (timestamp != 0L && latestTimestamp < timestamp) { latestTimestamp = timestamp; - latestResponse = response; + isLatestFailed = true; } continue; } + isLatestFailed = false; if (timestamp > latestTimestamp) { latestTimestamp = timestamp; latestResponse = response; } } Response response; - if (notFound == ack || latestResponse == null) { + if (notFound == ack || isLatestFailed || latestResponse == null) { response = new Response(Response.NOT_FOUND, Response.EMPTY); } else { response = new Response(Response.OK, latestResponse.getBody()); From 08b0644999be9aa602dcbe3dc3403dee5b036e35 Mon Sep 17 00:00:00 2001 From: lena Date: Sun, 31 Mar 2024 19:34:27 +0300 Subject: [PATCH 10/24] [stage-4] add replication --- .../test/elenakhodosova/HttpServerImpl.java | 5 ++- .../test/elenakhodosova/dao/SSTables.java | 38 +++++++++---------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index 7c008cb78..b89480143 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -123,7 +123,7 @@ private void processRequest(Request request, HttpSession session, String id) { } } - if (success < ack) { + if (success < ack ) { session.sendResponse(new Response(NOT_ENOUGH_REPLICAS, Response.EMPTY)); return; } @@ -154,6 +154,8 @@ private Response validateGetRequests(int ack, Response[] responses) { notFound++; if (timestamp != 0L && latestTimestamp < timestamp) { latestTimestamp = timestamp; + // latestResponse = response; + // latestResponse.setBody(null); isLatestFailed = true; } continue; @@ -268,6 +270,7 @@ private Response redirectRequest(AllowedMethods method, ).build(), HttpResponse.BodyHandlers.ofByteArray()); Response result = new Response(getResponseByCode(response.statusCode()), response.body()); Optional respTimestamp = response.headers().firstValue("X-timestamp"); + respTimestamp.ifPresent(s -> System.out.println("DBG: " + s.toString())); respTimestamp.ifPresent(v-> result.addHeader(TIMESTAMP_HEADER + v)); return result; } diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTables.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTables.java index 0f33c358c..a5114de7a 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTables.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTables.java @@ -35,26 +35,26 @@ private SSTables() { static Path indexName( final Path baseDir, - final long timestamp) { - return baseDir.resolve(timestamp + INDEX_SUFFIX); + final int sequence) { + return baseDir.resolve(sequence + INDEX_SUFFIX); } static Path dataName( final Path baseDir, - final long timestamp) { - return baseDir.resolve(timestamp + DATA_SUFFIX); + final int sequence) { + return baseDir.resolve(sequence + DATA_SUFFIX); } static Path tempIndexName( final Path baseDir, - final long timestamp) { - return baseDir.resolve(timestamp + INDEX_SUFFIX + TEMP_SUFFIX); + final int sequence) { + return baseDir.resolve(sequence + INDEX_SUFFIX + TEMP_SUFFIX); } static Path tempDataName( final Path baseDir, - final long timestamp) { - return baseDir.resolve(timestamp + DATA_SUFFIX + TEMP_SUFFIX); + final int sequence) { + return baseDir.resolve(sequence + DATA_SUFFIX + TEMP_SUFFIX); } /** @@ -76,15 +76,15 @@ static List discover( return; } - final long timestamp = + final int sequence = // .data -> N - Long.parseLong( + Integer.parseInt( fileName.substring( 0, fileName.length() - DATA_SUFFIX.length())); try { - result.add(open(arena, baseDir, timestamp)); + result.add(open(arena, baseDir, sequence)); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -92,7 +92,7 @@ static List discover( } // Sort from freshest to oldest - result.sort((o1, o2) -> Long.compare(o2.timestamp, o1.timestamp)); + result.sort((o1, o2) -> Integer.compare(o2.sequence, o1.sequence)); return Collections.unmodifiableList(result); } @@ -100,18 +100,18 @@ static List discover( static SSTable open( final Arena arena, final Path baseDir, - final long timestamp) throws IOException { + final int sequence) throws IOException { final MemorySegment index = mapReadOnly( arena, - indexName(baseDir, timestamp)); + indexName(baseDir, sequence)); final MemorySegment data = mapReadOnly( arena, - dataName(baseDir, timestamp)); + dataName(baseDir, sequence)); return new SSTable( - timestamp, + sequence, index, data); } @@ -133,10 +133,10 @@ private static MemorySegment mapReadOnly( static void remove( final Path baseDir, - final long timestamp) throws IOException { + final int sequence) throws IOException { // First delete data file to make SSTable invisible - Files.delete(dataName(baseDir, timestamp)); - Files.delete(indexName(baseDir, timestamp)); + Files.delete(dataName(baseDir, sequence)); + Files.delete(indexName(baseDir, sequence)); } static void promote( From e7fb47ab36377c2087c114eac7810787180f1d36 Mon Sep 17 00:00:00 2001 From: lena Date: Sun, 31 Mar 2024 21:39:56 +0300 Subject: [PATCH 11/24] [stage-4] fix overwrite --- .../test/elenakhodosova/HttpServerImpl.java | 124 +++++++++--------- .../itmo/test/elenakhodosova/dao/SSTable.java | 2 +- .../test/elenakhodosova/dao/TableSet.java | 7 +- 3 files changed, 61 insertions(+), 72 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index b89480143..f04574877 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -31,6 +31,7 @@ import java.util.TreeMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; +import java.util.stream.Collectors; public class HttpServerImpl extends HttpServer { @@ -68,78 +69,76 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); return; } - executorService.execute(() -> processRequest(request, session, id)); + AllowedMethods method = getMethod(request.getMethod()); + if (method == null) { + session.sendResponse(new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY)); + return; + } + executorService.execute(() -> { + try { + processRequest(request, session, id, method); + } catch (Exception e) { + logger.error("Unexpected error when processing request", e); + sendError(session, e); + } + }); } catch (RejectedExecutionException e) { logger.error("Request rejected", e); session.sendResponse(new Response(TOO_MANY_REQUESTS, Response.EMPTY)); } } - private void processRequest(Request request, HttpSession session, String id) { - try { - AllowedMethods method = getMethod(request.getMethod()); - if (method == null) { - session.sendResponse(new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY)); - return; - } + private void processRequest(Request request, HttpSession session, String id, AllowedMethods method) throws IOException { + String isRedirected = request.getHeader(REDIRECTED_HEADER); - String isRedirected = request.getHeader(REDIRECTED_HEADER); + if (isRedirected != null) { + session.sendResponse(handleLocalRequest(request, id)); + return; + } - if (isRedirected != null) { - session.sendResponse(handleLocalRequest(request, id)); - return; - } + String fromStr = request.getParameter("from="); + String ackStr = request.getParameter("ack="); + int from = fromStr == null || fromStr.isEmpty() ? nodes.size() : Integer.parseInt(fromStr); + int ack = ackStr == null || ackStr.isEmpty() ? from / 2 + 1 : Integer.parseInt(ackStr); + if (ack == 0 || from == 0 || ack > from || from > nodes.size()) { + session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); + return; + } - String fromStr = request.getParameter("from="); - String ackStr = request.getParameter("ack="); - int from = fromStr == null || fromStr.isEmpty() ? nodes.size() - : Integer.parseInt(fromStr); - int ack = ackStr == null || ackStr.isEmpty() ? from / 2 + 1 : Integer.parseInt(ackStr); - if (ack == 0 || ack > from || from > nodes.size()) { - session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); - return; + List nodesHashes = getSortedNodes(id, from); + int success = 0; + Response[] responses = new Response[ack]; + request.addHeader(TIMESTAMP_HEADER + System.currentTimeMillis()); + for (int i = 0; i < from; i++) { + if (success == ack) { + break; } - - List nodesHashes = getSortedNodes(id, from); - int success = 0; - Response[] responses = new Response[ack]; - - - for (int i = 0; i < from; i++) { - if (success == ack) { - break; - } - String node = nodesHashes.get(i); - try { - if (node.equals(selfUrl)) { - responses[success] = handleLocalRequest(request, id); - } else { - responses[success] = redirectRequest(method, id, node, request); - } - success++; - } catch (InterruptedException | IOException e) { - logger.error("Error during sending request", e); - Thread.currentThread().interrupt(); + String node = nodesHashes.get(i); + try { + if (node.equals(selfUrl)) { + responses[success] = handleLocalRequest(request, id); + } else { + responses[success] = redirectRequest(method, id, node, request); } - - } - if (success < ack ) { - session.sendResponse(new Response(NOT_ENOUGH_REPLICAS, Response.EMPTY)); - return; + success++; + } catch (InterruptedException | IOException e) { + logger.error("Error during sending request", e); + Thread.currentThread().interrupt(); } + } + if (success < ack) { + session.sendResponse(new Response(NOT_ENOUGH_REPLICAS, Response.EMPTY)); + return; + } - if (request.getMethod() == Request.METHOD_GET) { - Response response = validateGetRequests(ack, responses); - session.sendResponse(response); - return; - } - String responseStatusCode = getResponseByCode(responses[0].getStatus()); - Response response = new Response(responseStatusCode, responses[0].getBody()); + if (request.getMethod() == Request.METHOD_GET) { + Response response = validateGetRequests(ack, responses); session.sendResponse(response); - } catch (Exception e) { - logger.error("Unexpected error when processing request", e); - sendError(session, e); + return; } + String responseStatusCode = getResponseByCode(responses[0].getStatus()); + Response response = new Response(responseStatusCode, responses[0].getBody()); + session.sendResponse(response); } private Response validateGetRequests(int ack, Response[] responses) { @@ -154,8 +153,6 @@ private Response validateGetRequests(int ack, Response[] responses) { notFound++; if (timestamp != 0L && latestTimestamp < timestamp) { latestTimestamp = timestamp; - // latestResponse = response; - // latestResponse.setBody(null); isLatestFailed = true; } continue; @@ -175,7 +172,6 @@ private Response validateGetRequests(int ack, Response[] responses) { return response; } - private void sendError(HttpSession session, Exception e) { try { String responseCode = e.getClass() == HttpException.class ? Response.BAD_REQUEST : Response.INTERNAL_ERROR; @@ -192,10 +188,9 @@ private List getSortedNodes(String key, Integer from) { for (String node : nodes) { nodesHashes.put(Hash.murmur3(node + key), node); } - return nodesHashes.values().stream().limit(from).toList(); + return nodesHashes.values().stream().limit(from).collect(Collectors.toList()); } - private static HttpServerConfig createServerConfig(ServiceConfig serviceConfig) { HttpServerConfig httpServerConfig = new HttpServerConfig(); AcceptorConfig acceptorConfig = new AcceptorConfig(); @@ -266,12 +261,11 @@ private Response redirectRequest(AllowedMethods method, ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofByteArray(body)) .header(REDIRECTED_HEADER, "true") - .header("X-timestamp", String.valueOf(System.currentTimeMillis()) + .header("X-timestamp", request.getHeader(TIMESTAMP_HEADER) ).build(), HttpResponse.BodyHandlers.ofByteArray()); Response result = new Response(getResponseByCode(response.statusCode()), response.body()); Optional respTimestamp = response.headers().firstValue("X-timestamp"); - respTimestamp.ifPresent(s -> System.out.println("DBG: " + s.toString())); - respTimestamp.ifPresent(v-> result.addHeader(TIMESTAMP_HEADER + v)); + respTimestamp.ifPresent(v -> result.addHeader(TIMESTAMP_HEADER + v)); return result; } diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTable.java index 6d00834b7..c91dc5fd7 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTable.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/SSTable.java @@ -150,7 +150,7 @@ EntryWithTimestamp get(final MemorySegment key) { return new BaseEntryWithTimestamp<>(key, null, timestamp); } else { // Get value - // offset += Long.BYTES; + offset += Long.BYTES; final MemorySegment value = data.asSlice(offset, valueLength); // Get timestamp diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/TableSet.java index e6cd6994e..0c31968bb 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/TableSet.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/dao/TableSet.java @@ -176,11 +176,6 @@ EntryWithTimestamp get(final MemorySegment key) { return null; } -/* private static EntryWithTimestamp swallowTombstone(final EntryWithTimestamp entry) { - if (entry.value() == null) return new BaseEntryWithTimestamp<>(entry.key(), null, entry.timestamp());; - return new BaseEntryWithTimestamp<>(entry.key(), entry.value(), entry.timestamp()); - }*/ - EntryWithTimestamp upsert(final EntryWithTimestamp entry) { return memTable.upsert(entry); } @@ -201,4 +196,4 @@ Iterator> allSSTableEntries() { return new MergingEntryIterator(iterators); } -} \ No newline at end of file +} From 2f6b6cb8a5c75e6fedbcca5571020a7727b5d36b Mon Sep 17 00:00:00 2001 From: lena Date: Sun, 31 Mar 2024 22:36:10 +0300 Subject: [PATCH 12/24] [stage-4] fix codeclimate --- .../test/elenakhodosova/HttpServerImpl.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index f04574877..704768e57 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -74,9 +74,17 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti session.sendResponse(new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY)); return; } + String fromStr = request.getParameter("from="); + String ackStr = request.getParameter("ack="); + int from = fromStr == null || fromStr.isEmpty() ? nodes.size() : Integer.parseInt(fromStr); + int ack = ackStr == null || ackStr.isEmpty() ? from / 2 + 1 : Integer.parseInt(ackStr); + if (ack == 0 || from == 0 || ack > from || from > nodes.size()) { + session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); + return; + } executorService.execute(() -> { try { - processRequest(request, session, id, method); + processRequest(request, session, id, method, from, ack); } catch (Exception e) { logger.error("Unexpected error when processing request", e); sendError(session, e); @@ -88,7 +96,8 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti } } - private void processRequest(Request request, HttpSession session, String id, AllowedMethods method) throws IOException { + private void processRequest(Request request, HttpSession session, String id, + AllowedMethods method, int from, int ack) throws IOException { String isRedirected = request.getHeader(REDIRECTED_HEADER); if (isRedirected != null) { @@ -96,14 +105,6 @@ private void processRequest(Request request, HttpSession session, String id, All return; } - String fromStr = request.getParameter("from="); - String ackStr = request.getParameter("ack="); - int from = fromStr == null || fromStr.isEmpty() ? nodes.size() : Integer.parseInt(fromStr); - int ack = ackStr == null || ackStr.isEmpty() ? from / 2 + 1 : Integer.parseInt(ackStr); - if (ack == 0 || from == 0 || ack > from || from > nodes.size()) { - session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); - return; - } List nodesHashes = getSortedNodes(id, from); int success = 0; From caa2bd1d2be64ad3f10e3ca65a880f04b50b3d4a Mon Sep 17 00:00:00 2001 From: lena Date: Sun, 31 Mar 2024 22:38:00 +0300 Subject: [PATCH 13/24] [stage-4] fix codeclimate --- src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index 704768e57..89c6cf449 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -105,7 +105,6 @@ private void processRequest(Request request, HttpSession session, String id, return; } - List nodesHashes = getSortedNodes(id, from); int success = 0; Response[] responses = new Response[ack]; From 643eb6d987fd3fed13addc252924cb4bf8b43aad Mon Sep 17 00:00:00 2001 From: lena Date: Sun, 31 Mar 2024 22:44:03 +0300 Subject: [PATCH 14/24] [stage-4] fix respectRF --- .../test/elenakhodosova/HttpServerImpl.java | 45 +++++++++++-------- .../itmo/test/elenakhodosova/ServiceImpl.java | 3 +- .../test/elenakhodosova/reports/Stage4.md | 0 3 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage4.md diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index 89c6cf449..adda945bf 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -6,7 +6,9 @@ import one.nio.http.HttpSession; import one.nio.http.Request; import one.nio.http.Response; +import one.nio.net.Session; import one.nio.server.AcceptorConfig; +import one.nio.server.SelectorThread; import one.nio.util.Hash; import one.nio.util.Utf8; import org.slf4j.Logger; @@ -25,6 +27,7 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -99,34 +102,31 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti private void processRequest(Request request, HttpSession session, String id, AllowedMethods method, int from, int ack) throws IOException { String isRedirected = request.getHeader(REDIRECTED_HEADER); - if (isRedirected != null) { session.sendResponse(handleLocalRequest(request, id)); return; } - List nodesHashes = getSortedNodes(id, from); - int success = 0; - Response[] responses = new Response[ack]; + List sortedNodes = getSortedNodes(id, from); + List responses = new ArrayList<>(ack); request.addHeader(TIMESTAMP_HEADER + System.currentTimeMillis()); for (int i = 0; i < from; i++) { - if (success == ack) { + if (responses.size() == ack) { break; } - String node = nodesHashes.get(i); + String node = sortedNodes.get(i); try { if (node.equals(selfUrl)) { - responses[success] = handleLocalRequest(request, id); + responses.add(handleLocalRequest(request, id)); } else { - responses[success] = redirectRequest(method, id, node, request); + responses.add(redirectRequest(method, id, node, request)); } - success++; } catch (InterruptedException | IOException e) { logger.error("Error during sending request", e); Thread.currentThread().interrupt(); } } - if (success < ack) { + if (responses.size() < ack) { session.sendResponse(new Response(NOT_ENOUGH_REPLICAS, Response.EMPTY)); return; } @@ -136,12 +136,12 @@ private void processRequest(Request request, HttpSession session, String id, session.sendResponse(response); return; } - String responseStatusCode = getResponseByCode(responses[0].getStatus()); - Response response = new Response(responseStatusCode, responses[0].getBody()); + String responseStatusCode = getResponseByCode(responses.getFirst().getStatus()); + Response response = new Response(responseStatusCode, responses.getFirst().getBody()); session.sendResponse(response); } - private Response validateGetRequests(int ack, Response[] responses) { + private Response validateGetRequests(int ack, List responses) { int notFound = 0; long latestTimestamp = 0L; Response latestResponse = null; @@ -163,13 +163,10 @@ private Response validateGetRequests(int ack, Response[] responses) { latestResponse = response; } } - Response response; if (notFound == ack || isLatestFailed || latestResponse == null) { - response = new Response(Response.NOT_FOUND, Response.EMPTY); - } else { - response = new Response(Response.OK, latestResponse.getBody()); + return new Response(Response.NOT_FOUND, Response.EMPTY); } - return response; + return new Response(Response.OK, latestResponse.getBody()); } private void sendError(HttpSession session, Exception e) { @@ -288,6 +285,18 @@ private Response handleLocalRequest(Request request, String id) { } } + @Override + public synchronized void stop() { + super.stop(); + for (SelectorThread selector : selectors) { + if (selector.selector.isOpen()) { + for (Session session : selector.selector) { + session.close(); + } + } + } + } + private boolean isParamIncorrect(String param) { return param == null || param.isEmpty(); } diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java index 49b621adc..db83de47b 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java @@ -37,7 +37,7 @@ public CompletableFuture start() throws IOException { } @Override - public CompletableFuture stop() throws IOException { + public synchronized CompletableFuture stop() throws IOException { if (isServiceStopped.getAndSet(true)) { return CompletableFuture.completedFuture(null); } @@ -49,6 +49,7 @@ public CompletableFuture stop() throws IOException { } } catch (InterruptedException e) { Thread.currentThread().interrupt(); + executorService.shutdownNow(); } dao.close(); return CompletableFuture.completedFuture(null); diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage4.md b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage4.md new file mode 100644 index 000000000..e69de29bb From b47c05d2423e8c80fc318f20a15480d572ac86b0 Mon Sep 17 00:00:00 2001 From: lena Date: Mon, 1 Apr 2024 11:50:55 +0300 Subject: [PATCH 15/24] [stage-4] fix respectRF --- .../ru/vk/itmo/test/elenakhodosova/Server.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java index cfcbba2a0..22a2332db 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -26,9 +27,10 @@ public final class Server { private Server() { } - +//todo public static void main(String[] args) throws IOException { List nodesConfigs = new ArrayList<>(NODES_COUNT); + ExecutorService executorService = ExecutorServiceConfig.newExecutorService(); Map nodes = new HashMap<>(); int nodePort = 8080; @@ -50,12 +52,14 @@ public static void main(String[] args) throws IOException { } for (ServiceConfig config : nodesConfigs) { ServiceImpl server = new ServiceImpl(config); - try { - server.start().get(1, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - logger.error("Unable to start service instance: ", e); - Thread.currentThread().interrupt(); - } + executorService.execute(() -> { + try { + server.start().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException | IOException e) { + logger.error("Unable to start service instance: ", e); + Thread.currentThread().interrupt(); + } + }); } } } From e356d55668efcbc68d850cddb63d2133213d4bf1 Mon Sep 17 00:00:00 2001 From: lena Date: Tue, 2 Apr 2024 23:03:48 +0300 Subject: [PATCH 16/24] [stage-4] add report --- .../test/elenakhodosova/HttpServerImpl.java | 9 +- .../vk/itmo/test/elenakhodosova/Server.java | 2 +- .../test/elenakhodosova/reports/Stage4.md | 89 + .../data/stage4/profile-get-25000-alloc.html | 3300 ++++++++++ .../data/stage4/profile-get-25000-lock.html | 1070 +++ .../data/stage4/profile-get-25000.html | 5092 ++++++++++++++ .../data/stage4/profile-put-20000-alloc.html | 3335 ++++++++++ .../data/stage4/profile-put-20000-lock.html | 1114 ++++ .../data/stage4/profile-put-20000.html | 5859 +++++++++++++++++ 9 files changed, 19868 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-get-25000-alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-get-25000-lock.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-get-25000.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-put-20000-alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-put-20000-lock.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-put-20000.html diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index adda945bf..952efb63c 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -27,12 +27,14 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.TreeMap; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.stream.Collectors; @@ -44,6 +46,7 @@ public class HttpServerImpl extends HttpServer { private static final String NOT_ENOUGH_REPLICAS = "504 Not Enough Replicas"; private static final String TIMESTAMP_HEADER = "X-timestamp: "; private static final String REDIRECTED_HEADER = "X-redirected"; + private final static int THREADS = Runtime.getRuntime().availableProcessors(); private final ExecutorService executorService; private static final Logger logger = LoggerFactory.getLogger(HttpServerImpl.class); @@ -61,7 +64,11 @@ public HttpServerImpl(ServiceConfig config, ReferenceDao dao, ExecutorService ex this.executorService = executorService; this.selfUrl = config.selfUrl(); this.nodes = config.clusterUrls(); - this.client = HttpClient.newHttpClient(); + this.client = HttpClient.newBuilder() + .executor(Executors.newFixedThreadPool(THREADS)) + .connectTimeout(Duration.ofMillis(500)) + .version(HttpClient.Version.HTTP_1_1) + .build(); } @Override diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java index 22a2332db..da1e6c038 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java @@ -27,7 +27,7 @@ public final class Server { private Server() { } -//todo + public static void main(String[] args) throws IOException { List nodesConfigs = new ArrayList<>(NODES_COUNT); ExecutorService executorService = ExecutorServiceConfig.newExecutorService(); diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage4.md b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage4.md index e69de29bb..1215623fb 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage4.md +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage4.md @@ -0,0 +1,89 @@ +# Этап 4. Репликация + +## PUT +В этом этапе я изменила логику создания Http клиентов, поэтому для объективности сравнения нагрузим сперва с кодом идентичным 3 этапу: +Было: + +| RPS | Avg Latency | +|-------|-------------| +| 20000 | 1.69ms | +| 28000 | 5.92ms | +| 32000 | 6.42ms | +Стало: + +| RPS | Avg Latency | +|-------|-------------| +| 10000 | 1.84ms | +| 15000 | 2.12ms | +| 20000 | 16.08ms | + +Очевидно, что средняя latency ухудшилась, так как теперь запрос рассылаеется по нескольким нодам и необходимо дождаться ответа от них. + +Теперь создадим HttpClient с FixedThreadPool(availableProcessors) в качестве executor. + +| RPS | Avg Latency | +|-------|-------------| +| 15000 | 1.84ms | +| 20000 | 2.26ms | +| 25000 | 259.14ms | + +Итого, средняя latency заметно приблизилась к результатам 3 этапа + +Посмотрим на результаты профилирования для 20000 rps +```dtd +Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.57ms + 75.000% 2.30ms + 90.000% 3.12ms + 99.000% 6.86ms + 99.900% 10.29ms + 99.990% 13.61ms + 99.999% 15.94ms +100.000% 17.81ms +``` + +[CPU profile](data/stage4/profile-put-20000.html) +[Alloc profile](data/stage4/profile-put-20000-alloc.html) +[Lock profile](data/stage4/profile-put-20000-lock.html) + +Около 30% сэмплов занимает park при взятии задачи из очереди запросов +По сравнению с предыдущим этапом количество сэмплов на ThreadPoolExecutor getTask сократилось с 41% до 25%, а на редирект +(redirectRequest) наоборот с 12% выросло до 20% +Наибольшую часть занимают аллокации при редиректе запроса 59% (против 50% на предыдущем этапе) +Аналогично с 3 этапом основное время ожидания блокировок приходится на HttpClient (SequentialScheduler.run(), HttpClientImpl.send(), SelectorManager.run()) +Из них 34% во время редиректа запроса. + +## GET +Аналогично сравним GET +Было: + +| RPS | Avg Latency | +|-------|-------------| +| 25000 | 1.65ms | +| 30000 | 2.46ms | +| 35000 | 19.83ms | + +Стало: + +| RPS | Avg Latency | +|-------|-------------| +| 10000 | 1.73ms | +| 15000 | 2.12ms | +| 20000 | 10.30ms | +| 25000 | 2.88s | + +Добавим executor в HttpClient: + +| RPS | Avg Latency | +|-------|-------------| +| 25000 | 1.83ms | +| 28000 | 1.99ms | +| 30000 | 35.38ms | + +Ситуация аналогичная с PUT, latency ухудшается, так как необходимо собрать кворум запросов. + +[CPU profile](data/stage4/profile-get-25000.html) +[Alloc profile](data/stage4/profile-get-25000-alloc.html) +[Lock profile](data/stage4/profile-get-25000-lock.html) + +Аналогично с PUT чуть больше 30% сэмплов уходит на park, на редирект уходит 20%, что касается локов - 36% на редирект, а было 32%. \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-get-25000-alloc.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-get-25000-alloc.html new file mode 100644 index 000000000..127987aad --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-get-25000-alloc.html @@ -0,0 +1,3300 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-get-25000-lock.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-get-25000-lock.html new file mode 100644 index 000000000..68af4e4cb --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-get-25000-lock.html @@ -0,0 +1,1070 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-get-25000.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-get-25000.html new file mode 100644 index 000000000..49f187724 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-get-25000.html @@ -0,0 +1,5092 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-put-20000-alloc.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-put-20000-alloc.html new file mode 100644 index 000000000..29d08c48e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-put-20000-alloc.html @@ -0,0 +1,3335 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-put-20000-lock.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-put-20000-lock.html new file mode 100644 index 000000000..50faf316f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-put-20000-lock.html @@ -0,0 +1,1114 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-put-20000.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-put-20000.html new file mode 100644 index 000000000..629ffc642 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage4/profile-put-20000.html @@ -0,0 +1,5859 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ From b93905bad25b716b99ab69fe63b716c5be0ad1a8 Mon Sep 17 00:00:00 2001 From: lena Date: Tue, 2 Apr 2024 23:06:30 +0300 Subject: [PATCH 17/24] [stage-4] fix codeclimate --- .../java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index 952efb63c..c28931cc7 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -46,7 +46,7 @@ public class HttpServerImpl extends HttpServer { private static final String NOT_ENOUGH_REPLICAS = "504 Not Enough Replicas"; private static final String TIMESTAMP_HEADER = "X-timestamp: "; private static final String REDIRECTED_HEADER = "X-redirected"; - private final static int THREADS = Runtime.getRuntime().availableProcessors(); + private static final int THREADS = Runtime.getRuntime().availableProcessors(); private final ExecutorService executorService; private static final Logger logger = LoggerFactory.getLogger(HttpServerImpl.class); From d3b3c7436b217ec6bd7e7c83c8b4f209fec8017c Mon Sep 17 00:00:00 2001 From: lena Date: Wed, 10 Apr 2024 10:37:25 +0300 Subject: [PATCH 18/24] [stage-5] wip working code --- .../test/elenakhodosova/HttpServerImpl.java | 205 ++++++++++++------ .../itmo/test/elenakhodosova/ServiceImpl.java | 2 +- 2 files changed, 139 insertions(+), 68 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index c28931cc7..bdb1b94f5 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -28,14 +28,18 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.stream.Collectors; public class HttpServerImpl extends HttpServer { @@ -46,6 +50,7 @@ public class HttpServerImpl extends HttpServer { private static final String NOT_ENOUGH_REPLICAS = "504 Not Enough Replicas"; private static final String TIMESTAMP_HEADER = "X-timestamp: "; private static final String REDIRECTED_HEADER = "X-redirected"; + public static final String X_TOMB_HEADER = "X-tomb: "; private static final int THREADS = Runtime.getRuntime().availableProcessors(); private final ExecutorService executorService; private static final Logger logger = LoggerFactory.getLogger(HttpServerImpl.class); @@ -65,10 +70,10 @@ public HttpServerImpl(ServiceConfig config, ReferenceDao dao, ExecutorService ex this.selfUrl = config.selfUrl(); this.nodes = config.clusterUrls(); this.client = HttpClient.newBuilder() - .executor(Executors.newFixedThreadPool(THREADS)) - .connectTimeout(Duration.ofMillis(500)) - .version(HttpClient.Version.HTTP_1_1) - .build(); + .executor(Executors.newFixedThreadPool(THREADS)) + .connectTimeout(Duration.ofMillis(500)) + .version(HttpClient.Version.HTTP_1_1) + .build(); } @Override @@ -89,7 +94,7 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti int from = fromStr == null || fromStr.isEmpty() ? nodes.size() : Integer.parseInt(fromStr); int ack = ackStr == null || ackStr.isEmpty() ? from / 2 + 1 : Integer.parseInt(ackStr); if (ack == 0 || from == 0 || ack > from || from > nodes.size()) { - session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); + sendResponse(session, new Response(Response.BAD_REQUEST, Response.EMPTY)); return; } executorService.execute(() -> { @@ -98,6 +103,7 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti } catch (Exception e) { logger.error("Unexpected error when processing request", e); sendError(session, e); + Thread.currentThread().interrupt(); } }); } catch (RejectedExecutionException e) { @@ -107,73 +113,113 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti } private void processRequest(Request request, HttpSession session, String id, - AllowedMethods method, int from, int ack) throws IOException { + AllowedMethods method, int from, int ack) + throws ExecutionException, InterruptedException { String isRedirected = request.getHeader(REDIRECTED_HEADER); if (isRedirected != null) { - session.sendResponse(handleLocalRequest(request, id)); + sendResponse(session, handleLocalRequest(request, id).get()); return; } List sortedNodes = getSortedNodes(id, from); - List responses = new ArrayList<>(ack); + AtomicInteger success = new AtomicInteger(0); + AtomicInteger successRequests = new AtomicInteger(0); + AtomicBoolean requestHandled = new AtomicBoolean(false); + AtomicInteger lastSuccessResp = new AtomicInteger(); + CompletableFuture[] responses = new CompletableFuture[from]; + AtomicReferenceArray responseArray = new AtomicReferenceArray<>(from); request.addHeader(TIMESTAMP_HEADER + System.currentTimeMillis()); for (int i = 0; i < from; i++) { - if (responses.size() == ack) { - break; - } + final int currentIter = i; String node = sortedNodes.get(i); try { if (node.equals(selfUrl)) { - responses.add(handleLocalRequest(request, id)); + responses[currentIter] = handleLocalRequest(request, id); } else { - responses.add(redirectRequest(method, id, node, request)); + responses[currentIter] = redirectRequest(method, id, node, request); } + + CompletableFuture futureResult = responses[currentIter].whenCompleteAsync((response, throwable) -> { + responseArray.set(currentIter, (Response) response); + successRequests.incrementAndGet(); + if (throwable == null) { + success.incrementAndGet(); + lastSuccessResp.set(currentIter); + } + int lastSuccessCount = success.get(); + if (validateReplicas(session, lastSuccessCount, successRequests, requestHandled, from, ack)) { + return; + } + + if (lastSuccessCount >= ack && requestHandled.compareAndSet(false, true)) { + handleSendResponse(request, session, ack, responseArray, lastSuccessResp); + } + }); + validateFuture(futureResult); } catch (InterruptedException | IOException e) { logger.error("Error during sending request", e); Thread.currentThread().interrupt(); } } - if (responses.size() < ack) { - session.sendResponse(new Response(NOT_ENOUGH_REPLICAS, Response.EMPTY)); - return; + } + + private boolean validateReplicas(HttpSession session, + int lastSuccessCount, + AtomicInteger successRequests, + AtomicBoolean requestHandled, + int from, + int ack) { + if (successRequests.get() == from && lastSuccessCount < ack) { + if (requestHandled.compareAndSet(false, true)) { + sendResponse(session, new Response(NOT_ENOUGH_REPLICAS, Response.EMPTY)); + } + return true; } + return false; + } + private void handleSendResponse(Request request, + HttpSession session, + int ack, + AtomicReferenceArray responseArray, + AtomicInteger lastSuccessResp) { if (request.getMethod() == Request.METHOD_GET) { - Response response = validateGetRequests(ack, responses); - session.sendResponse(response); + sendResponse(session, validateGetRequests(ack, responseArray)); return; } - String responseStatusCode = getResponseByCode(responses.getFirst().getStatus()); - Response response = new Response(responseStatusCode, responses.getFirst().getBody()); - session.sendResponse(response); + Response result = new Response( + getResponseByCode(responseArray.get(lastSuccessResp.get()).getStatus()), + responseArray.get(lastSuccessResp.get()).getBody()); + sendResponse(session, result); } - private Response validateGetRequests(int ack, List responses) { + private Response validateGetRequests(int ack, AtomicReferenceArray responseArray) { int notFound = 0; long latestTimestamp = 0L; Response latestResponse = null; - boolean isLatestFailed = false; - for (Response response : responses) { - long timestamp = response.getHeader(TIMESTAMP_HEADER) == null ? 0L - : Long.parseLong(response.getHeader(TIMESTAMP_HEADER)); - if (response.getStatus() == HttpURLConnection.HTTP_NOT_FOUND) { + for (int j = 0; j < responseArray.length(); j++) { + Response currentResponse = responseArray.get(j); + if (currentResponse == null) { + continue; + } + if (currentResponse.getStatus() == HttpURLConnection.HTTP_NOT_FOUND) { notFound++; - if (timestamp != 0L && latestTimestamp < timestamp) { - latestTimestamp = timestamp; - isLatestFailed = true; - } continue; } - isLatestFailed = false; + long timestamp = Long.parseLong(currentResponse.getHeader(TIMESTAMP_HEADER)); if (timestamp > latestTimestamp) { latestTimestamp = timestamp; - latestResponse = response; + latestResponse = currentResponse; } } - if (notFound == ack || isLatestFailed || latestResponse == null) { + if (notFound == ack || latestResponse == null + || (latestResponse.getBody().length == 0 + && latestResponse.getHeader(X_TOMB_HEADER) != null) + ) { return new Response(Response.NOT_FOUND, Response.EMPTY); + } else { + return new Response(Response.OK, latestResponse.getBody()); } - return new Response(Response.OK, latestResponse.getBody()); } private void sendError(HttpSession session, Exception e) { @@ -215,7 +261,8 @@ public Response getEntity(String id) { Response response; if (value.value() == null) { - response = new Response(Response.NOT_FOUND, Response.EMPTY); + response = new Response(Response.OK, Response.EMPTY); + response.addHeader("X-tomb: true"); } else { response = Response.ok(value.value().toArray(ValueLayout.JAVA_BYTE)); } @@ -254,41 +301,59 @@ public Response deleteEntity(String id, long timestamp) { } } - private Response redirectRequest(AllowedMethods method, - String id, - String clusterUrl, - Request request) throws InterruptedException, IOException { + private CompletableFuture redirectRequest(AllowedMethods method, + String id, + String clusterUrl, + Request request) throws InterruptedException, IOException { byte[] body = request.getBody(); - HttpResponse response = client.send(HttpRequest.newBuilder() - .uri(URI.create(clusterUrl + PATH_NAME + "?id=" + id)) - .method(method.name(), body == null - ? HttpRequest.BodyPublishers.noBody() - : HttpRequest.BodyPublishers.ofByteArray(body)) + HttpRequest redirectedRequest = HttpRequest.newBuilder(URI.create(clusterUrl + PATH_NAME + "?id=" + id)) + .method( + method.name(), + body == null + ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofByteArray(body) + ) .header(REDIRECTED_HEADER, "true") - .header("X-timestamp", request.getHeader(TIMESTAMP_HEADER) - ).build(), HttpResponse.BodyHandlers.ofByteArray()); - Response result = new Response(getResponseByCode(response.statusCode()), response.body()); - Optional respTimestamp = response.headers().firstValue("X-timestamp"); - respTimestamp.ifPresent(v -> result.addHeader(TIMESTAMP_HEADER + v)); - return result; + .header("X-timestamp", request.getHeader(TIMESTAMP_HEADER)) + .build(); + return client.sendAsync(redirectedRequest, HttpResponse.BodyHandlers.ofByteArray()) + .thenApply(response -> { + Response result = new Response(getResponseByCode(response.statusCode()), response.body()); + Optional respTimestamp = response.headers().firstValue("X-timestamp"); + respTimestamp.ifPresent(v -> result.addHeader(TIMESTAMP_HEADER + v)); + Optional headerTomb = response.headers().firstValue("X-tomb"); + headerTomb.ifPresent(v -> result.addHeader(X_TOMB_HEADER + v)); + return result; + }); } - private Response handleLocalRequest(Request request, String id) { - String timestampHeader = request.getHeader(TIMESTAMP_HEADER); - long timestamp = timestampHeader == null ? System.currentTimeMillis() : Long.parseLong(timestampHeader); - switch (request.getMethod()) { - case Request.METHOD_GET -> { - return getEntity(id); - } - case Request.METHOD_PUT -> { - return putEntity(id, request, timestamp); - } - case Request.METHOD_DELETE -> { - return deleteEntity(id, timestamp); - } - default -> { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + private CompletableFuture handleLocalRequest(Request request, String id) { + return CompletableFuture.supplyAsync(() -> { + String timestampHeader = request.getHeader(TIMESTAMP_HEADER); + long timestamp = timestampHeader == null ? System.currentTimeMillis() : Long.parseLong(timestampHeader); + switch (request.getMethod()) { + case Request.METHOD_GET -> { + return getEntity(id); + } + case Request.METHOD_PUT -> { + return putEntity(id, request, timestamp); + } + case Request.METHOD_DELETE -> { + return deleteEntity(id, timestamp); + } + default -> { + return new Response(Response.BAD_REQUEST, Response.EMPTY); + } } + }, executorService); + } + + private static void sendResponse(HttpSession session, Response response) { + try { + session.sendResponse(response); + } catch (IOException e) { + logger.error("Error send response", e); + session.close(); } } @@ -321,6 +386,12 @@ private String getResponseByCode(int code) { }; } + private void validateFuture(CompletableFuture future) { + if (future == null) { + logger.warn("Future is null"); + } + } + private AllowedMethods getMethod(int method) { if (method == 1) return AllowedMethods.GET; if (method == 5) return AllowedMethods.PUT; diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java index db83de47b..9510f0e69 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java @@ -55,7 +55,7 @@ public synchronized CompletableFuture stop() throws IOException { return CompletableFuture.completedFuture(null); } - @ServiceFactory(stage = 4) + @ServiceFactory(stage = 5) public static class Factory implements ServiceFactory.Factory { @Override From a8b5fd265fcad4804eaec2f651de06520bdd2acf Mon Sep 17 00:00:00 2001 From: lena Date: Wed, 10 Apr 2024 23:57:27 +0300 Subject: [PATCH 19/24] [stage-5] fix codeclimate --- .../test/elenakhodosova/HttpServerImpl.java | 46 +++++++++---------- .../itmo/test/elenakhodosova/ServiceImpl.java | 2 +- .../test/elenakhodosova/reports/Stage5.md | 0 3 files changed, 24 insertions(+), 24 deletions(-) create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage5.md diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index bdb1b94f5..59c0177b1 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -33,7 +33,6 @@ import java.util.Optional; import java.util.TreeMap; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; @@ -99,6 +98,11 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti } executorService.execute(() -> { try { + String isRedirected = request.getHeader(REDIRECTED_HEADER); + if (isRedirected != null) { + sendResponse(session, handleLocalRequest(request, id).get()); + return; + } processRequest(request, session, id, method, from, ack); } catch (Exception e) { logger.error("Unexpected error when processing request", e); @@ -113,14 +117,7 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti } private void processRequest(Request request, HttpSession session, String id, - AllowedMethods method, int from, int ack) - throws ExecutionException, InterruptedException { - String isRedirected = request.getHeader(REDIRECTED_HEADER); - if (isRedirected != null) { - sendResponse(session, handleLocalRequest(request, id).get()); - return; - } - + AllowedMethods method, int from, int ack) { List sortedNodes = getSortedNodes(id, from); AtomicInteger success = new AtomicInteger(0); AtomicInteger successRequests = new AtomicInteger(0); @@ -133,12 +130,7 @@ private void processRequest(Request request, HttpSession session, String id, final int currentIter = i; String node = sortedNodes.get(i); try { - if (node.equals(selfUrl)) { - responses[currentIter] = handleLocalRequest(request, id); - } else { - responses[currentIter] = redirectRequest(method, id, node, request); - } - + responses[currentIter] = routeRequest(node, request, id, method); CompletableFuture futureResult = responses[currentIter].whenCompleteAsync((response, throwable) -> { responseArray.set(currentIter, (Response) response); successRequests.incrementAndGet(); @@ -163,12 +155,20 @@ private void processRequest(Request request, HttpSession session, String id, } } + private CompletableFuture routeRequest(String node, Request request, String id, AllowedMethods method) + throws IOException, InterruptedException { + if (node.equals(selfUrl)) { + return handleLocalRequest(request, id); + } + return redirectRequest(method, id, node, request); + } + private boolean validateReplicas(HttpSession session, - int lastSuccessCount, - AtomicInteger successRequests, - AtomicBoolean requestHandled, - int from, - int ack) { + int lastSuccessCount, + AtomicInteger successRequests, + AtomicBoolean requestHandled, + int from, + int ack) { if (successRequests.get() == from && lastSuccessCount < ack) { if (requestHandled.compareAndSet(false, true)) { sendResponse(session, new Response(NOT_ENOUGH_REPLICAS, Response.EMPTY)); @@ -198,7 +198,7 @@ private Response validateGetRequests(int ack, AtomicReferenceArray res long latestTimestamp = 0L; Response latestResponse = null; for (int j = 0; j < responseArray.length(); j++) { - Response currentResponse = responseArray.get(j); + Response currentResponse = responseArray.get(j); if (currentResponse == null) { continue; } @@ -304,7 +304,7 @@ public Response deleteEntity(String id, long timestamp) { private CompletableFuture redirectRequest(AllowedMethods method, String id, String clusterUrl, - Request request) throws InterruptedException, IOException { + Request request) { byte[] body = request.getBody(); HttpRequest redirectedRequest = HttpRequest.newBuilder(URI.create(clusterUrl + PATH_NAME + "?id=" + id)) .method( @@ -359,7 +359,6 @@ private static void sendResponse(HttpSession session, Response response) { @Override public synchronized void stop() { - super.stop(); for (SelectorThread selector : selectors) { if (selector.selector.isOpen()) { for (Session session : selector.selector) { @@ -367,6 +366,7 @@ public synchronized void stop() { } } } + super.stop(); } private boolean isParamIncorrect(String param) { diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java index 9510f0e69..5f5f0afb3 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java @@ -48,8 +48,8 @@ public synchronized CompletableFuture stop() throws IOException { executorService.shutdownNow(); } } catch (InterruptedException e) { - Thread.currentThread().interrupt(); executorService.shutdownNow(); + Thread.currentThread().interrupt(); } dao.close(); return CompletableFuture.completedFuture(null); diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage5.md b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage5.md new file mode 100644 index 000000000..e69de29bb From e2c792b420f9b6becafedf0e55f15fc88684daa7 Mon Sep 17 00:00:00 2001 From: lena Date: Thu, 11 Apr 2024 10:16:37 +0300 Subject: [PATCH 20/24] [stage-5] fix respectRf --- src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java index 5f5f0afb3..1e8159dee 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java @@ -33,6 +33,7 @@ public CompletableFuture start() throws IOException { executorService = ExecutorServiceConfig.newExecutorService(); server = new HttpServerImpl(config, dao, executorService); server.start(); + isServiceStopped.getAndSet(false); return CompletableFuture.completedFuture(null); } From bfdd97872740dbcb2591501570658f4aa2501130 Mon Sep 17 00:00:00 2001 From: lena Date: Tue, 16 Apr 2024 21:40:27 +0300 Subject: [PATCH 21/24] [stage-5] add report --- .../test/elenakhodosova/reports/Stage5.md | 95 + .../data/stage5/profile-get-10000-alloc.html | 4062 +++++++++ .../data/stage5/profile-get-10000-lock.html | 1358 +++ .../data/stage5/profile-get-10000.html | 7304 +++++++++++++++++ .../data/stage5/profile-put-10000-alloc.html | 3336 ++++++++ .../data/stage5/profile-put-10000-lock.html | 1141 +++ .../data/stage5/profile-put-10000.html | 5661 +++++++++++++ 7 files changed, 22957 insertions(+) create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-get-10000-alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-get-10000-lock.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-get-10000.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-put-10000-alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-put-10000-lock.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-put-10000.html diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage5.md b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage5.md index e69de29bb..771cd4353 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage5.md +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage5.md @@ -0,0 +1,95 @@ +# Этап 5. Асинхронное взаимодействие + +## PUT +Было: + +| RPS | Avg Latency | +|-------|-------------| +| 15000 | 1.84ms | +| 20000 | 2.26ms | +| 25000 | 259.14ms | + +Стало: + +| RPS | Avg Latency | +|-------|-------------| +| 10000 | 1.89ms | +| 12000 | 1.96ms | +| 15000 | 33.53ms | + +По сравнению с предыдущим этапом пропускная способность уменьшилась примерно на одну треть. + +Посмотрим на результаты профилирования для 10000 rps +```dtd + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.89ms 1.01ms 11.94ms 80.43% + Req/Sec 10.55k 1.36k 18.67k 78.58% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.66ms + 75.000% 2.28ms + 90.000% 3.07ms + 99.000% 6.09ms + 99.900% 9.10ms + 99.990% 10.67ms + 99.999% 11.62ms + 100.000% 11.95ms +``` + +[CPU profile](data/stage5/profile-put-10000.html) + +[Alloc profile](data/stage5/profile-put-10000-alloc.html) + +[Lock profile](data/stage5/profile-put-10000-lock.html) + +Распределение сэмплов особо не изменилось по сравнению с предыдущим этапом, но добавились сэмплы на работу CompletableFuture, +которые составляют чуть больше 6%. Примерно половина этих сэмплов - ожидание блокировок. +На аллокации, связанные с работой CompletableFuture уходит 34% сэмплов, это больше, чем на редирект (~19%), который занимал больше всего сэмплов в предыдущем этапе. +27% ожидания на блокировках происходит во время работы CompletableFuture. Но все же, суммарно блокировки при работе HttpClient (составлявшие бОльшую часть на предыдущем этапе) +занимают больше в процентном соотношении. + +## GET +Аналогично сравним GET +Было: + +| RPS | Avg Latency | +|-------|-------------| +| 25000 | 1.83ms | +| 28000 | 1.99ms | +| 30000 | 35.38ms | + +Стало: + +| RPS | Avg Latency | +|-------|-------------| +| 10000 | 1.84ms | +| 15000 | 2.14ms | +| 17000 | 2.57ms | +| 20000 | 3.15s | + +Посмотрим на результаты профилирования для 10000 rps +```dtd +Thread Stats Avg Stdev Max +/- Stdev + Latency 1.84ms 2.86ms 47.52ms 94.76% + Req/Sec 10.21k 2.67k 44.22k 91.25% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.26ms + 75.000% 1.81ms + 90.000% 2.49ms + 99.000% 15.66ms + 99.900% 33.79ms + 99.990% 43.23ms + 99.999% 45.89ms +100.000% 47.55ms +``` + +Ситуация аналогичная с PUT, latency ухудшается, а пропускная способность падает почти в 2 раза. + +[CPU profile](data/stage5/profile-get-10000.html) + +[Alloc profile](data/stage5/profile-get-10000-alloc.html) + +[Lock profile](data/stage5/profile-get-10000-lock.html) + +По сравнению с PUT на работу CompletableFuture уходит больше сэмплов (в процентном соотношении) - 33%. +С аллокациями ситуация аналогичная с PUT: ~34%. +Аналогично и с блокировками, время на ожидания при работе HttpClient больше, чем на CompletableFuture (~13% против ~40%). \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-get-10000-alloc.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-get-10000-alloc.html new file mode 100644 index 000000000..eabd31710 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-get-10000-alloc.html @@ -0,0 +1,4062 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-get-10000-lock.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-get-10000-lock.html new file mode 100644 index 000000000..65417c5a9 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-get-10000-lock.html @@ -0,0 +1,1358 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-get-10000.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-get-10000.html new file mode 100644 index 000000000..f1f346e71 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-get-10000.html @@ -0,0 +1,7304 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-put-10000-alloc.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-put-10000-alloc.html new file mode 100644 index 000000000..8f199337f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-put-10000-alloc.html @@ -0,0 +1,3336 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-put-10000-lock.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-put-10000-lock.html new file mode 100644 index 000000000..1032c1975 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-put-10000-lock.html @@ -0,0 +1,1141 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-put-10000.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-put-10000.html new file mode 100644 index 000000000..c2aac2401 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage5/profile-put-10000.html @@ -0,0 +1,5661 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ From 821c0848156545f523ca473847421e8258fbfa82 Mon Sep 17 00:00:00 2001 From: lena Date: Wed, 24 Apr 2024 21:55:46 +0300 Subject: [PATCH 22/24] [stage-6] add code --- .../test/elenakhodosova/HttpServerImpl.java | 132 ++++++++++++++---- .../itmo/test/elenakhodosova/ServiceImpl.java | 2 +- 2 files changed, 107 insertions(+), 27 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index 59c0177b1..57ec3d346 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -9,6 +9,7 @@ import one.nio.net.Session; import one.nio.server.AcceptorConfig; import one.nio.server.SelectorThread; +import one.nio.util.ByteArrayBuilder; import one.nio.util.Hash; import one.nio.util.Utf8; import org.slf4j.Logger; @@ -27,7 +28,9 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -50,6 +53,9 @@ public class HttpServerImpl extends HttpServer { private static final String TIMESTAMP_HEADER = "X-timestamp: "; private static final String REDIRECTED_HEADER = "X-redirected"; public static final String X_TOMB_HEADER = "X-tomb: "; + private static final char SEPARATOR = '\n'; + private static final byte[] CRLF = "\r\n".getBytes(StandardCharsets.UTF_8); + private static final byte[] EOF = "0\r\n\r\n".getBytes(StandardCharsets.UTF_8); private static final int THREADS = Runtime.getRuntime().availableProcessors(); private final ExecutorService executorService; private static final Logger logger = LoggerFactory.getLogger(HttpServerImpl.class); @@ -78,44 +84,82 @@ public HttpServerImpl(ServiceConfig config, ReferenceDao dao, ExecutorService ex @Override public void handleRequest(Request request, HttpSession session) throws IOException { try { - String id = request.getParameter("id="); - if (isParamIncorrect(id)) { - session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); - return; - } AllowedMethods method = getMethod(request.getMethod()); if (method == null) { session.sendResponse(new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY)); return; } - String fromStr = request.getParameter("from="); - String ackStr = request.getParameter("ack="); - int from = fromStr == null || fromStr.isEmpty() ? nodes.size() : Integer.parseInt(fromStr); - int ack = ackStr == null || ackStr.isEmpty() ? from / 2 + 1 : Integer.parseInt(ackStr); - if (ack == 0 || from == 0 || ack > from || from > nodes.size()) { - sendResponse(session, new Response(Response.BAD_REQUEST, Response.EMPTY)); - return; + + switch (request.getPath()) { + case PATH_NAME -> executeSingleRequest(request, session, method); + case "/v0/entities" -> executeRangeRequest(request, session); + default -> sendResponse(session, new Response(Response.BAD_REQUEST, Response.EMPTY)); } - executorService.execute(() -> { - try { - String isRedirected = request.getHeader(REDIRECTED_HEADER); - if (isRedirected != null) { - sendResponse(session, handleLocalRequest(request, id).get()); - return; - } - processRequest(request, session, id, method, from, ack); - } catch (Exception e) { - logger.error("Unexpected error when processing request", e); - sendError(session, e); - Thread.currentThread().interrupt(); - } - }); + } catch (RejectedExecutionException e) { logger.error("Request rejected", e); session.sendResponse(new Response(TOO_MANY_REQUESTS, Response.EMPTY)); } } + private void executeSingleRequest(Request request, HttpSession session, AllowedMethods method) { + executorService.execute(() -> { + try { + String fromStr = request.getParameter("from="); + String ackStr = request.getParameter("ack="); + int from = fromStr == null || fromStr.isEmpty() ? nodes.size() : Integer.parseInt(fromStr); + int ack = ackStr == null || ackStr.isEmpty() ? from / 2 + 1 : Integer.parseInt(ackStr); + if (ack == 0 || from == 0 || ack > from || from > nodes.size()) { + sendResponse(session, new Response(Response.BAD_REQUEST, Response.EMPTY)); + return; + } + String id = request.getParameter("id="); + if (isParamIncorrect(id)) { + session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); + return; + } + String isRedirected = request.getHeader(REDIRECTED_HEADER); + if (isRedirected != null) { + sendResponse(session, handleLocalRequest(request, id).get()); + return; + } + processRequest(request, session, id, method, from, ack); + } catch (Exception e) { + logger.error("Unexpected error when processing request", e); + sendError(session, e); + Thread.currentThread().interrupt(); + } + }); + } + + private void executeRangeRequest(Request request, HttpSession session) { + String start = request.getParameter("start="); + if (isParamIncorrect(start)) { + sendResponse(session, new Response(Response.BAD_REQUEST, Response.EMPTY)); + return; + } + String end = request.getParameter("end="); + + executorService.execute(() -> { + try { + Iterator> iterator = dao.get( + MemorySegment.ofArray(Utf8.toBytes(start)), + end == null ? null : MemorySegment.ofArray(Utf8.toBytes(end))); + byte[] headers = """ + HTTP/1.1 200 OK\r + Transfer-Encoding: chunked\r + Connection: close\r + \r + """.getBytes(StandardCharsets.UTF_8); + session.write(headers, 0, headers.length); + iterateOverChunks(session, iterator); + } catch (IOException e) { + logger.error("Error while handling range request for keys start={}, end={}", start, end, e); + sendResponse(session, new Response(Response.INTERNAL_ERROR, Response.EMPTY)); + } + }); + } + private void processRequest(Request request, HttpSession session, String id, AllowedMethods method, int from, int ack) { List sortedNodes = getSortedNodes(id, from); @@ -398,4 +442,40 @@ private AllowedMethods getMethod(int method) { if (method == 6) return AllowedMethods.DELETE; return null; } + + private void iterateOverChunks(HttpSession session, Iterator> iterator) { + try { + if (processChunk(session, iterator)) { + iterateOverChunks(session, iterator); + } + } catch (IOException e) { + logger.error("Error during iteration over chunks", e); + sendResponse(session, new Response(Response.INTERNAL_ERROR, Response.EMPTY)); + } + } + + private boolean processChunk(Session session, Iterator> iterator) + throws IOException { + if (iterator.hasNext()) { + EntryWithTimestamp entry = iterator.next(); + byte[] keyBytes = entry.key().toArray(ValueLayout.JAVA_BYTE); + byte[] valueBytes = entry.value().toArray(ValueLayout.JAVA_BYTE); + int length = keyBytes.length + valueBytes.length + 1; + ByteArrayBuilder bytesBuilder = new ByteArrayBuilder(); + bytesBuilder + .append(Utf8.toBytes(Integer.toHexString(length))) + .append(CRLF) + .append(keyBytes) + .append(SEPARATOR) + .append(valueBytes) + .append(CRLF); + byte[] bytes = bytesBuilder.toBytes(); + session.write(bytes, 0, bytes.length); + return true; + } else { + session.write(EOF, 0, EOF.length); + session.scheduleClose(); + return false; + } + } } diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java index 1e8159dee..e6745e9ac 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java @@ -56,7 +56,7 @@ public synchronized CompletableFuture stop() throws IOException { return CompletableFuture.completedFuture(null); } - @ServiceFactory(stage = 5) + @ServiceFactory(stage = 6) public static class Factory implements ServiceFactory.Factory { @Override From 0b2f2d7a9f617973dbf9c9a09f49cd7edd3f7178 Mon Sep 17 00:00:00 2001 From: lena Date: Wed, 1 May 2024 10:47:34 +0300 Subject: [PATCH 23/24] [stage-6] add report --- .../test/elenakhodosova/reports/Stage6.md | 17 + .../data/stage6/profile-get-range-lock.html | 260 +++++++++++ .../data/stage6/profile-get-range.html | 411 ++++++++++++++++++ .../data/stage6/profile-range-alloc.html | 361 +++++++++++++++ 4 files changed, 1049 insertions(+) create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage6.md create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range-lock.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range.html create mode 100644 src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-range-alloc.html diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage6.md b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage6.md new file mode 100644 index 000000000..4ccf9fb52 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage6.md @@ -0,0 +1,17 @@ +# Этап 6. Range-запросы + +C помощью wrk добавим записи в базу +```dtd +wrk2 -d 200 -t 1 -c 64 -R 2000 -L -s put.lua http://127.0.0.1:8080 +``` + +Далее получим весь диапазон запросов с помощью curl +``` +curl -v http://localhost:8080/v0/entities\?start\=1 +``` + +[CPU profile](data/stage6/profile-get-range.html) + +[Alloc profile](data/stage6/profile-range-alloc.html) + +[Lock profile](data/stage6/profile-get-range-lock.html) \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range-lock.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range-lock.html new file mode 100644 index 000000000..24b7ef49f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range-lock.html @@ -0,0 +1,260 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range.html new file mode 100644 index 000000000..872a9d8b3 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range.html @@ -0,0 +1,411 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-range-alloc.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-range-alloc.html new file mode 100644 index 000000000..6d56b794b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-range-alloc.html @@ -0,0 +1,361 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ From 90d5f128ec770783d508e75d70418a0b18149b11 Mon Sep 17 00:00:00 2001 From: lena Date: Fri, 3 May 2024 10:47:36 +0300 Subject: [PATCH 24/24] [stage-6] fix minor potential stackOverflow & report --- .../test/elenakhodosova/HttpServerImpl.java | 5 +- .../test/elenakhodosova/reports/Stage6.md | 16 +- .../data/stage6/profile-get-range.html | 878 +++++++++++++++--- .../data/stage6/profile-range-alloc.html | 204 ++-- ...ange-lock.html => profile-range-lock.html} | 34 +- 5 files changed, 927 insertions(+), 210 deletions(-) rename src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/{profile-get-range-lock.html => profile-range-lock.html} (93%) diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java index 57ec3d346..462b00f26 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -445,8 +445,9 @@ private AllowedMethods getMethod(int method) { private void iterateOverChunks(HttpSession session, Iterator> iterator) { try { - if (processChunk(session, iterator)) { - iterateOverChunks(session, iterator); + boolean callsCounter = true; + while (callsCounter) { + callsCounter = processChunk(session, iterator); } } catch (IOException e) { logger.error("Error during iteration over chunks", e); diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage6.md b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage6.md index 4ccf9fb52..b6d048d68 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage6.md +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage6.md @@ -2,7 +2,11 @@ C помощью wrk добавим записи в базу ```dtd -wrk2 -d 200 -t 1 -c 64 -R 2000 -L -s put.lua http://127.0.0.1:8080 +wrk2 -d 300 -t 1 -c 64 -R 4000 -L -s put.lua http://127.0.0.1:8080 +---------------------------------------------------------- +1199995 requests in 5.00m, 76.68MB read +Requests/sec: 3999.98 +Transfer/sec: 261.72KB ``` Далее получим весь диапазон запросов с помощью curl @@ -12,6 +16,14 @@ curl -v http://localhost:8080/v0/entities\?start\=1 [CPU profile](data/stage6/profile-get-range.html) +От всего времени выполнения запроса работа с dao занимает меньше процента, больше половины сэмплов уходит на Session.write() +Примерно 1/6 от запроса - работа с итератором + [Alloc profile](data/stage6/profile-range-alloc.html) -[Lock profile](data/stage6/profile-get-range-lock.html) \ No newline at end of file +На профиле аллокаций всего 16% сэмплов приходится на итератор, еще примерно 20% - выделения памяти при обработке чанка (toArray и toHexString), +все остальное - работа one.nio + +[Lock profile](data/stage6/profile-range-lock.html) + +По профилю локов видно, что все блокировки происходят во время работы с сессией \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range.html index 872a9d8b3..c181c27ac 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range.html +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range.html @@ -14,7 +14,7 @@ #status {left: 0} #match {right: 0} #reset {cursor: pointer} - #canvas {width: 100%; height: 368px} + #canvas {width: 100%; height: 1408px} @@ -32,7 +32,7 @@

Flame Graph

let root, rootLevel, px, pattern; let level0 = 0, left0 = 0, width0 = 0; let reverse = false; - const levels = Array(23); + const levels = Array(88); for (let h = 0; h < levels.length; h++) { levels[h] = []; } @@ -251,161 +251,797 @@

Flame Graph

const cpool = [ 'all', -' Assembler::ldp', +' AddPNode::Identity', +'!rena::contains', ' C2Compiler::compile_method', -'!ompilation::Compilation', +'!ProjNode::is_block_proj', +'!hunk::chop', +'\'next_chop', +'!lassFileParser::ClassFileParser', +'1parse_constant_pool', +'D_entries', +'7stream', +'!odeCache::gc_epoch', +'$EmitInfo::record_debug_info', +'"mpilation::Compilation', '-compile_java_method', '5method', '-emit_code_body', -'&e::Compile', +'7epilog', +'&e::Code_Gen', +'+mpile', ')Optimize', +')call_generator', +')disconnect_useless_nodes', +')identify_useful_nodes', +')optimize_loops', '\'Broker::compiler_thread_loop', '/invoke_compiler_on_method', '\'r::compile_method', -'"ncurrentGCThread::run', +'"nNKlassNode::Opcode', +'#currentGCThread::run', +'"untedLoopEndNode::bt', +' DIR_Chunk* GrowableArrayWithAllocator >::insert_sorted<&(DIR_Chunk::compare(DIR_Chunk* const&, DIR_Chunk* const&))>', +'!ebugInformationRecorder::add_safepoint', +':describe_scope', +'!irectiveSet::should_not_inline', +' ExceptionMark::ExceptionMark', ' G1ConcurrentRefineThread::run_service', '"PrimaryConcurrentRefineThread::wait_for_completed_buffers', -' InstanceKlass::find_method_index', +'!raphKit::clone_map', +'*set_map_clone', +' I2C/C2I adapters', +'!RScopeDebugInfo::record_debug_info', +'!dealLoopTree::counted_loop', +'!fNode::Ideal', +'-_common', +'(Opcode', +'"TrueNode::Opcode', +'!mplicitNullCheckStub::emit_code', +'!ndexSetIterator::advance_and_next', +'"lineTree::ok_to_inline', +',should_not_inline', +',try_to_inline', +'"stanceKlass::find_method_index', '/uncached_lookup_method', -' JavaThread::thread_main_inner', -'!vmtiAgentThread::start_function_wrapper', -' LIR_Assembler::emit_code', -'4lir_list', -'4opTypeCheck', -'$OpTypeCheck::emit_code', -'!inkResolver::linktime_resolve_static_method', -'.resolve_invoke', -'6method', -'6static_call', -' Monitor::wait_without_safepoint_check', -' PhaseIdealLoop::PhaseIdealLoop', +' JVM_DefineClassWithSource', +'!avaFrameAnchor::make_walkable', +'$Thread::thread_main_inner', +'$_java_lang_ClassLoader_defineClass1', +' KlassFactory::create_from_stream', +' LIR_Assembler::add_call_info', +'/emit_slow_case_stubs', +'!inkResolver::linktime_resolve_interface_method_or_null', +'?virtual_method_or_null', +'.resolve_interface_method', +' Matcher::match', +')xform', +'!ergeMemNode::MergeMemNode', +'.make', +'/emory_at', +'!onitor::wait_without_safepoint_check', +'!ultiNode::is_CFG', +' Node::Node', +'&del_req', +' Parse::Parse', +'\'do_all_blocks', +'*call', +'*field_access', +'*get_xxx', +'*one_block', +'\'return_current', +'%Generator::generate', +'!haseCCP::PhaseCCP', +'*analyze', +'*push_and', +'/child_nodes_to_worklist', +'/more_uses', +'&FG::do_global_code_motion', +'*global_code_motion', +'*insert_anti_dependences', +'*schedule_late', +'4ocal', +'&haitin::Register_Allocate', +'.Select', +'/implify', +'.build_ifg_physical', +'.gather_lrg_masks', +'.interfere_with_live', +'.lower_pressure', +'%GVN::transform_no_reclaim', +'%IdealLoop::Dominators', +'0PhaseIdealLoop', '0build_and_optimize', -'6loop_tree', -'?_impl', +'6loop_early', +';late', +'?_post_work', +'0compute_early_ctrl', +'8lca_of_uses', +'0dom_lca_for_get_late_ctrl_internal', '0optimize', +'0split_if_with_blocks', +'D_post', +'Fre', +'6thru_phi', +'0try_merge_identical_ifs', +'4sink_out_of_loop', +'&terGVN::PhaseIterGVN', +'.add_users_to_worklist', +'.optimize', +'.remove_globally_dead_node', +'.subsume_node', +'.transform_old', +'%MacroExpand::eliminate_allocate_node', +'', -'3thodHandleNatives.linkMethodHandleConstant', -'=s$Lookup.linkMethodHandleConstant', -'1VarHandleGuards.guard_LJ_J', -':SegmentAsLongs.get', -'%util/concurrent/ThreadPoolExecutor$Worker.run', +'*foreign/MemorySegment.copy', +'@get', +'@mismatch', +'*invoke/VarHandleGuards.guard_LJ_I', +'JJ', +':SegmentAsBytes.checkAddress', +'Iget', +'IoffsetNoVMAlignCheck', +'CLongs.get', +'IoffsetNoVMAlignCheck', +'*ref/SoftReference.get', +'%nio/ByteBuffer.', +'4put', +'7Buffer', +'4wrap', +')HeapByteBuffer.', +'%security/SecureClassLoader.defineClass', +'%util/AbstractQueue.remove', +'*Objects.requireNonNull', +'*PriorityQueue.add', +'8offer', +'8poll', +'8siftDownComparable', +'Comparable', +'*concurrent/ThreadPoolExecutor$Worker.run', 'G.runWorker', -'!dk/internal/foreign/AbstractMemorySegmentImpl.toArray', -'-misc/ScopedMemoryAccess.getLongUnaligned', +'!dk/internal/foreign/AbstractMemorySegmentImpl$$Lambda.0x0000008801133cb8.apply', +'N.checkAccess', +'TBounds', +'Popy', +'Omismatch', +'OsessionImpl', +'OtoArray', +'5MemorySessionImpl.checkValidStateRaw', +'5NativeMemorySegmentImpl.unsafeGetOffset', +'5layout/ValueLayouts$OfByteImpl.dup', +'-loader/BuiltinClassLoader.defineClass', +'GfindClassOnClassPathOrNull', +'GloadClass', +'POrNull', +'4ClassLoaders$AppClassLoader.loadClass', +'-misc/ScopedMemoryAccess.copyMemory', +'OInternal', +'EgetByte', +'LInternal', +'HLongUnaligned', 'UInternal', -'2Unsafe.getLongUnaligned', -' ru/vk/itmo/test/elenakhodosova/HttpServerImpl$$Lambda.0x00000005011b6fc0.run', +'2Unsafe.checkSize', +':opyMemory', +'CChecks', +'9getLongUnaligned', +'!short_disjoint_arraycopy', +'!vm_define_class_common', +' kevent', +' mach_absolute_time', +' nmethod::new_nmethod', +'*method', +' one/nio/net/JavaSelector.select', +'1ocket.write', +',Session$ArrayQueueItem.write', +'4QueueItem.append', +'3.process', +';Write', +'4write', +'(server/SelectorThread.run', +'(util/ByteArrayBuilder.', +'>append', +'>ensureCapacity', +'!s::javaTimeMillis', +' pthread_mutex_lock', +' ru/vk/itmo/test/elenakhodosova/HttpServerImpl$$Lambda.0x000000880114fb18.run', 'M.iterateOverChunks', 'Nlambda$executeRangeRequest$1', 'NprocessChunk', -'?dao/ReferenceDao.get', -'CSSTable.entryBinarySearch', -'POffset', -'Kget', -'NLength', -'CTableSet.get', +'?dao/LiveFilteringIterator.next', +'YskipTombstones', +'CMemorySegmentComparator.compare', +'ErgingEntryIterator.hasNext', +'Xnext', +'CReferenceDao.get', +'CSSTable$SliceIterator.next', +'J.getLength', +'CWeightedPeekingEntryIterator.compareTo', +'`next', +' scan_mem_chain', +'!un/nio/ch/IOUtil.write', +'7FromNativeBuffer', +'+KQueue.poll', +'1SelectorImpl.doSelect', +'+SelectorImpl.lockAndDoSelect', +'8select', +',ocketChannelImpl.write', +'1Dispatcher.write', +'A0', +'+Util$BufferCache.offerFirst', +'/.offerFirstTemporaryDirectBuffer', +'!ys_icache_invalidate', ' thread_native_entry', -'\'start' +'\'start', +' unknown_Java', +' write' ]; unpack(cpool); -n(3,12) -u(361,5) -u(369) -u(425) -u(433) -u(473) -u(489) -u(481,1) -u(497) -u(441) -u(393) -u(401) -u(385) -u(300) -u(292) -u(308) -u(284) -u(196) -u(212) -u(188) -u(204) -u(132) -u(124) -f(505,7,1,4) -u(505) -u(545) -u(529) -u(513) -u(521,3) -u(378) -u(410) -u(418) -u(450) -u(458) -u(466) -f(537,12,3,1) -u(377) -u(409) -u(417) -u(449) -u(457) -u(465) -f(563,1,1,7) -u(355) -u(555) -u(316) -u(100,1) -u(108) -u(116) -u(220) -u(268) -u(347) -f(140,5,1,3) -u(76,2) -u(84) -u(20,1) +n(3,196) +u(1385,121) +u(1393) +u(1601) +u(1609) +u(1969) +u(1985) +u(1977,120,0,1,0) +f(1993,8,1,119,0,1,0) +u(1665,6,1,0,0) +u(1666,6,2,0,2) +f(1346,11,2,2,1,0,0) +u(1354,2,1,0,0) +u(1482,1) +n(1694,1,0,1,0) +f(1402,11,1) +u(1642) +u(1738) +u(1746) +u(1794) +u(1819) +f(1616,11,1) +f(1913,9,1,93) +u(1914,93,54,0,0) +f(1881,11,3,42,3,0,0) +u(1873,42,6,0,0) +u(1514,3) +u(1522) +u(1490) +f(2137,13,3,39,3,0,0) +u(2089,39,3,0,0) +u(2089,39,3,0,0) +u(1498,2) +f(1506,17,1,1) +u(1738) +u(1746) +u(1794) +u(1802) +u(1786) +f(2097,16,1,36) +u(2145,36,0,1,0) +u(2153,36,1,0,0) +u(1331,1) +n(2211,35) +f(2170,16,35,1) +u(2162) +f(1890,11,1,48) +f(1930,9,48,1) +u(1930) +f(1938,9,1) +u(1938) +u(1946) +f(2001,9,1,18) +u(2001) +u(2009) +u(2030,1,0,1,0) +n(2033,17,4,0,0) +u(2033,17,4,1,0) +u(1537,4) +u(1569) +u(1577,4,0,1,0) +f(2065,17,1,3) +u(2065) +u(2017) +u(2017) +u(1417) +u(1418) +u(1650) +u(1410) +u(1426) +u(1450) +f(1658,27,1,1) +n(1754) +u(1762) +f(1554,14,1,5,2,0,0) +u(1562,5,2,0,0) +u(1586,5,2,0,0) +u(1594,5,2,1,0) +f(2066,18,2,3,1,0,0) +u(2066,3,1,0,0) +u(2018,3,1,0,0) +u(2018,3,1,0,0) +f(1417,22,1,2) +u(1418,2,1,0,0) +u(1650,2,1,1,0) +u(1410) +u(1426) +u(1450) +u(1458,1) +n(1754) +u(1762) +f(2017,14,1,5,1,0,0) +f(2017,15,1,4) +u(1417) +u(1418,4,3,0,0) +u(1650,4,3,1,0) +f(1410,19,1,3) +u(1426) +u(1450) +u(1442,2) +u(1546,1) +n(1626) +u(1634) +f(1754,22,1) +u(1762) +u(1674) +f(2074,14,1,3,2,0,0) +u(2050,3,2,0,0) +u(2050,3,2,0,0) +u(2058,3,2,0,0) +f(1410,18,1,2,1,0,0) +u(1434,2,1,0,0) +u(1466,2,1,0,0) +u(1473,1) +u(1686,1,0,1,0) +f(1770,21,1) +u(1778) +u(1810) +f(2041,7,1) +u(2041) +u(1377) +u(1729) +u(1713) +u(1721) +u(1705) +u(1697) +u(1529) +u(1361) +u(1368) +u(459) +u(435) +u(1827) +u(1148) +u(468) u(60) +u(84) u(68) +u(76) +u(1140) +u(1132) +f(1921,1,1,16) +u(1865,1) +u(2129) +u(2121) +u(2113) +u(2105) +u(1835) +f(1897,2,1,15) +u(1905) +u(1881) +u(1873) +u(2137) +u(2089) +u(2089) +u(2097) +u(2145) +u(2153) +u(2211) +f(2195,1,15,56) +u(1203) +u(2187) +u(1156) +u(236,2) +u(292) +u(300) +u(556) +u(1052) +u(1195) +f(452,5,2,53) +u(204) +u(212) +u(28,50) +u(156) +u(148,17) +u(516,3) +u(228,1) +n(524,2) +f(20,13,1,1) +f(692,11,1,3) +u(700) +u(36,1) +n(716) +u(708) +f(724,13,1) +f(732,11,1,8) +u(36,1) +n(740) +u(388) +f(748,12,1) +u(388) +f(756,12,1,3) +f(772,13,1,1) +n(780) +u(1076) +f(764,12,1) +n(1212) +f(1012,11,1,3) +u(1004,1) +n(1028) +u(1020) u(260) -u(228) -u(236) +f(1116,12,1) +u(1108) +u(1124) +f(164,10,1,24) +u(196,11) +u(868) +u(804,10) +u(812) +u(340,1) +u(340) +u(340) u(244) -u(252) -f(92,8,1) -u(28) -u(44) -u(36) +f(796,15,1,2) +n(828,4) +f(836,16,1,3) +f(876,15,3) +u(884,1) +u(916) +u(844) u(52) -u(156) -u(164) -u(180) +u(44) +u(1963) +f(892,16,1) +u(900) +u(948) +u(1172) +f(1092,16,1) +f(940,13,1) +u(964) +u(932) +f(652,11,1) +u(660) +u(676) +u(684) +u(668) +f(868,11,1,7) +u(804,4) +u(812) +u(796,1) +u(564) +f(820,14,1) +n(828) +u(836) +u(852) +u(860) +f(876,14,1) +u(884) +u(908) +f(940,12,1,3) +u(964,2) +u(348,1) +u(356) +f(1084,14,1) +u(372) +f(1068,13,1) +f(924,11,1) +n(940,2) +u(964) +u(956) +u(364,1) +n(580) +f(980,11,1) +u(972) +u(988) +u(996) +u(2083) +u(548) +f(1044,11,1) +u(1036) +u(188) +f(644,10,1,8) +u(588) +u(596) +u(628) +u(604) +u(172,1) +u(396) +u(1260) +u(1292) +u(1316) +u(1284) +u(1300) +u(1308) +f(644,15,1,2) +u(588) +u(596) +u(628) +u(604) +u(644,1) +u(588) +u(596) +u(628) +u(604) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604) u(172) -u(12) -f(148,6,1) +u(396) +u(412) +u(404) u(276) -u(324) -u(347) -f(332,5,1,3) -u(340) -u(220) +u(444) +f(1060,20,1) +u(644) +u(588) +u(596) +u(628) +u(604) +u(172) +u(396) +u(1260) +u(1292) +u(1316) +u(1284) +u(1300) +u(1268) +u(1220) +f(1060,15,1,5) +u(644,3) +u(588) +u(316,1) +u(308) +u(540) +u(532) +u(572) +f(596,18,1,2) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604) +u(644,1) +u(588) +u(596) +u(628) +u(604) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1228) +u(1236) +u(1244) +u(500) +u(284) +f(1060,27,1) +u(644) +u(588) +u(596) +u(628) +u(604) +u(172) +u(396) +u(1260) +u(1292) +u(1316) +u(1284) +u(1300) +u(1268) +u(1276) +u(1228) +u(1236) +u(1244) +u(492) +u(508) +u(428) +u(420) +f(1060,16,1,2) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604,1) +u(644) +u(588) +u(596) +u(628) +u(604) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(604) +u(1060) +u(644) +u(588) +u(596) +u(628) +u(636) +u(1100) +f(612,27,1) +u(620) +u(788) +u(12) +u(1164) +f(1036,10,1) +u(180) +f(220,8,1,3) +u(108) +u(124) +u(116,1) +u(132) +u(140) +u(484) +u(380) +u(476) +u(100) +u(332) +u(332) +u(332) +u(332) +u(332) u(268) -u(347) +u(252) +f(1252,11,1,2) +u(1852) +u(1860) +u(92,1) +n(2179) +f(1180,5,1) +u(1188) +u(556) +u(1052) +u(1195) +f(2203,1,1,3) +u(321,1) +n(1417) +n(1913) +u(1956) +u(1323) +u(1339) +u(1843) search(); diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-range-alloc.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-range-alloc.html index 6d56b794b..a05e3aaba 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-range-alloc.html +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-range-alloc.html @@ -14,7 +14,7 @@ #status {left: 0} #match {right: 0} #reset {cursor: pointer} - #canvas {width: 100%; height: 896px} + #canvas {width: 100%; height: 384px} @@ -32,7 +32,7 @@

Flame Graph

let root, rootLevel, px, pattern; let level0 = 0, left0 = 0, width0 = 0; let reverse = false; - const levels = Array(56); + const levels = Array(24); for (let h = 0; h < levels.length; h++) { levels[h] = []; } @@ -252,110 +252,152 @@

Flame Graph

const cpool = [ 'all', ' byte[]', -' java/lang/String.format', -'1split', -'2ubSequence', -'4string', -'0Latin1.newString', +' java.lang.String', +'*invoke.MethodHandles$Lookup', +'%nio.HeapByteBuffer', +'%util.HashMap$Node', +'$/lang/Integer.toHexString', +'4UnsignedString0', '*Thread.run', '4With', -'%security/AccessController.doPrivileged', -'?executePrivileged', -'%text/DecimalFormatSymbols.getInstance', -'%util/Arrays.copyOfRange', -'', +'Mdup', +'6emorySessionImpl.heapSession', +'5layout/ValueLayouts$AbstractValueLayout.withOrder', +'IOfByteImpl.dup', +'TwithOrder', +' one.nio.net.Session$ArrayQueueItem', +'(util.ByteArrayBuilder', +'#/nio/net/JavaSelector.select', +'1ocket.write', +',Session$ArrayQueueItem.write', +'3.process', +';Write', +'4write', +'(server/SelectorThread.run', +'(util/ByteArrayBuilder.', +'>toBytes', +'-Utf8.toBytes', +' ru.vk.itmo.test.elenakhodosova.dao.BaseEntryWithTimestamp', +'"/vk/itmo/test/elenakhodosova/HttpServerImpl$$Lambda.0x000000880114fb18.run', +'M.iterateOverChunks', +'Nlambda$executeRangeRequest$1', +'NprocessChunk', +'?dao/LiveFilteringIterator.next', 'YskipTombstones', 'CMergingEntryIterator.next', -'CReferenceDao.get', 'CSSTable$SliceIterator.next', 'CWeightedPeekingEntryIterator.next', -' sun/util/cldr/CLDRLocaleProviderAdapter.createLanguageTagSet', -')locale/provider/JRELocaleProviderAdapter$$Lambda.0x80000006c.run', -'Q.getDecimalFormatSymbolsProvider', -'ULanguageTagSet', -'VocaleServiceProvider', -'Rlambda$getDecimalFormatSymbolsProvider$4', -'9LocaleProviderAdapter.findAdapter', -'OgetAdapter' +' sun/nio/ch/KQueueSelectorImpl.doSelect', +'>processEvents', +'+SelectorImpl.lockAndDoSelect', +'8processReadyEvents', +'8select' ]; unpack(cpool); -n(3,1) +n(3,1177) +u(65,1172) +u(73) +u(145) +u(153) +u(401) +u(417) +u(409) +u(425) +u(49,48) u(57) -u(65) -u(161) -u(169) -u(249) -u(257) -u(289) -u(289) -u(265) +u(10,40) +n(18,8) +f(233,9,8,195) +u(89,1) +u(97) +u(26) +f(233,10,1,194) +u(193,153) +u(225) +u(10) +f(201,11,153,25) +u(81) +u(241) +u(170,13) +n(265,12) +u(162) +f(289,11,12,16) u(273) u(281) u(281) -u(305) -u(297) -u(297) -u(217) -u(217) -u(193) -u(201) -u(225) -u(241) -u(233) -u(185) -u(185) +u(186) +f(306,9,16,10) +n(353,80) +u(298,73) +n(353,7) +u(329) +u(321) +u(105) +u(34) +f(369,9,7,472) +u(369) +u(10) +f(377,9,472,145) +u(10) +f(385,9,145,34) +u(10) +f(433,9,34,188) +u(433) +u(441) +u(449) +u(449) +u(465) +u(457) +u(457) +u(249,141) +u(249) u(209) -u(17) -u(137) +u(217) +u(257) +u(257) +u(178) +f(394,17,141,47) +f(361,1,47,5) +u(313,1) +u(505) +u(489) +u(473) +u(481) +u(497) u(137) u(121) u(129) -u(121) u(113) -u(153) -u(145) -u(89) -u(369) -u(361) +u(42) +f(337,2,1,4) u(345) u(329) -u(73) -u(81) u(321) -u(353) -u(337) -u(313) -u(25) -u(25) -u(177) -u(177) -u(33) -u(41) -u(49) -u(97) u(105) -u(10) +u(34) search(); diff --git a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range-lock.html b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-range-lock.html similarity index 93% rename from src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range-lock.html rename to src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-range-lock.html index 24b7ef49f..a6b2aea87 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range-lock.html +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-range-lock.html @@ -14,7 +14,7 @@ #status {left: 0} #match {right: 0} #reset {cursor: pointer} - #canvas {width: 100%; height: 16px} + #canvas {width: 100%; height: 192px} @@ -32,7 +32,7 @@

Flame Graph

let root, rootLevel, px, pattern; let level0 = 0, left0 = 0, width0 = 0; let reverse = false; - const levels = Array(1); + const levels = Array(12); for (let h = 0; h < levels.length; h++) { levels[h] = []; } @@ -250,11 +250,37 @@

Flame Graph

} const cpool = [ -'all' +'all', +' java/lang/Thread.run', +'4With', +'%util/concurrent/ThreadPoolExecutor$Worker.run', +'G.runWorker', +' one.nio.http.HttpSession', +'#/nio/net/Session.process', +'4write', +'(server/SelectorThread.run', +' ru/vk/itmo/test/elenakhodosova/HttpServerImpl$$Lambda.0x000000880114fb18.run', +'M.iterateOverChunks', +'Nlambda$executeRangeRequest$1', +'NprocessChunk' ]; unpack(cpool); -n(2) +n(3,92) +u(9,83) +u(17) +u(25) +u(33) +u(73) +u(89) +u(81) +u(97) +u(57) +u(57) +u(42) +f(65,1,83,9) +u(49) +u(42) search();