diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/AsyncActions.java b/src/main/java/ru/vk/itmo/test/andreycheshev/AsyncActions.java index d657123ca..b61e5f6fd 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/AsyncActions.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/AsyncActions.java @@ -36,6 +36,10 @@ public class AsyncActions { CPU_THREADS_COUNT, new WorkerThreadFactory("RemoteCall-thread") ); + private final Executor streamingExecutor = Executors.newFixedThreadPool( + CPU_THREADS_COUNT / 2, + new WorkerThreadFactory("Streaming-thread") + ); private final HttpClient httpClient = HttpClient.newBuilder() .executor(remoteCallExecutor) @@ -49,6 +53,19 @@ public AsyncActions(HttpProvider httpProvider) { this.httpProvider = httpProvider; } + public CompletableFuture stream(Runnable runnable) { + return CompletableFuture.runAsync( + runnable, + streamingExecutor + ).exceptionallyAsync( + exception -> { + LOGGER.error("Error while streaming process", exception); + return null; + }, + internalExecutor + ); + } + public CompletableFuture processLocallyToSend( int method, String id, @@ -166,7 +183,7 @@ private CompletableFuture getLocalFuture( int method, String id, Request request, - long timestamp) { + long timestamp) throws IllegalArgumentException { return CompletableFuture.supplyAsync( () -> switch (method) { diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/HttpUtils.java b/src/main/java/ru/vk/itmo/test/andreycheshev/HttpUtils.java index fab54b214..b4f7afdf4 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/HttpUtils.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/HttpUtils.java @@ -16,7 +16,7 @@ public class HttpUtils { public static final int NOT_FOUND_CODE = 404; public static final int GONE_CODE = 410; - public static final Map AVAILABLE_RESPONSES = Map.of( + public static final Map AVAILABLE_RESPONSE_CODES = Map.of( OK_CODE, Response.OK, CREATED_CODE, Response.CREATED, ACCEPT_CODE, Response.ACCEPTED, @@ -83,7 +83,7 @@ public static Response getOneNioResponse(int method, ResponseElements elements) Response response = status == GONE_CODE ? new Response(Response.NOT_FOUND, Response.EMPTY) - : new Response(AVAILABLE_RESPONSES.get(status), elements.getBody()); + : new Response(AVAILABLE_RESPONSE_CODES.get(status), elements.getBody()); response.addHeader(TIMESTAMP_ONE_NIO_HEADER + elements.getTimestamp()); diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/ParametersParser.java b/src/main/java/ru/vk/itmo/test/andreycheshev/ParametersParser.java index c47e1fbd7..9c4928eb5 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/ParametersParser.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/ParametersParser.java @@ -6,6 +6,8 @@ public final class ParametersParser { private static final String ID_PARAMETER = "id="; private static final String ACK_PARAMETER = "ack="; private static final String FROM_PARAMETER = "from="; + private static final String START_PARAMETER = "start="; + private static final String END_PARAMETER = "end="; private ParametersParser() { @@ -35,4 +37,12 @@ public static ParametersTuple parseAckFromOrDefault( ); } } + + public static ParametersTuple parseStartEnd(Request request) { + + String start = request.getParameter(START_PARAMETER); + String end = request.getParameter(END_PARAMETER); + + return new ParametersTuple<>(start, end); + } } diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/RequestHandler.java b/src/main/java/ru/vk/itmo/test/andreycheshev/RequestHandler.java index b14da85b4..24e7b7d9c 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/RequestHandler.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/RequestHandler.java @@ -12,6 +12,7 @@ import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.nio.charset.StandardCharsets; +import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -19,15 +20,16 @@ public class RequestHandler implements HttpProvider { private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class); + private static final String FUTURE_CREATION_ERROR = "Error when CompletableFuture creation"; + private static final Set AVAILABLE_METHODS = Set.of( Request.METHOD_GET, Request.METHOD_PUT, Request.METHOD_DELETE ); - private static final String FUTURE_CREATION_ERROR = "Error when CompletableFuture creation"; - - private static final String REQUEST_PATH = "/v0/entity"; + private static final String ENTITY_REQUEST_PATH = "/v0/entity"; + private static final String ENTITIES_REQUEST_PATH = "/v0/entities"; private final Dao> dao; private final RendezvousDistributor distributor; @@ -50,17 +52,22 @@ public void handle(Request request, HttpSession session) { } } - public void sendAsync(Response response, HttpSession session) { - CompletableFuture future = asyncActions.sendAsync(response, session); - if (future == null) { - LOGGER.info(FUTURE_CREATION_ERROR); + private Response analyzeRequest(Request request, HttpSession session) { + if (!AVAILABLE_METHODS.contains(request.getMethod())) { + return HttpUtils.getMethodNotAllowed(); } + + return switch (request.getPath()) { + case ENTITY_REQUEST_PATH -> analyzeEntity(request, session); + case ENTITIES_REQUEST_PATH -> analyzeEntities(request, session); + default -> HttpUtils.getBadRequest(); + }; } - private Response analyzeRequest(Request request, HttpSession session) { + private Response analyzeEntity(Request request, HttpSession session) { String path = request.getPath(); - if (!path.equals(REQUEST_PATH)) { + if (!path.equals(ENTITY_REQUEST_PATH)) { return HttpUtils.getBadRequest(); } @@ -81,7 +88,6 @@ private Response analyzeRequest(Request request, HttpSession session) { } catch (IllegalArgumentException e) { return HttpUtils.getBadRequest(); } - } private Response processLocally(Request request, int method, String id, String timestamp, HttpSession session) { @@ -90,7 +96,7 @@ private Response processLocally(Request request, int method, String id, String t asyncActions.processLocallyToSend(method, id, request, Long.parseLong(timestamp), session); if (future == null) { - LOGGER.info(FUTURE_CREATION_ERROR); + LOGGER.error(FUTURE_CREATION_ERROR); return HttpUtils.getInternalError(); } } catch (NumberFormatException e) { @@ -99,12 +105,6 @@ private Response processLocally(Request request, int method, String id, String t return null; } - private boolean isRequestCameFromNode(String timestamp) { - // A timestamp is an indication that the request - // came from the client directly or from the cluster node. - return timestamp != null; - } - private Response processDistributed( Request request, int method, @@ -134,14 +134,64 @@ private Response processDistributed( ? asyncActions.processLocallyToCollect(method, id, request, timestamp, collector, session) : asyncActions.processRemotelyToCollect(node, request, timestamp, collector, session); + if (future == null) { + LOGGER.error(FUTURE_CREATION_ERROR); + return HttpUtils.getInternalError(); + } + } + + return null; + } + + private boolean isRequestCameFromNode(String timestamp) { + // A timestamp is an indication that the request + // came from the client directly or from the cluster node. + return timestamp != null; + } + + private Response analyzeEntities(Request request, HttpSession session) { + try { + ParametersTuple params = ParametersParser.parseStartEnd(request); + String start = params.first(); + String end = params.second(); + if (start == null || start.isEmpty() || (end != null && end.isEmpty())) { + throw new IllegalArgumentException(); + } + + Iterator> streamingIterator = dao.get( + fromString(start), + end == null ? null : fromString(end) + ); + + CompletableFuture future = asyncActions.stream( + () -> { + try { + ((StreamingSession) session).stream(streamingIterator); + } catch (Exception e) { + LOGGER.error("An error occurred while streaming response"); + } + } + ); + if (future == null) { LOGGER.info(FUTURE_CREATION_ERROR); + return HttpUtils.getInternalError(); } + } catch (IllegalArgumentException e) { + LOGGER.error("An error occurred while analyzing entities the parameters"); + return HttpUtils.getBadRequest(); } return null; } + public void sendAsync(Response response, HttpSession session) { + CompletableFuture future = asyncActions.sendAsync(response, session); + if (future == null) { + LOGGER.error(FUTURE_CREATION_ERROR); + } + } + @Override public ResponseElements get(String id) { ClusterEntry entry = (ClusterEntry) dao.get(fromString(id)); diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseCollector.java b/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseCollector.java index c6b903d31..e4d94a24a 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseCollector.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseCollector.java @@ -41,7 +41,7 @@ public void add(ResponseElements responseElements) { } private static boolean isResponseSucceeded(int status) { - return HttpUtils.AVAILABLE_RESPONSES.containsKey(status); + return HttpUtils.AVAILABLE_RESPONSE_CODES.containsKey(status); } private boolean isReadySendResponse() { diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseElements.java b/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseElements.java index 560887636..3936cd925 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseElements.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseElements.java @@ -1,13 +1,15 @@ package ru.vk.itmo.test.andreycheshev; +import java.util.Arrays; + public class ResponseElements implements Comparable { private final int status; - private final ByteArrayWrapper body; + private final byte[] body; private final long timestamp; public ResponseElements(int status, byte[] body, long timestamp) { this.status = status; - this.body = new ByteArrayWrapper(body); + this.body = Arrays.copyOf(body, body.length); // CodeClimate requirement. this.timestamp = timestamp; } @@ -16,7 +18,7 @@ public int getStatus() { } public byte[] getBody() { - return body.get(); + return Arrays.copyOf(body, body.length); // CodeClimate requirement. } public long getTimestamp() { @@ -25,7 +27,7 @@ public long getTimestamp() { @Override public int compareTo(ResponseElements o) { - long diff = timestamp - o.timestamp; + long diff = this.getTimestamp() - o.getTimestamp(); if (diff > 0) { return -1; } else if (diff < 0) { diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/ServerImpl.java b/src/main/java/ru/vk/itmo/test/andreycheshev/ServerImpl.java index 1b5b2bb3d..ac9aab90c 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/ServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/ServerImpl.java @@ -4,6 +4,8 @@ import one.nio.http.HttpServerConfig; import one.nio.http.HttpSession; import one.nio.http.Request; +import one.nio.net.Socket; +import one.nio.server.RejectedSessionException; import ru.vk.itmo.test.andreycheshev.dao.Dao; import ru.vk.itmo.test.andreycheshev.dao.Entry; @@ -29,6 +31,11 @@ public void handleRequest(Request request, HttpSession session) { executor.execute(request, session); } + @Override + public HttpSession createSession(Socket socket) throws RejectedSessionException { + return new StreamingSession(socket, this); + } + @Override public synchronized void stop() { super.stop(); diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/ServerStarter.java b/src/main/java/ru/vk/itmo/test/andreycheshev/ServerStarter.java index 7e214d871..94f85c07e 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/ServerStarter.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/ServerStarter.java @@ -11,7 +11,7 @@ import java.util.concurrent.ExecutionException; public final class ServerStarter { - private static final Path STORAGE_DIR_PATH = Path.of("/home/andrey/andrey/lab5/putput"); + private static final Path STORAGE_DIR_PATH = Path.of("/home/andrey/andrey/lab6/storage"); private static final String LOCALHOST = "http://localhost"; private static final int BASE_PORT = 8080; private static final int CLUSTER_NODE_COUNT = 4; diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/andreycheshev/ServiceImpl.java index 450b2b3e5..02b927cc8 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/ServiceImpl.java @@ -70,7 +70,7 @@ public CompletableFuture stop() throws IOException { return CompletableFuture.completedFuture(null); } - @ServiceFactory(stage = 5) + @ServiceFactory(stage = 6) public static class Factory implements ServiceFactory.Factory { @Override diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/StreamingBuffer.java b/src/main/java/ru/vk/itmo/test/andreycheshev/StreamingBuffer.java new file mode 100644 index 000000000..ad57aa346 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/StreamingBuffer.java @@ -0,0 +1,51 @@ +package ru.vk.itmo.test.andreycheshev; + +import one.nio.net.Socket; + +import java.io.IOException; + +public class StreamingBuffer { + private final byte[] buffer; + private int size; + private int pos; + + public StreamingBuffer(int buffSize) { + this.buffer = new byte[buffSize]; + } + + public void append(byte[] bytes) { + System.arraycopy(bytes, 0, buffer, size, bytes.length); + size += bytes.length; + } + + public int size() { + return size; + } + + public boolean isEmpty() { + return size - pos <= 0; + } + + public void reset() { + size = 0; + pos = 0; + } + + public int tryWrite(Socket socket) throws IOException { + int diff = size - pos; + if (diff > 0) { + int written = socket.write(buffer, pos, diff); + if (diff == written) { + reset(); + } else { + pos += written; + } + return written; + } + return 0; + } + + public boolean isFits(int length) { + return size + length <= buffer.length; + } +} diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/StreamingSession.java b/src/main/java/ru/vk/itmo/test/andreycheshev/StreamingSession.java new file mode 100644 index 000000000..f660ce396 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/StreamingSession.java @@ -0,0 +1,113 @@ +package ru.vk.itmo.test.andreycheshev; + +import one.nio.http.HttpServer; +import one.nio.http.HttpSession; +import one.nio.http.Request; +import one.nio.http.Response; +import one.nio.net.Session; +import one.nio.net.Socket; +import ru.vk.itmo.test.andreycheshev.dao.Entry; + +import java.io.IOException; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; + +public class StreamingSession extends HttpSession { + private static final int BUFF_SIZE = (2 << 10) * (2 << 10); + + private static final byte[] CRLF = "\r\n".getBytes(StandardCharsets.UTF_8); + private static final byte[] LF = "\n".getBytes(StandardCharsets.UTF_8); + private static final byte[] END_PART = "0\r\n\r\n".getBytes(StandardCharsets.UTF_8); + + public StreamingSession(Socket socket, HttpServer server) { + super(socket, server); + } + + public synchronized void stream( + Iterator> streamIterator) throws IOException, InterruptedException { + + Request handling = this.handling; + if (handling == null) { + throw new IOException("Out of order response"); + } + + write(new RangeItem(streamIterator, handling)); + + if ((this.handling = pipeline.pollFirst()) != null) { + if (handling == FIN) { + scheduleClose(); + } else { + server.handleRequest(handling, this); + } + } + } + + public static class RangeItem extends Session.QueueItem { + private final Iterator> rangeIterator; + private final StreamingBuffer buffer = new StreamingBuffer(BUFF_SIZE); + private Entry entry; + + public RangeItem(Iterator> iterator, Request request) { + this.rangeIterator = iterator; + initResponse(request); + } + + private void initResponse(Request request) { + Response response = new Response(Response.OK); + String connection = request.getHeader("Connection:"); + boolean keepAlive = request.isHttp11() + ? !"close".equalsIgnoreCase(connection) + : "Keep-Alive".equalsIgnoreCase(connection); + response.addHeader(keepAlive ? "Connection: Keep-Alive" : "Connection: close"); + response.addHeader("Transfer-Encoding: chunked"); + + buffer.append(response.toBytes(false)); + } + + @Override + public int remaining() { + return entry != null || rangeIterator.hasNext() || !buffer.isEmpty() ? 1 : 0; + } + + @Override + public int write(Socket socket) throws IOException { + + buffer.tryWrite(socket); + + while (entry != null || rangeIterator.hasNext()) { + if (entry == null) { + entry = rangeIterator.next(); + } + + byte[] keyBytes = entry.key().toArray(ValueLayout.JAVA_BYTE); + byte[] valueBytes = entry.value().toArray(ValueLayout.JAVA_BYTE); + int entrySize = keyBytes.length + LF.length + valueBytes.length; + byte[] lengthBytes = Integer.toHexString(entrySize).getBytes(StandardCharsets.UTF_8); + + if (!buffer.isFits(lengthBytes.length + entrySize + 2 * CRLF.length + END_PART.length)) { + break; + } + + writeEntry(lengthBytes, keyBytes, valueBytes); + entry = null; + } + + if (entry == null && !rangeIterator.hasNext()) { + buffer.append(END_PART); + } + + return buffer.tryWrite(socket); + } + + private void writeEntry(byte[] length, byte[] key, byte[] value) { + buffer.append(length); + buffer.append(CRLF); + buffer.append(key); + buffer.append(LF); + buffer.append(value); + buffer.append(CRLF); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/report-5-Cheshev.md b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/report-5-Cheshev.md index 46e08a4a8..f523485a2 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/report-5-Cheshev.md +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/report-5-Cheshev.md @@ -6,12 +6,20 @@ * Параметр ack = 3, from = 4 * Количество потоков = 6 + +При реализации было использовано 5 экзекьюторов: + * Distributor - для распределения задач по CompletableFuture (6 потоков core, 6 потоков максимум); + * Internal - Для обработки ошибок и добавлении результата в сборщик результатов (3 потока); + * Sender - Для отправки ответа (3 потока); + * RemoteCall - Для вызова на удаленную ноду и ожидания результата (3 потока); + * LocalCall - Для локального запроса к dao (3 потока); + ## Сравнение с предыдущей реализацией * GET (-c 250 -t 1 -R 1100) ![](./screens/get_cmp.png) Видно, что новая реализация по сравнению с предыдущей при 90м персентиле выдает задержку меньше примерно в два раз. - Однако, в данном эксперименте также видно, что при 99.99м персентиле ситуация получается обратной. + Однако, в данном эксперименте также видно, что при 99.99м персентиле ситуация получается обратной. * PUT (-c 250 -t 1 -R 750) ![](./screens/put_cmp.png) @@ -37,25 +45,7 @@ Таким образом систему целесообразно тестировать для get запросов при rps=1700, а для put при rps=1600. По сравнению с предыдущей реализацией уровень рабочего rps увеличился для: * GET в 1,5 раза; - * PUT в 2,1 раза. - - -## Количество потоков в экзекьюторах - -![](./visualvm/1.png) -![](./visualvm/2.png) -![](./visualvm/3.png) -Представлена информации работы потоков при rps=1600. -Видно, что наибольшая задействованность наблюдается среди потоков экзекьютора -для оправки запроса на другую ноду внутри кластера (RemoteCall). В свою очередь, визуально видно, что остальные задействованы на порядок меньше. - -Основываясь на общем представлении работы системы, было принято решение использовать экзекьюторы со следующими характеристиками: -* Distributor - для распределения задач по CompletableFuture (3 потоков core, 3 потоков максимум); -* Internal - Для обработки ошибок и добавлении результата в сборщик результатов (3 потока); -* Sender - Для отправки ответа (3 потока); -* RemoteCall - Для вызова на удаленную ноду и ожидания результата (6 потока); -* LocalCall - Для локального запроса к dao (3 потока); - + * PUT в 2,1 раза. ## Проведем профилирование @@ -63,14 +53,10 @@ * GET ![](./screens/img.png) ![](./screens/img_1.png) - * Поскольку решение было переведено на использование java.net.HttpClient, то появились сопутствующие аллокации. - * Асинхронная отправка методом jdk/internal/net/http/HttpClientFacade.sendAsync занимает 12.6% аллокаций. - * 20% всех аллокаций происходит внутри метода tryAsyncReceive при получении ответа от ноды кластера. * PUT ![](./screens/img_6.png) ![](./screens/img_7.png) - * При получении ответа от ноды кластера методом tryAsyncReceive аллокации снизились до 17% по сравнении с get запросом. - + * Поскольку решение было переведено на использование java.net.HttpClient, то появились сопутствующие аллокации. * CPU * GET ![](./screens/img_2.png) @@ -80,8 +66,6 @@ ![](./screens/img_9.png) * Нагрузка действительно распределилась по потокам. Наибольшее количество процессорного времени занимает RemoteCall протоки. * LocalCall, Distributor потоки занимают в среднем от 0.5 до 1 проценту процессорного времени. - * Запись в сокет при отправке запроса на ноду кластера занимает 11-13% процессорного всего времени для GET и PUT. - * Текущее тестирование выявило, что 25-27% процессорного времени занято методом java/util/concurrent/ThreadPoolExecutor.getTask, получающим и ожидающим задачи для выполнения. Актуально для GET и PUT запросов. * LOCK * GET diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab6/profiles/alloc.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab6/profiles/alloc.png new file mode 100644 index 000000000..38d1d1b81 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab6/profiles/alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab6/profiles/cpu.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab6/profiles/cpu.png new file mode 100644 index 000000000..3083fc27b Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab6/profiles/cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab6/report-6-Cheshev.md b/src/main/java/ru/vk/itmo/test/andreycheshev/lab6/report-6-Cheshev.md new file mode 100644 index 000000000..2f9732c46 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab6/report-6-Cheshev.md @@ -0,0 +1,29 @@ +## Параметры системы: +* Treshhold bytes = 1 mb +* Размер очереди = 400 +* Max heap size = 128 mb +* Тестируем кластер из 4-х нод +* Параметр ack = 3, from = 4 + +* Использовано 5 экзекьюторов: + * Distributor - для распределения задач по CompletableFuture (3 потоков core, 3 потоков максимум); + * Internal - Для обработки ошибок и добавлении результата в сборщик результатов (3 потока); + * Sender - Для отправки ответа (3 потока); + * RemoteCall - Для вызова на удаленную ноду и ожидания результата (6 потоков); + * LocalCall - Для локального запроса к dao (3 потока); + * StreamingExecutor - Для range запросов к dao (3 потока); + +## Проведем профилирование + +* GET (range запрос по всей базе) + * ALLOC + * ![](./profiles/alloc.png) + * Вычисление длины чанка состоит из двух операций: toHexString (10% аллокаций) и getBytes(10% аллокаций). Место, которое можно было бы оптимизировать. + * Аллокации Array из MemorySegment занимают 21%. + * Наибольший процент 54% занимают аллокации при итерации по SsTable (использовании метода `final MemorySegment value = data.asSlice(offset, valueLength)`). + * CPU + * ![](./profiles/cpu.png) + * 77% процентов процессорного времени тратится на итерацию по базе данных, где основная операция это работа метода mismatch (36%). + * Видно, что работа метода append в StreamingBuffer (класс-накопитель запроса) занимает 3.6% времени, что говорит о том, что данный метод вряд ли может стать узким местом при больших нагрузках. + * LOCK + * При запросе по всей базе asprof не обнаружил ни одной блокировки.