diff --git a/src/main/java/ru/vk/itmo/test/asvistukhin/ProxyRequestHandler.java b/src/main/java/ru/vk/itmo/test/asvistukhin/ProxyRequestHandler.java index f4808594e..abab1dba8 100644 --- a/src/main/java/ru/vk/itmo/test/asvistukhin/ProxyRequestHandler.java +++ b/src/main/java/ru/vk/itmo/test/asvistukhin/ProxyRequestHandler.java @@ -7,17 +7,18 @@ import org.slf4j.LoggerFactory; import ru.vk.itmo.ServiceConfig; -import java.io.IOException; -import java.net.ConnectException; 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.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; public class ProxyRequestHandler { @@ -27,65 +28,80 @@ public class ProxyRequestHandler { private final Map urlHashes; public ProxyRequestHandler(ServiceConfig serviceConfig) { - this.clients = HashMap.newHashMap(serviceConfig.clusterUrls().size() - 1); - + this.clients = new HashMap<>(); for (String url : serviceConfig.clusterUrls()) { if (!Objects.equals(url, serviceConfig.selfUrl())) { clients.put(url, HttpClient.newHttpClient()); } } - this.urlHashes = HashMap.newHashMap(serviceConfig.clusterUrls().size()); - for (String url : serviceConfig.clusterUrls()) { - urlHashes.put(url, Hash.murmur3(url)); - } + this.urlHashes = serviceConfig.clusterUrls().stream() + .collect(Collectors.toMap(url -> url, Hash::murmur3)); } public synchronized void close() { clients.values().forEach(HttpClient::close); } - public Map proxyRequests(Request request, List nodeUrls) throws IOException { - Map responses = HashMap.newHashMap(nodeUrls.size()); + public List> proxyRequests( + Request request, + List nodeUrls, + int ack, + List collectedResponses, + AtomicInteger unsuccessfulResponsesCount + ) { + List> futures = new ArrayList<>(); + AtomicInteger responsesCollected = new AtomicInteger(); + for (String url : nodeUrls) { - Response response = proxyRequest(request, url); - responses.put(url, response); - } + if (unsuccessfulResponsesCount.get() >= ack) { + futures.add(CompletableFuture.completedFuture(null)); + } - return responses; + CompletableFuture futureResponse = proxyRequest(request, url); + CompletableFuture resultFuture = futureResponse.thenApply(response -> { + boolean success = ServerImpl.isSuccessProcessed(response.getStatus()); + if (success && responsesCollected.getAndIncrement() < ack) { + collectedResponses.add(response); + } else if (collectedResponses.size() < ack) { + unsuccessfulResponsesCount.incrementAndGet(); + } + return response; + }); + futures.add(resultFuture); + } + return futures; } - public Response proxyRequest(Request request, String proxiedNodeUrl) throws IOException { + private CompletableFuture proxyRequest(Request request, String proxiedNodeUrl) { String id = request.getParameter("id="); byte[] body = request.getBody(); URI uri = URI.create(proxiedNodeUrl + request.getPath() + "?id=" + id); - try { - HttpResponse httpResponse = clients.get(proxiedNodeUrl).send( - HttpRequest.newBuilder() - .uri(uri) - .method( - request.getMethodName(), - body == null - ? HttpRequest.BodyPublishers.noBody() - : HttpRequest.BodyPublishers.ofByteArray(body) - ) - .header(RequestWrapper.SELF_HEADER, "true") - .build(), - HttpResponse.BodyHandlers.ofByteArray()); + + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .uri(uri) + .method( + request.getMethodName(), + body == null ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofByteArray(body) + ) + .header(RequestWrapper.SELF_HEADER, "true"); + + CompletableFuture> httpResponseFuture = clients.get(proxiedNodeUrl) + .sendAsync(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray()); + + return httpResponseFuture.thenApply(httpResponse -> { Response response = new Response(proxyResponseCode(httpResponse), httpResponse.body()); - long timestamp = httpResponse.headers().firstValueAsLong(RequestWrapper.NIO_TIMESTAMP_HEADER).orElse(0); + long timestamp = Long.parseLong( + httpResponse.headers() + .firstValue(RequestWrapper.NIO_TIMESTAMP_HEADER) + .orElse("0") + ); response.addHeader(RequestWrapper.NIO_TIMESTAMP_STRING_HEADER + timestamp); return response; - } catch (InterruptedException ex) { - log.error("Proxy request thread interrupted", ex); - Thread.currentThread().interrupt(); + }).exceptionally(ex -> { + log.error("Exception during proxy request to another node", ex); return new Response(Response.INTERNAL_ERROR, Response.EMPTY); - } catch (IllegalArgumentException ex) { - log.error("IllegalArgumentException during proxy request to another node", ex); - return new Response(Response.INTERNAL_ERROR, Response.EMPTY); - } catch (ConnectException ex) { - log.error("ConnectException during proxy request to another node", ex); - return new Response(Response.INTERNAL_ERROR, Response.EMPTY); - } + }); } private String proxyResponseCode(HttpResponse response) { diff --git a/src/main/java/ru/vk/itmo/test/asvistukhin/RequestHandler.java b/src/main/java/ru/vk/itmo/test/asvistukhin/RequestHandler.java index 01cfd1040..1f80e3a10 100644 --- a/src/main/java/ru/vk/itmo/test/asvistukhin/RequestHandler.java +++ b/src/main/java/ru/vk/itmo/test/asvistukhin/RequestHandler.java @@ -9,6 +9,9 @@ import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; public class RequestHandler { private final Dao> dao; @@ -27,6 +30,23 @@ public Response handle(Request request) { }; } + public CompletableFuture handle( + Request request, + List collectedResponses, + AtomicInteger unsuccessfulResponsesCount + ) { + return new CompletableFuture().completeAsync(() -> { + Response response = handle(request); + if (ServerImpl.isSuccessProcessed(response.getStatus())) { + collectedResponses.add(response); + } else { + unsuccessfulResponsesCount.incrementAndGet(); + } + + return response; + }); + } + public Response get(@Param(value = "id", required = true) String id) { if (RequestWrapper.isEmptyParam(id)) { return new Response(Response.BAD_REQUEST, Response.EMPTY); diff --git a/src/main/java/ru/vk/itmo/test/asvistukhin/ServerImpl.java b/src/main/java/ru/vk/itmo/test/asvistukhin/ServerImpl.java index 602c5c40e..c050c6dba 100644 --- a/src/main/java/ru/vk/itmo/test/asvistukhin/ServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/asvistukhin/ServerImpl.java @@ -14,25 +14,28 @@ import ru.vk.itmo.test.asvistukhin.dao.PersistentDao; import java.io.IOException; -import java.util.Comparator; +import java.util.ArrayList; import java.util.List; -import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; +import java.util.concurrent.atomic.AtomicInteger; public class ServerImpl extends HttpServer { private static final Logger log = LoggerFactory.getLogger(ServerImpl.class); + private static final Integer INTERNAL_SERVER_ERROR_CODE = 500; + private static final List NOT_SUCCESSFUL_BAD_REQUESTS_CODES = List.of(429); private static final List ALLOWED_METHODS = List.of( Request.METHOD_GET, Request.METHOD_PUT, Request.METHOD_DELETE ); - private static final String HTTP_SERVICE_NOT_AVAILABLE = "503"; + private static final String NOT_ENOUGH_REPLICAS_RESPONSE = "504 Not Enough Replicas"; private static final int QUEUE_CAPACITY = 3000; @@ -85,6 +88,10 @@ public synchronized void stop() { super.stop(); } + public static boolean isSuccessProcessed(int status) { + return status < INTERNAL_SERVER_ERROR_CODE && !NOT_SUCCESSFUL_BAD_REQUESTS_CODES.contains(status); + } + private void wrapHandleRequest(Request request, HttpSession session) { try { if (!ALLOWED_METHODS.contains(request.getMethod())) { @@ -98,15 +105,6 @@ private void wrapHandleRequest(Request request, HttpSession session) { } else { session.sendResponse(requestHandler.handle(request)); } - } catch (RejectedExecutionException executionException) { - try { - log.error("Rejected execution new request.", executionException); - session.sendError(HTTP_SERVICE_NOT_AVAILABLE, "Server is overload."); - } catch (IOException ex) { - log.error("Failed send error response to client.", ex); - session.close(); - Thread.currentThread().interrupt(); - } } catch (Exception ex) { try { String response = ex.getClass() == HttpException.class @@ -122,46 +120,81 @@ private void wrapHandleRequest(Request request, HttpSession session) { } private void processFirstRequest( - Request request, - HttpSession session, - RequestWrapper parameters - ) throws IOException { + Request request, + HttpSession session, + RequestWrapper parameters + ) throws IOException, ExecutionException, InterruptedException { List nodeUrls = proxyRequestHandler.getNodesByHash(parameters.from); + if (nodeUrls.size() < parameters.from) { - session.sendResponse(new Response(NOT_ENOUGH_REPLICAS_RESPONSE, Response.EMPTY)); + sendNotEnoughReplicasResponse(session); + return; } boolean isSelfProcessing = nodeUrls.remove(serviceConfig.selfUrl()); - Map responses = proxyRequestHandler.proxyRequests(request, nodeUrls); + + List validResponses = new ArrayList<>(); + AtomicInteger unsuccessfulResponsesCount = new AtomicInteger(0); + + List> futures = new ArrayList<>(proxyRequestHandler.proxyRequests( + request, + nodeUrls, + parameters.ack, + validResponses, + unsuccessfulResponsesCount + )); if (isSelfProcessing) { - responses.put(serviceConfig.selfUrl(), requestHandler.handle(request)); + futures.add(requestHandler.handle(request, validResponses, unsuccessfulResponsesCount)); + } + + CompletableFuture allFutures = CompletableFuture.allOf( + futures.stream() + .limit(parameters.ack) + .toArray(CompletableFuture[]::new) + ); + + try { + allFutures.get(5, TimeUnit.SECONDS); + } catch (TimeoutException e) { + log.warn("Timeout reached while waiting for responses"); } - List validResponses = responses.values().stream() - .filter(response -> isSuccessProcessed(response.getStatus())) - .collect(Collectors.toList()); if (validResponses.size() >= parameters.ack) { if (request.getMethod() == Request.METHOD_GET) { - validResponses.sort( - Comparator.comparingLong(r -> { - String timestamp = r.getHeader(RequestWrapper.NIO_TIMESTAMP_STRING_HEADER); - return timestamp == null ? 0 : Long.parseLong(timestamp); - } - ) - ); - session.sendResponse(validResponses.getLast()); + sendResponseToClient(session, validResponses); } else { session.sendResponse(validResponses.getFirst()); } } else { - session.sendResponse(new Response(NOT_ENOUGH_REPLICAS_RESPONSE, Response.EMPTY)); + sendNotEnoughReplicasResponse(session); + } + } + + private void sendResponseToClient(HttpSession session, List validResponses) throws IOException { + Response lastResponse = null; + long maxTimestamp = Long.MIN_VALUE; + + for (Response response : validResponses) { + String timestamp = response.getHeader(RequestWrapper.NIO_TIMESTAMP_STRING_HEADER); + if (timestamp != null) { + long currentTimestamp = Long.parseLong(timestamp); + if (currentTimestamp > maxTimestamp) { + maxTimestamp = currentTimestamp; + lastResponse = response; + } + } + } + + if (lastResponse == null) { + sendNotEnoughReplicasResponse(session); + } else { + session.sendResponse(lastResponse); } } - private boolean isSuccessProcessed(int status) { - // not server and time limit errors - return status < 500; + private void sendNotEnoughReplicasResponse(HttpSession session) throws IOException { + session.sendResponse(new Response(NOT_ENOUGH_REPLICAS_RESPONSE, Response.EMPTY)); } private static HttpServerConfig createHttpServerConfig(ServiceConfig serviceConfig) { diff --git a/src/main/java/ru/vk/itmo/test/asvistukhin/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/asvistukhin/ServiceImpl.java index 3bc2fb0cd..da3687036 100644 --- a/src/main/java/ru/vk/itmo/test/asvistukhin/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/asvistukhin/ServiceImpl.java @@ -39,7 +39,7 @@ public CompletableFuture stop() throws IOException { return CompletableFuture.completedFuture(null); } - @ServiceFactory(stage = 4) + @ServiceFactory(stage = 5) public static class Factory implements ServiceFactory.Factory { @Override diff --git a/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/get_cpu.html b/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/get_cpu.html new file mode 100644 index 000000000..032b3cd11 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/get_cpu.html @@ -0,0 +1,2913 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/report3/asyncprof/prof_get_lock_128.html b/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/get_lock.html similarity index 96% rename from src/main/java/ru/vk/itmo/test/osipovdaniil/report3/asyncprof/prof_get_lock_128.html rename to src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/get_lock.html index 5ce4fbc4f..4b690ec42 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/report3/asyncprof/prof_get_lock_128.html +++ b/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/get_lock.html @@ -276,47 +276,50 @@

Lock profile

'@Read', '9sendResponse', '(net/Session.process', -'(server/SelectorThread.run', -' ru/vk/itmo/test/osipovdaniil/ServerImpl$$Lambda.0x000079c21008ac78.run', -'G.handleRequest', -'UTask', -'Hlambda$handleRequest$3' +'(server/PayloadThread.run', +'/SelectorThread.run', +' ru/vk/itmo/test/asvistukhin/ServerImpl$$Lambda.0x00007e8e24148248.run', +'F.handleRequest', +'Glambda$handleRequest$0', +'GwrapHandleRequest' ]; unpack(cpool); -n(3,18857) -u(17,16419) +n(3,23347) +u(209,23144) +u(17) u(25) u(57) u(81) -u(73,14047) +u(73,4385) u(49) -u(89,6788) +u(89,656) u(97) u(113) u(153) u(10) -f(145,7,6788,7259) +f(145,8,656,3729) u(129) u(105) u(97) u(113) u(153) u(10) -f(217,5,7259,2372) +f(225,6,3729,18759) u(241) -u(233) +u(249) u(193) u(162) -f(209,1,2372,2438) +f(217,1,18759,203) u(201) -u(185) +u(162,24) +n(185,179) u(177) u(169) -u(225) +u(233) u(65) u(33) -u(41,2365) +u(41,121) u(137) u(121) u(97) @@ -324,7 +327,7 @@

Lock profile

u(113) u(153) u(10) -f(137,9,2365,73) +f(137,9,121,58) u(121) u(97) u(97) diff --git a/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/get_malloc.html b/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/get_malloc.html new file mode 100644 index 000000000..e6438c8d3 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/get_malloc.html @@ -0,0 +1,882 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/put_cpu.html b/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/put_cpu.html new file mode 100644 index 000000000..f23d2741f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/put_cpu.html @@ -0,0 +1,2666 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/bandurinvladislav/results/stage2/profile_html/putLock.html b/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/put_lock.html similarity index 91% rename from src/main/java/ru/vk/itmo/test/bandurinvladislav/results/stage2/profile_html/putLock.html rename to src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/put_lock.html index 6a2c156fd..15294d5ab 100644 --- a/src/main/java/ru/vk/itmo/test/bandurinvladislav/results/stage2/profile_html/putLock.html +++ b/src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5/put_lock.html @@ -255,8 +255,9 @@

Lock profile

'DReadWriteLock$NonfairSync', '$/lang/Thread.run', '4With', -'%util/concurrent/ArrayBlockingQueue.offer', -'Htake', +'%util/concurrent/LinkedBlockingQueue.offer', +'IsignalNotEmpty', +'Itake', '5ThreadPoolExecutor$Worker.run', 'G.execute', 'HgetTask', @@ -264,13 +265,12 @@

Lock profile

'5locks/AbstractQueuedSynchronizer$ConditionObject.await', 'U.acquire', ']Interruptibly', -']Shared', ';LockSupport.park', ';ReentrantLock$Sync.lock', 'RInterruptibly', 'H.lock', 'MInterruptibly', -'DReadWriteLock$ReadLock.lock', +'DReadWriteLock$WriteLock.lock', '!dk/internal/misc/Unsafe.park', ' one.nio.http.HttpSession', '#/nio/http/HttpSession.handleParsedRequest', @@ -280,66 +280,88 @@

Lock profile

'(net/Session.process', '(server/PayloadThread.run', '/SelectorThread.run', -' ru/vk/itmo/test/bandurinvladislav/DaoWorkerPool$DeadlineArrayBlockingQueue.offer', -'BServer$$Lambda.0x00007f73680c9128.run', -'H.handleDaoCall', -'ORequest', -'Ilambda$handleRequest$0', -'IputEntity', -'Bconcurrent/DeadlineRunnable.run', -'Bdao/ReferenceDao.upsert' +' ru.vk.itmo.test.asvistukhin.dao.PersistentDao', +'"/vk/itmo/test/asvistukhin/RequestHandler.handle', +'Kput', +' + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/asvistukhin/reports/stage5.md b/src/main/java/ru/vk/itmo/test/asvistukhin/reports/stage5.md new file mode 100644 index 000000000..9e80d9b87 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/asvistukhin/reports/stage5.md @@ -0,0 +1,95 @@ +## Нагрузочное тестирование +Тестирование проводил на одной машине в одной среде. Сервер и wrk/async запускались рядом. +JVM была прогрета запросами. Тестировал на трех нодах. Запросы шли с ack=2, from=3. +Процессор - i7-10510U. + +В рамках данного этапа было реализовано асинхронное взаимодействие между нодами. + +## Наполнение базы данных +Было сгенерировано с помощью WRK ~500MB данных. +1. Первая нода - 30 SSTables +2. Вторая нода - 30 SSTables +3. Третья нода - 30 SSTables + + +### PUT 12000 RPS +WRK отчет. Тестировал одну минуту, в одном потоке и 64 подключения. Точка разладки ~8000 RPS. С добавлением времени теста, +сервер не деградирует. Но если поставить ~12000 RPS, время ответа становится сильно больше. +Производительность PUT запросов увеличилась на 30%. +``` +Running 1m test @ http://localhost:8080/v0/entity + 1 threads and 64 connections + Thread calibration: mean lat.: 7.687ms, rate sampling interval: 17ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 4.19ms 5.72ms 78.78ms 92.55% + Req/Sec 12.32k 1.46k 15.44k 78.29% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.41ms + 75.000% 3.94ms + 90.000% 7.97ms + 99.000% 32.51ms + 99.900% 52.42ms + 99.990% 65.50ms + 99.999% 75.39ms +100.000% 78.85ms +``` + +### async-profiler +#### CPU +Производительность PUT запросов выросла на ~30%. Использование процессорного времени GC не выросло. Также видно, что меньше процессорного времени +съедает сетевое взаимодействие нод, из-за того, что мы теперь не блокируемся на запросе к серверу. +В остальном всё выглядит также, почти всё процессорное время съедает сетевое взаимодействие. +#### ALLOC +Аллокации убили GC. ~97% аллоков занимает GC, а именно AllocateHeap. Видимо, я не совсем правильно использую CompletableFuture, +и они накапливаются в памяти. Есть подозрение, что в памяти подвисают те фьючеры, которые ответили позже всего, так как дальше они +нигде не обрабатываются. +#### LOCK +63% на HttpSession.sendResponse(), и 33% на PersistentDao.upsert() (лок на вставку в MemTable). + +### GET 10000 RPS +WRK отчет. Тестировал одну минуту, в одном потоке и 64 подключения. Точка разладки ~10000 RPS. С добавлением времени теста, +сервер не деградирует. Но точка разладки очень чувствительна, даже шаг в 1000 RPS поднимает в разы время ответа. +Производительность GET запросов улучшилась на 20%. +``` +Running 1m test @ http://localhost:8080/v0/entity + 1 threads and 64 connections + Thread calibration: mean lat.: 65.018ms, rate sampling interval: 412ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.67ms 1.26ms 28.22ms 79.22% + Req/Sec 10.01k 37.58 10.16k 75.69% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.38ms + 75.000% 3.14ms + 90.000% 4.20ms + 99.000% 7.15ms + 99.900% 10.73ms + 99.990% 16.78ms + 99.999% 24.14ms +100.000% 28.24ms +``` +### async-profiler +#### CPU +Чтение выросло на ~20% по производительности. Также заметно то, что сетевые операции начали съедать меньше процессорного времени, если сравнить +по семплам с прошлой работой, как раз и выйдет ~20%. +В остальном, всё выглядит также, как и в прошлом этапе. GC ест 4.5% процессорного времени. + +#### ALLOC +Также, как и в случае с PUT запросами, практически вся аллокация (~97%) приходится на GC. Думаю, здесь ситуация аналогична с PUT, +часть CompletableFuture's, а именно те, которые никак не обрабатываются дальше (запросы которые выполнились после прохода порога +ack), остается в памяти и забивает её. + +#### LOCK +80% на HttpSession.sendResponse, 18% на работу с очередью в ThreadPoolExecutor. + +### Выводы +Реализация асинхронного взаимодействия улучшила производительность, для PUT на ~30%, для GET ~20%. Удивительно то, что производительность +вообще повысилась с учетом того, что вся память выжирается на фьючеры. Все запросы приходят с 200-ми кодами, ошибок никаких нет. +Если исправить проблему с фьючерами, думаю, что производительность должна вырасти ещё (на сколько не знаю). + +Асинхронное взаимодействие между нодами улучшает производительность, так как мы не блокируем потоки на ожидание ответа, но нужно +понимать, как правильно работать с CompletableFuture, так как тесты проходят, ошибок никаких нет, производительность даже хорошо выросла, +но без проверки через профилировщик можно нарваться на то, что GC жрет память в сумасшедших количествах. + +##### Результаты +Посмотреть результаты async-profiler: src/main/java/ru/vk/itmo/test/asvistukhin/reports/async_profiler/lab5 +Скрипты для генерации wrk запросов: src/main/java/ru/vk/itmo/test/asvistukhin/reports/wrk_script \ No newline at end of file