diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/ServerImpl.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/ServerImpl.java index 048feb37e..4e554a7ca 100644 --- a/src/main/java/ru/vk/itmo/test/alenkovayulya/ServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/ServerImpl.java @@ -3,37 +3,50 @@ 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 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.alenkovayulya.dao.BaseEntryWithTimestamp; +import ru.vk.itmo.test.alenkovayulya.dao.Dao; +import ru.vk.itmo.test.alenkovayulya.dao.EntryWithTimestamp; import java.io.IOException; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import static ru.vk.itmo.test.alenkovayulya.ShardRouter.REDIRECT_HEADER; +import static ru.vk.itmo.test.alenkovayulya.ShardRouter.TIMESTAMP_HEADER; import static ru.vk.itmo.test.alenkovayulya.ShardRouter.redirectRequest; public class ServerImpl extends HttpServer { - public static final String PATH = "/v0/entity"; + private static final Logger LOGGER = LoggerFactory.getLogger(ServerImpl.class); - private final Dao> referenceDao; + private final Dao> referenceDao; private final ExecutorService executorService; private final String url; private final ShardSelector shardSelector; + private static final Set ALLOWED_METHODS = Set.of( + Request.METHOD_GET, + Request.METHOD_PUT, + Request.METHOD_DELETE + ); + private static final int[] AVAILABLE_GOOD_RESPONSE_CODES = new int[] {200, 201, 202, 404}; public ServerImpl(ServiceConfig serviceConfig, - Dao> referenceDao, + Dao> referenceDao, ExecutorService executorService, ShardSelector shardSelector) throws IOException { super(createServerConfig(serviceConfig)); this.referenceDao = referenceDao; @@ -54,87 +67,251 @@ private static HttpServerConfig createServerConfig(ServiceConfig serviceConfig) } @Override - public void handleRequest(Request request, HttpSession session) { - executorService.execute(() -> { - try { - super.handleRequest(request, session); - } catch (Exception e) { + public void handleRequest(Request request, HttpSession session) throws IOException { + if (!ALLOWED_METHODS.contains(request.getMethod())) { + sendEmptyResponse(Response.METHOD_NOT_ALLOWED, session); + return; + } + + String id = request.getParameter("id="); + if (isEmptyId(id)) { + sendEmptyResponse(Response.BAD_REQUEST, session); + return; + } + + if (request.getHeader(REDIRECT_HEADER) != null) { + long timestamp = resolveTimestamp(request.getHeader(TIMESTAMP_HEADER)); + Response response = handleInternalRequest(request, id, timestamp); + session.sendResponse(response); + } else { + handleAsLeader(request, session, id); + } + } + + private void handleAsLeader(Request request, HttpSession session, String id) { + String ackS = request.getParameter("ack="); + String fromS = request.getParameter("from="); + + int from = isEmptyId(fromS) + ? shardSelector.getClusterSize() : Integer.parseInt(fromS); + int ack = isEmptyId(ackS) + ? quorum(from) : Integer.parseInt(ackS); + + if (ack == 0 || ack > from) { + sendEmptyResponse(Response.BAD_REQUEST, session); + } + + try { + executorService.execute(() -> { try { - session.sendError(Response.BAD_REQUEST, e.getMessage()); - } catch (IOException ex) { - LOGGER.info("Exception during sending the response: ", ex); - session.close(); + collectResponses(request, session, id, from, ack); + } catch (Exception e) { + try { + session.sendError(Response.BAD_REQUEST, e.getMessage()); + } catch (IOException ex) { + LOGGER.info("Exception during sending the response: ", ex); + session.close(); + } } - } - }); + }); + } catch (RejectedExecutionException e) { + LOGGER.error("Request rejected by policy", e); + sendEmptyResponse(Response.SERVICE_UNAVAILABLE, session); + } } - @Path(PATH) - @RequestMethod(Request.METHOD_GET) - public Response getEntity(@Param(value = "id", required = true) String id) { - if (isEmptyId(id)) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + private void collectResponses(Request request, + HttpSession session, + String id, + int from, + int ack + ) { + List> asyncResponses = new CopyOnWriteArrayList<>(); + long timestamp = System.currentTimeMillis(); + int firstOwnerShardIndex = shardSelector.getOwnerShardIndex(id); + + for (int i = 0; i < from; i++) { + CompletableFuture asyncResponse; + int shardIndex = (firstOwnerShardIndex + i) % shardSelector.getClusterSize(); + + if (isRedirectNeeded(shardIndex)) { + asyncResponse = handleRedirect(request, timestamp, shardIndex); + } else { + asyncResponse = handleInternalRequestAsync(request, id, timestamp); } - String ownerShardUrl = shardSelector.getOwnerShardUrl(id); - if (isRedirectNeeded(ownerShardUrl)) { - return redirectRequest("GET", id, ownerShardUrl, new byte[0]); + + asyncResponses.add(asyncResponse); + + } + + handleAsyncResponses(session, ack, from, request, asyncResponses); + + } + + private void handleAsyncResponses( + HttpSession session, int ack, int from, Request request, + List> completableFutureResponses + ) { + List validResponses = new CopyOnWriteArrayList<>(); + AtomicBoolean isEnoughValidResponses = new AtomicBoolean(); + AtomicInteger allResponsesCounter = new AtomicInteger(); + + for (CompletableFuture completableFuture : completableFutureResponses) { + completableFuture.whenCompleteAsync((response, throwable) -> { + if (isEnoughValidResponses.get()) { + return; + } + allResponsesCounter.incrementAndGet(); + + if (throwable != null) { + response = new Response(Response.INTERNAL_ERROR); + } + + if (isValidResponse(response)) { + validResponses.add(response); + } + + sendResponseIfEnoughReplicasResponsesNumber(request, + isEnoughValidResponses, + session, + validResponses, + ack); + + if (allResponsesCounter.get() == from && validResponses.size() < ack) { + sendEmptyResponse("504 Not Enough Replicas", session); + } + }, executorService).exceptionally((th) -> new Response(Response.INTERNAL_ERROR)); + } + } + + private void sendResponseIfEnoughReplicasResponsesNumber( + Request request, + AtomicBoolean isEnoughValidResponses, + HttpSession session, + List responses, + int ack + ) { + try { + if (responses.size() >= ack) { + isEnoughValidResponses.set(true); + if (request.getMethod() == Request.METHOD_GET) { + session.sendResponse(getResponseWithMaxTimestamp(responses)); + } else { + session.sendResponse(responses.getFirst()); + } } - Entry value = referenceDao.get( - convertBytesToMemorySegment(id.getBytes(StandardCharsets.UTF_8))); + } catch (IOException e) { + LOGGER.error("Exception during send win response: ", e); + sendEmptyResponse(Response.INTERNAL_ERROR, session); + session.close(); + } + } + + private boolean isValidResponse(Response response) { + return Arrays.stream(AVAILABLE_GOOD_RESPONSE_CODES) + .anyMatch(code -> code == response.getStatus()); + } - return value == null ? new Response(Response.NOT_FOUND, Response.EMPTY) - : Response.ok(value.value().toArray(ValueLayout.JAVA_BYTE)); + private CompletableFuture handleRedirect(Request request, long timestamp, int nodeIndex) { + return redirectRequest(request.getMethodName(), + request.getParameter("id="), + shardSelector.getShardUrlByIndex(nodeIndex), + request.getBody() == null + ? new byte[0] : request.getBody(), timestamp); } - @Path(PATH) - @RequestMethod(Request.METHOD_PUT) - public Response putEntity(@Param(value = "id", required = true) String id, Request request) { - if (isEmptyId(id)) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + private CompletableFuture handleInternalRequestAsync(Request request, String id, long timestamp) { + return CompletableFuture.supplyAsync(() -> + handleInternalRequest(request, id, timestamp), ShardRouter.proxyExecutor); + } + + private Response handleInternalRequest(Request request, String id, long timestamp) { + switch (request.getMethod()) { + case Request.METHOD_GET -> { + EntryWithTimestamp entry = referenceDao.get( + convertBytesToMemorySegment(id.getBytes(StandardCharsets.UTF_8))); + + if (entry == null) { + return new Response(Response.NOT_FOUND, Response.EMPTY); + } else if (entry.value() == null) { + Response response = new Response(Response.NOT_FOUND, Response.EMPTY); + response.addHeader(TIMESTAMP_HEADER + ": " + entry.timestamp()); + return response; + } else { + Response response = Response.ok(entry.value().toArray(ValueLayout.JAVA_BYTE)); + response.addHeader(TIMESTAMP_HEADER + ": " + entry.timestamp()); + return response; + } } - String ownerShardUrl = shardSelector.getOwnerShardUrl(id); - if (isRedirectNeeded(ownerShardUrl)) { - return redirectRequest("PUT", id, ownerShardUrl, request.getBody()); + case Request.METHOD_PUT -> { + referenceDao.upsert(new BaseEntryWithTimestamp<>( + convertBytesToMemorySegment(id.getBytes(StandardCharsets.UTF_8)), + convertBytesToMemorySegment(request.getBody()), timestamp)); + + return new Response(Response.CREATED, Response.EMPTY); } - referenceDao.upsert(new BaseEntry<>( - convertBytesToMemorySegment(id.getBytes(StandardCharsets.UTF_8)), - convertBytesToMemorySegment(request.getBody()))); - return new Response(Response.CREATED, Response.EMPTY); - } - - @Path(PATH) - @RequestMethod(Request.METHOD_DELETE) - public Response deleteEntity(@Param(value = "id", required = true) String id) { - String ownerShardUrl = shardSelector.getOwnerShardUrl(id); - if (isRedirectNeeded(ownerShardUrl)) { - return redirectRequest("DELETE", id, ownerShardUrl, new byte[0]); + case Request.METHOD_DELETE -> { + referenceDao.upsert(new BaseEntryWithTimestamp<>( + convertBytesToMemorySegment(id.getBytes(StandardCharsets.UTF_8)), + null, timestamp)); + + return new Response(Response.ACCEPTED, Response.EMPTY); } - if (isEmptyId(id)) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + default -> { + return new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); } - referenceDao.upsert(new BaseEntry<>( - convertBytesToMemorySegment(id.getBytes(StandardCharsets.UTF_8)), null)); - return new Response(Response.ACCEPTED, Response.EMPTY); - } - - @Override - public void handleDefault(Request request, HttpSession session) throws IOException { - switch (request.getMethodName()) { - case "GET", "PUT", "DELETE" -> session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); - default -> session.sendResponse(new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY)); } } - private boolean isRedirectNeeded(String ownerUrl) { - return !url.equals(ownerUrl); + private boolean isRedirectNeeded(int shardIndex) { + return !url.equals(shardSelector.getShardUrlByIndex(shardIndex)); } private boolean isEmptyId(String id) { - return id.isEmpty() && id.isBlank(); + return id == null || (id.isEmpty() && id.isBlank()); } private MemorySegment convertBytesToMemorySegment(byte[] byteArray) { return MemorySegment.ofArray(byteArray); } + private int quorum(int from) { + return from / 2 + 1; + } + + private long resolveTimestamp(String timestampHeader) { + if (isEmptyId(timestampHeader)) { + return 0L; + } + try { + return Long.parseLong(timestampHeader); + } catch (NumberFormatException e) { + return 0L; + } + } + + private void sendEmptyResponse(String response, HttpSession session) { + var emptyRes = new Response(response, Response.EMPTY); + try { + session.sendResponse(emptyRes); + } catch (IOException e) { + LOGGER.info("Exception during sending the empty response: ", e); + } + } + + private Response getResponseWithMaxTimestamp(List responses) { + Response result = responses.getFirst(); + long max = 0; + for (Response response : responses) { + String timestampHeader = response.getHeaders()[response.getHeaderCount() - 1]; + + long timestamp = resolveTimestamp(timestampHeader); + if (max < timestamp) { + max = timestamp; + result = response; + } + } + + return result; + } } diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/ServiceImpl.java index b06c120ec..d755191a1 100644 --- a/src/main/java/ru/vk/itmo/test/alenkovayulya/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/ServiceImpl.java @@ -3,9 +3,9 @@ import ru.vk.itmo.Service; import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.dao.Config; -import ru.vk.itmo.dao.Dao; -import ru.vk.itmo.dao.Entry; import ru.vk.itmo.test.ServiceFactory; +import ru.vk.itmo.test.alenkovayulya.dao.Dao; +import ru.vk.itmo.test.alenkovayulya.dao.EntryWithTimestamp; import ru.vk.itmo.test.alenkovayulya.dao.ReferenceDao; import java.io.IOException; @@ -17,7 +17,7 @@ public class ServiceImpl implements Service { - private Dao> referenceDao; + private Dao> referenceDao; private ExecutorService executorService; private ServerImpl server; private final ServiceConfig config; @@ -72,7 +72,7 @@ private void shutdownDao() { } } - @ServiceFactory(stage = 3) + @ServiceFactory(stage = 5) public static class Factory implements ServiceFactory.Factory { @Override public Service create(ServiceConfig config) { diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/ShardRouter.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/ShardRouter.java index 6a0dbea8a..c26f7b397 100644 --- a/src/main/java/ru/vk/itmo/test/alenkovayulya/ShardRouter.java +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/ShardRouter.java @@ -1,5 +1,6 @@ package ru.vk.itmo.test.alenkovayulya; +import one.nio.async.CustomThreadFactory; import one.nio.http.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -8,42 +9,69 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; - -import static ru.vk.itmo.test.alenkovayulya.ServerImpl.PATH; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public final class ShardRouter { private static final Logger LOGGER = LoggerFactory.getLogger(ShardRouter.class); + public static final String PATH = "/v0/entity"; + public static final String TIMESTAMP_HEADER = "Timestamp"; + public static final String REDIRECT_HEADER = "Redirect"; private static final HttpClient client = HttpClient.newHttpClient(); + public static ThreadPoolExecutor proxyExecutor = new ThreadPoolExecutor( + 8, + 8, + 0L, + TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(128), + new CustomThreadFactory("ShardRouter")); + private ShardRouter() { } - public static Response redirectRequest(String method, String id, String ownerShardUrl, byte[] body) { + public static CompletableFuture redirectRequest(String method, + String id, + String ownerShardUrl, + byte[] body, + long timestamp) { String path = ownerShardUrl + PATH + "?id=" + id; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(path)) + .header(REDIRECT_HEADER, "true") + .header(TIMESTAMP_HEADER, String.valueOf(timestamp)) .method(method, HttpRequest.BodyPublishers.ofByteArray(body)) .build(); try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofByteArray()); - return new Response(getHttpResponseByCode(response.statusCode()), response.body()); + CompletableFuture> response = client.sendAsync( + request, + HttpResponse.BodyHandlers.ofByteArray()); + return response.thenApplyAsync(ShardRouter::getHttpResponseByCode); } catch (Exception e) { LOGGER.error("Error during sending request by router", e); Thread.currentThread().interrupt(); - return new Response(Response.INTERNAL_ERROR, Response.EMPTY); + return CompletableFuture.completedFuture(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); } } - private static String getHttpResponseByCode(int code) { - return switch (code) { + private static Response getHttpResponseByCode(HttpResponse response) { + String responseCode = switch (response.statusCode()) { case 200 -> Response.OK; case 201 -> Response.CREATED; case 202 -> Response.ACCEPTED; case 400 -> Response.BAD_REQUEST; case 404 -> Response.NOT_FOUND; - case 405 -> Response.METHOD_NOT_ALLOWED; - default -> Response.INTERNAL_ERROR; + case 500 -> Response.INTERNAL_ERROR; + default -> throw new IllegalStateException("Not available status code: " + response.statusCode()); }; + + Response shardResponse = new Response(responseCode, response.body()); + shardResponse.addHeader(response.headers().firstValue(TIMESTAMP_HEADER).orElse("")); + + return shardResponse; + } } diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/ShardSelector.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/ShardSelector.java index 16e0a587d..0b70cf34d 100644 --- a/src/main/java/ru/vk/itmo/test/alenkovayulya/ShardSelector.java +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/ShardSelector.java @@ -12,16 +12,24 @@ public ShardSelector(List shardUrls) { this.shardUrls = shardUrls; } - public String getOwnerShardUrl(String id) { + public int getOwnerShardIndex(String id) { int maxHash = Integer.MIN_VALUE; - String maxHashShardUrl = null; - for (String shard : shardUrls) { - int hash = Hash.murmur3(id + shard); + int maxHashShardIndex = -1; + for (int i = 0; i < shardUrls.size(); i++) { + int hash = Hash.murmur3(id + shardUrls.get(i)); if (hash > maxHash) { maxHash = hash; - maxHashShardUrl = shard; + maxHashShardIndex = i; } } - return maxHashShardUrl; + return maxHashShardIndex; + } + + public int getClusterSize() { + return shardUrls.size(); + } + + public String getShardUrlByIndex(int index) { + return shardUrls.get(index); } } diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/BaseEntryWithTimestamp.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/BaseEntryWithTimestamp.java new file mode 100644 index 000000000..37404d423 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/BaseEntryWithTimestamp.java @@ -0,0 +1,8 @@ +package ru.vk.itmo.test.alenkovayulya.dao; + +public record BaseEntryWithTimestamp(D key, D value, long timestamp) implements EntryWithTimestamp { + @Override + public String toString() { + return "{" + key + ":" + value + ":" + timestamp + "}"; + } +} diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/Dao.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/Dao.java new file mode 100644 index 000000000..aa490b7c6 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/Dao.java @@ -0,0 +1,87 @@ +package ru.vk.itmo.test.alenkovayulya.dao; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Iterator; + +public interface Dao> extends Closeable { + /** + * Returns ordered iterator of entries with keys between from (inclusive) and to (exclusive). + * @param from lower bound of range (inclusive) + * @param to upper bound of range (exclusive) + * @return entries [from;to) + */ + Iterator get(D from, D to); + + /** + * Returns entry by key. Note: default implementation is far from optimal. + * @param key entry`s key + * @return entry + */ + default E get(D key) { + Iterator iterator = get(key, null); + if (!iterator.hasNext()) { + return null; + } + + E next = iterator.next(); + if (next.key().equals(key)) { + return next; + } + return null; + } + + /** + * Returns ordered iterator of all entries with keys from (inclusive). + * @param from lower bound of range (inclusive) + * @return entries with key >= from + */ + default Iterator allFrom(D from) { + return get(from, null); + } + + /** + * Returns ordered iterator of all entries with keys < to. + * @param to upper bound of range (exclusive) + * @return entries with key < to + */ + default Iterator allTo(D to) { + return get(null, to); + } + + /** + * Returns ordered iterator of all entries. + * @return all entries + */ + default Iterator all() { + return get(null, null); + } + + /** + * Inserts of replaces entry. + * @param entry element to upsert + */ + void upsert(E entry); + + /** + * Persists data (no-op by default). + */ + default void flush() throws IOException { + // Do nothing + } + + /** + * Compacts data (no-op by default). + */ + default void compact() throws IOException { + // Do nothing + } + + /* + * Releases Dao (calls flush by default). + */ + @Override + default void close() throws IOException { + flush(); + } +} diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/EntryWithTimestamp.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/EntryWithTimestamp.java new file mode 100644 index 000000000..81a424d81 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/EntryWithTimestamp.java @@ -0,0 +1,9 @@ +package ru.vk.itmo.test.alenkovayulya.dao; + +public interface EntryWithTimestamp { + D key(); + + D value(); + + long timestamp(); +} diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/MemTable.java index 3ff8f1f40..b1a384203 100644 --- a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/MemTable.java +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/MemTable.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.alenkovayulya.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/alenkovayulya/dao/MergingEntryIterator.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/MergingEntryIterator.java index 77e232a76..062cd2cb2 100644 --- a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/MergingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/MergingEntryIterator.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.alenkovayulya.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/alenkovayulya/dao/ReferenceDao.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/ReferenceDao.java index 306816ba5..3291e7fe9 100644 --- a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/ReferenceDao.java +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/ReferenceDao.java @@ -1,8 +1,6 @@ package ru.vk.itmo.test.alenkovayulya.dao; 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; @@ -22,7 +20,7 @@ * * @author incubos */ -public class ReferenceDao implements Dao> { +public class ReferenceDao implements Dao> { private final Config config; private final Arena arena; @@ -63,23 +61,22 @@ public ReferenceDao(final Config config) throws IOException { } @Override - public Iterator> get( + public Iterator> get( final MemorySegment from, final MemorySegment to) { - return new LiveFilteringIterator( - tableSet.get( - from, - to)); + return tableSet.get( + from, + to); } @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,7 +86,7 @@ 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)); @@ -103,16 +100,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() + Long.BYTES; } - return entry.key().byteSize() + entry.value().byteSize(); + return entry.key().byteSize() + entry.value().byteSize() + Long.BYTES; } private void initiateFlush(final boolean auto) { @@ -200,8 +197,7 @@ public void compact() throws IOException { .write( config.basePath(), 0, - new LiveFilteringIterator( - currentTableSet.allSSTableEntries())); + currentTableSet.allSSTableEntries()); } catch (IOException e) { e.printStackTrace(); Runtime.getRuntime().halt(-3); diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/SSTable.java index 91233ce1a..1f9db1b57 100644 --- a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/SSTable.java +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/SSTable.java @@ -1,8 +1,5 @@ package ru.vk.itmo.test.alenkovayulya.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; @@ -54,7 +51,7 @@ private long entryBinarySearch(final MemorySegment key) { while (low <= high) { final long mid = (low + high) >>> 1; final long midEntryOffset = entryOffset(mid); - final long midKeyLength = getLength(midEntryOffset); + final long midKeyLength = getValue(midEntryOffset); final int compare = MemorySegmentComparator.compare( data, @@ -82,13 +79,13 @@ private long entryOffset(final long entry) { entry * Long.BYTES); } - private long getLength(final long offset) { + private long getValue(final long offset) { return data.get( ValueLayout.OfLong.JAVA_LONG_UNALIGNED, 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; @@ -144,19 +141,23 @@ Entry get(final MemorySegment key) { long offset = entryOffset(entry); offset += Long.BYTES + key.byteSize(); // Extract value length - final long valueLength = getLength(offset); + final long valueLength = getValue(offset); if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { // Tombstone encountered - return new BaseEntry<>(key, null); + offset += Long.BYTES; + final long timestamp = getValue(offset); + return new BaseEntryWithTimestamp<>(key, null, timestamp); } else { // Get value offset += Long.BYTES; final MemorySegment value = data.asSlice(offset, valueLength); - return new BaseEntry<>(key, value); + offset += valueLength; + final long timestamp = getValue(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,13 +174,13 @@ public boolean hasNext() { } @Override - public Entry next() { + public EntryWithTimestamp next() { if (!hasNext()) { throw new NoSuchElementException(); } // Read key length - final long keyLength = getLength(offset); + final long keyLength = getValue(offset); offset += Long.BYTES; // Read key @@ -187,17 +188,21 @@ public Entry next() { offset += keyLength; // Read value length - final long valueLength = getLength(offset); + final long valueLength = getValue(offset); offset += Long.BYTES; // Read value if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { // Tombstone encountered - return new BaseEntry<>(key, null); + final long timestamp = getValue(offset); + offset += Long.BYTES; + return new BaseEntryWithTimestamp<>(key, null, timestamp); } else { final MemorySegment value = data.asSlice(offset, valueLength); offset += valueLength; - return new BaseEntry<>(key, value); + final long timestamp = getValue(offset); + offset += Long.BYTES; + return new BaseEntryWithTimestamp<>(key, value, timestamp); } } } diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/SSTableWriter.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/SSTableWriter.java index e2d828bd2..102e9da45 100644 --- a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/SSTableWriter.java +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/SSTableWriter.java @@ -40,7 +40,7 @@ final class SSTableWriter { void write( final Path baseDir, final int sequence, - final Iterator> entries) throws IOException { + final Iterator> entries) throws IOException { // Write to temporary files final Path tempIndexName = SSTables.tempIndexName(baseDir, sequence); final Path tempDataName = SSTables.tempDataName(baseDir, sequence); @@ -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); } } @@ -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 @@ -151,6 +152,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); @@ -159,6 +164,10 @@ private long writeEntry( // Value writeSegment(value, os); result += value.byteSize(); + + // Timestamp + writeLong(timestamp, os); + result += Long.BYTES; } return result; diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/TableSet.java index 1f8227824..dec62fbba 100644 --- a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/TableSet.java +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/TableSet.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.alenkovayulya.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; import java.util.ArrayList; import java.util.Collections; @@ -98,14 +96,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 +114,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 +127,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,22 +144,20 @@ 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); + return result; } // Then check flushing if (flushingTable != null) { result = flushingTable.get(key); if (result != null) { - // Transform tombstone - return swallowTombstone(result); + return result; } } @@ -169,8 +165,7 @@ Entry get(final MemorySegment key) { for (final SSTable ssTable : ssTables) { result = ssTable.get(key); if (result != null) { - // Transform tombstone - return swallowTombstone(result); + return result; } } @@ -178,21 +173,17 @@ Entry get(final MemorySegment key) { return null; } - private static Entry swallowTombstone(final Entry entry) { - return entry.value() == null ? null : entry; - } - - 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( diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/WeightedPeekingEntryIterator.java b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/WeightedPeekingEntryIterator.java index 05b8d74ba..8ec58cbd4 100644 --- a/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/WeightedPeekingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/dao/WeightedPeekingEntryIterator.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.alenkovayulya.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(); } diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/reports/report_stage4.md b/src/main/java/ru/vk/itmo/test/alenkovayulya/reports/report_stage4.md new file mode 100644 index 000000000..422fb643e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/reports/report_stage4.md @@ -0,0 +1,129 @@ +### Репликация + +Основная идея данного этапа заключается в повышении отказоустойчивости за счет дублирования данных. +Чтобы это реализовать, мы ввели новые параметры ack - кол-во необходимых ответов от нод и from - количество нод, на которые мы отправляем запрос. +Результаты, полученные от нод, мы мержим. При возникновении конфликтов, решаем их, опираясь на timestamp, которую мы теперь тоже храним. + + +### Latency + +В дальнейших исследованиях будем использовать кластер из 3-ех нод с дефолтными значениями для ack/from равными 2/3 соответсвенно (так как по умолчанию: from - число нод в кластере, ack - кворум от числа нод в кластере) + +Последоватльныи регулированием нагрузки была определена точка разладки для PUT-запросов: + +``` +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 20 -t 1 -L -R 2300 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/put.lua http://localhost:8080 + 50.000% 1.80ms + 75.000% 3.32ms + 90.000% 33.76ms + 99.000% 60.48ms + 99.900% 63.65ms + 99.990% 65.15ms + 99.999% 65.15ms +100.000% 65.15ms +``` + +Latency нереально сильно просела, теперь точка разладки для PUT запросов составляет всего 2300 rps. + +Для GET запросов была найдена следующая точка разладки: + +``` +./wrk -c 64 -d 30 -t 1 -L -R 2000 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/get.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 2.364ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.88ms 8.30ms 60.22ms 92.92% + Req/Sec 242.83 724.12 6.70k 89.10% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.61ms + 75.000% 2.31ms + 90.000% 4.50ms + 99.000% 45.50ms + 99.900% 55.20ms + 99.990% 60.26ms + 99.999% 60.26ms +100.000% 60.26ms +``` + +Из-за добавления реаликации производительность отностительно предыдущего этапа просела больше, чем в 10 раз. +При этом, чем больше в кластере будет нод, тем сильнее будет снижаться пропускная способность. + + + +### Профилирование + + +#### PUT, cpu + +![](../results_stage4/asprof-put-cpu.png) + +В целом, сильных отличий от профиля без реплицирования нет. +В процентном соотношении практически ничего не поменялось. +Добавились новые методы, но они встречаются в совсем небольшом количестве семплов, например: + +- checkReplicasResponsesNumber - 2.11% + +Стоит отметить, что обработка запроса на шарде, которая хранит нужные данные, занимает всего лишь 0.83%(handleInternalRequest), в то время как redirect на другую шарду съедает 12.28%(handleRedirect) + + +#### PUT, alloc + +![](../results_stage4/asprof-put-alloc.png) + +Сравнивая данный профиль с версией без репликации, можно заметить следующее: +в SelectorThread процент аллокаций увеличился. Это произошло из-за нового метода +resolveTimestamp (4.19%) для того, чтобы из заголовка ответа достать timestamp и далее производить сравнения +ответов разных нод. +Незначительные аллокации для методов: +checkReplicasResponsesNumber - 0.19% +handleInternalRequest - 0.61% , +handleRedirect - 54.38% + + +#### PUT, lock + +![](../results_stage4/asprof-put-lock.png) + +Заметно, что в новой версии с репликаций заметно увеличилось число локов при обработке запроса. +Можно попробовать от них избавиться, сделав сетевое взаимодействие узлов сделать асинхронным. + + +#### GET, cpu + +![](../results_stage4/asprof-get-cpu.png) + +В данном профиле отмечу, что процент сэмплов на проксированию увеличился на 2%, что, как мне кажется, связано с заполнением заголовка и проставления таймстемпа. + +Вновь появившиеся методы снова встречаются в совсем небольшом значении сэмлов. + +checkReplicasResponsesNumber - 2.39% +getResponseWithMaxTimestamp - 0.94% + + +#### GET, alloc + +![](../results_stage4/asprof-get-alloc.png) + +С точки зрения аллокаций, все абсолютно аналогично PUT-запросам. +checkReplicasResponsesNumber - 2.96% +getResponseWithMaxTimestamp - 2.63% + +Кроме того заметно, что увеличились аллокации в пуле воркеров, что связано с общением с другими нодами. + + +#### GET, lock + +![](../results_stage4/asprof-get-lock.png) + +По сравнения с предыдущей реализацией, появилось больше локов, связанных с общением нод. + +### Итог + +## Выводы и возможные улучшения + +С одной стороны, репликация позволяет нам быть готовым к отказам какой-либо ноды, так как благодаря дубликации мы не теряем доступ к данным, что хорошо. +С другой стороны, ради такой доступности мы очень сильно просядаем в производительности. + +Есть вариант попробовать сделать сетевое взаимодействие асинхронным и непоследовательным, так как сейчас нам приходится +ждать, когда каждая из нод получит,обработает запрос и ответит, что занимаем немало времени, которое можно было бы использовать с пользой, а не блокироваться. diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-get-alloc.png b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-get-alloc.png new file mode 100644 index 000000000..9aac29c80 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-get-alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-get-cpu.png b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-get-cpu.png new file mode 100644 index 000000000..1fde519fd Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-get-cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-get-lock.png b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-get-lock.png new file mode 100644 index 000000000..88eddb323 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-get-lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-put-alloc.png b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-put-alloc.png new file mode 100644 index 000000000..d1d837a8a Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-put-alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-put-cpu.png b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-put-cpu.png new file mode 100644 index 000000000..ca6890f41 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-put-cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-put-lock.png b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-put-lock.png new file mode 100644 index 000000000..493800930 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/asprof-put-lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-get-alloc.html b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-get-alloc.html new file mode 100644 index 000000000..170b9d624 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-get-alloc.html @@ -0,0 +1,2823 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-get-cpu.html b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-get-cpu.html new file mode 100644 index 000000000..9540c75af --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-get-cpu.html @@ -0,0 +1,2418 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-get-lock.html b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-get-lock.html new file mode 100644 index 000000000..ff715d3ad --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-get-lock.html @@ -0,0 +1,746 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-put-alloc.html b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-put-alloc.html new file mode 100644 index 000000000..eab12e9f9 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-put-alloc.html @@ -0,0 +1,2924 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-put-cpu.html b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-put-cpu.html new file mode 100644 index 000000000..ae4d51e1a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-put-cpu.html @@ -0,0 +1,2265 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-put-lock.html b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-put-lock.html new file mode 100644 index 000000000..05d340e3e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/html/asprof-put-lock.html @@ -0,0 +1,742 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/wrk2_get.txt b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/wrk2_get.txt new file mode 100644 index 000000000..aa0e359a6 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/wrk2_get.txt @@ -0,0 +1,518 @@ +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 30 -t 1 -L -R 1500 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/get.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 2.223ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.74ms 3.82ms 27.78ms 92.50% + Req/Sec 41.80 249.86 2.09k 97.06% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.69ms + 75.000% 2.38ms + 90.000% 4.16ms + 99.000% 20.61ms + 99.900% 27.58ms + 99.990% 27.79ms + 99.999% 27.79ms +100.000% 27.79ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.280 0.000000 1 1.00 + 0.828 0.100000 75 1.11 + 1.076 0.200000 151 1.25 + 1.278 0.300000 226 1.43 + 1.417 0.400000 299 1.67 + 1.689 0.500000 374 2.00 + 1.821 0.550000 411 2.22 + 1.972 0.600000 449 2.50 + 2.103 0.650000 486 2.86 + 2.243 0.700000 523 3.33 + 2.383 0.750000 561 4.00 + 2.531 0.775000 579 4.44 + 2.691 0.800000 598 5.00 + 2.853 0.825000 618 5.71 + 3.135 0.850000 635 6.67 + 3.577 0.875000 654 8.00 + 3.857 0.887500 663 8.89 + 4.191 0.900000 673 10.00 + 5.119 0.912500 682 11.43 + 6.207 0.925000 691 13.33 + 8.167 0.937500 701 16.00 + 9.831 0.943750 705 17.78 + 11.335 0.950000 710 20.00 + 12.031 0.956250 715 22.86 + 13.175 0.962500 719 26.67 + 15.591 0.968750 724 32.00 + 16.783 0.971875 726 35.56 + 17.103 0.975000 729 40.00 + 17.615 0.978125 731 45.71 + 18.063 0.981250 733 53.33 + 19.471 0.984375 736 64.00 + 19.759 0.985938 737 71.11 + 20.031 0.987500 738 80.00 + 20.255 0.989062 739 91.43 + 20.607 0.990625 740 106.67 + 23.423 0.992188 742 128.00 + 23.423 0.992969 742 142.22 + 23.823 0.993750 743 160.00 + 23.823 0.994531 743 182.86 + 24.319 0.995313 744 213.33 + 26.719 0.996094 745 256.00 + 26.719 0.996484 745 284.44 + 26.719 0.996875 745 320.00 + 26.719 0.997266 745 365.71 + 27.583 0.997656 746 426.67 + 27.583 0.998047 746 512.00 + 27.583 0.998242 746 568.89 + 27.583 0.998437 746 640.00 + 27.583 0.998633 746 731.43 + 27.791 0.998828 747 853.33 + 27.791 1.000000 747 inf +#[Mean = 2.744, StdDeviation = 3.825] +#[Max = 27.776, Total count = 747] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 30 -t 1 -L -R 1600 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/get.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 2.776ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.20ms 2.87ms 39.17ms 95.61% + Req/Sec 464.55 801.76 7.22k 74.88% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.62ms + 75.000% 2.22ms + 90.000% 3.15ms + 99.000% 16.30ms + 99.900% 34.11ms + 99.990% 36.26ms + 99.999% 39.20ms +100.000% 39.20ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.339 0.000000 1 1.00 + 0.888 0.100000 835 1.11 + 1.091 0.200000 1670 1.25 + 1.266 0.300000 2506 1.43 + 1.430 0.400000 3342 1.67 + 1.619 0.500000 4174 2.00 + 1.709 0.550000 4595 2.22 + 1.814 0.600000 5009 2.50 + 1.938 0.650000 5426 2.86 + 2.065 0.700000 5847 3.33 + 2.221 0.750000 6266 4.00 + 2.303 0.775000 6469 4.44 + 2.387 0.800000 6681 5.00 + 2.483 0.825000 6891 5.71 + 2.619 0.850000 7096 6.67 + 2.847 0.875000 7305 8.00 + 3.007 0.887500 7408 8.89 + 3.147 0.900000 7512 10.00 + 3.411 0.912500 7616 11.43 + 3.815 0.925000 7721 13.33 + 4.243 0.937500 7825 16.00 + 4.475 0.943750 7879 17.78 + 4.743 0.950000 7929 20.00 + 5.115 0.956250 7981 22.86 + 5.875 0.962500 8034 26.67 + 6.903 0.968750 8086 32.00 + 7.403 0.971875 8112 35.56 + 8.199 0.975000 8140 40.00 + 9.135 0.978125 8164 45.71 + 10.119 0.981250 8191 53.33 + 11.111 0.984375 8216 64.00 + 12.711 0.985938 8230 71.11 + 14.023 0.987500 8242 80.00 + 15.391 0.989062 8255 91.43 + 17.567 0.990625 8268 106.67 + 20.703 0.992188 8281 128.00 + 21.423 0.992969 8288 142.22 + 22.687 0.993750 8294 160.00 + 24.767 0.994531 8301 182.86 + 25.503 0.995313 8307 213.33 + 26.463 0.996094 8314 256.00 + 26.607 0.996484 8317 284.44 + 27.503 0.996875 8320 320.00 + 29.503 0.997266 8324 365.71 + 30.287 0.997656 8327 426.67 + 31.391 0.998047 8330 512.00 + 31.903 0.998242 8332 568.89 + 31.983 0.998437 8333 640.00 + 32.543 0.998633 8335 731.43 + 33.151 0.998828 8337 853.33 + 34.111 0.999023 8338 1024.00 + 34.527 0.999121 8339 1137.78 + 34.591 0.999219 8340 1280.00 + 35.231 0.999316 8341 1462.86 + 35.327 0.999414 8342 1706.67 + 35.327 0.999512 8342 2048.00 + 35.711 0.999561 8343 2275.56 + 35.711 0.999609 8343 2560.00 + 36.063 0.999658 8344 2925.71 + 36.063 0.999707 8344 3413.33 + 36.063 0.999756 8344 4096.00 + 36.255 0.999780 8345 4551.11 + 36.255 0.999805 8345 5120.00 + 36.255 0.999829 8345 5851.43 + 36.255 0.999854 8345 6826.67 + 36.255 0.999878 8345 8192.00 + 39.199 0.999890 8346 9102.22 + 39.199 1.000000 8346 inf +#[Mean = 2.200, StdDeviation = 2.872] +#[Max = 39.168, Total count = 8346] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 30 -t 1 -L -R 1800 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/get.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 2.455ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.43ms 4.17ms 43.52ms 94.76% + Req/Sec 351.26 793.45 8.22k 82.54% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.50ms + 75.000% 2.07ms + 90.000% 2.83ms + 99.000% 25.28ms + 99.900% 42.05ms + 99.990% 43.36ms + 99.999% 43.55ms +100.000% 43.55ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.364 0.000000 1 1.00 + 0.825 0.100000 634 1.11 + 1.007 0.200000 1266 1.25 + 1.173 0.300000 1895 1.43 + 1.340 0.400000 2534 1.67 + 1.501 0.500000 3161 2.00 + 1.590 0.550000 3474 2.22 + 1.691 0.600000 3791 2.50 + 1.801 0.650000 4109 2.86 + 1.921 0.700000 4422 3.33 + 2.069 0.750000 4738 4.00 + 2.141 0.775000 4895 4.44 + 2.233 0.800000 5053 5.00 + 2.331 0.825000 5212 5.71 + 2.449 0.850000 5370 6.67 + 2.613 0.875000 5529 8.00 + 2.727 0.887500 5607 8.89 + 2.839 0.900000 5686 10.00 + 3.031 0.912500 5766 11.43 + 3.425 0.925000 5843 13.33 + 4.583 0.937500 5922 16.00 + 5.887 0.943750 5961 17.78 + 7.391 0.950000 6001 20.00 + 8.695 0.956250 6040 22.86 + 10.679 0.962500 6080 26.67 + 12.647 0.968750 6119 32.00 + 14.431 0.971875 6139 35.56 + 15.631 0.975000 6159 40.00 + 16.847 0.978125 6178 45.71 + 18.879 0.981250 6198 53.33 + 21.151 0.984375 6218 64.00 + 22.335 0.985938 6228 71.11 + 23.807 0.987500 6238 80.00 + 24.687 0.989062 6247 91.43 + 25.743 0.990625 6257 106.67 + 26.895 0.992188 6267 128.00 + 27.535 0.992969 6272 142.22 + 28.639 0.993750 6277 160.00 + 29.807 0.994531 6282 182.86 + 31.327 0.995313 6287 213.33 + 32.079 0.996094 6292 256.00 + 34.623 0.996484 6294 284.44 + 36.959 0.996875 6297 320.00 + 37.439 0.997266 6299 365.71 + 37.951 0.997656 6302 426.67 + 39.679 0.998047 6304 512.00 + 39.711 0.998242 6305 568.89 + 40.799 0.998437 6307 640.00 + 40.863 0.998633 6308 731.43 + 41.503 0.998828 6309 853.33 + 42.047 0.999023 6310 1024.00 + 42.431 0.999121 6311 1137.78 + 43.103 0.999219 6312 1280.00 + 43.103 0.999316 6312 1462.86 + 43.263 0.999414 6313 1706.67 + 43.263 0.999512 6313 2048.00 + 43.359 0.999561 6315 2275.56 + 43.359 0.999609 6315 2560.00 + 43.359 0.999658 6315 2925.71 + 43.359 0.999707 6315 3413.33 + 43.359 0.999756 6315 4096.00 + 43.359 0.999780 6315 4551.11 + 43.359 0.999805 6315 5120.00 + 43.359 0.999829 6315 5851.43 + 43.551 0.999854 6316 6826.67 + 43.551 1.000000 6316 inf +#[Mean = 2.427, StdDeviation = 4.167] +#[Max = 43.520, Total count = 6316] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 30 -t 1 -L -R 1900 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/get.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 91.774ms, rate sampling interval: 793ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.59ms 7.05ms 51.94ms 93.11% + Req/Sec 216.16 593.52 2.00k 88.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.65ms + 75.000% 2.35ms + 90.000% 4.43ms + 99.000% 38.46ms + 99.900% 50.88ms + 99.990% 51.97ms + 99.999% 51.97ms +100.000% 51.97ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.263 0.000000 1 1.00 + 0.898 0.100000 431 1.11 + 1.096 0.200000 859 1.25 + 1.284 0.300000 1288 1.43 + 1.470 0.400000 1714 1.67 + 1.653 0.500000 2143 2.00 + 1.750 0.550000 2359 2.22 + 1.876 0.600000 2573 2.50 + 2.002 0.650000 2785 2.86 + 2.165 0.700000 2999 3.33 + 2.351 0.750000 3214 4.00 + 2.467 0.775000 3321 4.44 + 2.611 0.800000 3429 5.00 + 2.753 0.825000 3536 5.71 + 2.929 0.850000 3642 6.67 + 3.297 0.875000 3749 8.00 + 3.699 0.887500 3803 8.89 + 4.431 0.900000 3856 10.00 + 5.959 0.912500 3910 11.43 + 8.495 0.925000 3963 13.33 + 13.023 0.937500 4017 16.00 + 16.847 0.943750 4044 17.78 + 20.271 0.950000 4070 20.00 + 22.479 0.956250 4097 22.86 + 24.671 0.962500 4124 26.67 + 28.191 0.968750 4151 32.00 + 29.455 0.971875 4164 35.56 + 30.463 0.975000 4177 40.00 + 32.095 0.978125 4191 45.71 + 34.463 0.981250 4204 53.33 + 35.743 0.984375 4218 64.00 + 36.191 0.985938 4224 71.11 + 37.119 0.987500 4231 80.00 + 37.823 0.989062 4238 91.43 + 38.943 0.990625 4244 106.67 + 40.063 0.992188 4251 128.00 + 40.543 0.992969 4254 142.22 + 40.927 0.993750 4258 160.00 + 42.399 0.994531 4261 182.86 + 43.167 0.995313 4264 213.33 + 44.127 0.996094 4268 256.00 + 44.447 0.996484 4269 284.44 + 46.399 0.996875 4271 320.00 + 46.975 0.997266 4273 365.71 + 47.807 0.997656 4274 426.67 + 48.991 0.998047 4276 512.00 + 50.143 0.998242 4277 568.89 + 50.495 0.998437 4278 640.00 + 50.719 0.998633 4279 731.43 + 50.719 0.998828 4279 853.33 + 50.879 0.999023 4280 1024.00 + 51.103 0.999121 4281 1137.78 + 51.103 0.999219 4281 1280.00 + 51.423 0.999316 4282 1462.86 + 51.423 0.999414 4282 1706.67 + 51.423 0.999512 4282 2048.00 + 51.711 0.999561 4283 2275.56 + 51.711 0.999609 4283 2560.00 + 51.711 0.999658 4283 2925.71 + 51.711 0.999707 4283 3413.33 + 51.711 0.999756 4283 4096.00 + 51.967 0.999780 4284 4551.11 + 51.967 1.000000 4284 inf +#[Mean = 3.589, StdDeviation = 7.046] +#[Max = 51.936, Total count = 4284] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 30 -t 1 -L -R 2000 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/get.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 2.364ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.88ms 8.30ms 60.22ms 92.92% + Req/Sec 242.83 724.12 6.70k 89.10% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.61ms + 75.000% 2.31ms + 90.000% 4.50ms + 99.000% 45.50ms + 99.900% 55.20ms + 99.990% 60.26ms + 99.999% 60.26ms +100.000% 60.26ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.323 0.000000 1 1.00 + 0.830 0.100000 435 1.11 + 1.029 0.200000 869 1.25 + 1.211 0.300000 1306 1.43 + 1.400 0.400000 1738 1.67 + 1.609 0.500000 2170 2.00 + 1.710 0.550000 2386 2.22 + 1.803 0.600000 2603 2.50 + 1.932 0.650000 2821 2.86 + 2.105 0.700000 3039 3.33 + 2.305 0.750000 3257 4.00 + 2.425 0.775000 3364 4.44 + 2.537 0.800000 3471 5.00 + 2.701 0.825000 3579 5.71 + 2.867 0.850000 3688 6.67 + 3.233 0.875000 3796 8.00 + 3.629 0.887500 3850 8.89 + 4.535 0.900000 3906 10.00 + 6.003 0.912500 3960 11.43 + 9.783 0.925000 4013 13.33 + 16.799 0.937500 4067 16.00 + 19.599 0.943750 4094 17.78 + 22.703 0.950000 4122 20.00 + 25.279 0.956250 4149 22.86 + 29.279 0.962500 4176 26.67 + 32.959 0.968750 4203 32.00 + 35.199 0.971875 4216 35.56 + 36.959 0.975000 4230 40.00 + 39.103 0.978125 4244 45.71 + 41.055 0.981250 4257 53.33 + 42.719 0.984375 4271 64.00 + 43.711 0.985938 4277 71.11 + 44.639 0.987500 4284 80.00 + 45.215 0.989062 4291 91.43 + 45.887 0.990625 4298 106.67 + 46.559 0.992188 4305 128.00 + 47.135 0.992969 4308 142.22 + 47.487 0.993750 4311 160.00 + 48.223 0.994531 4315 182.86 + 49.183 0.995313 4319 213.33 + 49.695 0.996094 4322 256.00 + 50.687 0.996484 4323 284.44 + 51.391 0.996875 4325 320.00 + 53.119 0.997266 4327 365.71 + 53.663 0.997656 4328 426.67 + 53.823 0.998047 4330 512.00 + 53.887 0.998242 4331 568.89 + 54.015 0.998437 4332 640.00 + 55.199 0.998633 4334 731.43 + 55.199 0.998828 4334 853.33 + 55.199 0.999023 4334 1024.00 + 55.263 0.999121 4335 1137.78 + 55.263 0.999219 4335 1280.00 + 56.799 0.999316 4336 1462.86 + 56.799 0.999414 4336 1706.67 + 56.799 0.999512 4336 2048.00 + 57.663 0.999561 4337 2275.56 + 57.663 0.999609 4337 2560.00 + 57.663 0.999658 4337 2925.71 + 57.663 0.999707 4337 3413.33 + 57.663 0.999756 4337 4096.00 + 60.255 0.999780 4338 4551.11 + 60.255 1.000000 4338 inf +#[Mean = 3.879, StdDeviation = 8.304] +#[Max = 60.224, Total count = 4338] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + + +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 30 -t 1 -L -R 2100 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/get.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 427.846ms, rate sampling interval: 2807ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 44.48ms 90.10ms 353.54ms 84.98% + Req/Sec 178.14 436.36 1.25k 85.71% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.87ms + 75.000% 15.03ms + 90.000% 206.85ms + 99.000% 339.71ms + 99.900% 353.02ms + 99.990% 353.79ms + 99.999% 353.79ms +100.000% 353.79ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.285 0.000000 1 1.00 + 0.895 0.100000 352 1.11 + 1.121 0.200000 703 1.25 + 1.346 0.300000 1051 1.43 + 1.589 0.400000 1402 1.67 + 1.871 0.500000 1751 2.00 + 2.045 0.550000 1926 2.22 + 2.303 0.600000 2101 2.50 + 2.613 0.650000 2276 2.86 + 3.645 0.700000 2451 3.33 + 15.031 0.750000 2626 4.00 + 29.791 0.775000 2714 4.44 + 57.951 0.800000 2801 5.00 + 96.255 0.825000 2889 5.71 + 135.039 0.850000 2977 6.67 + 169.215 0.875000 3064 8.00 + 189.567 0.887500 3109 8.89 + 206.847 0.900000 3151 10.00 + 224.767 0.912500 3195 11.43 + 245.503 0.925000 3239 13.33 + 262.399 0.937500 3283 16.00 + 273.407 0.943750 3305 17.78 + 281.343 0.950000 3327 20.00 + 290.559 0.956250 3348 22.86 + 300.799 0.962500 3370 26.67 + 307.967 0.968750 3392 32.00 + 314.367 0.971875 3403 35.56 + 318.975 0.975000 3414 40.00 + 324.351 0.978125 3425 45.71 + 326.911 0.981250 3436 53.33 + 329.983 0.984375 3447 64.00 + 333.055 0.985938 3453 71.11 + 334.847 0.987500 3458 80.00 + 337.407 0.989062 3464 91.43 + 341.503 0.990625 3469 106.67 + 342.783 0.992188 3474 128.00 + 344.063 0.992969 3477 142.22 + 345.599 0.993750 3480 160.00 + 346.367 0.994531 3482 182.86 + 348.671 0.995313 3485 213.33 + 349.439 0.996094 3488 256.00 + 350.719 0.996484 3489 284.44 + 351.487 0.996875 3491 320.00 + 351.743 0.997266 3494 365.71 + 351.743 0.997656 3494 426.67 + 352.511 0.998047 3495 512.00 + 352.511 0.998242 3495 568.89 + 352.767 0.998437 3496 640.00 + 353.023 0.998633 3498 731.43 + 353.023 0.998828 3498 853.33 + 353.023 0.999023 3498 1024.00 + 353.023 0.999121 3498 1137.78 + 353.535 0.999219 3499 1280.00 + 353.535 0.999316 3499 1462.86 + 353.535 0.999414 3499 1706.67 + 353.791 0.999512 3501 2048.00 + 353.791 1.000000 3501 inf +#[Mean = 44.484, StdDeviation = 90.098] +#[Max = 353.536, Total count = 3501] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/wrk2_put.txt b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/wrk2_put.txt new file mode 100644 index 000000000..54e566a6b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage4/wrk2_put.txt @@ -0,0 +1,347 @@ +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 20 -t 1 -L -R 1000 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/put.lua http://localhost:8080 +Running 20s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 2.052ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.36ms 2.91ms 32.83ms 94.77% + Req/Sec 1.06k 317.45 4.33k 89.24% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.69ms + 75.000% 2.29ms + 90.000% 3.28ms + 99.000% 18.75ms + 99.900% 27.60ms + 99.990% 32.16ms + 99.999% 32.86ms +100.000% 32.86ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.264 0.000000 1 1.00 + 0.968 0.100000 971 1.11 + 1.160 0.200000 1936 1.25 + 1.337 0.300000 2908 1.43 + 1.507 0.400000 3877 1.67 + 1.686 0.500000 4845 2.00 + 1.775 0.550000 5325 2.22 + 1.876 0.600000 5812 2.50 + 1.994 0.650000 6292 2.86 + 2.129 0.700000 6780 3.33 + 2.289 0.750000 7264 4.00 + 2.375 0.775000 7507 4.44 + 2.477 0.800000 7748 5.00 + 2.595 0.825000 7988 5.71 + 2.763 0.850000 8230 6.67 + 2.955 0.875000 8471 8.00 + 3.077 0.887500 8591 8.89 + 3.281 0.900000 8714 10.00 + 3.541 0.912500 8833 11.43 + 3.889 0.925000 8954 13.33 + 4.491 0.937500 9077 16.00 + 4.975 0.943750 9136 17.78 + 5.535 0.950000 9197 20.00 + 6.483 0.956250 9257 22.86 + 7.955 0.962500 9317 26.67 + 9.639 0.968750 9378 32.00 + 10.703 0.971875 9408 35.56 + 11.719 0.975000 9438 40.00 + 12.639 0.978125 9469 45.71 + 14.023 0.981250 9499 53.33 + 15.287 0.984375 9529 64.00 + 16.095 0.985938 9544 71.11 + 17.071 0.987500 9559 80.00 + 18.255 0.989062 9575 91.43 + 19.167 0.990625 9590 106.67 + 19.919 0.992188 9605 128.00 + 20.399 0.992969 9613 142.22 + 21.071 0.993750 9620 160.00 + 21.919 0.994531 9628 182.86 + 22.255 0.995313 9635 213.33 + 22.975 0.996094 9643 256.00 + 23.167 0.996484 9646 284.44 + 23.583 0.996875 9650 320.00 + 24.063 0.997266 9654 365.71 + 24.591 0.997656 9658 426.67 + 25.263 0.998047 9662 512.00 + 25.535 0.998242 9663 568.89 + 25.871 0.998437 9665 640.00 + 26.895 0.998633 9668 731.43 + 27.471 0.998828 9669 853.33 + 27.871 0.999023 9671 1024.00 + 28.095 0.999121 9672 1137.78 + 28.799 0.999219 9673 1280.00 + 28.831 0.999316 9674 1462.86 + 28.975 0.999414 9675 1706.67 + 29.503 0.999512 9676 2048.00 + 29.503 0.999561 9676 2275.56 + 30.879 0.999609 9677 2560.00 + 30.879 0.999658 9677 2925.71 + 31.359 0.999707 9678 3413.33 + 31.359 0.999756 9678 4096.00 + 31.359 0.999780 9678 4551.11 + 32.159 0.999805 9679 5120.00 + 32.159 0.999829 9679 5851.43 + 32.159 0.999854 9679 6826.67 + 32.159 0.999878 9679 8192.00 + 32.159 0.999890 9679 9102.22 + 32.863 0.999902 9680 10240.00 + 32.863 1.000000 9680 inf +#[Mean = 2.357, StdDeviation = 2.912] +#[Max = 32.832, Total count = 9680] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + + +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 20 -t 1 -L -R 2000 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/put.lua http://localhost:8080 +Running 20s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 2.810ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.20ms 6.99ms 57.41ms 94.57% + Req/Sec 461.72 0.94k 9.44k 79.51% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.53ms + 75.000% 2.14ms + 90.000% 3.03ms + 99.000% 42.34ms + 99.900% 53.44ms + 99.990% 57.44ms + 99.999% 57.44ms +100.000% 57.44ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.342 0.000000 1 1.00 + 0.851 0.100000 415 1.11 + 1.037 0.200000 830 1.25 + 1.191 0.300000 1244 1.43 + 1.356 0.400000 1661 1.67 + 1.531 0.500000 2074 2.00 + 1.628 0.550000 2280 2.22 + 1.740 0.600000 2488 2.50 + 1.860 0.650000 2694 2.86 + 1.988 0.700000 2902 3.33 + 2.139 0.750000 3108 4.00 + 2.229 0.775000 3212 4.44 + 2.327 0.800000 3317 5.00 + 2.455 0.825000 3419 5.71 + 2.595 0.850000 3523 6.67 + 2.763 0.875000 3627 8.00 + 2.875 0.887500 3678 8.89 + 3.033 0.900000 3731 10.00 + 3.289 0.912500 3782 11.43 + 3.823 0.925000 3834 13.33 + 6.655 0.937500 3885 16.00 + 9.167 0.943750 3911 17.78 + 13.167 0.950000 3937 20.00 + 17.951 0.956250 3963 22.86 + 21.647 0.962500 3989 26.67 + 25.711 0.968750 4015 32.00 + 27.583 0.971875 4028 35.56 + 29.839 0.975000 4041 40.00 + 31.391 0.978125 4054 45.71 + 33.471 0.981250 4067 53.33 + 36.255 0.984375 4080 64.00 + 38.335 0.985938 4086 71.11 + 40.031 0.987500 4093 80.00 + 41.567 0.989062 4099 91.43 + 43.167 0.990625 4106 106.67 + 45.055 0.992188 4112 128.00 + 46.079 0.992969 4115 142.22 + 46.751 0.993750 4119 160.00 + 47.967 0.994531 4122 182.86 + 48.447 0.995313 4125 213.33 + 49.599 0.996094 4128 256.00 + 49.983 0.996484 4130 284.44 + 50.911 0.996875 4132 320.00 + 51.039 0.997266 4133 365.71 + 51.359 0.997656 4135 426.67 + 51.455 0.998047 4136 512.00 + 52.031 0.998242 4137 568.89 + 53.055 0.998437 4138 640.00 + 53.087 0.998633 4139 731.43 + 53.439 0.998828 4140 853.33 + 53.439 0.999023 4140 1024.00 + 55.103 0.999121 4141 1137.78 + 55.103 0.999219 4141 1280.00 + 55.327 0.999316 4142 1462.86 + 55.327 0.999414 4142 1706.67 + 55.327 0.999512 4142 2048.00 + 56.319 0.999561 4143 2275.56 + 56.319 0.999609 4143 2560.00 + 56.319 0.999658 4143 2925.71 + 56.319 0.999707 4143 3413.33 + 56.319 0.999756 4143 4096.00 + 57.439 0.999780 4144 4551.11 + 57.439 1.000000 4144 inf +#[Mean = 3.196, StdDeviation = 6.995] +#[Max = 57.408, Total count = 4144] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 20 -t 1 -L -R 2300 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/put.lua http://localhost:8080 +Running 20s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 3.310ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 8.29ms 14.47ms 65.12ms 85.34% + Req/Sec 123.28 630.90 9.55k 95.01% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.80ms + 75.000% 3.32ms + 90.000% 33.76ms + 99.000% 60.48ms + 99.900% 63.65ms + 99.990% 65.15ms + 99.999% 65.15ms +100.000% 65.15ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.378 0.000000 1 1.00 + 0.851 0.100000 110 1.11 + 1.121 0.200000 220 1.25 + 1.337 0.300000 330 1.43 + 1.544 0.400000 440 1.67 + 1.805 0.500000 550 2.00 + 1.915 0.550000 604 2.22 + 2.109 0.600000 659 2.50 + 2.313 0.650000 714 2.86 + 2.625 0.700000 769 3.33 + 3.317 0.750000 824 4.00 + 5.383 0.775000 851 4.44 + 10.831 0.800000 879 5.00 + 15.855 0.825000 906 5.71 + 22.383 0.850000 934 6.67 + 27.967 0.875000 961 8.00 + 30.623 0.887500 975 8.89 + 33.823 0.900000 989 10.00 + 37.183 0.912500 1002 11.43 + 38.623 0.925000 1016 13.33 + 41.151 0.937500 1030 16.00 + 42.271 0.943750 1037 17.78 + 43.903 0.950000 1044 20.00 + 45.439 0.956250 1050 22.86 + 47.231 0.962500 1057 26.67 + 49.151 0.968750 1064 32.00 + 51.295 0.971875 1068 35.56 + 53.343 0.975000 1071 40.00 + 54.175 0.978125 1074 45.71 + 55.583 0.981250 1078 53.33 + 57.951 0.984375 1081 64.00 + 58.847 0.985938 1083 71.11 + 60.319 0.987500 1085 80.00 + 60.351 0.989062 1086 91.43 + 60.895 0.990625 1088 106.67 + 61.183 0.992188 1090 128.00 + 61.343 0.992969 1091 142.22 + 61.759 0.993750 1092 160.00 + 61.759 0.994531 1092 182.86 + 62.047 0.995313 1093 213.33 + 62.079 0.996094 1094 256.00 + 62.111 0.996484 1095 284.44 + 62.111 0.996875 1095 320.00 + 62.111 0.997266 1095 365.71 + 63.039 0.997656 1096 426.67 + 63.039 0.998047 1096 512.00 + 63.647 0.998242 1097 568.89 + 63.647 0.998437 1097 640.00 + 63.647 0.998633 1097 731.43 + 63.647 0.998828 1097 853.33 + 63.647 0.999023 1097 1024.00 + 65.151 0.999121 1098 1137.78 + 65.151 1.000000 1098 inf +#[Mean = 8.289, StdDeviation = 14.467] +#[Max = 65.120, Total count = 1098] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + + +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 20 -t 1 -L -R 2500 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/put.lua http://localhost:8080 +Running 20s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 114.079ms, rate sampling interval: 1102ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.70s 443.71ms 2.19s 88.83% + Req/Sec 376.33 1.06k 3.39k 88.89% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.78s + 75.000% 1.98s + 90.000% 2.09s + 99.000% 2.17s + 99.900% 2.19s + 99.990% 2.19s + 99.999% 2.19s +100.000% 2.19s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.425 0.000000 1 1.00 + 1487.871 0.100000 376 1.11 + 1551.359 0.200000 747 1.25 + 1629.183 0.300000 1120 1.43 + 1707.007 0.400000 1495 1.67 + 1784.831 0.500000 1868 2.00 + 1821.695 0.550000 2056 2.22 + 1860.607 0.600000 2244 2.50 + 1898.495 0.650000 2428 2.86 + 1938.431 0.700000 2615 3.33 + 1976.319 0.750000 2807 4.00 + 1994.751 0.775000 2895 4.44 + 2015.231 0.800000 2993 5.00 + 2033.663 0.825000 3083 5.71 + 2053.119 0.850000 3179 6.67 + 2071.551 0.875000 3271 8.00 + 2080.767 0.887500 3315 8.89 + 2091.007 0.900000 3367 10.00 + 2101.247 0.912500 3411 11.43 + 2111.487 0.925000 3464 13.33 + 2119.679 0.937500 3510 16.00 + 2123.775 0.943750 3524 17.78 + 2129.919 0.950000 3552 20.00 + 2134.015 0.956250 3571 22.86 + 2142.207 0.962500 3598 26.67 + 2148.351 0.968750 3622 32.00 + 2150.399 0.971875 3631 35.56 + 2154.495 0.975000 3641 40.00 + 2156.543 0.978125 3654 45.71 + 2160.639 0.981250 3667 53.33 + 2162.687 0.984375 3675 64.00 + 2164.735 0.985938 3684 71.11 + 2166.783 0.987500 3689 80.00 + 2168.831 0.989062 3697 91.43 + 2170.879 0.990625 3702 106.67 + 2172.927 0.992188 3707 128.00 + 2172.927 0.992969 3707 142.22 + 2174.975 0.993750 3712 160.00 + 2177.023 0.994531 3716 182.86 + 2177.023 0.995313 3716 213.33 + 2179.071 0.996094 3719 256.00 + 2181.119 0.996484 3720 284.44 + 2183.167 0.996875 3725 320.00 + 2183.167 0.997266 3725 365.71 + 2183.167 0.997656 3725 426.67 + 2185.215 0.998047 3727 512.00 + 2185.215 0.998242 3727 568.89 + 2187.263 0.998437 3731 640.00 + 2187.263 0.998633 3731 731.43 + 2187.263 0.998828 3731 853.33 + 2187.263 0.999023 3731 1024.00 + 2187.263 0.999121 3731 1137.78 + 2187.263 0.999219 3731 1280.00 + 2187.263 0.999316 3731 1462.86 + 2187.263 0.999414 3731 1706.67 + 2189.311 0.999512 3733 2048.00 + 2189.311 1.000000 3733 inf +#[Mean = 1701.778, StdDeviation = 443.705] +#[Max = 2187.264, Total count = 3733] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + + + + diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-get-alloc.png b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-get-alloc.png new file mode 100644 index 000000000..78799bf51 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-get-alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-get-cpu.png b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-get-cpu.png new file mode 100644 index 000000000..4316bdc5f Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-get-cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-get-lock.png b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-get-lock.png new file mode 100644 index 000000000..e8c60f5c6 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-get-lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-put-alloc.png b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-put-alloc.png new file mode 100644 index 000000000..0b5d86723 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-put-alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-put-cpu.png b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-put-cpu.png new file mode 100644 index 000000000..6021bd3a9 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-put-cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-put-lock.png b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-put-lock.png new file mode 100644 index 000000000..6ea6526fd Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/asprof-put-lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-get-alloc.html b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-get-alloc.html new file mode 100644 index 000000000..c18da602b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-get-alloc.html @@ -0,0 +1,3464 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-get-cpu.html b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-get-cpu.html new file mode 100644 index 000000000..6d38fad19 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-get-cpu.html @@ -0,0 +1,2883 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-get-lock.html b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-get-lock.html new file mode 100644 index 000000000..bed89f9df --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-get-lock.html @@ -0,0 +1,872 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-put-alloc.html b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-put-alloc.html new file mode 100644 index 000000000..ba2a256b5 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-put-alloc.html @@ -0,0 +1,3437 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-put-cpu.html b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-put-cpu.html new file mode 100644 index 000000000..9dec540b4 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-put-cpu.html @@ -0,0 +1,3270 @@ +put-alloc.html + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-put-lock.html b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-put-lock.html new file mode 100644 index 000000000..36fdbf4c7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/html/asprof-put-lock.html @@ -0,0 +1,1017 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/wrk2_get.txt b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/wrk2_get.txt new file mode 100644 index 000000000..eefd88bcf --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/wrk2_get.txt @@ -0,0 +1,340 @@ +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 30 -t 1 -L -R 5000 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/get.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 1.556ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.55ms 0.95ms 12.30ms 85.64% + Req/Sec 5.27k 650.72 11.00k 83.20% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.35ms + 75.000% 1.88ms + 90.000% 2.43ms + 99.000% 5.65ms + 99.900% 8.60ms + 99.990% 10.94ms + 99.999% 12.27ms +100.000% 12.31ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.252 0.000000 1 1.00 + 0.704 0.100000 9869 1.11 + 0.881 0.200000 19710 1.25 + 1.038 0.300000 29571 1.43 + 1.187 0.400000 39361 1.67 + 1.346 0.500000 49256 2.00 + 1.430 0.550000 54148 2.22 + 1.525 0.600000 59086 2.50 + 1.631 0.650000 63960 2.86 + 1.749 0.700000 68908 3.33 + 1.878 0.750000 73833 4.00 + 1.943 0.775000 76294 4.44 + 2.014 0.800000 78726 5.00 + 2.091 0.825000 81208 5.71 + 2.179 0.850000 83669 6.67 + 2.289 0.875000 86143 8.00 + 2.351 0.887500 87352 8.89 + 2.429 0.900000 88570 10.00 + 2.521 0.912500 89790 11.43 + 2.633 0.925000 91023 13.33 + 2.791 0.937500 92251 16.00 + 2.889 0.943750 92875 17.78 + 3.019 0.950000 93493 20.00 + 3.189 0.956250 94099 22.86 + 3.415 0.962500 94712 26.67 + 3.715 0.968750 95327 32.00 + 3.907 0.971875 95634 35.56 + 4.111 0.975000 95942 40.00 + 4.347 0.978125 96248 45.71 + 4.595 0.981250 96558 53.33 + 4.915 0.984375 96863 64.00 + 5.091 0.985938 97019 71.11 + 5.299 0.987500 97170 80.00 + 5.503 0.989062 97327 91.43 + 5.767 0.990625 97479 106.67 + 6.091 0.992188 97635 128.00 + 6.267 0.992969 97709 142.22 + 6.431 0.993750 97785 160.00 + 6.599 0.994531 97864 182.86 + 6.807 0.995313 97939 213.33 + 7.023 0.996094 98016 256.00 + 7.207 0.996484 98055 284.44 + 7.355 0.996875 98093 320.00 + 7.531 0.997266 98131 365.71 + 7.719 0.997656 98170 426.67 + 7.943 0.998047 98208 512.00 + 8.099 0.998242 98228 568.89 + 8.207 0.998437 98247 640.00 + 8.295 0.998633 98266 731.43 + 8.479 0.998828 98285 853.33 + 8.607 0.999023 98304 1024.00 + 8.695 0.999121 98314 1137.78 + 8.831 0.999219 98324 1280.00 + 8.983 0.999316 98333 1462.86 + 9.127 0.999414 98343 1706.67 + 9.295 0.999512 98352 2048.00 + 9.399 0.999561 98357 2275.56 + 9.551 0.999609 98362 2560.00 + 9.919 0.999658 98367 2925.71 + 10.023 0.999707 98372 3413.33 + 10.223 0.999756 98376 4096.00 + 10.319 0.999780 98379 4551.11 + 10.343 0.999805 98381 5120.00 + 10.551 0.999829 98384 5851.43 + 10.639 0.999854 98386 6826.67 + 10.863 0.999878 98388 8192.00 + 10.943 0.999890 98390 9102.22 + 10.983 0.999902 98391 10240.00 + 10.999 0.999915 98392 11702.86 + 11.407 0.999927 98393 13653.33 + 11.423 0.999939 98394 16384.00 + 11.535 0.999945 98395 18204.44 + 11.623 0.999951 98396 20480.00 + 11.623 0.999957 98396 23405.71 + 11.935 0.999963 98397 27306.67 + 11.935 0.999969 98397 32768.00 + 12.095 0.999973 98398 36408.89 + 12.095 0.999976 98398 40960.00 + 12.095 0.999979 98398 46811.43 + 12.271 0.999982 98399 54613.33 + 12.271 0.999985 98399 65536.00 + 12.271 0.999986 98399 72817.78 + 12.271 0.999988 98399 81920.00 + 12.271 0.999989 98399 93622.86 + 12.311 0.999991 98400 109226.67 + 12.311 1.000000 98400 inf +#[Mean = 1.546, StdDeviation = 0.951] +#[Max = 12.304, Total count = 98400] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 30 -t 1 -L -R 8000 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/get.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 2.019ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.85ms 1.27ms 32.19ms 89.26% + Req/Sec 8.47k 1.09k 14.67k 80.08% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.60ms + 75.000% 2.23ms + 90.000% 3.01ms + 99.000% 6.43ms + 99.900% 16.41ms + 99.990% 23.26ms + 99.999% 27.97ms +100.000% 32.21ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.270 0.000000 1 1.00 + 0.823 0.100000 15781 1.11 + 1.052 0.200000 31516 1.25 + 1.254 0.300000 47294 1.43 + 1.428 0.400000 63026 1.67 + 1.601 0.500000 78723 2.00 + 1.695 0.550000 86618 2.22 + 1.801 0.600000 94546 2.50 + 1.920 0.650000 102365 2.86 + 2.063 0.700000 110309 3.33 + 2.229 0.750000 118165 4.00 + 2.321 0.775000 122037 4.44 + 2.423 0.800000 125960 5.00 + 2.541 0.825000 129922 5.71 + 2.675 0.850000 133886 6.67 + 2.823 0.875000 137786 8.00 + 2.911 0.887500 139741 8.89 + 3.011 0.900000 141739 10.00 + 3.117 0.912500 143675 11.43 + 3.233 0.925000 145657 13.33 + 3.363 0.937500 147612 16.00 + 3.441 0.943750 148606 17.78 + 3.529 0.950000 149594 20.00 + 3.637 0.956250 150567 22.86 + 3.803 0.962500 151547 26.67 + 4.055 0.968750 152526 32.00 + 4.227 0.971875 153025 35.56 + 4.419 0.975000 153514 40.00 + 4.667 0.978125 154006 45.71 + 4.967 0.981250 154496 53.33 + 5.379 0.984375 154988 64.00 + 5.615 0.985938 155232 71.11 + 5.899 0.987500 155478 80.00 + 6.219 0.989062 155725 91.43 + 6.611 0.990625 155970 106.67 + 7.075 0.992188 156217 128.00 + 7.359 0.992969 156340 142.22 + 7.647 0.993750 156462 160.00 + 8.095 0.994531 156586 182.86 + 8.503 0.995313 156708 213.33 + 9.215 0.996094 156833 256.00 + 9.623 0.996484 156893 284.44 + 10.191 0.996875 156954 320.00 + 10.855 0.997266 157016 365.71 + 11.711 0.997656 157077 426.67 + 12.759 0.998047 157139 512.00 + 13.527 0.998242 157170 568.89 + 13.943 0.998437 157200 640.00 + 14.711 0.998633 157231 731.43 + 15.487 0.998828 157263 853.33 + 16.575 0.999023 157294 1024.00 + 17.279 0.999121 157308 1137.78 + 17.951 0.999219 157323 1280.00 + 18.367 0.999316 157339 1462.86 + 18.751 0.999414 157354 1706.67 + 19.375 0.999512 157370 2048.00 + 19.551 0.999561 157377 2275.56 + 20.015 0.999609 157385 2560.00 + 20.463 0.999658 157393 2925.71 + 21.039 0.999707 157400 3413.33 + 21.279 0.999756 157408 4096.00 + 21.423 0.999780 157412 4551.11 + 21.519 0.999805 157416 5120.00 + 22.079 0.999829 157420 5851.43 + 22.223 0.999854 157423 6826.67 + 22.831 0.999878 157427 8192.00 + 23.167 0.999890 157429 9102.22 + 23.359 0.999902 157431 10240.00 + 23.887 0.999915 157433 11702.86 + 24.287 0.999927 157435 13653.33 + 24.463 0.999939 157437 16384.00 + 24.575 0.999945 157438 18204.44 + 24.591 0.999951 157439 20480.00 + 24.671 0.999957 157440 23405.71 + 25.615 0.999963 157441 27306.67 + 25.711 0.999969 157442 32768.00 + 25.711 0.999973 157442 36408.89 + 26.319 0.999976 157443 40960.00 + 26.319 0.999979 157443 46811.43 + 27.967 0.999982 157444 54613.33 + 27.967 0.999985 157444 65536.00 + 27.967 0.999986 157444 72817.78 + 28.015 0.999988 157445 81920.00 + 28.015 0.999989 157445 93622.86 + 28.015 0.999991 157445 109226.67 + 28.015 0.999992 157445 131072.00 + 28.015 0.999993 157445 145635.56 + 32.207 0.999994 157446 163840.00 + 32.207 1.000000 157446 inf +#[Mean = 1.854, StdDeviation = 1.271] +#[Max = 32.192, Total count = 157446] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 30 -t 1 -L -R 10000 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/get.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 71.319ms, rate sampling interval: 627ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.80ms 4.02ms 95.17ms 95.85% + Req/Sec 10.02k 97.62 10.51k 93.55% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.97ms + 75.000% 2.77ms + 90.000% 4.17ms + 99.000% 23.98ms + 99.900% 51.87ms + 99.990% 85.25ms + 99.999% 93.18ms +100.000% 95.23ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.307 0.000000 1 1.00 + 1.075 0.100000 19781 1.11 + 1.337 0.200000 39487 1.25 + 1.555 0.300000 59144 1.43 + 1.757 0.400000 78900 1.67 + 1.972 0.500000 98625 2.00 + 2.089 0.550000 108531 2.22 + 2.217 0.600000 118312 2.50 + 2.365 0.650000 128137 2.86 + 2.543 0.700000 138028 3.33 + 2.769 0.750000 147839 4.00 + 2.909 0.775000 152816 4.44 + 3.073 0.800000 157703 5.00 + 3.263 0.825000 162648 5.71 + 3.481 0.850000 167574 6.67 + 3.755 0.875000 172476 8.00 + 3.939 0.887500 174949 8.89 + 4.175 0.900000 177423 10.00 + 4.467 0.912500 179889 11.43 + 4.843 0.925000 182344 13.33 + 5.383 0.937500 184800 16.00 + 5.735 0.943750 186032 17.78 + 6.127 0.950000 187257 20.00 + 6.603 0.956250 188492 22.86 + 7.307 0.962500 189722 26.67 + 8.311 0.968750 190954 32.00 + 9.039 0.971875 191575 35.56 + 10.039 0.975000 192188 40.00 + 11.575 0.978125 192801 45.71 + 13.871 0.981250 193416 53.33 + 17.263 0.984375 194033 64.00 + 19.167 0.985938 194340 71.11 + 21.119 0.987500 194647 80.00 + 22.911 0.989062 194956 91.43 + 24.639 0.990625 195267 106.67 + 26.239 0.992188 195571 128.00 + 27.119 0.992969 195726 142.22 + 27.951 0.993750 195879 160.00 + 28.847 0.994531 196034 182.86 + 29.855 0.995313 196189 213.33 + 31.071 0.996094 196343 256.00 + 31.839 0.996484 196418 284.44 + 33.151 0.996875 196497 320.00 + 34.111 0.997266 196572 365.71 + 35.487 0.997656 196649 426.67 + 37.791 0.998047 196726 512.00 + 39.423 0.998242 196764 568.89 + 42.591 0.998437 196804 640.00 + 45.695 0.998633 196841 731.43 + 48.319 0.998828 196881 853.33 + 52.287 0.999023 196918 1024.00 + 53.759 0.999121 196937 1137.78 + 56.063 0.999219 196957 1280.00 + 58.495 0.999316 196978 1462.86 + 59.327 0.999414 196995 1706.67 + 61.823 0.999512 197014 2048.00 + 62.719 0.999561 197024 2275.56 + 64.351 0.999609 197034 2560.00 + 67.775 0.999658 197043 2925.71 + 71.359 0.999707 197053 3413.33 + 75.071 0.999756 197062 4096.00 + 76.991 0.999780 197067 4551.11 + 79.487 0.999805 197072 5120.00 + 81.663 0.999829 197077 5851.43 + 83.263 0.999854 197082 6826.67 + 84.415 0.999878 197086 8192.00 + 85.183 0.999890 197089 9102.22 + 85.375 0.999902 197091 10240.00 + 86.015 0.999915 197094 11702.86 + 87.359 0.999927 197096 13653.33 + 88.447 0.999939 197098 16384.00 + 88.895 0.999945 197100 18204.44 + 89.279 0.999951 197101 20480.00 + 89.791 0.999957 197102 23405.71 + 90.303 0.999963 197103 27306.67 + 91.135 0.999969 197104 32768.00 + 91.583 0.999973 197105 36408.89 + 92.799 0.999976 197106 40960.00 + 92.799 0.999979 197106 46811.43 + 92.991 0.999982 197107 54613.33 + 92.991 0.999985 197107 65536.00 + 93.183 0.999986 197108 72817.78 + 93.183 0.999988 197108 81920.00 + 93.183 0.999989 197108 93622.86 + 93.759 0.999991 197109 109226.67 + 93.759 0.999992 197109 131072.00 + 93.759 0.999993 197109 145635.56 + 93.759 0.999994 197109 163840.00 + 93.759 0.999995 197109 187245.71 + 95.231 0.999995 197110 218453.33 + 95.231 1.000000 197110 inf +#[Mean = 2.796, StdDeviation = 4.016] +#[Max = 95.168, Total count = 197110] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- diff --git a/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/wrk2_put.txt b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/wrk2_put.txt new file mode 100644 index 000000000..c0dc1d578 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/alenkovayulya/results_stage5/wrk2_put.txt @@ -0,0 +1,435 @@ +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 20 -t 1 -L -R 5000 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/put.lua http://localhost:8080 +Running 20s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 1.803ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.66ms 1.28ms 19.98ms 93.92% + Req/Sec 5.26k 848.73 13.55k 88.40% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.40ms + 75.000% 1.96ms + 90.000% 2.53ms + 99.000% 7.10ms + 99.900% 15.59ms + 99.990% 19.04ms + 99.999% 20.00ms +100.000% 20.00ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.290 0.000000 1 1.00 + 0.738 0.100000 4840 1.11 + 0.921 0.200000 9696 1.25 + 1.080 0.300000 14524 1.43 + 1.233 0.400000 19364 1.67 + 1.400 0.500000 24220 2.00 + 1.487 0.550000 26628 2.22 + 1.588 0.600000 29041 2.50 + 1.699 0.650000 31468 2.86 + 1.824 0.700000 33894 3.33 + 1.956 0.750000 36306 4.00 + 2.029 0.775000 37514 4.44 + 2.105 0.800000 38743 5.00 + 2.185 0.825000 39961 5.71 + 2.275 0.850000 41140 6.67 + 2.389 0.875000 42361 8.00 + 2.457 0.887500 42957 8.89 + 2.533 0.900000 43571 10.00 + 2.629 0.912500 44171 11.43 + 2.741 0.925000 44771 13.33 + 2.903 0.937500 45374 16.00 + 3.005 0.943750 45681 17.78 + 3.151 0.950000 45984 20.00 + 3.355 0.956250 46282 22.86 + 3.627 0.962500 46584 26.67 + 4.029 0.968750 46886 32.00 + 4.331 0.971875 47037 35.56 + 4.623 0.975000 47189 40.00 + 4.935 0.978125 47340 45.71 + 5.379 0.981250 47492 53.33 + 5.807 0.984375 47642 64.00 + 6.107 0.985938 47718 71.11 + 6.407 0.987500 47795 80.00 + 6.811 0.989062 47869 91.43 + 7.295 0.990625 47946 106.67 + 7.955 0.992188 48020 128.00 + 8.367 0.992969 48059 142.22 + 8.839 0.993750 48096 160.00 + 9.399 0.994531 48136 182.86 + 9.975 0.995313 48172 213.33 + 10.655 0.996094 48209 256.00 + 11.271 0.996484 48228 284.44 + 11.863 0.996875 48247 320.00 + 12.471 0.997266 48266 365.71 + 13.135 0.997656 48286 426.67 + 13.807 0.998047 48304 512.00 + 14.143 0.998242 48313 568.89 + 14.415 0.998437 48323 640.00 + 14.687 0.998633 48332 731.43 + 15.335 0.998828 48342 853.33 + 15.631 0.999023 48351 1024.00 + 15.943 0.999121 48356 1137.78 + 16.127 0.999219 48361 1280.00 + 16.311 0.999316 48365 1462.86 + 16.687 0.999414 48370 1706.67 + 16.847 0.999512 48375 2048.00 + 16.895 0.999561 48378 2275.56 + 17.055 0.999609 48380 2560.00 + 17.167 0.999658 48382 2925.71 + 17.231 0.999707 48384 3413.33 + 17.551 0.999756 48387 4096.00 + 17.583 0.999780 48388 4551.11 + 17.951 0.999805 48389 5120.00 + 18.271 0.999829 48390 5851.43 + 18.367 0.999854 48391 6826.67 + 19.039 0.999878 48393 8192.00 + 19.039 0.999890 48393 9102.22 + 19.327 0.999902 48394 10240.00 + 19.327 0.999915 48394 11702.86 + 19.343 0.999927 48395 13653.33 + 19.503 0.999939 48396 16384.00 + 19.503 0.999945 48396 18204.44 + 19.503 0.999951 48396 20480.00 + 19.503 0.999957 48396 23405.71 + 19.871 0.999963 48397 27306.67 + 19.871 0.999969 48397 32768.00 + 19.871 0.999973 48397 36408.89 + 19.871 0.999976 48397 40960.00 + 19.871 0.999979 48397 46811.43 + 19.999 0.999982 48398 54613.33 + 19.999 1.000000 48398 inf +#[Mean = 1.658, StdDeviation = 1.284] +#[Max = 19.984, Total count = 48398] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 20 -t 1 -L -R 10000 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/put.lua http://localhost:8080 +Running 20s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 2.858ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.97ms 3.11ms 29.87ms 90.63% + Req/Sec 10.55k 1.72k 15.50k 81.15% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.99ms + 75.000% 2.82ms + 90.000% 5.69ms + 99.000% 16.88ms + 99.900% 22.32ms + 99.990% 25.89ms + 99.999% 29.50ms +100.000% 29.89ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.394 0.000000 1 1.00 + 1.112 0.100000 9669 1.11 + 1.373 0.200000 19342 1.25 + 1.585 0.300000 29055 1.43 + 1.782 0.400000 38697 1.67 + 1.993 0.500000 48344 2.00 + 2.113 0.550000 53174 2.22 + 2.247 0.600000 58033 2.50 + 2.403 0.650000 62881 2.86 + 2.581 0.700000 67708 3.33 + 2.823 0.750000 72493 4.00 + 2.993 0.775000 74925 4.44 + 3.207 0.800000 77327 5.00 + 3.477 0.825000 79750 5.71 + 3.859 0.850000 82163 6.67 + 4.523 0.875000 84578 8.00 + 5.047 0.887500 85779 8.89 + 5.691 0.900000 86993 10.00 + 6.475 0.912500 88196 11.43 + 7.443 0.925000 89407 13.33 + 8.783 0.937500 90612 16.00 + 9.519 0.943750 91219 17.78 + 10.287 0.950000 91824 20.00 + 11.119 0.956250 92430 22.86 + 11.943 0.962500 93028 26.67 + 12.807 0.968750 93636 32.00 + 13.271 0.971875 93935 35.56 + 13.743 0.975000 94237 40.00 + 14.239 0.978125 94541 45.71 + 14.775 0.981250 94840 53.33 + 15.415 0.984375 95144 64.00 + 15.767 0.985938 95295 71.11 + 16.135 0.987500 95444 80.00 + 16.623 0.989062 95600 91.43 + 17.103 0.990625 95749 106.67 + 17.599 0.992188 95900 128.00 + 17.919 0.992969 95976 142.22 + 18.255 0.993750 96050 160.00 + 18.591 0.994531 96126 182.86 + 19.055 0.995313 96200 213.33 + 19.359 0.996094 96276 256.00 + 19.615 0.996484 96314 284.44 + 19.855 0.996875 96350 320.00 + 20.159 0.997266 96389 365.71 + 20.543 0.997656 96426 426.67 + 20.847 0.998047 96467 512.00 + 20.991 0.998242 96483 568.89 + 21.231 0.998437 96503 640.00 + 21.487 0.998633 96520 731.43 + 21.903 0.998828 96541 853.33 + 22.415 0.999023 96559 1024.00 + 22.575 0.999121 96568 1137.78 + 22.799 0.999219 96577 1280.00 + 23.071 0.999316 96586 1462.86 + 23.263 0.999414 96596 1706.67 + 23.551 0.999512 96605 2048.00 + 23.823 0.999561 96611 2275.56 + 23.983 0.999609 96616 2560.00 + 24.319 0.999658 96619 2925.71 + 24.511 0.999707 96624 3413.33 + 24.655 0.999756 96629 4096.00 + 24.687 0.999780 96632 4551.11 + 24.751 0.999805 96634 5120.00 + 24.895 0.999829 96636 5851.43 + 25.263 0.999854 96638 6826.67 + 25.535 0.999878 96641 8192.00 + 25.887 0.999890 96642 9102.22 + 25.935 0.999902 96643 10240.00 + 25.983 0.999915 96644 11702.86 + 26.991 0.999927 96645 13653.33 + 27.615 0.999939 96647 16384.00 + 27.615 0.999945 96647 18204.44 + 28.159 0.999951 96648 20480.00 + 28.159 0.999957 96648 23405.71 + 28.559 0.999963 96649 27306.67 + 29.263 0.999969 96650 32768.00 + 29.263 0.999973 96650 36408.89 + 29.263 0.999976 96650 40960.00 + 29.263 0.999979 96650 46811.43 + 29.503 0.999982 96651 54613.33 + 29.503 0.999985 96651 65536.00 + 29.503 0.999986 96651 72817.78 + 29.503 0.999988 96651 81920.00 + 29.503 0.999989 96651 93622.86 + 29.887 0.999991 96652 109226.67 + 29.887 1.000000 96652 inf +#[Mean = 2.965, StdDeviation = 3.112] +#[Max = 29.872, Total count = 96652] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 20 -t 1 -L -R 12000 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/put.lua http://localhost:8080 +Running 20s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 8.907ms, rate sampling interval: 42ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 7.92ms 6.93ms 50.08ms 83.18% + Req/Sec 11.81k 1.08k 15.52k 80.87% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 5.24ms + 75.000% 11.53ms + 90.000% 17.60ms + 99.000% 31.63ms + 99.900% 42.66ms + 99.990% 47.30ms + 99.999% 49.82ms +100.000% 50.11ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.411 0.000000 1 1.00 + 1.915 0.100000 11296 1.11 + 2.481 0.200000 22637 1.25 + 3.097 0.300000 33891 1.43 + 3.953 0.400000 45185 1.67 + 5.235 0.500000 56489 2.00 + 6.039 0.550000 62139 2.22 + 7.027 0.600000 67782 2.50 + 8.231 0.650000 73459 2.86 + 9.767 0.700000 79072 3.33 + 11.527 0.750000 84718 4.00 + 12.487 0.775000 87555 4.44 + 13.407 0.800000 90385 5.00 + 14.295 0.825000 93194 5.71 + 15.239 0.850000 96021 6.67 + 16.343 0.875000 98854 8.00 + 16.911 0.887500 100253 8.89 + 17.599 0.900000 101661 10.00 + 18.351 0.912500 103085 11.43 + 19.215 0.925000 104488 13.33 + 20.335 0.937500 105905 16.00 + 20.975 0.943750 106613 17.78 + 21.695 0.950000 107311 20.00 + 22.511 0.956250 108015 22.86 + 23.375 0.962500 108721 26.67 + 24.495 0.968750 109431 32.00 + 25.103 0.971875 109781 35.56 + 25.807 0.975000 110136 40.00 + 26.703 0.978125 110493 45.71 + 27.647 0.981250 110839 53.33 + 28.847 0.984375 111192 64.00 + 29.519 0.985938 111367 71.11 + 30.287 0.987500 111545 80.00 + 31.039 0.989062 111724 91.43 + 32.111 0.990625 111901 106.67 + 33.151 0.992188 112078 128.00 + 33.727 0.992969 112162 142.22 + 34.399 0.993750 112250 160.00 + 35.103 0.994531 112339 182.86 + 35.775 0.995313 112426 213.33 + 36.927 0.996094 112515 256.00 + 37.471 0.996484 112560 284.44 + 38.079 0.996875 112604 320.00 + 38.751 0.997266 112648 365.71 + 39.423 0.997656 112692 426.67 + 40.159 0.998047 112735 512.00 + 40.639 0.998242 112757 568.89 + 41.279 0.998437 112779 640.00 + 41.791 0.998633 112801 731.43 + 42.207 0.998828 112824 853.33 + 42.687 0.999023 112846 1024.00 + 42.975 0.999121 112856 1137.78 + 43.231 0.999219 112867 1280.00 + 43.615 0.999316 112881 1462.86 + 43.839 0.999414 112890 1706.67 + 44.191 0.999512 112900 2048.00 + 44.511 0.999561 112906 2275.56 + 44.703 0.999609 112911 2560.00 + 45.055 0.999658 112918 2925.71 + 45.151 0.999707 112922 3413.33 + 45.535 0.999756 112928 4096.00 + 45.727 0.999780 112931 4551.11 + 45.823 0.999805 112933 5120.00 + 46.207 0.999829 112936 5851.43 + 46.623 0.999854 112939 6826.67 + 46.911 0.999878 112942 8192.00 + 47.263 0.999890 112943 9102.22 + 47.295 0.999902 112944 10240.00 + 48.031 0.999915 112946 11702.86 + 48.255 0.999927 112947 13653.33 + 48.671 0.999939 112949 16384.00 + 48.671 0.999945 112949 18204.44 + 48.735 0.999951 112950 20480.00 + 48.863 0.999957 112951 23405.71 + 48.863 0.999963 112951 27306.67 + 49.503 0.999969 112952 32768.00 + 49.503 0.999973 112952 36408.89 + 49.791 0.999976 112953 40960.00 + 49.791 0.999979 112953 46811.43 + 49.791 0.999982 112953 54613.33 + 49.823 0.999985 112954 65536.00 + 49.823 0.999986 112954 72817.78 + 49.823 0.999988 112954 81920.00 + 49.823 0.999989 112954 93622.86 + 49.823 0.999991 112954 109226.67 + 50.111 0.999992 112955 131072.00 + 50.111 1.000000 112955 inf +#[Mean = 7.924, StdDeviation = 6.933] +#[Max = 50.080, Total count = 112955] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + +MacBook-Pro-Ulia:wrk2-arm yulalenk$ ./wrk -c 64 -d 20 -t 1 -L -R 15000 -s /Users/yulalenk/sem2/2024-highload-dht/src/main/java/ru/vk/itmo/test/alenkovayulya/lua/put.lua http://localhost:8080 +Running 20s test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 942.439ms, rate sampling interval: 3555ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.13s 675.89ms 4.48s 57.75% + Req/Sec 11.44k 352.00 11.79k 50.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 3.10s + 75.000% 3.75s + 90.000% 4.11s + 99.000% 4.35s + 99.900% 4.42s + 99.990% 4.46s + 99.999% 4.49s +100.000% 4.49s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 1904.639 0.000000 1 1.00 + 2265.087 0.100000 11114 1.11 + 2463.743 0.200000 22148 1.25 + 2627.583 0.300000 33203 1.43 + 2836.479 0.400000 44306 1.67 + 3096.575 0.500000 55383 2.00 + 3186.687 0.550000 60917 2.22 + 3278.847 0.600000 66369 2.50 + 3397.631 0.650000 71938 2.86 + 3614.719 0.700000 77486 3.33 + 3749.887 0.750000 82967 4.00 + 3811.327 0.775000 85763 4.44 + 3872.767 0.800000 88562 5.00 + 3936.255 0.825000 91306 5.71 + 3991.551 0.850000 94028 6.67 + 4052.991 0.875000 96858 8.00 + 4083.711 0.887500 98171 8.89 + 4114.431 0.900000 99571 10.00 + 4145.151 0.912500 100959 11.43 + 4173.823 0.925000 102325 13.33 + 4202.495 0.937500 103775 16.00 + 4214.783 0.943750 104445 17.78 + 4231.167 0.950000 105294 20.00 + 4243.455 0.956250 105972 22.86 + 4255.743 0.962500 106499 26.67 + 4272.127 0.968750 107229 32.00 + 4280.319 0.971875 107572 35.56 + 4288.511 0.975000 107872 40.00 + 4300.799 0.978125 108303 45.71 + 4308.991 0.981250 108603 53.33 + 4321.279 0.984375 108997 64.00 + 4325.375 0.985938 109137 71.11 + 4333.567 0.987500 109328 80.00 + 4341.759 0.989062 109502 91.43 + 4345.855 0.990625 109595 106.67 + 4354.047 0.992188 109772 128.00 + 4358.143 0.992969 109845 142.22 + 4362.239 0.993750 109921 160.00 + 4370.431 0.994531 110077 182.86 + 4374.527 0.995313 110141 213.33 + 4378.623 0.996094 110196 256.00 + 4382.719 0.996484 110244 284.44 + 4386.815 0.996875 110281 320.00 + 4390.911 0.997266 110327 365.71 + 4395.007 0.997656 110358 426.67 + 4403.199 0.998047 110423 512.00 + 4403.199 0.998242 110423 568.89 + 4407.295 0.998437 110453 640.00 + 4411.391 0.998633 110478 731.43 + 4415.487 0.998828 110499 853.33 + 4419.583 0.999023 110516 1024.00 + 4419.583 0.999121 110516 1137.78 + 4423.679 0.999219 110529 1280.00 + 4427.775 0.999316 110540 1462.86 + 4435.967 0.999414 110560 1706.67 + 4435.967 0.999512 110560 2048.00 + 4440.063 0.999561 110569 2275.56 + 4440.063 0.999609 110569 2560.00 + 4444.159 0.999658 110582 2925.71 + 4444.159 0.999707 110582 3413.33 + 4448.255 0.999756 110589 4096.00 + 4448.255 0.999780 110589 4551.11 + 4452.351 0.999805 110591 5120.00 + 4456.447 0.999829 110595 5851.43 + 4456.447 0.999854 110595 6826.67 + 4464.639 0.999878 110601 8192.00 + 4464.639 0.999890 110601 9102.22 + 4464.639 0.999902 110601 10240.00 + 4468.735 0.999915 110603 11702.86 + 4468.735 0.999927 110603 13653.33 + 4472.831 0.999939 110605 16384.00 + 4472.831 0.999945 110605 18204.44 + 4476.927 0.999951 110608 20480.00 + 4476.927 0.999957 110608 23405.71 + 4476.927 0.999963 110608 27306.67 + 4476.927 0.999969 110608 32768.00 + 4476.927 0.999973 110608 36408.89 + 4481.023 0.999976 110609 40960.00 + 4481.023 0.999979 110609 46811.43 + 4481.023 0.999982 110609 54613.33 + 4485.119 0.999985 110611 65536.00 + 4485.119 1.000000 110611 inf +#[Mean = 3133.208, StdDeviation = 675.893] +#[Max = 4481.024, Total count = 110611] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/AsyncActions.java b/src/main/java/ru/vk/itmo/test/andreycheshev/AsyncActions.java new file mode 100644 index 000000000..d657123ca --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/AsyncActions.java @@ -0,0 +1,203 @@ +package ru.vk.itmo.test.andreycheshev; + +import one.nio.http.HttpSession; +import one.nio.http.Request; +import one.nio.http.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class AsyncActions { + private static final Logger LOGGER = LoggerFactory.getLogger(AsyncActions.class); + + private static final int CPU_THREADS_COUNT = Runtime.getRuntime().availableProcessors(); + + private final Executor internalExecutor = Executors.newFixedThreadPool( + CPU_THREADS_COUNT / 2, + new WorkerThreadFactory("Internal-thread") + ); + private final Executor senderExecutor = Executors.newFixedThreadPool( + CPU_THREADS_COUNT / 2, + new WorkerThreadFactory("Sender-thread") + ); + private final Executor localCallExecutor = Executors.newFixedThreadPool( + CPU_THREADS_COUNT / 2, + new WorkerThreadFactory("LocalCall-thread") + ); + private final Executor remoteCallExecutor = Executors.newFixedThreadPool( + CPU_THREADS_COUNT, + new WorkerThreadFactory("RemoteCall-thread") + ); + + private final HttpClient httpClient = HttpClient.newBuilder() + .executor(remoteCallExecutor) + .connectTimeout(Duration.ofMillis(500)) + .version(HttpClient.Version.HTTP_1_1) + .build(); + + private final HttpProvider httpProvider; + + public AsyncActions(HttpProvider httpProvider) { + this.httpProvider = httpProvider; + } + + public CompletableFuture processLocallyToSend( + int method, + String id, + Request request, + long timestamp, + HttpSession session) { + + return getLocalFuture(method, id, request, timestamp) + .thenAcceptAsync( + elements -> HttpUtils.sendResponse( + HttpUtils.getOneNioResponse(method, elements), + session + ), + senderExecutor + ) + .exceptionallyAsync( + exception -> { + if (exception.getCause() instanceof SendingResponseException) { + LOGGER.error("Error when sending a response", exception); + } + return null; + }, + internalExecutor + ); + } + + public CompletableFuture processLocallyToCollect( + int method, + String id, + Request request, + long timestamp, + ResponseCollector collector, + HttpSession session) { + + CompletableFuture future = getLocalFuture(method, id, request, timestamp) + .exceptionallyAsync( + exception -> { + LOGGER.error("Internal error of the DAO operation", exception); + return null; + }, + internalExecutor + ) + .thenApplyAsync( + responseElements -> { + if (responseElements != null) { + collector.add(responseElements); + } + return collector.incrementResponsesCounter(); + }, + internalExecutor + ) + .thenAcceptAsync( + condition -> { + if (condition) { + HttpUtils.sendResponse(collector.getResponse(), session); + } + }, + senderExecutor + ); + + return withSendingErrorProcessing(future); + } + + public CompletableFuture processRemotelyToCollect( + String node, + Request request, + long timestamp, + ResponseCollector collector, + HttpSession session) { + + HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(node + request.getURI())) + .method( + request.getMethodName(), + request.getBody() == null + ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofByteArray(request.getBody()) + ) + .header(HttpUtils.TIMESTAMP_JAVA_NET_HEADER, String.valueOf(timestamp)) + .timeout(Duration.ofMillis(500)) + .build(); + + CompletableFuture future = httpClient + .sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()) + .exceptionallyAsync( + exception -> { + LOGGER.error("Error when sending a request to the remote node", exception); + return null; + }, + internalExecutor + ) + .thenApplyAsync( + response -> { // java.net response. + if (response != null) { + collector.add( + HttpUtils.getElementsFromJavaNetResponse(response) + ); + } + return collector.incrementResponsesCounter(); + }, + internalExecutor + ) + .thenAcceptAsync( + condition -> { + if (condition) { + HttpUtils.sendResponse(collector.getResponse(), session); + } + }, + senderExecutor + ); + + return withSendingErrorProcessing(future); + } + + private CompletableFuture getLocalFuture( + int method, + String id, + Request request, + long timestamp) { + + return CompletableFuture.supplyAsync( + () -> switch (method) { + case Request.METHOD_GET -> httpProvider.get(id); + case Request.METHOD_PUT -> httpProvider.put(id, request.getBody(), timestamp); + case Request.METHOD_DELETE -> httpProvider.delete(id, timestamp); + default -> throw new IllegalArgumentException("Unsupported method"); + }, + localCallExecutor + ); + } + + public CompletableFuture sendAsync(Response response, HttpSession session) { + return withSendingErrorProcessing( + CompletableFuture.runAsync( + () -> HttpUtils.sendResponse(response, session), + senderExecutor + ) + ); + } + + private CompletableFuture withSendingErrorProcessing( + CompletableFuture future) { + + return future + .exceptionallyAsync( + exception -> { + LOGGER.error("Error when sending a response", exception); + return null; + }, + internalExecutor + ); + } +} diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/ByteArrayWrapper.java b/src/main/java/ru/vk/itmo/test/andreycheshev/ByteArrayWrapper.java new file mode 100644 index 000000000..4a09769dd --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/ByteArrayWrapper.java @@ -0,0 +1,17 @@ +package ru.vk.itmo.test.andreycheshev; + +import java.nio.ByteBuffer; + +public class ByteArrayWrapper { + private final ByteBuffer buffer; + + public ByteArrayWrapper(byte[] data) { + this.buffer = ByteBuffer.wrap(data).asReadOnlyBuffer(); + } + + public byte[] get() { + byte[] copy = new byte[buffer.remaining()]; + buffer.slice().get(copy); + return copy; + } +} diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/HttpProvider.java b/src/main/java/ru/vk/itmo/test/andreycheshev/HttpProvider.java new file mode 100644 index 000000000..b070135cb --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/HttpProvider.java @@ -0,0 +1,9 @@ +package ru.vk.itmo.test.andreycheshev; + +public interface HttpProvider { + ResponseElements get(String id); + + ResponseElements put(String id, byte[] body, long timestamp); + + ResponseElements delete(String id, long timestamp); +} diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/HttpUtils.java b/src/main/java/ru/vk/itmo/test/andreycheshev/HttpUtils.java new file mode 100644 index 000000000..fab54b214 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/HttpUtils.java @@ -0,0 +1,101 @@ +package ru.vk.itmo.test.andreycheshev; + +import one.nio.http.HttpSession; +import one.nio.http.Request; +import one.nio.http.Response; + +import java.io.IOException; +import java.net.http.HttpResponse; +import java.util.Map; +import java.util.Optional; + +public class HttpUtils { + public static final int OK_CODE = 200; + public static final int CREATED_CODE = 201; + public static final int ACCEPT_CODE = 202; + public static final int NOT_FOUND_CODE = 404; + public static final int GONE_CODE = 410; + + public static final Map AVAILABLE_RESPONSES = Map.of( + OK_CODE, Response.OK, + CREATED_CODE, Response.CREATED, + ACCEPT_CODE, Response.ACCEPTED, + NOT_FOUND_CODE, Response.NOT_FOUND, + GONE_CODE, Response.GONE + ); + + public static final String TIMESTAMP_JAVA_NET_HEADER = "X-Timestamp"; + public static final String TIMESTAMP_ONE_NIO_HEADER = TIMESTAMP_JAVA_NET_HEADER + ": "; + + private static final String NOT_ENOUGH_REPLICAS_STATUS = "504 Not Enough Replicas"; + private static final String TOO_MANY_REQUESTS_STATUS = "429 Too many requests"; + + public static final long EMPTY_TIMESTAMP = -1; + + private HttpUtils() { + + } + + public static Response getBadRequest() { + return new Response(Response.BAD_REQUEST, Response.EMPTY); + } + + public static Response getMethodNotAllowed() { + return new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); + } + + public static Response getInternalError() { + return new Response(Response.INTERNAL_ERROR, Response.EMPTY); + } + + public static Response getNotEnoughReplicas() { + return new Response(NOT_ENOUGH_REPLICAS_STATUS, Response.EMPTY); + } + + public static Response getTooManyRequests() { + return new Response(TOO_MANY_REQUESTS_STATUS, Response.EMPTY); + } + + public static void sendResponse(Response response, HttpSession session) { + try { + session.sendResponse(response); + } catch (IOException e) { + throw new SendingResponseException(e); + } + } + + public static ResponseElements getElementsFromJavaNetResponse(HttpResponse response) { + Optional optTimestamp = response.headers().firstValue(TIMESTAMP_JAVA_NET_HEADER); + + long responseTimestamp = optTimestamp.map(Long::parseLong).orElse(EMPTY_TIMESTAMP); + + return new ResponseElements( + response.statusCode(), + response.body(), + responseTimestamp + ); + } + + public static Response getOneNioResponse(int method, ResponseElements elements) { + switch (method) { + case Request.METHOD_GET -> { + int status = elements.getStatus(); + + Response response = status == GONE_CODE + ? new Response(Response.NOT_FOUND, Response.EMPTY) + : new Response(AVAILABLE_RESPONSES.get(status), elements.getBody()); + + response.addHeader(TIMESTAMP_ONE_NIO_HEADER + elements.getTimestamp()); + + return response; + } + case Request.METHOD_PUT -> { + return new Response(Response.CREATED, Response.EMPTY); + } + case Request.METHOD_DELETE -> { + return new Response(Response.ACCEPTED, Response.EMPTY); + } + default -> throw new IllegalArgumentException("Unsupported request method"); + } + } +} 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 a53f033d3..c47e1fbd7 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/ParametersParser.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/ParametersParser.java @@ -1,41 +1,38 @@ package ru.vk.itmo.test.andreycheshev; -public class ParametersParser { - private final RendezvousDistributor distributor; - private int ack; - private int from; +import one.nio.http.Request; + +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 ParametersParser() { - public ParametersParser(RendezvousDistributor distributor) { - this.distributor = distributor; } - public void parseAckFrom(String ackParameter, String fromParameter) throws IllegalArgumentException { - if (ackParameter == null || fromParameter == null) { - setDefault(); - } else { - try { - ack = Integer.parseInt(ackParameter); - from = Integer.parseInt(fromParameter); - } catch (Exception e) { - setDefault(); - return; - } - if (ack <= 0 || ack > from) { - throw new IllegalArgumentException(); - } + public static String parseId(Request request) { + String id = request.getParameter(ID_PARAMETER); + if (id == null || id.isEmpty()) { + throw new IllegalArgumentException("Illegal parameter"); } + return id; } - private void setDefault() { - ack = distributor.getQuorumNumber(); - from = distributor.getNodeCount(); - } + public static ParametersTuple parseAckFromOrDefault( + Request request, + RendezvousDistributor distributor) { - public int getAck() { - return ack; - } + String ack = request.getParameter(ACK_PARAMETER); + String from = request.getParameter(FROM_PARAMETER); - public int getFrom() { - return from; + if (ack == null || from == null) { + return distributor.getDefaultAckFrom(); + } else { + return new ParametersTuple<>( + Integer.parseInt(ack), + Integer.parseInt(from) + ); + } } } diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/ParametersTuple.java b/src/main/java/ru/vk/itmo/test/andreycheshev/ParametersTuple.java new file mode 100644 index 000000000..f7ec5001b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/ParametersTuple.java @@ -0,0 +1,5 @@ +package ru.vk.itmo.test.andreycheshev; + +public record ParametersTuple(T first, T second) { + +} diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/RendezvousDistributor.java b/src/main/java/ru/vk/itmo/test/andreycheshev/RendezvousDistributor.java index 184d9c05d..682472af1 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/RendezvousDistributor.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/RendezvousDistributor.java @@ -9,12 +9,16 @@ import java.util.stream.Collectors; public class RendezvousDistributor { - private final int nodeCount; + private final List clusterUrls; private final int thisNodeNumber; + private final int quorumNumber; + private final ParametersTuple defaultAckFrom; - public RendezvousDistributor(int nodeCount, int thisNodeNumber) { - this.nodeCount = nodeCount; + public RendezvousDistributor(List clusterUrls, int thisNodeNumber) { + this.clusterUrls = clusterUrls; this.thisNodeNumber = thisNodeNumber; + this.quorumNumber = clusterUrls.size() / 2 + 1; + this.defaultAckFrom = new ParametersTuple<>(quorumNumber, clusterUrls.size()); } private static int hashCode(int key) { @@ -25,24 +29,36 @@ private static int hashCode(int key) { return x; } - public List getQuorumNodes(String stringKey, int quorumNumber) { + public List getNodesByKey(String stringKey, int number) { PriorityQueue queue = new PriorityQueue<>( - quorumNumber, + number, Comparator.comparingInt(HashPair::getHash).reversed() ); int key = Hash.murmur3(stringKey); - for (int i = 0; i < quorumNumber; i++) { + for (int i = 0; i < number; i++) { queue.add(new HashPair(hashCode(key + i), i)); } return queue.stream().map(HashPair::getIndex).collect(Collectors.toCollection(ArrayList::new)); } + public List getQuorumNodesByKey(String stringKey) { + return getNodesByKey(stringKey, quorumNumber); + } + + public String getNodeUrlByIndex(int index) { + return clusterUrls.get(index); + } + public int getNodeCount() { - return nodeCount; + return clusterUrls.size(); } public int getQuorumNumber() { - return nodeCount / 2 + 1; + return quorumNumber; + } + + public ParametersTuple getDefaultAckFrom() { + return defaultAckFrom; } public boolean isOurNode(int node) { diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/RequestExecutor.java b/src/main/java/ru/vk/itmo/test/andreycheshev/RequestExecutor.java index 29ec85e81..011882cac 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/RequestExecutor.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/RequestExecutor.java @@ -2,29 +2,21 @@ import one.nio.http.HttpSession; import one.nio.http.Request; -import one.nio.http.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.io.UncheckedIOException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import static one.nio.http.Response.INTERNAL_ERROR; - public class RequestExecutor { private static final Logger LOGGER = LoggerFactory.getLogger(RequestExecutor.class); - private static final String TOO_MANY_REQUESTS = "429 Too many requests"; - private static final int CPU_THREADS_COUNT = Runtime.getRuntime().availableProcessors(); - private static final int MAX_CPU_THREADS_TIMES = 1; private static final int KEEPALIVE_MILLIS = 3000; - private static final long MAX_TASK_AWAITING_TIME_MILLIS = 3000; private static final int MAX_WORK_QUEUE_SIZE = 300; private final ExecutorService executor; @@ -33,53 +25,22 @@ public class RequestExecutor { public RequestExecutor(RequestHandler requestHandler) { this.requestHandler = requestHandler; this.executor = new ThreadPoolExecutor( - CPU_THREADS_COUNT, - CPU_THREADS_COUNT * MAX_CPU_THREADS_TIMES, + CPU_THREADS_COUNT / 2, + CPU_THREADS_COUNT / 2, KEEPALIVE_MILLIS, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(MAX_WORK_QUEUE_SIZE), - new WorkerThreadFactory(), + new WorkerThreadFactory("Distributor-thread"), new ThreadPoolExecutor.AbortPolicy() ); } public void execute(Request request, HttpSession session) { - long currTime = System.currentTimeMillis(); - try { - executor.execute(() -> { - Response response; - - // If the deadline for completing the task has passed. - if (System.currentTimeMillis() - currTime > MAX_TASK_AWAITING_TIME_MILLIS) { - LOGGER.error("The server is overloaded with too many requests"); - response = new Response(TOO_MANY_REQUESTS, Response.EMPTY); - } else { - try { - response = requestHandler.handle(request); - } catch (Exception ex) { - LOGGER.error("Internal error of the DAO operation", ex); - response = new Response(INTERNAL_ERROR, Response.EMPTY); - } - } - - sendResponse(response, session); - }); - } catch (RejectedExecutionException e) { // Queue overflow. + executor.execute(() -> requestHandler.handle(request, session)); + } catch (RejectedExecutionException e) { LOGGER.error("Work queue overflow: task cannot be processed", e); - sendResponse( - new Response(TOO_MANY_REQUESTS, Response.EMPTY), - session - ); - } - } - - private void sendResponse(Response response, HttpSession session) { - try { - session.sendResponse(response); - } catch (IOException e) { - LOGGER.error("Error when sending a response to the client", e); - throw new UncheckedIOException(e); + requestHandler.sendAsync(HttpUtils.getTooManyRequests(), session); } } 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 f21aa237e..b14da85b4 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/RequestHandler.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/RequestHandler.java @@ -1,226 +1,166 @@ package ru.vk.itmo.test.andreycheshev; -import one.nio.http.HttpClient; -import one.nio.http.HttpException; +import one.nio.http.HttpSession; import one.nio.http.Request; import one.nio.http.Response; -import one.nio.pool.PoolException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.vk.itmo.test.andreycheshev.dao.ClusterEntry; import ru.vk.itmo.test.andreycheshev.dao.Dao; import ru.vk.itmo.test.andreycheshev.dao.Entry; -import java.io.IOException; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; -import java.net.SocketTimeoutException; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; -public class RequestHandler { +public class RequestHandler implements HttpProvider { private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class); - private static final TimestampComparator timestampComparator = new TimestampComparator(); - private static final Set AVAILABLE_METHODS; - - static { - AVAILABLE_METHODS = Set.of( - Request.METHOD_GET, - Request.METHOD_PUT, - Request.METHOD_DELETE - ); // Immutable set. - } - - private static final String NOT_ENOUGH_REPLICAS = "504 Not Enough Replicas"; - private static final String REQUEST_PATH = "/v0/entity"; - private static final String ID_PARAMETER = "id="; - private static final String ACK_PARAMETER = "ack="; - private static final String FROM_PARAMETER = "from="; + private static final Set AVAILABLE_METHODS = Set.of( + Request.METHOD_GET, + Request.METHOD_PUT, + Request.METHOD_DELETE + ); - public static final String TIMESTAMP_HEADER = "Timestamp: "; + private static final String FUTURE_CREATION_ERROR = "Error when CompletableFuture creation"; - private static final int OK = 200; - private static final int CREATED = 201; - private static final int ACCEPTED = 202; - private static final int NOT_FOUND = 404; - private static final int GONE = 410; - - private static final int EMPTY_TIMESTAMP = -1; + private static final String REQUEST_PATH = "/v0/entity"; private final Dao> dao; - private final HttpClient[] clusterConnections; private final RendezvousDistributor distributor; + private final AsyncActions asyncActions; public RequestHandler( Dao> dao, - HttpClient[] clusterConnections, RendezvousDistributor distributor) { this.dao = dao; - this.clusterConnections = clusterConnections; this.distributor = distributor; + this.asyncActions = new AsyncActions(this); } - public Response handle(Request request) throws InterruptedException { + public void handle(Request request, HttpSession session) { + Response errorResponse = analyzeRequest(request, session); - // Checking the correctness. - String path = request.getPath(); - if (!path.equals(REQUEST_PATH)) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + if (errorResponse != null) { + sendAsync(errorResponse, session); } + } - String id = request.getParameter(ID_PARAMETER); - if (id == null || id.isEmpty()) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + public void sendAsync(Response response, HttpSession session) { + CompletableFuture future = asyncActions.sendAsync(response, session); + if (future == null) { + LOGGER.info(FUTURE_CREATION_ERROR); } + } - int method = request.getMethod(); - if (!AVAILABLE_METHODS.contains(method)) { - return new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); + private Response analyzeRequest(Request request, HttpSession session) { + + String path = request.getPath(); + if (!path.equals(REQUEST_PATH)) { + return HttpUtils.getBadRequest(); } - // A timestamp is an indication that the request - // came from the client directly or from the cluster node. - String timestamp = request.getHeader(TIMESTAMP_HEADER); - if (timestamp != null) { - // The request came from a remote node. - try { - return processLocally(method, - id, - request, - Long.parseLong(timestamp) - ); - } catch (NumberFormatException e) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); - } + int method = request.getMethod(); + if (!AVAILABLE_METHODS.contains(method)) { + return HttpUtils.getMethodNotAllowed(); } - // Get and check "ack" and "from" parameters. - ParametersParser parser = new ParametersParser(distributor); try { - parser.parseAckFrom( - request.getParameter(ACK_PARAMETER), - request.getParameter(FROM_PARAMETER) - ); + + String id = ParametersParser.parseId(request); + String timestamp = request.getHeader(HttpUtils.TIMESTAMP_ONE_NIO_HEADER); + + return isRequestCameFromNode(timestamp) + ? processLocally(request, method, id, timestamp, session) + : processDistributed(request, method, id, session); + } catch (IllegalArgumentException e) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + return HttpUtils.getBadRequest(); } - // Start processing on remote and local nodes. - return processDistributed(method, id, request, parser.getAck(), parser.getFrom()); } - private Response processDistributed( - int method, - String id, - Request request, - int ack, - int from) throws InterruptedException { - - List nodes = distributor.getQuorumNodes(id, from); - List responses = new ArrayList<>(); - - long currTimestamp = (method == Request.METHOD_GET) - ? EMPTY_TIMESTAMP - : System.currentTimeMillis(); + private Response processLocally(Request request, int method, String id, String timestamp, HttpSession session) { + try { + CompletableFuture future = + asyncActions.processLocallyToSend(method, id, request, Long.parseLong(timestamp), session); - for (int node : nodes) { - try { - Response response = distributor.isOurNode(node) - ? processLocally(method, id, request, currTimestamp) - : redirectRequest(node, request, currTimestamp); - - if (isRequestSucceeded(method, response.getStatus())) { - responses.add(response); - } - - // For the GET method, we do not need to continue to request - // if we have already received the required number of successful responses. - if (method == Request.METHOD_GET && areEnoughSuccessfulResponses(responses, ack)) { - return analyzeResponses(method, responses); - } - } catch (SocketTimeoutException e) { - LOGGER.error("Request processing time exceeded on another node", e); - } catch (PoolException | HttpException | IOException e) { - LOGGER.error("An error occurred when processing a request on another node", e); + if (future == null) { + LOGGER.info(FUTURE_CREATION_ERROR); + return HttpUtils.getInternalError(); } + } catch (NumberFormatException e) { + return HttpUtils.getBadRequest(); } - - return !areEnoughSuccessfulResponses(responses, ack) - ? new Response(NOT_ENOUGH_REPLICAS, Response.EMPTY) - : analyzeResponses(method, responses); - } - - private static boolean isRequestSucceeded(int method, int status) { - return (method == Request.METHOD_GET && (status == OK || status == NOT_FOUND || status == GONE)) - || (method == Request.METHOD_PUT && status == CREATED) - || (method == Request.METHOD_DELETE && status == ACCEPTED); + return null; } - private static boolean areEnoughSuccessfulResponses(List responses, int ack) { - return responses.size() >= ack; + 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 redirectRequest( - int nodeNumber, + private Response processDistributed( Request request, - long timestamp) throws HttpException, IOException, PoolException, InterruptedException { + int method, + String id, + HttpSession session) { + + ParametersTuple ackFrom = ParametersParser.parseAckFromOrDefault(request, distributor); + int ack = ackFrom.first(); + int from = ackFrom.second(); + if (ack <= 0 || ack > from) { + LOGGER.error("An error occurred while analyzing the parameters"); + return HttpUtils.getBadRequest(); + } - HttpClient client = clusterConnections[nodeNumber]; - request.addHeader(TIMESTAMP_HEADER + timestamp); - return client.invoke(request); - } + List nodesIndices = distributor.getNodesByKey(id, from); - private Response processLocally(int method, String id, Request request, long timestamp) { - return switch (method) { - case Request.METHOD_GET -> get(id); - case Request.METHOD_PUT -> put(id, request.getBody(), timestamp); - default -> delete(id, timestamp); - }; - } + long timestamp = method == Request.METHOD_GET + ? HttpUtils.EMPTY_TIMESTAMP + : System.currentTimeMillis(); - private Response analyzeResponses(int method, List responses) { - // The required number of successful responses has already been received here. - switch (method) { - case Request.METHOD_GET -> { - responses.sort(timestampComparator); + ResponseCollector collector = new ResponseCollector(method, ack, from); - Response response = responses.getFirst(); + for (int nodeIndex : nodesIndices) { + String node = distributor.getNodeUrlByIndex(nodeIndex); - return (response.getStatus() == GONE) - ? new Response(Response.NOT_FOUND, Response.EMPTY) - : response; - } - case Request.METHOD_PUT -> { - return new Response(Response.CREATED, Response.EMPTY); - } - default -> { // For delete method. - return new Response(Response.ACCEPTED, Response.EMPTY); + CompletableFuture future = distributor.isOurNode(nodeIndex) + ? asyncActions.processLocallyToCollect(method, id, request, timestamp, collector, session) + : asyncActions.processRemotelyToCollect(node, request, timestamp, collector, session); + + if (future == null) { + LOGGER.info(FUTURE_CREATION_ERROR); } } + + return null; } - private Response get(String id) { + @Override + public ResponseElements get(String id) { ClusterEntry entry = (ClusterEntry) dao.get(fromString(id)); - Response response; + ResponseElements response; if (entry == null) { - response = new Response(Response.NOT_FOUND, Response.EMPTY); - response.addHeader(TIMESTAMP_HEADER + EMPTY_TIMESTAMP); + response = new ResponseElements(HttpUtils.NOT_FOUND_CODE, Response.EMPTY, HttpUtils.EMPTY_TIMESTAMP); } else { + long timestamp = entry.timestamp(); response = (entry.value() == null) - ? new Response(Response.GONE, Response.EMPTY) - : new Response(Response.OK, entry.value().toArray(ValueLayout.JAVA_BYTE)); - response.addHeader(TIMESTAMP_HEADER + entry.timestamp()); + ? new ResponseElements(HttpUtils.GONE_CODE, Response.EMPTY, timestamp) + : new ResponseElements(HttpUtils.OK_CODE, entry.value().toArray(ValueLayout.JAVA_BYTE), timestamp); } return response; } - private Response put(String id, byte[] body, long timestamp) { + @Override + public ResponseElements put(String id, byte[] body, long timestamp) { Entry entry = new ClusterEntry<>( fromString(id), MemorySegment.ofArray(body), @@ -228,10 +168,11 @@ private Response put(String id, byte[] body, long timestamp) { ); dao.upsert(entry); - return new Response(Response.CREATED, Response.EMPTY); + return new ResponseElements(HttpUtils.CREATED_CODE, Response.EMPTY, HttpUtils.EMPTY_TIMESTAMP); } - private Response delete(String id, long timestamp) { + @Override + public ResponseElements delete(String id, long timestamp) { Entry entry = new ClusterEntry<>( fromString(id), null, @@ -239,7 +180,7 @@ private Response delete(String id, long timestamp) { ); dao.upsert(entry); - return new Response(Response.ACCEPTED, Response.EMPTY); + return new ResponseElements(HttpUtils.ACCEPT_CODE, Response.EMPTY, HttpUtils.EMPTY_TIMESTAMP); } private static MemorySegment fromString(String data) { diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseCollector.java b/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseCollector.java new file mode 100644 index 000000000..c6b903d31 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseCollector.java @@ -0,0 +1,56 @@ +package ru.vk.itmo.test.andreycheshev; + +import one.nio.http.Request; +import one.nio.http.Response; + +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +public class ResponseCollector { + private final PriorityBlockingQueue collector; // Contains only successful responses. + + private final int method; + private final int ack; + private final int from; + private final AtomicBoolean sendCondition = new AtomicBoolean(false); + private final AtomicInteger responseCount = new AtomicInteger(0); + + public ResponseCollector(int method, int ack, int from) { + this.collector = new PriorityBlockingQueue<>(from); + + this.method = method; + this.ack = ack; + this.from = from; + } + + public boolean incrementResponsesCounter() { + responseCount.incrementAndGet(); + + if (isReadySendResponse()) { + // Setting the flag to ensure that only one response is sent. + return sendCondition.compareAndSet(false, true); + } + return false; + } + + public void add(ResponseElements responseElements) { + if (isResponseSucceeded(responseElements.getStatus())) { + collector.put(responseElements); + } + } + + private static boolean isResponseSucceeded(int status) { + return HttpUtils.AVAILABLE_RESPONSES.containsKey(status); + } + + private boolean isReadySendResponse() { + return (method == Request.METHOD_GET && collector.size() >= ack) || responseCount.get() >= from; + } + + public Response getResponse() { + return collector.size() >= ack + ? HttpUtils.getOneNioResponse(method, collector.poll()) + : HttpUtils.getNotEnoughReplicas(); + } +} diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseElements.java b/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseElements.java new file mode 100644 index 000000000..560887636 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/ResponseElements.java @@ -0,0 +1,36 @@ +package ru.vk.itmo.test.andreycheshev; + +public class ResponseElements implements Comparable { + private final int status; + private final ByteArrayWrapper body; + private final long timestamp; + + public ResponseElements(int status, byte[] body, long timestamp) { + this.status = status; + this.body = new ByteArrayWrapper(body); + this.timestamp = timestamp; + } + + public int getStatus() { + return status; + } + + public byte[] getBody() { + return body.get(); + } + + public long getTimestamp() { + return timestamp; + } + + @Override + public int compareTo(ResponseElements o) { + long diff = timestamp - o.timestamp; + if (diff > 0) { + return -1; + } else if (diff < 0) { + return 1; + } + return 0; + } +} diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/SendingResponseException.java b/src/main/java/ru/vk/itmo/test/andreycheshev/SendingResponseException.java new file mode 100644 index 000000000..bd1e4b072 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/SendingResponseException.java @@ -0,0 +1,7 @@ +package ru.vk.itmo.test.andreycheshev; + +public class SendingResponseException extends RuntimeException { + public SendingResponseException(Throwable cause) { + super(cause); + } +} 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 18b617cfb..7e214d871 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/lab3/"); + private static final Path STORAGE_DIR_PATH = Path.of("/home/andrey/andrey/lab5/putput"); private static final String LOCALHOST = "http://localhost"; private static final int BASE_PORT = 8080; private static final int CLUSTER_NODE_COUNT = 4; @@ -39,7 +39,7 @@ private static void startCluster() throws IOException, ExecutionException, Inter List urls = new ArrayList<>(CLUSTER_NODE_COUNT); for (Integer port: ports) { - urls.add(LOCALHOST + port); + urls.add(LOCALHOST + ":" + port); } for (int i = 0; i < CLUSTER_NODE_COUNT; i++) { 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 224f522b7..450b2b3e5 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/ServiceImpl.java @@ -1,8 +1,6 @@ package ru.vk.itmo.test.andreycheshev; -import one.nio.http.HttpClient; import one.nio.http.HttpServerConfig; -import one.nio.net.ConnectionString; import one.nio.server.AcceptorConfig; import one.nio.server.Server; import ru.vk.itmo.Service; @@ -20,44 +18,22 @@ public class ServiceImpl implements Service { private static final int THRESHOLD_BYTES = 1024 * 1024; - private static final int CLUSTER_NODE_RESPONSE_TIMEOUT_MILLIS = 1000; private final HttpServerConfig serverConfig; private final Config daoConfig; - private final String selfUrl; - private final List sortedClusterUrls; private final RendezvousDistributor rendezvousDistributor; - private HttpClient[] clusterConnections; private Server server; public ServiceImpl(ServiceConfig config) { - this.sortedClusterUrls = new ArrayList<>(config.clusterUrls()); - Collections.sort(sortedClusterUrls); - - this.selfUrl = config.selfUrl(); this.serverConfig = createServerConfig(config); this.daoConfig = new Config(config.workingDir(), THRESHOLD_BYTES); - int thisNodeNumber = sortedClusterUrls.indexOf(selfUrl); - this.rendezvousDistributor = new RendezvousDistributor(sortedClusterUrls.size(), thisNodeNumber); - } - - private void initCluster(String selfUrl) { - this.clusterConnections = new HttpClient[sortedClusterUrls.size()]; - - int nodeNumber = 0; - for (String serverUrl : sortedClusterUrls) { - if (serverUrl.equals(selfUrl)) { - nodeNumber++; - continue; - } - - HttpClient client = new HttpClient(new ConnectionString(serverUrl)); - client.setTimeout(CLUSTER_NODE_RESPONSE_TIMEOUT_MILLIS); + List sortedClusterUrls = new ArrayList<>(config.clusterUrls()); + Collections.sort(sortedClusterUrls); - clusterConnections[nodeNumber++] = client; - } + int thisNodeNumber = sortedClusterUrls.indexOf(config.selfUrl()); + this.rendezvousDistributor = new RendezvousDistributor(sortedClusterUrls, thisNodeNumber); } private HttpServerConfig createServerConfig(ServiceConfig config) { @@ -75,10 +51,8 @@ private HttpServerConfig createServerConfig(ServiceConfig config) { @Override public CompletableFuture start() throws IOException { try { - initCluster(selfUrl); - PersistentReferenceDao dao = new PersistentReferenceDao(daoConfig); - RequestHandler handler = new RequestHandler(dao, clusterConnections, rendezvousDistributor); + RequestHandler handler = new RequestHandler(dao, rendezvousDistributor); RequestExecutor executor = new RequestExecutor(handler); server = new ServerImpl(serverConfig, dao, executor); @@ -93,16 +67,10 @@ public CompletableFuture start() throws IOException { public CompletableFuture stop() throws IOException { server.stop(); - for (HttpClient clusterConnection : clusterConnections) { - if (clusterConnection != null && !clusterConnection.isClosed()) { - clusterConnection.close(); - } - } - 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/andreycheshev/TimestampComparator.java b/src/main/java/ru/vk/itmo/test/andreycheshev/TimestampComparator.java deleted file mode 100644 index 231d4065b..000000000 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/TimestampComparator.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.vk.itmo.test.andreycheshev; - -import one.nio.http.Response; - -import java.util.Comparator; - -import static ru.vk.itmo.test.andreycheshev.RequestHandler.TIMESTAMP_HEADER; - -public class TimestampComparator implements Comparator { - @Override - public int compare(Response first, Response second) { - long timestamp1 = Long.parseLong(first.getHeader(TIMESTAMP_HEADER)); - long timestamp2 = Long.parseLong(second.getHeader(TIMESTAMP_HEADER)); - if (timestamp1 > timestamp2) { - return -1; - } else if (timestamp1 < timestamp2) { - return 1; - } - return 0; - } -} diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/WorkerThreadFactory.java b/src/main/java/ru/vk/itmo/test/andreycheshev/WorkerThreadFactory.java index e7c3e3391..89d632c50 100644 --- a/src/main/java/ru/vk/itmo/test/andreycheshev/WorkerThreadFactory.java +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/WorkerThreadFactory.java @@ -3,11 +3,15 @@ import java.util.concurrent.ThreadFactory; public class WorkerThreadFactory implements ThreadFactory { - private static final String PREFIX = "RequestProcessingWorkerThread-"; + private final String prefix; private int counter; + public WorkerThreadFactory(String prefix) { + this.prefix = prefix + "-"; + } + @Override public Thread newThread(Runnable runnable) { - return new Thread(runnable, PREFIX + counter++); + return new Thread(runnable, prefix + counter++); } } diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/1100_c-250.txt b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/1100_c-250.txt new file mode 100644 index 000000000..540b35421 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/1100_c-250.txt @@ -0,0 +1,109 @@ +[andrey@archlinux wrk2]$ ./wrk -c 250 -t 1 -d 60 -R 1100 -L -s ~/andrey/scripts/getRandom.lua http://localhost:8081/v0/entity/ +Running 1m test @ http://localhost:8081/v0/entity/ + 1 threads and 250 connections + Thread calibration: mean lat.: 17.547ms, rate sampling interval: 61ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 19.06ms 13.59ms 232.70ms 83.98% + Req/Sec 1.11k 285.81 2.20k 73.58% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 16.91ms + 75.000% 24.25ms + 90.000% 32.29ms + 99.000% 58.30ms + 99.900% 177.02ms + 99.990% 224.64ms + 99.999% 231.42ms +100.000% 232.83ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.958 0.000000 1 1.00 + 6.783 0.100000 5368 1.11 + 9.727 0.200000 10737 1.25 + 12.135 0.300000 16093 1.43 + 14.511 0.400000 21456 1.67 + 16.911 0.500000 26810 2.00 + 18.255 0.550000 29496 2.22 + 19.583 0.600000 32190 2.50 + 21.007 0.650000 34870 2.86 + 22.527 0.700000 37539 3.33 + 24.255 0.750000 40224 4.00 + 25.231 0.775000 41559 4.44 + 26.303 0.800000 42907 5.00 + 27.519 0.825000 44243 5.71 + 28.911 0.850000 45587 6.67 + 30.415 0.875000 46924 8.00 + 31.311 0.887500 47586 8.89 + 32.287 0.900000 48263 10.00 + 33.375 0.912500 48932 11.43 + 34.687 0.925000 49623 13.33 + 36.159 0.937500 50277 16.00 + 37.119 0.943750 50615 17.78 + 38.111 0.950000 50943 20.00 + 39.199 0.956250 51275 22.86 + 40.671 0.962500 51609 26.67 + 42.527 0.968750 51943 32.00 + 43.583 0.971875 52112 35.56 + 44.735 0.975000 52277 40.00 + 46.271 0.978125 52445 45.71 + 48.095 0.981250 52612 53.33 + 50.463 0.984375 52780 64.00 + 51.935 0.985938 52864 71.11 + 53.727 0.987500 52947 80.00 + 56.191 0.989062 53032 91.43 + 59.327 0.990625 53115 106.67 + 63.583 0.992188 53199 128.00 + 66.239 0.992969 53241 142.22 + 69.119 0.993750 53283 160.00 + 74.431 0.994531 53324 182.86 + 80.383 0.995313 53366 213.33 + 89.151 0.996094 53408 256.00 + 95.743 0.996484 53429 284.44 + 106.559 0.996875 53450 320.00 + 121.087 0.997266 53471 365.71 + 134.655 0.997656 53492 426.67 + 151.807 0.998047 53513 512.00 + 156.415 0.998242 53524 568.89 + 163.199 0.998437 53535 640.00 + 168.191 0.998633 53544 731.43 + 174.463 0.998828 53555 853.33 + 177.791 0.999023 53565 1024.00 + 183.167 0.999121 53570 1137.78 + 190.463 0.999219 53576 1280.00 + 194.559 0.999316 53581 1462.86 + 198.143 0.999414 53586 1706.67 + 204.799 0.999512 53591 2048.00 + 206.591 0.999561 53594 2275.56 + 208.895 0.999609 53597 2560.00 + 212.095 0.999658 53599 2925.71 + 215.807 0.999707 53602 3413.33 + 217.087 0.999756 53604 4096.00 + 218.495 0.999780 53606 4551.11 + 219.391 0.999805 53607 5120.00 + 219.519 0.999829 53609 5851.43 + 220.927 0.999854 53610 6826.67 + 223.231 0.999878 53611 8192.00 + 224.639 0.999890 53612 9102.22 + 224.639 0.999902 53612 10240.00 + 226.687 0.999915 53613 11702.86 + 228.095 0.999927 53614 13653.33 + 228.095 0.999939 53614 16384.00 + 230.655 0.999945 53615 18204.44 + 230.655 0.999951 53615 20480.00 + 230.655 0.999957 53615 23405.71 + 231.423 0.999963 53616 27306.67 + 231.423 0.999969 53616 32768.00 + 231.423 0.999973 53616 36408.89 + 231.423 0.999976 53616 40960.00 + 231.423 0.999979 53616 46811.43 + 232.831 0.999982 53617 54613.33 + 232.831 1.000000 53617 inf +#[Mean = 19.063, StdDeviation = 13.587] +#[Max = 232.704, Total count = 53617] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 65409 requests in 1.00m, 5.44MB read + Non-2xx or 3xx responses: 56392 +Requests/sec: 1090.11 +Transfer/sec: 92.87KB diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/1100_c-250_old.txt b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/1100_c-250_old.txt new file mode 100644 index 000000000..c9f14cc08 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/1100_c-250_old.txt @@ -0,0 +1,105 @@ +[andrey@archlinux wrk2]$ ./wrk -d 40 -t 1 -c 250 -s ~/andrey/scripts/getRandom.lua -L -R 1100 http://localhost:8080/v0/entity/ +Running 40s test @ http://localhost:8080/v0/entity/ + 1 threads and 250 connections + Thread calibration: mean lat.: 47.670ms, rate sampling interval: 177ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 35.32ms 22.03ms 121.41ms 66.05% + Req/Sec 1.10k 151.99 1.45k 63.75% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 31.81ms + 75.000% 49.63ms + 90.000% 67.26ms + 99.000% 91.39ms + 99.900% 105.73ms + 99.990% 115.26ms + 99.999% 121.47ms +100.000% 121.47ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.587 0.000000 1 1.00 + 9.263 0.100000 3168 1.11 + 15.111 0.200000 6338 1.25 + 20.543 0.300000 9509 1.43 + 25.951 0.400000 12684 1.67 + 31.807 0.500000 15844 2.00 + 35.007 0.550000 17443 2.22 + 38.207 0.600000 19021 2.50 + 41.695 0.650000 20603 2.86 + 45.471 0.700000 22188 3.33 + 49.631 0.750000 23772 4.00 + 51.903 0.775000 24553 4.44 + 54.463 0.800000 25348 5.00 + 57.055 0.825000 26137 5.71 + 60.063 0.850000 26935 6.67 + 63.423 0.875000 27721 8.00 + 65.311 0.887500 28117 8.89 + 67.263 0.900000 28518 10.00 + 69.503 0.912500 28909 11.43 + 72.191 0.925000 29306 13.33 + 74.815 0.937500 29706 16.00 + 76.287 0.943750 29908 17.78 + 77.695 0.950000 30099 20.00 + 79.167 0.956250 30295 22.86 + 80.767 0.962500 30495 26.67 + 82.623 0.968750 30693 32.00 + 83.775 0.971875 30792 35.56 + 84.735 0.975000 30895 40.00 + 85.695 0.978125 30995 45.71 + 86.911 0.981250 31089 53.33 + 88.127 0.984375 31186 64.00 + 88.703 0.985938 31235 71.11 + 89.663 0.987500 31284 80.00 + 90.879 0.989062 31339 91.43 + 91.903 0.990625 31385 106.67 + 92.607 0.992188 31432 128.00 + 93.247 0.992969 31457 142.22 + 93.951 0.993750 31482 160.00 + 94.527 0.994531 31506 182.86 + 95.679 0.995313 31531 213.33 + 96.767 0.996094 31556 256.00 + 97.407 0.996484 31568 284.44 + 98.495 0.996875 31582 320.00 + 99.007 0.997266 31593 365.71 + 100.031 0.997656 31605 426.67 + 100.863 0.998047 31618 512.00 + 101.631 0.998242 31624 568.89 + 102.719 0.998437 31630 640.00 + 104.319 0.998633 31637 731.43 + 104.959 0.998828 31642 853.33 + 106.047 0.999023 31650 1024.00 + 106.303 0.999121 31653 1137.78 + 106.623 0.999219 31655 1280.00 + 107.199 0.999316 31658 1462.86 + 108.415 0.999414 31661 1706.67 + 108.799 0.999512 31665 2048.00 + 109.055 0.999561 31666 2275.56 + 109.951 0.999609 31667 2560.00 + 110.591 0.999658 31670 2925.71 + 110.591 0.999707 31670 3413.33 + 112.127 0.999756 31672 4096.00 + 112.831 0.999780 31673 4551.11 + 112.831 0.999805 31673 5120.00 + 113.023 0.999829 31674 5851.43 + 114.303 0.999854 31675 6826.67 + 115.263 0.999878 31677 8192.00 + 115.263 0.999890 31677 9102.22 + 115.263 0.999902 31677 10240.00 + 115.263 0.999915 31677 11702.86 + 115.263 0.999927 31677 13653.33 + 115.583 0.999939 31678 16384.00 + 115.583 0.999945 31678 18204.44 + 115.583 0.999951 31678 20480.00 + 115.583 0.999957 31678 23405.71 + 115.583 0.999963 31678 27306.67 + 121.471 0.999969 31679 32768.00 + 121.471 1.000000 31679 inf +#[Mean = 35.320, StdDeviation = 22.028] +#[Max = 121.408, Total count = 31679] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 43431 requests in 40.00s, 4.31MB read + Non-2xx or 3xx responses: 33212 +Requests/sec: 1085.72 +Transfer/sec: 110.42KB diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/1500_c-250.txt b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/1500_c-250.txt new file mode 100644 index 000000000..63e1c15f4 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/1500_c-250.txt @@ -0,0 +1,112 @@ +[andrey@archlinux wrk2]$ ./wrk -c 250 -t 1 -d 60 -R 1500 -L -s ~/andrey/scripts/getRandom.lua http://localhost:8081/v0/entity/ +Running 1m test @ http://localhost:8081/v0/entity/ + 1 threads and 250 connections + Thread calibration: mean lat.: 26.633ms, rate sampling interval: 94ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 24.41ms 20.76ms 289.54ms 93.68% + Req/Sec 1.51k 304.00 3.95k 74.95% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 20.99ms + 75.000% 29.30ms + 90.000% 38.53ms + 99.000% 121.15ms + 99.900% 244.86ms + 99.990% 278.53ms + 99.999% 285.18ms +100.000% 289.79ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.921 0.000000 1 1.00 + 9.415 0.100000 7318 1.11 + 12.791 0.200000 14638 1.25 + 15.663 0.300000 21942 1.43 + 18.319 0.400000 29276 1.67 + 20.991 0.500000 36568 2.00 + 22.431 0.550000 40219 2.22 + 23.887 0.600000 43883 2.50 + 25.535 0.650000 47524 2.86 + 27.247 0.700000 51200 3.33 + 29.295 0.750000 54848 4.00 + 30.431 0.775000 56665 4.44 + 31.631 0.800000 58492 5.00 + 32.991 0.825000 60332 5.71 + 34.463 0.850000 62157 6.67 + 36.319 0.875000 63994 8.00 + 37.375 0.887500 64904 8.89 + 38.527 0.900000 65825 10.00 + 39.839 0.912500 66725 11.43 + 41.407 0.925000 67632 13.33 + 43.551 0.937500 68552 16.00 + 44.735 0.943750 69004 17.78 + 46.111 0.950000 69462 20.00 + 47.807 0.956250 69916 22.86 + 49.855 0.962500 70371 26.67 + 52.575 0.968750 70829 32.00 + 54.463 0.971875 71058 35.56 + 56.735 0.975000 71285 40.00 + 61.055 0.978125 71513 45.71 + 67.263 0.981250 71746 53.33 + 78.591 0.984375 71970 64.00 + 91.135 0.985938 72084 71.11 + 102.655 0.987500 72200 80.00 + 113.599 0.989062 72313 91.43 + 126.207 0.990625 72427 106.67 + 137.343 0.992188 72542 128.00 + 142.591 0.992969 72598 142.22 + 155.647 0.993750 72656 160.00 + 172.927 0.994531 72714 182.86 + 184.063 0.995313 72770 213.33 + 196.351 0.996094 72827 256.00 + 202.111 0.996484 72855 284.44 + 205.823 0.996875 72884 320.00 + 212.863 0.997266 72913 365.71 + 220.799 0.997656 72941 426.67 + 226.943 0.998047 72970 512.00 + 228.607 0.998242 72984 568.89 + 232.831 0.998437 72998 640.00 + 237.055 0.998633 73013 731.43 + 241.279 0.998828 73030 853.33 + 246.143 0.999023 73041 1024.00 + 249.087 0.999121 73048 1137.78 + 250.495 0.999219 73055 1280.00 + 254.975 0.999316 73064 1462.86 + 255.871 0.999414 73070 1706.67 + 258.559 0.999512 73077 2048.00 + 259.967 0.999561 73080 2275.56 + 261.631 0.999609 73084 2560.00 + 263.167 0.999658 73088 2925.71 + 265.215 0.999707 73092 3413.33 + 267.263 0.999756 73095 4096.00 + 267.519 0.999780 73096 4551.11 + 268.287 0.999805 73098 5120.00 + 269.823 0.999829 73100 5851.43 + 271.871 0.999854 73102 6826.67 + 274.943 0.999878 73104 8192.00 + 274.943 0.999890 73104 9102.22 + 278.527 0.999902 73105 10240.00 + 279.295 0.999915 73106 11702.86 + 279.551 0.999927 73107 13653.33 + 279.807 0.999939 73108 16384.00 + 279.807 0.999945 73108 18204.44 + 282.111 0.999951 73109 20480.00 + 282.111 0.999957 73109 23405.71 + 283.903 0.999963 73110 27306.67 + 283.903 0.999969 73110 32768.00 + 283.903 0.999973 73110 36408.89 + 285.183 0.999976 73111 40960.00 + 285.183 0.999979 73111 46811.43 + 285.183 0.999982 73111 54613.33 + 285.183 0.999985 73111 65536.00 + 285.183 0.999986 73111 72817.78 + 289.791 0.999988 73112 81920.00 + 289.791 1.000000 73112 inf +#[Mean = 24.406, StdDeviation = 20.758] +#[Max = 289.536, Total count = 73112] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 89151 requests in 1.00m, 7.42MB read + Non-2xx or 3xx responses: 76949 +Requests/sec: 1485.82 +Transfer/sec: 126.57KB diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/1800_c-250.txt b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/1800_c-250.txt new file mode 100644 index 000000000..2b6a31c52 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/1800_c-250.txt @@ -0,0 +1,113 @@ +[andrey@archlinux wrk2]$ ./wrk -c 250 -t 1 -d 60 -R 1800 -L -s ~/andrey/scripts/getRandom.lua http://localhost:8081/v0/entity/ +Running 1m test @ http://localhost:8081/v0/entity/ + 1 threads and 250 connections + Thread calibration: mean lat.: 54.145ms, rate sampling interval: 237ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 82.63ms 133.28ms 821.25ms 87.69% + Req/Sec 1.80k 483.53 4.35k 82.18% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 26.83ms + 75.000% 73.02ms + 90.000% 249.85ms + 99.000% 642.56ms + 99.900% 776.70ms + 99.990% 808.96ms + 99.999% 819.71ms +100.000% 821.76ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.987 0.000000 1 1.00 + 7.803 0.100000 8777 1.11 + 12.775 0.200000 17546 1.25 + 17.151 0.300000 26350 1.43 + 21.551 0.400000 35109 1.67 + 26.831 0.500000 43886 2.00 + 30.175 0.550000 48260 2.22 + 34.591 0.600000 52652 2.50 + 41.375 0.650000 57034 2.86 + 52.671 0.700000 61415 3.33 + 73.023 0.750000 65807 4.00 + 89.535 0.775000 67993 4.44 + 109.311 0.800000 70188 5.00 + 139.519 0.825000 72380 5.71 + 176.255 0.850000 74579 6.67 + 213.247 0.875000 76764 8.00 + 230.783 0.887500 77870 8.89 + 249.855 0.900000 78957 10.00 + 275.455 0.912500 80055 11.43 + 304.639 0.925000 81149 13.33 + 342.783 0.937500 82245 16.00 + 367.359 0.943750 82798 17.78 + 396.031 0.950000 83342 20.00 + 423.935 0.956250 83890 22.86 + 458.751 0.962500 84442 26.67 + 490.751 0.968750 84987 32.00 + 508.159 0.971875 85265 35.56 + 526.847 0.975000 85538 40.00 + 548.351 0.978125 85813 45.71 + 572.927 0.981250 86087 53.33 + 594.431 0.984375 86358 64.00 + 606.207 0.985938 86496 71.11 + 619.519 0.987500 86636 80.00 + 632.831 0.989062 86771 91.43 + 648.191 0.990625 86907 106.67 + 666.623 0.992188 87043 128.00 + 675.839 0.992969 87113 142.22 + 684.031 0.993750 87187 160.00 + 690.687 0.994531 87250 182.86 + 698.879 0.995313 87318 213.33 + 710.143 0.996094 87388 256.00 + 717.311 0.996484 87420 284.44 + 723.967 0.996875 87455 320.00 + 732.671 0.997266 87490 365.71 + 740.351 0.997656 87523 426.67 + 750.591 0.998047 87560 512.00 + 755.199 0.998242 87574 568.89 + 761.855 0.998437 87591 640.00 + 766.463 0.998633 87609 731.43 + 772.607 0.998828 87626 853.33 + 777.215 0.999023 87644 1024.00 + 779.263 0.999121 87654 1137.78 + 781.823 0.999219 87660 1280.00 + 784.383 0.999316 87671 1462.86 + 787.455 0.999414 87679 1706.67 + 790.015 0.999512 87686 2048.00 + 793.087 0.999561 87692 2275.56 + 794.111 0.999609 87694 2560.00 + 795.135 0.999658 87699 2925.71 + 796.159 0.999707 87703 3413.33 + 797.183 0.999756 87707 4096.00 + 799.231 0.999780 87709 4551.11 + 800.255 0.999805 87711 5120.00 + 806.399 0.999829 87714 5851.43 + 806.911 0.999854 87716 6826.67 + 807.423 0.999878 87718 8192.00 + 808.959 0.999890 87720 9102.22 + 808.959 0.999902 87720 10240.00 + 810.495 0.999915 87721 11702.86 + 811.007 0.999927 87722 13653.33 + 811.519 0.999939 87723 16384.00 + 812.543 0.999945 87724 18204.44 + 812.543 0.999951 87724 20480.00 + 813.055 0.999957 87725 23405.71 + 813.055 0.999963 87725 27306.67 + 818.687 0.999969 87726 32768.00 + 818.687 0.999973 87726 36408.89 + 818.687 0.999976 87726 40960.00 + 819.711 0.999979 87727 46811.43 + 819.711 0.999982 87727 54613.33 + 819.711 0.999985 87727 65536.00 + 819.711 0.999986 87727 72817.78 + 819.711 0.999988 87727 81920.00 + 821.759 0.999989 87728 93622.86 + 821.759 1.000000 87728 inf +#[Mean = 82.629, StdDeviation = 133.280] +#[Max = 821.248, Total count = 87728] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 106902 requests in 1.00m, 8.90MB read + Non-2xx or 3xx responses: 92005 +Requests/sec: 1781.56 +Transfer/sec: 151.81KB diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/2200_c-250.txt b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/2200_c-250.txt new file mode 100644 index 000000000..86f3a3fc7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/get/2200_c-250.txt @@ -0,0 +1,107 @@ +[andrey@archlinux wrk2]$ ./wrk -c 250 -t 1 -d 60 -R 2200 -L -s ~/andrey/scripts/getRandom.lua http://localhost:8081/v0/entity/ +Running 1m test @ http://localhost:8081/v0/entity/ + 1 threads and 250 connections + Thread calibration: mean lat.: 28.189ms, rate sampling interval: 92ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.08s 3.80s 11.60s 78.46% + Req/Sec 2.71k 1.08k 6.38k 75.44% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 50.65ms + 75.000% 6.23s + 90.000% 9.22s + 99.000% 11.32s + 99.900% 11.57s + 99.990% 11.60s + 99.999% 11.61s +100.000% 11.61s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 1.397 0.000000 1 1.00 + 16.943 0.100000 10064 1.11 + 22.783 0.200000 20117 1.25 + 28.431 0.300000 30187 1.43 + 35.647 0.400000 40219 1.67 + 50.655 0.500000 50284 2.00 + 359.935 0.550000 55300 2.22 + 3696.639 0.600000 60331 2.50 + 4550.655 0.650000 65378 2.86 + 5337.087 0.700000 70403 3.33 + 6234.111 0.750000 75409 4.00 + 6709.247 0.775000 77940 4.44 + 7143.423 0.800000 80446 5.00 + 7577.599 0.825000 82955 5.71 + 8118.271 0.850000 85467 6.67 + 8658.943 0.875000 88017 8.00 + 8945.663 0.887500 89236 8.89 + 9224.191 0.900000 90496 10.00 + 9510.911 0.912500 91778 11.43 + 9814.015 0.925000 93023 13.33 + 10117.119 0.937500 94267 16.00 + 10272.767 0.943750 94920 17.78 + 10412.031 0.950000 95551 20.00 + 10551.295 0.956250 96174 22.86 + 10690.559 0.962500 96781 26.67 + 10821.631 0.968750 97404 32.00 + 10887.167 0.971875 97737 35.56 + 10969.087 0.975000 98062 40.00 + 11042.815 0.978125 98360 45.71 + 11108.351 0.981250 98688 53.33 + 11165.695 0.984375 98997 64.00 + 11214.847 0.985938 99157 71.11 + 11255.807 0.987500 99309 80.00 + 11296.767 0.989062 99458 91.43 + 11337.727 0.990625 99627 106.67 + 11378.687 0.992188 99773 128.00 + 11395.071 0.992969 99839 142.22 + 11411.455 0.993750 99919 160.00 + 11427.839 0.994531 99996 182.86 + 11452.415 0.995313 100093 213.33 + 11476.991 0.996094 100160 256.00 + 11493.375 0.996484 100206 284.44 + 11501.567 0.996875 100243 320.00 + 11509.759 0.997266 100276 365.71 + 11526.143 0.997656 100328 426.67 + 11534.335 0.998047 100349 512.00 + 11542.527 0.998242 100373 568.89 + 11558.911 0.998437 100412 640.00 + 11558.911 0.998633 100412 731.43 + 11567.103 0.998828 100451 853.33 + 11567.103 0.999023 100451 1024.00 + 11575.295 0.999121 100475 1137.78 + 11575.295 0.999219 100475 1280.00 + 11583.487 0.999316 100505 1462.86 + 11583.487 0.999414 100505 1706.67 + 11583.487 0.999512 100505 2048.00 + 11583.487 0.999561 100505 2275.56 + 11591.679 0.999609 100527 2560.00 + 11591.679 0.999658 100527 2925.71 + 11591.679 0.999707 100527 3413.33 + 11591.679 0.999756 100527 4096.00 + 11591.679 0.999780 100527 4551.11 + 11591.679 0.999805 100527 5120.00 + 11599.871 0.999829 100542 5851.43 + 11599.871 0.999854 100542 6826.67 + 11599.871 0.999878 100542 8192.00 + 11599.871 0.999890 100542 9102.22 + 11599.871 0.999902 100542 10240.00 + 11599.871 0.999915 100542 11702.86 + 11599.871 0.999927 100542 13653.33 + 11599.871 0.999939 100542 16384.00 + 11599.871 0.999945 100542 18204.44 + 11599.871 0.999951 100542 20480.00 + 11599.871 0.999957 100542 23405.71 + 11599.871 0.999963 100542 27306.67 + 11599.871 0.999969 100542 32768.00 + 11608.063 0.999973 100545 36408.89 + 11608.063 1.000000 100545 inf +#[Mean = 3077.713, StdDeviation = 3799.173] +#[Max = 11599.872, Total count = 100545] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 124008 requests in 1.00m, 10.32MB read + Socket errors: connect 0, read 0, write 0, timeout 250 + Non-2xx or 3xx responses: 107011 +Requests/sec: 2066.77 +Transfer/sec: 176.06KB diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/put/1500_c-250.txt b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/put/1500_c-250.txt new file mode 100644 index 000000000..42d13d8c0 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/put/1500_c-250.txt @@ -0,0 +1,107 @@ +[andrey@archlinux wrk2]$ ./wrk -c 250 -t 1 -d 40 -R 1500 -L -s ~/andrey/scripts/put.lua http://localhost:8081/v0/entity/ +Running 40s test @ http://localhost:8081/v0/entity/ + 1 threads and 250 connections + Thread calibration: mean lat.: 92.795ms, rate sampling interval: 376ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 65.29ms 40.77ms 331.52ms 78.34% + Req/Sec 1.50k 246.17 2.07k 73.33% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 57.02ms + 75.000% 80.13ms + 90.000% 111.93ms + 99.000% 219.90ms + 99.900% 315.65ms + 99.990% 325.89ms + 99.999% 331.77ms +100.000% 331.77ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 1.526 0.000000 1 1.00 + 25.071 0.100000 4311 1.11 + 35.071 0.200000 8627 1.25 + 43.583 0.300000 12944 1.43 + 50.079 0.400000 17248 1.67 + 57.023 0.500000 21552 2.00 + 60.639 0.550000 23705 2.22 + 64.671 0.600000 25862 2.50 + 68.991 0.650000 28036 2.86 + 74.047 0.700000 30189 3.33 + 80.127 0.750000 32323 4.00 + 83.327 0.775000 33411 4.44 + 87.103 0.800000 34502 5.00 + 92.031 0.825000 35566 5.71 + 97.663 0.850000 36633 6.67 + 105.151 0.875000 37714 8.00 + 108.479 0.887500 38250 8.89 + 111.999 0.900000 38790 10.00 + 120.063 0.912500 39329 11.43 + 126.463 0.925000 39872 13.33 + 133.247 0.937500 40411 16.00 + 136.575 0.943750 40685 17.78 + 139.647 0.950000 40944 20.00 + 149.375 0.956250 41216 22.86 + 157.055 0.962500 41483 26.67 + 161.023 0.968750 41752 32.00 + 169.855 0.971875 41887 35.56 + 176.895 0.975000 42026 40.00 + 184.575 0.978125 42155 45.71 + 190.847 0.981250 42293 53.33 + 195.327 0.984375 42425 64.00 + 202.879 0.985938 42491 71.11 + 206.207 0.987500 42561 80.00 + 215.039 0.989062 42626 91.43 + 222.207 0.990625 42693 106.67 + 232.191 0.992188 42762 128.00 + 238.591 0.992969 42794 142.22 + 240.511 0.993750 42829 160.00 + 242.303 0.994531 42862 182.86 + 243.199 0.995313 42895 213.33 + 246.911 0.996094 42929 256.00 + 259.327 0.996484 42946 284.44 + 290.303 0.996875 42963 320.00 + 292.351 0.997266 42981 365.71 + 293.887 0.997656 42998 426.67 + 299.007 0.998047 43013 512.00 + 302.079 0.998242 43023 568.89 + 303.871 0.998437 43030 640.00 + 305.663 0.998633 43039 731.43 + 313.087 0.998828 43047 853.33 + 315.903 0.999023 43055 1024.00 + 317.951 0.999121 43060 1137.78 + 321.279 0.999219 43064 1280.00 + 321.791 0.999316 43069 1462.86 + 322.815 0.999414 43073 1706.67 + 323.583 0.999512 43076 2048.00 + 324.095 0.999561 43079 2275.56 + 324.607 0.999609 43084 2560.00 + 324.607 0.999658 43084 2925.71 + 324.863 0.999707 43085 3413.33 + 325.119 0.999756 43088 4096.00 + 325.119 0.999780 43088 4551.11 + 325.375 0.999805 43089 5120.00 + 325.631 0.999829 43092 5851.43 + 325.631 0.999854 43092 6826.67 + 325.631 0.999878 43092 8192.00 + 325.887 0.999890 43093 9102.22 + 325.887 0.999902 43093 10240.00 + 326.143 0.999915 43094 11702.86 + 326.143 0.999927 43094 13653.33 + 329.983 0.999939 43096 16384.00 + 329.983 0.999945 43096 18204.44 + 329.983 0.999951 43096 20480.00 + 329.983 0.999957 43096 23405.71 + 329.983 0.999963 43096 27306.67 + 329.983 0.999969 43096 32768.00 + 329.983 0.999973 43096 36408.89 + 329.983 0.999976 43096 40960.00 + 331.775 0.999979 43097 46811.43 + 331.775 1.000000 43097 inf +#[Mean = 65.291, StdDeviation = 40.773] +#[Max = 331.520, Total count = 43097] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 58478 requests in 40.00s, 3.74MB read +Requests/sec: 1461.83 +Transfer/sec: 95.65KB diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/put/1800_c-250.txt b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/put/1800_c-250.txt new file mode 100644 index 000000000..62d2d8abf --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/put/1800_c-250.txt @@ -0,0 +1,107 @@ +[andrey@archlinux wrk2]$ ./wrk -c 250 -t 1 -d 40 -R 1800 -L -s ~/andrey/scripts/put.lua http://localhost:8081/v0/entity/ +Running 40s test @ http://localhost:8081/v0/entity/ + 1 threads and 250 connections + Thread calibration: mean lat.: 26.602ms, rate sampling interval: 96ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 314.87ms 744.68ms 4.49s 89.76% + Req/Sec 1.55k 702.95 3.63k 67.93% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 42.65ms + 75.000% 100.86ms + 90.000% 1.11s + 99.000% 3.63s + 99.900% 4.22s + 99.990% 4.39s + 99.999% 4.49s +100.000% 4.49s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 1.342 0.000000 1 1.00 + 13.679 0.100000 4391 1.11 + 20.239 0.200000 8787 1.25 + 26.543 0.300000 13186 1.43 + 33.695 0.400000 17569 1.67 + 42.655 0.500000 21957 2.00 + 48.575 0.550000 24163 2.22 + 56.063 0.600000 26346 2.50 + 66.111 0.650000 28546 2.86 + 79.871 0.700000 30737 3.33 + 100.863 0.750000 32931 4.00 + 117.055 0.775000 34031 4.44 + 149.119 0.800000 35127 5.00 + 222.719 0.825000 36226 5.71 + 382.975 0.850000 37322 6.67 + 661.503 0.875000 38420 8.00 + 857.087 0.887500 38969 8.89 + 1107.967 0.900000 39518 10.00 + 1390.591 0.912500 40071 11.43 + 1626.111 0.925000 40617 13.33 + 1883.135 0.937500 41166 16.00 + 2040.831 0.943750 41440 17.78 + 2215.935 0.950000 41718 20.00 + 2400.255 0.956250 41991 22.86 + 2553.855 0.962500 42265 26.67 + 2711.551 0.968750 42536 32.00 + 2836.479 0.971875 42674 35.56 + 2977.791 0.975000 42812 40.00 + 3143.679 0.978125 42951 45.71 + 3229.695 0.981250 43088 53.33 + 3348.479 0.984375 43222 64.00 + 3399.679 0.985938 43291 71.11 + 3481.599 0.987500 43362 80.00 + 3575.807 0.989062 43428 91.43 + 3651.583 0.990625 43498 106.67 + 3710.975 0.992188 43566 128.00 + 3743.743 0.992969 43600 142.22 + 3813.375 0.993750 43634 160.00 + 3928.063 0.994531 43669 182.86 + 3993.599 0.995313 43703 213.33 + 4042.751 0.996094 43737 256.00 + 4057.087 0.996484 43754 284.44 + 4073.471 0.996875 43771 320.00 + 4093.951 0.997266 43790 365.71 + 4114.431 0.997656 43808 426.67 + 4136.959 0.998047 43824 512.00 + 4147.199 0.998242 43831 568.89 + 4159.487 0.998437 43840 640.00 + 4179.967 0.998633 43848 731.43 + 4198.399 0.998828 43858 853.33 + 4227.071 0.999023 43866 1024.00 + 4239.359 0.999121 43870 1137.78 + 4247.551 0.999219 43874 1280.00 + 4263.935 0.999316 43878 1462.86 + 4280.319 0.999414 43883 1706.67 + 4292.607 0.999512 43889 2048.00 + 4292.607 0.999561 43889 2275.56 + 4308.991 0.999609 43891 2560.00 + 4313.087 0.999658 43893 2925.71 + 4333.567 0.999707 43897 3413.33 + 4337.663 0.999756 43898 4096.00 + 4358.143 0.999780 43900 4551.11 + 4358.143 0.999805 43900 5120.00 + 4362.239 0.999829 43901 5851.43 + 4370.431 0.999854 43902 6826.67 + 4382.719 0.999878 43903 8192.00 + 4386.815 0.999890 43904 9102.22 + 4386.815 0.999902 43904 10240.00 + 4390.911 0.999915 43905 11702.86 + 4390.911 0.999927 43905 13653.33 + 4407.295 0.999939 43906 16384.00 + 4407.295 0.999945 43906 18204.44 + 4407.295 0.999951 43906 20480.00 + 4411.391 0.999957 43907 23405.71 + 4411.391 0.999963 43907 27306.67 + 4411.391 0.999969 43907 32768.00 + 4411.391 0.999973 43907 36408.89 + 4411.391 0.999976 43907 40960.00 + 4493.311 0.999979 43908 46811.43 + 4493.311 1.000000 43908 inf +#[Mean = 314.870, StdDeviation = 744.678] +#[Max = 4489.216, Total count = 43908] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 63120 requests in 40.00s, 4.03MB read +Requests/sec: 1577.97 +Transfer/sec: 103.25KB diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/put/750_c-250.txt b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/put/750_c-250.txt new file mode 100644 index 000000000..d3e154aad --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/put/750_c-250.txt @@ -0,0 +1,102 @@ +[andrey@archlinux wrk2]$ ./wrk -c 250 -t 1 -d 40 -R 750 -L -s ~/andrey/scripts/put.lua http://localhost:8081/v0/entity/ +Running 40s test @ http://localhost:8081/v0/entity/ + 1 threads and 250 connections + Thread calibration: mean lat.: 20.858ms, rate sampling interval: 75ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 21.81ms 11.94ms 110.08ms 70.83% + Req/Sec 755.07 161.26 1.41k 71.58% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 20.11ms + 75.000% 28.33ms + 90.000% 37.22ms + 99.000% 59.17ms + 99.900% 78.53ms + 99.990% 101.44ms + 99.999% 110.14ms +100.000% 110.14ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 1.388 0.000000 1 1.00 + 8.047 0.100000 2155 1.11 + 11.719 0.200000 4310 1.25 + 14.639 0.300000 6464 1.43 + 17.295 0.400000 8621 1.67 + 20.111 0.500000 10775 2.00 + 21.487 0.550000 11856 2.22 + 22.975 0.600000 12926 2.50 + 24.591 0.650000 14005 2.86 + 26.383 0.700000 15090 3.33 + 28.335 0.750000 16156 4.00 + 29.535 0.775000 16693 4.44 + 30.735 0.800000 17232 5.00 + 31.999 0.825000 17771 5.71 + 33.567 0.850000 18321 6.67 + 35.199 0.875000 18848 8.00 + 36.191 0.887500 19120 8.89 + 37.215 0.900000 19393 10.00 + 38.367 0.912500 19663 11.43 + 39.903 0.925000 19927 13.33 + 41.567 0.937500 20197 16.00 + 42.527 0.943750 20328 17.78 + 43.583 0.950000 20463 20.00 + 44.767 0.956250 20597 22.86 + 46.239 0.962500 20735 26.67 + 47.711 0.968750 20868 32.00 + 48.927 0.971875 20934 35.56 + 50.207 0.975000 21001 40.00 + 51.455 0.978125 21068 45.71 + 53.087 0.981250 21136 53.33 + 54.911 0.984375 21204 64.00 + 55.935 0.985938 21237 71.11 + 57.087 0.987500 21272 80.00 + 58.335 0.989062 21304 91.43 + 59.711 0.990625 21338 106.67 + 61.215 0.992188 21371 128.00 + 62.079 0.992969 21388 142.22 + 63.775 0.993750 21405 160.00 + 64.863 0.994531 21422 182.86 + 66.495 0.995313 21440 213.33 + 67.903 0.996094 21455 256.00 + 68.799 0.996484 21465 284.44 + 69.631 0.996875 21472 320.00 + 70.719 0.997266 21481 365.71 + 72.191 0.997656 21490 426.67 + 73.407 0.998047 21497 512.00 + 74.431 0.998242 21502 568.89 + 75.199 0.998437 21506 640.00 + 76.415 0.998633 21510 731.43 + 77.631 0.998828 21514 853.33 + 78.719 0.999023 21518 1024.00 + 79.871 0.999121 21521 1137.78 + 80.703 0.999219 21523 1280.00 + 81.535 0.999316 21525 1462.86 + 83.263 0.999414 21527 1706.67 + 84.671 0.999512 21529 2048.00 + 85.055 0.999561 21530 2275.56 + 87.295 0.999609 21531 2560.00 + 88.895 0.999658 21532 2925.71 + 89.855 0.999707 21533 3413.33 + 90.239 0.999756 21534 4096.00 + 93.631 0.999780 21535 4551.11 + 93.631 0.999805 21535 5120.00 + 96.255 0.999829 21536 5851.43 + 96.255 0.999854 21536 6826.67 + 101.439 0.999878 21537 8192.00 + 101.439 0.999890 21537 9102.22 + 101.439 0.999902 21537 10240.00 + 103.167 0.999915 21538 11702.86 + 103.167 0.999927 21538 13653.33 + 103.167 0.999939 21538 16384.00 + 103.167 0.999945 21538 18204.44 + 103.167 0.999951 21538 20480.00 + 110.143 0.999957 21539 23405.71 + 110.143 1.000000 21539 inf +#[Mean = 21.807, StdDeviation = 11.938] +#[Max = 110.080, Total count = 21539] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 29624 requests in 40.01s, 1.89MB read +Requests/sec: 740.41 +Transfer/sec: 48.44KB diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/put/750_c-250_old.txt b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/put/750_c-250_old.txt new file mode 100644 index 000000000..524dfe550 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/old_cmp/put/750_c-250_old.txt @@ -0,0 +1,102 @@ +[andrey@archlinux wrk2]$ ./wrk -d 40 -t 1 -c 250 -s ~/andrey/scripts/put.lua -L -R 750 http://localhost:8080/v0/entity/ +Running 40s test @ http://localhost:8080/v0/entity/ + 1 threads and 250 connections + Thread calibration: mean lat.: 26.719ms, rate sampling interval: 104ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 34.03ms 25.09ms 169.47ms 77.28% + Req/Sec 754.59 148.31 1.16k 69.12% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 28.58ms + 75.000% 43.81ms + 90.000% 65.02ms + 99.000% 132.61ms + 99.900% 155.52ms + 99.990% 166.14ms + 99.999% 169.60ms +100.000% 169.60ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.834 0.000000 1 1.00 + 8.807 0.100000 2158 1.11 + 14.871 0.200000 4317 1.25 + 19.551 0.300000 6474 1.43 + 24.031 0.400000 8638 1.67 + 28.575 0.500000 10790 2.00 + 30.991 0.550000 11874 2.22 + 33.727 0.600000 12951 2.50 + 36.703 0.650000 14029 2.86 + 40.063 0.700000 15106 3.33 + 43.807 0.750000 16196 4.00 + 45.759 0.775000 16727 4.44 + 48.287 0.800000 17265 5.00 + 51.071 0.825000 17809 5.71 + 54.783 0.850000 18344 6.67 + 59.327 0.875000 18883 8.00 + 61.983 0.887500 19155 8.89 + 65.023 0.900000 19422 10.00 + 68.735 0.912500 19693 11.43 + 73.087 0.925000 19964 13.33 + 77.695 0.937500 20232 16.00 + 80.319 0.943750 20370 17.78 + 83.199 0.950000 20502 20.00 + 87.039 0.956250 20636 22.86 + 91.135 0.962500 20772 26.67 + 95.359 0.968750 20906 32.00 + 98.303 0.971875 20975 35.56 + 100.735 0.975000 21042 40.00 + 104.319 0.978125 21108 45.71 + 109.823 0.981250 21176 53.33 + 117.439 0.984375 21244 64.00 + 121.087 0.985938 21277 71.11 + 127.679 0.987500 21311 80.00 + 131.007 0.989062 21346 91.43 + 134.271 0.990625 21378 106.67 + 139.007 0.992188 21412 128.00 + 140.543 0.992969 21431 142.22 + 142.463 0.993750 21449 160.00 + 143.871 0.994531 21462 182.86 + 145.663 0.995313 21479 213.33 + 147.327 0.996094 21496 256.00 + 148.223 0.996484 21506 284.44 + 149.503 0.996875 21514 320.00 + 150.143 0.997266 21522 365.71 + 151.039 0.997656 21530 426.67 + 152.319 0.998047 21538 512.00 + 153.215 0.998242 21544 568.89 + 153.983 0.998437 21548 640.00 + 154.495 0.998633 21551 731.43 + 155.391 0.998828 21556 853.33 + 155.647 0.999023 21559 1024.00 + 156.287 0.999121 21562 1137.78 + 156.927 0.999219 21564 1280.00 + 157.695 0.999316 21567 1462.86 + 157.823 0.999414 21568 1706.67 + 160.383 0.999512 21570 2048.00 + 160.639 0.999561 21571 2275.56 + 161.535 0.999609 21572 2560.00 + 162.303 0.999658 21574 2925.71 + 162.303 0.999707 21574 3413.33 + 163.199 0.999756 21575 4096.00 + 163.967 0.999780 21576 4551.11 + 163.967 0.999805 21576 5120.00 + 166.015 0.999829 21577 5851.43 + 166.015 0.999854 21577 6826.67 + 166.143 0.999878 21578 8192.00 + 166.143 0.999890 21578 9102.22 + 166.143 0.999902 21578 10240.00 + 168.063 0.999915 21579 11702.86 + 168.063 0.999927 21579 13653.33 + 168.063 0.999939 21579 16384.00 + 168.063 0.999945 21579 18204.44 + 168.063 0.999951 21579 20480.00 + 169.599 0.999957 21580 23405.71 + 169.599 1.000000 21580 inf +#[Mean = 34.026, StdDeviation = 25.088] +#[Max = 169.472, Total count = 21580] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 29639 requests in 40.00s, 1.89MB read +Requests/sec: 740.89 +Transfer/sec: 48.48KB diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_alloc.html b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_alloc.html new file mode 100644 index 000000000..3772eb8f3 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_alloc.html @@ -0,0 +1,3489 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_alloct.html b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_alloct.html new file mode 100644 index 000000000..f813993c1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_alloct.html @@ -0,0 +1,8200 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_cpu.html b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_cpu.html new file mode 100644 index 000000000..3c7041fd7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_cpu.html @@ -0,0 +1,7277 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_cput.html b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_cput.html new file mode 100644 index 000000000..07d6c1a4a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_cput.html @@ -0,0 +1,21696 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_lock.html b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_lock.html new file mode 100644 index 000000000..cee3e8c85 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_lock.html @@ -0,0 +1,1188 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_lockt.html b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_lockt.html new file mode 100644 index 000000000..6752db472 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/get/get_lockt.html @@ -0,0 +1,5348 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_alloc.html b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_alloc.html new file mode 100644 index 000000000..1bf92ec5c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_alloc.html @@ -0,0 +1,3359 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_alloct.html b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_alloct.html new file mode 100644 index 000000000..209dcb9ae --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_alloct.html @@ -0,0 +1,2562 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_cpu.html b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_cpu.html new file mode 100644 index 000000000..c13a10ec6 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_cpu.html @@ -0,0 +1,8060 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_cput.html b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_cput.html new file mode 100644 index 000000000..cbd6cba75 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_cput.html @@ -0,0 +1,20496 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_lock.html b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_lock.html new file mode 100644 index 000000000..721b30119 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_lock.html @@ -0,0 +1,1132 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_lockt.html b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_lockt.html new file mode 100644 index 000000000..d057ed2a4 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/profiling/put/put_lockt.html @@ -0,0 +1,4823 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ 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 new file mode 100644 index 000000000..46e08a4a8 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/report-5-Cheshev.md @@ -0,0 +1,100 @@ +## Параметры системы: +* Treshhold bytes = 1 mb +* Размер очереди = 400 +* Max heap size = 128 mb +* Тестируем кластер из 4-х нод +* Параметр ack = 3, from = 4 +* Количество потоков = 6 + +## Сравнение с предыдущей реализацией + +* GET (-c 250 -t 1 -R 1100) + ![](./screens/get_cmp.png) + Видно, что новая реализация по сравнению с предыдущей при 90м персентиле выдает задержку меньше примерно в два раз. + Однако, в данном эксперименте также видно, что при 99.99м персентиле ситуация получается обратной. + +* PUT (-c 250 -t 1 -R 750) + ![](./screens/put_cmp.png) + При 90м, 99м, 99.9м персентилях задержка при новой реализация меньше примерно в два раза. Наблюдает значительное улучшение производительности. + +И get и put запросы выдали меньшую задержку, чем ранее, но для get запросов после 99го персентиля замечен тренд резкого увеличения задержки. + +## Определим рабочую нагрузку + +* GET + ![](./screens/get.png) + Видно, что при rps > 1500 задержка начинает расти намного стремительней. + Так, замечена разница между 99м персентилями при: + * rps1 = 1100 и rps2 = 1500 : в 2.1 раза; + * rps2 = 1500 и rps3 = 1800 : в 5.14 раз. + +* PUT + ![](./screens/put.png) + Разница при между 99м персентилями при: + * rps1 = 750 и rps2 = 1500 : в 3,7 раза; + * rps2 = 1500 и rps3 = 1800 : в 16,59 раз. + +Таким образом систему целесообразно тестировать для 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 потока); + + +## Проведем профилирование + +* ALLOC + * 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 запросом. + +* CPU + * GET + ![](./screens/img_2.png) + ![](./screens/img_3.png) + * PUT + ![](./screens/img_8.png) + ![](./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 + ![](./screens/img_4.png) + ![](./screens/img_5.png) + * PUT + ![](./screens/img_10.png) + ![](./screens/img_11.png) + * Основные блокировки происходят на RemoteCall потоках в ожидании получения результата с удаленной ноды. + * Distributor и Sender потоки по блокировкам занимают примерно по одному проценту всех блокировок. + * Internal и LocalCall потоки по блокировкам занимают меньше одного процента всех блокировок. + +### Итого +Текущая асинхронная реализация действительно показала лучший результат, а именно меньшую задержку при как при get, так и при put запросах. +За счет использования CompletableFuture появилось намного больше используемых потоков, обеспечивающих наилучшую производительность системы. +Значительно возросло количество блокировок. \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/get.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/get.png new file mode 100644 index 000000000..3fd6b29d2 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/get.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/get_cmp.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/get_cmp.png new file mode 100644 index 000000000..1216b29d2 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/get_cmp.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img.png new file mode 100644 index 000000000..8451c7694 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_1.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_1.png new file mode 100644 index 000000000..d25c3cb39 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_1.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_10.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_10.png new file mode 100644 index 000000000..e8f544d7c Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_10.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_11.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_11.png new file mode 100644 index 000000000..6eaac7864 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_11.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_2.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_2.png new file mode 100644 index 000000000..4cff5055b Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_2.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_3.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_3.png new file mode 100644 index 000000000..c62bb1ad0 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_3.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_4.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_4.png new file mode 100644 index 000000000..4bcf5fb4a Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_5.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_5.png new file mode 100644 index 000000000..1b376241c Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_5.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_6.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_6.png new file mode 100644 index 000000000..34649d7a2 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_6.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_7.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_7.png new file mode 100644 index 000000000..ced206645 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_7.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_8.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_8.png new file mode 100644 index 000000000..4d2b32a12 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_8.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_9.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_9.png new file mode 100644 index 000000000..82d5714e5 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/img_9.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/put.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/put.png new file mode 100644 index 000000000..14b46a985 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/put.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/put_cmp.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/put_cmp.png new file mode 100644 index 000000000..0627c36ca Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/screens/put_cmp.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/visualvm/1.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/visualvm/1.png new file mode 100644 index 000000000..b27a5c49b Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/visualvm/1.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/visualvm/2.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/visualvm/2.png new file mode 100644 index 000000000..66f7b9c87 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/visualvm/2.png differ diff --git a/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/visualvm/3.png b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/visualvm/3.png new file mode 100644 index 000000000..2626165de Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/andreycheshev/lab5/visualvm/3.png differ 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 diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/CustomHttpSession.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/CustomHttpSession.java new file mode 100644 index 000000000..0ac3fb3ad --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/CustomHttpSession.java @@ -0,0 +1,50 @@ +package ru.vk.itmo.test.dariasupriadkina; + +import one.nio.http.HttpServer; +import one.nio.http.HttpSession; +import one.nio.net.Socket; +import one.nio.util.ByteArrayBuilder; +import ru.vk.itmo.test.dariasupriadkina.dao.ExtendedEntry; + +import java.io.IOException; +import java.lang.foreign.MemorySegment; +import java.util.Iterator; + +public class CustomHttpSession extends HttpSession { + + private Iterator> iterator; + private ByteArrayBuilder builder; + + public CustomHttpSession(Socket socket, HttpServer server) { + super(socket, server); + } + + @Override + protected void processWrite() throws Exception { + super.processWrite(); + + sendNextChunkScope(builder == null ? new ByteArrayBuilder() : builder); + } + + public void streaming(Iterator> iterator) throws IOException { + this.iterator = iterator; + this.builder = new ByteArrayBuilder(); + + write(EntryChunkUtils.HEADER_BYTES, 0, EntryChunkUtils.HEADER_BYTES.length); + sendNextChunkScope(builder); + } + + private void sendNextChunkScope(ByteArrayBuilder builder) throws IOException { + while (iterator.hasNext() && queueHead == null) { + ExtendedEntry ee = iterator.next(); + EntryChunkUtils.getEntryByteChunk(ee, builder); + write(builder.buffer(), 0, builder.length()); + builder.setLength(0); + } + if (!iterator.hasNext()) { + write(EntryChunkUtils.LAST_BYTES, 0, EntryChunkUtils.LAST_BYTES.length); + } + scheduleClose(); + } + +} diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/EntryChunkUtils.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/EntryChunkUtils.java new file mode 100644 index 000000000..af697d633 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/EntryChunkUtils.java @@ -0,0 +1,53 @@ +package ru.vk.itmo.test.dariasupriadkina; + +import one.nio.util.ByteArrayBuilder; +import ru.vk.itmo.dao.Entry; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; + +public final class EntryChunkUtils { + + private EntryChunkUtils() { + } + + static final byte[] HEADER_BYTES = + """ + HTTP/1.1 200 OK\r + Content-Type: text/plain\r + Transfer-Encoding: chunked\r + Connection: keep-alive\r + \r + """.getBytes(StandardCharsets.UTF_8); + static final byte[] LAST_BYTES = "0\r\n\r\n".getBytes(StandardCharsets.UTF_8); + private static final byte[] DELIMITER_BYTES = "\n".getBytes(StandardCharsets.UTF_8); + private static final byte[] CLRF_BYTES = "\r\n".getBytes(StandardCharsets.UTF_8); + + public static void getEntryByteChunk(Entry ee, ByteArrayBuilder bb) { + byte[] key = getEntryKeyChunk(ee); + byte[] value = getEntryValueChunk(ee); + byte[] kvLength = getKVLengthChunk(ee); + + bb.append(kvLength, 0, kvLength.length); + bb.append(CLRF_BYTES, 0, CLRF_BYTES.length); + bb.append(key, 0, key.length); + bb.append(DELIMITER_BYTES, 0, DELIMITER_BYTES.length); + bb.append(value, 0, value.length); + bb.append(CLRF_BYTES, 0, CLRF_BYTES.length); + } + + public static byte[] getEntryKeyChunk(Entry entry) { + return entry.key().toArray(ValueLayout.JAVA_BYTE); + } + + public static byte[] getEntryValueChunk(Entry entry) { + return entry.value().toArray(ValueLayout.JAVA_BYTE); + } + + public static byte[] getKVLengthChunk(Entry entry) { + return Long.toHexString((entry.value().byteSize() + + entry.key().byteSize() + DELIMITER_BYTES.length)).getBytes(StandardCharsets.UTF_8); + } + +} diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/HeaderConstraints.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/HeaderConstraints.java index 8c81842c0..fc4d99a39 100644 --- a/src/main/java/ru/vk/itmo/test/dariasupriadkina/HeaderConstraints.java +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/HeaderConstraints.java @@ -4,7 +4,8 @@ public final class HeaderConstraints { public static final String TIMESTAMP_MILLIS_HEADER = "X-TIMESTAMP-MILLIS: "; public static final String TIMESTAMP_MILLIS_HEADER_NORMAL = "X-TIMESTAMP-MILLIS"; - public static final String FROM_HEADER = "X-FROM"; + public static final String FROM_HEADER_NORMAL = "X-FROM"; + public static final String FROM_HEADER = "X-FROM: "; private HeaderConstraints() { } diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/NodeThreadPoolExecutor.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/NodeThreadPoolExecutor.java deleted file mode 100644 index 2a1953732..000000000 --- a/src/main/java/ru/vk/itmo/test/dariasupriadkina/NodeThreadPoolExecutor.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.vk.itmo.test.dariasupriadkina; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import ru.vk.itmo.test.dariasupriadkina.workers.WorkerConfig; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -public class NodeThreadPoolExecutor extends ThreadPoolExecutor { - - private static final Logger logger = LoggerFactory.getLogger(NodeThreadPoolExecutor.class.getName()); - private final int shutdownTimeoutSec; - - public NodeThreadPoolExecutor(int corePoolSize, int maximumPoolSize, BlockingQueue workQueue, - ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler, - int shutdownTimeoutSec) { - super(corePoolSize, maximumPoolSize, WorkerConfig.KEEP_ALIVE_TIME, - WorkerConfig.KEEP_ALIVE_TIME_SECONDS, workQueue, threadFactory, rejectedExecutionHandler); - this.shutdownTimeoutSec = shutdownTimeoutSec; - } - - // Метод из документации на ExecutorService - public void shutdownAndAwaitTermination() { - this.shutdown(); // Disable new tasks from being submitted - try { - // Wait a while for existing tasks to terminate - if (!awaitTermination(shutdownTimeoutSec, TimeUnit.SECONDS)) { - shutdownNow(); // Cancel currently executing tasks - // Wait a while for tasks to respond to being cancelled - if (!this.awaitTermination(shutdownTimeoutSec, TimeUnit.SECONDS)) { - logger.error("Pool did not terminate"); - } - } - } catch (InterruptedException ie) { - // (Re-)Cancel if current thread also interrupted - shutdownNow(); - // Preserve interrupt status - Thread.currentThread().interrupt(); - } - } -} diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/SelfRequestHandler.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/SelfRequestHandler.java index 86cde0b25..4d7262d2c 100644 --- a/src/main/java/ru/vk/itmo/test/dariasupriadkina/SelfRequestHandler.java +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/SelfRequestHandler.java @@ -1,5 +1,6 @@ package ru.vk.itmo.test.dariasupriadkina; +import one.nio.http.HttpSession; import one.nio.http.Request; import one.nio.http.Response; import ru.vk.itmo.dao.BaseEntry; @@ -7,14 +8,18 @@ import ru.vk.itmo.test.dariasupriadkina.dao.ExtendedBaseEntry; import ru.vk.itmo.test.dariasupriadkina.dao.ExtendedEntry; +import java.io.IOException; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.concurrent.CompletableFuture; public class SelfRequestHandler { + private static final String TIMESTAMP_MILLIS_HEADER = "X-TIMESTAMP-MILLIS: "; private final Dao> dao; private final Utils utils; - private static final String TIMESTAMP_MILLIS_HEADER = "X-TIMESTAMP-MILLIS: "; public SelfRequestHandler(Dao> dao, Utils utils) { this.dao = dao; @@ -23,14 +28,22 @@ public SelfRequestHandler(Dao> dao, public Response handleRequest(Request request) { String id = utils.getIdParameter(request); - return switch (request.getMethodName()) { - case "GET" -> get(id); - case "PUT" -> put(id, request); - case "DELETE" -> delete(id); + return switch (request.getMethod()) { + case Request.METHOD_GET -> get(id); + case Request.METHOD_PUT -> put(id, request); + case Request.METHOD_DELETE -> delete(id); default -> new Response(Response.NOT_FOUND, Response.EMPTY); }; } + public CompletableFuture handleAsyncRequest(Request request) { + return composeFuture(handleRequest(request)); + } + + private CompletableFuture composeFuture(Response response) { + return CompletableFuture.completedFuture(response); + } + public Response get(String id) { try { if (id == null || id.isEmpty()) { @@ -86,4 +99,26 @@ public Response delete(String id) { return new Response(Response.INTERNAL_ERROR, Response.EMPTY); } } + + public void handleRange(Request request, HttpSession session) throws IOException { + String start = request.getParameter("start="); + String end = request.getParameter("end="); + + if (start == null + || request.getMethod() != Request.METHOD_GET + || start.isEmpty() + || (end != null && end.isEmpty())) { + session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); + return; + } + + Iterator> it = dao.get( + utils.convertByteArrToMemorySegment(start.getBytes(StandardCharsets.UTF_8)), + end == null ? null : + utils.convertByteArrToMemorySegment(end.getBytes(StandardCharsets.UTF_8)) + ); + + ((CustomHttpSession) session).streaming(it); + } + } diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/Server.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/Server.java index dc5108bf6..5d5857712 100644 --- a/src/main/java/ru/vk/itmo/test/dariasupriadkina/Server.java +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/Server.java @@ -6,7 +6,9 @@ import one.nio.http.HttpSession; import one.nio.http.Request; import one.nio.http.Response; +import one.nio.net.Socket; import one.nio.server.AcceptorConfig; +import one.nio.server.RejectedSessionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.vk.itmo.ServiceConfig; @@ -25,27 +27,28 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; 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.AtomicInteger; import static ru.vk.itmo.test.dariasupriadkina.HeaderConstraints.FROM_HEADER; +import static ru.vk.itmo.test.dariasupriadkina.HeaderConstraints.FROM_HEADER_NORMAL; import static ru.vk.itmo.test.dariasupriadkina.HeaderConstraints.TIMESTAMP_MILLIS_HEADER; import static ru.vk.itmo.test.dariasupriadkina.HeaderConstraints.TIMESTAMP_MILLIS_HEADER_NORMAL; public class Server extends HttpServer { private static final Logger logger = LoggerFactory.getLogger(Server.class.getName()); + private static final byte[] EMPTY_BYTE_ARRAY = new byte[]{}; private final ExecutorService workerExecutor; private final Set permittedMethods = Set.of(Request.METHOD_GET, Request.METHOD_PUT, Request.METHOD_DELETE); private final String selfUrl; private final ShardingPolicy shardingPolicy; private final HttpClient httpClient; - private static final int REQUEST_TIMEOUT_SEC = 2; private final List clusterUrls; private final Utils utils; private final SelfRequestHandler selfHandler; @@ -65,14 +68,11 @@ public Server(ServiceConfig config, Dao { try { - if (!permittedMethods.contains(request.getMethod())) { + if (request.getURI().startsWith(Utils.LOCAL_STREAM_ENTRY_PREFIX)) { + selfHandler.handleRange(request, session); + return; + } + Map ackFrom = getFromAndAck(request); + int from = ackFrom.get("from"); + int ack = ackFrom.get("ack"); + if (!permittedMethods.contains(request.getMethod()) + || checkBadRequest(ack, from, request.getMethod())) { handleDefault(request, session); + return; } - if (request.getHeader(FROM_HEADER) == null) { + if (request.getHeader(FROM_HEADER_NORMAL) == null) { request.addHeader(FROM_HEADER + selfUrl); - - Map ackFrom = getFromAndAck(request); - int from = ackFrom.get("from"); - int ack = ackFrom.get("ack"); - - Response response = mergeResponses(broadcast( - shardingPolicy.getNodesById(utils.getIdParameter(request), from), request, ack), - ack, from); - - session.sendResponse(response); - + collectResponsesCallback( + broadcast( + shardingPolicy.getNodesById(utils.getIdParameter(request), from), + request + ), ack, from, session); } else { Response resp = selfHandler.handleRequest(request); checkTimestampHeaderExistenceAndSet(resp); session.sendResponse(resp); } - } catch (Exception e) { - logger.error("Unexpected error", e); - try { - if (e.getClass() == HttpException.class) { - session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); - } else { - session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); - } - } catch (IOException ex) { - logger.error("Failed to send error response", e); - session.close(); - } + solveUnexpectedError(e, session); } }); } catch (RejectedExecutionException e) { logger.error("Service is unavailable", e); session.sendResponse(new Response(Response.SERVICE_UNAVAILABLE, Response.EMPTY)); + } + } + + @Override + public HttpSession createSession(Socket socket) throws RejectedSessionException { + return new CustomHttpSession(socket, this); + } + private void solveUnexpectedError(Exception e, HttpSession session) { + logger.error("Unexpected error", e); + try { + if (e.getClass() == HttpException.class) { + session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); + } else { + session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); + } + } catch (Exception exception) { + logger.error("Failed to send error response", exception); + session.scheduleClose(); } } - private List broadcast(List nodes, Request request, int ack) { - int internalAck = ack; - List responses = new ArrayList<>(ack); - Response response; + private boolean checkBadRequest(int ack, int from, int method) { + return !permittedMethods.contains(method) || ack > from || ack <= 0 || from > clusterUrls.size(); + } + + private List> broadcast(List nodes, Request request) { + List> futureResponses = new ArrayList<>(nodes.size()); + CompletableFuture response; if (nodes.contains(selfUrl)) { - response = selfHandler.handleRequest(request); - checkTimestampHeaderExistenceAndSet(response); - responses.add(response); + response = selfHandler.handleAsyncRequest(request); + futureResponses.add(response); nodes.remove(selfUrl); - if (--internalAck == 0) { - return responses; - } } for (String node : nodes) { response = handleProxy(utils.getEntryUrl(node, utils.getIdParameter(request)), request); - if (response.getStatus() < 500) { - checkTimestampHeaderExistenceAndSet(response); - responses.add(response); - if (--internalAck == 0) { - return responses; - } - } + futureResponses.add(response); } - return responses; + return futureResponses; } private void checkTimestampHeaderExistenceAndSet(Response response) { @@ -171,19 +173,45 @@ private void checkTimestampHeaderExistenceAndSet(Response response) { } } - private Response mergeResponses(List responses, int ack, int from) { - if (responses.stream().filter(response -> response.getStatus() == 400).count() == from - || ack > from - || from > clusterUrls.size() - || ack <= 0) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + private void collectResponsesCallback(List> futureResponses, + int ack, int from, HttpSession session) { + List responses = new CopyOnWriteArrayList<>(); + AtomicInteger successCount = new AtomicInteger(0); + AtomicInteger exceptionCount = new AtomicInteger(0); + for (CompletableFuture futureResponse : futureResponses) { + futureResponse.whenCompleteAsync((response, exception) -> { + + if (exception == null && response.getStatus() < 500) { + checkTimestampHeaderExistenceAndSet(response); + responses.add(response); + successCount.incrementAndGet(); + if (successCount.get() == ack) { + sendAsyncResponse(chooseResponse(responses), session); + } + } else { + exceptionCount.incrementAndGet(); + if (exceptionCount.get() > from - ack) { + sendAsyncResponse(new Response("504 Not enough replicas", Response.EMPTY), session); + } + } + + }, workerExecutor).exceptionally(exception -> { + logger.error("Error happened while collecting responses from nodes", exception); + return null; + }); } - if (responses.stream().filter(response -> response.getStatus() == 200 - || response.getStatus() == 404 - || response.getStatus() == 202 - || response.getStatus() == 201).count() < ack) { - return new Response("504 Not Enough Replicas", Response.EMPTY); + } + + private void sendAsyncResponse(Response resp, HttpSession session) { + try { + session.sendResponse(resp); + } catch (IOException e) { + logger.error("Failed to send error response", e); + session.scheduleClose(); } + } + + private Response chooseResponse(List responses) { return responses.stream().max((o1, o2) -> { Long header1 = Long.parseLong(o1.getHeader(TIMESTAMP_MILLIS_HEADER)); Long header2 = Long.parseLong(o2.getHeader(TIMESTAMP_MILLIS_HEADER)); @@ -191,38 +219,26 @@ private Response mergeResponses(List responses, int ack, int from) { }).get(); } - public Response handleProxy(String redirectedUrl, Request request) { - try { - HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(redirectedUrl)) - .header(FROM_HEADER, selfUrl) - .method(request.getMethodName(), HttpRequest.BodyPublishers.ofByteArray( - request.getBody() == null ? new byte[]{} : request.getBody()) - ).build(); - HttpResponse response = httpClient - .sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()) - .get(REQUEST_TIMEOUT_SEC, TimeUnit.SECONDS); - Response response1 = new Response(String.valueOf(response.statusCode()), response.body()); - if (response.headers().map().get(TIMESTAMP_MILLIS_HEADER_NORMAL) == null) { - response1.addHeader(TIMESTAMP_MILLIS_HEADER + "0"); - } else { - response1.addHeader(TIMESTAMP_MILLIS_HEADER - + response.headers().map().get( + public CompletableFuture handleProxy(String redirectedUrl, Request request) { + HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(redirectedUrl)) + .header(FROM_HEADER_NORMAL, selfUrl) + .method(request.getMethodName(), HttpRequest.BodyPublishers.ofByteArray( + request.getBody() == null ? EMPTY_BYTE_ARRAY : request.getBody()) + ).build(); + return httpClient + .sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()) + .thenApplyAsync(httpResponse -> { + Response response1 = new Response(String.valueOf(httpResponse.statusCode()), httpResponse.body()); + if (httpResponse.headers().map().get(TIMESTAMP_MILLIS_HEADER_NORMAL) == null) { + response1.addHeader(TIMESTAMP_MILLIS_HEADER + "0"); + } else { + response1.addHeader(TIMESTAMP_MILLIS_HEADER + + httpResponse.headers().map().get( TIMESTAMP_MILLIS_HEADER_NORMAL.toLowerCase(Locale.ROOT)).getFirst() - ); - } - - return response1; - } catch (ExecutionException e) { - logger.error("Unexpected error", e); - return new Response(Response.INTERNAL_ERROR, Response.EMPTY); - } catch (TimeoutException e) { - logger.error("Timeout reached", e); - return new Response(Response.REQUEST_TIMEOUT, Response.EMPTY); - } catch (InterruptedException e) { - logger.error("Service is unavailable", e); - Thread.currentThread().interrupt(); - return new Response(Response.SERVICE_UNAVAILABLE, Response.EMPTY); - } + ); + } + return response1; + }, workerExecutor); } private Map getFromAndAck(Request request) { diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/ServiceIml.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/ServiceIml.java index 6a6eb0270..78c54bdef 100644 --- a/src/main/java/ru/vk/itmo/test/dariasupriadkina/ServiceIml.java +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/ServiceIml.java @@ -1,6 +1,5 @@ package ru.vk.itmo.test.dariasupriadkina; -import one.nio.async.CustomThreadFactory; import ru.vk.itmo.Service; import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.dao.Config; @@ -8,46 +7,42 @@ import ru.vk.itmo.test.dariasupriadkina.dao.ExtendedEntry; import ru.vk.itmo.test.dariasupriadkina.dao.ReferenceDao; import ru.vk.itmo.test.dariasupriadkina.sharding.ShardingPolicy; -import ru.vk.itmo.test.dariasupriadkina.workers.WorkerConfig; -import ru.vk.itmo.test.dariasupriadkina.workers.WorkerThreadPoolExecutor; +import ru.vk.itmo.test.dariasupriadkina.workers.CustomThreadConfig; +import ru.vk.itmo.test.dariasupriadkina.workers.CustomThreadPoolExecutor; import java.io.IOException; import java.lang.foreign.MemorySegment; -import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicBoolean; public class ServiceIml implements Service { - private Server server; - private Dao> dao; private final Config daoConfig; private final ServiceConfig serviceConfig; - private final WorkerConfig workerConfig; - private WorkerThreadPoolExecutor workerThreadPoolExecutor; - private NodeThreadPoolExecutor nodeThreadPoolExecutor; + private final CustomThreadConfig workerConfig; + private final CustomThreadConfig nodeConfig; private final ShardingPolicy shardingPolicy; private final AtomicBoolean stopped = new AtomicBoolean(false); + private Server server; + private Dao> dao; + private CustomThreadPoolExecutor workerThreadPoolExecutor; + private CustomThreadPoolExecutor nodeThreadPoolExecutor; public ServiceIml(ServiceConfig serviceConfig, Config daoConfig, - WorkerConfig workerConfig, ShardingPolicy shardingPolicy) { + CustomThreadConfig workerConfig, ShardingPolicy shardingPolicy, + CustomThreadConfig nodeConfig) { this.daoConfig = daoConfig; this.serviceConfig = serviceConfig; this.workerConfig = workerConfig; this.shardingPolicy = shardingPolicy; + this.nodeConfig = nodeConfig; } @Override public synchronized CompletableFuture start() throws IOException { dao = new ReferenceDao(daoConfig); - workerThreadPoolExecutor = new WorkerThreadPoolExecutor(workerConfig); - // TODO вынести параметры в отдельную конфигурацию для большей гибкости - nodeThreadPoolExecutor = new NodeThreadPoolExecutor(8, - 8, - new ArrayBlockingQueue<>(1024), - new CustomThreadFactory("node-executor", true), - new ThreadPoolExecutor.AbortPolicy(), 30); + workerThreadPoolExecutor = new CustomThreadPoolExecutor(workerConfig); + nodeThreadPoolExecutor = new CustomThreadPoolExecutor(nodeConfig); nodeThreadPoolExecutor.prestartAllCoreThreads(); workerThreadPoolExecutor.prestartAllCoreThreads(); diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/ServiceImlFactory.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/ServiceImlFactory.java index 27c9a3c73..2cffc1ba9 100644 --- a/src/main/java/ru/vk/itmo/test/dariasupriadkina/ServiceImlFactory.java +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/ServiceImlFactory.java @@ -6,18 +6,14 @@ import ru.vk.itmo.test.ServiceFactory; import ru.vk.itmo.test.dariasupriadkina.sharding.RendezvousHashing; import ru.vk.itmo.test.dariasupriadkina.sharding.ShardingPolicy; -import ru.vk.itmo.test.dariasupriadkina.workers.WorkerConfig; +import ru.vk.itmo.test.dariasupriadkina.workers.CustomThreadConfig; import java.nio.file.Path; -@ServiceFactory(stage = 4) +@ServiceFactory(stage = 6) public class ServiceImlFactory implements ServiceFactory.Factory { - private static final long FLUSH_THRESHOLD_BYTES = 1024 * 1024; - private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors(); - private static final int MAXIMUM_POOL_SIZE = Runtime.getRuntime().availableProcessors(); - private static final int QUEUE_SIZE = 1024; - private static final int SHUTDOWN_TIMEOUT_SEC = 30; + public static final long FLUSH_THRESHOLD_BYTES = 1024 * 1024; @Override public Service create(ServiceConfig serviceConfig) { @@ -25,8 +21,8 @@ public Service create(ServiceConfig serviceConfig) { serviceConfig.clusterUrls() ); Config referenceDaoConfig = new Config(Path.of(serviceConfig.workingDir().toUri()), FLUSH_THRESHOLD_BYTES); - WorkerConfig workerConfig = new WorkerConfig(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, - QUEUE_SIZE, SHUTDOWN_TIMEOUT_SEC); - return new ServiceIml(serviceConfig, referenceDaoConfig, workerConfig, shardingPolicy); + CustomThreadConfig workerConfig = CustomThreadConfig.baseConfig("worker-thread"); + CustomThreadConfig nodeConfig = CustomThreadConfig.baseConfig("node-thread"); + return new ServiceIml(serviceConfig, referenceDaoConfig, workerConfig, shardingPolicy, nodeConfig); } } diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/TestServer.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/TestServer.java index 264814dd1..8d60b27ab 100644 --- a/src/main/java/ru/vk/itmo/test/dariasupriadkina/TestServer.java +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/TestServer.java @@ -4,7 +4,7 @@ import ru.vk.itmo.dao.Config; import ru.vk.itmo.test.dariasupriadkina.sharding.RendezvousHashing; import ru.vk.itmo.test.dariasupriadkina.sharding.ShardingPolicy; -import ru.vk.itmo.test.dariasupriadkina.workers.WorkerConfig; +import ru.vk.itmo.test.dariasupriadkina.workers.CustomThreadConfig; import java.io.IOException; import java.nio.file.Files; @@ -18,12 +18,12 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import static ru.vk.itmo.test.dariasupriadkina.ServiceImlFactory.FLUSH_THRESHOLD_BYTES; + public final class TestServer { - private static final int THREADS = Runtime.getRuntime().availableProcessors(); - private static final int QUEUE_SIZE = 1024; private static final String LOCALHOST_PREFIX = "http://localhost:"; - private static final int NODE_AMOUNT = 3; + private static final int NODE_AMOUNT = 1; private TestServer() { } @@ -56,9 +56,9 @@ public static void main(String[] args) throws IOException, ExecutionException, for (ServiceConfig serviceConfig : clusterConfs) { ServiceIml serviceIml = new ServiceIml(serviceConfig, new Config(serviceConfig.workingDir(), - 1024 * 1024), - new WorkerConfig(THREADS, THREADS, QUEUE_SIZE, 30), - shardingPolicy); + FLUSH_THRESHOLD_BYTES), + CustomThreadConfig.baseConfig("worker-thread"), shardingPolicy, + CustomThreadConfig.baseConfig("node-thread")); serviceIml.start().get(2, TimeUnit.SECONDS); } } diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/Utils.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/Utils.java index c40445221..41f77c088 100644 --- a/src/main/java/ru/vk/itmo/test/dariasupriadkina/Utils.java +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/Utils.java @@ -11,6 +11,7 @@ public class Utils { public static final String ENTRY_PREFIX = "/v0/entity"; + public static final String LOCAL_STREAM_ENTRY_PREFIX = "/v0/entities"; public static final String ENTRY_PREFIX_WITH_ID_PARAM = ENTRY_PREFIX + "?id="; private final Dao> dao; diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/dao/SSTable.java index d0f597cfa..8058d4542 100644 --- a/src/main/java/ru/vk/itmo/test/dariasupriadkina/dao/SSTable.java +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/dao/SSTable.java @@ -196,12 +196,14 @@ public ExtendedEntry next() { // Read value if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { timestamp = getLength(offset); + offset += Long.BYTES; // Tombstone encountered return new ExtendedBaseEntry<>(key, null, timestamp); } else { final MemorySegment value = data.asSlice(offset, valueLength); offset += valueLength; timestamp = getLength(offset); + offset += Long.BYTES; return new ExtendedBaseEntry<>(key, value, timestamp); } } diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/REPORT.md b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/REPORT.md new file mode 100644 index 000000000..11afa32d9 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/REPORT.md @@ -0,0 +1,248 @@ +# Асинхронное взаимодействие + +При добавлении асинхронного взаимодействия на рассылку по сети, мы ожидаем, что показатели нашей системы улучшатся (повысится rps и уменьшится latency). +Так как теперь мы вместо того, чтобы ждать пока все запросы к другим нодам не будут последовательно обработаны, мы: +- Отправляем запросы параллельно +- Отправляем ответ клиенту со скоростью ответов от ack самых быстрых нод (ранее, мы ждали обработку всех from запросов) + +## Нагрузочное тестирование + +### PUT + +Точка разладки: ≈7000rps + +60s +``` +wrk -d 60 -t 4 -c 64 -R 7000 -L -s /Users/dariasupriadkina/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/dariasupriadkina/scripts/upsert.lua http://localhost:8080 +Running 1m test @ http://localhost:8080 + Thread Stats Avg Stdev Max +/- Stdev + Latency 33.10ms 47.13ms 201.22ms 80.92% + Req/Sec 1.75k 593.61 3.05k 62.83% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.11ms + 75.000% 59.10ms + 90.000% 115.71ms + 99.000% 159.62ms + 99.900% 177.15ms + 99.990% 195.71ms + 99.999% 200.45ms +100.000% 201.34ms +``` + +30s +``` +wrk -d 30 -t 4 -c 64 -R 7000 -L -s /Users/dariasupriadkina/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/dariasupriadkina/scripts/upsert.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + Thread Stats Avg Stdev Max +/- Stdev + Latency 33.54ms 46.81ms 200.58ms 80.57% + Req/Sec 1.76k 624.58 3.09k 61.76% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.90ms + 75.000% 61.12ms + 90.000% 115.01ms + 99.000% 154.75ms + 99.900% 185.22ms + 99.990% 196.86ms + 99.999% 199.42ms +100.000% 200.70ms +``` + +### GET-random + +Точка разладки: ≈10000rps + +60s +``` +wrk -d 60 -t 4 -c 64 -R 10000 -L -s /Users/dariasupriadkina/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/dariasupriadkina/scripts/getrand.lua http://localhost:8080 +Running 1m test @ http://localhost:8080 + Thread Stats Avg Stdev Max +/- Stdev + Latency 21.34ms 39.43ms 185.09ms 85.56% + Req/Sec 2.50k 752.75 4.83k 78.54% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.99ms + 75.000% 13.81ms + 90.000% 90.11ms + 99.000% 155.52ms + 99.900% 174.08ms + 99.990% 179.71ms + 99.999% 182.53ms +100.000% 185.22ms +``` + +30s +``` +wrk -d 30 -t 4 -c 64 -R 10000 -L -s /Users/dariasupriadkina/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/dariasupriadkina/scripts/getrand.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + Thread Stats Avg Stdev Max +/- Stdev + Latency 20.91ms 38.72ms 194.05ms 85.65% + Req/Sec 2.51k 739.50 4.33k 78.71% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.00ms + 75.000% 13.31ms + 90.000% 89.28ms + 99.000% 151.68ms + 99.900% 167.17ms + 99.990% 172.41ms + 99.999% 185.60ms +100.000% 194.18ms +``` + +### Результаты с прошлой лабораторной работы: + +Так как в текущей лабораторной работе измерения делались с другими параметрами wrk (64 connections/4threads), +было принято решение перемерить результаты, полученные в предыдущей лабораторной работе + +#### PUT + +Точка разладки ≈4000rps +``` + wrk -d 30 -t 4 -c 64 -R 4000 -L -s /Users/dariasupriadkina/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/dariasupriadkina/scripts/upsert.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + Thread Stats Avg Stdev Max +/- Stdev + Latency 27.73ms 40.23ms 176.26ms 81.04% + Req/Sec 1.00k 238.36 1.65k 73.24% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.85ms + 75.000% 49.31ms + 90.000% 99.78ms + 99.000% 134.01ms + 99.900% 162.30ms + 99.990% 174.08ms + 99.999% 176.25ms +100.000% 176.38ms + +``` + +#### GET-random + +Точка разладки ≈5000rps +``` +dariasupriadkina@MacBook-Air async-profiler-3.0-macos % wrk -d 60 -t 4 -c 64 -R 5000 -L -s /Users/dariasupriadkina/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/dariasupriadkina/scripts/getrand.lua http://localhost:8080 +Running 1m test @ http://localhost:8080 + Thread Stats Avg Stdev Max +/- Stdev + Latency 55.25ms 42.87ms 181.12ms 55.09% + Req/Sec 1.25k 154.40 2.09k 81.95% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 51.84ms + 75.000% 91.01ms + 90.000% 116.16ms + 99.000% 144.51ms + 99.900% 168.57ms + 99.990% 176.77ms + 99.999% 179.97ms +100.000% 181.25ms +``` + + +### Сравнение +Latency очень близки друг к другу, однако, выдерживаемый rps увеличился почти в 2 раза, что на get, что на put запросах. + +В прошлой лабораторной, большая часть %CPU уходила на метод handleRequest, в рамках которого +последовательно осуществлялось взаимодействие по сети с другими нодами, сейчас, когда это взаимодействие происходит параллельно, +можно было предполагать, что работа будет происходить быстрее. Сейчас выглядит так, будто из идеи асинхронного +взаимодействия можно было вытянуть еще выгоды. + +Повлиять на производительность могло несколько факторов: +- Неграмотная работа с пулами потоков (слишком мало или слишком много выделенных потоков под конкретную задачу) +- Свич контекст между потоками при выполнении коллбэков + +Проведем профилирование и посмотрим, можем ли мы как-то добиться меньшей latency и большей производительности + + +## Профилироване + +### PUT +Результаты профилирования PUT-запросов доступны по ссылкам: + +[upsert-alloc.html](data%2Fupsert-alloc.html) + +[upsert-cpu.html](data%2Fupsert-cpu.html) + +[upsert-lock.html](data%2Fupsert-lock.html) + +**Из интересного, при рассмотрении профиля CPU**, можно заметить, что теперь часть нашего пользовательского кода исполняется в `ForkJoinWorkerThread`. +Это связано с работой коллбэков в `CompletableFuture`. Не сказать, что они там исполняются часто: + +- apply(), вызванный коллбэком `thenApply()` в данном пуле потоков встречается в 0,35% сэмплов +- accept(), вызванный, вероятно, с помощью `.whenComplete()` в методе `collectResponsesCallback()` встречается в 0,70% сэмплов + +По идее, хоть исполнение этих коллбэков в commonPool и не сыграло глобальной роли в распределении сэмплов, но сам факт того, что +наш код исполняется в рамках разделяемого пула, который мы не можем контролировать, выглядит не очень правильным. +Вероятно, следует рассмотреть возможность замены `whenComplete` на `whenCompleteAsync` и явно указать пул потоков, на котором мы хотим исполнять +коллбэки. + +Также бросается в глаза, что в процентном соотношении метод `handleRequest` стал занимать 12% вместо 18%, как в прошлом этапе, снижение произошло как раз благодаря тому, что мы не ждем ответа всех нод, +а отправляем все запросы параллельно и ждем только `ack` ответов (стоит отметить, что снижении было бы еще более значительным, если бы профиль в 4ой лабораторной был снят, +когда я не экспериментировала с возможностью при получении ack ответов выходить из программы) + +**Профили аллокаций** выглядят похожим образом, на сравнении с профилями предыдущей лабораторной работой, однако, наличие коллбэков, исполняемых в `ForJoinWorkerThread`, добавило +новых аллокаций в этом месте, которых раньше не было + +Изменение метода `handleRequest` также немного повлияло на распределение аллокаций: изменилось с 23% до 30% +(но здесь надо учитывать, что на эту цифру повлияло и добавление абсолютно новых аллокаций в виде аллокаций в +`ForJoinWorkerThread`, о которых было сказано ранее) + +**На профиле локов:** + +В процентном соотношении `SelectorManager.run` и `PayloadThread.run` по-прежнему занимают 16% и 83% соответственно +В рамках работы `SelectorManager.run` заметно увеличилось время ожидания на локах для метода `ConnectionPool.purgeExpiredConnectionsAndReturnNextDeadline`: +в прошлой реализации он занимал 0.93%, а теперь 9.36% + +`ThreadPoolExecutor.getTask` уменьшилось с 8% до 3%, что может говорить о том, что на получение задач наши потоки стали тратить меньше времени, что +может быть вызвано тем фактом, что у нас добавляется количество тасок, которые надо выполнить в виде коллбэков. Коллбэки сами по себе не слишком +долго выполняются, однако могут занять для этого целый поток. + +### GET +Результаты профилирования GET-запросов доступны по ссылкам: + +[getrand-alloc.html](data%2Fgetrand-alloc.html) + +[getrand-cpu.html](data%2Fgetrand-cpu.html) + +[getrand-lock.html](data%2Fgetrand-lock.html) + +С get-запросами все астоит аналогичным образом, что и с put, так как разницы в серверной обработке в моем решении +нет абсолютно никакой, отличается лишь работа, выполняемая в dao. Отличается лишь степень изменения в %. Например, +на профиле локов `ThreadPoolExecutor.getTask` уменьшилось с 11% до 2%, что говорит о том, что в очереди у нас почти всегда есть таски, +которые необходимо обработать, благодаря чему, мы не блокируемся. + +Вообще, такое значение `ThreadPoolExecutor.getTask` и увеличение количества выполняемых тасок в поле, натолкнуло меня на мысль, +что, возможно, имеет смысл увеличить количество потоков в `workerExecutor` и `nodeExecutor`. Раньше, увеличение количества потоков не приводило +меня к каким-то серьезным положительным изменениям, но сейчас это выглядит весьма логично. + +Я увеличила количество потоков в этих пулах в 2 раза: +с 8 (по количеству ядер в машине, на которой выполняется лабораторная работа) до 16. + +И это дало свои результаты. При повторном использовании wrk на get-запросах при 10000rps получилось следующее: + +``` +wrk -d 30 -t 4 -c 64 -R 10000 -L -s /Users/dariasupriadkina/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/dariasupriadkina/scripts/getrand.lua http://localhost:8080 +Running 30s test @ http://localhost:8080 + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.12ms 2.79ms 28.14ms 90.93% + Req/Sec 2.64k 849.06 8.22k 85.14% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.33ms + 75.000% 1.84ms + 90.000% 4.01ms + 99.000% 14.73ms + 99.900% 20.14ms + 99.990% 24.37ms + 99.999% 27.63ms +100.000% 28.16ms +``` + +Latency в разы уменьшилась + +Чтобы обеспечить выполнение коллбэков в наших пулах, я явно указала, что хочу, чтобы эти коллбэки +вызывались в `workerExecutor`'е за счет замены `.thenApply()` и `.whenComplete()` на `.thenApplyAsync()` и +`.whenCompleteAsync()` с указанием `workerExecutor` в качестве параметра. + +Сама производительность вроде как не изменилась, персентили не поменялись, а коллбэки из `ForkJoinWorkerThread` +исчезли: + +[getrand2-cpu.html](data%2Fgetrand2-cpu.html) + +[getrand2-alloc.html](data%2Fgetrand2-alloc.html) + +[getrand2-lock.html](data%2Fgetrand2-lock.html) \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand-alloc.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand-alloc.html new file mode 100644 index 000000000..f3bb6624d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand-alloc.html @@ -0,0 +1,3153 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand-cpu.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand-cpu.html new file mode 100644 index 000000000..cd7e5aa0c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand-cpu.html @@ -0,0 +1,3413 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand-lock.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand-lock.html new file mode 100644 index 000000000..637db51a7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand-lock.html @@ -0,0 +1,907 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand2-alloc.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand2-alloc.html new file mode 100644 index 000000000..9d576c197 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand2-alloc.html @@ -0,0 +1,3215 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand2-cpu.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand2-cpu.html new file mode 100644 index 000000000..1e42e972a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand2-cpu.html @@ -0,0 +1,2846 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand2-lock.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand2-lock.html new file mode 100644 index 000000000..b423ff688 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/getrand2-lock.html @@ -0,0 +1,1136 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/upsert-alloc.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/upsert-alloc.html new file mode 100644 index 000000000..f9ae283af --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/upsert-alloc.html @@ -0,0 +1,3218 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/upsert-cpu.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/upsert-cpu.html new file mode 100644 index 000000000..97d50de4d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/upsert-cpu.html @@ -0,0 +1,3142 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/upsert-lock.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/upsert-lock.html new file mode 100644 index 000000000..ca2bf3f48 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw5/data/upsert-lock.html @@ -0,0 +1,953 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/REPORT.md b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/REPORT.md new file mode 100644 index 000000000..2499434f2 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/REPORT.md @@ -0,0 +1,101 @@ +# Range-запросы + +На начало проведения экспериментов база была заполнена на 777Мб, количество записей - ≈8_990_000 + +Снимем профили аллокации, блокировок и cpu, нагрузив систему командой curl, +с помощью которой запросим все записи базы данных, начиная с 1ой: + +``` +curl -v http://localhost:8080/v0/entities\?start=1 +``` + +## Результаты профилирования + +[range-alloc.html](data%2Frange-alloc.html) + +[range-cpu.html](data%2Frange-cpu.html) + +[range-lock.html](data%2Frange-lock.html) + + +На профиле блокировок видно, что практически 100% локов уходит на `Session.write` + +Если посмотреть на профиль cpu, то разделение сеймплов примерно такое: + +- `Session.write` - 48.7% +- `getEntryByteChunk` (метод, отвечающий за формирование байтового чанка с данными) - 14.7%, что выглядит весьма солидными +затратами для обычного преобразования `MemorySegment`'ов в массив байт +- `MergingEntryIterator` (работа dao) - 28.7% +- Оставшаяся часть - работа `SelectorThread` + +На профиле аллокаций дела обстоят несколько иначе: + +- `MergingEntryIterator.next` (работа dao) - 10.6% +- `Session.write` - всего около 9% несмотря на то, что занимает больше всего процессорного времени +- `getEntryByteChunk` - ≈45%. В рамках этого метода у нас по отдельности преобразуется в байтовый массив каждая из частей +- `ByteArrayBuilder.` - ≈25% - инициализация ByteArrayBuilder + +`getEntryByteChunk` забирает на себя практически половину всех аллокаций, что выглядит узким местом, которое нуждается +в оптимизации + +`ByteArrayBuilder.` также занимает весьма солидный процент аллокаций - целую четверть. Пичина возникновения этих алллокаций весьма очевидна, +в коде мы аллоцируем новый ByteArrayBuilder каждый раз, когда формируем новый чанк key-value с данными + +``` + while (it.hasNext()) { + ByteArrayBuilder bb = new ByteArrayBuilder(); + ExtendedEntry ee = it.next(); + EntryChunkUtils.getEntryByteChunk(ee, bb); + session.write(bb.toBytes(), 0, bb.length()); + } +``` + +Это решение было принято, чтобы отправлять через `session.write` весь чанк с данными, а не каждую его часть +(length, crlf, , \n, value) по отдельность. В качестве альтернативы можно как раз отправлять каждую часть через session.write, +тогда аллокацию ByteArrayBuilder можно избежать, но, так как на профиле локов, фигурирует в основном только метод Session.write, +вероятно, лишнее использование Session.write может привести к затратам на синхронизацию, отчего в конце концов пострадает latency +и сама производительность системы. + +Тем не менее имеет смысл судя по полученным результатам профилирования, имеет смысл попытаться оптимизировать процесс формирования +массивов байт. + +Заметно, что наибольшие затраты в рамках `getEntryByteChunk` происходят в методе расчета длины чанка - `getKVLengthChank` +(27.7%). +В первоначальной реализации дл того, чтобы получить длину ключа и значения, `MemorySegment` приводился сначала к массиву +байт, а затем бралась длина. С целью попытки оптимизации этого момента, вся цепочка преобразований была заменена на метод +получения длины `MemorySegment`'а в байтах - `byteSize()`, также расчет длины символа `\n` была заменена с `"\n".length()` +на `DELIMITER_BYTES.length`, где DELIMITER_BYTES - константа. Таким образом мы избегаем инициализации новой строчки. + +Даже такие небольшие изменения позволили уменьшить процент аллокаций в методе `getEntryByteChunk` с 45% до 33%, а в самом +методе `getKVLengthChank` процент уменьшился с 27.7% до 11.17%. + +Новые результаты профилирования аллокаций: + +[range-alloc-2.html](data%2Frange-alloc-2.html) + +Как видно на профиле, при снижении процента аллокаций на работу с преобразованием `MemorySegment`'ов в массивы байт +гораздо заметнее стали ресурсы, затрачиваемые на инициализацию `ByteArrayBuilder`, который инициализируется при формировании каждого +чанка. В идеале подобных инициализаций в цикле нам явно хотелось бы избежать. Несильно уверена в следующем предположении, +но, обратив внимание на принципы работы +`ByteArrayBuilder`, предполагая, что вместо инициализации `ByteArrayBuilder` можно использовать метод `.setLength(0)`. + +В таком случае вышеупомянутый кусок кода можно заменить на: + +``` +while (it.hasNext()) { + ExtendedEntry ee = it.next(); + EntryChunkUtils.getEntryByteChunk(ee, bb); + session.write(bb.toBytes(), 0, bb.length()); + bb.setLength(0); +} +``` + +P.S. По крайней мере после таких изменений тесты по-прежнему проходят и curl работает точно так же, как и до этого изменения. + +Результаты профилирования последней версии: + +[range-alloc-3.html](data%2Frange-alloc-3.html) + +[range-cpu-3.html](data%2Frange-cpu-3.html) + +Если опять же обратиться к профилю аллокаций, то можно увидеть, что `ByteArrayBuilder.` пропал совсем. \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-alloc-2.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-alloc-2.html new file mode 100644 index 000000000..339545457 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-alloc-2.html @@ -0,0 +1,388 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-alloc-3.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-alloc-3.html new file mode 100644 index 000000000..b0153f38b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-alloc-3.html @@ -0,0 +1,365 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-alloc.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-alloc.html new file mode 100644 index 000000000..705dbe74d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-alloc.html @@ -0,0 +1,361 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-cpu-3.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-cpu-3.html new file mode 100644 index 000000000..a5d74e8b1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-cpu-3.html @@ -0,0 +1,649 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-cpu.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-cpu.html new file mode 100644 index 000000000..f4ed65308 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-cpu.html @@ -0,0 +1,599 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-lock.html b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-lock.html new file mode 100644 index 000000000..9d44d2db0 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/report/hw6/data/range-lock.html @@ -0,0 +1,288 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/scripts/getrand.lua b/src/main/java/ru/vk/itmo/test/dariasupriadkina/scripts/getrand.lua index 7f9c03539..f0c079cec 100644 --- a/src/main/java/ru/vk/itmo/test/dariasupriadkina/scripts/getrand.lua +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/scripts/getrand.lua @@ -5,6 +5,6 @@ headers = {} headers["Host"] = "localhost:8080" request = function() - id = math.random(0, 3500000) + id = math.random(0, 109000) return wrk.format("GET", tostring(url .. id), headers) end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/workers/CustomThreadConfig.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/workers/CustomThreadConfig.java new file mode 100644 index 000000000..05d9b66e0 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/workers/CustomThreadConfig.java @@ -0,0 +1,64 @@ +package ru.vk.itmo.test.dariasupriadkina.workers; + +import one.nio.async.CustomThreadFactory; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class CustomThreadConfig { + + public static final long KEEP_ALIVE_TIME = 1000L; + public static final TimeUnit KEEP_ALIVE_TIME_SECONDS = TimeUnit.SECONDS; + public static final int QUEUE_SIZE = 1024; + public static final int THREADS = Runtime.getRuntime().availableProcessors(); + public static final int SHUTDOWN_TIMEOUT_SEC = 60; + + private final int corePoolSize; + private final int maximumPoolSize; + private final int shutdownTimeoutSec; + private final ArrayBlockingQueue workQueue; + private final CustomThreadFactory threadFactory; + private final RejectedExecutionHandler handler; + + public CustomThreadConfig(int corePoolSize, int maximumPoolSize, int queueSize, int shutdownTimeoutSec, + String threadName, RejectedExecutionHandler handler) { + this.corePoolSize = corePoolSize; + this.maximumPoolSize = maximumPoolSize; + this.workQueue = new ArrayBlockingQueue<>(queueSize); + this.shutdownTimeoutSec = shutdownTimeoutSec; + this.threadFactory = new CustomThreadFactory(threadName, true); + this.handler = handler; + } + + public static CustomThreadConfig baseConfig(String threadName) { + return new CustomThreadConfig(THREADS * 2, THREADS * 2, + QUEUE_SIZE, SHUTDOWN_TIMEOUT_SEC, threadName, new ThreadPoolExecutor.AbortPolicy()); + } + + public int getCorePoolSize() { + return corePoolSize; + } + + public int getMaximumPoolSize() { + return maximumPoolSize; + } + + public BlockingQueue getWorkQueue() { + return workQueue; + } + + public int getShutdownTimeoutSec() { + return shutdownTimeoutSec; + } + + public CustomThreadFactory getThreadFactory() { + return threadFactory; + } + + public RejectedExecutionHandler getHandler() { + return handler; + } +} diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/workers/WorkerThreadPoolExecutor.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/workers/CustomThreadPoolExecutor.java similarity index 76% rename from src/main/java/ru/vk/itmo/test/dariasupriadkina/workers/WorkerThreadPoolExecutor.java rename to src/main/java/ru/vk/itmo/test/dariasupriadkina/workers/CustomThreadPoolExecutor.java index 26123f689..72bfcb564 100644 --- a/src/main/java/ru/vk/itmo/test/dariasupriadkina/workers/WorkerThreadPoolExecutor.java +++ b/src/main/java/ru/vk/itmo/test/dariasupriadkina/workers/CustomThreadPoolExecutor.java @@ -1,22 +1,21 @@ package ru.vk.itmo.test.dariasupriadkina.workers; -import one.nio.async.CustomThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -public class WorkerThreadPoolExecutor extends ThreadPoolExecutor { +public class CustomThreadPoolExecutor extends ThreadPoolExecutor { - private static final Logger logger = LoggerFactory.getLogger(WorkerThreadPoolExecutor.class.getName()); + private static final Logger logger = LoggerFactory.getLogger(CustomThreadPoolExecutor.class.getName()); private final int shutdownTimeoutSec; - public WorkerThreadPoolExecutor(WorkerConfig workerConfig) { + public CustomThreadPoolExecutor(CustomThreadConfig workerConfig) { super(workerConfig.getCorePoolSize(), workerConfig.getMaximumPoolSize(), - WorkerConfig.KEEP_ALIVE_TIME, WorkerConfig.KEEP_ALIVE_TIME_SECONDS, - workerConfig.getWorkQueue(), new CustomThreadFactory("worker-thread", true), - new AbortPolicy()); + CustomThreadConfig.KEEP_ALIVE_TIME, CustomThreadConfig.KEEP_ALIVE_TIME_SECONDS, + workerConfig.getWorkQueue(), workerConfig.getThreadFactory(), + workerConfig.getHandler()); this.shutdownTimeoutSec = workerConfig.getShutdownTimeoutSec(); } diff --git a/src/main/java/ru/vk/itmo/test/dariasupriadkina/workers/WorkerConfig.java b/src/main/java/ru/vk/itmo/test/dariasupriadkina/workers/WorkerConfig.java deleted file mode 100644 index a46b99965..000000000 --- a/src/main/java/ru/vk/itmo/test/dariasupriadkina/workers/WorkerConfig.java +++ /dev/null @@ -1,39 +0,0 @@ -package ru.vk.itmo.test.dariasupriadkina.workers; - -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; - -public class WorkerConfig { - - public static final long KEEP_ALIVE_TIME = 1000L; - public static final TimeUnit KEEP_ALIVE_TIME_SECONDS = TimeUnit.SECONDS; - - private final int corePoolSize; - private final int maximumPoolSize; - private final int shutdownTimeoutSec; - private final ArrayBlockingQueue workQueue; - - public WorkerConfig(int corePoolSize, int maximumPoolSize, int queueSize, int shutdownTimeoutSec) { - this.corePoolSize = corePoolSize; - this.maximumPoolSize = maximumPoolSize; - this.workQueue = new ArrayBlockingQueue<>(queueSize); - this.shutdownTimeoutSec = shutdownTimeoutSec; - } - - public int getCorePoolSize() { - return corePoolSize; - } - - public int getMaximumPoolSize() { - return maximumPoolSize; - } - - public BlockingQueue getWorkQueue() { - return workQueue; - } - - public int getShutdownTimeoutSec() { - return shutdownTimeoutSec; - } -} 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..462b00f26 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/HttpServerImpl.java @@ -4,39 +4,59 @@ 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.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; 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.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Iterator; import java.util.List; -import java.util.Objects; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; 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 { - 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"; + 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); @@ -54,25 +74,195 @@ 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 public void handleRequest(Request request, HttpSession session) throws IOException { try { - executorService.execute(() -> processRequest(request, session)); + AllowedMethods method = getMethod(request.getMethod()); + if (method == null) { + session.sendResponse(new Response(Response.METHOD_NOT_ALLOWED, 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)); + } + } catch (RejectedExecutionException e) { logger.error("Request rejected", e); session.sendResponse(new Response(TOO_MANY_REQUESTS, Response.EMPTY)); } } - private void processRequest(Request request, HttpSession session) { - try { - super.handleRequest(request, session); - } catch (Exception e) { - logger.error("Unexpected error when processing request", e); - sendError(session, e); + 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); + 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++) { + final int currentIter = i; + String node = sortedNodes.get(i); + try { + responses[currentIter] = routeRequest(node, request, id, method); + 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(); + } + } + } + + 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) { + 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) { + sendResponse(session, validateGetRequests(ack, responseArray)); + return; + } + Response result = new Response( + getResponseByCode(responseArray.get(lastSuccessResp.get()).getStatus()), + responseArray.get(lastSuccessResp.get()).getBody()); + sendResponse(session, result); + } + + private Response validateGetRequests(int ack, AtomicReferenceArray responseArray) { + int notFound = 0; + long latestTimestamp = 0L; + Response latestResponse = null; + 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++; + continue; + } + long timestamp = Long.parseLong(currentResponse.getHeader(TIMESTAMP_HEADER)); + if (timestamp > latestTimestamp) { + latestTimestamp = timestamp; + latestResponse = currentResponse; + } + } + 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()); } } @@ -86,6 +276,15 @@ 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).collect(Collectors.toList()); + } + private static HttpServerConfig createServerConfig(ServiceConfig serviceConfig) { HttpServerConfig httpServerConfig = new HttpServerConfig(); AcceptorConfig acceptorConfig = new AcceptorConfig(); @@ -97,100 +296,125 @@ 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.OK, Response.EMPTY); + response.addHeader("X-tomb: true"); + } 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); + private CompletableFuture redirectRequest(AllowedMethods method, + String id, + String clusterUrl, + Request request) { + byte[] body = request.getBody(); + 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(); + 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; + }); } - @Override - public void handleDefault(Request request, HttpSession session) throws IOException { - Response badRequest = new Response(Response.BAD_REQUEST, Response.EMPTY); - session.sendResponse(badRequest); + 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 Response redirectRequest(AllowedMethods method, String id, String clusterUrl, byte[] body) { + private static void sendResponse(HttpSession session, Response response) { 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); + session.sendResponse(response); + } catch (IOException e) { + logger.error("Error send response", e); + session.close(); } } - 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; + @Override + public synchronized void stop() { + for (SelectorThread selector : selectors) { + if (selector.selector.isOpen()) { + for (Session session : selector.selector) { + session.close(); + } } } - return nodeUrl; + super.stop(); + } + + private boolean isParamIncorrect(String param) { + return param == null || param.isEmpty(); } private String getResponseByCode(int code) { @@ -205,4 +429,54 @@ private String getResponseByCode(int code) { default -> Response.INTERNAL_ERROR; }; } + + 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; + if (method == 6) return AllowedMethods.DELETE; + return null; + } + + private void iterateOverChunks(HttpSession session, Iterator> iterator) { + try { + boolean callsCounter = true; + while (callsCounter) { + callsCounter = processChunk(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/Server.java b/src/main/java/ru/vk/itmo/test/elenakhodosova/Server.java index cfcbba2a0..da1e6c038 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; @@ -29,6 +30,7 @@ private Server() { 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(); + } + }); } } } 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..e6745e9ac 100644 --- a/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/ServiceImpl.java @@ -33,11 +33,12 @@ public CompletableFuture start() throws IOException { executorService = ExecutorServiceConfig.newExecutorService(); server = new HttpServerImpl(config, dao, executorService); server.start(); + isServiceStopped.getAndSet(false); return CompletableFuture.completedFuture(null); } @Override - public CompletableFuture stop() throws IOException { + public synchronized CompletableFuture stop() throws IOException { if (isServiceStopped.getAndSet(true)) { return CompletableFuture.completedFuture(null); } @@ -48,13 +49,14 @@ public CompletableFuture stop() throws IOException { executorService.shutdownNow(); } } catch (InterruptedException e) { + executorService.shutdownNow(); Thread.currentThread().interrupt(); } dao.close(); return CompletableFuture.completedFuture(null); } - @ServiceFactory(stage = 3) + @ServiceFactory(stage = 6) 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..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 @@ -2,7 +2,6 @@ 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; @@ -22,7 +21,7 @@ * * @author incubos */ -public class ReferenceDao implements Dao> { +public class ReferenceDao implements Dao> { private final Config config; private final Arena arena; @@ -63,7 +62,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 +72,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,7 +88,7 @@ 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)); @@ -103,16 +102,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() + Long.BYTES; } - return entry.key().byteSize() + entry.value().byteSize(); + return entry.key().byteSize() + entry.value().byteSize() + Long.BYTES; } private void initiateFlush(final boolean auto) { @@ -200,8 +199,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); 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..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 @@ -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; @@ -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; @@ -146,17 +143,24 @@ Entry 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 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 += valueLength; + 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 +177,7 @@ public boolean hasNext() { } @Override - public Entry next() { + public EntryWithTimestamp next() { if (!hasNext()) { throw new NoSuchElementException(); } @@ -193,11 +197,15 @@ public Entry next() { // Read value if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { // Tombstone encountered - return new BaseEntry<>(key, null); + final long timestamp = getLength(offset); + offset += Long.BYTES; + 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); + 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..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, ...]} @@ -40,7 +38,7 @@ final class SSTableWriter { void write( final Path baseDir, final int sequence, - final Iterator> entries) throws IOException { + final Iterator> entries) throws IOException { // Write to temporary files final Path tempIndexName = SSTables.tempIndexName(baseDir, sequence); final Path tempDataName = SSTables.tempDataName(baseDir, sequence); @@ -71,7 +69,7 @@ void write( writeLong(entryOffset, index); // Then write the entry - final Entry entry = entries.next(); + final EntryWithTimestamp entry = entries.next(); entryOffset += writeEntry(entry, data); } } @@ -127,15 +125,16 @@ private void writeSegment( } /** - * Writes {@link Entry} to {@link FileChannel}. + * Writes {@link EntryWithTimestamp} to {@link FileChannel}. * * @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 @@ -151,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); @@ -159,6 +162,10 @@ private long writeEntry( // Value writeSegment(value, os); result += value.byteSize(); + + // 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 eb3a935f9..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 @@ -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; @@ -98,14 +96,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 +114,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 +127,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,14 +144,14 @@ 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); + return result; } // Then check flushing @@ -161,7 +159,7 @@ Entry get(final MemorySegment key) { result = flushingTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } } @@ -170,7 +168,7 @@ Entry get(final MemorySegment key) { result = ssTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } } @@ -178,21 +176,17 @@ Entry get(final MemorySegment key) { return null; } - private static Entry swallowTombstone(final Entry entry) { - return entry.value() == null ? null : entry; - } - - 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( 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(); } 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..1215623fb --- /dev/null +++ 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/Stage5.md b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage5.md new file mode 100644 index 000000000..771cd4353 --- /dev/null +++ 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/Stage6.md b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage6.md new file mode 100644 index 000000000..b6d048d68 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/Stage6.md @@ -0,0 +1,29 @@ +# Этап 6. Range-запросы + +C помощью wrk добавим записи в базу +```dtd +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 +``` +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) + +На профиле аллокаций всего 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/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:

+ 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:

+ 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..c181c27ac --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-get-range.html @@ -0,0 +1,1047 @@ + + + + + + + +

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..a05e3aaba --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/elenakhodosova/reports/data/stage6/profile-range-alloc.html @@ -0,0 +1,403 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

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

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/ConsistentHashing.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/ConsistentHashing.java deleted file mode 100644 index 9df933db2..000000000 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/ConsistentHashing.java +++ /dev/null @@ -1,35 +0,0 @@ -package ru.vk.itmo.test.georgiidalbeev; - -import java.util.Collections; -import java.util.List; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.function.Function; - -public class ConsistentHashing { - - private final SortedMap circle; - private final Function hashFunction; - - public ConsistentHashing(Function hashFunction, List nodes) { - this.hashFunction = hashFunction; - SortedMap tempCircle = new TreeMap<>(); - for (T node : nodes) { - int hash = hashFunction.apply((String) node); - tempCircle.put(hash, node); - } - this.circle = Collections.unmodifiableSortedMap(tempCircle); - } - - public T getNode(String key) { - if (circle.isEmpty()) { - return null; - } - int hash = hashFunction.apply(key); - if (!circle.containsKey(hash)) { - SortedMap tailMap = circle.tailMap(hash); - hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); - } - return circle.get(hash); - } -} diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/MyHandleResult.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/MyHandleResult.java new file mode 100644 index 000000000..a5a7d6ddd --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/MyHandleResult.java @@ -0,0 +1,14 @@ +package ru.vk.itmo.test.georgiidalbeev; + +public record MyHandleResult(int status, byte[] data, long timestamp) { + + public MyHandleResult(int status, byte[] data, long timestamp) { + this.status = status; + this.data = data; + this.timestamp = timestamp; + } + + public MyHandleResult(int status, byte[] data) { + this(status, data, 0); + } +} diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/MyMergeHandleResult.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/MyMergeHandleResult.java new file mode 100644 index 000000000..90cb3e747 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/MyMergeHandleResult.java @@ -0,0 +1,69 @@ +package ru.vk.itmo.test.georgiidalbeev; + +import one.nio.http.HttpSession; +import one.nio.http.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.concurrent.atomic.AtomicInteger; + +public class MyMergeHandleResult { + private static final Logger log = LoggerFactory.getLogger(MyMergeHandleResult.class); + private final AtomicInteger count; + private final AtomicInteger success; + private final int ack; + private final int from; + private final HttpSession session; + private boolean isSent; + MyHandleResult mergedResult = new MyHandleResult(HttpURLConnection.HTTP_GATEWAY_TIMEOUT, null); + + public MyMergeHandleResult(HttpSession session, int size, int ack) { + this.session = session; + this.count = new AtomicInteger(); + this.ack = ack; + this.from = size; + this.success = new AtomicInteger(); + } + + public void add(MyHandleResult handleResult) { + int get = count.incrementAndGet(); + + if (handleResult.status() == HttpURLConnection.HTTP_OK + || handleResult.status() == HttpURLConnection.HTTP_CREATED + || handleResult.status() == HttpURLConnection.HTTP_ACCEPTED + || handleResult.status() == HttpURLConnection.HTTP_NOT_FOUND) { + success.incrementAndGet(); + if (mergedResult.timestamp() <= handleResult.timestamp()) { + mergedResult = handleResult; + } + if (success.get() >= ack && !isSent) { + isSent = true; + sendResult(); + } + } + + if (get == from && success.get() < ack && !isSent) { + sendResult(); + } + } + + private void sendResult() { + try { + if (success.get() < ack) { + session.sendResponse(new Response(Response.GATEWAY_TIMEOUT, Response.EMPTY)); + } else { + session.sendResponse(new Response(String.valueOf(mergedResult.status()), mergedResult.data())); + } + } catch (Exception e) { + log.error("Exception during handleRequest", e); + try { + session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); + } catch (IOException ex) { + log.error("Exception while sending close connection", e); + session.scheduleClose(); + } + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/MyReferenceServer.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/MyReferenceServer.java new file mode 100644 index 000000000..914a3e28c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/MyReferenceServer.java @@ -0,0 +1,286 @@ +package ru.vk.itmo.test.georgiidalbeev; + +import one.nio.async.CustomThreadFactory; +import one.nio.http.HttpServer; +import one.nio.http.HttpServerConfig; +import one.nio.http.HttpSession; +import one.nio.http.Path; +import one.nio.http.Request; +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; +import ru.vk.itmo.ServiceConfig; +import ru.vk.itmo.test.georgiidalbeev.dao.ReferenceBaseEntry; +import ru.vk.itmo.test.georgiidalbeev.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.time.Duration; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Supplier; + +import static ru.vk.itmo.test.georgiidalbeev.MyReferenceService.shutdownAndAwaitTermination; + +public class MyReferenceServer extends HttpServer { + + private static final String HEADER_REMOTE = "X-flag-remote-reference-server-to-node-by-paschenko"; + private static final String HEADER_REMOTE_ONE_NIO_HEADER = HEADER_REMOTE + ": da"; + private static final String HEADER_TIMESTAMP = "X-flag-remote-reference-server-to-node-by-paschenko2"; + private static final String HEADER_TIMESTAMP_ONE_NIO_HEADER = HEADER_TIMESTAMP + ": "; + private static final Logger log = LoggerFactory.getLogger(MyReferenceServer.class); + private static final int THREADS = Runtime.getRuntime().availableProcessors(); + + private final ExecutorService executorLocal = Executors.newFixedThreadPool(THREADS / 2, + new CustomThreadFactory("local-work")); + private final ExecutorService executorRemote = Executors.newFixedThreadPool(THREADS / 2, + new CustomThreadFactory("remote-work")); + private final ReferenceDao dao; + private final ServiceConfig config; + private final HttpClient httpClient; + + public MyReferenceServer(ServiceConfig config, ReferenceDao dao) throws IOException { + super(createServerConfigWithPort(config.selfPort())); + this.dao = dao; + this.config = config; + + this.httpClient = HttpClient.newBuilder() + .executor(Executors.newFixedThreadPool(THREADS)) + .connectTimeout(Duration.ofMillis(500)) + .version(HttpClient.Version.HTTP_1_1).build(); + } + + private static HttpServerConfig createServerConfigWithPort(int port) { + HttpServerConfig serverConfig = new HttpServerConfig(); + AcceptorConfig acceptorConfig = new AcceptorConfig(); + acceptorConfig.port = port; + acceptorConfig.reusePort = true; + serverConfig.selectors = Runtime.getRuntime().availableProcessors() / 2; + + serverConfig.acceptors = new AcceptorConfig[]{acceptorConfig}; + serverConfig.closeSessions = true; + return serverConfig; + } + + @Override + public void handleRequest(Request request, HttpSession session) throws IOException { + if (!"/v0/entity".equals(request.getPath())) { + sendError(session, Response.BAD_REQUEST); + return; + } + + if (!isValidMethod(request.getMethod())) { + sendError(session, Response.METHOD_NOT_ALLOWED); + return; + } + + String id = request.getParameter("id="); + if (id == null || id.isBlank()) { + sendError(session, Response.BAD_REQUEST); + return; + } + + if (request.getHeader(HEADER_REMOTE_ONE_NIO_HEADER) != null) { + handleLocalRequest(request, session, id); + return; + } + + int ack = getInt(request, "ack=", config.clusterUrls().size() / 2 + 1); + int from = getInt(request, "from=", config.clusterUrls().size()); + + if (!isValidAckFrom(ack, from)) { + sendError(session, Response.BAD_REQUEST); + return; + } + + handleRemoteRequest(request, session, id, ack, from); + } + + private void sendError(HttpSession session, String errorCode) throws IOException { + session.sendError(errorCode, null); + } + + private boolean isValidMethod(int method) { + return method == Request.METHOD_GET || method == Request.METHOD_DELETE || method == Request.METHOD_PUT; + } + + private void handleLocalRequest(Request request, HttpSession session, String id) { + executorLocal.execute(() -> { + try { + MyHandleResult local = local(request, id); + Response response = new Response(String.valueOf(local.status()), local.data()); + response.addHeader(HEADER_TIMESTAMP_ONE_NIO_HEADER + local.timestamp()); + session.sendResponse(response); + } catch (Exception e) { + handleError(session, e); + } + }); + } + + private boolean isValidAckFrom(int ack, int from) { + return from > 0 && from <= config.clusterUrls().size() && ack <= from && ack > 0; + } + + private void handleRemoteRequest(Request request, HttpSession session, String id, int ack, int from) { + int[] indexes = getIndexes(id, from); + MyMergeHandleResult mergeHandleResult = new MyMergeHandleResult(session, indexes.length, ack); + for (int index : indexes) { + String executorNode = config.clusterUrls().get(index); + if (executorNode.equals(config.selfUrl())) { + handleAsync(mergeHandleResult, () -> CompletableFuture.completedFuture(local(request, id))); + } else { + handleAsync(mergeHandleResult, () -> invokeRemote(executorNode, request)); + } + } + } + + private void handleError(HttpSession session, Exception e) { + log.error("Exception during handleRequest", e); + try { + session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); + } catch (IOException ex) { + log.error("Exception while sending close connection", e); + session.scheduleClose(); + } + } + + private int getInt(Request request, String param, int defaultValue) throws IOException { + int ack; + String ackStr = request.getParameter(param); + if (ackStr == null || ackStr.isBlank()) { + ack = defaultValue; + } else { + try { + ack = Integer.parseInt(ackStr); + } catch (Exception e) { + throw new IllegalArgumentException("parse error"); + } + } + return ack; + } + + private void handleAsync(MyMergeHandleResult mergeHandleResult, + Supplier> supplier) { + supplier.get().thenAccept(mergeHandleResult::add).exceptionally(e -> { + log.error("Exception during handleRequest", e); + mergeHandleResult.add(new MyHandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY)); + return null; + }); + } + + @Override + public void handleDefault(Request request, HttpSession session) throws IOException { + session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); + } + + @Path("/v0/status") + public Response status() { + return Response.ok("OK"); + } + + private CompletableFuture invokeRemote(String executorNode, Request request) { + HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(executorNode + request.getURI())) + .method(request.getMethodName(), request.getBody() == null ? HttpRequest.BodyPublishers.noBody() : + HttpRequest.BodyPublishers.ofByteArray(request.getBody())).header(HEADER_REMOTE, "da") + .timeout(Duration.ofMillis(500)).build(); + + return httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()).thenApply(httpResponse -> { + Optional string = httpResponse.headers().firstValue(HEADER_TIMESTAMP); + long timestamp; + if (string.isPresent()) { + try { + timestamp = Long.parseLong(string.get()); + } catch (Exception e) { + log.error("Error while parsing timestamp"); + timestamp = 0; + } + } else { + timestamp = 0; + } + return new MyHandleResult(httpResponse.statusCode(), httpResponse.body(), timestamp); + }).exceptionally(e -> { + log.info("I/O exception while calling remote node", e); + return new MyHandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY); + }); + } + + private MyHandleResult local(Request request, String id) { + long currentTimeMillis = System.currentTimeMillis(); + switch (request.getMethod()) { + case Request.METHOD_GET -> { + MemorySegment key = MemorySegment.ofArray(Utf8.toBytes(id)); + ReferenceBaseEntry entry = dao.get(key); + if (entry == null) { + return new MyHandleResult(HttpURLConnection.HTTP_NOT_FOUND, Response.EMPTY); + } + if (entry.value() == null) { + return new MyHandleResult(HttpURLConnection.HTTP_NOT_FOUND, Response.EMPTY, entry.timestamp()); + } + + return new MyHandleResult(HttpURLConnection.HTTP_OK, + entry.value().toArray(ValueLayout.JAVA_BYTE), + entry.timestamp()); + } + case Request.METHOD_PUT -> { + MemorySegment key = MemorySegment.ofArray(Utf8.toBytes(id)); + MemorySegment value = MemorySegment.ofArray(request.getBody()); + dao.upsert(new ReferenceBaseEntry<>(key, value, currentTimeMillis)); + return new MyHandleResult(HttpURLConnection.HTTP_CREATED, Response.EMPTY); + } + case Request.METHOD_DELETE -> { + MemorySegment key = MemorySegment.ofArray(Utf8.toBytes(id)); + dao.upsert(new ReferenceBaseEntry<>(key, null, currentTimeMillis)); + return new MyHandleResult(HttpURLConnection.HTTP_ACCEPTED, Response.EMPTY); + } + default -> { + return new MyHandleResult(HttpURLConnection.HTTP_BAD_METHOD, Response.EMPTY); + } + } + } + + private int[] getIndexes(String id, int count) { + assert count < 5; + + int[] result = new int[count]; + int[] maxHashs = new int[count]; + + for (int i = 0; i < count; i++) { + String url = config.clusterUrls().get(i); + int hash = Hash.murmur3(url + id); + result[i] = i; + maxHashs[i] = hash; + } + + for (int i = count; i < config.clusterUrls().size(); i++) { + String url = config.clusterUrls().get(i); + int hash = Hash.murmur3(url + id); + for (int j = 0; j < maxHashs.length; j++) { + int maxHash = maxHashs[j]; + if (maxHash < hash) { + maxHashs[j] = hash; + result[j] = i; + break; + } + } + } + return result; + } + + @Override + public synchronized void stop() { + super.stop(); + shutdownAndAwaitTermination(executorLocal); + shutdownAndAwaitTermination(executorRemote); + } +} diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/MyReferenceService.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/MyReferenceService.java new file mode 100644 index 000000000..b71333587 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/MyReferenceService.java @@ -0,0 +1,124 @@ +package ru.vk.itmo.test.georgiidalbeev; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.vk.itmo.Service; +import ru.vk.itmo.ServiceConfig; +import ru.vk.itmo.dao.Config; +import ru.vk.itmo.test.ServiceFactory; +import ru.vk.itmo.test.georgiidalbeev.dao.ReferenceDao; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class MyReferenceService implements Service { + private static final Logger log = LoggerFactory.getLogger(MyReferenceService.class); + + private static final long FLUSHING_THRESHOLD_BYTES = 1024 * 1024; + + private static final String LOCALHOST_PREFIX = "http://localhost:"; + + private final ServiceConfig config; + + private ReferenceDao dao; + private MyReferenceServer server; + private boolean stopped; + + public MyReferenceService(ServiceConfig config) { + this.config = config; + } + + public static void shutdownAndAwaitTermination(ExecutorService pool) { + pool.shutdown(); + try { + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { + pool.shutdownNow(); + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { + log.error("Pool did not terminate"); + } + } + } catch (InterruptedException ex) { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + public static void main(String[] args) throws IOException { + Map nodes = new HashMap<>(); + int nodePort = 8080; + for (int i = 0; i < 3; i++) { + nodes.put(nodePort, LOCALHOST_PREFIX + nodePort); + nodePort += 10; + } + + List clusterUrls = new ArrayList<>(nodes.values()); + List clusterConfs = new ArrayList<>(); + for (Map.Entry entry : nodes.entrySet()) { + int port = entry.getKey(); + String url = entry.getValue(); + Path path = Paths.get("tmp/db/" + port); + Files.createDirectories(path); + ServiceConfig serviceConfig = new ServiceConfig(port, + url, + clusterUrls, + path); + clusterConfs.add(serviceConfig); + } + + for (ServiceConfig serviceConfig : clusterConfs) { + MyReferenceService instance = new MyReferenceService(serviceConfig); + try { + instance.start().get(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Interrupted exception while start instance"); + } catch (ExecutionException | TimeoutException e) { + throw new IllegalArgumentException(e); + } + } + } + + @Override + public synchronized CompletableFuture start() throws IOException { + dao = new ReferenceDao(new Config(config.workingDir(), FLUSHING_THRESHOLD_BYTES)); + server = new MyReferenceServer(config, dao); + server.start(); + stopped = false; + return CompletableFuture.completedFuture(null); + } + + @Override + public synchronized CompletableFuture stop() throws IOException { + if (stopped) { + return CompletableFuture.completedFuture(null); + } + try { + server.stop(); + + } finally { + dao.close(); + } + stopped = true; + return CompletableFuture.completedFuture(null); + } + + @ServiceFactory(stage = 5) + public static class Factory implements ServiceFactory.Factory { + + @Override + public Service create(ServiceConfig config) { + return new MyReferenceService(config); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/NewFactory.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/NewFactory.java deleted file mode 100644 index 1bbce254b..000000000 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/NewFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.vk.itmo.test.georgiidalbeev; - -import ru.vk.itmo.Service; -import ru.vk.itmo.ServiceConfig; -import ru.vk.itmo.test.ServiceFactory; - -@ServiceFactory(stage = 3) -public class NewFactory implements ServiceFactory.Factory { - - @Override - public Service create(ServiceConfig config) { - return new NewService(config); - } -} diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/NewResponseCodes.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/NewResponseCodes.java deleted file mode 100644 index 61644e693..000000000 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/NewResponseCodes.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.vk.itmo.test.georgiidalbeev; - -public enum NewResponseCodes { - TOO_MANY_REQUESTS("429 Too Many Requests"); - - private final String code; - - NewResponseCodes(String code) { - this.code = code; - } - - public String getCode() { - return code; - } -} - diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/NewServer.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/NewServer.java deleted file mode 100644 index 671cb4d18..000000000 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/NewServer.java +++ /dev/null @@ -1,233 +0,0 @@ -package ru.vk.itmo.test.georgiidalbeev; - -import one.nio.http.HttpException; -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; -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 java.io.IOException; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; - -public class NewServer extends HttpServer { - - private static final String PATH = "/v0/entity"; - private static final long MAX_RESPONSE_TIME = TimeUnit.SECONDS.toNanos(1); - private final Dao> dao; - private final ExecutorService executorService; - private final Logger log = LoggerFactory.getLogger(NewServer.class); - private final Map httpClients; - private final ConsistentHashing consistentHashing; - private final String selfUrl; - - public NewServer(ServiceConfig config, - Dao> dao, - ExecutorService executorService, - List clusterUrls, - List httpClients - ) throws IOException { - super(configureServer(config)); - this.dao = dao; - this.executorService = executorService; - this.consistentHashing = new ConsistentHashing<>(Hash::murmur3, clusterUrls); - this.selfUrl = config.selfUrl(); - this.httpClients = new HashMap<>(); - for (int i = 0; i < clusterUrls.size(); i++) { - this.httpClients.put(clusterUrls.get(i), httpClients.get(i)); - } - } - - private static HttpServerConfig configureServer(ServiceConfig serviceConfig) { - HttpServerConfig serverConfig = new HttpServerConfig(); - AcceptorConfig acceptorConfig = new AcceptorConfig(); - acceptorConfig.port = serviceConfig.selfPort(); - acceptorConfig.reusePort = true; - - serverConfig.acceptors = new AcceptorConfig[]{acceptorConfig}; - serverConfig.closeSessions = true; - return serverConfig; - } - - @Path(PATH) - @RequestMethod(Request.METHOD_PUT) - public Response putEntity(@Param(value = "id", required = true) String id, Request request) throws IOException { - MemorySegment key = validateId(id); - if (key == null) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); - } - - String node = consistentHashing.getNode(id); - if (!node.equals(this.selfUrl)) { - return proxyRequest(request, node, id, request.getBody()); - } - - Entry entry = new BaseEntry<>( - key, - MemorySegment.ofArray(request.getBody()) - ); - - dao.upsert(entry); - - return new Response(Response.CREATED, Response.EMPTY); - } - - @Path(PATH) - @RequestMethod(Request.METHOD_GET) - public Response getEntity(@Param(value = "id", required = true) String id, Request request) throws IOException { - MemorySegment key = validateId(id); - if (key == null) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); - } - - String node = consistentHashing.getNode(id); - if (!node.equals(this.selfUrl)) { - return proxyRequest(request, node, id, null); - } - - Entry entry = dao.get(key); - - if (entry == null) { - return new Response(Response.NOT_FOUND, Response.EMPTY); - } - - return Response.ok(entry.value().toArray(ValueLayout.JAVA_BYTE)); - } - - @Path(PATH) - @RequestMethod(Request.METHOD_DELETE) - public Response deleteEntity(@Param(value = "id", required = true) String id, Request request) throws IOException { - MemorySegment key = validateId(id); - if (key == null) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); - } - - String node = consistentHashing.getNode(id); - if (!node.equals(this.selfUrl)) { - return proxyRequest(request, node, id, null); - } - - Entry entry = new BaseEntry<>( - key, - null - ); - - dao.upsert(entry); - - return new Response(Response.ACCEPTED, Response.EMPTY); - } - - @Path(PATH) - public Response otherMethods() { - return new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); - } - - @Override - public void handleDefault(Request request, HttpSession session) throws IOException { - Response response = new Response(Response.BAD_REQUEST, Response.EMPTY); - session.sendResponse(response); - } - - private MemorySegment validateId(String id) { - return (id == null || id.isEmpty()) ? null : MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)); - } - - @Override - public void handleRequest(Request request, HttpSession session) throws IOException { - long createdAt = System.nanoTime(); - try { - executorService.execute( - () -> { - if (System.nanoTime() - createdAt > MAX_RESPONSE_TIME) { - try { - session.sendResponse(new Response(Response.REQUEST_TIMEOUT, Response.EMPTY)); - return; - } catch (IOException e) { - log.error("Exception while handing request", e); - session.scheduleClose(); - return; - } - } - - try { - super.handleRequest(request, session); - } catch (Exception e) { - handleRequestException(session, e); - } - } - ); - } catch (RejectedExecutionException e) { - log.error("Pool queue overflow", e); - session.sendResponse(new Response(NewResponseCodes.TOO_MANY_REQUESTS.getCode(), Response.EMPTY)); - } catch (Exception e) { - handleRequestException(session, e); - } - } - - private void handleRequestException(HttpSession session, Exception e) { - try { - if (e instanceof HttpException) { - session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); - return; - } - session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); - } catch (IOException ex) { - log.error("Exception while handing request", e); - session.scheduleClose(); - } - } - - private Response proxyRequest(Request request, String node, String id, byte[] body) throws IOException { - HttpClient httpClient = httpClients.get(node); - HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() - .uri(URI.create(node + PATH + "?id=" + id)); - - switch (request.getMethod()) { - case Request.METHOD_GET: - requestBuilder.GET(); - break; - case Request.METHOD_PUT: - requestBuilder.PUT(HttpRequest.BodyPublishers.ofByteArray(body)); - break; - case Request.METHOD_DELETE: - requestBuilder.DELETE(); - break; - default: - return new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); - } - - HttpRequest httpRequest = requestBuilder.build(); - - try { - HttpResponse response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray()); - return new Response(String.valueOf(response.statusCode()), response.body()); - } catch (InterruptedException e) { - log.error("Exception while sending request", e); - Thread.currentThread().interrupt(); - return new Response(Response.INTERNAL_ERROR, Response.EMPTY); - } - } -} diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/NewService.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/NewService.java deleted file mode 100644 index 99cd9501c..000000000 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/NewService.java +++ /dev/null @@ -1,95 +0,0 @@ -package ru.vk.itmo.test.georgiidalbeev; - -import one.nio.async.CustomThreadFactory; -import ru.vk.itmo.Service; -import ru.vk.itmo.ServiceConfig; -import ru.vk.itmo.dao.Config; -import ru.vk.itmo.dao.Dao; -import ru.vk.itmo.dao.Entry; -import ru.vk.itmo.test.georgiidalbeev.dao.ReferenceDao; - -import java.io.IOException; -import java.lang.foreign.MemorySegment; -import java.net.http.HttpClient; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -public class NewService implements Service { - - private static final long FLUSH_THRESHOLD = 512 * 1024; - private static final int THREADS = 16; - private static final int KEEP_ALIVE_TIME_SECONDS = 60; - private static final int AWAIT_TERMINATION_SECONDS = 60; - private static final int QUEUE_SIZE = 1000; - private volatile boolean isStopped; - private final ServiceConfig config; - private NewServer server; - private Dao> dao; - private ExecutorService executorService; - private List httpClients; - - public NewService(ServiceConfig config) { - this.config = config; - } - - @Override - public CompletableFuture start() throws IOException { - dao = new ReferenceDao(new Config(config.workingDir(), FLUSH_THRESHOLD)); - executorService = createPool(); - - httpClients = config.clusterUrls().stream() - .map(url -> HttpClient.newHttpClient()) - .collect(Collectors.toList()); - - server = new NewServer(config, dao, executorService, config.clusterUrls(), httpClients); - server.start(); - return CompletableFuture.completedFuture(null); - } - - @Override - public CompletableFuture stop() throws IOException { - if (!isStopped) { - server.stop(); - isStopped = true; - } - shutdownExecutorService(); - - for (HttpClient httpClient : httpClients) { - httpClient.close(); - } - - dao.close(); - return CompletableFuture.completedFuture(null); - } - - private void shutdownExecutorService() { - executorService.shutdown(); - try { - if (!executorService.awaitTermination(AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS)) { - executorService.shutdownNow(); - } - } catch (InterruptedException ex) { - executorService.shutdownNow(); - Thread.currentThread().interrupt(); - } - } - - private ThreadPoolExecutor createPool() { - ThreadPoolExecutor pool = new ThreadPoolExecutor( - THREADS, - THREADS, - KEEP_ALIVE_TIME_SECONDS, - TimeUnit.SECONDS, - new ArrayBlockingQueue<>(QUEUE_SIZE), - new CustomThreadFactory("PoolThread", true), - new ThreadPoolExecutor.AbortPolicy() - ); - pool.prestartAllCoreThreads(); - return pool; - } -} diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/Server.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/Server.java deleted file mode 100644 index a2ec5454e..000000000 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/Server.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.vk.itmo.test.georgiidalbeev; - -import ru.vk.itmo.ServiceConfig; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; - -public final class Server { - - private static final String DIRECTORY = "tmp/dao1"; - - private Server() { - - } - - @SuppressWarnings("FutureReturnValueIgnored") - public static void main(String[] args) throws IOException { - Path directoryPath = Paths.get(DIRECTORY); - if (!Files.exists(directoryPath)) { - Files.createDirectories(directoryPath); - } - - NewService service = new NewService( - new ServiceConfig( - 8080, - "http://localhost:8080", - List.of("http://localhost:8080", "http://localhost:8081", "http://localhost:8082"), - directoryPath - ) - ); - - service.start(); - } -} diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/LiveFilteringIterator.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/LiveFilteringIterator.java index 86976013a..c3ef7ead4 100644 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/LiveFilteringIterator.java +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/LiveFilteringIterator.java @@ -11,18 +11,18 @@ * * @author incubos */ -final class LiveFilteringIterator implements Iterator> { - private final Iterator> delegate; - private Entry next; +final class LiveFilteringIterator implements Iterator> { + private final Iterator> delegate; + private ReferenceBaseEntry 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 ReferenceBaseEntry entry = delegate.next(); if (entry.value() != null) { this.next = entry; break; @@ -36,13 +36,13 @@ public boolean hasNext() { } @Override - public Entry next() { + public ReferenceBaseEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } // Consume - final Entry result = next; + final ReferenceBaseEntry result = next; next = null; skipTombstones(); diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/MemTable.java index 5eff34656..1422f42c8 100644 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/MemTable.java +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/MemTable.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.georgiidalbeev.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) { + ReferenceBaseEntry get(final MemorySegment key) { return map.get(key); } - Entry upsert(final Entry entry) { + ReferenceBaseEntry upsert(final ReferenceBaseEntry entry) { return map.put(entry.key(), entry); } } diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/MergingEntryIterator.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/MergingEntryIterator.java index 3ad8522f5..b0ff9cbfd 100644 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/MergingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/MergingEntryIterator.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.georgiidalbeev.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 ReferenceBaseEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } final WeightedPeekingEntryIterator top = iterators.remove(); - final Entry result = top.next(); + final ReferenceBaseEntry 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 ReferenceBaseEntry 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/georgiidalbeev/dao/ReferenceBaseEntry.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/ReferenceBaseEntry.java new file mode 100644 index 000000000..a27c0b4ae --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/ReferenceBaseEntry.java @@ -0,0 +1,10 @@ +package ru.vk.itmo.test.georgiidalbeev.dao; + +import ru.vk.itmo.dao.Entry; + +public record ReferenceBaseEntry(D key, D value, long timestamp) implements Entry { + @Override + public String toString() { + return "{" + key + ":" + value + "," + timestamp + "}"; + } +} diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/ReferenceDao.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/ReferenceDao.java index 5097c1541..d5e0326fe 100644 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/ReferenceDao.java +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/ReferenceDao.java @@ -22,7 +22,7 @@ * * @author incubos */ -public class ReferenceDao implements Dao> { +public class ReferenceDao implements Dao> { private final Config config; private final Arena arena; @@ -63,7 +63,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 +73,13 @@ public Iterator> get( } @Override - public Entry get(final MemorySegment key) { + public ReferenceBaseEntry 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 ReferenceBaseEntry entry) { final boolean autoFlush; lock.readLock().lock(); try { diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/SSTable.java index dde19cc32..8a23f682d 100644 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/SSTable.java +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/SSTable.java @@ -1,8 +1,5 @@ package ru.vk.itmo.test.georgiidalbeev.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; @@ -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) { + ReferenceBaseEntry get(final MemorySegment key) { final long entry = entryBinarySearch(key); if (entry < 0) { return null; @@ -143,20 +140,23 @@ Entry get(final MemorySegment key) { // Skip key (will reuse the argument) long offset = entryOffset(entry); offset += Long.BYTES + key.byteSize(); + // Extract timestamp + long timestamp = data.get(ValueLayout.OfLong.JAVA_LONG_UNALIGNED, offset); + offset += Long.BYTES; // Extract value length final long valueLength = getLength(offset); if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { // Tombstone encountered - return new BaseEntry<>(key, null); + return new ReferenceBaseEntry<>(key, null, timestamp); } else { // Get value offset += Long.BYTES; final MemorySegment value = data.asSlice(offset, valueLength); - return new BaseEntry<>(key, value); + return new ReferenceBaseEntry<>(key, value, timestamp); } } - private final class SliceIterator implements Iterator> { + private final class SliceIterator implements Iterator> { private long offset; private final long toOffset; @@ -173,7 +173,7 @@ public boolean hasNext() { } @Override - public Entry next() { + public ReferenceBaseEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } @@ -186,6 +186,10 @@ public Entry next() { final MemorySegment key = data.asSlice(offset, keyLength); offset += keyLength; + // Read timestamp + long timestamp = data.get(ValueLayout.OfLong.JAVA_LONG_UNALIGNED, offset); + offset += Long.BYTES; + // Read value length final long valueLength = getLength(offset); offset += Long.BYTES; @@ -193,11 +197,11 @@ public Entry next() { // Read value if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { // Tombstone encountered - return new BaseEntry<>(key, null); + return new ReferenceBaseEntry<>(key, null, timestamp); } else { final MemorySegment value = data.asSlice(offset, valueLength); offset += valueLength; - return new BaseEntry<>(key, value); + return new ReferenceBaseEntry<>(key, value, timestamp); } } } diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/SSTableWriter.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/SSTableWriter.java index 196527145..2be2b1bd4 100644 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/SSTableWriter.java +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/SSTableWriter.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.georgiidalbeev.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 ReferenceBaseEntry} {@link Iterator} to SSTable on disk. * *

Index file {@code .index} contains {@code long} offsets to entries in data file: * {@code [offset0, offset1, ...]} @@ -40,7 +38,7 @@ final class SSTableWriter { void write( final Path baseDir, final int sequence, - final Iterator> entries) throws IOException { + final Iterator> entries) throws IOException { // Write to temporary files final Path tempIndexName = SSTables.tempIndexName(baseDir, sequence); final Path tempDataName = SSTables.tempDataName(baseDir, sequence); @@ -71,7 +69,7 @@ void write( writeLong(entryOffset, index); // Then write the entry - final Entry entry = entries.next(); + final ReferenceBaseEntry entry = entries.next(); entryOffset += writeEntry(entry, data); } } @@ -127,15 +125,16 @@ private void writeSegment( } /** - * Writes {@link Entry} to {@link FileChannel}. + * Writes {@link ReferenceBaseEntry} to {@link FileChannel}. * * @return written bytes */ private long writeEntry( - final Entry entry, + final ReferenceBaseEntry 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 @@ -146,6 +145,10 @@ private long writeEntry( writeSegment(key, os); result += key.byteSize(); + // timestamp + writeLong(timestamp, os); + result += Long.BYTES; + // Value size and possibly value if (value == null) { // Tombstone diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/TableSet.java index cb908eee1..b6034666a 100644 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/TableSet.java +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/TableSet.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.georgiidalbeev.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; import java.util.ArrayList; import java.util.Collections; @@ -98,14 +96,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 +114,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 +127,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,14 +144,14 @@ Iterator> get( }; } - Entry get(final MemorySegment key) { + ReferenceBaseEntry get(final MemorySegment key) { // Slightly optimized version not to pollute the heap // First check MemTable - Entry result = memTable.get(key); + ReferenceBaseEntry result = memTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } // Then check flushing @@ -161,7 +159,7 @@ Entry get(final MemorySegment key) { result = flushingTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } } @@ -170,7 +168,7 @@ Entry get(final MemorySegment key) { result = ssTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } } @@ -178,21 +176,17 @@ Entry get(final MemorySegment key) { return null; } - private static Entry swallowTombstone(final Entry entry) { - return entry.value() == null ? null : entry; - } - - Entry upsert(final Entry entry) { + ReferenceBaseEntry upsert(final ReferenceBaseEntry 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( diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/WeightedPeekingEntryIterator.java b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/WeightedPeekingEntryIterator.java index 551e9d90b..6d843a27f 100644 --- a/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/WeightedPeekingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/dao/WeightedPeekingEntryIterator.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.georgiidalbeev.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 ReferenceBaseEntry 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 ReferenceBaseEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } - final Entry result = next; + final ReferenceBaseEntry result = next; next = delegate.hasNext() ? delegate.next() : null; return result; } - Entry peek() { + ReferenceBaseEntry peek() { if (!hasNext()) { throw new NoSuchElementException(); } diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/get_alloc.html b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/get_alloc.html new file mode 100644 index 000000000..4381ca633 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/get_alloc.html @@ -0,0 +1,25771 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/get_cpu.html b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/get_cpu.html new file mode 100644 index 000000000..a2b719f9d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/get_cpu.html @@ -0,0 +1,23590 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/get_lock.html b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/get_lock.html new file mode 100644 index 000000000..0a7655e0c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/get_lock.html @@ -0,0 +1,4892 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/put_alloc.html b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/put_alloc.html new file mode 100644 index 000000000..b3220fadf --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/put_alloc.html @@ -0,0 +1,26726 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/put_cpu.html b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/put_cpu.html new file mode 100644 index 000000000..eaa90ded0 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/put_cpu.html @@ -0,0 +1,25040 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/put_lock.html b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/put_lock.html new file mode 100644 index 000000000..81e5b3b66 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/async_profiler_result/put_lock.html @@ -0,0 +1,4851 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/scripts/fill.lua b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/scripts/fill.lua new file mode 100644 index 000000000..d3573bceb --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/scripts/fill.lua @@ -0,0 +1,20 @@ +local random = require("math").random +local char = require("string").char + +local function randomString(n) + local result = {} + for i = 1, n do + result[i] = char(random(33, 126)) -- ASCII символы с 33 по 126 + end + return table.concat(result) +end + +counter = 0 +function request() + counter = counter + 1 + local headers = {} + headers["Host"] = "localhost:8080" + local key = "key" .. counter + local value = "value$" .. randomString(100) + return wrk.format("PUT", "/v0/entity?id=" .. key .. "&ack=3&from=3", headers, value) +end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/scripts/get.lua b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/scripts/get.lua new file mode 100644 index 000000000..0dd0303ac --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/scripts/get.lua @@ -0,0 +1,7 @@ +function request() + local headers = {} + headers["Host"] = "localhost:8080" + local id = math.random(1, 10000) + local key = "key" .. id + return wrk.format("GET", "/v0/entity?id=" .. key .. "&ack=3&from=3", headers) +end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/full_result/full_get_result.txt b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/full_result/full_get_result.txt new file mode 100644 index 000000000..13354bce7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/full_result/full_get_result.txt @@ -0,0 +1,108 @@ + Thread Stats Avg Stdev Max +/- Stdev + Latency 44.91ms 33.99ms 142.85ms 52.62% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 44.61ms + 75.000% 74.24ms + 90.000% 90.62ms + 99.000% 114.50ms + 99.900% 129.15ms + 99.990% 138.24ms + 99.999% 141.57ms +100.000% 142.98ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.664 0.000000 1 1.00 + 3.191 0.100000 8305 1.11 + 5.719 0.200000 16611 1.25 + 14.503 0.300000 24912 1.43 + 28.143 0.400000 33212 1.67 + 44.607 0.500000 41518 2.00 + 52.511 0.550000 45672 2.22 + 58.911 0.600000 49835 2.50 + 64.319 0.650000 53979 2.86 + 69.375 0.700000 58171 3.33 + 74.239 0.750000 62318 4.00 + 76.799 0.775000 64399 4.44 + 79.167 0.800000 66452 5.00 + 81.791 0.825000 68508 5.71 + 84.671 0.850000 70591 6.67 + 87.487 0.875000 72666 8.00 + 89.087 0.887500 73733 8.89 + 90.623 0.900000 74735 10.00 + 92.351 0.912500 75785 11.43 + 93.951 0.925000 76812 13.33 + 95.935 0.937500 77871 16.00 + 97.023 0.943750 78377 17.78 + 98.239 0.950000 78890 20.00 + 99.583 0.956250 79398 22.86 + 101.247 0.962500 79921 26.67 + 103.039 0.968750 80444 32.00 + 104.063 0.971875 80699 35.56 + 105.151 0.975000 80961 40.00 + 106.431 0.978125 81217 45.71 + 107.967 0.981250 81481 53.33 + 110.079 0.984375 81735 64.00 + 111.487 0.985938 81870 71.11 + 112.639 0.987500 81994 80.00 + 113.791 0.989062 82122 91.43 + 115.135 0.990625 82257 106.67 + 116.287 0.992188 82384 128.00 + 116.991 0.992969 82447 142.22 + 117.695 0.993750 82513 160.00 + 118.399 0.994531 82576 182.86 + 119.487 0.995313 82644 213.33 + 120.767 0.996094 82708 256.00 + 121.343 0.996484 82740 284.44 + 122.111 0.996875 82774 320.00 + 122.751 0.997266 82803 365.71 + 123.711 0.997656 82837 426.67 + 124.671 0.998047 82869 512.00 + 125.503 0.998242 82885 568.89 + 126.399 0.998437 82903 640.00 + 127.167 0.998633 82917 731.43 + 128.063 0.998828 82934 853.33 + 129.343 0.999023 82949 1024.00 + 130.175 0.999121 82958 1137.78 + 131.071 0.999219 82966 1280.00 + 131.711 0.999316 82974 1462.86 + 132.479 0.999414 82982 1706.67 + 133.759 0.999512 82990 2048.00 + 134.271 0.999561 82995 2275.56 + 134.655 0.999609 82998 2560.00 + 135.167 0.999658 83002 2925.71 + 135.679 0.999707 83007 3413.33 + 136.063 0.999756 83010 4096.00 + 136.319 0.999780 83012 4551.11 + 136.447 0.999805 83014 5120.00 + 136.959 0.999829 83016 5851.43 + 137.855 0.999854 83018 6826.67 + 137.983 0.999878 83020 8192.00 + 138.239 0.999890 83023 9102.22 + 138.239 0.999902 83023 10240.00 + 138.239 0.999915 83023 11702.86 + 138.495 0.999927 83024 13653.33 + 139.391 0.999939 83025 16384.00 + 139.775 0.999945 83026 18204.44 + 139.775 0.999951 83026 20480.00 + 140.159 0.999957 83027 23405.71 + 140.159 0.999963 83027 27306.67 + 140.287 0.999969 83028 32768.00 + 140.287 0.999973 83028 36408.89 + 140.287 0.999976 83028 40960.00 + 141.567 0.999979 83029 46811.43 + 141.567 0.999982 83029 54613.33 + 141.567 0.999985 83029 65536.00 + 141.567 0.999986 83029 72817.78 + 141.567 0.999988 83029 81920.00 + 142.975 0.999989 83030 93622.86 + 142.975 1.000000 83030 inf +#[Mean = 44.908, StdDeviation = 33.987] +#[Max = 142.848, Total count = 83030] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 94196 requests in 9.88s, 15.27MB read +Requests/sec: 9532.71 +Transfer/sec: 1.55MB diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/full_result/full_get_result_ack2.txt b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/full_result/full_get_result_ack2.txt new file mode 100644 index 000000000..817261331 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/full_result/full_get_result_ack2.txt @@ -0,0 +1,111 @@ + Thread Stats Avg Stdev Max +/- Stdev + Latency 96.85ms 42.12ms 217.22ms 70.21% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 95.93ms + 75.000% 126.59ms + 90.000% 150.01ms + 99.000% 188.67ms + 99.900% 209.41ms + 99.990% 215.55ms + 99.999% 216.83ms +100.000% 217.34ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.410 0.000000 1 1.00 + 33.247 0.100000 13618 1.11 + 68.671 0.200000 27257 1.25 + 79.807 0.300000 40869 1.43 + 87.871 0.400000 54555 1.67 + 95.935 0.500000 68157 2.00 + 100.671 0.550000 74914 2.22 + 106.239 0.600000 81705 2.50 + 112.959 0.650000 88498 2.86 + 119.871 0.700000 95322 3.33 + 126.591 0.750000 102128 4.00 + 129.983 0.775000 105535 4.44 + 133.631 0.800000 108993 5.00 + 137.215 0.825000 112313 5.71 + 141.183 0.850000 115772 6.67 + 145.407 0.875000 119219 8.00 + 147.583 0.887500 120885 8.89 + 150.015 0.900000 122577 10.00 + 152.447 0.912500 124227 11.43 + 155.263 0.925000 125940 13.33 + 158.591 0.937500 127657 16.00 + 160.511 0.943750 128513 17.78 + 162.687 0.950000 129351 20.00 + 164.863 0.956250 130181 22.86 + 167.295 0.962500 131045 26.67 + 170.367 0.968750 131881 32.00 + 172.031 0.971875 132310 35.56 + 173.823 0.975000 132752 40.00 + 175.871 0.978125 133167 45.71 + 178.303 0.981250 133594 53.33 + 181.887 0.984375 134019 64.00 + 183.807 0.985938 134233 71.11 + 185.855 0.987500 134439 80.00 + 187.647 0.989062 134654 91.43 + 189.567 0.990625 134857 106.67 + 191.743 0.992188 135075 128.00 + 193.023 0.992969 135177 142.22 + 194.175 0.993750 135282 160.00 + 195.327 0.994531 135391 182.86 + 197.375 0.995313 135495 213.33 + 199.807 0.996094 135603 256.00 + 201.087 0.996484 135658 284.44 + 202.111 0.996875 135710 320.00 + 203.391 0.997266 135764 365.71 + 204.543 0.997656 135819 426.67 + 205.695 0.998047 135869 512.00 + 206.207 0.998242 135900 568.89 + 207.103 0.998437 135923 640.00 + 207.743 0.998633 135947 731.43 + 208.639 0.998828 135975 853.33 + 209.535 0.999023 136001 1024.00 + 210.047 0.999121 136015 1137.78 + 210.431 0.999219 136029 1280.00 + 210.943 0.999316 136039 1462.86 + 211.583 0.999414 136053 1706.67 + 212.095 0.999512 136067 2048.00 + 212.607 0.999561 136075 2275.56 + 212.863 0.999609 136079 2560.00 + 213.375 0.999658 136086 2925.71 + 213.759 0.999707 136093 3413.33 + 214.399 0.999756 136099 4096.00 + 214.655 0.999780 136105 4551.11 + 214.911 0.999805 136110 5120.00 + 214.911 0.999829 136110 5851.43 + 215.167 0.999854 136113 6826.67 + 215.423 0.999878 136116 8192.00 + 215.551 0.999890 136119 9102.22 + 215.551 0.999902 136119 10240.00 + 215.679 0.999915 136121 11702.86 + 215.935 0.999927 136123 13653.33 + 216.063 0.999939 136125 16384.00 + 216.063 0.999945 136125 18204.44 + 216.319 0.999951 136127 20480.00 + 216.319 0.999957 136127 23405.71 + 216.447 0.999963 136129 27306.67 + 216.447 0.999969 136129 32768.00 + 216.447 0.999973 136129 36408.89 + 216.447 0.999976 136129 40960.00 + 216.703 0.999979 136130 46811.43 + 216.703 0.999982 136130 54613.33 + 216.703 0.999985 136130 65536.00 + 216.831 0.999986 136131 72817.78 + 216.831 0.999988 136131 81920.00 + 216.831 0.999989 136131 93622.86 + 216.831 0.999991 136131 109226.67 + 216.831 0.999992 136131 131072.00 + 217.343 0.999993 136132 145635.56 + 217.343 1.000000 136132 inf +#[Mean = 96.854, StdDeviation = 42.125] +#[Max = 217.216, Total count = 136132] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 142960 requests in 9.89s, 23.18MB read +Requests/sec: 14452.83 +Transfer/sec: 2.34MB diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/full_result/full_put_result.txt b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/full_result/full_put_result.txt new file mode 100644 index 000000000..c99ba2991 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/full_result/full_put_result.txt @@ -0,0 +1,108 @@ + Thread Stats Avg Stdev Max +/- Stdev + Latency 37.12ms 47.28ms 190.34ms 80.55% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 8.73ms + 75.000% 73.41ms + 90.000% 119.29ms + 99.000% 162.69ms + 99.900% 178.43ms + 99.990% 187.39ms + 99.999% 190.08ms +100.000% 190.46ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.623 0.000000 1 1.00 + 2.351 0.100000 8234 1.11 + 3.605 0.200000 16462 1.25 + 4.919 0.300000 24698 1.43 + 6.447 0.400000 32938 1.67 + 8.735 0.500000 41162 2.00 + 10.695 0.550000 45272 2.22 + 15.591 0.600000 49376 2.50 + 22.095 0.650000 53493 2.86 + 59.007 0.700000 57613 3.33 + 73.407 0.750000 61732 4.00 + 78.463 0.775000 63788 4.44 + 83.391 0.800000 65841 5.00 + 88.383 0.825000 67895 5.71 + 94.783 0.850000 69952 6.67 + 103.231 0.875000 72007 8.00 + 111.935 0.887500 73040 8.89 + 119.295 0.900000 74067 10.00 + 124.991 0.912500 75093 11.43 + 129.407 0.925000 76133 13.33 + 133.887 0.937500 77181 16.00 + 136.191 0.943750 77668 17.78 + 138.623 0.950000 78181 20.00 + 141.439 0.956250 78707 22.86 + 144.255 0.962500 79208 26.67 + 147.199 0.968750 79727 32.00 + 148.991 0.971875 79979 35.56 + 150.911 0.975000 80247 40.00 + 153.087 0.978125 80501 45.71 + 155.519 0.981250 80764 53.33 + 157.695 0.984375 81011 64.00 + 158.975 0.985938 81144 71.11 + 160.383 0.987500 81275 80.00 + 161.663 0.989062 81401 91.43 + 163.455 0.990625 81536 106.67 + 165.631 0.992188 81658 128.00 + 166.911 0.992969 81722 142.22 + 168.319 0.993750 81780 160.00 + 169.855 0.994531 81843 182.86 + 171.391 0.995313 81911 213.33 + 172.927 0.996094 81973 256.00 + 173.567 0.996484 82006 284.44 + 174.079 0.996875 82038 320.00 + 174.591 0.997266 82073 365.71 + 175.487 0.997656 82110 426.67 + 176.127 0.998047 82140 512.00 + 176.511 0.998242 82155 568.89 + 177.023 0.998437 82166 640.00 + 177.663 0.998633 82187 731.43 + 178.047 0.998828 82201 853.33 + 178.559 0.999023 82216 1024.00 + 178.815 0.999121 82221 1137.78 + 179.327 0.999219 82230 1280.00 + 179.839 0.999316 82237 1462.86 + 180.735 0.999414 82246 1706.67 + 181.503 0.999512 82253 2048.00 + 182.143 0.999561 82259 2275.56 + 182.783 0.999609 82261 2560.00 + 183.423 0.999658 82265 2925.71 + 184.063 0.999707 82270 3413.33 + 184.447 0.999756 82273 4096.00 + 184.959 0.999780 82275 4551.11 + 185.087 0.999805 82277 5120.00 + 185.215 0.999829 82279 5851.43 + 185.855 0.999854 82282 6826.67 + 186.495 0.999878 82283 8192.00 + 187.263 0.999890 82284 9102.22 + 187.391 0.999902 82285 10240.00 + 187.519 0.999915 82286 11702.86 + 187.647 0.999927 82287 13653.33 + 188.159 0.999939 82288 16384.00 + 188.799 0.999945 82289 18204.44 + 188.799 0.999951 82289 20480.00 + 188.927 0.999957 82290 23405.71 + 188.927 0.999963 82290 27306.67 + 189.183 0.999969 82291 32768.00 + 189.183 0.999973 82291 36408.89 + 189.183 0.999976 82291 40960.00 + 190.079 0.999979 82292 46811.43 + 190.079 0.999982 82292 54613.33 + 190.079 0.999985 82292 65536.00 + 190.079 0.999986 82292 72817.78 + 190.079 0.999988 82292 81920.00 + 190.463 0.999989 82293 93622.86 + 190.463 1.000000 82293 inf +#[Mean = 37.116, StdDeviation = 47.281] +#[Max = 190.336, Total count = 82293] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 95902 requests in 9.90s, 6.13MB read +Requests/sec: 9683.85 +Transfer/sec: 633.61KB diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/full_result/full_put_result_ack2.txt b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/full_result/full_put_result_ack2.txt new file mode 100644 index 000000000..adb0cc17f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/full_result/full_put_result_ack2.txt @@ -0,0 +1,111 @@ + Thread Stats Avg Stdev Max +/- Stdev + Latency 68.08ms 40.66ms 183.94ms 65.04% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 66.05ms + 75.000% 95.29ms + 90.000% 124.74ms + 99.000% 163.33ms + 99.900% 175.74ms + 99.990% 181.63ms + 99.999% 183.55ms +100.000% 184.06ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.405 0.000000 1 1.00 + 16.943 0.100000 14186 1.11 + 29.231 0.200000 28361 1.25 + 40.927 0.300000 42547 1.43 + 53.343 0.400000 56736 1.67 + 66.047 0.500000 70967 2.00 + 72.191 0.550000 78066 2.22 + 77.823 0.600000 85128 2.50 + 83.839 0.650000 92211 2.86 + 89.535 0.700000 99264 3.33 + 95.295 0.750000 106380 4.00 + 98.687 0.775000 109914 4.44 + 102.655 0.800000 113450 5.00 + 107.199 0.825000 117012 5.71 + 112.127 0.850000 120524 6.67 + 117.887 0.875000 124081 8.00 + 121.023 0.887500 125835 8.89 + 124.735 0.900000 127615 10.00 + 128.703 0.912500 129398 11.43 + 132.863 0.925000 131152 13.33 + 137.983 0.937500 132921 16.00 + 140.927 0.943750 133813 17.78 + 143.743 0.950000 134702 20.00 + 146.431 0.956250 135581 22.86 + 149.247 0.962500 136502 26.67 + 151.935 0.968750 137375 32.00 + 153.343 0.971875 137791 35.56 + 154.879 0.975000 138253 40.00 + 156.415 0.978125 138702 45.71 + 158.079 0.981250 139154 53.33 + 159.743 0.984375 139566 64.00 + 160.639 0.985938 139790 71.11 + 161.663 0.987500 140032 80.00 + 162.687 0.989062 140244 91.43 + 163.711 0.990625 140451 106.67 + 164.991 0.992188 140680 128.00 + 165.631 0.992969 140783 142.22 + 166.399 0.993750 140907 160.00 + 167.167 0.994531 141012 182.86 + 168.191 0.995313 141119 213.33 + 169.343 0.996094 141228 256.00 + 169.983 0.996484 141283 284.44 + 170.623 0.996875 141346 320.00 + 171.519 0.997266 141394 365.71 + 172.287 0.997656 141450 426.67 + 173.055 0.998047 141503 512.00 + 173.439 0.998242 141529 568.89 + 174.079 0.998437 141561 640.00 + 174.463 0.998633 141586 731.43 + 175.103 0.998828 141614 853.33 + 175.743 0.999023 141641 1024.00 + 176.127 0.999121 141657 1137.78 + 176.511 0.999219 141669 1280.00 + 176.895 0.999316 141682 1462.86 + 177.407 0.999414 141699 1706.67 + 177.791 0.999512 141709 2048.00 + 178.303 0.999561 141717 2275.56 + 178.943 0.999609 141725 2560.00 + 179.199 0.999658 141734 2925.71 + 179.327 0.999707 141737 3413.33 + 179.967 0.999756 141746 4096.00 + 180.095 0.999780 141748 4551.11 + 180.223 0.999805 141751 5120.00 + 180.351 0.999829 141755 5851.43 + 180.991 0.999854 141758 6826.67 + 181.503 0.999878 141762 8192.00 + 181.631 0.999890 141764 9102.22 + 181.759 0.999902 141766 10240.00 + 181.759 0.999915 141766 11702.86 + 181.887 0.999927 141768 13653.33 + 182.271 0.999939 141770 16384.00 + 182.655 0.999945 141771 18204.44 + 182.783 0.999951 141772 20480.00 + 182.783 0.999957 141772 23405.71 + 182.911 0.999963 141773 27306.67 + 183.039 0.999969 141774 32768.00 + 183.423 0.999973 141776 36408.89 + 183.423 0.999976 141776 40960.00 + 183.423 0.999979 141776 46811.43 + 183.423 0.999982 141776 54613.33 + 183.423 0.999985 141776 65536.00 + 183.551 0.999986 141777 72817.78 + 183.551 0.999988 141777 81920.00 + 183.551 0.999989 141777 93622.86 + 183.551 0.999991 141777 109226.67 + 183.551 0.999992 141777 131072.00 + 184.063 0.999993 141778 145635.56 + 184.063 1.000000 141778 inf +#[Mean = 68.085, StdDeviation = 40.657] +#[Max = 183.936, Total count = 141778] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 143025 requests in 9.88s, 9.14MB read +Requests/sec: 14478.74 +Transfer/sec: 0.93MB diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/get.png b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/get.png new file mode 100644 index 000000000..eca738eee Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/get.png differ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/get_ack2.png b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/get_ack2.png new file mode 100644 index 000000000..03af36c9b Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/get_ack2.png differ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/get_result.txt b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/get_result.txt new file mode 100644 index 000000000..7f55f1485 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/get_result.txt @@ -0,0 +1,87 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.664 0.000000 1 1.00 + 3.191 0.100000 8305 1.11 + 5.719 0.200000 16611 1.25 + 14.503 0.300000 24912 1.43 + 28.143 0.400000 33212 1.67 + 44.607 0.500000 41518 2.00 + 52.511 0.550000 45672 2.22 + 58.911 0.600000 49835 2.50 + 64.319 0.650000 53979 2.86 + 69.375 0.700000 58171 3.33 + 74.239 0.750000 62318 4.00 + 76.799 0.775000 64399 4.44 + 79.167 0.800000 66452 5.00 + 81.791 0.825000 68508 5.71 + 84.671 0.850000 70591 6.67 + 87.487 0.875000 72666 8.00 + 89.087 0.887500 73733 8.89 + 90.623 0.900000 74735 10.00 + 92.351 0.912500 75785 11.43 + 93.951 0.925000 76812 13.33 + 95.935 0.937500 77871 16.00 + 97.023 0.943750 78377 17.78 + 98.239 0.950000 78890 20.00 + 99.583 0.956250 79398 22.86 + 101.247 0.962500 79921 26.67 + 103.039 0.968750 80444 32.00 + 104.063 0.971875 80699 35.56 + 105.151 0.975000 80961 40.00 + 106.431 0.978125 81217 45.71 + 107.967 0.981250 81481 53.33 + 110.079 0.984375 81735 64.00 + 111.487 0.985938 81870 71.11 + 112.639 0.987500 81994 80.00 + 113.791 0.989062 82122 91.43 + 115.135 0.990625 82257 106.67 + 116.287 0.992188 82384 128.00 + 116.991 0.992969 82447 142.22 + 117.695 0.993750 82513 160.00 + 118.399 0.994531 82576 182.86 + 119.487 0.995313 82644 213.33 + 120.767 0.996094 82708 256.00 + 121.343 0.996484 82740 284.44 + 122.111 0.996875 82774 320.00 + 122.751 0.997266 82803 365.71 + 123.711 0.997656 82837 426.67 + 124.671 0.998047 82869 512.00 + 125.503 0.998242 82885 568.89 + 126.399 0.998437 82903 640.00 + 127.167 0.998633 82917 731.43 + 128.063 0.998828 82934 853.33 + 129.343 0.999023 82949 1024.00 + 130.175 0.999121 82958 1137.78 + 131.071 0.999219 82966 1280.00 + 131.711 0.999316 82974 1462.86 + 132.479 0.999414 82982 1706.67 + 133.759 0.999512 82990 2048.00 + 134.271 0.999561 82995 2275.56 + 134.655 0.999609 82998 2560.00 + 135.167 0.999658 83002 2925.71 + 135.679 0.999707 83007 3413.33 + 136.063 0.999756 83010 4096.00 + 136.319 0.999780 83012 4551.11 + 136.447 0.999805 83014 5120.00 + 136.959 0.999829 83016 5851.43 + 137.855 0.999854 83018 6826.67 + 137.983 0.999878 83020 8192.00 + 138.239 0.999890 83023 9102.22 + 138.239 0.999902 83023 10240.00 + 138.239 0.999915 83023 11702.86 + 138.495 0.999927 83024 13653.33 + 139.391 0.999939 83025 16384.00 + 139.775 0.999945 83026 18204.44 + 139.775 0.999951 83026 20480.00 + 140.159 0.999957 83027 23405.71 + 140.159 0.999963 83027 27306.67 + 140.287 0.999969 83028 32768.00 + 140.287 0.999973 83028 36408.89 + 140.287 0.999976 83028 40960.00 + 141.567 0.999979 83029 46811.43 + 141.567 0.999982 83029 54613.33 + 141.567 0.999985 83029 65536.00 + 141.567 0.999986 83029 72817.78 + 141.567 0.999988 83029 81920.00 + 142.975 0.999989 83030 93622.86 + 142.975 1.000000 83030 inf diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/get_result_ack2.txt b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/get_result_ack2.txt new file mode 100644 index 000000000..49ffb554c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/get_result_ack2.txt @@ -0,0 +1,90 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.410 0.000000 1 1.00 + 33.247 0.100000 13618 1.11 + 68.671 0.200000 27257 1.25 + 79.807 0.300000 40869 1.43 + 87.871 0.400000 54555 1.67 + 95.935 0.500000 68157 2.00 + 100.671 0.550000 74914 2.22 + 106.239 0.600000 81705 2.50 + 112.959 0.650000 88498 2.86 + 119.871 0.700000 95322 3.33 + 126.591 0.750000 102128 4.00 + 129.983 0.775000 105535 4.44 + 133.631 0.800000 108993 5.00 + 137.215 0.825000 112313 5.71 + 141.183 0.850000 115772 6.67 + 145.407 0.875000 119219 8.00 + 147.583 0.887500 120885 8.89 + 150.015 0.900000 122577 10.00 + 152.447 0.912500 124227 11.43 + 155.263 0.925000 125940 13.33 + 158.591 0.937500 127657 16.00 + 160.511 0.943750 128513 17.78 + 162.687 0.950000 129351 20.00 + 164.863 0.956250 130181 22.86 + 167.295 0.962500 131045 26.67 + 170.367 0.968750 131881 32.00 + 172.031 0.971875 132310 35.56 + 173.823 0.975000 132752 40.00 + 175.871 0.978125 133167 45.71 + 178.303 0.981250 133594 53.33 + 181.887 0.984375 134019 64.00 + 183.807 0.985938 134233 71.11 + 185.855 0.987500 134439 80.00 + 187.647 0.989062 134654 91.43 + 189.567 0.990625 134857 106.67 + 191.743 0.992188 135075 128.00 + 193.023 0.992969 135177 142.22 + 194.175 0.993750 135282 160.00 + 195.327 0.994531 135391 182.86 + 197.375 0.995313 135495 213.33 + 199.807 0.996094 135603 256.00 + 201.087 0.996484 135658 284.44 + 202.111 0.996875 135710 320.00 + 203.391 0.997266 135764 365.71 + 204.543 0.997656 135819 426.67 + 205.695 0.998047 135869 512.00 + 206.207 0.998242 135900 568.89 + 207.103 0.998437 135923 640.00 + 207.743 0.998633 135947 731.43 + 208.639 0.998828 135975 853.33 + 209.535 0.999023 136001 1024.00 + 210.047 0.999121 136015 1137.78 + 210.431 0.999219 136029 1280.00 + 210.943 0.999316 136039 1462.86 + 211.583 0.999414 136053 1706.67 + 212.095 0.999512 136067 2048.00 + 212.607 0.999561 136075 2275.56 + 212.863 0.999609 136079 2560.00 + 213.375 0.999658 136086 2925.71 + 213.759 0.999707 136093 3413.33 + 214.399 0.999756 136099 4096.00 + 214.655 0.999780 136105 4551.11 + 214.911 0.999805 136110 5120.00 + 214.911 0.999829 136110 5851.43 + 215.167 0.999854 136113 6826.67 + 215.423 0.999878 136116 8192.00 + 215.551 0.999890 136119 9102.22 + 215.551 0.999902 136119 10240.00 + 215.679 0.999915 136121 11702.86 + 215.935 0.999927 136123 13653.33 + 216.063 0.999939 136125 16384.00 + 216.063 0.999945 136125 18204.44 + 216.319 0.999951 136127 20480.00 + 216.319 0.999957 136127 23405.71 + 216.447 0.999963 136129 27306.67 + 216.447 0.999969 136129 32768.00 + 216.447 0.999973 136129 36408.89 + 216.447 0.999976 136129 40960.00 + 216.703 0.999979 136130 46811.43 + 216.703 0.999982 136130 54613.33 + 216.703 0.999985 136130 65536.00 + 216.831 0.999986 136131 72817.78 + 216.831 0.999988 136131 81920.00 + 216.831 0.999989 136131 93622.86 + 216.831 0.999991 136131 109226.67 + 216.831 0.999992 136131 131072.00 + 217.343 0.999993 136132 145635.56 + 217.343 1.000000 136132 inf \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/put.png b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/put.png new file mode 100644 index 000000000..f010094e8 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/put.png differ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/put_ack2.png b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/put_ack2.png new file mode 100644 index 000000000..9563edeca Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/put_ack2.png differ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/put_result.txt b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/put_result.txt new file mode 100644 index 000000000..48c006df1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/put_result.txt @@ -0,0 +1,87 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.623 0.000000 1 1.00 + 2.351 0.100000 8234 1.11 + 3.605 0.200000 16462 1.25 + 4.919 0.300000 24698 1.43 + 6.447 0.400000 32938 1.67 + 8.735 0.500000 41162 2.00 + 10.695 0.550000 45272 2.22 + 15.591 0.600000 49376 2.50 + 22.095 0.650000 53493 2.86 + 59.007 0.700000 57613 3.33 + 73.407 0.750000 61732 4.00 + 78.463 0.775000 63788 4.44 + 83.391 0.800000 65841 5.00 + 88.383 0.825000 67895 5.71 + 94.783 0.850000 69952 6.67 + 103.231 0.875000 72007 8.00 + 111.935 0.887500 73040 8.89 + 119.295 0.900000 74067 10.00 + 124.991 0.912500 75093 11.43 + 129.407 0.925000 76133 13.33 + 133.887 0.937500 77181 16.00 + 136.191 0.943750 77668 17.78 + 138.623 0.950000 78181 20.00 + 141.439 0.956250 78707 22.86 + 144.255 0.962500 79208 26.67 + 147.199 0.968750 79727 32.00 + 148.991 0.971875 79979 35.56 + 150.911 0.975000 80247 40.00 + 153.087 0.978125 80501 45.71 + 155.519 0.981250 80764 53.33 + 157.695 0.984375 81011 64.00 + 158.975 0.985938 81144 71.11 + 160.383 0.987500 81275 80.00 + 161.663 0.989062 81401 91.43 + 163.455 0.990625 81536 106.67 + 165.631 0.992188 81658 128.00 + 166.911 0.992969 81722 142.22 + 168.319 0.993750 81780 160.00 + 169.855 0.994531 81843 182.86 + 171.391 0.995313 81911 213.33 + 172.927 0.996094 81973 256.00 + 173.567 0.996484 82006 284.44 + 174.079 0.996875 82038 320.00 + 174.591 0.997266 82073 365.71 + 175.487 0.997656 82110 426.67 + 176.127 0.998047 82140 512.00 + 176.511 0.998242 82155 568.89 + 177.023 0.998437 82166 640.00 + 177.663 0.998633 82187 731.43 + 178.047 0.998828 82201 853.33 + 178.559 0.999023 82216 1024.00 + 178.815 0.999121 82221 1137.78 + 179.327 0.999219 82230 1280.00 + 179.839 0.999316 82237 1462.86 + 180.735 0.999414 82246 1706.67 + 181.503 0.999512 82253 2048.00 + 182.143 0.999561 82259 2275.56 + 182.783 0.999609 82261 2560.00 + 183.423 0.999658 82265 2925.71 + 184.063 0.999707 82270 3413.33 + 184.447 0.999756 82273 4096.00 + 184.959 0.999780 82275 4551.11 + 185.087 0.999805 82277 5120.00 + 185.215 0.999829 82279 5851.43 + 185.855 0.999854 82282 6826.67 + 186.495 0.999878 82283 8192.00 + 187.263 0.999890 82284 9102.22 + 187.391 0.999902 82285 10240.00 + 187.519 0.999915 82286 11702.86 + 187.647 0.999927 82287 13653.33 + 188.159 0.999939 82288 16384.00 + 188.799 0.999945 82289 18204.44 + 188.799 0.999951 82289 20480.00 + 188.927 0.999957 82290 23405.71 + 188.927 0.999963 82290 27306.67 + 189.183 0.999969 82291 32768.00 + 189.183 0.999973 82291 36408.89 + 189.183 0.999976 82291 40960.00 + 190.079 0.999979 82292 46811.43 + 190.079 0.999982 82292 54613.33 + 190.079 0.999985 82292 65536.00 + 190.079 0.999986 82292 72817.78 + 190.079 0.999988 82292 81920.00 + 190.463 0.999989 82293 93622.86 + 190.463 1.000000 82293 inf diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/put_result_ack2.txt b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/put_result_ack2.txt new file mode 100644 index 000000000..9123c128e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab4/wrk_result/put_result_ack2.txt @@ -0,0 +1,90 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.405 0.000000 1 1.00 + 16.943 0.100000 14186 1.11 + 29.231 0.200000 28361 1.25 + 40.927 0.300000 42547 1.43 + 53.343 0.400000 56736 1.67 + 66.047 0.500000 70967 2.00 + 72.191 0.550000 78066 2.22 + 77.823 0.600000 85128 2.50 + 83.839 0.650000 92211 2.86 + 89.535 0.700000 99264 3.33 + 95.295 0.750000 106380 4.00 + 98.687 0.775000 109914 4.44 + 102.655 0.800000 113450 5.00 + 107.199 0.825000 117012 5.71 + 112.127 0.850000 120524 6.67 + 117.887 0.875000 124081 8.00 + 121.023 0.887500 125835 8.89 + 124.735 0.900000 127615 10.00 + 128.703 0.912500 129398 11.43 + 132.863 0.925000 131152 13.33 + 137.983 0.937500 132921 16.00 + 140.927 0.943750 133813 17.78 + 143.743 0.950000 134702 20.00 + 146.431 0.956250 135581 22.86 + 149.247 0.962500 136502 26.67 + 151.935 0.968750 137375 32.00 + 153.343 0.971875 137791 35.56 + 154.879 0.975000 138253 40.00 + 156.415 0.978125 138702 45.71 + 158.079 0.981250 139154 53.33 + 159.743 0.984375 139566 64.00 + 160.639 0.985938 139790 71.11 + 161.663 0.987500 140032 80.00 + 162.687 0.989062 140244 91.43 + 163.711 0.990625 140451 106.67 + 164.991 0.992188 140680 128.00 + 165.631 0.992969 140783 142.22 + 166.399 0.993750 140907 160.00 + 167.167 0.994531 141012 182.86 + 168.191 0.995313 141119 213.33 + 169.343 0.996094 141228 256.00 + 169.983 0.996484 141283 284.44 + 170.623 0.996875 141346 320.00 + 171.519 0.997266 141394 365.71 + 172.287 0.997656 141450 426.67 + 173.055 0.998047 141503 512.00 + 173.439 0.998242 141529 568.89 + 174.079 0.998437 141561 640.00 + 174.463 0.998633 141586 731.43 + 175.103 0.998828 141614 853.33 + 175.743 0.999023 141641 1024.00 + 176.127 0.999121 141657 1137.78 + 176.511 0.999219 141669 1280.00 + 176.895 0.999316 141682 1462.86 + 177.407 0.999414 141699 1706.67 + 177.791 0.999512 141709 2048.00 + 178.303 0.999561 141717 2275.56 + 178.943 0.999609 141725 2560.00 + 179.199 0.999658 141734 2925.71 + 179.327 0.999707 141737 3413.33 + 179.967 0.999756 141746 4096.00 + 180.095 0.999780 141748 4551.11 + 180.223 0.999805 141751 5120.00 + 180.351 0.999829 141755 5851.43 + 180.991 0.999854 141758 6826.67 + 181.503 0.999878 141762 8192.00 + 181.631 0.999890 141764 9102.22 + 181.759 0.999902 141766 10240.00 + 181.759 0.999915 141766 11702.86 + 181.887 0.999927 141768 13653.33 + 182.271 0.999939 141770 16384.00 + 182.655 0.999945 141771 18204.44 + 182.783 0.999951 141772 20480.00 + 182.783 0.999957 141772 23405.71 + 182.911 0.999963 141773 27306.67 + 183.039 0.999969 141774 32768.00 + 183.423 0.999973 141776 36408.89 + 183.423 0.999976 141776 40960.00 + 183.423 0.999979 141776 46811.43 + 183.423 0.999982 141776 54613.33 + 183.423 0.999985 141776 65536.00 + 183.551 0.999986 141777 72817.78 + 183.551 0.999988 141777 81920.00 + 183.551 0.999989 141777 93622.86 + 183.551 0.999991 141777 109226.67 + 183.551 0.999992 141777 131072.00 + 184.063 0.999993 141778 145635.56 + 184.063 1.000000 141778 inf \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/async_profiler_result/get.jfr b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/async_profiler_result/get.jfr new file mode 100644 index 000000000..86ed4110b Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/async_profiler_result/get.jfr differ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/async_profiler_result/put.jfr b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/async_profiler_result/put.jfr new file mode 100644 index 000000000..729f25adf Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/async_profiler_result/put.jfr differ diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/scripts/fill.lua b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/scripts/fill.lua new file mode 100644 index 000000000..8e33ede77 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/scripts/fill.lua @@ -0,0 +1,20 @@ +local random = require("math").random +local char = require("string").char + +local function randomString(n) + local result = {} + for i = 1, n do + result[i] = char(random(33, 126)) -- ASCII символы с 33 по 126 + end + return table.concat(result) +end + +counter = 0 +function request() + counter = counter + 1 + local headers = {} + headers["Host"] = "localhost:8080" + local key = "key" .. counter + local value = "value$" .. randomString(100) + return wrk.format("PUT", "/v0/entity?id=" .. key .. "&ack=2&from=3", headers, value) +end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/scripts/get.lua b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/scripts/get.lua new file mode 100644 index 000000000..0dd0303ac --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/scripts/get.lua @@ -0,0 +1,7 @@ +function request() + local headers = {} + headers["Host"] = "localhost:8080" + local id = math.random(1, 10000) + local key = "key" .. id + return wrk.format("GET", "/v0/entity?id=" .. key .. "&ack=3&from=3", headers) +end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/wrk_result/get_result.txt b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/wrk_result/get_result.txt new file mode 100644 index 000000000..300e4d803 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/wrk_result/get_result.txt @@ -0,0 +1,91 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.474 0.000000 1 1.00 + 1.630 0.100000 15521 1.11 + 2.053 0.200000 31043 1.25 + 2.533 0.300000 46522 1.43 + 3.331 0.400000 61977 1.67 + 4.647 0.500000 77489 2.00 + 5.531 0.550000 85208 2.22 + 6.627 0.600000 92971 2.50 + 7.955 0.650000 100699 2.86 + 9.487 0.700000 108449 3.33 + 11.303 0.750000 116216 4.00 + 12.351 0.775000 120070 4.44 + 13.567 0.800000 123937 5.00 + 14.967 0.825000 127809 5.71 + 16.671 0.850000 131707 6.67 + 18.671 0.875000 135568 8.00 + 19.791 0.887500 137500 8.89 + 20.991 0.900000 139427 10.00 + 22.319 0.912500 141371 11.43 + 23.807 0.925000 143309 13.33 + 25.487 0.937500 145239 16.00 + 26.447 0.943750 146204 17.78 + 27.535 0.950000 147185 20.00 + 28.639 0.956250 148144 22.86 + 29.935 0.962500 149113 26.67 + 31.375 0.968750 150078 32.00 + 32.159 0.971875 150561 35.56 + 33.119 0.975000 151058 40.00 + 34.175 0.978125 151540 45.71 + 35.295 0.981250 152020 53.33 + 36.575 0.984375 152510 64.00 + 37.279 0.985938 152753 71.11 + 38.015 0.987500 152989 80.00 + 39.007 0.989062 153231 91.43 + 40.063 0.990625 153469 106.67 + 41.279 0.992188 153712 128.00 + 41.983 0.992969 153833 142.22 + 42.847 0.993750 153953 160.00 + 43.679 0.994531 154074 182.86 + 44.767 0.995313 154197 213.33 + 45.919 0.996094 154315 256.00 + 46.495 0.996484 154374 284.44 + 47.103 0.996875 154438 320.00 + 47.711 0.997266 154495 365.71 + 48.447 0.997656 154555 426.67 + 49.311 0.998047 154618 512.00 + 49.663 0.998242 154648 568.89 + 50.239 0.998437 154678 640.00 + 50.815 0.998633 154707 731.43 + 51.615 0.998828 154738 853.33 + 52.479 0.999023 154767 1024.00 + 52.863 0.999121 154783 1137.78 + 53.215 0.999219 154799 1280.00 + 53.759 0.999316 154814 1462.86 + 54.335 0.999414 154828 1706.67 + 55.135 0.999512 154844 2048.00 + 55.231 0.999561 154850 2275.56 + 55.551 0.999609 154858 2560.00 + 56.191 0.999658 154866 2925.71 + 56.895 0.999707 154873 3413.33 + 57.695 0.999756 154881 4096.00 + 57.759 0.999780 154884 4551.11 + 58.079 0.999805 154888 5120.00 + 58.815 0.999829 154892 5851.43 + 59.423 0.999854 154896 6826.67 + 59.903 0.999878 154900 8192.00 + 60.095 0.999890 154902 9102.22 + 60.223 0.999902 154904 10240.00 + 60.255 0.999915 154905 11702.86 + 60.799 0.999927 154908 13653.33 + 60.863 0.999939 154909 16384.00 + 60.991 0.999945 154910 18204.44 + 61.279 0.999951 154911 20480.00 + 61.567 0.999957 154912 23405.71 + 62.111 0.999963 154913 27306.67 + 62.239 0.999969 154914 32768.00 + 62.239 0.999973 154914 36408.89 + 62.367 0.999976 154915 40960.00 + 62.367 0.999979 154915 46811.43 + 62.751 0.999982 154916 54613.33 + 62.751 0.999985 154916 65536.00 + 62.751 0.999986 154916 72817.78 + 63.391 0.999988 154917 81920.00 + 63.391 0.999989 154917 93622.86 + 63.391 0.999991 154917 109226.67 + 63.391 0.999992 154917 131072.00 + 63.391 0.999993 154917 145635.56 + 63.743 0.999994 154918 163840.00 + 63.743 1.000000 154918 inf diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/wrk_result/put_result.txt b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/wrk_result/put_result.txt new file mode 100644 index 000000000..6241de4f4 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/lab5/wrk_result/put_result.txt @@ -0,0 +1,98 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.103 0.000000 1 1.00 + 1.050 0.100000 79953 1.11 + 1.683 0.200000 159758 1.25 + 3.405 0.300000 239697 1.43 + 6.191 0.400000 319526 1.67 + 9.935 0.500000 399356 2.00 + 12.471 0.550000 439360 2.22 + 15.407 0.600000 479217 2.50 + 17.967 0.650000 519173 2.86 + 20.655 0.700000 559092 3.33 + 23.615 0.750000 599092 4.00 + 25.407 0.775000 619128 4.44 + 27.359 0.800000 638947 5.00 + 30.143 0.825000 658938 5.71 + 33.919 0.850000 678924 6.67 + 38.559 0.875000 698878 8.00 + 41.919 0.887500 708888 8.89 + 46.143 0.900000 718869 10.00 + 52.191 0.912500 728831 11.43 + 58.815 0.925000 738798 13.33 + 63.999 0.937500 748792 16.00 + 66.431 0.943750 753845 17.78 + 68.735 0.950000 758905 20.00 + 70.783 0.956250 763746 22.86 + 72.703 0.962500 768774 26.67 + 74.559 0.968750 773784 32.00 + 75.519 0.971875 776324 35.56 + 76.415 0.975000 778725 40.00 + 77.439 0.978125 781342 45.71 + 78.527 0.981250 783755 53.33 + 79.807 0.984375 786203 64.00 + 80.511 0.985938 787485 71.11 + 81.343 0.987500 788705 80.00 + 82.303 0.989062 789969 91.43 + 83.391 0.990625 791257 106.67 + 84.479 0.992188 792456 128.00 + 85.119 0.992969 793084 142.22 + 85.759 0.993750 793698 160.00 + 86.463 0.994531 794315 182.86 + 87.231 0.995313 794964 213.33 + 88.063 0.996094 795578 256.00 + 88.575 0.996484 795899 284.44 + 89.023 0.996875 796185 320.00 + 89.599 0.997266 796503 365.71 + 90.239 0.997656 796813 426.67 + 90.943 0.998047 797125 512.00 + 91.391 0.998242 797269 568.89 + 91.967 0.998437 797442 640.00 + 92.607 0.998633 797583 731.43 + 93.375 0.998828 797747 853.33 + 94.207 0.999023 797898 1024.00 + 94.655 0.999121 797983 1137.78 + 95.103 0.999219 798054 1280.00 + 95.615 0.999316 798134 1462.86 + 96.127 0.999414 798209 1706.67 + 96.959 0.999512 798285 2048.00 + 97.471 0.999561 798325 2275.56 + 98.047 0.999609 798363 2560.00 + 98.687 0.999658 798401 2925.71 + 99.327 0.999707 798439 3413.33 + 100.031 0.999756 798480 4096.00 + 100.351 0.999780 798500 4551.11 + 100.671 0.999805 798519 5120.00 + 101.119 0.999829 798538 5851.43 + 101.567 0.999854 798558 6826.67 + 102.015 0.999878 798577 8192.00 + 102.271 0.999890 798586 9102.22 + 102.719 0.999902 798597 10240.00 + 103.167 0.999915 798607 11702.86 + 103.423 0.999927 798615 13653.33 + 103.743 0.999939 798625 16384.00 + 103.935 0.999945 798629 18204.44 + 104.127 0.999951 798634 20480.00 + 104.319 0.999957 798639 23405.71 + 104.575 0.999963 798644 27306.67 + 104.767 0.999969 798648 32768.00 + 105.151 0.999973 798651 36408.89 + 105.215 0.999976 798653 40960.00 + 105.343 0.999979 798657 46811.43 + 105.407 0.999982 798658 54613.33 + 105.599 0.999985 798661 65536.00 + 105.855 0.999986 798662 72817.78 + 105.919 0.999988 798664 81920.00 + 105.919 0.999989 798664 93622.86 + 106.111 0.999991 798666 109226.67 + 106.111 0.999992 798666 131072.00 + 106.559 0.999993 798667 145635.56 + 106.943 0.999994 798668 163840.00 + 106.943 0.999995 798668 187245.71 + 107.071 0.999995 798669 218453.33 + 107.071 0.999996 798669 262144.00 + 107.647 0.999997 798670 291271.11 + 107.647 0.999997 798670 327680.00 + 107.647 0.999997 798670 374491.43 + 107.775 0.999998 798672 436906.67 + 107.775 1.000000 798672 inf \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/results/stage4.md b/src/main/java/ru/vk/itmo/test/georgiidalbeev/results/stage4.md new file mode 100644 index 000000000..4b365775e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/results/stage4.md @@ -0,0 +1,57 @@ +## Производительность + +- **PUT Запросы:** + + | Запрос | lab3 | lab4 ack2 | lab4 ack3 | + |--------|---------|-----------|-----------| + | rps | 35000 | 14500 | 9750 | + | avg | 15.85ms | 68.08ms | 37.12ms | + | Stdev | 29.97ms | 40.66ms | 47.28ms | + + Произошло снижение производительности примерно в `2.5` раза для `ack = 2` и `3.5` раза для `ack = 3`. + + - `ack = 2` +![put_ack2.png](..%2Flab4%2Fwrk_result%2Fput_ack2.png) + - `ack = 3` +![put.png](..%2Flab4%2Fwrk_result%2Fput.png) + + +- **GET Запросы:** + + | Запрос | lab3 | lab4 ack2 | lab4 ack3 | + |--------|---------|-----------|-----------| + | rps | 40000 | 14500 | 9500 | + | avg | 17.40ms | 96.85ms | 44.91ms | + | Stdev | 20.24ms | 42.12ms | 33.99ms | + + Произошло снижение производительности примерно в `2.75` раза для `ack = 2` и `4` раза для `ack = 3`. + + - `ack = 2` +![get_ack2.png](..%2Flab4%2Fwrk_result%2Fget_ack2.png) + - `ack = 3` +![get.png](..%2Flab4%2Fwrk_result%2Fget.png) + + +## Причины Деградации Производительности + +Снижение производительности происходит из-за необходимости ожидания ответа не только от одного, а сразу от кворума узлов. +Это приводит к последовательному ожиданию ответов от нескольких узлов, что замедляет обработку запросов. + +## Результат профилирования + +- **PUT Запросы:** + - Время на отправку ответов клиенту и ожидание ответов от проксируемых узлов осталось на прежнем уровне. + - Объем работы в воркерах увеличился, особенно заметно на `ThreadPoolExecutor.runWorker`. +- **GET Запросы:** + - Заметно увеличение работы в воркерах, особенно на этапе `Server.processRequest`, где происходит параллельное ожидание +ответов от кворума узлов. + +## Выводы +При использовании значений ack = 3 и from = 3, произошло существенное снижение производительности системы. +Это связано с необходимостью ожидания ответа не только от одного, а сразу от кворума узлов. Это приводит +к увеличению времени обработки запросов из-за последовательного ожидания ответов от нескольких узлов. +Но для придания системе отказоустойчивости, приходится жертвовать быстродействием. При неполадках с одним или несколькими узлами, +данные остаются доступными, если количество активных узлов превышает или равно значению from - ack + 1. В данном случае +оптимальными значениями будут являться ack = 2 и from = 3, позволяя восстановить данные в случае неполадок с одним из узлов +и увеличить показатель rps. + diff --git a/src/main/java/ru/vk/itmo/test/georgiidalbeev/results/stage5.md b/src/main/java/ru/vk/itmo/test/georgiidalbeev/results/stage5.md new file mode 100644 index 000000000..c9fa6b0a8 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/georgiidalbeev/results/stage5.md @@ -0,0 +1,47 @@ +## Производительность + +- **PUT Запросы:** + + | Запрос | lab3 | lab4 | lab5 | + |--------|---------|---------|---------| + | rps | 35000 | 14500 | 17000 | + | avg | 15.85ms | 68.08ms | 45.28ms | + | Stdev | 29.97ms | 40.66ms | 33.32ms | + + Произошло снижение производительности примерно на `~17%` и уменьшение задержки на `~30%`. + +- **GET Запросы:** + + | Запрос | lab3 | lab4 | lab5 | + |--------|---------|---------|---------| + | rps | 40000 | 14500 | 18000 | + | avg | 17.40ms | 96.85ms | 63.81ms | + | Stdev | 20.24ms | 42.12ms | 34.65ms | + + Произошло снижение производительности примерно на `~24%` и уменьшение задержки на `~30%`. + +## Профилирование + +### PUT + +В процессе профилирования были обнаружены следующие изменения: +- **CPU**: Класс `CompletableFuture` занимает `~15%` от общего времени CPU. Это связано с тем, что запросы теперь + обрабатываются асинхронно. +- **ALLOC**: `CompletableFuture` занимает `~33%` от общего объема выделенной памяти. Это связано с созданием новых + экземпляров `CompletableFuture` для каждого асинхронного запроса. +- **LOCK**: `~37%` времени занимают операции с блокировками, связанные с обработкой `CompletableFuture`. + +### GET + +Аналогичные изменения наблюдаются и при обработке GET запросов: +- **CPU**: В процессе выполнения запросов появился новый класс `CompletableFuture`, который занимает `~12%` от общего +времени CPU. Это связано с тем, что теперь запросы обрабатываются асинхронно. +- **ALLOC**: Класс `CompletableFuture` также заметно влияет на распределение памяти. Он занимает `~28%` от общего объема +выделенной памяти. +- **LOCK**: `~30%` времени занимают операции с блокировками, связанные с обработкой `CompletableFuture`. + +## Выводы + +При использовании `CompletableFuture` для асинхронной обработки запросов, мы смогли добиться некоторого увеличения +производительности. Это связано с тем, что запросы теперь обрабатываются +параллельно, и клиент не должен ждать завершения всех запросов, что приводит к уменьшению задержки. \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/replication/MergeHandleResult.java b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/replication/MergeHandleResult.java index d0cf3ec85..3f3eb0429 100644 --- a/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/replication/MergeHandleResult.java +++ b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/replication/MergeHandleResult.java @@ -4,68 +4,70 @@ import one.nio.http.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ru.vk.itmo.test.khadyrovalmasgali.util.HttpUtil; -import java.io.IOException; import java.net.HttpURLConnection; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; public class MergeHandleResult { private static final Logger log = LoggerFactory.getLogger(MergeHandleResult.class); + private final AtomicInteger successCount; private final AtomicInteger count; private final int from; private final int ack; private final HttpSession session; - private final HandleResult[] handleResults; + private HandleResult mergedResult = new HandleResult(HttpURLConnection.HTTP_GATEWAY_TIMEOUT, Response.EMPTY); public MergeHandleResult(int from, int ack, HttpSession session) { - handleResults = new HandleResult[from]; this.count = new AtomicInteger(); + this.successCount = new AtomicInteger(); this.ack = ack; this.from = from; this.session = session; } - public void add(HandleResult handleResult, int index) { - handleResults[index] = handleResult; - int get = count.incrementAndGet(); - - if (get == from) { - sendResult(); - } - } - - private void sendResult() { - HandleResult mergedResult = new HandleResult(HttpURLConnection.HTTP_GATEWAY_TIMEOUT, null); - - int accepted = 0; - for (HandleResult handleResult : handleResults) { - if (handleResult.status() == HttpURLConnection.HTTP_OK - || handleResult.status() == HttpURLConnection.HTTP_CREATED - || handleResult.status() == HttpURLConnection.HTTP_ACCEPTED - || handleResult.status() == HttpURLConnection.HTTP_NOT_FOUND) { - accepted++; + @SuppressWarnings("FutureReturnValueIgnored") + public void add(CompletableFuture futureHandleResult) { + futureHandleResult.whenComplete((handleResult, t) -> { + checkThrowable(t); + if (validateHandleResult(handleResult)) { if (mergedResult.timestamp() <= handleResult.timestamp()) { mergedResult = handleResult; } + int get = successCount.incrementAndGet(); + if (get == ack) { + HttpUtil.sessionSendSafe( + session, + new Response(String.valueOf(mergedResult.status()), mergedResult.data()), + log); + } } - } + int currentCount = count.incrementAndGet(); + if (currentCount == from && successCount.get() < ack) { + HttpUtil.sessionSendSafe(session, new Response(Response.GATEWAY_TIMEOUT, Response.EMPTY), log); + } + }); + } - try { - if (accepted < ack) { - session.sendResponse(new Response(Response.GATEWAY_TIMEOUT, Response.EMPTY)); + private void checkThrowable(Throwable t) { + if (t != null) { + if (t instanceof Exception) { + log.info("Exception in mergeHandleResult", t); } else { - session.sendResponse(new Response(String.valueOf(mergedResult.status()), mergedResult.data())); - } - } catch (Exception e) { - log.error("Exception during handleRequest", e); - try { - session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); - } catch (IOException ex) { - log.error("Exception while sending close connection", e); - session.scheduleClose(); + HttpUtil.sessionSendSafe(session, new Response(Response.INTERNAL_ERROR, Response.EMPTY), log); } } + } + private static boolean validateHandleResult(HandleResult handleResult) { + if (handleResult == null) { + return false; + } + return handleResult.status() == HttpURLConnection.HTTP_OK + || handleResult.status() == HttpURLConnection.HTTP_CREATED + || handleResult.status() == HttpURLConnection.HTTP_ACCEPTED + || handleResult.status() == HttpURLConnection.HTTP_NOT_FOUND; } } diff --git a/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/report/stage5/img.png b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/report/stage5/img.png new file mode 100644 index 000000000..4724cbdae Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/report/stage5/img.png differ diff --git a/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/report/stage5/report.md b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/report/stage5/report.md new file mode 100644 index 000000000..74e3fdea3 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/report/stage5/report.md @@ -0,0 +1,41 @@ +# Отчёт Stage 5 + +Работа сделана после soft deadline, соответственно тестировался почти +референс. + +## PUT + +10к RPS + +| Percentile | Latency | +|------------|---------| +| 50.000% | 1.59ms | +| 75.000% | 2.43ms | +| 90.000% | 3.76ms | +| 99.000% | 9.89ms | +| 99.900% | 15.04ms | +| 99.990% | 19.57ms | +| 99.999% | 22.09ms | +| 100.000% | 23.02ms | + +## GET + +10k RPS + +| Percentile | Latency | +|------------|---------| +| 50.000% | 1.53ms | +| 75.000% | 2.27ms | +| 90.000% | 3.18ms | +| 99.000% | 5.95ms | +| 99.900% | 10.57ms | +| 99.990% | 14.53ms | +| 99.999% | 16.42ms | +| 100.000% | 17.39ms | + +RPS вырос на 25% по сравнению с предыдущим этапом, так еще и +Latency уменьшилась. + +По причине, которую мне так и не удалось установить, asprof сломался +и напрочь отказывается предоставить какой-либо вывод( +[img.png](img.png) diff --git a/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/server/DaoServer.java b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/server/DaoServer.java index 84a4bda63..7019c086c 100644 --- a/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/server/DaoServer.java +++ b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/server/DaoServer.java @@ -1,6 +1,6 @@ package ru.vk.itmo.test.khadyrovalmasgali.server; -import one.nio.http.HttpException; +import one.nio.async.CustomThreadFactory; import one.nio.http.HttpServer; import one.nio.http.HttpServerConfig; import one.nio.http.HttpSession; @@ -15,6 +15,7 @@ import ru.vk.itmo.test.khadyrovalmasgali.hashing.Node; import ru.vk.itmo.test.khadyrovalmasgali.replication.HandleResult; import ru.vk.itmo.test.khadyrovalmasgali.replication.MergeHandleResult; +import ru.vk.itmo.test.khadyrovalmasgali.util.HttpUtil; import java.io.IOException; import java.lang.foreign.MemorySegment; @@ -27,13 +28,12 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Optional; -import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class DaoServer extends HttpServer { @@ -42,22 +42,29 @@ public class DaoServer extends HttpServer { private static final String HEADER_REMOTE_ONE_NIO_HEADER = HEADER_REMOTE + ": da"; private static final String HEADER_TIMESTAMP = "X-flag-remote-reference-server-to-node-by-paschenko2"; private static final String HEADER_TIMESTAMP_ONE_NIO_HEADER = HEADER_TIMESTAMP + ": "; + private static final byte[] CHUNK_HEADERS = """ + HTTP/1.1 200 OK\r + Content-Type: text/plain\r + Transfer-Encoding: chunked\r + Connection: keep-alive\r + \r + """.getBytes(StandardCharsets.UTF_8); + private static final byte[] CHUNK_SEP = "\r\n".getBytes(StandardCharsets.UTF_8); + private static final byte[] ENTRY_SEP = "\n".getBytes(StandardCharsets.UTF_8); private static final Logger log = LoggerFactory.getLogger(DaoServer.class); private static final String ENTITY_PATH = "/v0/entity"; + private static final String ENTITIES_PATH = "/v0/entities"; private static final int THREADS = Runtime.getRuntime().availableProcessors(); - private static final int Q_SIZE = 512; - private static final int KEEP_ALIVE_TIME = 50; private static final int TERMINATION_TIMEOUT = 60; private static final int HTTP_CLIENT_TIMEOUT_MS = 1000; private final ServiceConfig config; - private final ExecutorService executorService = new ThreadPoolExecutor( - THREADS, - THREADS, - KEEP_ALIVE_TIME, - TimeUnit.MILLISECONDS, - new ArrayBlockingQueue<>(Q_SIZE), - new ThreadPoolExecutor.AbortPolicy()); + private final ExecutorService executorLocal = Executors + .newFixedThreadPool(THREADS, new CustomThreadFactory("local-worker")); + + private final ExecutorService executorRemote = Executors + .newFixedThreadPool(THREADS, new CustomThreadFactory("remote-worker")); + private final List nodes; private final Dao> dao; private final HttpClient httpClient; @@ -84,11 +91,69 @@ public void handleDefault(Request request, HttpSession session) throws IOExcepti @Override public void handleRequest(Request request, HttpSession session) throws IOException { - if (!ENTITY_PATH.equals(request.getPath())) { + if (ENTITY_PATH.equals(request.getPath())) { + handleEntity(request, session); + } else if (ENTITIES_PATH.equals(request.getPath())) { + handleEntities(request, session); + } else { session.sendError(Response.BAD_REQUEST, null); - return; } + } + + @SuppressWarnings("FutureReturnValueIgnored") + private void handleEntities(Request request, HttpSession session) { + CompletableFuture.runAsync(() -> { + try { + if (request.getMethod() != Request.METHOD_GET) { + session.sendError(Response.METHOD_NOT_ALLOWED, null); + return; + } + String start = request.getParameter("start="); + if (start == null || start.isBlank()) { + session.sendError(Response.BAD_REQUEST, null); + return; + } + String end = request.getParameter("end="); + Iterator> iterator; + if (end == null || end.isBlank()) { + iterator = dao.get(stringToMemorySegment(start), null); + } else { + iterator = dao.get(stringToMemorySegment(start), stringToMemorySegment(end)); + } + writeFull(session, CHUNK_HEADERS); + while (iterator.hasNext()) { + writeChunk(session, iterator.next()); + } + writeEmptyChunk(session); + session.close(); + } catch (IOException e) { + log.error("Error handling range", e); + } + }, executorLocal); + } + + private void writeChunk(HttpSession session, TimestampEntry entry) throws IOException { + byte[] key = entry.key().toArray(ValueLayout.JAVA_BYTE); + byte[] value = entry.value().toArray(ValueLayout.JAVA_BYTE); + writeFull(session, lengthToHexBytes(key.length + value.length + ENTRY_SEP.length)); + writeFull(session, CHUNK_SEP); + writeFull(session, key); + writeFull(session, ENTRY_SEP); + writeFull(session, value); + writeFull(session, CHUNK_SEP); + } + + private void writeEmptyChunk(HttpSession session) throws IOException { + writeFull(session, lengthToHexBytes(0)); + writeFull(session, CHUNK_SEP); + writeFull(session, CHUNK_SEP); + } + + private void writeFull(HttpSession session, byte[] data) throws IOException { + session.write(data, 0, data.length); + } + private void handleEntity(Request request, HttpSession session) throws IOException { if (request.getMethod() != Request.METHOD_GET && request.getMethod() != Request.METHOD_DELETE && request.getMethod() != Request.METHOD_PUT) { @@ -117,120 +182,99 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti int[] indexes = determineResponsibleNodes(id, from); MergeHandleResult mergeResult = new MergeHandleResult(indexes.length, ack, session); - for (int i = 0; i < indexes.length; ++i) { - Node node = nodes.get(indexes[i]); + for (int index : indexes) { + Node node = nodes.get(index); if (!config.selfUrl().equals(node.getUrl())) { - handleAsync(mergeResult, i, () -> remote(request, node)); + handle(mergeResult, () -> remote(request, node)); } else { - handleAsync(mergeResult, i, () -> local(request, id)); + handle(mergeResult, () -> local(request, id)); } } } + @SuppressWarnings("FutureReturnValueIgnored") private void processLocal(Request request, HttpSession session, String id) { - executorService.execute(() -> { - try { - HandleResult local = local(request, id); - Response response = new Response(String.valueOf(local.status()), local.data()); - response.addHeader(HEADER_TIMESTAMP_ONE_NIO_HEADER + local.timestamp()); - session.sendResponse(response); - } catch (Exception e) { - log.error("Exception during handleRequest", e); - try { - session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); - } catch (IOException ex) { - log.error("Exception while sending close connection", e); - session.scheduleClose(); - } + CompletableFuture localResult = local(request, id); + localResult.whenComplete((local, t) -> { + if (t != null) { + HttpUtil.sessionSendSafe(session, new Response(Response.INTERNAL_ERROR, Response.EMPTY), log); + return; } + Response response = new Response(String.valueOf(local.status()), local.data()); + response.addHeader(HEADER_TIMESTAMP_ONE_NIO_HEADER + local.timestamp()); + HttpUtil.sessionSendSafe(session, response, log); }); } - private HandleResult remote(Request request, Node node) { - try { - HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(node.getUrl() + request.getURI())) - .method( - request.getMethodName(), - request.getBody() == null - ? HttpRequest.BodyPublishers.noBody() - : HttpRequest.BodyPublishers.ofByteArray(request.getBody()) - ) - .header(HEADER_REMOTE, "da") - .timeout(Duration.ofMillis(HTTP_CLIENT_TIMEOUT_MS)) - .build(); - HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray()); + private CompletableFuture remote(Request request, Node node) { + HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(node.getUrl() + request.getURI())) + .method( + request.getMethodName(), + request.getBody() == null + ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofByteArray(request.getBody()) + ) + .header(HEADER_REMOTE, "da") + .timeout(Duration.ofMillis(HTTP_CLIENT_TIMEOUT_MS)) + .build(); + CompletableFuture> httpResponseFuture = httpClient + .sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()); + return httpResponseFuture.thenApplyAsync(httpResponse -> { Optional string = httpResponse.headers().firstValue(HEADER_TIMESTAMP); long timestamp; timestamp = string.map(Long::parseLong).orElse(0L); return new HandleResult(httpResponse.statusCode(), httpResponse.body(), timestamp); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.error(e.getMessage()); - return new HandleResult(HttpURLConnection.HTTP_UNAVAILABLE, Response.EMPTY); - } catch (IOException e) { - return new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY); - } + }, executorRemote); } - private HandleResult local(Request request, String id) { - long timestamp = System.currentTimeMillis(); - switch (request.getMethod()) { - case Request.METHOD_GET -> { - TimestampEntry entry = dao.get(stringToMemorySegment(id)); - if (entry == null) { - return new HandleResult(HttpURLConnection.HTTP_NOT_FOUND, Response.EMPTY); + private CompletableFuture local(Request request, String id) { + return CompletableFuture.supplyAsync(() -> { + long timestamp = System.currentTimeMillis(); + switch (request.getMethod()) { + case Request.METHOD_GET -> { + TimestampEntry entry = dao.get(stringToMemorySegment(id)); + if (entry == null) { + return new HandleResult(HttpURLConnection.HTTP_NOT_FOUND, Response.EMPTY); + } + if (entry.value() == null) { + return new HandleResult(HttpURLConnection.HTTP_NOT_FOUND, Response.EMPTY, entry.timestamp()); + } + return new HandleResult( + HttpURLConnection.HTTP_OK, + entry.value().toArray(ValueLayout.JAVA_BYTE), + entry.timestamp()); } - if (entry.value() == null) { - return new HandleResult(HttpURLConnection.HTTP_NOT_FOUND, Response.EMPTY, entry.timestamp()); + case Request.METHOD_PUT -> { + dao.upsert(new TimestampEntry<>( + stringToMemorySegment(id), + MemorySegment.ofArray(request.getBody()), + timestamp)); + return new HandleResult(HttpURLConnection.HTTP_CREATED, Response.EMPTY); + } + case Request.METHOD_DELETE -> { + dao.upsert(new TimestampEntry<>(stringToMemorySegment(id), null, timestamp)); + return new HandleResult(HttpURLConnection.HTTP_ACCEPTED, Response.EMPTY); + } + default -> { + return new HandleResult(HttpURLConnection.HTTP_BAD_METHOD, Response.EMPTY); } - return new HandleResult( - HttpURLConnection.HTTP_OK, - entry.value().toArray(ValueLayout.JAVA_BYTE), - entry.timestamp()); - } - case Request.METHOD_PUT -> { - dao.upsert(new TimestampEntry<>( - stringToMemorySegment(id), - MemorySegment.ofArray(request.getBody()), - timestamp)); - return new HandleResult(HttpURLConnection.HTTP_CREATED, Response.EMPTY); - } - case Request.METHOD_DELETE -> { - dao.upsert(new TimestampEntry<>(stringToMemorySegment(id), null, timestamp)); - return new HandleResult(HttpURLConnection.HTTP_ACCEPTED, Response.EMPTY); - } - default -> { - return new HandleResult(HttpURLConnection.HTTP_BAD_METHOD, Response.EMPTY); } - } + }, executorLocal); } - private void handleAsync(MergeHandleResult mergeResult, int index, ERunnable r) { + private void handle(MergeHandleResult mergeResult, ERunnable r) { try { - executorService.execute(() -> { - try { - HandleResult result = r.run(); - mergeResult.add(result, index); - } catch (Exception e) { - if (e instanceof HttpException) { - log.error(e.getMessage()); - mergeResult.add(new HandleResult(HttpURLConnection.HTTP_BAD_REQUEST, Response.EMPTY), index); - } else { - log.error(e.getMessage()); - mergeResult.add(new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY), index); - } - } - }); - } catch (RejectedExecutionException e) { - log.error("too many requests", e); - mergeResult.add(new HandleResult(HttpURLConnection.HTTP_UNAVAILABLE, Response.EMPTY), index); + mergeResult.add(r.run()); + } catch (Exception e) { + log.error("Exception during handle request"); + mergeResult.add(CompletableFuture.failedFuture(e)); } } @Override public synchronized void stop() { super.stop(); - shutdownAndAwaitTermination(executorService); + shutdownAndAwaitTermination(executorLocal); } private int getParam(Request request, String p, int defaultValue) { @@ -288,6 +332,10 @@ private static MemorySegment stringToMemorySegment(String s) { return MemorySegment.ofArray(s.getBytes(StandardCharsets.UTF_8)); } + private static byte[] lengthToHexBytes(int length) { + return Integer.toHexString(length).getBytes(StandardCharsets.UTF_8); + } + private static HttpServerConfig createHttpServerConfig(ServiceConfig config) { HttpServerConfig serverConfig = new HttpServerConfig(); AcceptorConfig acceptorConfig = new AcceptorConfig(); @@ -299,6 +347,6 @@ private static HttpServerConfig createHttpServerConfig(ServiceConfig config) { } private interface ERunnable { - HandleResult run() throws IOException; + CompletableFuture run() throws IOException; } } diff --git a/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/server/LinkedBlockingStack.java b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/server/LinkedBlockingStack.java deleted file mode 100644 index 34c5592e8..000000000 --- a/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/server/LinkedBlockingStack.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.vk.itmo.test.khadyrovalmasgali.server; - -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.TimeUnit; - -public class LinkedBlockingStack extends LinkedBlockingDeque { - - public LinkedBlockingStack(int capacity) { - super(capacity); - } - - @Override - public E poll() { - return pollLast(); - } - - @Override - public E poll(long timeout, TimeUnit unit) throws InterruptedException { - return pollLast(timeout, unit); - } - - @Override - public E take() throws InterruptedException { - return takeLast(); - } -} diff --git a/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/service/DaoServiceFactory.java b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/service/DaoServiceFactory.java index fb23821f7..03e8e307b 100644 --- a/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/service/DaoServiceFactory.java +++ b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/service/DaoServiceFactory.java @@ -4,7 +4,7 @@ import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.test.ServiceFactory; -@ServiceFactory(stage = 4) +@ServiceFactory(stage = 6) public class DaoServiceFactory implements ServiceFactory.Factory { @Override public Service create(ServiceConfig config) { diff --git a/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/util/HttpUtil.java b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/util/HttpUtil.java new file mode 100644 index 000000000..3b957d90c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/khadyrovalmasgali/util/HttpUtil.java @@ -0,0 +1,23 @@ +package ru.vk.itmo.test.khadyrovalmasgali.util; + +import one.nio.http.HttpSession; +import one.nio.http.Response; +import org.slf4j.Logger; + +import java.io.IOException; + +public final class HttpUtil { + + private HttpUtil() { + + } + + public static void sessionSendSafe(HttpSession session, Response r, Logger log) { + try { + session.sendResponse(r); + } catch (IOException e) { + log.error("IO exception during sending response", e); + session.scheduleClose(); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/ChunkGenerator.java b/src/main/java/ru/vk/itmo/test/klimplyasov/ChunkGenerator.java new file mode 100644 index 000000000..ad49731c1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/ChunkGenerator.java @@ -0,0 +1,57 @@ +package ru.vk.itmo.test.klimplyasov; + +import one.nio.http.HttpSession; +import ru.vk.itmo.dao.Entry; + +import java.io.IOException; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; + +public final class ChunkGenerator { + private static final byte[] HTTP_RESPONSE_LINE = "HTTP/1.1 200 OK".getBytes(StandardCharsets.UTF_8); + private static final byte[] CRLF = "\r\n".getBytes(StandardCharsets.UTF_8); + private static final byte[] CONTENT_TYPE_HEADER = "Content-Type: text/plain".getBytes(StandardCharsets.UTF_8); + private static final byte[] TRANSFER_ENCODING_HEADER = + "Transfer-Encoding: chunked".getBytes(StandardCharsets.UTF_8); + private static final byte[] CONNECTION_HEADER = "Connection: keep-alive".getBytes(StandardCharsets.UTF_8); + private static final byte[] LF = "\n".getBytes(StandardCharsets.UTF_8); + + private ChunkGenerator() { + //:) + } + + public static void writeResponseHeaders(HttpSession session) throws IOException { + session.write(HTTP_RESPONSE_LINE, 0, HTTP_RESPONSE_LINE.length); + session.write(CRLF, 0, CRLF.length); + session.write(CONTENT_TYPE_HEADER, 0, CONTENT_TYPE_HEADER.length); + session.write(CRLF, 0, CRLF.length); + session.write(TRANSFER_ENCODING_HEADER, 0, TRANSFER_ENCODING_HEADER.length); + session.write(CRLF, 0, CRLF.length); + session.write(CONNECTION_HEADER, 0, CONNECTION_HEADER.length); + session.write(CRLF, 0, CRLF.length); + session.write(CRLF, 0, CRLF.length); + } + + public static void writeDataChunk(HttpSession session, Entry entry) throws IOException { + byte[] key = entry.key().toArray(ValueLayout.JAVA_BYTE); + byte[] value = entry.value().toArray(ValueLayout.JAVA_BYTE); + byte[] lengthBytes = + Integer + .toHexString(key.length + value.length + LF.length) + .getBytes(StandardCharsets.UTF_8); + session.write(lengthBytes, 0, lengthBytes.length); + session.write(CRLF, 0, CRLF.length); + session.write(key, 0, key.length); + session.write(LF, 0, LF.length); + session.write(value, 0, value.length); + session.write(CRLF, 0, CRLF.length); + } + + public static void writeEmptyChunk(HttpSession session) throws IOException { + byte[] dataSizeBytes = Integer.toHexString(0).getBytes(StandardCharsets.UTF_8); + session.write(dataSizeBytes, 0, dataSizeBytes.length); + session.write(CRLF, 0, CRLF.length); + session.write(CRLF, 0, CRLF.length); + } +} diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/ExecutorConfig.java b/src/main/java/ru/vk/itmo/test/klimplyasov/ExecutorConfig.java deleted file mode 100644 index eac57ec9f..000000000 --- a/src/main/java/ru/vk/itmo/test/klimplyasov/ExecutorConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.vk.itmo.test.klimplyasov; - -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -public final class ExecutorConfig { - private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors(); - private static final int MAXIMUM_POOL_SIZE = 20; - private static final int KEEP_ALIVE_TIME = 60; - private static final int CAPACITY = 1000; - - private ExecutorConfig() { - //:) - } - - public static ExecutorService getExecutorService() { - return new ThreadPoolExecutor( - CORE_POOL_SIZE, - MAXIMUM_POOL_SIZE, - KEEP_ALIVE_TIME, - TimeUnit.SECONDS, - new ArrayBlockingQueue<>(CAPACITY) - ); - } -} diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/HandleResult.java b/src/main/java/ru/vk/itmo/test/klimplyasov/HandleResult.java new file mode 100644 index 000000000..ac6d44560 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/HandleResult.java @@ -0,0 +1,14 @@ +package ru.vk.itmo.test.klimplyasov; + +public record HandleResult(int status, byte[] data, long timestamp) { + + public HandleResult(int status, byte[] data, long timestamp) { + this.status = status; + this.data = data; + this.timestamp = timestamp; + } + + public HandleResult(int status, byte[] data) { + this(status, data, 0); + } +} diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/MergeHandleResult.java b/src/main/java/ru/vk/itmo/test/klimplyasov/MergeHandleResult.java new file mode 100644 index 000000000..62b66289c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/MergeHandleResult.java @@ -0,0 +1,74 @@ +package ru.vk.itmo.test.klimplyasov; + +import one.nio.http.HttpSession; +import one.nio.http.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.concurrent.atomic.AtomicInteger; + +public class MergeHandleResult { + private static final Logger log = LoggerFactory.getLogger(MergeHandleResult.class); + private final HandleResult[] handleResults; + private final AtomicInteger totalCount; + private final AtomicInteger validCount; + private final int ackThreshold; + private final int totalExpected; + private final HttpSession currentSession; + + public MergeHandleResult(HttpSession session, int size, int ackThreshold) { + this.currentSession = session; + this.handleResults = new HandleResult[size]; + this.totalCount = new AtomicInteger(); + this.validCount = new AtomicInteger(); + this.ackThreshold = ackThreshold; + this.totalExpected = size; + } + + public boolean add(int index, HandleResult handleResult) { + handleResults[index] = handleResult; + int valid = handleResult.status() == HttpURLConnection.HTTP_OK + || handleResult.status() == HttpURLConnection.HTTP_CREATED + || handleResult.status() == HttpURLConnection.HTTP_ACCEPTED + || handleResult.status() == HttpURLConnection.HTTP_NOT_FOUND + ? validCount.incrementAndGet() : validCount.get(); + if (valid >= ackThreshold || totalCount.incrementAndGet() == totalExpected) { + sendResult(); + return true; + } + return false; + } + + private void sendResult() { + HandleResult mergedResult = new HandleResult(HttpURLConnection.HTTP_GATEWAY_TIMEOUT, null); + int localValidCount = 0; + for (HandleResult handleResult : handleResults) { + if (handleResult.status() == HttpURLConnection.HTTP_OK + || handleResult.status() == HttpURLConnection.HTTP_CREATED + || handleResult.status() == HttpURLConnection.HTTP_ACCEPTED + || handleResult.status() == HttpURLConnection.HTTP_NOT_FOUND) { + localValidCount++; + if (mergedResult.timestamp() <= handleResult.timestamp()) { + mergedResult = handleResult; + } + } + } + try { + if (localValidCount < ackThreshold) { + currentSession.sendResponse(new Response(Response.GATEWAY_TIMEOUT, Response.EMPTY)); + } else { + currentSession.sendResponse(new Response(String.valueOf(mergedResult.status()), mergedResult.data())); + } + } catch (Exception e) { + log.error("Exception during handleRequest", e); + try { + currentSession.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); + } catch (IOException ex) { + log.error("Exception while sending close connection", e); + currentSession.scheduleClose(); + } + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/PlyasovServer.java b/src/main/java/ru/vk/itmo/test/klimplyasov/PlyasovServer.java index f671706e5..84ec2242f 100644 --- a/src/main/java/ru/vk/itmo/test/klimplyasov/PlyasovServer.java +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/PlyasovServer.java @@ -1,145 +1,421 @@ package ru.vk.itmo.test.klimplyasov; -import one.nio.http.HttpException; +import one.nio.async.CustomThreadFactory; 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; +import one.nio.util.Utf8; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.vk.itmo.ServiceConfig; -import ru.vk.itmo.dao.BaseEntry; -import ru.vk.itmo.dao.Entry; +import ru.vk.itmo.test.klimplyasov.dao.ReferenceBaseEntry; import ru.vk.itmo.test.klimplyasov.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.nio.charset.StandardCharsets; -import java.util.Set; +import java.time.Duration; +import java.util.Iterator; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; -import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.Executors; public class PlyasovServer extends HttpServer { + private static final String HEADER_REMOTE = "X-flag-remote-reference-server-to-node-by-plyasov"; + private static final String HEADER_REMOTE_ONE_NIO_HEADER = HEADER_REMOTE + ": da"; + private static final String HEADER_TIMESTAMP = "X-flag-remote-reference-server-to-node-by-plyasov2"; + private static final String HEADER_TIMESTAMP_ONE_NIO_HEADER = HEADER_TIMESTAMP + ": "; + private static final Logger log = LoggerFactory.getLogger(PlyasovServer.class); + private static final int THREADS = Runtime.getRuntime().availableProcessors(); + private final ExecutorService executorLocal = + Executors.newFixedThreadPool(THREADS / 2, new CustomThreadFactory("local-work")); + private final ExecutorService executorRemote = + Executors.newFixedThreadPool(THREADS / 2, new CustomThreadFactory("remote-work")); private final ReferenceDao dao; - private final ExecutorService executorService; - private static final Logger logger = LoggerFactory.getLogger(Server.class.getName()); + private final ServiceConfig config; + private final HttpClient httpClient; - public PlyasovServer(ServiceConfig config, ReferenceDao dao, ExecutorService executorService) throws IOException { - super(createConfig(config)); + public PlyasovServer(ServiceConfig config, + ReferenceDao dao) throws IOException { + super(createServerConfigWithPort(config.selfPort())); this.dao = dao; - this.executorService = executorService; + this.config = config; + + this.httpClient = HttpClient.newBuilder() + .executor(Executors.newFixedThreadPool(THREADS)) + .connectTimeout(Duration.ofMillis(500)) + .version(HttpClient.Version.HTTP_1_1) + .build(); } - public static HttpServerConfig createConfig(ServiceConfig serviceConfig) { + private static HttpServerConfig createServerConfigWithPort(int port) { HttpServerConfig serverConfig = new HttpServerConfig(); AcceptorConfig acceptorConfig = new AcceptorConfig(); - acceptorConfig.port = serviceConfig.selfPort(); + acceptorConfig.port = port; acceptorConfig.reusePort = true; + serverConfig.selectors = Runtime.getRuntime().availableProcessors() / 2; - serverConfig.acceptors = new AcceptorConfig[] {acceptorConfig}; + serverConfig.acceptors = new AcceptorConfig[]{acceptorConfig}; serverConfig.closeSessions = true; - return serverConfig; } - @Path("/v0/entity") - @RequestMethod(Request.METHOD_GET) - public Response get(@Param(value = "id", required = true) String id) { - if (id.isEmpty()) return new Response(Response.BAD_REQUEST, Response.EMPTY); + @Override + public void handleRequest(Request request, HttpSession session) throws IOException { + if (!isValidRequest(request, session)) { + return; + } - Entry value = dao.get(MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8))); - return value == null - ? new Response(Response.NOT_FOUND, Response.EMPTY) - : Response.ok(value.value().toArray(ValueLayout.JAVA_BYTE)); + String path = request.getPath(); + if ("/v0/entities".equals(path)) { + handleEntitiesRequest(request, session); + } else { + handleOtherRequest(request, session); + } } - @Path("/v0/entity") - @RequestMethod(Request.METHOD_PUT) - public Response put(@Param(value = "id", required = true) String id, Request request) { - if (id.isEmpty()) return new Response(Response.BAD_REQUEST, Response.EMPTY); + private boolean isValidRequest(Request request, HttpSession session) throws IOException { + if (!isValidPath(request.getPath())) { + session.sendError(Response.BAD_REQUEST, null); + return false; + } - dao.upsert(new BaseEntry<>( - MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)), - MemorySegment.ofArray(request.getBody()) - ) - ); - return new Response(Response.CREATED, Response.EMPTY); + if (!isValidMethod(request.getMethod())) { + session.sendError(Response.METHOD_NOT_ALLOWED, null); + return false; + } + + return true; } - @Path("/v0/entity") - @RequestMethod(Request.METHOD_DELETE) - public Response delete(@Param(value = "id", required = true) String id) { - if (id.isEmpty()) return new Response(Response.BAD_REQUEST, Response.EMPTY); + private void handleOtherRequest(Request request, HttpSession session) throws IOException { + String id = request.getParameter("id="); - dao.upsert(new BaseEntry<>(MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)), null)); - return new Response(Response.ACCEPTED, Response.EMPTY); + if (isValidPathEntity(request.getPath()) && isInvalidId(id)) { + session.sendError(Response.BAD_REQUEST, null); + return; + } + + if (request.getHeader(HEADER_REMOTE_ONE_NIO_HEADER) != null) { + handleRemoteRequest(request, session, id); + return; + } + + int ack = getInt(request, "ack=", config.clusterUrls().size() / 2 + 1); + int from = getInt(request, "from=", config.clusterUrls().size()); + + if (isInvalidFromOrAck(from, ack)) { + session.sendError(Response.BAD_REQUEST, null); + return; + } + + int[] indexes = getIndexes(id, from); + MergeHandleResult mergeHandleResult = new MergeHandleResult(session, indexes.length, ack); + for (int i = 0; i < indexes.length; i++) { + int index = indexes[i]; + String executorNode = config.clusterUrls().get(index); + if (executorNode.equals(config.selfUrl())) { + handleAsync(executorLocal, i, mergeHandleResult, () -> handleLocalAsync(request, id)); + } else { + handleAsync(executorRemote, i, mergeHandleResult, () -> handleRemoteAsync(request, executorNode)); + } + } } - @Override - public void handleDefault(Request request, HttpSession session) throws IOException { - Set allowedMethods = Set.of( - Request.METHOD_GET, - Request.METHOD_PUT, - Request.METHOD_DELETE - ); + private boolean isValidPath(String path) { + return "/v0/entity".equals(path) || "/v0/entities".equals(path); + } - Response response = allowedMethods.contains(request.getMethod()) - ? new Response(Response.BAD_REQUEST, Response.EMPTY) - : new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); + private boolean isValidPathEntity(String path) { + return "/v0/entity".equals(path); + } - session.sendResponse(response); + private boolean isValidMethod(int method) { + return method == Request.METHOD_GET || method == Request.METHOD_DELETE || method == Request.METHOD_PUT; } - @Override - public void handleRequest(Request request, HttpSession session) throws IOException { - executorService.execute(() -> { - handleRequestAsync(request, session); + private boolean isInvalidId(String id) { + return id == null || id.isBlank(); + } + + private void handleRemoteRequest(Request request, HttpSession session, String id) { + executorLocal.execute(() -> { + try { + HandleResult local = local(request, id); + Response response = new Response(String.valueOf(local.status()), local.data()); + response.addHeader(HEADER_TIMESTAMP_ONE_NIO_HEADER + local.timestamp()); + session.sendResponse(response); + } catch (Exception e) { + logErrorAndHandleIOException(session, e); + } }); } - public void handleRequestAsync(Request request, HttpSession session) { + private boolean isInvalidFromOrAck(int from, int ack) { + return from <= 0 || from > config.clusterUrls().size() || ack > from || ack <= 0; + } + + private void logErrorAndHandleIOException(HttpSession session, Exception e) { + log.error("Exception during handleRequest", e); try { - super.handleRequest(request, session); - } catch (RejectedExecutionException executionException) { - handleRejectedExecutionException(executionException, session); - } catch (Exception e) { - handleOtherExceptions(e, session); + session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); + } catch (IOException ex) { + log.error("Exception while sending close connection", ex); + session.scheduleClose(); } } - private void handleRejectedExecutionException(RejectedExecutionException executionException, HttpSession session) { + private CompletableFuture handleRemoteAsync(final Request request, final String executorNode) { + return CompletableFuture.supplyAsync(() -> { + try { + return remote(request, executorNode); + } catch (Exception e) { + log.error("Exception during remote handling", e); + return new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY); + } + }, executorRemote); + } + + private CompletableFuture handleLocalAsync(final Request request, final String executorNode) { + return CompletableFuture.supplyAsync(() -> { + try { + return local(request, executorNode); + } catch (Exception e) { + log.error("Exception during local handling", e); + return new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY); + } + }, executorLocal); + } + + private void handleAsync(final ExecutorService executor, + final int index, + final MergeHandleResult mergeHandleResult, + final ERunnable runnable) { + CompletableFuture futureResult; try { - logger.error("Error handling rejected execution", executionException); - session.sendError(Response.SERVICE_UNAVAILABLE, ""); - } catch (IOException ioException) { - handleIoException(ioException, session); + futureResult = runnable.run(); + } catch (Exception e) { + log.error("Exception during handling", e); + futureResult = CompletableFuture.completedFuture( + new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, + Response.EMPTY) + ); + } + + futureResult = futureResult.whenCompleteAsync((handleResult, throwable) -> { + if (throwable != null) { + log.error("Exception during handleAsync", throwable); + handleResult = new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY); + } + mergeHandleResult.add(index, handleResult); + }, executor); + log.trace(futureResult.toString()); + } + + private int getInt(Request request, String param, int defaultValue) throws IOException { + int ack; + String ackStr = request.getParameter(param); + if (ackStr == null || ackStr.isBlank()) { + ack = defaultValue; + } else { + try { + ack = Integer.parseInt(ackStr); + } catch (Exception e) { + // todo ваша ошибка + throw new IllegalArgumentException("parse error"); + } } + return ack; } - private void handleOtherExceptions(Exception e, HttpSession session) { + private HandleResult remote(Request request, String executorNode) { try { - String response = e instanceof HttpException ? Response.BAD_REQUEST : Response.INTERNAL_ERROR; - session.sendError(response, ""); - } catch (IOException ex) { - handleIoException(ex, session); + return invokeRemote(executorNode, request); + } catch (IOException e) { + log.info("I/O exception while calling remote node", e); + return new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.info("Thread interrupted"); + return new HandleResult(HttpURLConnection.HTTP_UNAVAILABLE, Response.EMPTY); } + } - private void handleIoException(IOException ioException, HttpSession session) { - logger.error("IO Exception occurred", ioException); - session.close(); - Thread.currentThread().interrupt(); + @Override + public void handleDefault(Request request, HttpSession session) throws IOException { + session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); } @Path("/v0/status") public Response status() { return Response.ok("OK"); } + + private HandleResult invokeRemote(String executorNode, Request request) throws IOException, InterruptedException { + HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(executorNode + request.getURI())) + .method( + request.getMethodName(), + request.getBody() == null + ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofByteArray(request.getBody()) + ) + .header(HEADER_REMOTE, "da") + .timeout(Duration.ofMillis(500)) + .build(); + HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray()); + Optional string = httpResponse.headers().firstValue(HEADER_TIMESTAMP); + long timestamp; + if (string.isPresent()) { + try { + timestamp = Long.parseLong(string.get()); + } catch (Exception e) { + log.error("todo "); + timestamp = 0; + } + } else { + timestamp = 0; + } + return new HandleResult(httpResponse.statusCode(), httpResponse.body(), timestamp); + } + + private HandleResult local(Request request, String id) { + long currentTimeMillis = System.currentTimeMillis(); + switch (request.getMethod()) { + case Request.METHOD_GET -> { + if (isValidPathEntity(request.getPath())) { + MemorySegment key = MemorySegment.ofArray(Utf8.toBytes(id)); + ReferenceBaseEntry entry = dao.get(key); + if (entry == null) { + return new HandleResult(HttpURLConnection.HTTP_NOT_FOUND, Response.EMPTY); + } + if (entry.value() == null) { + return new HandleResult( + HttpURLConnection.HTTP_NOT_FOUND, + Response.EMPTY, + entry.timestamp()); + } + + return new HandleResult( + HttpURLConnection.HTTP_OK, + entry.value().toArray(ValueLayout.JAVA_BYTE), + entry.timestamp() + ); + } + } + case Request.METHOD_PUT -> { + MemorySegment key = MemorySegment.ofArray(Utf8.toBytes(id)); + MemorySegment value = MemorySegment.ofArray(request.getBody()); + dao.upsert(new ReferenceBaseEntry<>(key, value, currentTimeMillis)); + return new HandleResult(HttpURLConnection.HTTP_CREATED, Response.EMPTY); + } + case Request.METHOD_DELETE -> { + MemorySegment key = MemorySegment.ofArray(Utf8.toBytes(id)); + dao.upsert(new ReferenceBaseEntry<>(key, null, currentTimeMillis)); + return new HandleResult(HttpURLConnection.HTTP_ACCEPTED, Response.EMPTY); + } + default -> { + return new HandleResult(HttpURLConnection.HTTP_BAD_METHOD, Response.EMPTY); + } + } + return null; + } + + public void handleEntitiesRequest(Request request, HttpSession session) throws IOException { + String start = request.getParameter("start="); + String end = request.getParameter("end="); + if (start == null || start.isBlank()) { + session.sendError(Response.BAD_REQUEST, null); + } + if (end != null && !end.isBlank()) { + handleLocalEntitiesRangeRequest(session, start, end); + } else { + handleLocalEntitiesRangeRequest(session, start, null); + } + } + + private void handleLocalEntitiesRangeRequest(HttpSession session, String start, String end) { + executorLocal.execute(() -> { + try { + ChunkGenerator.writeResponseHeaders(session); + Iterator> iterator = getMemorySegmentsIterator(start, end); + writeDataChunks(session, iterator); + ChunkGenerator.writeEmptyChunk(session); + session.scheduleClose(); + } catch (IOException e) { + log.error("EXC"); + } + }); + } + + private Iterator> getMemorySegmentsIterator( + String start, + String end + ) throws IOException { + return dao.get( + MemorySegment.ofArray(start.getBytes(StandardCharsets.UTF_8)), + end == null ? null : MemorySegment.ofArray(end.getBytes(StandardCharsets.UTF_8)) + ); + } + + private void writeDataChunks( + HttpSession session, + Iterator> iterator + ) throws IOException { + while (iterator.hasNext()) { + ChunkGenerator.writeDataChunk(session, iterator.next()); + } + } + + private int[] getIndexes(String id, int count) { + assert count < 5; + + int[] result = new int[count]; + int[] maxHashs = new int[count]; + + for (int i = 0; i < count; i++) { + String url = config.clusterUrls().get(i); + int hash = Hash.murmur3(url + id); + result[i] = i; + maxHashs[i] = hash; + } + + for (int i = count; i < config.clusterUrls().size(); i++) { + String url = config.clusterUrls().get(i); + int hash = Hash.murmur3(url + id); + for (int j = 0; j < maxHashs.length; j++) { + int maxHash = maxHashs[j]; + if (maxHash < hash) { + maxHashs[j] = hash; + result[j] = i; + break; + } + } + } + return result; + } + + private interface ERunnable { + CompletableFuture run(); + } + + @Override + public synchronized void stop() { + super.stop(); + PlyasovService.shutdownAndAwaitTermination(executorLocal); + PlyasovService.shutdownAndAwaitTermination(executorRemote); + } } diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/PlyasovService.java b/src/main/java/ru/vk/itmo/test/klimplyasov/PlyasovService.java index 84838e86c..92d43e9f4 100644 --- a/src/main/java/ru/vk/itmo/test/klimplyasov/PlyasovService.java +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/PlyasovService.java @@ -1,5 +1,7 @@ package ru.vk.itmo.test.klimplyasov; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ru.vk.itmo.Service; import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.dao.Config; @@ -7,43 +9,76 @@ import ru.vk.itmo.test.klimplyasov.dao.ReferenceDao; import java.io.IOException; -import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; public class PlyasovService implements Service { - private PlyasovServer server; - private ReferenceDao dao; + private static final long FLUSHING_THRESHOLD_BYTES = 1024 * 1024; + private static final Logger logger = LoggerFactory.getLogger(PlyasovServer.class); + + private static final String LOCALHOST_PREFIX = "http://localhost:"; + private final ServiceConfig config; - private ExecutorService executorService; + + private ReferenceDao dao; + private PlyasovServer server; + private boolean stopped; public PlyasovService(ServiceConfig config) { this.config = config; } @Override - public CompletableFuture start() throws IOException { - dao = new ReferenceDao(new Config(config.workingDir(), 48000)); - try { - executorService = ExecutorConfig.getExecutorService(); - server = new PlyasovServer(config, dao, executorService); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + public synchronized CompletableFuture start() throws IOException { + dao = new ReferenceDao(new Config(config.workingDir(), FLUSHING_THRESHOLD_BYTES)); + server = new PlyasovServer(config, dao); server.start(); + stopped = false; return CompletableFuture.completedFuture(null); } @Override - public CompletableFuture stop() throws IOException { - server.stop(); - executorService.close(); - dao.close(); + public synchronized CompletableFuture stop() throws IOException { + if (stopped) { + return CompletableFuture.completedFuture(null); + } + try { + server.stop(); + + } finally { + dao.close(); + } + stopped = true; return CompletableFuture.completedFuture(null); } - @ServiceFactory(stage = 2) + public static void shutdownAndAwaitTermination(ExecutorService pool) { + pool.shutdown(); + try { + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { + pool.shutdownNow(); + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { + logger.error("Pool did not terminate"); + } + } + } catch (InterruptedException ex) { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + @ServiceFactory(stage = 6) public static class Factory implements ServiceFactory.Factory { @Override @@ -51,4 +86,37 @@ public Service create(ServiceConfig config) { return new PlyasovService(config); } } + + public static void main(String[] args) throws IOException { + //port -> url + Map nodes = new HashMap<>(); + int nodePort = 8080; + for (int i = 0; i < 3; i++) { + nodes.put(nodePort, LOCALHOST_PREFIX + nodePort); + nodePort += 10; + } + + List clusterUrls = new ArrayList<>(nodes.values()); + List clusterConfs = new ArrayList<>(); + for (Map.Entry entry : nodes.entrySet()) { + int port = entry.getKey(); + String url = entry.getValue(); + Path path = Paths.get("tmp/db/" + port); + Files.createDirectories(path); + ServiceConfig serviceConfig = new ServiceConfig(port, + url, + clusterUrls, + path); + clusterConfs.add(serviceConfig); + } + + for (ServiceConfig serviceConfig : clusterConfs) { + PlyasovService instance = new PlyasovService(serviceConfig); + try { + instance.start().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Thread.currentThread().interrupt(); + } + } + } } diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/Server.java b/src/main/java/ru/vk/itmo/test/klimplyasov/Server.java deleted file mode 100644 index 308399691..000000000 --- a/src/main/java/ru/vk/itmo/test/klimplyasov/Server.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.vk.itmo.test.klimplyasov; - -import ru.vk.itmo.ServiceConfig; -import ru.vk.itmo.dao.Config; -import ru.vk.itmo.test.klimplyasov.dao.ReferenceDao; - -import java.io.IOException; -import java.nio.file.Files; -import java.util.List; -import java.util.concurrent.ExecutorService; - -public final class Server { - - private Server() { - //:) - } - - public static void main(String[] args) throws IOException { - ServiceConfig serviceConfig = new ServiceConfig( - 8080, - "localhost", - List.of("localhost"), - Files.createTempDirectory(".") - ); - ReferenceDao dao = createDao(serviceConfig); - ExecutorService executorService = ExecutorConfig.getExecutorService(); - PlyasovServer server = new PlyasovServer(serviceConfig, dao, executorService); - server.start(); - } - - private static ReferenceDao createDao(ServiceConfig serviceConfig) throws IOException { - return new ReferenceDao(new Config(serviceConfig.workingDir(), 48000)); - } -} diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/LiveFilteringIterator.java b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/LiveFilteringIterator.java index 95546138e..4db2d5765 100644 --- a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/LiveFilteringIterator.java +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/LiveFilteringIterator.java @@ -11,18 +11,18 @@ * * @author incubos */ -final class LiveFilteringIterator implements Iterator> { - private final Iterator> delegate; - private Entry next; +final class LiveFilteringIterator implements Iterator> { + private final Iterator> delegate; + private ReferenceBaseEntry 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 ReferenceBaseEntry entry = delegate.next(); if (entry.value() != null) { this.next = entry; break; @@ -36,13 +36,13 @@ public boolean hasNext() { } @Override - public Entry next() { + public ReferenceBaseEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } // Consume - final Entry result = next; + final ReferenceBaseEntry result = next; next = null; skipTombstones(); diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/MemTable.java index ff5bef636..68e4f4daf 100644 --- a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/MemTable.java +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/MemTable.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.klimplyasov.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) { + ReferenceBaseEntry get(final MemorySegment key) { return map.get(key); } - Entry upsert(final Entry entry) { + ReferenceBaseEntry upsert(final ReferenceBaseEntry entry) { return map.put(entry.key(), entry); } } diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/MergingEntryIterator.java b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/MergingEntryIterator.java index 673dc30e1..4f190d0e7 100644 --- a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/MergingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/MergingEntryIterator.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.klimplyasov.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 ReferenceBaseEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } final WeightedPeekingEntryIterator top = iterators.remove(); - final Entry result = top.next(); + final ReferenceBaseEntry 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 ReferenceBaseEntry 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/klimplyasov/dao/ReferenceBaseEntry.java b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/ReferenceBaseEntry.java new file mode 100644 index 000000000..3df01a88e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/ReferenceBaseEntry.java @@ -0,0 +1,10 @@ +package ru.vk.itmo.test.klimplyasov.dao; + +import ru.vk.itmo.dao.Entry; + +public record ReferenceBaseEntry(D key, D value, long timestamp) implements Entry { + @Override + public String toString() { + return "{" + key + ":" + value + "," + timestamp + "}"; + } +} diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/ReferenceDao.java b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/ReferenceDao.java index ad4b99fd8..fe7e6821b 100644 --- a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/ReferenceDao.java +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/ReferenceDao.java @@ -22,7 +22,7 @@ * * @author incubos */ -public class ReferenceDao implements Dao> { +public class ReferenceDao implements Dao> { private final Config config; private final Arena arena; @@ -63,7 +63,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 +73,13 @@ public Iterator> get( } @Override - public Entry get(final MemorySegment key) { + public ReferenceBaseEntry 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 ReferenceBaseEntry entry) { final boolean autoFlush; lock.readLock().lock(); try { diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/SSTable.java index c3dfff75d..8ed53ae88 100644 --- a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/SSTable.java +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/SSTable.java @@ -1,8 +1,5 @@ package ru.vk.itmo.test.klimplyasov.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; @@ -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) { + ReferenceBaseEntry get(final MemorySegment key) { final long entry = entryBinarySearch(key); if (entry < 0) { return null; @@ -143,20 +140,23 @@ Entry get(final MemorySegment key) { // Skip key (will reuse the argument) long offset = entryOffset(entry); offset += Long.BYTES + key.byteSize(); + // Extract timestamp + long timestamp = data.get(ValueLayout.OfLong.JAVA_LONG_UNALIGNED, offset); + offset += Long.BYTES; // Extract value length final long valueLength = getLength(offset); if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { // Tombstone encountered - return new BaseEntry<>(key, null); + return new ReferenceBaseEntry<>(key, null, timestamp); } else { // Get value offset += Long.BYTES; final MemorySegment value = data.asSlice(offset, valueLength); - return new BaseEntry<>(key, value); + return new ReferenceBaseEntry<>(key, value, timestamp); } } - private final class SliceIterator implements Iterator> { + private final class SliceIterator implements Iterator> { private long offset; private final long toOffset; @@ -173,7 +173,7 @@ public boolean hasNext() { } @Override - public Entry next() { + public ReferenceBaseEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } @@ -186,6 +186,10 @@ public Entry next() { final MemorySegment key = data.asSlice(offset, keyLength); offset += keyLength; + // Read timestamp + long timestamp = data.get(ValueLayout.OfLong.JAVA_LONG_UNALIGNED, offset); + offset += Long.BYTES; + // Read value length final long valueLength = getLength(offset); offset += Long.BYTES; @@ -193,11 +197,11 @@ public Entry next() { // Read value if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { // Tombstone encountered - return new BaseEntry<>(key, null); + return new ReferenceBaseEntry<>(key, null, timestamp); } else { final MemorySegment value = data.asSlice(offset, valueLength); offset += valueLength; - return new BaseEntry<>(key, value); + return new ReferenceBaseEntry<>(key, value, timestamp); } } } diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/SSTableWriter.java b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/SSTableWriter.java index 4fbd52436..902ea02fa 100644 --- a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/SSTableWriter.java +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/SSTableWriter.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.klimplyasov.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 ReferenceBaseEntry} {@link Iterator} to SSTable on disk. * *

Index file {@code .index} contains {@code long} offsets to entries in data file: * {@code [offset0, offset1, ...]} @@ -40,7 +38,7 @@ final class SSTableWriter { void write( final Path baseDir, final int sequence, - final Iterator> entries) throws IOException { + final Iterator> entries) throws IOException { // Write to temporary files final Path tempIndexName = SSTables.tempIndexName(baseDir, sequence); final Path tempDataName = SSTables.tempDataName(baseDir, sequence); @@ -71,7 +69,7 @@ void write( writeLong(entryOffset, index); // Then write the entry - final Entry entry = entries.next(); + final ReferenceBaseEntry entry = entries.next(); entryOffset += writeEntry(entry, data); } } @@ -127,15 +125,16 @@ private void writeSegment( } /** - * Writes {@link Entry} to {@link FileChannel}. + * Writes {@link ReferenceBaseEntry} to {@link FileChannel}. * * @return written bytes */ private long writeEntry( - final Entry entry, + final ReferenceBaseEntry 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 @@ -146,6 +145,10 @@ private long writeEntry( writeSegment(key, os); result += key.byteSize(); + // timestamp + writeLong(timestamp, os); + result += Long.BYTES; + // Value size and possibly value if (value == null) { // Tombstone diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/TableSet.java index bc1693183..5a3f1d5cb 100644 --- a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/TableSet.java +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/TableSet.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.klimplyasov.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; import java.util.ArrayList; import java.util.Collections; @@ -98,14 +96,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 +114,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 +127,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,14 +144,14 @@ Iterator> get( }; } - Entry get(final MemorySegment key) { + ReferenceBaseEntry get(final MemorySegment key) { // Slightly optimized version not to pollute the heap // First check MemTable - Entry result = memTable.get(key); + ReferenceBaseEntry result = memTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } // Then check flushing @@ -161,7 +159,7 @@ Entry get(final MemorySegment key) { result = flushingTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } } @@ -170,7 +168,7 @@ Entry get(final MemorySegment key) { result = ssTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } } @@ -178,21 +176,17 @@ Entry get(final MemorySegment key) { return null; } - private static Entry swallowTombstone(final Entry entry) { - return entry.value() == null ? null : entry; - } - - Entry upsert(final Entry entry) { + ReferenceBaseEntry upsert(final ReferenceBaseEntry 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( diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/WeightedPeekingEntryIterator.java b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/WeightedPeekingEntryIterator.java index 49e19fb28..9d718baad 100644 --- a/src/main/java/ru/vk/itmo/test/klimplyasov/dao/WeightedPeekingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/dao/WeightedPeekingEntryIterator.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.klimplyasov.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 ReferenceBaseEntry 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 ReferenceBaseEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } - final Entry result = next; + final ReferenceBaseEntry result = next; next = delegate.hasNext() ? delegate.next() : null; return result; } - Entry peek() { + ReferenceBaseEntry peek() { if (!hasNext()) { throw new NoSuchElementException(); } diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/async_profiler/get_alloc.html b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/async_profiler/get_alloc.html new file mode 100644 index 000000000..e31cd9738 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/async_profiler/get_alloc.html @@ -0,0 +1,2637 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/async_profiler/get_cpu.html b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/async_profiler/get_cpu.html new file mode 100644 index 000000000..2d6e529dd --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/async_profiler/get_cpu.html @@ -0,0 +1,4333 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/async_profiler/put_alloc.html b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/async_profiler/put_alloc.html new file mode 100644 index 000000000..ca719b788 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/async_profiler/put_alloc.html @@ -0,0 +1,2994 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/async_profiler/put_cpu.html b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/async_profiler/put_cpu.html new file mode 100644 index 000000000..7a55387eb --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/async_profiler/put_cpu.html @@ -0,0 +1,4535 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/stage5.md b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/stage5.md new file mode 100644 index 000000000..a499cd33e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/stage5.md @@ -0,0 +1,301 @@ + +## Нагрузочное тестирование +### PUT 12000 RPS +``` +Running 30s test @ http://localhost:8080 + 4 threads and 64 connections + Thread calibration: mean lat.: 967.433ms, rate sampling interval: 2715ms + Thread calibration: mean lat.: 970.451ms, rate sampling interval: 2717ms + Thread calibration: mean lat.: 965.740ms, rate sampling interval: 2711ms + Thread calibration: mean lat.: 971.021ms, rate sampling interval: 2717ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 9.96ms 30.28ms 269.82ms 95.10% + Req/Sec 3.03k 76.42 3.22k 85.71% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.46ms + 75.000% 3.87ms + 90.000% 12.25ms + 99.000% 177.41ms + 99.900% 238.08ms + 99.990% 266.24ms + 99.999% 269.31ms +100.000% 270.08ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.157 0.000000 1 1.00 + 1.242 0.100000 24161 1.11 + 1.585 0.200000 48322 1.25 + 1.872 0.300000 72486 1.43 + 2.149 0.400000 96581 1.67 + 2.463 0.500000 120778 2.00 + 2.635 0.550000 132826 2.22 + 2.837 0.600000 144867 2.50 + 3.081 0.650000 156999 2.86 + 3.389 0.700000 169012 3.33 + 3.871 0.750000 181052 4.00 + 4.215 0.775000 187088 4.44 + 4.671 0.800000 193154 5.00 + 5.315 0.825000 199149 5.71 + 6.295 0.850000 205195 6.67 + 7.959 0.875000 211213 8.00 + 9.879 0.887500 214231 8.89 + 12.247 0.900000 217250 10.00 + 14.415 0.912500 220266 11.43 + 16.639 0.925000 223298 13.33 + 20.655 0.937500 226300 16.00 + 23.983 0.943750 227813 17.78 + 32.575 0.950000 229317 20.00 + 69.823 0.956250 230829 22.86 + 93.439 0.962500 232339 26.67 + 112.447 0.968750 233845 32.00 + 120.191 0.971875 234604 35.56 + 127.743 0.975000 235352 40.00 + 136.319 0.978125 236114 45.71 + 146.559 0.981250 236868 53.33 + 156.927 0.984375 237628 64.00 + 161.407 0.985938 237996 71.11 + 167.039 0.987500 238375 80.00 + 172.927 0.989062 238750 91.43 + 180.351 0.990625 239131 106.67 + 187.263 0.992188 239509 128.00 + 190.591 0.992969 239693 142.22 + 193.919 0.993750 239885 160.00 + 197.247 0.994531 240067 182.86 + 201.087 0.995313 240255 213.33 + 205.951 0.996094 240445 256.00 + 208.767 0.996484 240541 284.44 + 211.967 0.996875 240633 320.00 + 215.039 0.997266 240728 365.71 + 220.031 0.997656 240821 426.67 + 224.895 0.998047 240916 512.00 + 227.071 0.998242 240962 568.89 + 229.759 0.998437 241012 640.00 + 232.191 0.998633 241057 731.43 + 234.623 0.998828 241107 853.33 + 238.719 0.999023 241152 1024.00 + 240.383 0.999121 241174 1137.78 + 243.199 0.999219 241198 1280.00 + 246.015 0.999316 241221 1462.86 + 248.959 0.999414 241245 1706.67 + 251.647 0.999512 241269 2048.00 + 253.055 0.999561 241281 2275.56 + 254.847 0.999609 241293 2560.00 + 255.999 0.999658 241304 2925.71 + 257.535 0.999707 241316 3413.33 + 260.095 0.999756 241328 4096.00 + 261.759 0.999780 241333 4551.11 + 263.167 0.999805 241339 5120.00 + 263.935 0.999829 241345 5851.43 + 264.959 0.999854 241351 6826.67 + 265.727 0.999878 241357 8192.00 + 265.983 0.999890 241361 9102.22 + 266.239 0.999902 241365 10240.00 + 266.495 0.999915 241368 11702.86 + 267.007 0.999927 241372 13653.33 + 267.007 0.999939 241372 16384.00 + 267.263 0.999945 241375 18204.44 + 267.263 0.999951 241375 20480.00 + 267.519 0.999957 241376 23405.71 + 268.287 0.999963 241378 27306.67 + 268.543 0.999969 241379 32768.00 + 268.799 0.999973 241381 36408.89 + 268.799 0.999976 241381 40960.00 + 268.799 0.999979 241381 46811.43 + 269.055 0.999982 241383 54613.33 + 269.055 0.999985 241383 65536.00 + 269.055 0.999986 241383 72817.78 + 269.311 0.999988 241384 81920.00 + 269.311 0.999989 241384 93622.86 + 269.311 0.999991 241384 109226.67 + 269.823 0.999992 241385 131072.00 + 269.823 0.999993 241385 145635.56 + 269.823 0.999994 241385 163840.00 + 269.823 0.999995 241385 187245.71 + 269.823 0.999995 241385 218453.33 + 270.079 0.999996 241386 262144.00 + 270.079 1.000000 241386 inf +#[Mean = 9.955, StdDeviation = 30.281] +#[Max = 269.824, Total count = 241386] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 359553 requests in 30.00s, 20.23MB read +Requests/sec: 11985.14 +Transfer/sec: 690.55KB + + +``` + +Гистограмма `PUT`: +![put_histogram.png](./wrk/put_histogram.png) +### GET 12000 RPS +``` +Running 30s test @ http://localhost:8080 + 4 threads and 64 connections + Thread calibration: mean lat.: 17.082ms, rate sampling interval: 107ms + Thread calibration: mean lat.: 17.162ms, rate sampling interval: 107ms + Thread calibration: mean lat.: 17.326ms, rate sampling interval: 108ms + Thread calibration: mean lat.: 17.412ms, rate sampling interval: 109ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.37ms 6.12ms 75.26ms 95.67% + Req/Sec 3.01k 189.51 4.74k 92.41% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.21ms + 75.000% 3.02ms + 90.000% 4.41ms + 99.000% 32.83ms + 99.900% 73.09ms + 99.990% 74.56ms + 99.999% 75.20ms +100.000% 75.33ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.133 0.000000 1 1.00 + 1.184 0.100000 23953 1.11 + 1.487 0.200000 47844 1.25 + 1.735 0.300000 71713 1.43 + 1.969 0.400000 95696 1.67 + 2.211 0.500000 119535 2.00 + 2.341 0.550000 131485 2.22 + 2.481 0.600000 143485 2.50 + 2.637 0.650000 155428 2.86 + 2.813 0.700000 167461 3.33 + 3.021 0.750000 179357 4.00 + 3.145 0.775000 185280 4.44 + 3.293 0.800000 191259 5.00 + 3.465 0.825000 197255 5.71 + 3.685 0.850000 203188 6.67 + 3.985 0.875000 209183 8.00 + 4.179 0.887500 212177 8.89 + 4.411 0.900000 215155 10.00 + 4.707 0.912500 218151 11.43 + 5.183 0.925000 221121 13.33 + 6.111 0.937500 224105 16.00 + 7.083 0.943750 225595 17.78 + 8.179 0.950000 227091 20.00 + 9.399 0.956250 228591 22.86 + 10.799 0.962500 230079 26.67 + 12.855 0.968750 231575 32.00 + 13.863 0.971875 232322 35.56 + 14.855 0.975000 233066 40.00 + 16.007 0.978125 233815 45.71 + 17.903 0.981250 234562 53.33 + 19.999 0.984375 235308 64.00 + 22.143 0.985938 235677 71.11 + 25.471 0.987500 236051 80.00 + 30.319 0.989062 236424 91.43 + 34.271 0.990625 236799 106.67 + 40.735 0.992188 237171 128.00 + 44.351 0.992969 237358 142.22 + 47.455 0.993750 237547 160.00 + 50.527 0.994531 237731 182.86 + 56.991 0.995313 237918 213.33 + 63.519 0.996094 238105 256.00 + 66.751 0.996484 238198 284.44 + 69.439 0.996875 238297 320.00 + 70.975 0.997266 238392 365.71 + 71.615 0.997656 238479 426.67 + 72.063 0.998047 238573 512.00 + 72.319 0.998242 238629 568.89 + 72.511 0.998437 238681 640.00 + 72.639 0.998633 238717 731.43 + 72.831 0.998828 238763 853.33 + 73.087 0.999023 238810 1024.00 + 73.151 0.999121 238828 1137.78 + 73.343 0.999219 238860 1280.00 + 73.471 0.999316 238877 1462.86 + 73.599 0.999414 238903 1706.67 + 73.727 0.999512 238922 2048.00 + 73.855 0.999561 238938 2275.56 + 73.919 0.999609 238947 2560.00 + 74.047 0.999658 238964 2925.71 + 74.111 0.999707 238973 3413.33 + 74.175 0.999756 238982 4096.00 + 74.239 0.999780 238986 4551.11 + 74.367 0.999805 239000 5120.00 + 74.367 0.999829 239000 5851.43 + 74.431 0.999854 239006 6826.67 + 74.495 0.999878 239011 8192.00 + 74.559 0.999890 239014 9102.22 + 74.623 0.999902 239019 10240.00 + 74.623 0.999915 239019 11702.86 + 74.687 0.999927 239021 13653.33 + 74.751 0.999939 239024 16384.00 + 74.815 0.999945 239026 18204.44 + 74.879 0.999951 239030 20480.00 + 74.879 0.999957 239030 23405.71 + 74.879 0.999963 239030 27306.67 + 74.943 0.999969 239032 32768.00 + 74.943 0.999973 239032 36408.89 + 75.007 0.999976 239033 40960.00 + 75.007 0.999979 239033 46811.43 + 75.071 0.999982 239034 54613.33 + 75.135 0.999985 239035 65536.00 + 75.135 0.999986 239035 72817.78 + 75.199 0.999988 239037 81920.00 + 75.199 0.999989 239037 93622.86 + 75.199 0.999991 239037 109226.67 + 75.199 0.999992 239037 131072.00 + 75.199 0.999993 239037 145635.56 + 75.199 0.999994 239037 163840.00 + 75.199 0.999995 239037 187245.71 + 75.199 0.999995 239037 218453.33 + 75.327 0.999996 239038 262144.00 + 75.327 1.000000 239038 inf +#[Mean = 3.366, StdDeviation = 6.123] +#[Max = 75.264, Total count = 239038] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 359563 requests in 30.00s, 23.92MB read +Requests/sec: 11984.82 +Transfer/sec: 816.53KB + + +``` +Гистограмма `GET`: +![get_histogram.png](./wrk/get_histogram.png) + + +### async-profiler +## Вывод профилирования PUT аллокации + +### completebleFuture.completeAsync: + +Выделение памяти для выполнения асинхронных операций в CompletableFuture составляет приблизительно 31% + +### threadpoolexecutor.run: + +Выделение памяти при выполнении задач в пуле потоков составляет приблизительно 30%. +Оставшиеся ~13% занимают операции в рамках one/nio/server/SelectorThread.run. + +## Вывод профилирования PUT CPU + +### one/nio/server/SelectorThread.run: + +Выполнение операций внутри потока селектора занимает приблизительно 20% CPU времени: +- ~12% на one/nio/net/JavaSelector.select +- ~9% на обработку сетевых сессий внутри потока селектора + +- Выполнение задач в пуле потоков занимает приблизительно 30% CPU времени: + - ~12% на выполнение асинхронных операций в рамках CompletableFuture. + - ~14% на получение и обработка задач в пуле потоков. + +## Вывод профилирования GET аллокации + + +- Обработка операций в CompletableFuture составляет приблизительно 21%. +- Обработка сессий составляет приблизительно 12%. +- Процесс записи составляет приблизительно 4%. +- Выполнение задач воркерами пула потоков занимает более половины всех аллоцированных ресурсов, около 54%. + +## Вывод профилирования GET CPU + +- Операции с LinkedBlockingQueue занимают около 22% процессорного времени. +- Выполнение операций в CompletableFuture занимает приблизительно 12% процессорного времени. +- Операция select в Java Selector занимает приблизительно 12% процессорного времени. +- Обработка сессий занимает около 10% процессорного времени. +- Операции в SelectorManager занимают приблизительно 14% процессорного времени. +- Выполнение задач в планировщике занимает около 10% процессорного времени. diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/wrk/get_histogram.png b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/wrk/get_histogram.png new file mode 100644 index 000000000..e7c4d3834 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/wrk/get_histogram.png differ diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/wrk/get_hw5.txt b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/wrk/get_hw5.txt new file mode 100644 index 000000000..4f1ea3961 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/wrk/get_hw5.txt @@ -0,0 +1,121 @@ +Running 30s test @ http://localhost:8080 + 4 threads and 64 connections + Thread calibration: mean lat.: 17.082ms, rate sampling interval: 107ms + Thread calibration: mean lat.: 17.162ms, rate sampling interval: 107ms + Thread calibration: mean lat.: 17.326ms, rate sampling interval: 108ms + Thread calibration: mean lat.: 17.412ms, rate sampling interval: 109ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.37ms 6.12ms 75.26ms 95.67% + Req/Sec 3.01k 189.51 4.74k 92.41% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.21ms + 75.000% 3.02ms + 90.000% 4.41ms + 99.000% 32.83ms + 99.900% 73.09ms + 99.990% 74.56ms + 99.999% 75.20ms +100.000% 75.33ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.133 0.000000 1 1.00 + 1.184 0.100000 23953 1.11 + 1.487 0.200000 47844 1.25 + 1.735 0.300000 71713 1.43 + 1.969 0.400000 95696 1.67 + 2.211 0.500000 119535 2.00 + 2.341 0.550000 131485 2.22 + 2.481 0.600000 143485 2.50 + 2.637 0.650000 155428 2.86 + 2.813 0.700000 167461 3.33 + 3.021 0.750000 179357 4.00 + 3.145 0.775000 185280 4.44 + 3.293 0.800000 191259 5.00 + 3.465 0.825000 197255 5.71 + 3.685 0.850000 203188 6.67 + 3.985 0.875000 209183 8.00 + 4.179 0.887500 212177 8.89 + 4.411 0.900000 215155 10.00 + 4.707 0.912500 218151 11.43 + 5.183 0.925000 221121 13.33 + 6.111 0.937500 224105 16.00 + 7.083 0.943750 225595 17.78 + 8.179 0.950000 227091 20.00 + 9.399 0.956250 228591 22.86 + 10.799 0.962500 230079 26.67 + 12.855 0.968750 231575 32.00 + 13.863 0.971875 232322 35.56 + 14.855 0.975000 233066 40.00 + 16.007 0.978125 233815 45.71 + 17.903 0.981250 234562 53.33 + 19.999 0.984375 235308 64.00 + 22.143 0.985938 235677 71.11 + 25.471 0.987500 236051 80.00 + 30.319 0.989062 236424 91.43 + 34.271 0.990625 236799 106.67 + 40.735 0.992188 237171 128.00 + 44.351 0.992969 237358 142.22 + 47.455 0.993750 237547 160.00 + 50.527 0.994531 237731 182.86 + 56.991 0.995313 237918 213.33 + 63.519 0.996094 238105 256.00 + 66.751 0.996484 238198 284.44 + 69.439 0.996875 238297 320.00 + 70.975 0.997266 238392 365.71 + 71.615 0.997656 238479 426.67 + 72.063 0.998047 238573 512.00 + 72.319 0.998242 238629 568.89 + 72.511 0.998437 238681 640.00 + 72.639 0.998633 238717 731.43 + 72.831 0.998828 238763 853.33 + 73.087 0.999023 238810 1024.00 + 73.151 0.999121 238828 1137.78 + 73.343 0.999219 238860 1280.00 + 73.471 0.999316 238877 1462.86 + 73.599 0.999414 238903 1706.67 + 73.727 0.999512 238922 2048.00 + 73.855 0.999561 238938 2275.56 + 73.919 0.999609 238947 2560.00 + 74.047 0.999658 238964 2925.71 + 74.111 0.999707 238973 3413.33 + 74.175 0.999756 238982 4096.00 + 74.239 0.999780 238986 4551.11 + 74.367 0.999805 239000 5120.00 + 74.367 0.999829 239000 5851.43 + 74.431 0.999854 239006 6826.67 + 74.495 0.999878 239011 8192.00 + 74.559 0.999890 239014 9102.22 + 74.623 0.999902 239019 10240.00 + 74.623 0.999915 239019 11702.86 + 74.687 0.999927 239021 13653.33 + 74.751 0.999939 239024 16384.00 + 74.815 0.999945 239026 18204.44 + 74.879 0.999951 239030 20480.00 + 74.879 0.999957 239030 23405.71 + 74.879 0.999963 239030 27306.67 + 74.943 0.999969 239032 32768.00 + 74.943 0.999973 239032 36408.89 + 75.007 0.999976 239033 40960.00 + 75.007 0.999979 239033 46811.43 + 75.071 0.999982 239034 54613.33 + 75.135 0.999985 239035 65536.00 + 75.135 0.999986 239035 72817.78 + 75.199 0.999988 239037 81920.00 + 75.199 0.999989 239037 93622.86 + 75.199 0.999991 239037 109226.67 + 75.199 0.999992 239037 131072.00 + 75.199 0.999993 239037 145635.56 + 75.199 0.999994 239037 163840.00 + 75.199 0.999995 239037 187245.71 + 75.199 0.999995 239037 218453.33 + 75.327 0.999996 239038 262144.00 + 75.327 1.000000 239038 inf +#[Mean = 3.366, StdDeviation = 6.123] +#[Max = 75.264, Total count = 239038] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 359563 requests in 30.00s, 23.92MB read +Requests/sec: 11984.82 +Transfer/sec: 816.53KB diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/wrk/put_histogram.png b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/wrk/put_histogram.png new file mode 100644 index 000000000..4cbdd686e Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/wrk/put_histogram.png differ diff --git a/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/wrk/put_hw5.txt b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/wrk/put_hw5.txt new file mode 100644 index 000000000..2eba27be2 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/klimplyasov/reports_hw5/wrk/put_hw5.txt @@ -0,0 +1,121 @@ +Running 30s test @ http://localhost:8080 + 4 threads and 64 connections + Thread calibration: mean lat.: 967.433ms, rate sampling interval: 2715ms + Thread calibration: mean lat.: 970.451ms, rate sampling interval: 2717ms + Thread calibration: mean lat.: 965.740ms, rate sampling interval: 2711ms + Thread calibration: mean lat.: 971.021ms, rate sampling interval: 2717ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 9.96ms 30.28ms 269.82ms 95.10% + Req/Sec 3.03k 76.42 3.22k 85.71% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.46ms + 75.000% 3.87ms + 90.000% 12.25ms + 99.000% 177.41ms + 99.900% 238.08ms + 99.990% 266.24ms + 99.999% 269.31ms +100.000% 270.08ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.157 0.000000 1 1.00 + 1.242 0.100000 24161 1.11 + 1.585 0.200000 48322 1.25 + 1.872 0.300000 72486 1.43 + 2.149 0.400000 96581 1.67 + 2.463 0.500000 120778 2.00 + 2.635 0.550000 132826 2.22 + 2.837 0.600000 144867 2.50 + 3.081 0.650000 156999 2.86 + 3.389 0.700000 169012 3.33 + 3.871 0.750000 181052 4.00 + 4.215 0.775000 187088 4.44 + 4.671 0.800000 193154 5.00 + 5.315 0.825000 199149 5.71 + 6.295 0.850000 205195 6.67 + 7.959 0.875000 211213 8.00 + 9.879 0.887500 214231 8.89 + 12.247 0.900000 217250 10.00 + 14.415 0.912500 220266 11.43 + 16.639 0.925000 223298 13.33 + 20.655 0.937500 226300 16.00 + 23.983 0.943750 227813 17.78 + 32.575 0.950000 229317 20.00 + 69.823 0.956250 230829 22.86 + 93.439 0.962500 232339 26.67 + 112.447 0.968750 233845 32.00 + 120.191 0.971875 234604 35.56 + 127.743 0.975000 235352 40.00 + 136.319 0.978125 236114 45.71 + 146.559 0.981250 236868 53.33 + 156.927 0.984375 237628 64.00 + 161.407 0.985938 237996 71.11 + 167.039 0.987500 238375 80.00 + 172.927 0.989062 238750 91.43 + 180.351 0.990625 239131 106.67 + 187.263 0.992188 239509 128.00 + 190.591 0.992969 239693 142.22 + 193.919 0.993750 239885 160.00 + 197.247 0.994531 240067 182.86 + 201.087 0.995313 240255 213.33 + 205.951 0.996094 240445 256.00 + 208.767 0.996484 240541 284.44 + 211.967 0.996875 240633 320.00 + 215.039 0.997266 240728 365.71 + 220.031 0.997656 240821 426.67 + 224.895 0.998047 240916 512.00 + 227.071 0.998242 240962 568.89 + 229.759 0.998437 241012 640.00 + 232.191 0.998633 241057 731.43 + 234.623 0.998828 241107 853.33 + 238.719 0.999023 241152 1024.00 + 240.383 0.999121 241174 1137.78 + 243.199 0.999219 241198 1280.00 + 246.015 0.999316 241221 1462.86 + 248.959 0.999414 241245 1706.67 + 251.647 0.999512 241269 2048.00 + 253.055 0.999561 241281 2275.56 + 254.847 0.999609 241293 2560.00 + 255.999 0.999658 241304 2925.71 + 257.535 0.999707 241316 3413.33 + 260.095 0.999756 241328 4096.00 + 261.759 0.999780 241333 4551.11 + 263.167 0.999805 241339 5120.00 + 263.935 0.999829 241345 5851.43 + 264.959 0.999854 241351 6826.67 + 265.727 0.999878 241357 8192.00 + 265.983 0.999890 241361 9102.22 + 266.239 0.999902 241365 10240.00 + 266.495 0.999915 241368 11702.86 + 267.007 0.999927 241372 13653.33 + 267.007 0.999939 241372 16384.00 + 267.263 0.999945 241375 18204.44 + 267.263 0.999951 241375 20480.00 + 267.519 0.999957 241376 23405.71 + 268.287 0.999963 241378 27306.67 + 268.543 0.999969 241379 32768.00 + 268.799 0.999973 241381 36408.89 + 268.799 0.999976 241381 40960.00 + 268.799 0.999979 241381 46811.43 + 269.055 0.999982 241383 54613.33 + 269.055 0.999985 241383 65536.00 + 269.055 0.999986 241383 72817.78 + 269.311 0.999988 241384 81920.00 + 269.311 0.999989 241384 93622.86 + 269.311 0.999991 241384 109226.67 + 269.823 0.999992 241385 131072.00 + 269.823 0.999993 241385 145635.56 + 269.823 0.999994 241385 163840.00 + 269.823 0.999995 241385 187245.71 + 269.823 0.999995 241385 218453.33 + 270.079 0.999996 241386 262144.00 + 270.079 1.000000 241386 inf +#[Mean = 9.955, StdDeviation = 30.281] +#[Max = 269.824, Total count = 241386] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 359553 requests in 30.00s, 20.23MB read +Requests/sec: 11985.14 +Transfer/sec: 690.55KB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/MyFactory.java b/src/main/java/ru/vk/itmo/test/nikitaprokopev/MyFactory.java index 3a0ea827a..2176c52e4 100644 --- a/src/main/java/ru/vk/itmo/test/nikitaprokopev/MyFactory.java +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/MyFactory.java @@ -4,7 +4,7 @@ import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.test.ServiceFactory; -@ServiceFactory(stage = 4) +@ServiceFactory(stage = 5) public class MyFactory implements ServiceFactory.Factory { @Override public Service create(ServiceConfig config) { diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/MyServer.java b/src/main/java/ru/vk/itmo/test/nikitaprokopev/MyServer.java index a8ac672a4..359694c49 100644 --- a/src/main/java/ru/vk/itmo/test/nikitaprokopev/MyServer.java +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/MyServer.java @@ -12,6 +12,7 @@ import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.test.nikitaprokopev.dao.Dao; import ru.vk.itmo.test.nikitaprokopev.dao.Entry; +import ru.vk.itmo.test.nikitaprokopev.exceptions.NotEnoughReplicasException; import java.io.IOException; import java.lang.foreign.MemorySegment; @@ -22,9 +23,12 @@ import java.util.Collection; import java.util.List; import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; public class MyServer extends HttpServer { public static final String HEADER_TIMESTAMP = "X-Timestamp: "; @@ -123,21 +127,26 @@ private void executeRequests(Request request, HttpSession session, long createdA Collection targetNodes = getNodesSortedByRendezvousHashing(key, serviceConfig, from); Collection httpRequests = requestHandler.createRequests(request, key, targetNodes, serviceConfig.selfUrl()); - List responses = getResponses(request, ack, httpRequests); - if (responses.size() < ack) { - sendResponseWithEmptyBody(session, CustomResponseCodes.RESPONSE_NOT_ENOUGH_REPLICAS.getCode()); - return; - } - try { - if (request.getMethod() != Request.METHOD_GET) { - session.sendResponse(responses.getFirst()); - return; - } + CompletableFuture> responses = getResponses(request, ack, httpRequests); + responses = responses.whenComplete( + (list, throwable) -> { + if (throwable != null || list == null || list.size() < ack) { + sendResponseWithEmptyBody(session, CustomResponseCodes.RESPONSE_NOT_ENOUGH_REPLICAS.getCode()); + return; + } + try { + if (request.getMethod() != Request.METHOD_GET) { + session.sendResponse(list.getFirst()); + return; + } - mergeGetResponses(session, responses); - } catch (IOException e) { - logIOExceptionAndCloseSession(session, e); - } + mergeGetResponses(session, list); + } catch (IOException e) { + logIOExceptionAndCloseSession(session, e); + } + } + ); + checkFuture(responses); } private void mergeGetResponses(HttpSession session, List responses) throws IOException { @@ -159,33 +168,54 @@ private void mergeGetResponses(HttpSession session, List responses) th sendResponseWithEmptyBody(session, Response.NOT_FOUND); } - private List getResponses( + private CompletableFuture> getResponses( Request request, int ack, Collection requests ) { - List responses = new ArrayList<>(requests.size()); - // stop if too many errors - int acceptableErrors = requests.size() - ack + 1; + final CompletableFuture> result = new CompletableFuture<>(); + List responses = new CopyOnWriteArrayList<>(); + AtomicInteger successCounter = new AtomicInteger(ack); + AtomicInteger leftErrorsToFail = new AtomicInteger(requests.size() - ack + 1); for (HttpRequest httpRequest : requests) { - Response response; + CompletableFuture cfResponse; if (httpRequest.headers().map().containsKey(LOCAL_REQUEST)) { request.addHeader( HEADER_TIMESTAMP + httpRequest.headers().map().get(HEADER_TIMESTAMP_LOWER_CASE).getFirst()); - response = handleInternalRequest(request); + cfResponse = getInternalResponse(request); } else { - response = proxyRequest(httpRequest); + cfResponse = proxyRequest(httpRequest); } - if (response == null) { - --acceptableErrors; - if (acceptableErrors == 0) { - break; - } - continue; - } - responses.add(response); + cfResponse = cfResponse.whenComplete( + (response, throwable) -> { + if (response == null) { + if (leftErrorsToFail.decrementAndGet() == 0) { + result.completeExceptionally(new NotEnoughReplicasException()); + } + return; + } + responses.add(response); + if (successCounter.decrementAndGet() == 0) { + result.complete(responses); + } + } + ); + checkFuture(cfResponse); } - return responses; + return result; + } + + private CompletableFuture getInternalResponse(Request request) { + final CompletableFuture cfResponse = new CompletableFuture<>(); + workerPool.execute( + () -> { + try { + cfResponse.complete(handleInternalRequest(request)); + } catch (IllegalArgumentException e) { + cfResponse.completeExceptionally(e); + } + }); + return cfResponse; } private Response handleInternalRequest(Request request) { @@ -203,17 +233,28 @@ private Response handleInternalRequest(Request request) { return response; } - private Response proxyRequest(HttpRequest httpRequest) { - try { - HttpResponse response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray()); - return proxyResponse(response, response.body()); - } catch (InterruptedException e) { - log.error("Interrupted while sending request", e); - Thread.currentThread().interrupt(); - return null; - } catch (Exception e) { - log.info("Exception while sending request to another node", e); - return null; + private CompletableFuture proxyRequest(HttpRequest httpRequest) { + final CompletableFuture cfResponse = new CompletableFuture<>(); + CompletableFuture> response = + httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()) + .whenComplete( + (httpResponse, throwable) -> { + if (throwable != null) { + cfResponse.completeExceptionally(throwable); + return; + } + cfResponse.complete( + proxyResponse(httpResponse) + ); + } + ); + checkFuture(response); + return cfResponse; + } + + private void checkFuture(CompletableFuture cf) { + if (cf == null) { + log.error("Error while working with CompletableFuture"); } } @@ -230,7 +271,7 @@ private void logIOExceptionAndCloseSession(HttpSession session, IOException e) { session.scheduleClose(); } - private Response proxyResponse(HttpResponse response, byte[] body) { + private Response proxyResponse(HttpResponse response) { String responseCode = switch (response.statusCode()) { case 200 -> Response.OK; case 201 -> Response.CREATED; @@ -240,7 +281,7 @@ private Response proxyResponse(HttpResponse response, byte[] body) { default -> Response.INTERNAL_ERROR; }; - Response responseProxied = new Response(responseCode, body); + Response responseProxied = new Response(responseCode, response.body()); long timestamp = response.headers().map().containsKey(HEADER_TIMESTAMP_LOWER_CASE) ? Long.parseLong(response.headers().map().get(HEADER_TIMESTAMP_LOWER_CASE).getFirst()) : -1; diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-get-2800rps(3-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-get-2800rps(3-3).txt new file mode 100644 index 000000000..a55c1a5a2 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-get-2800rps(3-3).txt @@ -0,0 +1,102 @@ +../wrk2/wrk -c 128 -d 10 -t 16 -R 2800 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/scripts/get.lua http://localhost:8080 +Running 10s test @ http://localhost:8080 + 16 threads and 128 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 35.08ms 54.21ms 195.07ms 82.23% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 5.20ms + 75.000% 41.22ms + 90.000% 135.55ms + 99.000% 182.14ms + 99.900% 190.21ms + 99.990% 193.28ms + 99.999% 195.20ms +100.000% 195.20ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 1.013 0.000000 1 1.00 + 2.207 0.100000 2700 1.11 + 2.711 0.200000 5380 1.25 + 3.265 0.300000 8072 1.43 + 3.991 0.400000 10765 1.67 + 5.195 0.500000 13451 2.00 + 6.239 0.550000 14797 2.22 + 8.271 0.600000 16138 2.50 + 13.663 0.650000 17484 2.86 + 19.759 0.700000 18827 3.33 + 41.215 0.750000 20173 4.00 + 51.743 0.775000 20845 4.44 + 77.375 0.800000 21517 5.00 + 90.623 0.825000 22189 5.71 + 103.999 0.850000 22862 6.67 + 122.559 0.875000 23536 8.00 + 128.703 0.887500 23873 8.89 + 135.551 0.900000 24208 10.00 + 143.999 0.912500 24547 11.43 + 154.751 0.925000 24882 13.33 + 164.991 0.937500 25217 16.00 + 168.063 0.943750 25389 17.78 + 170.495 0.950000 25567 20.00 + 172.031 0.956250 25731 22.86 + 173.439 0.962500 25888 26.67 + 174.975 0.968750 26057 32.00 + 175.743 0.971875 26150 35.56 + 176.383 0.975000 26223 40.00 + 177.407 0.978125 26309 45.71 + 178.559 0.981250 26393 53.33 + 179.711 0.984375 26481 64.00 + 180.351 0.985938 26524 71.11 + 180.863 0.987500 26561 80.00 + 181.631 0.989062 26602 91.43 + 182.399 0.990625 26644 106.67 + 183.295 0.992188 26686 128.00 + 183.935 0.992969 26706 142.22 + 184.447 0.993750 26734 160.00 + 184.959 0.994531 26756 182.86 + 185.343 0.995313 26773 213.33 + 185.727 0.996094 26793 256.00 + 185.983 0.996484 26804 284.44 + 186.367 0.996875 26813 320.00 + 186.879 0.997266 26822 365.71 + 187.775 0.997656 26833 426.67 + 188.671 0.998047 26847 512.00 + 188.799 0.998242 26852 568.89 + 188.927 0.998437 26854 640.00 + 189.311 0.998633 26860 731.43 + 189.695 0.998828 26864 853.33 + 190.207 0.999023 26869 1024.00 + 190.719 0.999121 26872 1137.78 + 190.975 0.999219 26874 1280.00 + 191.487 0.999316 26878 1462.86 + 191.615 0.999414 26880 1706.67 + 191.743 0.999512 26882 2048.00 + 191.871 0.999561 26884 2275.56 + 191.999 0.999609 26885 2560.00 + 192.127 0.999658 26886 2925.71 + 192.383 0.999707 26888 3413.33 + 193.023 0.999756 26889 4096.00 + 193.151 0.999780 26890 4551.11 + 193.151 0.999805 26890 5120.00 + 193.279 0.999829 26893 5851.43 + 193.279 0.999854 26893 6826.67 + 193.279 0.999878 26893 8192.00 + 193.279 0.999890 26893 9102.22 + 193.279 0.999902 26893 10240.00 + 193.279 0.999915 26893 11702.86 + 194.431 0.999927 26894 13653.33 + 194.431 0.999939 26894 16384.00 + 194.431 0.999945 26894 18204.44 + 194.431 0.999951 26894 20480.00 + 194.431 0.999957 26894 23405.71 + 195.199 0.999963 26895 27306.67 + 195.199 1.000000 26895 inf +#[Mean = 35.084, StdDeviation = 54.208] +#[Max = 195.072, Total count = 26895] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 26911 requests in 10.00s, 4.36MB read +Requests/sec: 2691.30 +Transfer/sec: 446.80KB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-get-4500rps(2-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-get-4500rps(2-3).txt new file mode 100644 index 000000000..39d1b2867 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-get-4500rps(2-3).txt @@ -0,0 +1,105 @@ +../wrk2/wrk -c 128 -d 10 -t 16 -R 4500 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/scripts/get.lua http://localhost:8080 +Running 10s test @ http://localhost:8080 + 16 threads and 128 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 520.39ms 700.18ms 1.79s 75.04% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 31.26ms + 75.000% 1.21s + 90.000% 1.70s + 99.000% 1.77s + 99.900% 1.78s + 99.990% 1.78s + 99.999% 1.79s +100.000% 1.79s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.488 0.000000 1 1.00 + 1.452 0.100000 3753 1.11 + 1.860 0.200000 7503 1.25 + 2.373 0.300000 11251 1.43 + 4.143 0.400000 14998 1.67 + 31.263 0.500000 18748 2.00 + 47.487 0.550000 20626 2.22 + 125.503 0.600000 22498 2.50 + 501.759 0.650000 24372 2.86 + 908.799 0.700000 26250 3.33 + 1209.343 0.750000 28122 4.00 + 1312.767 0.775000 29083 4.44 + 1619.967 0.800000 30000 5.00 + 1655.807 0.825000 30950 5.71 + 1668.095 0.850000 31909 6.67 + 1683.455 0.875000 32841 8.00 + 1693.695 0.887500 33297 8.89 + 1704.959 0.900000 33782 10.00 + 1716.223 0.912500 34227 11.43 + 1727.487 0.925000 34682 13.33 + 1737.727 0.937500 35185 16.00 + 1741.823 0.943750 35401 17.78 + 1745.919 0.950000 35632 20.00 + 1750.015 0.956250 35907 22.86 + 1753.087 0.962500 36178 26.67 + 1755.135 0.968750 36342 32.00 + 1757.183 0.971875 36520 35.56 + 1758.207 0.975000 36615 40.00 + 1759.231 0.978125 36707 45.71 + 1761.279 0.981250 36852 53.33 + 1762.303 0.984375 36915 64.00 + 1763.327 0.985938 36984 71.11 + 1764.351 0.987500 37050 80.00 + 1765.375 0.989062 37102 91.43 + 1766.399 0.990625 37159 106.67 + 1767.423 0.992188 37212 128.00 + 1768.447 0.992969 37263 142.22 + 1768.447 0.993750 37263 160.00 + 1769.471 0.994531 37291 182.86 + 1770.495 0.995313 37331 213.33 + 1771.519 0.996094 37354 256.00 + 1772.543 0.996484 37385 284.44 + 1772.543 0.996875 37385 320.00 + 1773.567 0.997266 37418 365.71 + 1773.567 0.997656 37418 426.67 + 1774.591 0.998047 37442 512.00 + 1774.591 0.998242 37442 568.89 + 1774.591 0.998437 37442 640.00 + 1775.615 0.998633 37452 731.43 + 1775.615 0.998828 37452 853.33 + 1776.639 0.999023 37463 1024.00 + 1776.639 0.999121 37463 1137.78 + 1777.663 0.999219 37472 1280.00 + 1777.663 0.999316 37472 1462.86 + 1778.687 0.999414 37477 1706.67 + 1778.687 0.999512 37477 2048.00 + 1779.711 0.999561 37482 2275.56 + 1779.711 0.999609 37482 2560.00 + 1779.711 0.999658 37482 2925.71 + 1780.735 0.999707 37485 3413.33 + 1780.735 0.999756 37485 4096.00 + 1781.759 0.999780 37490 4551.11 + 1781.759 0.999805 37490 5120.00 + 1781.759 0.999829 37490 5851.43 + 1781.759 0.999854 37490 6826.67 + 1781.759 0.999878 37490 8192.00 + 1781.759 0.999890 37490 9102.22 + 1782.783 0.999902 37493 10240.00 + 1782.783 0.999915 37493 11702.86 + 1782.783 0.999927 37493 13653.33 + 1782.783 0.999939 37493 16384.00 + 1782.783 0.999945 37493 18204.44 + 1782.783 0.999951 37493 20480.00 + 1782.783 0.999957 37493 23405.71 + 1782.783 0.999963 37493 27306.67 + 1782.783 0.999969 37493 32768.00 + 1782.783 0.999973 37493 36408.89 + 1786.879 0.999976 37494 40960.00 + 1786.879 1.000000 37494 inf +#[Mean = 520.388, StdDeviation = 700.180] +#[Max = 1785.856, Total count = 37494] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 37510 requests in 9.98s, 6.08MB read +Requests/sec: 3758.03 +Transfer/sec: 623.89KB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-get-7000rps(1-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-get-7000rps(1-3).txt new file mode 100644 index 000000000..96202af98 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-get-7000rps(1-3).txt @@ -0,0 +1,109 @@ +../wrk2/wrk -c 128 -d 10 -t 16 -R 7000 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/scripts/get.lua http://localhost:8080 +Running 10s test @ http://localhost:8080 + 16 threads and 128 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 127.49ms 158.41ms 495.62ms 75.98% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 32.99ms + 75.000% 270.08ms + 90.000% 399.10ms + 99.000% 462.85ms + 99.900% 480.26ms + 99.990% 490.49ms + 99.999% 494.08ms +100.000% 495.87ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.160 0.000000 1 1.00 + 1.391 0.100000 6972 1.11 + 2.125 0.200000 13945 1.25 + 3.431 0.300000 20916 1.43 + 9.031 0.400000 27887 1.67 + 32.991 0.500000 34859 2.00 + 50.559 0.550000 38341 2.22 + 87.807 0.600000 41834 2.50 + 133.119 0.650000 45315 2.86 + 179.071 0.700000 48797 3.33 + 270.079 0.750000 52287 4.00 + 304.383 0.775000 54029 4.44 + 323.839 0.800000 55775 5.00 + 346.111 0.825000 57529 5.71 + 365.311 0.850000 59282 6.67 + 379.135 0.875000 61023 8.00 + 388.351 0.887500 61881 8.89 + 399.103 0.900000 62754 10.00 + 408.831 0.912500 63618 11.43 + 417.535 0.925000 64498 13.33 + 425.727 0.937500 65372 16.00 + 429.823 0.943750 65799 17.78 + 433.663 0.950000 66249 20.00 + 437.503 0.956250 66684 22.86 + 441.599 0.962500 67124 26.67 + 445.951 0.968750 67546 32.00 + 448.255 0.971875 67758 35.56 + 450.559 0.975000 67988 40.00 + 452.863 0.978125 68194 45.71 + 455.167 0.981250 68420 53.33 + 457.215 0.984375 68624 64.00 + 458.495 0.985938 68731 71.11 + 460.031 0.987500 68843 80.00 + 461.567 0.989062 68953 91.43 + 463.615 0.990625 69074 106.67 + 465.663 0.992188 69177 128.00 + 466.943 0.992969 69233 142.22 + 467.967 0.993750 69276 160.00 + 469.247 0.994531 69330 182.86 + 471.039 0.995313 69395 213.33 + 472.319 0.996094 69441 256.00 + 473.087 0.996484 69473 284.44 + 474.111 0.996875 69501 320.00 + 474.879 0.997266 69521 365.71 + 475.903 0.997656 69556 426.67 + 476.671 0.998047 69575 512.00 + 477.439 0.998242 69589 568.89 + 478.207 0.998437 69607 640.00 + 478.463 0.998633 69615 731.43 + 479.487 0.998828 69633 853.33 + 480.511 0.999023 69644 1024.00 + 481.023 0.999121 69656 1137.78 + 481.023 0.999219 69656 1280.00 + 482.047 0.999316 69664 1462.86 + 482.559 0.999414 69670 1706.67 + 483.583 0.999512 69679 2048.00 + 483.839 0.999561 69683 2275.56 + 483.839 0.999609 69683 2560.00 + 485.375 0.999658 69688 2925.71 + 486.655 0.999707 69690 3413.33 + 487.167 0.999756 69693 4096.00 + 487.423 0.999780 69695 4551.11 + 487.679 0.999805 69697 5120.00 + 488.447 0.999829 69699 5851.43 + 488.703 0.999854 69700 6826.67 + 490.239 0.999878 69702 8192.00 + 490.495 0.999890 69703 9102.22 + 491.519 0.999902 69705 10240.00 + 491.519 0.999915 69705 11702.86 + 491.519 0.999927 69705 13653.33 + 492.031 0.999939 69706 16384.00 + 492.543 0.999945 69707 18204.44 + 492.543 0.999951 69707 20480.00 + 493.055 0.999957 69708 23405.71 + 493.055 0.999963 69708 27306.67 + 493.055 0.999969 69708 32768.00 + 494.079 0.999973 69709 36408.89 + 494.079 0.999976 69709 40960.00 + 494.079 0.999979 69709 46811.43 + 494.079 0.999982 69709 54613.33 + 494.079 0.999985 69709 65536.00 + 495.871 0.999986 69710 72817.78 + 495.871 1.000000 69710 inf +#[Mean = 127.487, StdDeviation = 158.411] +#[Max = 495.616, Total count = 69710] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 69726 requests in 9.95s, 11.30MB read +Requests/sec: 7008.70 +Transfer/sec: 1.14MB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-put-2000rps(3-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-put-2000rps(3-3).txt new file mode 100644 index 000000000..3909ab745 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-put-2000rps(3-3).txt @@ -0,0 +1,100 @@ +../wrk2/wrk -c 128 -d 10 -t 16 -R 2000 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/scripts/put.lua http://localhost:8080 +Running 10s test @ http://localhost:8080 + 16 threads and 128 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 10.29ms 22.51ms 187.90ms 93.79% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 3.77ms + 75.000% 6.27ms + 90.000% 17.28ms + 99.000% 135.68ms + 99.900% 162.18ms + 99.990% 180.10ms + 99.999% 188.03ms +100.000% 188.03ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 1.057 0.000000 1 1.00 + 2.377 0.100000 2007 1.11 + 2.725 0.200000 4006 1.25 + 3.041 0.300000 6006 1.43 + 3.353 0.400000 8000 1.67 + 3.765 0.500000 9998 2.00 + 4.019 0.550000 11002 2.22 + 4.331 0.600000 11997 2.50 + 4.743 0.650000 13003 2.86 + 5.331 0.700000 13998 3.33 + 6.267 0.750000 14995 4.00 + 6.975 0.775000 15496 4.44 + 7.935 0.800000 15994 5.00 + 9.367 0.825000 16497 5.71 + 11.199 0.850000 16994 6.67 + 13.399 0.875000 17493 8.00 + 15.167 0.887500 17743 8.89 + 17.279 0.900000 17993 10.00 + 20.191 0.912500 18242 11.43 + 24.527 0.925000 18492 13.33 + 32.511 0.937500 18743 16.00 + 37.663 0.943750 18868 17.78 + 42.047 0.950000 18992 20.00 + 46.367 0.956250 19118 22.86 + 53.343 0.962500 19242 26.67 + 79.167 0.968750 19367 32.00 + 86.655 0.971875 19429 35.56 + 103.615 0.975000 19493 40.00 + 109.375 0.978125 19554 45.71 + 119.295 0.981250 19618 53.33 + 128.319 0.984375 19679 64.00 + 130.367 0.985938 19710 71.11 + 132.095 0.987500 19743 80.00 + 134.527 0.989062 19775 91.43 + 136.575 0.990625 19804 106.67 + 139.007 0.992188 19836 128.00 + 141.055 0.992969 19854 142.22 + 142.847 0.993750 19868 160.00 + 145.279 0.994531 19882 182.86 + 146.559 0.995313 19899 213.33 + 147.839 0.996094 19913 256.00 + 149.503 0.996484 19921 284.44 + 151.167 0.996875 19929 320.00 + 152.575 0.997266 19937 365.71 + 155.007 0.997656 19945 426.67 + 156.287 0.998047 19952 512.00 + 156.799 0.998242 19956 568.89 + 158.335 0.998437 19960 640.00 + 159.359 0.998633 19964 731.43 + 161.535 0.998828 19968 853.33 + 162.431 0.999023 19972 1024.00 + 162.687 0.999121 19974 1137.78 + 164.095 0.999219 19976 1280.00 + 164.735 0.999316 19978 1462.86 + 166.783 0.999414 19980 1706.67 + 167.167 0.999512 19982 2048.00 + 168.319 0.999561 19983 2275.56 + 168.831 0.999609 19984 2560.00 + 170.111 0.999658 19985 2925.71 + 170.495 0.999707 19986 3413.33 + 172.799 0.999756 19987 4096.00 + 172.799 0.999780 19987 4551.11 + 174.591 0.999805 19988 5120.00 + 174.591 0.999829 19988 5851.43 + 180.095 0.999854 19989 6826.67 + 180.095 0.999878 19989 8192.00 + 180.095 0.999890 19989 9102.22 + 186.495 0.999902 19990 10240.00 + 186.495 0.999915 19990 11702.86 + 186.495 0.999927 19990 13653.33 + 186.495 0.999939 19990 16384.00 + 186.495 0.999945 19990 18204.44 + 188.031 0.999951 19991 20480.00 + 188.031 1.000000 19991 inf +#[Mean = 10.293, StdDeviation = 22.514] +#[Max = 187.904, Total count = 19991] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 20007 requests in 9.99s, 1.28MB read +Requests/sec: 2003.01 +Transfer/sec: 131.06KB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-put-2200rps(2-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-put-2200rps(2-3).txt new file mode 100644 index 000000000..847900caa --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-put-2200rps(2-3).txt @@ -0,0 +1,129 @@ +../wrk2/wrk -c 128 -d 60 -t 16 -R 2200 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/scripts/put.lua http://localhost:8080 +Running 1m test @ http://localhost:8080 + 16 threads and 128 connections + Thread calibration: mean lat.: 23.719ms, rate sampling interval: 141ms + Thread calibration: mean lat.: 24.537ms, rate sampling interval: 144ms + Thread calibration: mean lat.: 23.729ms, rate sampling interval: 138ms + Thread calibration: mean lat.: 24.357ms, rate sampling interval: 141ms + Thread calibration: mean lat.: 25.145ms, rate sampling interval: 150ms + Thread calibration: mean lat.: 24.427ms, rate sampling interval: 144ms + Thread calibration: mean lat.: 24.397ms, rate sampling interval: 142ms + Thread calibration: mean lat.: 24.720ms, rate sampling interval: 144ms + Thread calibration: mean lat.: 24.633ms, rate sampling interval: 141ms + Thread calibration: mean lat.: 25.377ms, rate sampling interval: 147ms + Thread calibration: mean lat.: 24.812ms, rate sampling interval: 142ms + Thread calibration: mean lat.: 25.014ms, rate sampling interval: 143ms + Thread calibration: mean lat.: 25.300ms, rate sampling interval: 144ms + Thread calibration: mean lat.: 26.083ms, rate sampling interval: 152ms + Thread calibration: mean lat.: 25.225ms, rate sampling interval: 143ms + Thread calibration: mean lat.: 25.526ms, rate sampling interval: 142ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 10.86ms 23.80ms 255.10ms 92.82% + Req/Sec 137.13 18.17 268.00 90.24% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 3.77ms + 75.000% 6.94ms + 90.000% 18.86ms + 99.000% 129.98ms + 99.900% 210.69ms + 99.990% 229.38ms + 99.999% 237.82ms +100.000% 255.23ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.706 0.000000 1 1.00 + 1.931 0.100000 10969 1.11 + 2.341 0.200000 21980 1.25 + 2.727 0.300000 32904 1.43 + 3.177 0.400000 43890 1.67 + 3.765 0.500000 54847 2.00 + 4.139 0.550000 60334 2.22 + 4.599 0.600000 65810 2.50 + 5.175 0.650000 71303 2.86 + 5.931 0.700000 76791 3.33 + 6.939 0.750000 82274 4.00 + 7.611 0.775000 85009 4.44 + 8.455 0.800000 87756 5.00 + 9.543 0.825000 90489 5.71 + 11.159 0.850000 93230 6.67 + 13.895 0.875000 95970 8.00 + 16.047 0.887500 97339 8.89 + 18.863 0.900000 98711 10.00 + 23.983 0.912500 100082 11.43 + 31.823 0.925000 101453 13.33 + 44.479 0.937500 102822 16.00 + 51.487 0.943750 103509 17.78 + 57.535 0.950000 104197 20.00 + 62.911 0.956250 104878 22.86 + 68.799 0.962500 105574 26.67 + 75.839 0.968750 106250 32.00 + 81.343 0.971875 106593 35.56 + 86.463 0.975000 106938 40.00 + 95.167 0.978125 107279 45.71 + 105.727 0.981250 107622 53.33 + 113.151 0.984375 107964 64.00 + 117.759 0.985938 108135 71.11 + 122.687 0.987500 108306 80.00 + 127.743 0.989062 108478 91.43 + 131.711 0.990625 108649 106.67 + 137.727 0.992188 108823 128.00 + 140.927 0.992969 108905 142.22 + 147.455 0.993750 108991 160.00 + 158.463 0.994531 109077 182.86 + 166.527 0.995313 109162 213.33 + 176.767 0.996094 109248 256.00 + 181.887 0.996484 109291 284.44 + 186.367 0.996875 109334 320.00 + 189.439 0.997266 109377 365.71 + 192.767 0.997656 109420 426.67 + 199.167 0.998047 109462 512.00 + 202.111 0.998242 109485 568.89 + 204.287 0.998437 109505 640.00 + 207.359 0.998633 109527 731.43 + 209.023 0.998828 109551 853.33 + 211.199 0.999023 109569 1024.00 + 212.735 0.999121 109580 1137.78 + 213.887 0.999219 109591 1280.00 + 214.911 0.999316 109602 1462.86 + 216.959 0.999414 109612 1706.67 + 219.007 0.999512 109624 2048.00 + 219.775 0.999561 109628 2275.56 + 220.927 0.999609 109634 2560.00 + 221.823 0.999658 109639 2925.71 + 222.975 0.999707 109644 3413.33 + 224.511 0.999756 109650 4096.00 + 224.895 0.999780 109652 4551.11 + 225.151 0.999805 109656 5120.00 + 226.559 0.999829 109658 5851.43 + 226.687 0.999854 109660 6826.67 + 228.095 0.999878 109663 8192.00 + 228.223 0.999890 109664 9102.22 + 229.759 0.999902 109666 10240.00 + 230.911 0.999915 109667 11702.86 + 232.191 0.999927 109668 13653.33 + 232.831 0.999939 109670 16384.00 + 232.831 0.999945 109670 18204.44 + 233.343 0.999951 109671 20480.00 + 233.855 0.999957 109672 23405.71 + 233.855 0.999963 109672 27306.67 + 233.983 0.999969 109673 32768.00 + 233.983 0.999973 109673 36408.89 + 237.439 0.999976 109674 40960.00 + 237.439 0.999979 109674 46811.43 + 237.439 0.999982 109674 54613.33 + 237.823 0.999985 109675 65536.00 + 237.823 0.999986 109675 72817.78 + 237.823 0.999988 109675 81920.00 + 237.823 0.999989 109675 93622.86 + 237.823 0.999991 109675 109226.67 + 255.231 0.999992 109676 131072.00 + 255.231 1.000000 109676 inf +#[Mean = 10.863, StdDeviation = 23.800] +#[Max = 255.104, Total count = 109676] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 131803 requests in 1.00m, 8.42MB read +Requests/sec: 2196.19 +Transfer/sec: 143.70KB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-put-2500rps(2-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-put-2500rps(2-3).txt new file mode 100644 index 000000000..7e3f2a574 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-put-2500rps(2-3).txt @@ -0,0 +1,124 @@ +../wrk2/wrk -c 128 -d 60 -t 16 -R 2500 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/scripts/put.lua http://localhost:8080 +Running 1m test @ http://localhost:8080 + 16 threads and 128 connections + Thread calibration: mean lat.: 562.915ms, rate sampling interval: 1797ms + Thread calibration: mean lat.: 547.685ms, rate sampling interval: 1767ms + Thread calibration: mean lat.: 557.194ms, rate sampling interval: 1778ms + Thread calibration: mean lat.: 559.464ms, rate sampling interval: 1795ms + Thread calibration: mean lat.: 559.035ms, rate sampling interval: 1780ms + Thread calibration: mean lat.: 549.590ms, rate sampling interval: 1763ms + Thread calibration: mean lat.: 550.055ms, rate sampling interval: 1766ms + Thread calibration: mean lat.: 554.145ms, rate sampling interval: 1768ms + Thread calibration: mean lat.: 552.023ms, rate sampling interval: 1775ms + Thread calibration: mean lat.: 550.097ms, rate sampling interval: 1750ms + Thread calibration: mean lat.: 552.890ms, rate sampling interval: 1768ms + Thread calibration: mean lat.: 554.257ms, rate sampling interval: 1766ms + Thread calibration: mean lat.: 554.774ms, rate sampling interval: 1776ms + Thread calibration: mean lat.: 551.512ms, rate sampling interval: 1770ms + Thread calibration: mean lat.: 560.022ms, rate sampling interval: 1796ms + Thread calibration: mean lat.: 562.823ms, rate sampling interval: 1800ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 172.47ms 309.85ms 1.19s 86.36% + Req/Sec 158.77 11.25 198.00 83.56% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 9.91ms + 75.000% 166.78ms + 90.000% 826.37ms + 99.000% 1.09s + 99.900% 1.13s + 99.990% 1.17s + 99.999% 1.19s +100.000% 1.19s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.853 0.000000 1 1.00 + 2.275 0.100000 12728 1.11 + 2.965 0.200000 25458 1.25 + 3.833 0.300000 38195 1.43 + 5.307 0.400000 50913 1.67 + 9.911 0.500000 63620 2.00 + 18.031 0.550000 69981 2.22 + 36.639 0.600000 76346 2.50 + 70.207 0.650000 82703 2.86 + 114.047 0.700000 89066 3.33 + 166.783 0.750000 95430 4.00 + 189.695 0.775000 98608 4.44 + 225.279 0.800000 101797 5.00 + 302.335 0.825000 104969 5.71 + 409.343 0.850000 108153 6.67 + 695.807 0.875000 111334 8.00 + 778.239 0.887500 112928 8.89 + 826.367 0.900000 114527 10.00 + 859.135 0.912500 116116 11.43 + 901.631 0.925000 117701 13.33 + 943.615 0.937500 119291 16.00 + 964.607 0.943750 120092 17.78 + 981.503 0.950000 120895 20.00 + 997.375 0.956250 121681 22.86 + 1014.271 0.962500 122482 26.67 + 1030.655 0.968750 123260 32.00 + 1039.871 0.971875 123674 35.56 + 1047.039 0.975000 124057 40.00 + 1055.743 0.978125 124457 45.71 + 1063.935 0.981250 124863 53.33 + 1073.151 0.984375 125258 64.00 + 1078.271 0.985938 125466 71.11 + 1082.367 0.987500 125654 80.00 + 1087.487 0.989062 125875 91.43 + 1092.607 0.990625 126069 106.67 + 1097.727 0.992188 126260 128.00 + 1100.799 0.992969 126380 142.22 + 1102.847 0.993750 126454 160.00 + 1105.919 0.994531 126547 182.86 + 1110.015 0.995313 126671 213.33 + 1113.087 0.996094 126754 256.00 + 1115.135 0.996484 126806 284.44 + 1117.183 0.996875 126851 320.00 + 1119.231 0.997266 126901 365.71 + 1122.303 0.997656 126953 426.67 + 1124.351 0.998047 126993 512.00 + 1125.375 0.998242 127018 568.89 + 1127.423 0.998437 127047 640.00 + 1129.471 0.998633 127069 731.43 + 1131.519 0.998828 127094 853.33 + 1133.567 0.999023 127114 1024.00 + 1135.615 0.999121 127127 1137.78 + 1137.663 0.999219 127142 1280.00 + 1138.687 0.999316 127154 1462.86 + 1140.735 0.999414 127161 1706.67 + 1143.807 0.999512 127175 2048.00 + 1144.831 0.999561 127183 2275.56 + 1145.855 0.999609 127187 2560.00 + 1148.927 0.999658 127194 2925.71 + 1149.951 0.999707 127199 3413.33 + 1151.999 0.999756 127204 4096.00 + 1155.071 0.999780 127208 4551.11 + 1157.119 0.999805 127212 5120.00 + 1158.143 0.999829 127214 5851.43 + 1160.191 0.999854 127218 6826.67 + 1161.215 0.999878 127220 8192.00 + 1165.311 0.999890 127222 9102.22 + 1167.359 0.999902 127223 10240.00 + 1170.431 0.999915 127225 11702.86 + 1172.479 0.999927 127226 13653.33 + 1175.551 0.999939 127228 16384.00 + 1176.575 0.999945 127229 18204.44 + 1176.575 0.999951 127229 20480.00 + 1177.599 0.999957 127230 23405.71 + 1178.623 0.999963 127231 27306.67 + 1180.671 0.999969 127232 32768.00 + 1180.671 0.999973 127232 36408.89 + 1180.671 0.999976 127232 40960.00 + 1184.767 0.999979 127233 46811.43 + 1184.767 0.999982 127233 54613.33 + 1193.983 0.999985 127235 65536.00 + 1193.983 1.000000 127235 inf +#[Mean = 172.474, StdDeviation = 309.850] +#[Max = 1192.960, Total count = 127235] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 150016 requests in 1.00m, 9.59MB read +Requests/sec: 2500.44 +Transfer/sec: 163.60KB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-put-6000rps(1-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-put-6000rps(1-3).txt new file mode 100644 index 000000000..0f02589ba --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/results/wrk-put-6000rps(1-3).txt @@ -0,0 +1,108 @@ +../wrk2/wrk -c 128 -d 10 -t 16 -R 6000 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage4/scripts/put.lua http://localhost:8080 +Running 10s test @ http://localhost:8080 + 16 threads and 128 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 47.65ms 93.19ms 396.54ms 87.33% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 7.15ms + 75.000% 27.79ms + 90.000% 199.17ms + 99.000% 368.38ms + 99.900% 385.02ms + 99.990% 393.21ms + 99.999% 394.75ms +100.000% 396.80ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.213 0.000000 1 1.00 + 1.555 0.100000 5733 1.11 + 2.257 0.200000 11457 1.25 + 3.075 0.300000 17179 1.43 + 4.359 0.400000 22911 1.67 + 7.147 0.500000 28634 2.00 + 10.359 0.550000 31500 2.22 + 14.135 0.600000 34360 2.50 + 17.759 0.650000 37221 2.86 + 22.127 0.700000 40092 3.33 + 27.807 0.750000 42952 4.00 + 31.535 0.775000 44379 4.44 + 37.887 0.800000 45812 5.00 + 67.135 0.825000 47246 5.71 + 79.423 0.850000 48681 6.67 + 145.919 0.875000 50106 8.00 + 176.383 0.887500 50831 8.89 + 199.167 0.900000 51537 10.00 + 248.063 0.912500 52255 11.43 + 263.423 0.925000 52978 13.33 + 280.831 0.937500 53690 16.00 + 297.983 0.943750 54042 17.78 + 310.015 0.950000 54409 20.00 + 321.535 0.956250 54762 22.86 + 334.335 0.962500 55122 26.67 + 344.063 0.968750 55481 32.00 + 347.647 0.971875 55656 35.56 + 351.231 0.975000 55835 40.00 + 354.559 0.978125 56012 45.71 + 358.399 0.981250 56197 53.33 + 361.983 0.984375 56374 64.00 + 363.519 0.985938 56459 71.11 + 365.567 0.987500 56559 80.00 + 367.359 0.989062 56641 91.43 + 369.151 0.990625 56741 106.67 + 371.199 0.992188 56822 128.00 + 372.223 0.992969 56861 142.22 + 373.503 0.993750 56917 160.00 + 374.783 0.994531 56960 182.86 + 375.807 0.995313 56995 213.33 + 377.087 0.996094 57044 256.00 + 378.111 0.996484 57065 284.44 + 379.135 0.996875 57095 320.00 + 379.647 0.997266 57109 365.71 + 380.671 0.997656 57130 426.67 + 381.951 0.998047 57154 512.00 + 382.463 0.998242 57166 568.89 + 382.975 0.998437 57175 640.00 + 383.743 0.998633 57188 731.43 + 384.511 0.998828 57202 853.33 + 385.279 0.999023 57211 1024.00 + 385.535 0.999121 57214 1137.78 + 385.791 0.999219 57219 1280.00 + 386.303 0.999316 57225 1462.86 + 387.071 0.999414 57230 1706.67 + 388.095 0.999512 57237 2048.00 + 388.607 0.999561 57238 2275.56 + 389.375 0.999609 57244 2560.00 + 389.375 0.999658 57244 2925.71 + 390.399 0.999707 57248 3413.33 + 391.167 0.999756 57251 4096.00 + 391.167 0.999780 57251 4551.11 + 391.423 0.999805 57252 5120.00 + 392.191 0.999829 57255 5851.43 + 392.191 0.999854 57255 6826.67 + 393.215 0.999878 57257 8192.00 + 393.215 0.999890 57257 9102.22 + 393.727 0.999902 57258 10240.00 + 393.983 0.999915 57259 11702.86 + 393.983 0.999927 57259 13653.33 + 394.239 0.999939 57260 16384.00 + 394.239 0.999945 57260 18204.44 + 394.495 0.999951 57261 20480.00 + 394.495 0.999957 57261 23405.71 + 394.495 0.999963 57261 27306.67 + 394.751 0.999969 57262 32768.00 + 394.751 0.999973 57262 36408.89 + 394.751 0.999976 57262 40960.00 + 394.751 0.999979 57262 46811.43 + 394.751 0.999982 57262 54613.33 + 396.799 0.999985 57263 65536.00 + 396.799 1.000000 57263 inf +#[Mean = 47.645, StdDeviation = 93.190] +#[Max = 396.544, Total count = 57263] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 57279 requests in 9.99s, 3.66MB read +Requests/sec: 5735.69 +Transfer/sec: 375.28KB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/reports/stage5.md b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/reports/stage5.md new file mode 100644 index 000000000..579ee4ccf --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/reports/stage5.md @@ -0,0 +1,51 @@ +## Значения переменных +* Размер очереди `500` +* Размер кластера `3` +* flushThreshold `1 Мб` +* потоков в каждом workerPool `8` + +Прогрев ноды, запускаем нагрузку put запросов, потом сразу же запускаем нагрузку get. + +## Тест 1 - кворум 2/3 +Далее пробуем нагружать сервер предельными значениями Rps из прошлого этапа. +Проведя эксперименты оказалось, что сервис не справляется со старыми Rps и предельной нагрузкой(точкой разладки) для него является: +* `3800` запросов для put (было `3500`) - было 11 ms и отклонение 24 ms -> стало 21 ms и отклоненеи 23 ms +* `4300` запросов для get (было `3500`) - было 10 ms и отклонение 32 ms -> стало 26 ms и отклонение 29 ms + + +## Тест 2 - ack 1, from 3 +Изменим ack на 1, from на 3 в скриптах wrk и проведем такие же замеры. +* `4500` запросов для put (было `3500`) - было 47 ms и отклонение 93 ms -> стало 18 ms и отклоненеи 36 ms +* `5500` запросов для get (было `3500`) - было 127 ms и отклонение 158 ms -> стало 7 ms и отклоненеи 18 ms + +## Тест 3 - ack 3, from 3 +* `3750` запросов для put (было `3500`) - было 10 ms и отклонение 22 ms -> стало 9 ms и отклоненеи 10 ms +* `3750` запросов для get (было `3500`) - было 35 ms и отклонение 54 ms -> стало 24 ms и отклоненеи 26 ms + +Тестирование с увеличением числа нод приводить не стал, так как проводил это сравнение и в прошлом этапе и производительность сильно уменьшилась, так как мы работаем в рамках одной машины. + +`RPS` увеличился, так как теперь все запросы отправляются паралелльно и не ждут друг друга. Значительное увеличение произошло при ack=1 и from=3, так как при первом успешном ответе отправляется ответ клиенту, не ожидая выполнения всех запросов. +То есть чем меньше ack тем быстрее клиент получит ответ. +`latency` уменьшилась, так как мы уменьшили время ожидания ответа от сервера, за счет того что запросы параллельны и не ожидаем всех запросов, а отвечаем при достижении ack усепешных ответов. + +## Профилирование + +## GET + +Особых изменений нет, кроме: +- CPU: появился класс CompletableFuture, который занимает 12% sample, которые "перетекли" из моего пула потоков. +- ALLOC: появился класс CompletableFuture, который занимает 30% sample, которые "перетекли" из моего пула потоков. +- LOCK: 30% занимают локи на обработке CompletableFuture + +## PUT + +Особых изменений нет, кроме: +- CPU: появился класс CompletableFuture, который занимает 13% sample, которые "перетекли" из моего пула потоков. +- ALLOC: появился класс CompletableFuture, который занимает 33% sample, которые "перетекли" из моего пула потоков. +- LOCK: 37% занимают локи на обработке CompletableFuture + + +## Выводы + +При использовании CompletableFuture, мы увеличили немного производительность в случае использования ack < from и уменьшили latency, так как запросы отправляются параллельно и не ждут друг друга. + diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/get.jfr b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/get.jfr new file mode 100644 index 000000000..86ed4110b Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/get.jfr differ diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/put.jfr b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/put.jfr new file mode 100644 index 000000000..729f25adf Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/put.jfr differ diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-get-(1-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-get-(1-3).txt new file mode 100644 index 000000000..1bcf233c1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-get-(1-3).txt @@ -0,0 +1,108 @@ + ../wrk2/wrk -c 128 -d 10 -t 16 -R 5500 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/scripts/get.lua http://localhost:8080 +Running 10s test @ http://localhost:8080 + 16 threads and 128 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 7.49ms 17.81ms 136.32ms 91.09% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.48ms + 75.000% 2.74ms + 90.000% 19.85ms + 99.000% 90.94ms + 99.900% 112.64ms + 99.990% 123.39ms + 99.999% 132.48ms +100.000% 136.45ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.143 0.000000 1 1.00 + 0.671 0.100000 5499 1.11 + 0.908 0.200000 10993 1.25 + 1.105 0.300000 16507 1.43 + 1.286 0.400000 21976 1.67 + 1.478 0.500000 27483 2.00 + 1.593 0.550000 30240 2.22 + 1.732 0.600000 32965 2.50 + 1.917 0.650000 35720 2.86 + 2.193 0.700000 38472 3.33 + 2.741 0.750000 41210 4.00 + 3.211 0.775000 42583 4.44 + 3.973 0.800000 43952 5.00 + 5.167 0.825000 45325 5.71 + 7.407 0.850000 46701 6.67 + 11.495 0.875000 48074 8.00 + 15.007 0.887500 48760 8.89 + 19.855 0.900000 49447 10.00 + 26.303 0.912500 50134 11.43 + 33.727 0.925000 50820 13.33 + 42.399 0.937500 51508 16.00 + 46.911 0.943750 51853 17.78 + 51.135 0.950000 52194 20.00 + 55.871 0.956250 52536 22.86 + 61.119 0.962500 52879 26.67 + 66.751 0.968750 53224 32.00 + 69.631 0.971875 53396 35.56 + 72.767 0.975000 53567 40.00 + 76.095 0.978125 53740 45.71 + 79.231 0.981250 53911 53.33 + 82.943 0.984375 54082 64.00 + 84.991 0.985938 54168 71.11 + 87.359 0.987500 54253 80.00 + 89.855 0.989062 54341 91.43 + 91.775 0.990625 54424 106.67 + 94.783 0.992188 54511 128.00 + 95.935 0.992969 54553 142.22 + 97.407 0.993750 54596 160.00 + 99.135 0.994531 54639 182.86 + 100.991 0.995313 54682 213.33 + 102.847 0.996094 54725 256.00 + 103.935 0.996484 54746 284.44 + 105.215 0.996875 54769 320.00 + 106.175 0.997266 54790 365.71 + 107.199 0.997656 54811 426.67 + 108.479 0.998047 54833 512.00 + 109.119 0.998242 54843 568.89 + 110.463 0.998437 54854 640.00 + 111.295 0.998633 54865 731.43 + 111.999 0.998828 54875 853.33 + 112.703 0.999023 54886 1024.00 + 114.047 0.999121 54891 1137.78 + 114.623 0.999219 54897 1280.00 + 115.391 0.999316 54902 1462.86 + 116.223 0.999414 54908 1706.67 + 116.799 0.999512 54913 2048.00 + 116.863 0.999561 54915 2275.56 + 117.759 0.999609 54918 2560.00 + 118.015 0.999658 54921 2925.71 + 118.719 0.999707 54923 3413.33 + 120.063 0.999756 54926 4096.00 + 120.319 0.999780 54927 4551.11 + 120.959 0.999805 54929 5120.00 + 121.023 0.999829 54930 5851.43 + 121.791 0.999854 54932 6826.67 + 122.111 0.999878 54933 8192.00 + 122.111 0.999890 54933 9102.22 + 123.391 0.999902 54934 10240.00 + 124.479 0.999915 54935 11702.86 + 124.479 0.999927 54935 13653.33 + 125.183 0.999939 54936 16384.00 + 125.183 0.999945 54936 18204.44 + 130.239 0.999951 54937 20480.00 + 130.239 0.999957 54937 23405.71 + 130.239 0.999963 54937 27306.67 + 132.479 0.999969 54938 32768.00 + 132.479 0.999973 54938 36408.89 + 132.479 0.999976 54938 40960.00 + 132.479 0.999979 54938 46811.43 + 132.479 0.999982 54938 54613.33 + 136.447 0.999985 54939 65536.00 + 136.447 1.000000 54939 inf +#[Mean = 7.491, StdDeviation = 17.810] +#[Max = 136.320, Total count = 54939] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 54955 requests in 10.00s, 8.91MB read +Requests/sec: 5497.92 +Transfer/sec: 0.89MB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-get-(2-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-get-(2-3).txt new file mode 100644 index 000000000..6901bb4da --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-get-(2-3).txt @@ -0,0 +1,106 @@ +../wrk2/wrk -c 128 -d 10 -t 16 -R 4300 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/scripts/get.lua http://localhost:8080 +Running 10s test @ http://localhost:8080 + 16 threads and 128 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 26.41ms 28.72ms 156.54ms 82.42% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 14.05ms + 75.000% 35.36ms + 90.000% 76.61ms + 99.000% 109.95ms + 99.900% 130.94ms + 99.990% 147.46ms + 99.999% 156.67ms +100.000% 156.67ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.624 0.000000 1 1.00 + 3.157 0.100000 4301 1.11 + 4.815 0.200000 8591 1.25 + 7.067 0.300000 12890 1.43 + 9.975 0.400000 17190 1.67 + 14.047 0.500000 21479 2.00 + 16.655 0.550000 23632 2.22 + 19.503 0.600000 25786 2.50 + 22.735 0.650000 27924 2.86 + 27.967 0.700000 30071 3.33 + 35.359 0.750000 32217 4.00 + 40.735 0.775000 33291 4.44 + 47.743 0.800000 34368 5.00 + 55.391 0.825000 35442 5.71 + 62.879 0.850000 36516 6.67 + 69.759 0.875000 37589 8.00 + 73.151 0.887500 38129 8.89 + 76.607 0.900000 38665 10.00 + 79.487 0.912500 39200 11.43 + 82.943 0.925000 39736 13.33 + 86.719 0.937500 40277 16.00 + 88.767 0.943750 40542 17.78 + 91.007 0.950000 40812 20.00 + 93.055 0.956250 41078 22.86 + 95.487 0.962500 41345 26.67 + 97.791 0.968750 41613 32.00 + 99.135 0.971875 41751 35.56 + 100.479 0.975000 41883 40.00 + 101.823 0.978125 42017 45.71 + 103.551 0.981250 42150 53.33 + 105.471 0.984375 42292 64.00 + 106.687 0.985938 42354 71.11 + 107.839 0.987500 42421 80.00 + 109.119 0.989062 42487 91.43 + 110.847 0.990625 42554 106.67 + 112.447 0.992188 42620 128.00 + 113.407 0.992969 42654 142.22 + 114.367 0.993750 42688 160.00 + 115.263 0.994531 42723 182.86 + 116.543 0.995313 42754 213.33 + 118.591 0.996094 42789 256.00 + 119.103 0.996484 42805 284.44 + 120.447 0.996875 42821 320.00 + 121.983 0.997266 42838 365.71 + 123.519 0.997656 42856 426.67 + 124.863 0.998047 42872 512.00 + 125.503 0.998242 42880 568.89 + 126.335 0.998437 42889 640.00 + 127.231 0.998633 42897 731.43 + 128.383 0.998828 42905 853.33 + 131.327 0.999023 42914 1024.00 + 132.479 0.999121 42918 1137.78 + 133.247 0.999219 42922 1280.00 + 134.015 0.999316 42926 1462.86 + 135.807 0.999414 42930 1706.67 + 136.831 0.999512 42935 2048.00 + 137.471 0.999561 42937 2275.56 + 137.983 0.999609 42940 2560.00 + 138.111 0.999658 42941 2925.71 + 139.007 0.999707 42943 3413.33 + 141.055 0.999756 42945 4096.00 + 141.311 0.999780 42946 4551.11 + 142.079 0.999805 42947 5120.00 + 142.335 0.999829 42948 5851.43 + 145.151 0.999854 42949 6826.67 + 146.303 0.999878 42950 8192.00 + 147.455 0.999890 42951 9102.22 + 147.455 0.999902 42951 10240.00 + 148.223 0.999915 42952 11702.86 + 148.223 0.999927 42952 13653.33 + 148.991 0.999939 42953 16384.00 + 148.991 0.999945 42953 18204.44 + 148.991 0.999951 42953 20480.00 + 149.119 0.999957 42954 23405.71 + 149.119 0.999963 42954 27306.67 + 149.119 0.999969 42954 32768.00 + 149.119 0.999973 42954 36408.89 + 149.119 0.999976 42954 40960.00 + 156.671 0.999979 42955 46811.43 + 156.671 1.000000 42955 inf +#[Mean = 26.407, StdDeviation = 28.722] +#[Max = 156.544, Total count = 42955] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 42971 requests in 10.00s, 6.97MB read +Requests/sec: 4297.49 +Transfer/sec: 713.45KB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-get-(3-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-get-(3-3).txt new file mode 100644 index 000000000..d7ae0475a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-get-(3-3).txt @@ -0,0 +1,105 @@ +../wrk2/wrk -c 128 -d 10 -t 16 -R 3750 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/scripts/get.lua http://localhost:8080 +Running 10s test @ http://localhost:8080 + 16 threads and 128 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 24.19ms 26.36ms 202.37ms 89.37% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 15.73ms + 75.000% 30.72ms + 90.000% 51.97ms + 99.000% 142.98ms + 99.900% 180.86ms + 99.990% 198.53ms + 99.999% 202.49ms +100.000% 202.49ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 1.129 0.000000 1 1.00 + 4.171 0.100000 3749 1.11 + 6.107 0.200000 7496 1.25 + 8.679 0.300000 11237 1.43 + 12.031 0.400000 14984 1.67 + 15.735 0.500000 18727 2.00 + 18.127 0.550000 20599 2.22 + 20.671 0.600000 22478 2.50 + 23.487 0.650000 24345 2.86 + 26.703 0.700000 26222 3.33 + 30.735 0.750000 28093 4.00 + 33.183 0.775000 29036 4.44 + 35.871 0.800000 29963 5.00 + 38.911 0.825000 30898 5.71 + 42.463 0.850000 31840 6.67 + 46.495 0.875000 32772 8.00 + 49.279 0.887500 33244 8.89 + 51.967 0.900000 33713 10.00 + 55.231 0.912500 34180 11.43 + 59.327 0.925000 34643 13.33 + 64.703 0.937500 35111 16.00 + 68.287 0.943750 35345 17.78 + 72.831 0.950000 35579 20.00 + 78.527 0.956250 35814 22.86 + 85.631 0.962500 36049 26.67 + 95.295 0.968750 36281 32.00 + 100.863 0.971875 36398 35.56 + 107.263 0.975000 36515 40.00 + 113.919 0.978125 36633 45.71 + 120.447 0.981250 36751 53.33 + 128.447 0.984375 36866 64.00 + 132.095 0.985938 36929 71.11 + 136.063 0.987500 36984 80.00 + 140.415 0.989062 37042 91.43 + 144.639 0.990625 37100 106.67 + 148.735 0.992188 37160 128.00 + 151.935 0.992969 37188 142.22 + 154.751 0.993750 37217 160.00 + 157.439 0.994531 37249 182.86 + 160.511 0.995313 37277 213.33 + 163.583 0.996094 37306 256.00 + 164.863 0.996484 37320 284.44 + 167.679 0.996875 37334 320.00 + 170.111 0.997266 37349 365.71 + 171.647 0.997656 37364 426.67 + 173.823 0.998047 37378 512.00 + 174.975 0.998242 37386 568.89 + 176.639 0.998437 37393 640.00 + 177.791 0.998633 37400 731.43 + 179.967 0.998828 37408 853.33 + 181.119 0.999023 37415 1024.00 + 182.015 0.999121 37419 1137.78 + 182.527 0.999219 37422 1280.00 + 183.423 0.999316 37426 1462.86 + 185.215 0.999414 37430 1706.67 + 185.727 0.999512 37433 2048.00 + 186.751 0.999561 37435 2275.56 + 187.135 0.999609 37437 2560.00 + 187.519 0.999658 37439 2925.71 + 188.927 0.999707 37441 3413.33 + 189.951 0.999756 37442 4096.00 + 192.767 0.999780 37443 4551.11 + 194.047 0.999805 37444 5120.00 + 194.175 0.999829 37445 5851.43 + 195.711 0.999854 37446 6826.67 + 198.527 0.999878 37447 8192.00 + 198.527 0.999890 37447 9102.22 + 198.655 0.999902 37449 10240.00 + 198.655 0.999915 37449 11702.86 + 198.655 0.999927 37449 13653.33 + 198.655 0.999939 37449 16384.00 + 198.655 0.999945 37449 18204.44 + 200.447 0.999951 37450 20480.00 + 200.447 0.999957 37450 23405.71 + 200.447 0.999963 37450 27306.67 + 200.447 0.999969 37450 32768.00 + 200.447 0.999973 37450 36408.89 + 202.495 0.999976 37451 40960.00 + 202.495 1.000000 37451 inf +#[Mean = 24.185, StdDeviation = 26.362] +#[Max = 202.368, Total count = 37451] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 37467 requests in 10.00s, 6.07MB read +Requests/sec: 3746.97 +Transfer/sec: 622.06KB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-put-(1-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-put-(1-3).txt new file mode 100644 index 000000000..b3665dec1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-put-(1-3).txt @@ -0,0 +1,101 @@ +../wrk2/wrk -c 128 -d 10 -t 16 -R 4500 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/scripts/put.lua http://localhost:8080 +Running 10s test @ http://localhost:8080 + 16 threads and 128 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 17.68ms 36.18ms 246.14ms 87.08% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.01ms + 75.000% 8.02ms + 90.000% 71.81ms + 99.000% 160.00ms + 99.900% 219.26ms + 99.990% 240.13ms + 99.999% 246.27ms +100.000% 246.27ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.202 0.000000 1 1.00 + 0.859 0.100000 4506 1.11 + 1.144 0.200000 8999 1.25 + 1.384 0.300000 13503 1.43 + 1.636 0.400000 18000 1.67 + 2.011 0.500000 22491 2.00 + 2.299 0.550000 24731 2.22 + 2.763 0.600000 26979 2.50 + 3.595 0.650000 29228 2.86 + 5.023 0.700000 31478 3.33 + 8.019 0.750000 33723 4.00 + 10.695 0.775000 34847 4.44 + 16.575 0.800000 35972 5.00 + 26.911 0.825000 37096 5.71 + 41.247 0.850000 38220 6.67 + 56.031 0.875000 39343 8.00 + 63.903 0.887500 39905 8.89 + 71.807 0.900000 40468 10.00 + 79.231 0.912500 41034 11.43 + 87.679 0.925000 41591 13.33 + 96.703 0.937500 42155 16.00 + 101.055 0.943750 42435 17.78 + 105.855 0.950000 42719 20.00 + 111.167 0.956250 42997 22.86 + 117.951 0.962500 43277 26.67 + 124.415 0.968750 43560 32.00 + 127.999 0.971875 43700 35.56 + 132.095 0.975000 43841 40.00 + 137.983 0.978125 43984 45.71 + 143.743 0.981250 44123 53.33 + 148.735 0.984375 44265 64.00 + 151.679 0.985938 44332 71.11 + 154.367 0.987500 44403 80.00 + 158.207 0.989062 44472 91.43 + 161.791 0.990625 44547 106.67 + 166.015 0.992188 44614 128.00 + 168.447 0.992969 44648 142.22 + 172.031 0.993750 44684 160.00 + 174.719 0.994531 44718 182.86 + 179.583 0.995313 44753 213.33 + 186.879 0.996094 44788 256.00 + 189.823 0.996484 44805 284.44 + 192.511 0.996875 44823 320.00 + 196.479 0.997266 44841 365.71 + 199.679 0.997656 44858 426.67 + 203.647 0.998047 44876 512.00 + 206.847 0.998242 44884 568.89 + 210.175 0.998437 44894 640.00 + 212.223 0.998633 44902 731.43 + 215.551 0.998828 44912 853.33 + 220.159 0.999023 44920 1024.00 + 221.183 0.999121 44924 1137.78 + 222.463 0.999219 44928 1280.00 + 225.791 0.999316 44933 1462.86 + 227.199 0.999414 44937 1706.67 + 230.015 0.999512 44942 2048.00 + 230.655 0.999561 44944 2275.56 + 231.039 0.999609 44946 2560.00 + 231.935 0.999658 44948 2925.71 + 233.855 0.999707 44950 3413.33 + 235.391 0.999756 44953 4096.00 + 235.775 0.999780 44954 4551.11 + 235.903 0.999805 44955 5120.00 + 236.543 0.999829 44956 5851.43 + 239.615 0.999854 44957 6826.67 + 239.743 0.999878 44958 8192.00 + 240.127 0.999890 44959 9102.22 + 240.127 0.999902 44959 10240.00 + 240.511 0.999915 44960 11702.86 + 240.511 0.999927 44960 13653.33 + 243.839 0.999939 44961 16384.00 + 243.839 0.999945 44961 18204.44 + 243.839 0.999951 44961 20480.00 + 246.271 0.999957 44963 23405.71 + 246.271 1.000000 44963 inf +#[Mean = 17.685, StdDeviation = 36.183] +#[Max = 246.144, Total count = 44963] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 44980 requests in 10.00s, 2.87MB read +Requests/sec: 4498.88 +Transfer/sec: 294.36KB \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-put-(2-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-put-(2-3).txt new file mode 100644 index 000000000..73c4868ec --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-put-(2-3).txt @@ -0,0 +1,105 @@ +../wrk2/wrk -c 128 -d 10 -t 16 -R 3800 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/scripts/put.lua http://localhost:8080 +Running 10s test @ http://localhost:8080 + 16 threads and 128 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 20.75ms 22.66ms 126.59ms 81.91% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 10.45ms + 75.000% 31.53ms + 90.000% 57.22ms + 99.000% 89.47ms + 99.900% 106.62ms + 99.990% 119.81ms + 99.999% 126.65ms +100.000% 126.65ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.662 0.000000 1 1.00 + 2.171 0.100000 3800 1.11 + 3.163 0.200000 7603 1.25 + 4.859 0.300000 11395 1.43 + 7.243 0.400000 15191 1.67 + 10.455 0.500000 18985 2.00 + 12.463 0.550000 20884 2.22 + 15.047 0.600000 22782 2.50 + 19.023 0.650000 24682 2.86 + 25.695 0.700000 26580 3.33 + 31.535 0.750000 28480 4.00 + 35.071 0.775000 29427 4.44 + 39.615 0.800000 30375 5.00 + 44.447 0.825000 31325 5.71 + 48.639 0.850000 32273 6.67 + 52.639 0.875000 33229 8.00 + 54.815 0.887500 33697 8.89 + 57.215 0.900000 34172 10.00 + 59.711 0.912500 34647 11.43 + 62.431 0.925000 35123 13.33 + 65.503 0.937500 35596 16.00 + 67.327 0.943750 35838 17.78 + 69.439 0.950000 36070 20.00 + 71.551 0.956250 36315 22.86 + 73.727 0.962500 36545 26.67 + 76.223 0.968750 36782 32.00 + 77.631 0.971875 36901 35.56 + 78.911 0.975000 37019 40.00 + 80.383 0.978125 37140 45.71 + 82.175 0.981250 37258 53.33 + 84.479 0.984375 37376 64.00 + 85.951 0.985938 37437 71.11 + 87.231 0.987500 37494 80.00 + 88.639 0.989062 37554 91.43 + 90.175 0.990625 37613 106.67 + 91.647 0.992188 37673 128.00 + 92.799 0.992969 37703 142.22 + 93.887 0.993750 37731 160.00 + 95.167 0.994531 37762 182.86 + 96.511 0.995313 37792 213.33 + 98.303 0.996094 37821 256.00 + 99.135 0.996484 37836 284.44 + 100.031 0.996875 37850 320.00 + 100.927 0.997266 37865 365.71 + 102.271 0.997656 37882 426.67 + 103.167 0.998047 37895 512.00 + 103.551 0.998242 37903 568.89 + 104.383 0.998437 37910 640.00 + 104.831 0.998633 37917 731.43 + 105.343 0.998828 37924 853.33 + 107.455 0.999023 37931 1024.00 + 108.095 0.999121 37935 1137.78 + 108.735 0.999219 37939 1280.00 + 109.887 0.999316 37943 1462.86 + 110.847 0.999414 37947 1706.67 + 111.487 0.999512 37950 2048.00 + 111.999 0.999561 37952 2275.56 + 112.447 0.999609 37954 2560.00 + 113.279 0.999658 37956 2925.71 + 113.855 0.999707 37957 3413.33 + 113.919 0.999756 37959 4096.00 + 115.071 0.999780 37960 4551.11 + 115.327 0.999805 37961 5120.00 + 117.119 0.999829 37962 5851.43 + 118.271 0.999854 37963 6826.67 + 119.807 0.999878 37964 8192.00 + 119.807 0.999890 37964 9102.22 + 121.471 0.999902 37965 10240.00 + 121.471 0.999915 37965 11702.86 + 121.855 0.999927 37966 13653.33 + 121.855 0.999939 37966 16384.00 + 121.855 0.999945 37966 18204.44 + 122.431 0.999951 37967 20480.00 + 122.431 0.999957 37967 23405.71 + 122.431 0.999963 37967 27306.67 + 122.431 0.999969 37967 32768.00 + 122.431 0.999973 37967 36408.89 + 126.655 0.999976 37968 40960.00 + 126.655 1.000000 37968 inf +#[Mean = 20.753, StdDeviation = 22.661] +#[Max = 126.592, Total count = 37968] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 37984 requests in 10.00s, 2.43MB read +Requests/sec: 3799.55 +Transfer/sec: 248.60KB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-put-(3-3).txt b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-put-(3-3).txt new file mode 100644 index 000000000..f42d2afdb --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/results/wrk-put-(3-3).txt @@ -0,0 +1,105 @@ +../wrk2/wrk -c 128 -d 10 -t 16 -R 3750 -L -s ./src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/scripts/put.lua http://localhost:8080 +Running 10s test @ http://localhost:8080 + 16 threads and 128 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 8.82ms 10.48ms 101.50ms 91.21% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 5.13ms + 75.000% 9.94ms + 90.000% 18.05ms + 99.000% 58.94ms + 99.900% 82.62ms + 99.990% 97.73ms + 99.999% 101.57ms +100.000% 101.57ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.859 0.000000 1 1.00 + 2.509 0.100000 3759 1.11 + 3.037 0.200000 7497 1.25 + 3.575 0.300000 11258 1.43 + 4.255 0.400000 15007 1.67 + 5.131 0.500000 18751 2.00 + 5.711 0.550000 20609 2.22 + 6.459 0.600000 22483 2.50 + 7.383 0.650000 24356 2.86 + 8.551 0.700000 26233 3.33 + 9.935 0.750000 28104 4.00 + 10.767 0.775000 29039 4.44 + 11.711 0.800000 29976 5.00 + 12.783 0.825000 30912 5.71 + 14.095 0.850000 31850 6.67 + 15.799 0.875000 32785 8.00 + 16.863 0.887500 33257 8.89 + 18.047 0.900000 33725 10.00 + 19.343 0.912500 34193 11.43 + 21.151 0.925000 34662 13.33 + 23.279 0.937500 35127 16.00 + 25.199 0.943750 35364 17.78 + 27.263 0.950000 35595 20.00 + 30.223 0.956250 35829 22.86 + 34.207 0.962500 36063 26.67 + 38.079 0.968750 36298 32.00 + 40.127 0.971875 36415 35.56 + 42.303 0.975000 36533 40.00 + 44.991 0.978125 36649 45.71 + 48.479 0.981250 36766 53.33 + 52.351 0.984375 36883 64.00 + 53.535 0.985938 36943 71.11 + 55.455 0.987500 37000 80.00 + 57.631 0.989062 37059 91.43 + 59.935 0.990625 37117 106.67 + 62.591 0.992188 37176 128.00 + 64.351 0.992969 37205 142.22 + 65.727 0.993750 37234 160.00 + 67.263 0.994531 37264 182.86 + 68.735 0.995313 37293 213.33 + 71.487 0.996094 37322 256.00 + 72.575 0.996484 37338 284.44 + 73.407 0.996875 37351 320.00 + 74.751 0.997266 37367 365.71 + 76.031 0.997656 37382 426.67 + 77.119 0.998047 37395 512.00 + 78.207 0.998242 37403 568.89 + 79.359 0.998437 37410 640.00 + 80.255 0.998633 37417 731.43 + 81.279 0.998828 37425 853.33 + 82.751 0.999023 37432 1024.00 + 83.519 0.999121 37436 1137.78 + 83.903 0.999219 37439 1280.00 + 85.823 0.999316 37443 1462.86 + 86.335 0.999414 37447 1706.67 + 86.911 0.999512 37450 2048.00 + 88.511 0.999561 37452 2275.56 + 89.279 0.999609 37454 2560.00 + 90.815 0.999658 37456 2925.71 + 91.711 0.999707 37458 3413.33 + 91.903 0.999756 37459 4096.00 + 94.079 0.999780 37460 4551.11 + 94.271 0.999805 37461 5120.00 + 94.527 0.999829 37462 5851.43 + 97.471 0.999854 37463 6826.67 + 97.727 0.999878 37464 8192.00 + 97.727 0.999890 37464 9102.22 + 100.159 0.999902 37465 10240.00 + 100.159 0.999915 37465 11702.86 + 100.543 0.999927 37466 13653.33 + 100.543 0.999939 37466 16384.00 + 100.543 0.999945 37466 18204.44 + 101.183 0.999951 37467 20480.00 + 101.183 0.999957 37467 23405.71 + 101.183 0.999963 37467 27306.67 + 101.183 0.999969 37467 32768.00 + 101.183 0.999973 37467 36408.89 + 101.567 0.999976 37468 40960.00 + 101.567 1.000000 37468 inf +#[Mean = 8.815, StdDeviation = 10.476] +#[Max = 101.504, Total count = 37468] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 37484 requests in 10.00s, 2.40MB read +Requests/sec: 3749.51 +Transfer/sec: 245.33KB diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/scripts/get.lua b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/scripts/get.lua new file mode 100644 index 000000000..82da6145d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/scripts/get.lua @@ -0,0 +1,7 @@ +function request() + local headers = {} + headers["Host"] = "localhost:8080" + local id = math.random(1, 10000) + local key = "key" .. id + return wrk.format("GET", "/v0/entity?id=" .. key .. "&ack=1&from=3", headers) +end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/scripts/put.lua b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/scripts/put.lua new file mode 100644 index 000000000..d3573bceb --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/analysis/stage5/scripts/put.lua @@ -0,0 +1,20 @@ +local random = require("math").random +local char = require("string").char + +local function randomString(n) + local result = {} + for i = 1, n do + result[i] = char(random(33, 126)) -- ASCII символы с 33 по 126 + end + return table.concat(result) +end + +counter = 0 +function request() + counter = counter + 1 + local headers = {} + headers["Host"] = "localhost:8080" + local key = "key" .. counter + local value = "value$" .. randomString(100) + return wrk.format("PUT", "/v0/entity?id=" .. key .. "&ack=3&from=3", headers, value) +end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/nikitaprokopev/exceptions/NotEnoughReplicasException.java b/src/main/java/ru/vk/itmo/test/nikitaprokopev/exceptions/NotEnoughReplicasException.java new file mode 100644 index 000000000..e3d4e3f94 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/nikitaprokopev/exceptions/NotEnoughReplicasException.java @@ -0,0 +1,4 @@ +package ru.vk.itmo.test.nikitaprokopev.exceptions; + +public class NotEnoughReplicasException extends RuntimeException { +} diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/HandleResult.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/HandleResult.java new file mode 100644 index 000000000..9ce1e2949 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/HandleResult.java @@ -0,0 +1,14 @@ +package ru.vk.itmo.test.osipovdaniil; + +public record HandleResult(int status, byte[] data, long timestamp) { + + public HandleResult(int status, byte[] data, long timestamp) { + this.status = status; + this.data = data; + this.timestamp = timestamp; + } + + public HandleResult(int status, byte[] data) { + this(status, data, 0); + } +} diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/MergeHandleResult.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/MergeHandleResult.java new file mode 100644 index 000000000..6482076e1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/MergeHandleResult.java @@ -0,0 +1,73 @@ +package ru.vk.itmo.test.osipovdaniil; + +import one.nio.http.HttpSession; +import one.nio.http.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.HttpURLConnection; +import java.util.concurrent.atomic.AtomicInteger; + +public class MergeHandleResult { + private static final Logger log = LoggerFactory.getLogger(MergeHandleResult.class); + private final HandleResult[] handleResults; + private final AtomicInteger count; + private final AtomicInteger countValid; + private final int ack; + private final int from; + private final HttpSession session; + + public MergeHandleResult(HttpSession session, int size, int ack) { + this.session = session; + this.handleResults = new HandleResult[size]; + this.count = new AtomicInteger(); + this.countValid = new AtomicInteger(); + this.ack = ack; + this.from = size; + } + + public boolean add(int index, HandleResult handleResult) { + handleResults[index] = handleResult; + int valid = validateResultStatus(handleResult.status()) ? countValid.getAndIncrement() : countValid.get(); + if (valid >= ack) { + sendResult(); + return true; + } + int get = count.incrementAndGet(); + if (get == from) { + sendResult(); + return true; + } + return false; + } + + private boolean validateResultStatus(final int status) { + return status == HttpURLConnection.HTTP_OK + || status == HttpURLConnection.HTTP_CREATED + || status == HttpURLConnection.HTTP_ACCEPTED + || status == HttpURLConnection.HTTP_NOT_FOUND; + } + + private void sendResult() { + HandleResult mergedResult = new HandleResult(HttpURLConnection.HTTP_GATEWAY_TIMEOUT, null); + int cnt = 0; + for (HandleResult handleResult : handleResults) { + if (validateResultStatus(handleResult.status())) { + cnt++; + if (mergedResult.timestamp() <= handleResult.timestamp()) { + mergedResult = handleResult; + } + } + } + try { + if (cnt < ack) { + session.sendResponse(new Response(Response.GATEWAY_TIMEOUT, Response.EMPTY)); + } else { + session.sendResponse(new Response(String.valueOf(mergedResult.status()), mergedResult.data())); + } + } catch (Exception e) { + log.error("Exception during handleRequest", e); + Utils.sendEmptyInternal(session, log); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/ServerImpl.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/ServerImpl.java index eae39435a..6c3db3040 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/ServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/ServerImpl.java @@ -1,18 +1,19 @@ package ru.vk.itmo.test.osipovdaniil; +import one.nio.async.CustomThreadFactory; 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; +import one.nio.util.Utf8; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ru.vk.itmo.ServiceConfig; -import ru.vk.itmo.dao.BaseEntry; -import ru.vk.itmo.dao.Entry; +import ru.vk.itmo.test.osipovdaniil.dao.ReferenceBaseEntry; import ru.vk.itmo.test.osipovdaniil.dao.ReferenceDao; import java.io.IOException; import java.lang.foreign.MemorySegment; @@ -23,180 +24,359 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; -import java.util.List; +import java.time.Duration; +import java.util.Iterator; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.logging.Logger; +import java.util.concurrent.atomic.AtomicBoolean; public class ServerImpl extends HttpServer { - private static final String ENTITY_PATH = "/v0/entity"; - private static final Integer MAX_THREADS = 64; - private static final String ID = "id="; - private final ExecutorService requestExecutor; - private final List clusterUrls; - private final Logger logger; + public static final String ENTITY_PATH = "/v0/entity"; + public static final String START = "start="; + private static final String HEADER_REMOTE = "X-flag-remote-server-to-node"; + private static final String HEADER_REMOTE_ONE_NIO_HEADER = HEADER_REMOTE + ": true"; + private static final String HEADER_TIMESTAMP = "X-flag-remote-server-to-node"; + private static final String HEADER_TIMESTAMP_ONE_NIO_HEADER = HEADER_TIMESTAMP + ": "; + private static final Logger log = LoggerFactory.getLogger(ServerImpl.class); + private static final int THREADS = Runtime.getRuntime().availableProcessors(); + public static final String ID = "id="; + public static final String ACK = "ack="; + public static final String FROM = "from="; + public static final String ENTITIES_PATH = "/v0/entities"; + public static final String END = "end="; + private static final byte[] CHUNK_HEADERS = + """ + HTTP/1.1 200\r + OKContentType: application/octet-stream\r + Transfer-Encoding: chunked\r + Connection: keep-alive\r + \r + """.getBytes(StandardCharsets.UTF_8); + private static final byte[] N_SEP = "\n".getBytes(StandardCharsets.UTF_8); + private static final byte[] CHUNK_SEP = "\r\n".getBytes(StandardCharsets.UTF_8); + private final ExecutorService executorLocal = Executors.newFixedThreadPool(THREADS / 2, + new CustomThreadFactory("local-work")); + private final ExecutorService executorRemote = Executors.newFixedThreadPool(THREADS / 2, + new CustomThreadFactory("remote-work")); private final ReferenceDao dao; - private final String url; + private final ServiceConfig config; private final HttpClient httpClient; - public ServerImpl(final ServiceConfig serviceConfig, ReferenceDao dao) throws IOException { - super(createHttpServerConfig(serviceConfig)); + public ServerImpl(final ServiceConfig config, + final ReferenceDao dao) throws IOException { + super(createServerConfigWithPort(config.selfPort())); this.dao = dao; - this.requestExecutor = Executors.newFixedThreadPool(MAX_THREADS); - this.logger = Logger.getLogger(ServerImpl.class.getName()); - this.clusterUrls = serviceConfig.clusterUrls(); - this.url = serviceConfig.selfUrl(); - this.httpClient = HttpClient.newHttpClient(); + this.config = config; + this.httpClient = HttpClient.newBuilder() + .executor(Executors.newFixedThreadPool(THREADS)) + .connectTimeout(Duration.ofMillis(500)) + .version(HttpClient.Version.HTTP_1_1) + .build(); } - private static HttpServerConfig createHttpServerConfig(final ServiceConfig serviceConfig) { - final HttpServerConfig httpServerConfig = new HttpServerConfig(); - final AcceptorConfig acceptorConfig = new AcceptorConfig(); - acceptorConfig.port = serviceConfig.selfPort(); + private static HttpServerConfig createServerConfigWithPort(final int port) { + HttpServerConfig serverConfig = new HttpServerConfig(); + AcceptorConfig acceptorConfig = new AcceptorConfig(); + acceptorConfig.port = port; acceptorConfig.reusePort = true; - httpServerConfig.acceptors = new AcceptorConfig[]{acceptorConfig}; - httpServerConfig.closeSessions = true; - return httpServerConfig; + serverConfig.selectors = Runtime.getRuntime().availableProcessors() / 2; + + serverConfig.acceptors = new AcceptorConfig[]{acceptorConfig}; + serverConfig.closeSessions = true; + return serverConfig; } private boolean validateId(final String id) { return id != null && !id.isEmpty(); } - private Response requestHandle(final String id, final Function request) { + private boolean validateMethod(final int method) { + return method == Request.METHOD_PUT || method == Request.METHOD_DELETE || method == Request.METHOD_GET; + } + + private boolean validateAckFrom(final int ack, final int from) { + return from > 0 && from <= config.clusterUrls().size() && ack <= from && ack > 0; + } + + @Override + public void handleRequest(final Request request, final HttpSession session) throws IOException { + final String path = request.getPath(); + if (path.startsWith(ENTITIES_PATH)) { + getRange(request, session); + return; + } + if (!ENTITY_PATH.equals(path)) { + session.sendError(Response.BAD_REQUEST, null); + return; + } + if (!validateMethod(request.getMethod())) { + session.sendError(Response.METHOD_NOT_ALLOWED, null); + return; + } + final String id = request.getParameter(ID); if (!validateId(id)) { - return new Response(Response.BAD_REQUEST, ("invalid id: " + id).getBytes(StandardCharsets.UTF_8)); - } - final MemorySegment key = MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)); - return request.apply(key); - } - - private Response handleProxyRequest(final Request request, final String url) throws IOException, - InterruptedException { - final HttpResponse response = processProxyRequest(request, url); - final String statusCode = switch (response.statusCode()) { - case HttpURLConnection.HTTP_OK -> Response.OK; - case HttpURLConnection.HTTP_CREATED -> Response.CREATED; - case HttpURLConnection.HTTP_ACCEPTED -> Response.ACCEPTED; - case HttpURLConnection.HTTP_BAD_REQUEST -> Response.BAD_REQUEST; - case HttpURLConnection.HTTP_NOT_FOUND -> Response.NOT_FOUND; - default -> throw new IllegalStateException("Unexpected response status code: " + response.statusCode()); - }; - return new Response(statusCode, response.body()); - } - - private HttpResponse processProxyRequest(final Request request, final String url) throws IOException, - InterruptedException { - final byte[] rBody = request.getBody(); - final byte[] requestBody = rBody == null ? new byte[0] : rBody; - return httpClient - .send(HttpRequest.newBuilder(URI.create(url + request.getURI())) - .method(request.getMethodName(), HttpRequest.BodyPublishers.ofByteArray(requestBody)) - .build(), HttpResponse.BodyHandlers.ofByteArray()); - } - - private String getTargetUrl(final String id) { - int max = 0; - int maxId = 0; - for (int i = 0; i < clusterUrls.size(); i++) { - int hash = Hash.murmur3(id + i); - if (hash > max) { - max = hash; - maxId = i; + session.sendError(Response.BAD_REQUEST, null); + return; + } + if (request.getHeader(HEADER_REMOTE_ONE_NIO_HEADER) != null) { + executorLocal.execute(() -> { + try { + final HandleResult local = local(request, id); + final Response response = new Response(String.valueOf(local.status()), local.data()); + response.addHeader(HEADER_TIMESTAMP_ONE_NIO_HEADER + local.timestamp()); + session.sendResponse(response); + } catch (Exception e) { + log.error("Exception during handleRequest", e); + Utils.sendEmptyInternal(session, log); + } + }); + return; + } + final int ack = getInt(request, ACK, config.clusterUrls().size() / 2 + 1); + final int from = getInt(request, FROM, config.clusterUrls().size()); + if (!validateAckFrom(ack, from)) { + session.sendError(Response.BAD_REQUEST, null); + return; + } + final int[] indexes = getIndexes(id, from); + final MergeHandleResult mergeHandleResult = new MergeHandleResult(session, indexes.length, ack); + for (int i = 0; i < indexes.length; i++) { + int index = indexes[i]; + final String executorNode = config.clusterUrls().get(index); + if (executorNode.equals(config.selfUrl())) { + handleAsync(executorLocal, i, mergeHandleResult, () -> localAsync(request, id)); + } else { + handleAsync(executorRemote, i, mergeHandleResult, () -> remoteAsync(request, executorNode)); } } - return clusterUrls.get(maxId); + + } + + private void getRange(final Request request, final HttpSession session) throws IOException { + final String startParameter = request.getParameter(START); + final String endParameter = request.getParameter(END); + if (!validateStartEnd(startParameter, endParameter)) { + session.sendError(Response.BAD_REQUEST, null); + return; + } + final MemorySegment startKeySegment = MemorySegment.ofArray(startParameter.getBytes(StandardCharsets.UTF_8)); + final MemorySegment endKeySegment = endParameter == null + ? null : MemorySegment.ofArray(endParameter.getBytes(StandardCharsets.UTF_8)); + final Iterator> iterator = dao.get(startKeySegment, endKeySegment); + writeChunkRes(iterator, session); } - @Path(ENTITY_PATH) - @RequestMethod(Request.METHOD_GET) - public Response get(@Param("id") final String id) { - return requestHandle(id, - key -> { - final Entry entry = dao.get(key); - return entry == null - ? new Response(Response.NOT_FOUND, Response.EMPTY) : - Response.ok(entry.value().toArray(ValueLayout.JAVA_BYTE)); - }); + private void writeChunkRes(final Iterator> iterator, final HttpSession session) + throws IOException { + if (!iterator.hasNext()) { + session.sendResponse(new Response(Response.OK, Response.EMPTY)); + return; + } + session.write(CHUNK_HEADERS, 0, CHUNK_HEADERS.length); + while (iterator.hasNext()) { + writeChunkEntry(iterator.next(), session); + } + final byte[] zero = Integer.toHexString(0).toUpperCase().getBytes(StandardCharsets.UTF_8); + session.write(zero, 0, zero.length); + session.write(CHUNK_SEP, 0, CHUNK_SEP.length); + session.write(CHUNK_SEP, 0, CHUNK_SEP.length); + session.close(); } - @Path(ENTITY_PATH) - @RequestMethod(Request.METHOD_DELETE) - public Response delete(@Param("id") final String id) { - return requestHandle(id, - key -> { - dao.upsert(new BaseEntry<>(key, null)); - return new Response(Response.ACCEPTED, Response.EMPTY); - }); + private void writeChunkEntry(final ReferenceBaseEntry entry, final HttpSession session) + throws IOException { + final byte[] key = entry.key().toArray(ValueLayout.JAVA_BYTE); + final byte[] value = entry.value().toArray(ValueLayout.JAVA_BYTE); + final byte[] length = Integer.toHexString(key.length + value.length + N_SEP.length) + .toUpperCase() + .getBytes(StandardCharsets.UTF_8); + session.write(length, 0, length.length); + session.write(CHUNK_SEP, 0, CHUNK_SEP.length); + session.write(key, 0, key.length); + session.write(N_SEP, 0, N_SEP.length); + session.write(value, 0, value.length); + session.write(CHUNK_SEP, 0, CHUNK_SEP.length); } - @Path(ENTITY_PATH) - @RequestMethod(Request.METHOD_PUT) - public Response put(@Param("id") final String id, final Request value) { - return requestHandle(id, - key -> { - dao.upsert(new BaseEntry<>(key, MemorySegment.ofArray(value.getBody()))); - return new Response(Response.CREATED, Response.EMPTY); - }); + private boolean validateStartEnd(final String startParameter, final String endParameter) { + return startParameter != null && !startParameter.isEmpty() && (endParameter == null || !endParameter.isEmpty()); + } + + private int getInt(final Request request, final String param, final int defaultValue) { + int ack; + String ackStr = request.getParameter(param); + if (ackStr == null || ackStr.isBlank()) { + ack = defaultValue; + } else { + try { + ack = Integer.parseInt(ackStr); + } catch (Exception e) { + throw new IllegalArgumentException("parse error (not int)"); + } + } + return ack; } - private void handleRequestTask(final Request request, final HttpSession session) { + private CompletableFuture remoteAsync(final Request request, final String executorNode) { + return CompletableFuture.supplyAsync(() -> remote(request, executorNode)); + } + + private HandleResult remote(final Request request, final String executorNode) { try { - Response response; - if (!request.getPath().startsWith(ENTITY_PATH)) { - response = new Response(Response.BAD_REQUEST, Response.EMPTY); - session.sendResponse(response); + return invokeRemote(executorNode, request); + } catch (IOException e) { + log.info("I/O exception while calling remote node", e); + return new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.info("Thread interrupted"); + return new HandleResult(HttpURLConnection.HTTP_UNAVAILABLE, Response.EMPTY); + } + + } + + private void handleAsync(final ExecutorService executor, + final int index, + final MergeHandleResult mergeHandleResult, + final ERunnable runnable) { + final AtomicBoolean done = new AtomicBoolean(); + + CompletableFuture completableFuture; + try { + completableFuture = runnable.run(); + } catch (Exception e) { + log.error("Exception during handleRequest", e); + completableFuture = CompletableFuture.supplyAsync( + () -> new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY)); + } + completableFuture.whenCompleteAsync((handleResult, throwable) -> { + if (done.get()) { return; } - final String targetUrl = getTargetUrl(request.getParameter(ID)); - if (!url.equals(targetUrl)) { - session.sendResponse(handleProxyRequest(request, targetUrl)); - return; + // throwable != null + done.set(mergeHandleResult.add(index, handleResult)); + }, executor).exceptionally( + throwable -> new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY)); + } + + @Override + public void handleDefault(final Request request, final HttpSession session) throws IOException { + session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); + } + + @Path("/v0/status") + public Response status() { + return Response.ok("OK"); + } + + private HandleResult invokeRemote(final String executorNode, final Request request) + throws IOException, InterruptedException { + final HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(executorNode + request.getURI())) + .method( + request.getMethodName(), + request.getBody() == null + ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofByteArray(request.getBody()) + ) + .header(HEADER_REMOTE, "true") + .timeout(Duration.ofMillis(500)) + .build(); + final HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray()); + final Optional string = httpResponse.headers().firstValue(HEADER_TIMESTAMP); + long timestamp; + if (string.isPresent()) { + try { + timestamp = Long.parseLong(string.get()); + } catch (Exception e) { + log.error("error timestamp parsing\n" + e); + timestamp = 0; } + } else { + timestamp = 0; + } + return new HandleResult(httpResponse.statusCode(), httpResponse.body(), timestamp); + } - final int method = request.getMethod(); - if (method == Request.METHOD_GET) { - response = get(request.getParameter(ID)); - } else if (method == Request.METHOD_DELETE) { - response = delete(request.getParameter(ID)); - } else if (method == Request.METHOD_PUT) { - response = put(request.getParameter(ID), request); - } else { - response = new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); + private CompletableFuture localAsync(final Request request, final String id) { + return CompletableFuture.supplyAsync(() -> local(request, id)); + } + + private HandleResult local(final Request request, final String id) { + final long currentTimeMillis = System.currentTimeMillis(); + switch (request.getMethod()) { + case Request.METHOD_GET -> { + final MemorySegment key = MemorySegment.ofArray(Utf8.toBytes(id)); + final ReferenceBaseEntry entry = dao.get(key); + if (entry == null) { + return new HandleResult(HttpURLConnection.HTTP_NOT_FOUND, Response.EMPTY); + } + if (entry.value() == null) { + return new HandleResult(HttpURLConnection.HTTP_NOT_FOUND, Response.EMPTY, entry.timestamp()); + } + return new HandleResult(HttpURLConnection.HTTP_OK, + entry.value().toArray(ValueLayout.JAVA_BYTE), entry.timestamp()); } - session.sendResponse(response); - } catch (IOException | InterruptedException e) { - try { - session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); - } catch (IOException e1) { - logger.info(e.getMessage()); + case Request.METHOD_PUT -> { + final MemorySegment key = MemorySegment.ofArray(Utf8.toBytes(id)); + final MemorySegment value = MemorySegment.ofArray(request.getBody()); + dao.upsert(new ReferenceBaseEntry<>(key, value, currentTimeMillis)); + return new HandleResult(HttpURLConnection.HTTP_CREATED, Response.EMPTY); + } + case Request.METHOD_DELETE -> { + final MemorySegment key = MemorySegment.ofArray(Utf8.toBytes(id)); + dao.upsert(new ReferenceBaseEntry<>(key, null, currentTimeMillis)); + return new HandleResult(HttpURLConnection.HTTP_ACCEPTED, Response.EMPTY); + } + default -> { + return new HandleResult(HttpURLConnection.HTTP_BAD_METHOD, Response.EMPTY); } - logger.info("IO or Interrupted exception in execution request: " + request + "\n"); - Thread.currentThread().interrupt(); } } - @Override - public void handleRequest(final Request request, final HttpSession session) throws IOException { - try { - requestExecutor.execute(() -> handleRequestTask(request, session)); - } catch (RejectedExecutionException e) { - logger.info("Execution has been rejected in request: " + request + "\n"); - session.sendError(Response.SERVICE_UNAVAILABLE, ""); + // count <= config.clusterUrls().size() + private int[] getIndexes(final String id, final int count) { + assert count < 5; + + int[] result = new int[count]; + int[] maxHashs = new int[count]; + + for (int i = 0; i < count; i++) { + String url = config.clusterUrls().get(i); + int hash = Hash.murmur3(url + id); + result[i] = i; + maxHashs[i] = hash; + } + + for (int i = count; i < config.clusterUrls().size(); i++) { + String url = config.clusterUrls().get(i); + int hash = Hash.murmur3(url + id); + for (int j = 0; j < maxHashs.length; j++) { + int maxHash = maxHashs[j]; + if (maxHash < hash) { + maxHashs[j] = hash; + result[j] = i; + break; + } + } } + return result; + } + + private interface ERunnable { + CompletableFuture run() throws IOException; } - void shutdownAndAwaitTermination(ExecutorService pool) { + private void shutdownAndAwaitTermination(ExecutorService pool) { try { if (!pool.awaitTermination(60, TimeUnit.MILLISECONDS)) { pool.shutdownNow(); if (!pool.awaitTermination(60, TimeUnit.MILLISECONDS)) { - logger.info("Pool did not terminate"); + log.info("Pool did not terminate"); } } } catch (InterruptedException ie) { @@ -208,7 +388,7 @@ void shutdownAndAwaitTermination(ExecutorService pool) { @Override public synchronized void stop() { super.stop(); - shutdownAndAwaitTermination(requestExecutor); - httpClient.close(); + shutdownAndAwaitTermination(executorLocal); + shutdownAndAwaitTermination(executorRemote); } } diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/ServiceFactoryImpl.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/ServiceFactoryImpl.java index 5b181552e..bcf8b6b21 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/ServiceFactoryImpl.java +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/ServiceFactoryImpl.java @@ -4,7 +4,7 @@ import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.test.ServiceFactory; -@ServiceFactory(stage = 3) +@ServiceFactory(stage = 6) public class ServiceFactoryImpl implements ServiceFactory.Factory { @Override diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/ServiceImpl.java index e9f1cc3ab..ff4eeeea4 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/ServiceImpl.java @@ -3,46 +3,56 @@ import ru.vk.itmo.Service; import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.dao.Config; +import ru.vk.itmo.test.ServiceFactory; import ru.vk.itmo.test.osipovdaniil.dao.ReferenceDao; import java.io.IOException; -import java.io.UncheckedIOException; import java.util.concurrent.CompletableFuture; public class ServiceImpl implements Service { - public static final long FLUSH_THRESHOLD_BYTES = 1024L * 1024; - private ServerImpl server; - private final ServiceConfig serviceConfig; - private ReferenceDao dao; + private static final long FLUSHING_THRESHOLD_BYTES = 1024 * 1024; - private final Config daoConfig; + private final ServiceConfig config; - public ServiceImpl(final ServiceConfig serviceConfig) { - this.serviceConfig = serviceConfig; - this.daoConfig = createDaoConfig(serviceConfig); - } + private ReferenceDao dao; + private ServerImpl server; + private boolean stopped; - private static Config createDaoConfig(final ServiceConfig config) { - return new Config(config.workingDir(), FLUSH_THRESHOLD_BYTES); + public ServiceImpl(ServiceConfig config) { + this.config = config; } @Override - public CompletableFuture start() throws IOException { - this.dao = new ReferenceDao(daoConfig); - this.server = new ServerImpl(serviceConfig, dao); + public synchronized CompletableFuture start() throws IOException { + dao = new ReferenceDao(new Config(config.workingDir(), FLUSHING_THRESHOLD_BYTES)); + server = new ServerImpl(config, dao); server.start(); + stopped = false; return CompletableFuture.completedFuture(null); } @Override - public CompletableFuture stop() { - server.stop(); + public synchronized CompletableFuture stop() throws IOException { + if (stopped) { + return CompletableFuture.completedFuture(null); + } try { + server.stop(); + + } finally { dao.close(); - } catch (IOException e) { - throw new UncheckedIOException(e); } + stopped = true; return CompletableFuture.completedFuture(null); } + + @ServiceFactory(stage = 4) + public static class Factory implements ServiceFactory.Factory { + + @Override + public Service create(ServiceConfig config) { + return new ServiceImpl(config); + } + } } diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/Utils.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/Utils.java new file mode 100644 index 000000000..970b8ccd1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/Utils.java @@ -0,0 +1,22 @@ +package ru.vk.itmo.test.osipovdaniil; + +import one.nio.http.HttpSession; +import one.nio.http.Response; +import org.slf4j.Logger; + +import java.io.IOException; + +public final class Utils { + + private Utils() { + } + + public static void sendEmptyInternal(final HttpSession session, final Logger logger) { + try { + session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); + } catch (IOException e) { + logger.error("Exception while sending close connection", e); + session.scheduleClose(); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/LiveFilteringIterator.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/LiveFilteringIterator.java index c30b99f54..dec39e11c 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/LiveFilteringIterator.java +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/LiveFilteringIterator.java @@ -11,18 +11,18 @@ * * @author incubos */ -final class LiveFilteringIterator implements Iterator> { - private final Iterator> delegate; - private Entry next; +final class LiveFilteringIterator implements Iterator> { + private final Iterator> delegate; + private ReferenceBaseEntry 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 ReferenceBaseEntry entry = delegate.next(); if (entry.value() != null) { this.next = entry; break; @@ -36,13 +36,13 @@ public boolean hasNext() { } @Override - public Entry next() { + public ReferenceBaseEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } // Consume - final Entry result = next; + final ReferenceBaseEntry result = next; next = null; skipTombstones(); diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/MemTable.java index aa38f2feb..e4a3b1724 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/MemTable.java +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/MemTable.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.osipovdaniil.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) { + ReferenceBaseEntry get(final MemorySegment key) { return map.get(key); } - Entry upsert(final Entry entry) { + ReferenceBaseEntry upsert(final ReferenceBaseEntry entry) { return map.put(entry.key(), entry); } } diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/MergingEntryIterator.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/MergingEntryIterator.java index 0d5343f39..0659679f2 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/MergingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/MergingEntryIterator.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.osipovdaniil.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 ReferenceBaseEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } final WeightedPeekingEntryIterator top = iterators.remove(); - final Entry result = top.next(); + final ReferenceBaseEntry 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 ReferenceBaseEntry 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/osipovdaniil/dao/ReferenceBaseEntry.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/ReferenceBaseEntry.java new file mode 100644 index 000000000..9e0a60a4e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/ReferenceBaseEntry.java @@ -0,0 +1,10 @@ +package ru.vk.itmo.test.osipovdaniil.dao; + +import ru.vk.itmo.dao.Entry; + +public record ReferenceBaseEntry(D key, D value, long timestamp) implements Entry { + @Override + public String toString() { + return "{" + key + ":" + value + "," + timestamp + "}"; + } +} diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/ReferenceDao.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/ReferenceDao.java index 5ebda7ae3..fdce16822 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/ReferenceDao.java +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/ReferenceDao.java @@ -22,7 +22,7 @@ * * @author incubos */ -public class ReferenceDao implements Dao> { +public class ReferenceDao implements Dao> { private final Config config; private final Arena arena; @@ -63,7 +63,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 +73,13 @@ public Iterator> get( } @Override - public Entry get(final MemorySegment key) { + public ReferenceBaseEntry 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 ReferenceBaseEntry entry) { final boolean autoFlush; lock.readLock().lock(); try { diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/SSTable.java index 1c08d2242..98176bedc 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/SSTable.java +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/SSTable.java @@ -1,8 +1,5 @@ package ru.vk.itmo.test.osipovdaniil.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; @@ -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) { + ReferenceBaseEntry get(final MemorySegment key) { final long entry = entryBinarySearch(key); if (entry < 0) { return null; @@ -143,20 +140,23 @@ Entry get(final MemorySegment key) { // Skip key (will reuse the argument) long offset = entryOffset(entry); offset += Long.BYTES + key.byteSize(); + // Extract timestamp + long timestamp = data.get(ValueLayout.OfLong.JAVA_LONG_UNALIGNED, offset); + offset += Long.BYTES; // Extract value length final long valueLength = getLength(offset); if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { // Tombstone encountered - return new BaseEntry<>(key, null); + return new ReferenceBaseEntry<>(key, null, timestamp); } else { // Get value offset += Long.BYTES; final MemorySegment value = data.asSlice(offset, valueLength); - return new BaseEntry<>(key, value); + return new ReferenceBaseEntry<>(key, value, timestamp); } } - private final class SliceIterator implements Iterator> { + private final class SliceIterator implements Iterator> { private long offset; private final long toOffset; @@ -173,7 +173,7 @@ public boolean hasNext() { } @Override - public Entry next() { + public ReferenceBaseEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } @@ -186,6 +186,10 @@ public Entry next() { final MemorySegment key = data.asSlice(offset, keyLength); offset += keyLength; + // Read timestamp + long timestamp = data.get(ValueLayout.OfLong.JAVA_LONG_UNALIGNED, offset); + offset += Long.BYTES; + // Read value length final long valueLength = getLength(offset); offset += Long.BYTES; @@ -193,11 +197,11 @@ public Entry next() { // Read value if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { // Tombstone encountered - return new BaseEntry<>(key, null); + return new ReferenceBaseEntry<>(key, null, timestamp); } else { final MemorySegment value = data.asSlice(offset, valueLength); offset += valueLength; - return new BaseEntry<>(key, value); + return new ReferenceBaseEntry<>(key, value, timestamp); } } } diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/SSTableWriter.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/SSTableWriter.java index 920a0bbb5..caa5f2b70 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/SSTableWriter.java +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/SSTableWriter.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.osipovdaniil.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 ReferenceBaseEntry} {@link Iterator} to SSTable on disk. * *

Index file {@code .index} contains {@code long} offsets to entries in data file: * {@code [offset0, offset1, ...]} @@ -40,7 +38,7 @@ final class SSTableWriter { void write( final Path baseDir, final int sequence, - final Iterator> entries) throws IOException { + final Iterator> entries) throws IOException { // Write to temporary files final Path tempIndexName = SSTables.tempIndexName(baseDir, sequence); final Path tempDataName = SSTables.tempDataName(baseDir, sequence); @@ -71,7 +69,7 @@ void write( writeLong(entryOffset, index); // Then write the entry - final Entry entry = entries.next(); + final ReferenceBaseEntry entry = entries.next(); entryOffset += writeEntry(entry, data); } } @@ -127,15 +125,16 @@ private void writeSegment( } /** - * Writes {@link Entry} to {@link FileChannel}. + * Writes {@link ReferenceBaseEntry} to {@link FileChannel}. * * @return written bytes */ private long writeEntry( - final Entry entry, + final ReferenceBaseEntry 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 @@ -146,6 +145,10 @@ private long writeEntry( writeSegment(key, os); result += key.byteSize(); + // timestamp + writeLong(timestamp, os); + result += Long.BYTES; + // Value size and possibly value if (value == null) { // Tombstone diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/SSTables.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/SSTables.java index 6aaeb776a..75b0adb6b 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/SSTables.java +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/SSTables.java @@ -51,7 +51,9 @@ static Path tempIndexName( return baseDir.resolve(sequence + INDEX_SUFFIX + TEMP_SUFFIX); } - static Path tempDataName(final Path baseDir, final int sequence) { + static Path tempDataName( + final Path baseDir, + final int sequence) { return baseDir.resolve(sequence + DATA_SUFFIX + TEMP_SUFFIX); } diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/TableSet.java index 98771eba5..b80b7b5c5 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/TableSet.java +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/TableSet.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.osipovdaniil.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; import java.util.ArrayList; import java.util.Collections; @@ -98,14 +96,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 +114,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 +127,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,14 +144,14 @@ Iterator> get( }; } - Entry get(final MemorySegment key) { + ReferenceBaseEntry get(final MemorySegment key) { // Slightly optimized version not to pollute the heap // First check MemTable - Entry result = memTable.get(key); + ReferenceBaseEntry result = memTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } // Then check flushing @@ -161,7 +159,7 @@ Entry get(final MemorySegment key) { result = flushingTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } } @@ -170,29 +168,24 @@ Entry get(final MemorySegment key) { result = ssTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } } - // Nothing found return null; } - private static Entry swallowTombstone(final Entry entry) { - return entry.value() == null ? null : entry; - } - - Entry upsert(final Entry entry) { + ReferenceBaseEntry upsert(final ReferenceBaseEntry 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( diff --git a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/WeightedPeekingEntryIterator.java b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/WeightedPeekingEntryIterator.java index 91f299d9c..08d3c9015 100644 --- a/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/WeightedPeekingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/osipovdaniil/dao/WeightedPeekingEntryIterator.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.osipovdaniil.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 ReferenceBaseEntry 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 ReferenceBaseEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } - final Entry result = next; + final ReferenceBaseEntry result = next; next = delegate.hasNext() ? delegate.next() : null; return result; } - Entry peek() { + ReferenceBaseEntry peek() { if (!hasNext()) { throw new NoSuchElementException(); } diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/Convertor.java b/src/main/java/ru/vk/itmo/test/pelogeikomakar/Convertor.java new file mode 100644 index 000000000..6f307444f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/Convertor.java @@ -0,0 +1,91 @@ +package ru.vk.itmo.test.pelogeikomakar; + +import org.slf4j.Logger; +import ru.vk.itmo.dao.BaseEntry; +import ru.vk.itmo.dao.Entry; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; + +public final class Convertor { + + private Convertor() { + throw new UnsupportedOperationException("cannot be instantiated"); + } + + public static byte[] addLongToArray(long encodedValue, byte[] given) { + int givenLength = 0; + long value = encodedValue; + if (given != null) { + givenLength = given.length; + } + byte[] result = new byte[Long.BYTES + givenLength]; + for (int i = 0; i < Long.BYTES; ++i) { + result[i] = (byte)(value & 0xFF); + value >>= Byte.SIZE; + } + + if (given == null) { + result[Long.BYTES - 1] = (byte)(result[Long.BYTES - 1] | 0x80); + } else { + System.arraycopy(given, 0, result, 8, given.length); + } + + return result; + } + + public static long getTimeStamp(MemorySegment segment) { + long time = segment.get(ValueLayout.JAVA_LONG_UNALIGNED, 0); + + if (((time >> (Byte.SIZE * 7)) & 0x80) != 0) { + time &= ~(1L << 63); + } + return time; + } + + public static boolean isValNull(MemorySegment segment) { + long time = segment.get(ValueLayout.JAVA_LONG_UNALIGNED, 0); + return ((time >> (Byte.SIZE * 7)) & 0x80) != 0; + } + + public static MemorySegment stringToMemorySegment(String str) { + return MemorySegment.ofArray(str.getBytes(StandardCharsets.UTF_8)); + } + + public static Entry requestToEntry(String key, byte[] value, long timeStamp) { + byte[] storedValue = addLongToArray(timeStamp, value); + return new BaseEntry<>(stringToMemorySegment(key), MemorySegment.ofArray(storedValue)); + } + + public static byte[] getValueNotNullAsBytes(MemorySegment segment) { + return segment.asSlice(Long.BYTES).toArray(ValueLayout.JAVA_BYTE); + } + + public static long longOfString(String inputString, long defaultValue, Logger log) { + long result = defaultValue; + if (inputString == null) { + return result; + } + String clearString = inputString.replaceAll("[^0-9]", ""); + try { + result = Long.parseLong(clearString); + } catch (NumberFormatException e) { + log.warn("Can not parse Log number", e); + } + return result; + } + + public static int intOfString(String inputString, int defaultValue, Logger log) { + int result = defaultValue; + if (inputString == null) { + return result; + } + try { + result = Integer.parseInt(inputString); + } catch (NumberFormatException e) { + log.warn("Can not parse Integer number", e); + } + return result; + } +} diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/DaoHttpServer.java b/src/main/java/ru/vk/itmo/test/pelogeikomakar/DaoHttpServer.java index 0a77e7f2d..6fff59337 100644 --- a/src/main/java/ru/vk/itmo/test/pelogeikomakar/DaoHttpServer.java +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/DaoHttpServer.java @@ -1,128 +1,237 @@ package ru.vk.itmo.test.pelogeikomakar; -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 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 java.io.IOException; import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; -import java.nio.charset.StandardCharsets; -import java.util.Set; +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.Optional; +import java.util.Queue; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; public class DaoHttpServer extends one.nio.http.HttpServer { private static final Logger log = LoggerFactory.getLogger(DaoHttpServer.class); - - private final ExecutorService executorService; + private final ExecutorService localExecutorService; + private final ExecutorService remoteExecutorService; private final Dao> dao; - private static final Set ALLOWED_METHODS = Set.of(Request.METHOD_GET, Request.METHOD_PUT, - Request.METHOD_DELETE); + private static final String TIME_HEADER = "X-VALUE_TIME"; + private static final String TIME_HEADER_ONENIO = TIME_HEADER + ": "; + private static final String INTERNAL_RQ_HEADER = "X-INTERNAL_RQ"; + private static final String NOT_REPLICAS_HEADER = "504 Not Enough Replicas"; + private final int defaultAck; + private final int defaultFrom; + private final List clusterUrls; + private final String selfUrl; + private final HttpClient httpClient; - public DaoHttpServer(ServiceConfig config, Dao> dao, - ExecutorService executorService) throws IOException { - super(createHttpServerConfig(config)); - this.dao = dao; - this.executorService = executorService; - } + public DaoHttpServer(ServiceConfig config, Dao> dao) throws IOException { + super(ServerUtils.createHttpServerConfig(config)); - private static HttpServerConfig createHttpServerConfig(ServiceConfig config) { - HttpServerConfig serverConfig = new HttpServerConfig(); - AcceptorConfig acceptorConfig = new AcceptorConfig(); - acceptorConfig.port = config.selfPort(); - acceptorConfig.reusePort = true; + this.clusterUrls = config.clusterUrls(); + this.selfUrl = config.selfUrl(); + this.httpClient = HttpClient.newBuilder() + .executor(ExecutorServiceFactory.newExecutorService("javaClientExecutor-", 512, 6)) + .connectTimeout(Duration.ofMillis(500)) + .version(HttpClient.Version.HTTP_1_1) + .build(); - serverConfig.acceptors = new AcceptorConfig[]{acceptorConfig}; - serverConfig.closeSessions = true; - return serverConfig; + defaultFrom = this.clusterUrls.size(); + defaultAck = defaultFrom / 2 + 1; + + this.dao = dao; + + localExecutorService = ExecutorServiceFactory.newExecutorService("localExecutor-", 512, 6); + remoteExecutorService = ExecutorServiceFactory.newExecutorService("remoteExecutor-", 512, 6); } public Dao> getDao() { return dao; } - public ExecutorService getExecutorService() { - return executorService; - } + private List> invokeAsyncAllRequests(String id, Request request, String[] urls, + long time) { - @Path("/v0/entity") - @RequestMethod(Request.METHOD_PUT) - public Response upsertDaoMethod(@Param(value = "id", required = false) String id, Request request) { + List> futureResponses = new ArrayList<>(urls.length); - if (id == null || id.isEmpty() || request.getBody() == null) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + for (String url : urls) { + Supplier supplier; + ExecutorService executor; + + if (url.equals(selfUrl)) { + supplier = () -> executeLocalMethod(id, request, time); + executor = localExecutorService; + } else { + supplier = () -> executeRemoteMethod(url, request, time); + executor = remoteExecutorService; + } + + Optional> optionalFuture = ServerUtils.addTask(supplier, executor, log); + optionalFuture.ifPresent(futureResponses::add); } - try { - dao.upsert(requestToEntry(id, request.getBody())); - } catch (IllegalStateException e) { - log.error("Exception during upsert (key: {})", id, e); - return new Response(Response.CONFLICT, Response.EMPTY); + return futureResponses; + } + + private void accumulateAndSendResults(List> responses, HttpSession session, + int currAck, int currFrom) { + + AtomicInteger doneResponses = new AtomicInteger(0); + AtomicInteger successResponses = new AtomicInteger(0); + AtomicBoolean queryProcessed = new AtomicBoolean(false); + Queue readyResponses = new ConcurrentLinkedQueue<>(); + + for (CompletableFuture future : responses) { + future.whenCompleteAsync((response, exeption) -> { + if (exeption == null && response.getStatus() < 500) { + successResponses.incrementAndGet(); + readyResponses.add(response); + } + + doneResponses.incrementAndGet(); + + if (successResponses.get() >= currAck + && queryProcessed.compareAndSet(false, true)) { + mergeAndSend(session, readyResponses); + return; + } + + if (doneResponses.get() == currFrom && successResponses.get() < currAck + && queryProcessed.compareAndSet(false, true)) { + sendResponseWithError(session, new Response(NOT_REPLICAS_HEADER, Response.EMPTY)); + } + }, localExecutorService) + .exceptionally(exception -> { + log.error("Error in future", exception); + return new Response(Response.INTERNAL_ERROR, Response.EMPTY); + }); } - return new Response(Response.CREATED, Response.EMPTY); } - @Path("/v0/entity") - @RequestMethod(Request.METHOD_GET) - public Response getDaoMethod(@Param(value = "id", required = false) String id) { - if (id == null || id.isEmpty()) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + private void mergeAndSend(HttpSession session, Iterable responses) { + long youngestTime = -2; + Response youngestResponse = responses.iterator().next(); + for (Response currentResp : responses) { + long curTime = Convertor.longOfString(currentResp.getHeader(TIME_HEADER_ONENIO), + -1, log); + if (youngestTime == -2 || youngestTime < curTime) { + youngestTime = curTime; + youngestResponse = currentResp; + } } - Entry result = dao.get(stringToMemorySegment(id)); + sendResponseWithError(session, youngestResponse); + } - if (result == null) { - return new Response(Response.NOT_FOUND, Response.EMPTY); + private void sendResponseWithError(HttpSession session, Response response) { + try { + session.sendResponse(response); + } catch (IOException ex) { + log.error("IOException while sendResponse", ex); + session.scheduleClose(); } - - return Response.ok(memorySegmentToBytes(result.value())); } - @Path("/v0/entity") - @RequestMethod(Request.METHOD_DELETE) - public Response deleteDaoMethod(@Param(value = "id", required = false) String id) { + private Response executeLocalMethod(String id, Request request, long timeSt) { + switch (request.getMethod()) { + case Request.METHOD_GET: + Entry result = dao.get(Convertor.stringToMemorySegment(id)); + if (result == null) { + return new Response("404", Response.EMPTY); - if (id == null || id.isEmpty()) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + } else if (Convertor.isValNull(result.value())) { + Response response = new Response("404", Response.EMPTY); + long time = Convertor.getTimeStamp(result.value()); + response.addHeader(TIME_HEADER_ONENIO + time); + return response; + + } else { + long time = Convertor.getTimeStamp(result.value()); + Response response = new Response("200", + Convertor.getValueNotNullAsBytes(result.value())); + response.addHeader(TIME_HEADER_ONENIO + time); + return response; + } + + case Request.METHOD_PUT: + try { + dao.upsert(Convertor.requestToEntry(id, request.getBody(), timeSt)); + } catch (IllegalStateException e) { + log.error("Exception during upsert (key: {})", id, e); + return new Response("409", Response.EMPTY); + } + + Response putResp = new Response("201", Response.EMPTY); + putResp.addHeader(TIME_HEADER_ONENIO + timeSt); + return putResp; + + case Request.METHOD_DELETE: + try { + dao.upsert(Convertor.requestToEntry(id, null, timeSt)); + } catch (IllegalStateException e) { + log.error("Exception during delete-upsert", e); + return new Response("409", Response.EMPTY); + } + + Response deleteResp = new Response("202", Response.EMPTY); + deleteResp.addHeader(TIME_HEADER_ONENIO + timeSt); + return deleteResp; + + default: + return new Response("405", Response.EMPTY); } + } + + private Response executeRemoteMethod(String executorNode, Request request, long givenTime) { + HttpRequest httpRequest = ServerUtils.buildHttpRequest(executorNode, request, givenTime, + INTERNAL_RQ_HEADER, TIME_HEADER); try { - dao.upsert(requestToEntry(id, null)); - } catch (IllegalStateException e) { - log.error("Exception during delete-upsert", e); - return new Response(Response.CONFLICT, Response.EMPTY); - } + HttpResponse httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray()); + Response response = new Response(Integer.toString(httpResponse.statusCode()), httpResponse.body()); + + Optional timeStamp = httpResponse.headers().firstValue(TIME_HEADER); + timeStamp.ifPresent(s -> response.addHeader(TIME_HEADER_ONENIO + s)); + + return response; - return new Response(Response.ACCEPTED, Response.EMPTY); + } catch (IOException e) { + log.error("Error in internal request", e); + return new Response(Response.BAD_GATEWAY, Response.EMPTY); + } catch (InterruptedException e) { + log.error("Error in internal request (INTERRUPTION)", e); + Thread.currentThread().interrupt(); + return new Response(Response.BAD_GATEWAY, Response.EMPTY); + } } - @Override - public void handleRequest(Request request, HttpSession session) throws IOException { + private void applyRequestToExecutor(HttpSession session, ExecutorService executor, + Callable method) throws IOException { try { - executorService.execute(() -> { + executor.execute(() -> { try { - super.handleRequest(request, session); + session.sendResponse(method.call()); } catch (Exception e) { log.error("Exception during handleRequest", e); - try { - session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); - } catch (IOException ex) { - log.error("IOException while sendResponse ExecServer", ex); - session.scheduleClose(); - } + sendResponseWithError(session, new Response(Response.INTERNAL_ERROR, Response.EMPTY)); } }); } catch (RejectedExecutionException e) { @@ -132,26 +241,54 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti } @Override - public void handleDefault(Request request, HttpSession session) throws IOException { + public void handleRequest(Request request, HttpSession session) throws IOException { - Response response; - if (ALLOWED_METHODS.contains(request.getMethod())) { - response = new Response(Response.BAD_REQUEST, Response.EMPTY); - } else { - response = new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); + if (!"/v0/entity".equals(request.getPath())) { + session.sendError(Response.BAD_REQUEST, null); + return; } - session.sendResponse(response); - } - private MemorySegment stringToMemorySegment(String str) { - return MemorySegment.ofArray(str.getBytes(StandardCharsets.UTF_8)); + String id = request.getParameter("id="); + if (id == null || id.isBlank()) { + session.sendError(Response.BAD_REQUEST, null); + return; + } + + long time = System.currentTimeMillis(); + + if (request.getHeader(INTERNAL_RQ_HEADER) == null) { + // Request from outside + int currAck = Convertor.intOfString(request.getParameter("ack="), defaultAck, log); + int currFrom = Convertor.intOfString(request.getParameter("from="), defaultFrom, log); + + if (currFrom > clusterUrls.size() || currAck > currFrom || currAck < 1) { + session.sendError(Response.BAD_REQUEST, null); + return; + } + + String[] urls = ServerUtils.getServerUrlsForKey(id, currFrom, clusterUrls); + List> responses = invokeAsyncAllRequests(id, request, urls, time); + accumulateAndSendResults(responses, session, currAck, currFrom); + + } else { + // Redirected request + time = Convertor.longOfString(request.getHeader(TIME_HEADER_ONENIO), time, log); + + final long finalTime = time; + applyRequestToExecutor(session, localExecutorService, () -> executeLocalMethod(id, request, finalTime)); + } } - private Entry requestToEntry(String key, byte[] value) { - return new BaseEntry<>(stringToMemorySegment(key), value == null ? null : MemorySegment.ofArray(value)); + @Override + public synchronized void start() { + log.info("start server on url: {}", selfUrl); + super.start(); } - private byte[] memorySegmentToBytes(MemorySegment segment) { - return segment.toArray(ValueLayout.JAVA_BYTE); + @Override + public synchronized void stop() { + super.stop(); + ServiceImpl.shutdownAndAwaitTermination(localExecutorService); + ServiceImpl.shutdownAndAwaitTermination(remoteExecutorService); } } diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/ExecutorServiceFactory.java b/src/main/java/ru/vk/itmo/test/pelogeikomakar/ExecutorServiceFactory.java index 8e2be9400..324a4f1b8 100644 --- a/src/main/java/ru/vk/itmo/test/pelogeikomakar/ExecutorServiceFactory.java +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/ExecutorServiceFactory.java @@ -9,7 +9,7 @@ import java.util.concurrent.atomic.AtomicInteger; public final class ExecutorServiceFactory { - private static final int CORE_POOL_SIZE = 20; + private static final int CORE_POOL_SIZE = 6; private static final long KEEP_ALIVE_TIME_SEC = 2; private static final TimeUnit UNIT = TimeUnit.SECONDS; @@ -20,16 +20,20 @@ private ExecutorServiceFactory() { throw new UnsupportedOperationException("cannot be instantiated"); } - public static ExecutorService newExecutorService() { + public static ExecutorService newExecutorService(String threadPrefix) { + return newExecutorService(threadPrefix, QUEUE_CAPACITY, CORE_POOL_SIZE); + } - BlockingQueue queue = new ArrayBlockingQueue<>(QUEUE_CAPACITY); - ThreadPoolExecutor tpe = new ThreadPoolExecutor(CORE_POOL_SIZE, CORE_POOL_SIZE, + public static ExecutorService newExecutorService(String threadPrefix, + int queuCapasity, int corePoolSize) { + BlockingQueue queue = new ArrayBlockingQueue<>(queuCapasity); + ThreadPoolExecutor tpe = new ThreadPoolExecutor(corePoolSize, corePoolSize, KEEP_ALIVE_TIME_SEC, UNIT, queue, new ThreadFactory() { private final AtomicInteger id = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { - return new Thread(r, "ExecutorServiceThread-" + id.incrementAndGet()); + return new Thread(r, threadPrefix + id.incrementAndGet()); } }, new ThreadPoolExecutor.AbortPolicy()); tpe.prestartAllCoreThreads(); diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/MainServ.java b/src/main/java/ru/vk/itmo/test/pelogeikomakar/MainServ.java index 27f562b83..a579e1bd0 100644 --- a/src/main/java/ru/vk/itmo/test/pelogeikomakar/MainServ.java +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/MainServ.java @@ -7,8 +7,8 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ExecutorService; public final class MainServ { @@ -18,15 +18,25 @@ private MainServ() { public static void main(String[] args) throws IOException { Path basePath = Path.of("/media/user/DATA/projects/gitproj/DWS-ITMO-2023/sem_2/dao_content"); - Path daoPath = Files.createTempDirectory(basePath, "tmpServ"); - ServiceConfig serviceConfig = new ServiceConfig(8080, "http://localhost", List.of("http://localhost"), daoPath); - Config daoConfig = new Config(daoPath, 160_384L); - - ExecutorService execServ = ExecutorServiceFactory.newExecutorService(); - - DaoHttpServer server = new DaoHttpServer(serviceConfig, new ReferenceDaoPel(daoConfig), execServ); - - server.start(); + List ports = List.of(8070, 8080, 8090); + List clusterUrls = new ArrayList<>(3); + for (int port : ports) { + clusterUrls.add("http://localhost:" + port); + } + + List configs = new ArrayList<>(3); + + for (int i = 0; i < ports.size(); ++i) { + Path daoPath = Files.createTempDirectory(basePath, "tmpServ_" + i); + ServiceConfig serviceConfig = new ServiceConfig(ports.get(i), clusterUrls.get(i), clusterUrls, daoPath); + configs.add(serviceConfig); + } + + for (ServiceConfig config: configs) { + Config daoConfig = new Config(config.workingDir(), 1_048_576L); + DaoHttpServer server = new DaoHttpServer(config, new ReferenceDaoPel(daoConfig)); + server.start(); + } } } diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/ServerUtils.java b/src/main/java/ru/vk/itmo/test/pelogeikomakar/ServerUtils.java new file mode 100644 index 000000000..47bfeaba5 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/ServerUtils.java @@ -0,0 +1,86 @@ +package ru.vk.itmo.test.pelogeikomakar; + +import one.nio.http.HttpServerConfig; +import one.nio.http.Request; +import one.nio.http.Response; +import one.nio.server.AcceptorConfig; +import one.nio.util.Hash; +import org.slf4j.Logger; +import ru.vk.itmo.ServiceConfig; +import java.net.URI; +import java.net.http.HttpRequest; +import java.time.Duration; +import java.util.List; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.function.Supplier; + +public final class ServerUtils { + + private ServerUtils() { + throw new UnsupportedOperationException("cannot be instantiated"); + } + + public static String[] getServerUrlsForKey(String key, int from, List clusterUrls) { + NavigableMap allCandidates = new TreeMap<>(); + + for (String url : clusterUrls) { + int currentHash = Hash.murmur3(url + key); + allCandidates.put(currentHash, url); + } + + String[] resultUrls = new String[from]; + + for (int i = 0; i < from; i++) { + resultUrls[i] = allCandidates.pollLastEntry().getValue(); + } + + return resultUrls; + } + + public static HttpServerConfig createHttpServerConfig(ServiceConfig config) { + HttpServerConfig serverConfig = new HttpServerConfig(); + AcceptorConfig acceptorConfig = new AcceptorConfig(); + acceptorConfig.port = config.selfPort(); + acceptorConfig.reusePort = true; + + serverConfig.acceptors = new AcceptorConfig[]{acceptorConfig}; + serverConfig.closeSessions = true; + return serverConfig; + } + + public static Optional> addTask(Supplier supplier, + ExecutorService executor, Logger log) { + CompletableFuture futureResponse = null; + try { + futureResponse = CompletableFuture.supplyAsync(supplier, executor); + } catch (RejectedExecutionException e) { + log.info("not enough queue size while adding task", e); + } + + return Optional.ofNullable(futureResponse); + } + + public static HttpRequest buildHttpRequest(String executorNode, Request request, long givenTime, + String internalRedirect, String timeHeader) { + HttpRequest.Builder httpRequestBuilder = HttpRequest.newBuilder(URI.create(executorNode + request.getURI())) + .method( + request.getMethodName(), + request.getBody() == null + ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofByteArray(request.getBody()) + ) + .header(internalRedirect, "true") + .timeout(Duration.ofSeconds(1)); + + if (givenTime >= 0) { + httpRequestBuilder = httpRequestBuilder.header(timeHeader, Long.toString(givenTime)); + } + + return httpRequestBuilder.build(); + } +} diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/pelogeikomakar/ServiceImpl.java index c833e6ccd..9a89551d5 100644 --- a/src/main/java/ru/vk/itmo/test/pelogeikomakar/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/ServiceImpl.java @@ -15,38 +15,41 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; public class ServiceImpl implements Service { private final Config daoConfig; private final ServiceConfig serviceConfig; private DaoHttpServer server; + private final AtomicBoolean isStopped = new AtomicBoolean(false); private static final Logger log = LoggerFactory.getLogger(ServiceImpl.class); public ServiceImpl(ServiceConfig config) { - daoConfig = new Config(config.workingDir(), 160_384L); + daoConfig = new Config(config.workingDir(), 1_048_576L); serviceConfig = config; } @Override public CompletableFuture start() throws IOException { - + isStopped.set(false); Dao> dao = new ReferenceDaoPel(daoConfig); - server = new DaoHttpServer(serviceConfig, dao, ExecutorServiceFactory.newExecutorService()); + server = new DaoHttpServer(serviceConfig, dao); server.start(); return CompletableFuture.completedFuture(null); } @Override public CompletableFuture stop() throws IOException { - server.stop(); - shutdownAndAwaitTermination(server.getExecutorService()); - server.getDao().close(); + if (!isStopped.getAndSet(true)) { + server.stop(); + server.getDao().close(); + } return CompletableFuture.completedFuture(null); } - private static void shutdownAndAwaitTermination(ExecutorService pool) { + public static void shutdownAndAwaitTermination(ExecutorService pool) { pool.shutdown(); try { if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { @@ -61,7 +64,7 @@ private static void shutdownAndAwaitTermination(ExecutorService pool) { } } - @ServiceFactory(stage = 2) + @ServiceFactory(stage = 5) public static class Factory implements ServiceFactory.Factory { @Override diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/get_req.lua b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/get_req.lua deleted file mode 100644 index b13b730c3..000000000 --- a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/get_req.lua +++ /dev/null @@ -1,8 +0,0 @@ -id = 0 -function request() - headers = {} - headers["Host"] = "localhost:8080" - reqPath = "/v0/entity?id=" .. id - id = id + 1 - return wrk.format("GET", reqPath, headers) -end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/lua/get_random_req.lua b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/lua/get_random_req.lua index 3add870f7..8cd7f8405 100644 --- a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/lua/get_random_req.lua +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/lua/get_random_req.lua @@ -2,6 +2,6 @@ math.randomseed(42) function request() headers = {} headers["Host"] = "localhost:8080" - reqPath = "/v0/entity?id=" .. math.random(0, 350000) + reqPath = "/v0/entity?id=" .. math.random(0, 371000) return wrk.format("GET", reqPath, headers) end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/put_req.lua b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/put_req.lua deleted file mode 100644 index 7ea1657e7..000000000 --- a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/put_req.lua +++ /dev/null @@ -1,9 +0,0 @@ -id = 0 -function request() - headers = {} - headers["Host"] = "localhost:8080" - reqPath = "/v0/entity?id=" .. id - valStr = id .. "abracadabra" .. id - id = id + 1 - return wrk.format("PUT", reqPath, headers, valStr) -end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-GET-t1c120rps-35000-alloc.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-GET-t1c120rps-35000-alloc.html new file mode 100644 index 000000000..6f685f32b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-GET-t1c120rps-35000-alloc.html @@ -0,0 +1,556 @@ + + + + + + + +

GET-t1c120rps-35000-ALLOC

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-GET-t1c120rps-35000-cpu.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-GET-t1c120rps-35000-cpu.html new file mode 100644 index 000000000..bccb34ad7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-GET-t1c120rps-35000-cpu.html @@ -0,0 +1,3198 @@ + + + + + + + +

GET-t1c120rps-35000-CPU

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-GET-t1c120rps-35000-lock.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-GET-t1c120rps-35000-lock.html new file mode 100644 index 000000000..883d9b9af --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-GET-t1c120rps-35000-lock.html @@ -0,0 +1,346 @@ + + + + + + + +

GET-t1c120rps-35000-LOCK

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-PUT-t1c120rps-50000-alloc.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-PUT-t1c120rps-50000-alloc.html new file mode 100644 index 000000000..5d20a92a9 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-PUT-t1c120rps-50000-alloc.html @@ -0,0 +1,594 @@ + + + + + + + +

PUT-t1c120rps-50000-ALLOC

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-PUT-t1c120rps-50000-cpu.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-PUT-t1c120rps-50000-cpu.html new file mode 100644 index 000000000..de0a41803 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-PUT-t1c120rps-50000-cpu.html @@ -0,0 +1,4018 @@ + + + + + + + +

PUT-t1c120rps-50000-CPU

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-PUT-t1c120rps-50000-lock.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-PUT-t1c120rps-50000-lock.html new file mode 100644 index 000000000..3247b9c67 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/data/profile3-PUT-t1c120rps-50000-lock.html @@ -0,0 +1,388 @@ + + + + + + + +

PUT-t1c120rps-50000-LOCK

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/stage3.md b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/stage3.md new file mode 100644 index 000000000..fd72a3a56 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_3/stage3.md @@ -0,0 +1,271 @@ +# stage 3 +Используется для `wrk2`: 1 поток, 120 подключений, 30 секунд; порог для `flush` ~1Мб; 3 кластера. + +## PUT + +Максимальная задержка ~10ms получилась при ~10 000 RPS. +На CPU flame graph видно "плато" у метода `DaoHttpServer.upsertDaoMethod`. +После изменения количетсва потоков в ThreadPoolExecutor с 20 на 6 (6 * 3 = 18 ~= 20) latency изменилась при 12 000 RPS: +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.42ms -> 2.06ms + 75.000% 3.22ms -> 2.61ms + 90.000% 4.08ms -> 3.15ms + 99.000% 5.99ms -> 4.37ms + 99.900% 8.26ms -> 7.39ms + 99.990% 11.14ms -> 8.77ms + 99.999% 13.02ms -> 9.35ms +100.000% 14.60ms -> 9.38ms +``` + +Далее была убрана блокировка для доступа к HTTP Client'а, +убрано создание неиспользуемых объектов. + +Задержка меняется при RPS + +12 000 -> 20 000 -> 30 000 -> 35 000 -> 40 000 -> 45 000 -> 50 000 -> 60 000: +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.00ms -> 1.34ms -> 1.38ms -> 1.36ms -> 1.40ms -> 1.42ms -> 1.40ms -> 1.42ms + 75.000% 2.59ms -> 1.87ms -> 1.90ms -> 1.88ms -> 1.95ms -> 1.97ms -> 1.97ms -> 2.01ms + 90.000% 3.23ms -> 2.39ms -> 2.38ms -> 2.39ms -> 2.62ms -> 2.57ms -> 2.66ms -> 2.94ms + 99.000% 4.43ms -> 3.71ms -> 4.81ms -> 5.93ms -> 6.12ms -> 7.89ms -> 8.22ms -> 9.22ms + 99.900% 6.60ms -> 6.92ms -> 7.23ms -> 8.61ms -> 9.37ms -> 12.97ms -> 13.05ms -> 13.68ms + 99.990% 7.92ms -> 8.48ms -> 9.53ms -> 10.89ms -> 12.35ms -> 15.16ms -> 15.86ms -> 17.28ms + 99.999% 8.53ms -> 9.18ms -> 12.03ms -> 11.87ms -> 13.77ms -> 18.75ms -> 18.80ms -> 19.55ms +100.000% 8.94ms -> 9.70ms -> 12.85ms -> 13.51ms -> 15.41ms -> 20.51ms -> 19.28ms -> 21.22ms +``` + +Для RPS 70 000, 80 000, 90 000 и 100 000 видно, что задержка начала увеличиваться быстрее, +так же разница между 90 и 99 перцентилями увиеличилась: +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.41ms -> 1.37ms -> 1.39ms -> 1.60ms + 75.000% 2.04ms -> 2.05ms -> 2.10ms -> 2.96ms + 90.000% 3.63ms -> 4.35ms -> 4.85ms -> 9.35ms + 99.000% 13.99ms -> 12.47ms -> 16.05ms -> 22.74ms + 99.900% 20.22ms -> 18.00ms -> 22.61ms -> 33.57ms + 99.990% 22.64ms -> 28.22ms -> 25.14ms -> 42.56ms + 99.999% 26.06ms -> 31.60ms -> 29.18ms -> 46.91ms +100.000% 27.31ms -> 32.24ms -> 30.32ms -> 48.67ms +``` + +При увеличении времени нагрузки до 1 минуты при 50 000, 60 000 RPS: +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.41ms -> 1.43ms + 75.000% 1.97ms -> 2.02ms + 90.000% 2.61ms -> 2.86ms + 99.000% 7.10ms -> 9.52ms + 99.900% 13.40ms -> 15.85ms + 99.990% 18.32ms -> 19.06ms + 99.999% 22.91ms -> 22.30ms +100.000% 24.48ms -> 26.85ms +``` +При 70 000 RPS в течении минуты уже не каждый раз получается задержка меньше 30ms. +Таким образом, было выбрано 60 000 RPS. +```shell +wrk -d 60 -t 1 -c 120 -R 60000 -L -s ./lua/put_req.lua http://127.0.0.1:8080 +``` + + +### async-profiler + +```shell +wrk -d 60 -t 1 -c 120 -R 50000 -L -s ./lua/put_req.lua http://127.0.0.1:8080 +``` +```shell +cd /opt/async-profiler-3.0-linux-x64 +export PATH=$PATH:/opt/async-profiler-3.0-linux-x64 +export JAVA_HOME=/home/user/.jdks/openjdk-21.0.1 +export PATH=$JAVA_HOME/bin:$PATH + +source ~/.bashrc + +sudo sysctl kernel.perf_event_paranoid=1 +sudo sysctl kernel.kptr_restrict=0 + +jps -m +``` + +```shell +STAGE=3 +METHOD_NAME=PUT +TITLE_PREF=${METHOD_NAME}-t1c120rps-50000 +DATA_DIR="/media/user/DATA/projects/gitproj/DWS-ITMO-2023/sem_2/2024-highload-dht/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_${STAGE}/data" +JFR_FILENAME="profile${STAGE}-${TITLE_PREF}.jfr" + + +./bin/asprof --title ${TITLE_PREF} -e cpu,alloc,lock -d 30 -f ${DATA_DIR}/${JFR_FILENAME} MainServ + +java -cp lib/converter.jar jfr2flame --alloc --title ${TITLE_PREF}-ALLOC ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-alloc.html" +java -cp lib/converter.jar jfr2flame --lock --title ${TITLE_PREF}-LOCK ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-lock.html" +java -cp lib/converter.jar jfr2flame --title ${TITLE_PREF}-CPU ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-cpu.html" +``` + +### Flame graphs +[CPU-PUT flame graph](data/profile3-PUT-t1c120rps-50000-cpu.html) + +[Allocation-PUT flame graph](data/profile3-PUT-t1c120rps-50000-alloc.html) + +[Lock-PUT flame graph](data/profile3-PUT-t1c120rps-50000-lock.html) + + +
+ +--------------------------------------------------- +
+ +## GET +Сначала были вставлены ~3 710 000 записей. + +Используется скрипт с рандомной генерацией ключа (get_random_req.lua). + +RPS 10 000 -> 20 000 -> 30 000 -> 40 000 -> 50 000: +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.42ms -> 1.48ms -> 1.55ms -> 1.60ms -> 1.77ms + 75.000% 1.94ms -> 2.07ms -> 2.14ms -> 2.28ms -> 2.66ms + 90.000% 2.51ms -> 2.61ms -> 2.73ms -> 3.02ms -> 4.20ms + 99.000% 3.28ms -> 3.55ms -> 4.05ms -> 5.88ms -> 9.83ms + 99.900% 3.92ms -> 4.64ms -> 5.45ms -> 8.67ms -> 14.10ms + 99.990% 5.52ms -> 6.28ms -> 6.54ms -> 12.49ms -> 18.58ms + 99.999% 6.41ms -> 6.86ms -> 8.15ms -> 14.87ms -> 21.18ms +100.000% 6.53ms -> 7.14ms -> 8.65ms -> 15.75ms -> 22.24ms +``` + +При увеличении нагрузки до минуты RPS 30 000 -> 40 000 -> 50 000: +```text + 50.000% 1.55ms -> 1.59ms -> 1.80ms + 75.000% 2.12ms -> 2.23ms -> 2.74ms + 90.000% 2.71ms -> 2.91ms -> 4.35ms + 99.000% 4.12ms -> 5.66ms -> 10.01ms + 99.900% 6.01ms -> 7.97ms -> 14.18ms + 99.990% 7.53ms -> 9.60ms -> 17.09ms + 99.999% 8.89ms -> 10.96ms -> 19.38ms +100.000% 10.59ms -> 11.98ms -> 22.35ms +``` + +Поэтому было выбрано 40 000 RPS. + +### async-profiler + +```shell +wrk -d 60 -t 1 -c 120 -R 35000 -L -s ./lua/get_random_req.lua http://127.0.0.1:8080 +``` +```shell +STAGE=3 +METHOD_NAME=GET +TITLE_PREF=${METHOD_NAME}-t1c120rps-35000 +DATA_DIR="/media/user/DATA/projects/gitproj/DWS-ITMO-2023/sem_2/2024-highload-dht/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_${STAGE}/data" +JFR_FILENAME="profile${STAGE}-${TITLE_PREF}.jfr" + + +./bin/asprof --title ${TITLE_PREF} -e cpu,alloc,lock -d 30 -f ${DATA_DIR}/${JFR_FILENAME} MainServ + +java -cp lib/converter.jar jfr2flame --alloc --title ${TITLE_PREF}-ALLOC ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-alloc.html" +java -cp lib/converter.jar jfr2flame --lock --title ${TITLE_PREF}-LOCK ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-lock.html" +java -cp lib/converter.jar jfr2flame --title ${TITLE_PREF}-CPU ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-cpu.html" +``` + +### Flame graphs + +[CPU-GET flame graph](data/profile3-GET-t1c120rps-35000-cpu.html) + +[Allocation-GET flame graph](data/profile3-GET-t1c120rps-35000-alloc.html) + +[Lock-GET flame graph](data/profile3-GET-t1c120rps-35000-lock.html) + +
+ +--------------------------------------------------- +
+ +## Вывод + +- у GET и PUT запросов примерно одинаковое количество процентов занимают аллокации, +связанные с `HttpClient`. +- Точка разладки сместилась вправо для GET и PUT запросов, это связано с тем, +что дополнительный HTTP запрос требует много ресурсов. +- Я ожидал, что на cpu flame graph у PUT запросов будет больше семплов, +связанных с отправкой запроса на другую ноду, так как надо писать в сокет тело запроса; +действительно отправка запросов на другую ноду у PUT заняла больше процентов, чем у GET, +но разница весьма мала (тело запроса тоже). +- Блокировки у GET и PUT запросов тоже примерно одинаковы. + + +
+ +--------------------------------------------------- +
+ +## Исправление для stage 2 +Используется для `wrk2`: 1 поток, 120 подключений, 30 секунд; порог для `flush` ~1Мб. + +### Найдем точку разладки для PUT запросов +При RPS = 125 000 получены перцентили: +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.27ms + 75.000% 1.65ms + 90.000% 2.00ms + 99.000% 2.91ms + 99.900% 7.78ms + 99.990% 9.21ms + 99.999% 10.13ms +100.000% 10.57ms +``` + +При RPS = 130 000, если попробовать нагрузить несколько раз, +то можно 1 - 2 раза получить задержку меньше 10ms на 100 перцентили. +При RPS = 140 000 видно, что сервер уже не справляется: +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.40ms + 75.000% 1.88ms + 90.000% 2.56ms + 99.000% 53.15ms + 99.900% 64.10ms + 99.990% 75.46ms + 99.999% 78.14ms +100.000% 78.46ms +``` +Таким образом, было выбрано значение RPS = 125 000: +```shell +wrk -d 30 -t 1 -c 120 -R 125000 -L -s ./lua/put_req.lua http://127.0.0.1:8080 +``` + +### Найдем точку разладки для GET запросов +Сначала были вставлены ~3 710 000 записей. + +Используется скрипт с рандомной генерацией ключа (`get_random_req.lua`), так как в stage 3 будет использоваться именно этот способ нагрузки. + +При RPS = 40 000 получены перцентили: +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.53ms + 75.000% 2.12ms + 90.000% 2.75ms + 99.000% 4.72ms + 99.900% 7.31ms + 99.990% 8.85ms + 99.999% 9.81ms +100.000% 10.44ms +``` + +При 60 000 RPS уже начинается быстрый рост задержки: +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.03ms + 75.000% 3.68ms + 90.000% 5.68ms + 99.000% 12.91ms + 99.900% 17.52ms + 99.990% 21.26ms + 99.999% 25.10ms +100.000% 26.22ms +``` +Таким образом, для GET запросов было выбрано 40 000 RPS +```shell +wrk -d 30 -t 1 -c 120 -R 40000 -L -s ./lua/get_random_req.lua http://127.0.0.1:8080 +``` \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-GET-t1c10rps-1000-alloc.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-GET-t1c10rps-1000-alloc.html new file mode 100644 index 000000000..a4e6ea9b2 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-GET-t1c10rps-1000-alloc.html @@ -0,0 +1,687 @@ + + + + + + + +

GET-t1c10rps-1000-ALLOC

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-GET-t1c10rps-1000-cpu.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-GET-t1c10rps-1000-cpu.html new file mode 100644 index 000000000..cd5b5fb9c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-GET-t1c10rps-1000-cpu.html @@ -0,0 +1,4040 @@ + + + + + + + +

GET-t1c10rps-1000-CPU

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-GET-t1c10rps-1000-lock.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-GET-t1c10rps-1000-lock.html new file mode 100644 index 000000000..8a8296723 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-GET-t1c10rps-1000-lock.html @@ -0,0 +1,352 @@ + + + + + + + +

GET-t1c10rps-1000-LOCK

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-PUT-t1c10rps-1000-alloc.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-PUT-t1c10rps-1000-alloc.html new file mode 100644 index 000000000..5b67be304 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-PUT-t1c10rps-1000-alloc.html @@ -0,0 +1,719 @@ + + + + + + + +

PUT-t1c10rps-1000-ALLOC

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-PUT-t1c10rps-1000-cpu.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-PUT-t1c10rps-1000-cpu.html new file mode 100644 index 000000000..98a3f3c22 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-PUT-t1c10rps-1000-cpu.html @@ -0,0 +1,2973 @@ + + + + + + + +

PUT-t1c10rps-1000-CPU

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-PUT-t1c10rps-1000-lock.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-PUT-t1c10rps-1000-lock.html new file mode 100644 index 000000000..f5bd3c049 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/data/profile4-PUT-t1c10rps-1000-lock.html @@ -0,0 +1,401 @@ + + + + + + + +

PUT-t1c10rps-1000-LOCK

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/stage4.md b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/stage4.md new file mode 100644 index 000000000..862b9be44 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_4/stage4.md @@ -0,0 +1,141 @@ +# stage 4 + +## PUT + +По неизвестной причине wrk2 Выдавал следующий результат: + +```text +Running 10s test @ http://127.0.0.1:8080 + 1 threads and 1 connections + Thread calibration: mean lat.: 9223372036854776.000ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency -nanus -nanus 0.00us 0.00% + Req/Sec -nan -nan 0.00 0.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 0.00us + 75.000% 0.00us + 90.000% 0.00us + 99.000% 0.00us + 99.900% 0.00us + 99.990% 0.00us + 99.999% 0.00us +100.000% 0.00us + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.000 1.000000 0 inf +#[Mean = -nan, StdDeviation = -nan] +#[Max = 0.000, Total count = 0] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 0 requests in 10.00s, 0.00B read + Socket errors: connect 0, read 92848, write 0, timeout 0 +Requests/sec: 0.00 +Transfer/sec: 0.00B +``` +```shell +wrk -d 10 -t 1 -c 1 -R 1 -L -s ./lua/put_req.lua http://127.0.0.1:8080 +``` + +В GitHab [wrk2](https://github.com/giltene/wrk2) сказано, что стандартное значение +R (requests per second) это 1000. Я не нашел информации о том, что нельзя задавать +параметр R = 1 (меньше 1000). При попытке запуска wrk2 с значениями `-d 10 -t 1 -c 1 -R 1 ` +ожидается ~10 запросов за 10 секунд, но судя по логам и информации в базе, было совершено более 8 000 запросов. + +### async-profiler + +```shell +STAGE=4 +METHOD_NAME=PUT +TITLE_PREF=${METHOD_NAME}-t1c10rps-1000 +DATA_DIR="/media/user/DATA/projects/gitproj/DWS-ITMO-2023/sem_2/2024-highload-dht/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_${STAGE}/data" +JFR_FILENAME="profile${STAGE}-${TITLE_PREF}.jfr" + + +./bin/asprof --title ${TITLE_PREF} -e cpu,alloc,lock -d 30 -f ${DATA_DIR}/${JFR_FILENAME} MainServ + +java -cp lib/converter.jar jfr2flame --alloc --title ${TITLE_PREF}-ALLOC ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-alloc.html" +java -cp lib/converter.jar jfr2flame --lock --title ${TITLE_PREF}-LOCK ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-lock.html" +java -cp lib/converter.jar jfr2flame --title ${TITLE_PREF}-CPU ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-cpu.html" +``` + +### Flame graphs +[CPU-PUT flame graph](data/profile4-PUT-t1c10rps-1000-cpu.html) + +[Allocation-PUT flame graph](data/profile4-PUT-t1c10rps-1000-alloc.html) + +[Lock-PUT flame graph](data/profile4-PUT-t1c10rps-1000-lock.html) + + +
+ +--------------------------------------------------- +
+ +## GET + +Данные +```shell +wrk -d 40 -t 1 -c 120 -R 10000 -L -s ./lua/put_req.lua http://127.0.0.1:8080 +``` + +Запросы +```shell +wrk -d 40 -t 1 -c 120 -R 1000 -L -s ./lua/get_random_req.lua http://127.0.0.1:8080 +``` + +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 14.25s + 75.000% 25.10s + 90.000% 25.10s + 99.000% 25.10s + 99.900% 25.10s + 99.990% 25.10s + 99.999% 25.10s +100.000% 25.10s +``` + +При изменении количества подключений 1 -> 10 -> 50 -> 100 -> 120 четких изменений выявлено не было. +Видно, что сервер не справляется +### async-profiler + +```shell +STAGE=4 +METHOD_NAME=GET +TITLE_PREF=${METHOD_NAME}-t1c10rps-1000 +DATA_DIR="/media/user/DATA/projects/gitproj/DWS-ITMO-2023/sem_2/2024-highload-dht/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_${STAGE}/data" +JFR_FILENAME="profile${STAGE}-${TITLE_PREF}.jfr" + + +./bin/asprof --title ${TITLE_PREF} -e cpu,alloc,lock -d 30 -f ${DATA_DIR}/${JFR_FILENAME} MainServ + +java -cp lib/converter.jar jfr2flame --alloc --title ${TITLE_PREF}-ALLOC ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-alloc.html" +java -cp lib/converter.jar jfr2flame --lock --title ${TITLE_PREF}-LOCK ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-lock.html" +java -cp lib/converter.jar jfr2flame --title ${TITLE_PREF}-CPU ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-cpu.html" +``` + +### Flame graphs + +[CPU-GET flame graph](data/profile4-GET-t1c10rps-1000-cpu.html) + +[Allocation-GET flame graph](data/profile4-GET-t1c10rps-1000-alloc.html) + +[Lock-GET flame graph](data/profile4-GET-t1c10rps-1000-lock.html) + +
+ +--------------------------------------------------- +
+ +## Вывод + +- Точка разладки сместилась влево (уменьшилась) для GET и PUT запросов причем очень сильно (RPS менее 1000). +Появилось 3 дополнительных последовательных запроса (ack = 2, from = 3), которые требуют "дорогие" +операции записи в сокет и "дорогие" операции поиска (для GET запросов). Возможно, если распараллелить запросы +на реплики, то точка разладки сместиться вправо (увеличится). +- То, что wrk "не может" выдавать меньше 1000 RPS оказалось удивительным открытием. +- Думал, что добавление времени (long) к value в Entry будет сильно негативано сказываться, но, +исходя из flame grahs, видно, что на преобразование тратиться мало ресурсов +(меньше 0.5% аллокаций и меньше 0.5% семплов CPU). \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6000-alloc.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6000-alloc.html new file mode 100644 index 000000000..1bb70e426 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6000-alloc.html @@ -0,0 +1,3230 @@ + + + + + + + +

GET-t1c64rps-6000-ALLOC

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6000-cpu.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6000-cpu.html new file mode 100644 index 000000000..19070527b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6000-cpu.html @@ -0,0 +1,8826 @@ + + + + + + + +

GET-t1c64rps-6000-CPU

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6000-lock.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6000-lock.html new file mode 100644 index 000000000..53c83ac56 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6000-lock.html @@ -0,0 +1,1016 @@ + + + + + + + +

GET-t1c64rps-6000-LOCK

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6100-alloc.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6100-alloc.html new file mode 100644 index 000000000..f318a1abf --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6100-alloc.html @@ -0,0 +1,3244 @@ + + + + + + + +

GET-t1c64rps-6100-ALLOC

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6100-cpu.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6100-cpu.html new file mode 100644 index 000000000..e31597f71 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6100-cpu.html @@ -0,0 +1,8931 @@ + + + + + + + +

GET-t1c64rps-6100-CPU

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6100-lock.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6100-lock.html new file mode 100644 index 000000000..3cc281ffa --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-GET-t1c64rps-6100-lock.html @@ -0,0 +1,1045 @@ + + + + + + + +

GET-t1c64rps-6100-LOCK

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-PUT-t1c64rps-5000-alloc.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-PUT-t1c64rps-5000-alloc.html new file mode 100644 index 000000000..e9874428c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-PUT-t1c64rps-5000-alloc.html @@ -0,0 +1,3298 @@ + + + + + + + +

PUT-t1c64rps-5000-ALLOC

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-PUT-t1c64rps-5000-cpu.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-PUT-t1c64rps-5000-cpu.html new file mode 100644 index 000000000..5cf6df64b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-PUT-t1c64rps-5000-cpu.html @@ -0,0 +1,9456 @@ + + + + + + + +

PUT-t1c64rps-5000-CPU

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-PUT-t1c64rps-5000-lock.html b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-PUT-t1c64rps-5000-lock.html new file mode 100644 index 000000000..5c3e2e0b8 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/data/profile5-PUT-t1c64rps-5000-lock.html @@ -0,0 +1,1012 @@ + + + + + + + +

PUT-t1c64rps-5000-LOCK

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/stage5.md b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/stage5.md new file mode 100644 index 000000000..2ea3f2c07 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_5/stage5.md @@ -0,0 +1,208 @@ +# stage 5 + +## PUT + +```shell +wrk -d 30 -t 1 -c 64 -R 5000 -L -s ./lua/put_req.lua http://127.0.0.1:8080 +``` +При 5000 RPS, 64 подключениях получились следующие результаты: + +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.42ms + 75.000% 1.98ms + 90.000% 2.49ms + 99.000% 5.20ms + 99.900% 8.07ms + 99.990% 10.38ms + 99.999% 10.95ms +100.000% 12.20ms +----------------------- +Requests/sec: 4974.31 +``` + +Была расширена очередь для пулов, поскольку при тестировании появлялись ошибки `RejectedExecutionException`. + +При RPS 5000 и изменении количесва подключений 1 -> 10 -> 20 -> 40 -> 80 -> 120 +получены реузльтаты: + +```text + Latency Distribution (HdrHistogram - Recorded Latency) + cons 1 -> 10 -> 20 -> 40 -> 80 -> 120 + 50.000% 1.08ms -> 1.34ms -> 1.45ms -> 1.43ms -> 1.43ms -> 1.41ms + 75.000% 1.89ms -> 1.91ms -> 2.01ms -> 1.98ms -> 2.01ms -> 1.96ms + 90.000% 4.95ms -> 2.40ms -> 2.58ms -> 2.54ms -> 2.59ms -> 2.56ms + 99.000% 15.66ms -> 5.39ms -> 5.45ms -> 5.20ms -> 5.29ms -> 5.20ms + 99.900% 17.87ms -> 7.94ms -> 7.70ms -> 7.78ms -> 7.70ms -> 7.68ms + 99.990% 18.37ms -> 10.52ms -> 8.70ms -> 9.02ms -> 9.36ms -> 9.80ms + 99.999% 18.42ms -> 12.41ms -> 9.62ms -> 9.65ms -> 10.31ms -> 10.53ms +100.000% 18.42ms -> 12.41ms -> 9.62ms -> 9.65ms -> 10.31ms -> 10.53ms +------------------------------------------------------------------------- +RPS: 4997.15 -> 4988.35 -> 4976.17 -> 4952.39 -> 4904.31 -> 4856.38 +``` + +Видно, что фактический RPS падает, при этом latency (100 перцентиль) сначала уменьшается, а +потом постепенно увеличивается. Предположу что есть влияние самого wrk и то, что все-таки +подключения пересоздаются, что увеличивает нагрузку. + + +### async-profiler + +```shell +STAGE=5 +METHOD_NAME=PUT +TITLE_PREF=${METHOD_NAME}-t1c64rps-5000 +DATA_DIR="/media/user/DATA/projects/gitproj/DWS-ITMO-2023/sem_2/2024-highload-dht/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_${STAGE}/data" +JFR_FILENAME="profile${STAGE}-${TITLE_PREF}.jfr" + + +./bin/asprof --title ${TITLE_PREF} -e cpu,alloc,lock -d 30 -f ${DATA_DIR}/${JFR_FILENAME} MainServ + +java -cp lib/converter.jar jfr2flame --alloc --title ${TITLE_PREF}-ALLOC ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-alloc.html" +java -cp lib/converter.jar jfr2flame --lock --title ${TITLE_PREF}-LOCK ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-lock.html" +java -cp lib/converter.jar jfr2flame --title ${TITLE_PREF}-CPU ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-cpu.html" +``` + +### Flame graphs +[CPU-PUT flame graph](data/profile5-PUT-t1c64rps-5000-cpu.html) + +[Allocation-PUT flame graph](data/profile5-PUT-t1c64rps-5000-alloc.html) + +[Lock-PUT flame graph](data/profile5-PUT-t1c64rps-5000-lock.html) + +На flame graph видно, что аллокаций связанных с отправкой запроса на реплику стало меньше +по сравнению с `stage4` (~45% против ~55%), но суммарно аллокаций, связанных +с `http.client` стало больше. Это связано с +переходом на `java.net.http.HttpClient`. + +Профиль локов изменился по сравнению со `stage4`: раньше были только локи сервера +и пула потоков (точнее очереди), теперь добавились локи `java.net.http.HttpClient` +для получения ответа из send/sendAsync и внутренние локи `java.net.http.HttpClient` + +Большая часть CPU сэмплов отводится обработке запроса и http клиенту и серверу, +на работу с Dao потрачено ~2% сэмплов. + +
+ +--------------------------------------------------- +
+ +## GET + +Данные +```shell +wrk -d 38 -t 1 -c 64 -R 10000 -L -s ./lua/put_req.lua http://127.0.0.1:8080 +``` +и +```shell +wrk -d 160 -t 1 -c 64 -R 10000 -L -s ./lua/put_req.lua http://127.0.0.1:8080 +``` +
+ +При наличии ~371 000 уникальных ключей RPS 6000 и 64 подключениях получились следующие результаты: +```shell +wrk -d 10 -t 1 -c 64 -R 6000 -L -s ./lua/get_random_req.lua http://127.0.0.1:8080 +``` + +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.45ms + 75.000% 1.98ms + 90.000% 2.46ms + 99.000% 3.29ms + 99.900% 4.31ms + 99.990% 5.35ms + 99.999% 5.64ms +100.000% 6.05ms +------------------ +Requests/sec: 5907.26 +``` + +При наличии ~1 600 000 уникальных ключей RPS 6000 и 64 подключениях получились следующие результаты: +```shell +wrk -d 10 -t 1 -c 64 -R 6000 -L -s ./lua/get_random_req.lua http://127.0.0.1:8080 +``` + +```text + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.66ms + 75.000% 2.29ms + 90.000% 2.88ms + 99.000% 4.21ms + 99.900% 5.97ms + 99.990% 6.82ms + 99.999% 7.36ms +100.000% 7.53ms +------------------ +Requests/sec: 5906.82 +``` + + +### async-profiler + +При количестве ключей: ~371 000 +```shell +STAGE=5 +METHOD_NAME=GET +TITLE_PREF=${METHOD_NAME}-t1c64rps-6000 +DATA_DIR="/media/user/DATA/projects/gitproj/DWS-ITMO-2023/sem_2/2024-highload-dht/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_${STAGE}/data" +JFR_FILENAME="profile${STAGE}-${TITLE_PREF}.jfr" + + +./bin/asprof --title ${TITLE_PREF} -e cpu,alloc,lock -d 30 -f ${DATA_DIR}/${JFR_FILENAME} MainServ + +java -cp lib/converter.jar jfr2flame --alloc --title ${TITLE_PREF}-ALLOC ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-alloc.html" +java -cp lib/converter.jar jfr2flame --lock --title ${TITLE_PREF}-LOCK ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-lock.html" +java -cp lib/converter.jar jfr2flame --title ${TITLE_PREF}-CPU ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-cpu.html" +``` + +При количестве ключей: ~1 600 000 +```shell +STAGE=5 +METHOD_NAME=GET +TITLE_PREF=${METHOD_NAME}-t1c64rps-6100 +DATA_DIR="/media/user/DATA/projects/gitproj/DWS-ITMO-2023/sem_2/2024-highload-dht/src/main/java/ru/vk/itmo/test/pelogeikomakar/load_tests/stage_${STAGE}/data" +JFR_FILENAME="profile${STAGE}-${TITLE_PREF}.jfr" + + +./bin/asprof --title ${TITLE_PREF} -e cpu,alloc,lock -d 30 -f ${DATA_DIR}/${JFR_FILENAME} MainServ + +java -cp lib/converter.jar jfr2flame --alloc --title ${TITLE_PREF}-ALLOC ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-alloc.html" +java -cp lib/converter.jar jfr2flame --lock --title ${TITLE_PREF}-LOCK ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-lock.html" +java -cp lib/converter.jar jfr2flame --title ${TITLE_PREF}-CPU ${DATA_DIR}/${JFR_FILENAME} > "${DATA_DIR}/profile${STAGE}-${TITLE_PREF}-cpu.html" +``` + +### Flame graphs + +[CPU-GET flame graph 371k](data/profile5-GET-t1c64rps-6000-cpu.html) + +[Allocation-GET flame graph 371k](data/profile5-GET-t1c64rps-6000-alloc.html) + +[Lock-GET flame graph 371k](data/profile5-GET-t1c64rps-6000-lock.html) + +[CPU-GET flame graph 1600k](data/profile5-GET-t1c64rps-6100-cpu.html) + +[Allocation-GET flame graph 1600k](data/profile5-GET-t1c64rps-6100-alloc.html) + +[Lock-GET flame graph 1600k](data/profile5-GET-t1c64rps-6100-lock.html) + +
+ +При увеличении количесва уникальных ключей количество сэмплов CPU на +поиск ключа в локальном хранилище вырасло с ~7% до ~18%. Большая часть CPU теперь +тратиться на отправку запросов между репликами по сравнению с предыдущим этапом. Это связано +с переходом на `java.net.http.HttpClient`. + +Практически все аналогично тому, что видно при `PUT` запросах, кроме того, +что работа с Dao занимет больше сэмплов в процентном соотношении. + +--------------------------------------------------- +
+ +## Заключение + +- Точка разладки вправо (увеличилась) для GET и PUT запросов причем очень сильно (~5000 RPS). +Это связано с распараллеливанием запросов и отправкой ответа при наличии кворума +(то есть при выполнении 2х запросов из 3х) +- Разница в RPS между `GET` и `PUT` запросами сократилась. Это связано с тем, что +большая часть работы уходит на http клиент/сервер и обработку самих запросов \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/CustomHttpSession.java b/src/main/java/ru/vk/itmo/test/proninvalentin/CustomHttpSession.java new file mode 100644 index 000000000..9c4dc0139 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/CustomHttpSession.java @@ -0,0 +1,47 @@ +package ru.vk.itmo.test.proninvalentin; + +import one.nio.http.HttpServer; +import one.nio.http.HttpSession; +import one.nio.http.Response; +import one.nio.net.Socket; +import ru.vk.itmo.test.proninvalentin.streaming.StreamingResponse; + +import java.io.IOException; + +public class CustomHttpSession extends HttpSession { + public CustomHttpSession(Socket socket, HttpServer server) { + super(socket, server); + } + + public void safetySendResponse(Response response) { + try { + if (response instanceof StreamingResponse streamingResponse) { + sendStreamResponse(streamingResponse); + } else { + super.sendResponse(response); + } + } catch (IOException e) { + log.error("Error while sending response", e); + scheduleClose(); + } + } + + private void sendStreamResponse(StreamingResponse response) { + try { + if (response.remaining() < 1) { + safetySendResponse(new Response(Response.OK, Response.EMPTY)); + return; + } + + response.start(socket); + while (response.remaining() > 0) { + response.writePart(socket); + } + response.finish(socket); + close(); + } catch (IOException e) { + log.error("Error while sending response", e); + scheduleClose(); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/RequestHandler.java b/src/main/java/ru/vk/itmo/test/proninvalentin/RequestHandler.java new file mode 100644 index 000000000..2a1ad68ae --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/RequestHandler.java @@ -0,0 +1,80 @@ +package ru.vk.itmo.test.proninvalentin; + +import one.nio.http.Param; +import one.nio.http.Request; +import one.nio.http.Response; +import ru.vk.itmo.test.proninvalentin.dao.ExtendedBaseEntry; +import ru.vk.itmo.test.proninvalentin.dao.ExtendedEntry; +import ru.vk.itmo.test.proninvalentin.dao.ReferenceDao; +import ru.vk.itmo.test.proninvalentin.request_parameter.RangeRequestParameters; +import ru.vk.itmo.test.proninvalentin.streaming.StreamingResponse; +import ru.vk.itmo.test.proninvalentin.utils.Constants; +import ru.vk.itmo.test.proninvalentin.utils.MemorySegmentFactory; +import ru.vk.itmo.test.proninvalentin.utils.Utils; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; + +public class RequestHandler { + private final ReferenceDao dao; + + private static final String HTTP_TIMESTAMP_HEADER = Constants.HTTP_TIMESTAMP_HEADER + ":"; + + public RequestHandler(ReferenceDao dao) { + this.dao = dao; + } + + public Response handle(Request request, String entryId) { + return switch (request.getMethod()) { + case Request.METHOD_PUT -> upsert(entryId, request); + case Request.METHOD_GET -> get(entryId); + case Request.METHOD_DELETE -> delete(entryId); + default -> new Response(Response.BAD_REQUEST, Response.EMPTY); + }; + } + + public Response upsert(@Param(value = "id", required = true) String id, Request request) { + if (Utils.isNullOrBlank(id) || request.getBody() == null) { + return new Response(Response.BAD_REQUEST, Response.EMPTY); + } + + MemorySegment key = MemorySegmentFactory.fromString(id); + MemorySegment value = MemorySegment.ofArray(request.getBody()); + dao.upsert(new ExtendedBaseEntry<>(key, value, System.currentTimeMillis())); + return new Response(Response.CREATED, Response.EMPTY); + } + + public Response get(@Param(required = true, value = "id") String id) { + if (Utils.isNullOrBlank(id)) { + return new Response(Response.BAD_REQUEST, Response.EMPTY); + } + + MemorySegment key = MemorySegmentFactory.fromString(id); + ExtendedEntry entry = dao.get(key); + + if (entry == null || entry.value() == null) { + Response response = new Response(Response.NOT_FOUND, Response.EMPTY); + response.addHeader(HTTP_TIMESTAMP_HEADER + (entry != null ? entry.timestamp() : 0)); + return response; + } + + Response response = new Response(Response.OK, MemorySegmentFactory.toByteArray(entry.value())); + response.addHeader(HTTP_TIMESTAMP_HEADER + entry.timestamp()); + return response; + } + + public Response delete(@Param(required = true, value = "id") String id) { + if (Utils.isNullOrBlank(id)) { + return new Response(Response.BAD_REQUEST, Response.EMPTY); + } + ExtendedEntry deletedMemorySegment = + MemorySegmentFactory.toDeletedMemorySegment(id, System.currentTimeMillis()); + dao.upsert(deletedMemorySegment); + return new Response(Response.ACCEPTED, Response.EMPTY); + } + + public StreamingResponse getRange(RangeRequestParameters params) { + Iterator> entryIterator = dao.get(params.start(), params.end()); + return new StreamingResponse(entryIterator); + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/Server.java b/src/main/java/ru/vk/itmo/test/proninvalentin/Server.java index 9faca2079..44fb01def 100644 --- a/src/main/java/ru/vk/itmo/test/proninvalentin/Server.java +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/Server.java @@ -1,266 +1,300 @@ package ru.vk.itmo.test.proninvalentin; -import one.nio.http.HttpException; 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.net.Socket; +import one.nio.server.RejectedSessionException; 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.proninvalentin.failure_limiter.FailureLimiter; +import ru.vk.itmo.test.proninvalentin.dao.ReferenceDao; +import ru.vk.itmo.test.proninvalentin.request_parameter.RangeRequestParameters; +import ru.vk.itmo.test.proninvalentin.request_parameter.RequestParameters; import ru.vk.itmo.test.proninvalentin.sharding.ShardingAlgorithm; +import ru.vk.itmo.test.proninvalentin.utils.Constants; +import ru.vk.itmo.test.proninvalentin.utils.Utils; import ru.vk.itmo.test.proninvalentin.workers.WorkerPool; -import ru.vk.itmo.test.reference.dao.ReferenceDao; import java.io.IOException; -import java.lang.foreign.MemorySegment; -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.Comparator; +import java.util.List; +import java.util.Map; import java.util.Objects; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; public class Server extends HttpServer { - public static final int SERVER_ERRORS = 500; - private final Dao> dao; private static final Logger logger = LoggerFactory.getLogger(Server.class); + public static final String FUTURE_RETURN_VALUE_IGNORED = "FutureReturnValueIgnored"; private final ExecutorService workerPool; private final ShardingAlgorithm shardingAlgorithm; private final HttpClient httpClient; - private final FailureLimiter failureLimiter; + private final RequestHandler requestHandler; + private final ScheduledExecutorService timeoutChecker = Executors.newSingleThreadScheduledExecutor(); private final String selfUrl; + private final List clusterUrls; private final long requestMaxTimeToTakeInWorkInMillis; private final long httpRequestTimeoutInMillis; - private static final String TOO_MANY_REQUESTS = "429 Too Many Requests"; - private static final String METHOD_ADDRESS = "/v0/entity"; - private static final String ID_PARAMETER_NAME = "id="; - private static final String REQUEST_PATH = METHOD_ADDRESS + "?" + ID_PARAMETER_NAME; public Server(ServiceConfig config, ReferenceDao dao, WorkerPool workerPool, - ShardingAlgorithm shardingAlgorithm, ServerConfig serverConfig, - FailureLimiter failureLimiter) + ShardingAlgorithm shardingAlgorithm, ServerConfig serverConfig) throws IOException { - super(createServerConfig(config)); + super(Utils.createServerConfig(config)); - this.dao = dao; this.shardingAlgorithm = shardingAlgorithm; this.workerPool = workerPool.pool; this.httpClient = HttpClient.newBuilder() - .executor(Executors.newFixedThreadPool(serverConfig.getMaxWorkersNumber())) + .executor(workerPool.pool) .build(); this.selfUrl = config.selfUrl(); + this.clusterUrls = config.clusterUrls(); this.requestMaxTimeToTakeInWorkInMillis = serverConfig.getRequestMaxTimeToTakeInWorkInMillis(); this.httpRequestTimeoutInMillis = serverConfig.getHttpRequestTimeoutInMillis(); - this.failureLimiter = failureLimiter; - } - - private static HttpServerConfig createServerConfig(ServiceConfig serviceConfig) { - HttpServerConfig serverConfig = new HttpServerConfig(); - AcceptorConfig acceptorConfig = new AcceptorConfig(); - acceptorConfig.port = serviceConfig.selfPort(); - acceptorConfig.reusePort = true; - - serverConfig.acceptors = new AcceptorConfig[]{acceptorConfig}; - serverConfig.closeSessions = true; - return serverConfig; - } - - private static void sendResponse(HttpSession session, Response response) { - try { - session.sendResponse(response); - } catch (IOException e) { - logger.error("Error while sending response", e); - } - } - - private static void sendExceptionInfo(HttpSession session, Exception exception) { - try { - String responseCode; - if (exception.getClass().equals(TimeoutException.class)) { - responseCode = Response.REQUEST_TIMEOUT; - } else { - responseCode = Response.INTERNAL_ERROR; - } - session.sendResponse(new Response(responseCode, Response.EMPTY)); - } catch (IOException e) { - logger.error("Error while sending exception info", e); - } - } - - @Override - public void handleDefault(Request request, HttpSession session) { - int httpMethod = request.getMethod(); - Response response = Utils.isSupportedMethod(httpMethod) - ? new Response(Response.BAD_REQUEST, Response.EMPTY) - : new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); - sendResponse(session, response); + this.requestHandler = new RequestHandler(dao); } @Override - public void handleRequest(Request request, HttpSession session) { + public void handleRequest(Request request, HttpSession classicSession) { + if (!(classicSession instanceof CustomHttpSession session)) { + throw new IllegalArgumentException("this method support only SafetyHttpSession"); + } try { long createdAt = System.currentTimeMillis(); workerPool.execute(() -> processRequest(request, session, createdAt)); } catch (RejectedExecutionException e) { logger.error("New request processing task cannot be scheduled for execution", e); - sendResponse(session, new Response(TOO_MANY_REQUESTS, Response.EMPTY)); + session.safetySendResponse(new Response(Constants.TOO_MANY_REQUESTS, Response.EMPTY)); } } - private void processRequest(Request request, HttpSession session, long createdAt) { + @SuppressWarnings(FUTURE_RETURN_VALUE_IGNORED) + private void processRequest(Request request, CustomHttpSession session, long createdAt) { boolean timeoutExpired = System.currentTimeMillis() - createdAt > requestMaxTimeToTakeInWorkInMillis; if (timeoutExpired) { - sendResponse(session, new Response(Response.REQUEST_TIMEOUT, Response.EMPTY)); + session.safetySendResponse(new Response(Response.REQUEST_TIMEOUT, Response.EMPTY)); return; } // To prevent sending unnecessary proxied requests to the nodes - if (!hasHandler(request)) { + if (!Utils.hasHandler(request) || !Utils.isSupportedMethod(request.getMethod())) { handleDefault(request, session); return; } - String parameter = request.getParameter(ID_PARAMETER_NAME); - if (Utils.isNullOrBlank(parameter)) { - sendResponse(session, new Response(Response.BAD_REQUEST, Response.EMPTY)); + if (Utils.isRangeRequest(request)) { + handleRangeRequest(request, session); + } else { + handleSingleRequest(request, session); + } + } + + private void handleRangeRequest(Request request, CustomHttpSession session) { + RangeRequestParameters params = new RangeRequestParameters( + request.getParameter(Constants.START_REQUEST_PARAMETER_NAME), + request.getParameter(Constants.END_REQUEST_PARAMETER_NAME) + ); + + if (!params.isValid()) { + handleDefault(request, session); return; } - try { - String nodeUrl = shardingAlgorithm.getNodeByKey(parameter); - if (Objects.equals(selfUrl, nodeUrl)) { - logger.debug("[%s] Process request: %s%s".formatted(selfUrl, request.getMethodName(), - request.getURI())); - super.handleRequest(request, session); - } else { - logger.debug("[%s] Send request to node [%s]: %s%s".formatted(selfUrl, nodeUrl, request.getMethodName(), - request.getURI())); - if (failureLimiter.readyForRequests(nodeUrl)) { - handleProxyRequest(request, session, nodeUrl, parameter); - } else { - logger.warn("[%s] Can't send request to closed node [%s]: %s%s".formatted(selfUrl, nodeUrl, - request.getMethodName(), request.getURI())); - sendResponse(session, new Response(Response.SERVICE_UNAVAILABLE, Response.EMPTY)); - } - } - } catch (Exception e) { - logger.error("Error while processing request", e); + session.safetySendResponse(requestHandler.getRange(params)); + } - String responseCode = e.getClass() == HttpException.class - ? Response.BAD_REQUEST - : Response.INTERNAL_ERROR; - sendResponse(session, new Response(responseCode, Response.EMPTY)); + @SuppressWarnings(FUTURE_RETURN_VALUE_IGNORED) + private void handleSingleRequest(Request request, CustomHttpSession session) { + RequestParameters params = new RequestParameters( + request.getParameter(Constants.ID_PARAMETER_NAME), + request.getParameter(Constants.FROM_PARAMETER_NAME), + request.getParameter(Constants.ACK_PARAMETER_NAME), + clusterUrls.size() + ); + + if (!params.isValid()) { + session.safetySendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); + return; + } + + if (request.getHeader(Constants.TERMINATION_HEADER) == null) { + CompletableFuture handleLeaderRequestFuture = handleLeaderRequest(request, params); + handleLeaderRequestFuture.whenComplete((response, throwable) -> session.safetySendResponse(response)); + checkForTimeout(handleLeaderRequestFuture); + } else { + session.safetySendResponse(safetyHandleRequest(request, params.key())); } } - private boolean hasHandler(Request request) { - return request.getURI().startsWith(REQUEST_PATH); + @Override + public void handleDefault(Request request, HttpSession sessionI) { + if (!(sessionI instanceof CustomHttpSession session)) { + throw new IllegalArgumentException("this method support only CustomHttpSession"); + } + + int httpMethod = request.getMethod(); + Response response = Utils.isSupportedMethod(httpMethod) + ? new Response(Response.BAD_REQUEST, Response.EMPTY) + : new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); + session.safetySendResponse(response); } - private void handleProxyRequest(Request request, HttpSession session, String nodeUrl, String parameter) { - byte[] body = request.getBody(); - HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(nodeUrl + REQUEST_PATH + parameter)) - .method(request.getMethodName(), body == null - ? HttpRequest.BodyPublishers.noBody() - : HttpRequest.BodyPublishers.ofByteArray(body)) - .build(); - sendProxyRequest(session, httpRequest, nodeUrl); + private static Response processProxyResponse(HttpResponse httpResponse) { + String statusCode = Utils.httpCodeMapping.getOrDefault(httpResponse.statusCode(), null); + if (statusCode == null) { + logger.error("The proxied node returned a response with an unexpected status: " + + httpResponse.statusCode()); + return new Response(Response.INTERNAL_ERROR, httpResponse.body()); + } else { + Response response = new Response(statusCode, httpResponse.body()); + long timestamp = httpResponse.headers() + .firstValueAsLong(Constants.HTTP_TIMESTAMP_HEADER).orElse(0); + response.addHeader(Constants.NIO_TIMESTAMP_HEADER + timestamp); + return response; + } } - private void sendProxyRequest(HttpSession session, HttpRequest httpRequest, String nodeUrl) { + private Response safetyHandleRequest(Request request, String entryId) { + logger.debug("[%s] Process request: %s %s".formatted(selfUrl, request.getMethodName(), + request.getURI())); try { - HttpResponse httpResponse = httpClient - .sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()) - .get(httpRequestTimeoutInMillis, TimeUnit.MILLISECONDS); - - String statusCode = Utils.httpCodeMapping.getOrDefault(httpResponse.statusCode(), null); - if (statusCode == null) { - logger.error("The proxied node returned a response with an unexpected status: " - + httpResponse.statusCode()); - sendResponse(session, new Response(Response.INTERNAL_ERROR, httpResponse.body())); - } else { - if (httpResponse.statusCode() >= SERVER_ERRORS) { - failureLimiter.handleFailure(nodeUrl); - } + return requestHandler.handle(request, entryId); + } catch (Exception e) { + logger.error("Error while processing request", e); + return new Response(Response.INTERNAL_ERROR, Response.EMPTY); + } + } - sendResponse(session, new Response(statusCode, httpResponse.body())); + @SuppressWarnings(FUTURE_RETURN_VALUE_IGNORED) + private void checkForTimeout(CompletableFuture requestFuture) { + timeoutChecker.schedule(() -> { + if (!requestFuture.isDone()) { + logger.error("Request timed out. Maximum processing time exceeded"); + requestFuture.complete(new Response(Response.REQUEST_TIMEOUT, Response.EMPTY)); } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - logger.error(e.getMessage()); - sendExceptionInfo(session, e); - failureLimiter.handleFailure(nodeUrl); - } catch (ExecutionException e) { - logger.error("Execution exception while processing the httpRequest", e); - sendExceptionInfo(session, e); - failureLimiter.handleFailure(nodeUrl); - } catch (TimeoutException e) { - logger.error("Request timed out. Maximum processing time exceeded", e); - sendExceptionInfo(session, e); - failureLimiter.handleFailure(nodeUrl); - } + }, httpRequestTimeoutInMillis, TimeUnit.MILLISECONDS); } - // region Handlers + private CompletableFuture handleLeaderRequest(Request request, RequestParameters params) { + String entryId = params.key(); + int from = params.from(); + int ack = params.ack(); - @Path(METHOD_ADDRESS) - @RequestMethod(Request.METHOD_PUT) - public Response upsert(@Param(value = "id", required = true) String id, Request request) { - if (Utils.isNullOrBlank(id) || request.getBody() == null) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + logger.debug("[%s] Handle leader request from/ack %d/%d".formatted(selfUrl, from, ack)); + List nodeUrls = shardingAlgorithm.getNodesByKey(entryId, from); + Map requests = Utils.buildRequests(request, nodeUrls, entryId); + List> requestsFutures = getRequestsFutures(requests, nodeUrls); + + if (requests.get(selfUrl) != null) { + requestsFutures.add(safetyHandleRequestFuture(request, entryId)); } - MemorySegment key = MemorySegmentFactory.fromString(id); - MemorySegment value = MemorySegment.ofArray(request.getBody()); - dao.upsert(new BaseEntry<>(key, value)); - return new Response(Response.CREATED, Response.EMPTY); + return getWaitQuorumFuture(request, from, ack, requestsFutures); } - @Path(METHOD_ADDRESS) - @RequestMethod(Request.METHOD_GET) - public Response get(@Param(required = true, value = "id") String id) { - if (Utils.isNullOrBlank(id)) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + private List> getRequestsFutures(Map requests, + List nodeUrls) { + List> responses = new ArrayList<>(); + for (String nodeUrl : nodeUrls) { + HttpRequest request = requests.get(nodeUrl); + if (!Objects.equals(selfUrl, nodeUrl)) { + responses.add(sendRequestToProxyAsync(request, nodeUrl)); + } } + return responses; + } - MemorySegment key = MemorySegmentFactory.fromString(id); + @SuppressWarnings(FUTURE_RETURN_VALUE_IGNORED) + private CompletableFuture sendRequestToProxyAsync(HttpRequest httpRequest, String nodeUrl) { + logger.debug("[%s] Send request to node [%s]: %s %s".formatted(selfUrl, nodeUrl, httpRequest.method(), + httpRequest.uri())); + + final CompletableFuture sendRequestFuture = new CompletableFuture<>(); + httpClient + .sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()) + .whenComplete((httpResponse, throwable) -> { + if (throwable != null) { + logger.error(throwable.getMessage()); + sendRequestFuture.completeExceptionally(throwable); + return; + } + sendRequestFuture.complete(processProxyResponse(httpResponse)); + } + ); + + return sendRequestFuture; + } - Entry entry = dao.get(key); - if (entry == null) { - return new Response(Response.NOT_FOUND, Response.EMPTY); - } + @SuppressWarnings(FUTURE_RETURN_VALUE_IGNORED) + private CompletableFuture getWaitQuorumFuture(Request request, int from, int ack, + List> requestsFutures) { + List positiveResponses = new ArrayList<>(); + CompletableFuture waitQuorumFuture = new CompletableFuture<>(); + AtomicInteger remainingFailures = new AtomicInteger(from - ack + 1); + AtomicInteger remainingAcks = new AtomicInteger(ack); + + for (CompletableFuture requestFuture : requestsFutures) { + requestFuture.whenComplete((response, throwable) -> { + boolean positiveResponse = throwable == null + || (response != null && response.getStatus() < Constants.SERVER_ERRORS); + if (positiveResponse) { + remainingAcks.decrementAndGet(); + positiveResponses.add(response); + } else { + remainingFailures.decrementAndGet(); + } - byte[] value = MemorySegmentFactory.toByteArray(entry.value()); - return Response.ok(value); + logger.debug("[%s] Remaining acks = %d ; Remaining failures = %d" + .formatted(selfUrl, remainingAcks.get(), remainingFailures.get())); + if (remainingAcks.get() <= 0) { + mergeResponses(request, positiveResponses, waitQuorumFuture); + } else if (remainingFailures.get() == 0) { + waitQuorumFuture.complete(new Response(Constants.NOT_ENOUGH_REPLICAS, Response.EMPTY)); + } + }); + } + return waitQuorumFuture; } - @Path(METHOD_ADDRESS) - @RequestMethod(Request.METHOD_DELETE) - public Response delete(@Param(required = true, value = "id") String id) { - if (Utils.isNullOrBlank(id)) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); + private static void mergeResponses(Request request, List positiveResponses, + CompletableFuture waitQuorumFuture) { + if (request.getMethod() == Request.METHOD_GET) { + positiveResponses.sort(Comparator.comparingLong(r -> + Long.parseLong(r.getHeader(Constants.NIO_TIMESTAMP_HEADER)))); + waitQuorumFuture.complete(positiveResponses.getLast()); + } else { + waitQuorumFuture.complete(positiveResponses.getFirst()); } + } - Entry deletedMemorySegment = MemorySegmentFactory.toDeletedMemorySegment(id); - dao.upsert(deletedMemorySegment); - return new Response(Response.ACCEPTED, Response.EMPTY); + private CompletableFuture safetyHandleRequestFuture(Request request, String entryId) { + final CompletableFuture handleLocalRequestFuture = new CompletableFuture<>(); + workerPool.execute(() -> { + logger.debug("[%s] Process request: %s %s".formatted(selfUrl, request.getMethodName(), + request.getURI())); + try { + handleLocalRequestFuture.complete(requestHandler.handle(request, entryId)); + } catch (Exception e) { + logger.error("Error while processing request", e); + handleLocalRequestFuture.completeExceptionally(e); + } + }); + return handleLocalRequestFuture; } - // endregion + @Override + public HttpSession createSession(Socket socket) throws RejectedSessionException { + return new CustomHttpSession(socket, this); + } } diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/ServerConfig.java b/src/main/java/ru/vk/itmo/test/proninvalentin/ServerConfig.java index 94bd65c80..947a282c9 100644 --- a/src/main/java/ru/vk/itmo/test/proninvalentin/ServerConfig.java +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/ServerConfig.java @@ -28,7 +28,7 @@ public ServerConfig(int maxWorkersNumber, int requestMaxTimeToTakeInWork, int ht public static ServerConfig defaultConfig() { int workersNumber = Runtime.getRuntime().availableProcessors() * 2; int requestMaxTimeToTakeInWorkInMillis = 300; - int httpRequestTimeoutInMillis = 350; + int httpRequestTimeoutInMillis = 200; return new ServerConfig(workersNumber, requestMaxTimeToTakeInWorkInMillis, httpRequestTimeoutInMillis); } } diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/proninvalentin/ServiceImpl.java index 0ca0f012b..11ebf9db7 100644 --- a/src/main/java/ru/vk/itmo/test/proninvalentin/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/ServiceImpl.java @@ -6,14 +6,13 @@ import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.dao.Config; import ru.vk.itmo.test.ServiceFactory; -import ru.vk.itmo.test.proninvalentin.failure_limiter.FailureLimiter; -import ru.vk.itmo.test.proninvalentin.failure_limiter.FailureLimiterConfig; +import ru.vk.itmo.test.proninvalentin.dao.ReferenceDao; import ru.vk.itmo.test.proninvalentin.sharding.ConsistentHashing; import ru.vk.itmo.test.proninvalentin.sharding.ShardingAlgorithm; import ru.vk.itmo.test.proninvalentin.sharding.ShardingConfig; +import ru.vk.itmo.test.proninvalentin.utils.Utils; import ru.vk.itmo.test.proninvalentin.workers.WorkerPool; import ru.vk.itmo.test.proninvalentin.workers.WorkerPoolConfig; -import ru.vk.itmo.test.reference.dao.ReferenceDao; import java.io.IOException; import java.io.UncheckedIOException; @@ -47,10 +46,7 @@ public synchronized CompletableFuture start() throws IOException { ShardingConfig shardingConfig = ShardingConfig.defaultConfig(clusterUrls); ShardingAlgorithm shardingAlgorithm = new ConsistentHashing(shardingConfig); - FailureLimiterConfig failureLimiterConfig = FailureLimiterConfig.defaultConfig(clusterUrls); - FailureLimiter failureLimiter = new FailureLimiter(failureLimiterConfig); - - server = new Server(config, dao, workerPool, shardingAlgorithm, ServerConfig.defaultConfig(), failureLimiter); + server = new Server(config, dao, workerPool, shardingAlgorithm, ServerConfig.defaultConfig()); server.start(); serverAlreadyClosed = false; return CompletableFuture.completedFuture(null); @@ -75,7 +71,7 @@ public synchronized CompletableFuture stop() throws IOException { return CompletableFuture.completedFuture(null); } - @ServiceFactory(stage = 3) + @ServiceFactory(stage = 6) public static class Factory implements ServiceFactory.Factory { @Override public Service create(ServiceConfig config) { diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/StartServer.java b/src/main/java/ru/vk/itmo/test/proninvalentin/StartServer.java index 52e31fc31..49528a0ce 100644 --- a/src/main/java/ru/vk/itmo/test/proninvalentin/StartServer.java +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/StartServer.java @@ -4,14 +4,12 @@ import org.slf4j.LoggerFactory; import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.dao.Config; -import ru.vk.itmo.test.proninvalentin.failure_limiter.FailureLimiter; -import ru.vk.itmo.test.proninvalentin.failure_limiter.FailureLimiterConfig; +import ru.vk.itmo.test.proninvalentin.dao.ReferenceDao; import ru.vk.itmo.test.proninvalentin.sharding.ConsistentHashing; import ru.vk.itmo.test.proninvalentin.sharding.ShardingAlgorithm; import ru.vk.itmo.test.proninvalentin.sharding.ShardingConfig; import ru.vk.itmo.test.proninvalentin.workers.WorkerPool; import ru.vk.itmo.test.proninvalentin.workers.WorkerPoolConfig; -import ru.vk.itmo.test.reference.dao.ReferenceDao; import java.io.IOException; import java.nio.file.Files; @@ -46,9 +44,6 @@ public static void main(String[] args) throws IOException { ShardingConfig shardingConfig = ShardingConfig.defaultConfig(clusterUrls); ShardingAlgorithm shardingAlgorithm = new ConsistentHashing(shardingConfig); - FailureLimiterConfig failureLimiterConfig = FailureLimiterConfig.defaultConfig(clusterUrls); - FailureLimiter failureLimiter = new FailureLimiter(failureLimiterConfig); - for (int i = 0; i < clusterSize; i++) { int flushThresholdBytes = 1 << 20; // 1 MB Path profilingDataPath = Path.of( @@ -63,7 +58,7 @@ public static void main(String[] args) throws IOException { ServiceConfig serviceConfig = new ServiceConfig(ports[i], clusterUrls.get(i), clusterUrls, profilingDataPath); Server server = new Server(serviceConfig, referenceDao, workerPool, shardingAlgorithm, - ServerConfig.defaultConfig(), failureLimiter); + ServerConfig.defaultConfig()); server.start(); logger.info("[" + clusterUrls.get(i) + "] successfully started"); } diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/Utils.java b/src/main/java/ru/vk/itmo/test/proninvalentin/Utils.java deleted file mode 100644 index 72e7b0ad6..000000000 --- a/src/main/java/ru/vk/itmo/test/proninvalentin/Utils.java +++ /dev/null @@ -1,56 +0,0 @@ -package ru.vk.itmo.test.proninvalentin; - -import one.nio.http.Request; -import one.nio.http.Response; - -import java.net.HttpURLConnection; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; - -public final class Utils { - private static final int SOFT_SHUT_DOWN_TIME = 20; - private static final int HARD_SHUT_DOWN_TIME = 10; - private static final Set SUPPORTED_HTTP_METHODS = Set.of( - Request.METHOD_GET, - Request.METHOD_PUT, - Request.METHOD_DELETE - ); - public static final Map httpCodeMapping = Map.of( - HttpURLConnection.HTTP_OK, Response.OK, - HttpURLConnection.HTTP_ACCEPTED, Response.ACCEPTED, - HttpURLConnection.HTTP_CREATED, Response.CREATED, - HttpURLConnection.HTTP_BAD_REQUEST, Response.BAD_REQUEST, - HttpURLConnection.HTTP_INTERNAL_ERROR, Response.INTERNAL_ERROR, - HttpURLConnection.HTTP_NOT_FOUND, Response.NOT_FOUND, - HttpURLConnection.HTTP_CLIENT_TIMEOUT, Response.REQUEST_TIMEOUT - ); - - private Utils() { - } - - public static boolean isSupportedMethod(int httpMethod) { - return SUPPORTED_HTTP_METHODS.contains(httpMethod); - } - - public static void shutdownGracefully(ExecutorService pool) { - pool.shutdown(); - try { - pool.awaitTermination(SOFT_SHUT_DOWN_TIME, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - pool.shutdownNow(); - try { - pool.awaitTermination(HARD_SHUT_DOWN_TIME, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - public static boolean isNullOrBlank(String str) { - return str == null || str.isBlank(); - } -} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/WorkerPoolConfig.java b/src/main/java/ru/vk/itmo/test/proninvalentin/WorkerPoolConfig.java deleted file mode 100644 index 7b23f78be..000000000 --- a/src/main/java/ru/vk/itmo/test/proninvalentin/WorkerPoolConfig.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.vk.itmo.test.proninvalentin; - -import one.nio.async.CustomThreadFactory; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -public class WorkerPoolConfig { - private static final int MAX_QUEUE_SIZE = 256; - public final int corePoolSize; - public final int maxPoolSize; - public final long keepAliveTime; - public final TimeUnit timeUnit; - public final BlockingQueue workQueue; - public final ThreadFactory threadFactory; - public final RejectedExecutionHandler rejectedExecutionHandler; - - public WorkerPoolConfig(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit timeUnit, - BlockingQueue workQueue, ThreadFactory threadFactory, - RejectedExecutionHandler rejectedExecutionHandler) { - this.corePoolSize = corePoolSize; - this.maxPoolSize = maximumPoolSize; - this.keepAliveTime = keepAliveTime; - this.timeUnit = timeUnit; - this.workQueue = workQueue; - this.threadFactory = threadFactory; - this.rejectedExecutionHandler = rejectedExecutionHandler; - } - - public static WorkerPoolConfig defaultConfig() { - int poolSize = Runtime.getRuntime().availableProcessors() * 2; - return new WorkerPoolConfig( - poolSize, - poolSize, - 60L, - TimeUnit.SECONDS, - new LinkedBlockingQueue<>(MAX_QUEUE_SIZE), - new CustomThreadFactory("Custom worker", true), - new ThreadPoolExecutor.CallerRunsPolicy()); - } -} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/ByteArraySegment.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/ByteArraySegment.java new file mode 100644 index 000000000..9e9594dd2 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/ByteArraySegment.java @@ -0,0 +1,48 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +import java.io.IOException; +import java.lang.foreign.MemorySegment; +import java.nio.ByteBuffer; + +/** + * Growable buffer with {@link ByteBuffer} and {@link MemorySegment} interface. + * + * @author incubos + */ +final class ByteArraySegment { + private byte[] array; + private MemorySegment segment; + + ByteArraySegment(final int capacity) { + this.array = new byte[capacity]; + this.segment = MemorySegment.ofArray(array); + } + + void withArray(final ArrayConsumer consumer) throws IOException { + consumer.process(array); + } + + MemorySegment segment() { + return segment; + } + + void ensureCapacity(final long size) { + if (size > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Too big!"); + } + + final int capacity = (int) size; + if (array.length >= capacity) { + return; + } + + // Grow to the nearest bigger power of 2 + final int newSize = Integer.highestOneBit(capacity) << 1; + array = new byte[newSize]; + segment = MemorySegment.ofArray(array); + } + + interface ArrayConsumer { + void process(byte[] array) throws IOException; + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/Dao.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/Dao.java new file mode 100644 index 000000000..7af33822e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/Dao.java @@ -0,0 +1,87 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Iterator; + +public interface Dao> extends Closeable { + /** + * Returns ordered iterator of entries with keys between from (inclusive) and to (exclusive). + * @param from lower bound of range (inclusive) + * @param to upper bound of range (exclusive) + * @return entries [from;to) + */ + Iterator get(D from, D to); + + /** + * Returns entry by key. Note: default implementation is far from optimal. + * @param key entry`s key + * @return entry + */ + default E get(D key) { + Iterator iterator = get(key, null); + if (!iterator.hasNext()) { + return null; + } + + E next = iterator.next(); + if (next.key().equals(key)) { + return next; + } + return null; + } + + /** + * Returns ordered iterator of all entries with keys from (inclusive). + * @param from lower bound of range (inclusive) + * @return entries with key >= from + */ + default Iterator allFrom(D from) { + return get(from, null); + } + + /** + * Returns ordered iterator of all entries with keys < to. + * @param to upper bound of range (exclusive) + * @return entries with key < to + */ + default Iterator allTo(D to) { + return get(null, to); + } + + /** + * Returns ordered iterator of all entries. + * @return all entries + */ + default Iterator all() { + return get(null, null); + } + + /** + * Inserts of replaces entry. + * @param entry element to upsert + */ + void upsert(E entry); + + /** + * Persists data (no-op by default). + */ + default void flush() throws IOException { + // Do nothing + } + + /** + * Compacts data (no-op by default). + */ + default void compact() throws IOException { + // Do nothing + } + + /* + * Releases Dao (calls flush by default). + */ + @Override + default void close() throws IOException { + flush(); + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/ExtendedBaseEntry.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/ExtendedBaseEntry.java new file mode 100644 index 000000000..42106a4d7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/ExtendedBaseEntry.java @@ -0,0 +1,8 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +public record ExtendedBaseEntry(D key, D value, long timestamp) implements ExtendedEntry { + @Override + public String toString() { + return "{" + key + ":" + value + ":" + timestamp + "}"; + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/ExtendedEntry.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/ExtendedEntry.java new file mode 100644 index 000000000..1d614fe51 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/ExtendedEntry.java @@ -0,0 +1,9 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +public interface ExtendedEntry { + D key(); + + D value(); + + long timestamp(); +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/MemTable.java new file mode 100644 index 000000000..8d4e53bec --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/MemTable.java @@ -0,0 +1,47 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; +import java.util.NavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; + +/** + * Memory table. + * + * @author incubos + */ +final class MemTable { + private final NavigableMap> map = + new ConcurrentSkipListMap<>( + MemorySegmentComparator.INSTANCE); + + boolean isEmpty() { + return map.isEmpty(); + } + + Iterator> get( + final MemorySegment from, + final MemorySegment to) { + if (from == null && to == null) { + // All + return map.values().iterator(); + } else if (from == null) { + // Head + return map.headMap(to).values().iterator(); + } else if (to == null) { + // Tail + return map.tailMap(from).values().iterator(); + } else { + // Slice + return map.subMap(from, to).values().iterator(); + } + } + + ExtendedEntry get(final MemorySegment key) { + return map.get(key); + } + + ExtendedEntry upsert(final ExtendedEntry entry) { + return map.put(entry.key(), entry); + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/MemorySegmentComparator.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/MemorySegmentComparator.java new file mode 100644 index 000000000..017070b59 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/MemorySegmentComparator.java @@ -0,0 +1,89 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.Comparator; + +/** + * Compares {@link MemorySegment}s. + * + * @author incubos + */ +final class MemorySegmentComparator implements Comparator { + static final Comparator INSTANCE = + new MemorySegmentComparator(); + + private MemorySegmentComparator() { + // Singleton + } + + @Override + public int compare( + final MemorySegment left, + final MemorySegment right) { + final long mismatch = left.mismatch(right); + if (mismatch == -1L) { + // No mismatch + return 0; + } + + if (mismatch == left.byteSize()) { + // left is prefix of right, so left is smaller + return -1; + } + + if (mismatch == right.byteSize()) { + // right is prefix of left, so left is greater + return 1; + } + + // Compare mismatched bytes as unsigned + return Byte.compareUnsigned( + left.getAtIndex( + ValueLayout.OfByte.JAVA_BYTE, + mismatch), + right.getAtIndex( + ValueLayout.OfByte.JAVA_BYTE, + mismatch)); + } + + static int compare( + final MemorySegment srcSegment, + final long srcFromOffset, + final long srcLength, + final MemorySegment dstSegment, + final long dstFromOffset, + final long dstLength) { + final long mismatch = + MemorySegment.mismatch( + srcSegment, + srcFromOffset, + srcFromOffset + srcLength, + dstSegment, + dstFromOffset, + dstFromOffset + dstLength); + if (mismatch == -1L) { + // No mismatch + return 0; + } + + if (mismatch == srcLength) { + // left is prefix of right, so left is smaller + return -1; + } + + if (mismatch == dstLength) { + // right is prefix of left, so left is greater + return 1; + } + + // Compare mismatched bytes as unsigned + return Byte.compareUnsigned( + srcSegment.getAtIndex( + ValueLayout.OfByte.JAVA_BYTE, + srcFromOffset + mismatch), + dstSegment.getAtIndex( + ValueLayout.OfByte.JAVA_BYTE, + dstFromOffset + mismatch)); + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/MergingEntryIterator.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/MergingEntryIterator.java new file mode 100644 index 000000000..4abef506c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/MergingEntryIterator.java @@ -0,0 +1,70 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.PriorityQueue; +import java.util.Queue; + +/** + * Merges entry {@link Iterator}s. + * + * @author incubos + */ +final class MergingEntryIterator implements Iterator> { + private final Queue iterators; + + MergingEntryIterator(final List iterators) { + assert iterators.stream().allMatch(WeightedPeekingEntryIterator::hasNext); + + this.iterators = new PriorityQueue<>(iterators); + } + + @Override + public boolean hasNext() { + return !iterators.isEmpty(); + } + + @Override + public ExtendedEntry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final WeightedPeekingEntryIterator top = iterators.remove(); + final ExtendedEntry result = top.next(); + + if (top.hasNext()) { + // Not exhausted + iterators.add(top); + } + + // Remove older versions of the key + while (true) { + final WeightedPeekingEntryIterator iterator = iterators.peek(); + if (iterator == null) { + // Nothing left + break; + } + + // Skip entries with the same key + final ExtendedEntry entry = iterator.peek(); + if (MemorySegmentComparator.INSTANCE.compare(result.key(), entry.key()) != 0) { + // Reached another key + break; + } + + // Drop + iterators.remove(); + // Skip + iterator.next(); + if (iterator.hasNext()) { + // Not exhausted + iterators.add(iterator); + } + } + + return result; + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/ReferenceDao.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/ReferenceDao.java new file mode 100644 index 000000000..7cf3ff71e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/ReferenceDao.java @@ -0,0 +1,288 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +import ru.vk.itmo.dao.Config; + +import java.io.IOException; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Reference implementation of {@link Dao}. + * + * @author incubos + */ +public class ReferenceDao implements Dao> { + private final Config config; + private final Arena arena; + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + // Guarded by lock + private volatile TableSet tableSet; + + private final ExecutorService flusher = + Executors.newSingleThreadExecutor(r -> { + final Thread result = new Thread(r); + result.setName("flusher"); + return result; + }); + private final ExecutorService compactor = + Executors.newSingleThreadExecutor(r -> { + final Thread result = new Thread(r); + result.setName("compactor"); + return result; + }); + + private final AtomicBoolean closed = new AtomicBoolean(); + + public ReferenceDao(final Config config) throws IOException { + this.config = config; + this.arena = Arena.ofShared(); + + // First complete promotion of compacted SSTables + SSTables.promote( + config.basePath(), + 0, + 1); + + this.tableSet = + TableSet.from( + SSTables.discover( + arena, + config.basePath())); + } + + @Override + public Iterator> get( + final MemorySegment from, + final MemorySegment to) { + return tableSet.get( + from, + to); + } + + @Override + public ExtendedEntry get(final MemorySegment key) { + // Without lock, just snapshot of table set + return tableSet.get(key); + } + + @Override + public void upsert(final ExtendedEntry entry) { + final boolean autoFlush; + lock.readLock().lock(); + try { + if (tableSet.memTableSize.get() > config.flushThresholdBytes() + && tableSet.flushingTable != null) { + throw new IllegalStateException("Can't keep up with flushing!"); + } + + // Upsert + final ExtendedEntry previous = tableSet.upsert(entry); + + // Update size estimate + final long size = tableSet.memTableSize.addAndGet(sizeOf(entry) - sizeOf(previous)); + autoFlush = size > config.flushThresholdBytes(); + } finally { + lock.readLock().unlock(); + } + + if (autoFlush) { + initiateFlush(true); + } + } + + private static long sizeOf(final ExtendedEntry entry) { + if (entry == null) { + return 0L; + } + + if (entry.value() == null) { + return entry.key().byteSize() + Long.BYTES; + } + + return entry.key().byteSize() + entry.value().byteSize() + Long.BYTES; + } + + private void initiateFlush(final boolean auto) { + flusher.submit(() -> { + final TableSet currentTableSet; + lock.writeLock().lock(); + try { + if (this.tableSet.memTable.isEmpty()) { + // Nothing to flush + return; + } + + if (auto && this.tableSet.memTableSize.get() < config.flushThresholdBytes()) { + // Not enough data to flush + return; + } + + // Switch memTable to flushing + currentTableSet = this.tableSet.flushing(); + this.tableSet = currentTableSet; + } finally { + lock.writeLock().unlock(); + } + + // Write + final int sequence = currentTableSet.nextSequence(); + try { + new SSTableWriter() + .write( + config.basePath(), + sequence, + currentTableSet.flushingTable.get(null, null)); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-1); + return; + } + + // Open + final SSTable flushed; + try { + flushed = SSTables.open( + arena, + config.basePath(), + sequence); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-2); + return; + } + + // Switch + lock.writeLock().lock(); + try { + this.tableSet = this.tableSet.flushed(flushed); + } finally { + lock.writeLock().unlock(); + } + }).state(); + } + + @Override + public void flush() throws IOException { + initiateFlush(false); + } + + @Override + public void compact() throws IOException { + compactor.submit(() -> { + final TableSet currentTableSet; + lock.writeLock().lock(); + try { + currentTableSet = this.tableSet; + if (currentTableSet.ssTables.size() < 2) { + // Nothing to compact + return; + } + } finally { + lock.writeLock().unlock(); + } + + // Compact to 0 + try { + new SSTableWriter() + .write( + config.basePath(), + 0, + currentTableSet.allSSTableEntries()); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-3); + } + + // Open 0 + final SSTable compacted; + try { + compacted = + SSTables.open( + arena, + config.basePath(), + 0); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-4); + return; + } + + // Replace old SSTables with compacted one to + // keep serving requests + final Set replaced = new HashSet<>(currentTableSet.ssTables); + lock.writeLock().lock(); + try { + this.tableSet = + this.tableSet.compacted( + replaced, + compacted); + } finally { + lock.writeLock().unlock(); + } + + // Remove compacted SSTables starting from the oldest ones. + // If we crash, 0 contains all the data, and + // it will be promoted on reopen. + for (final SSTable ssTable : currentTableSet.ssTables.reversed()) { + try { + SSTables.remove( + config.basePath(), + ssTable.sequence); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-5); + } + } + + // Promote zero to one (possibly replacing) + try { + SSTables.promote( + config.basePath(), + 0, + 1); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-6); + } + + // Replace promoted SSTable + lock.writeLock().lock(); + try { + this.tableSet = + this.tableSet.compacted( + Collections.singleton(compacted), + compacted.withSequence(1)); + } finally { + lock.writeLock().unlock(); + } + }).state(); + } + + @Override + public void close() throws IOException { + if (closed.getAndSet(true)) { + // Already closed + return; + } + + // Maybe flush + flush(); + + // Stop all the threads + flusher.close(); + compactor.close(); + + // Close arena + arena.close(); + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/SSTable.java new file mode 100644 index 000000000..cd6838b65 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/SSTable.java @@ -0,0 +1,209 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Persistent SSTable in data file and index file. + * + * @author incubos + * @see SSTables + */ +final class SSTable { + final int sequence; + + private final MemorySegment index; + private final MemorySegment data; + private final long size; + + SSTable( + final int sequence, + final MemorySegment index, + final MemorySegment data) { + this.sequence = sequence; + this.index = index; + this.data = data; + this.size = index.byteSize() / Long.BYTES; + } + + SSTable withSequence(final int sequence) { + return new SSTable( + sequence, + index, + data); + } + + /** + * Returns index of the entry if found; otherwise, (-(insertion point) - 1). + * The insertion point is defined as the point at which the key would be inserted: + * the index of the first element greater than the key, + * or size if all keys are less than the specified key. + * Note that this guarantees that the return value will be >= 0 + * if and only if the key is found. + */ + private long entryBinarySearch(final MemorySegment key) { + long low = 0L; + long high = size - 1; + + while (low <= high) { + final long mid = (low + high) >>> 1; + final long midEntryOffset = entryOffset(mid); + final long midKeyLength = getValue(midEntryOffset); + final int compare = + MemorySegmentComparator.compare( + data, + midEntryOffset + Long.BYTES, // Position at key + midKeyLength, + key, + 0L, + key.byteSize()); + + if (compare < 0) { + low = mid + 1; + } else if (compare > 0) { + high = mid - 1; + } else { + return mid; + } + } + + return -(low + 1); + } + + private long entryOffset(final long entry) { + return index.get( + ValueLayout.OfLong.JAVA_LONG, + entry * Long.BYTES); + } + + private long getValue(final long offset) { + return data.get( + ValueLayout.OfLong.JAVA_LONG_UNALIGNED, + offset); + } + + Iterator> get( + final MemorySegment from, + final MemorySegment to) { + assert from == null || to == null || MemorySegmentComparator.INSTANCE.compare(from, to) <= 0; + + // Slice of SSTable in absolute offsets + final long fromOffset; + final long toOffset; + + // Left offset bound + if (from == null) { + // Start from the beginning + fromOffset = 0L; + } else { + final long fromEntry = entryBinarySearch(from); + if (fromEntry >= 0L) { + fromOffset = entryOffset(fromEntry); + } else if (-fromEntry - 1 == size) { + // No relevant data + return Collections.emptyIterator(); + } else { + // Greater but existing key found + fromOffset = entryOffset(-fromEntry - 1); + } + } + + // Right offset bound + if (to == null) { + // Up to the end + toOffset = data.byteSize(); + } else { + final long toEntry = entryBinarySearch(to); + if (toEntry >= 0L) { + toOffset = entryOffset(toEntry); + } else if (-toEntry - 1 == size) { + // Up to the end + toOffset = data.byteSize(); + } else { + // Greater but existing key found + toOffset = entryOffset(-toEntry - 1); + } + } + + return new SliceIterator(fromOffset, toOffset); + } + + ExtendedEntry get(final MemorySegment key) { + final long entry = entryBinarySearch(key); + if (entry < 0) { + return null; + } + + // Skip key (will reuse the argument) + long offset = entryOffset(entry); + offset += Long.BYTES + key.byteSize(); + // Extract value length + final long valueLength = getValue(offset); + if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { + // Tombstone encountered + offset += Long.BYTES; + final long timestamp = getValue(offset); + return new ExtendedBaseEntry<>(key, null, timestamp); + } else { + // Get value + offset += Long.BYTES; + final MemorySegment value = data.asSlice(offset, valueLength); + offset += valueLength; + final long timestamp = getValue(offset); + return new ExtendedBaseEntry<>(key, value, timestamp); + } + } + + private final class SliceIterator implements Iterator> { + private long offset; + private final long toOffset; + + private SliceIterator( + final long offset, + final long toOffset) { + this.offset = offset; + this.toOffset = toOffset; + } + + @Override + public boolean hasNext() { + return offset < toOffset; + } + + @Override + public ExtendedEntry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + // Read key length + final long keyLength = getValue(offset); + offset += Long.BYTES; + + // Read key + final MemorySegment key = data.asSlice(offset, keyLength); + offset += keyLength; + + // Read value length + final long valueLength = getValue(offset); + offset += Long.BYTES; + + // Read value + if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { + // Tombstone encountered + final long timestamp = getValue(offset); + offset += Long.BYTES; + return new ExtendedBaseEntry<>(key, null, timestamp); + } else { + final MemorySegment value = data.asSlice(offset, valueLength); + offset += valueLength; + final long timestamp = getValue(offset); + offset += Long.BYTES; + return new ExtendedBaseEntry<>(key, value, timestamp); + } + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/SSTableWriter.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/SSTableWriter.java new file mode 100644 index 000000000..547034904 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/SSTableWriter.java @@ -0,0 +1,175 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +import ru.vk.itmo.dao.Entry; + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Iterator; + +/** + * Writes {@link Entry} {@link Iterator} to SSTable on disk. + * + *

Index file {@code .index} contains {@code long} offsets to entries in data file: + * {@code [offset0, offset1, ...]} + * + *

Data file {@code .data} contains serialized entries: + * {@code } + * + *

Tombstones are encoded as {@code valueLength} {@code -1} and no subsequent value. + * + * @author incubos + */ +final class SSTableWriter { + private static final int BUFFER_SIZE = 64 * 1024; + + // Reusable buffers to eliminate allocations. + // But excessive memory copying is still there :( + // Long cell + private final ByteArraySegment longBuffer = new ByteArraySegment(Long.BYTES); + // Growable blob cell + private final ByteArraySegment blobBuffer = new ByteArraySegment(512); + + void write( + final Path baseDir, + final int sequence, + final Iterator> entries) throws IOException { + // Write to temporary files + final Path tempIndexName = SSTables.tempIndexName(baseDir, sequence); + final Path tempDataName = SSTables.tempDataName(baseDir, sequence); + + // Delete temporary files to eliminate tails + Files.deleteIfExists(tempIndexName); + Files.deleteIfExists(tempDataName); + + // Iterate in a single pass! + // Will write through FileChannel despite extra memory copying and + // no buffering (which may be implemented later). + // Looking forward to MemorySegment facilities in FileChannel! + try (OutputStream index = + new BufferedOutputStream( + new FileOutputStream( + tempIndexName.toFile()), + BUFFER_SIZE); + OutputStream data = + new BufferedOutputStream( + new FileOutputStream( + tempDataName.toFile()), + BUFFER_SIZE)) { + long entryOffset = 0L; + + // Iterate and serialize + while (entries.hasNext()) { + // First write offset to the entry + writeLong(entryOffset, index); + + // Then write the entry + final ExtendedEntry entry = entries.next(); + entryOffset += writeEntry(entry, data); + } + } + + // Publish files atomically + // FIRST index, LAST data + final Path indexName = + SSTables.indexName( + baseDir, + sequence); + Files.move( + tempIndexName, + indexName, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + final Path dataName = + SSTables.dataName( + baseDir, + sequence); + Files.move( + tempDataName, + dataName, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + } + + private void writeLong( + final long value, + final OutputStream os) throws IOException { + longBuffer.segment().set( + ValueLayout.OfLong.JAVA_LONG_UNALIGNED, + 0, + value); + longBuffer.withArray(os::write); + } + + private void writeSegment( + final MemorySegment value, + final OutputStream os) throws IOException { + final long size = value.byteSize(); + blobBuffer.ensureCapacity(size); + MemorySegment.copy( + value, + 0L, + blobBuffer.segment(), + 0L, + size); + blobBuffer.withArray(array -> + os.write( + array, + 0, + (int) size)); + } + + /** + * Writes {@link Entry} to {@link FileChannel}. + * + * @return written bytes + */ + private long writeEntry( + final ExtendedEntry 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 + writeLong(key.byteSize(), os); + result += Long.BYTES; + + // Key + writeSegment(key, os); + result += key.byteSize(); + + // Value size and possibly value + if (value == null) { + // Tombstone + writeLong(SSTables.TOMBSTONE_VALUE_LENGTH, os); + result += Long.BYTES; + + // Timestamp + writeLong(timestamp, os); + result += Long.BYTES; + } else { + // Value length + writeLong(value.byteSize(), os); + result += Long.BYTES; + + // Value + writeSegment(value, os); + result += value.byteSize(); + + // Timestamp + writeLong(timestamp, os); + result += Long.BYTES; + } + + return result; + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/SSTables.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/SSTables.java new file mode 100644 index 000000000..abba3849f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/SSTables.java @@ -0,0 +1,162 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +/** + * Provides {@link SSTable} management facilities: dumping and discovery. + * + * @author incubos + */ +final class SSTables { + public static final String INDEX_SUFFIX = ".index"; + public static final String DATA_SUFFIX = ".data"; + public static final long TOMBSTONE_VALUE_LENGTH = -1L; + + private static final String TEMP_SUFFIX = ".tmp"; + + /** + * Can't instantiate. + */ + private SSTables() { + // Only static methods + } + + static Path indexName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + INDEX_SUFFIX); + } + + static Path dataName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + DATA_SUFFIX); + } + + static Path tempIndexName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + INDEX_SUFFIX + TEMP_SUFFIX); + } + + static Path tempDataName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + DATA_SUFFIX + TEMP_SUFFIX); + } + + /** + * Returns {@link List} of {@link SSTable}s from freshest to oldest. + */ + static List discover( + final Arena arena, + final Path baseDir) throws IOException { + if (!Files.exists(baseDir)) { + return Collections.emptyList(); + } + + final List result = new ArrayList<>(); + try (Stream files = Files.list(baseDir)) { + files.forEach(file -> { + final String fileName = file.getFileName().toString(); + if (!fileName.endsWith(DATA_SUFFIX)) { + // Skip non data + return; + } + + final int sequence = + // .data -> N + Integer.parseInt( + fileName.substring( + 0, + fileName.length() - DATA_SUFFIX.length())); + + try { + result.add(open(arena, baseDir, sequence)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + + // Sort from freshest to oldest + result.sort((o1, o2) -> Integer.compare(o2.sequence, o1.sequence)); + + return Collections.unmodifiableList(result); + } + + static SSTable open( + final Arena arena, + final Path baseDir, + final int sequence) throws IOException { + final MemorySegment index = + mapReadOnly( + arena, + indexName(baseDir, sequence)); + final MemorySegment data = + mapReadOnly( + arena, + dataName(baseDir, sequence)); + + return new SSTable( + sequence, + index, + data); + } + + private static MemorySegment mapReadOnly( + final Arena arena, + final Path file) throws IOException { + try (FileChannel channel = + FileChannel.open( + file, + StandardOpenOption.READ)) { + return channel.map( + FileChannel.MapMode.READ_ONLY, + 0L, + Files.size(file), + arena); + } + } + + static void remove( + final Path baseDir, + final int sequence) throws IOException { + // First delete data file to make SSTable invisible + Files.delete(dataName(baseDir, sequence)); + Files.delete(indexName(baseDir, sequence)); + } + + static void promote( + final Path baseDir, + final int from, + final int to) throws IOException { + // Build to progress to the same outcome + if (Files.exists(indexName(baseDir, from))) { + Files.move( + indexName(baseDir, from), + indexName(baseDir, to), + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + } + if (Files.exists(dataName(baseDir, from))) { + Files.move( + dataName(baseDir, from), + dataName(baseDir, to), + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/TableSet.java new file mode 100644 index 000000000..3352efafd --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/TableSet.java @@ -0,0 +1,196 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +import java.lang.foreign.MemorySegment; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Data set in various tables. + * + * @author incubos + */ +final class TableSet { + final MemTable memTable; + final AtomicLong memTableSize; + // null or read-only + final MemTable flushingTable; + // From freshest to oldest + final List ssTables; + + private TableSet( + final MemTable memTable, + final AtomicLong memTableSize, + final MemTable flushingTable, + final List ssTables) { + this.memTable = memTable; + this.memTableSize = memTableSize; + this.flushingTable = flushingTable; + this.ssTables = ssTables; + } + + static TableSet from(final List ssTables) { + return new TableSet( + new MemTable(), + new AtomicLong(), + null, + 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"); + } + + if (flushingTable != null) { + throw new IllegalStateException("Already flushing"); + } + + return new TableSet( + new MemTable(), + new AtomicLong(), + memTable, + ssTables); + } + + TableSet flushed(final SSTable flushed) { + final List newSSTables = new ArrayList<>(ssTables.size() + 1); + newSSTables.add(flushed); + newSSTables.addAll(ssTables); + return new TableSet( + memTable, + memTableSize, + null, + newSSTables); + } + + TableSet compacted( + final Set replaced, + final SSTable with) { + final List newSsTables = new ArrayList<>(this.ssTables.size() + 1); + + // Keep not replaced SSTables + for (final SSTable ssTable : this.ssTables) { + if (!replaced.contains(ssTable)) { + newSsTables.add(ssTable); + } + } + + // Logically the oldest one + newSsTables.add(with); + + return new TableSet( + memTable, + memTableSize, + flushingTable, + newSsTables); + } + + Iterator> get( + final MemorySegment from, + final MemorySegment to) { + final List iterators = + new ArrayList<>(2 + ssTables.size()); + + // MemTable goes first + final Iterator> memTableIterator = + memTable.get(from, to); + if (memTableIterator.hasNext()) { + iterators.add( + new WeightedPeekingEntryIterator( + Integer.MIN_VALUE, + memTableIterator)); + } + + // Then goes flushing + if (flushingTable != null) { + final Iterator> flushingIterator = + flushingTable.get(from, to); + if (flushingIterator.hasNext()) { + iterators.add( + new WeightedPeekingEntryIterator( + Integer.MIN_VALUE + 1, + flushingIterator)); + } + } + + // Then go all the SSTables + for (int i = 0; i < ssTables.size(); i++) { + final SSTable ssTable = ssTables.get(i); + final Iterator> ssTableIterator = + ssTable.get(from, to); + if (ssTableIterator.hasNext()) { + iterators.add( + new WeightedPeekingEntryIterator( + i, + ssTableIterator)); + } + } + + return switch (iterators.size()) { + case 0 -> Collections.emptyIterator(); + case 1 -> iterators.get(0); + default -> new MergingEntryIterator(iterators); + }; + } + + ExtendedEntry get(final MemorySegment key) { + // Slightly optimized version not to pollute the heap + + // First check MemTable + ExtendedEntry result = memTable.get(key); + if (result != null) { + return result; + } + + // Then check flushing + if (flushingTable != null) { + result = flushingTable.get(key); + if (result != null) { + return result; + } + } + + // At last check SSTables from freshest to oldest + for (final SSTable ssTable : ssTables) { + result = ssTable.get(key); + if (result != null) { + return result; + } + } + + // Nothing found + return null; + } + + ExtendedEntry upsert(final ExtendedEntry entry) { + return memTable.upsert(entry); + } + + 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 = + ssTable.get(null, null); + iterators.add( + new WeightedPeekingEntryIterator( + i, + ssTableIterator)); + } + + return new MergingEntryIterator(iterators); + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/dao/WeightedPeekingEntryIterator.java b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/WeightedPeekingEntryIterator.java new file mode 100644 index 000000000..a9e589baf --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/dao/WeightedPeekingEntryIterator.java @@ -0,0 +1,65 @@ +package ru.vk.itmo.test.proninvalentin.dao; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Peeking {@link Iterator} wrapper. + * + * @author incubos + */ +final class WeightedPeekingEntryIterator + implements Iterator>, + Comparable { + private final int weight; + private final Iterator> delegate; + private ExtendedEntry next; + + WeightedPeekingEntryIterator( + final int weight, + final Iterator> delegate) { + this.weight = weight; + this.delegate = delegate; + this.next = delegate.hasNext() ? delegate.next() : null; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public ExtendedEntry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final ExtendedEntry result = next; + next = delegate.hasNext() ? delegate.next() : null; + return result; + } + + ExtendedEntry peek() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + return next; + } + + @Override + public int compareTo(final WeightedPeekingEntryIterator other) { + // First compare keys + int result = + MemorySegmentComparator.INSTANCE.compare( + peek().key(), + other.peek().key()); + if (result != 0) { + return result; + } + + // Then compare weights if keys are equal + return Integer.compare(weight, other.weight); + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/failure_limiter/FailureLimiter.java b/src/main/java/ru/vk/itmo/test/proninvalentin/failure_limiter/FailureLimiter.java index 8a8a126a1..c63685e65 100644 --- a/src/main/java/ru/vk/itmo/test/proninvalentin/failure_limiter/FailureLimiter.java +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/failure_limiter/FailureLimiter.java @@ -1,6 +1,6 @@ package ru.vk.itmo.test.proninvalentin.failure_limiter; -import ru.vk.itmo.test.proninvalentin.Utils; +import ru.vk.itmo.test.proninvalentin.utils.Utils; import java.util.HashMap; import java.util.List; diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/get-alloc.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/get-alloc.png new file mode 100644 index 000000000..4ce8ad5ca Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/get-alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/get-cpu.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/get-cpu.png new file mode 100644 index 000000000..388c22fe5 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/get-cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/get-lock.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/get-lock.png new file mode 100644 index 000000000..2b73ea7d6 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/get-lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/profile-alloc-get-3300.html b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/profile-alloc-get-3300.html new file mode 100644 index 000000000..6ec797212 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/profile-alloc-get-3300.html @@ -0,0 +1,2935 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/profile-cpu-get-3300.html b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/profile-cpu-get-3300.html new file mode 100644 index 000000000..bd19f48cd --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/profile-cpu-get-3300.html @@ -0,0 +1,3442 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/profile-lock-get-3300.html b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/profile-lock-get-3300.html new file mode 100644 index 000000000..eaff64f53 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/get/profile-lock-get-3300.html @@ -0,0 +1,689 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/profile-alloc-put-5500.html b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/profile-alloc-put-5500.html new file mode 100644 index 000000000..f008b9bff --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/profile-alloc-put-5500.html @@ -0,0 +1,3001 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/profile-cpu-put-5500.html b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/profile-cpu-put-5500.html new file mode 100644 index 000000000..46514d140 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/profile-cpu-put-5500.html @@ -0,0 +1,3901 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/profile-lock-put-5500.html b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/profile-lock-put-5500.html new file mode 100644 index 000000000..34c6c8d99 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/profile-lock-put-5500.html @@ -0,0 +1,679 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/put-alloc.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/put-alloc.png new file mode 100644 index 000000000..280ee658c Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/put-alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/put-cpu.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/put-cpu.png new file mode 100644 index 000000000..f30d139e8 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/put-cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/put-lock.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/put-lock.png new file mode 100644 index 000000000..4e0835d05 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/aprof/put/put-lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/report.md b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/report.md new file mode 100644 index 000000000..0bb8f414d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage4/report.md @@ -0,0 +1,146 @@ +Работа происходила с тремя узлами (from=3) и кворумом из двух узлов (ack=2) + +### Перцентили с PUT 5500 RPS (было 24K RPS): + +``` +Перцентиль было стало +50.000% 1.28ms -> 1.17ms +75.000% 1.75ms -> 7.50ms +90.000% 2.27ms -> 327.42ms +99.000% 3.45ms -> 536.58ms +99.900% 23.44ms -> 558.59ms +99.990% 61.53ms -> 560.13ms +99.999% 74.24ms -> 560.13ms +100.000% 81.15ms -> 560.13ms +``` + +### Перцентили с GET 3300 RPS (было 28K RPS): + +``` +Перцентиль было стало + 50.000% 1.13ms -> 1.28ms + 75.000% 1.60ms -> 12.30ms + 90.000% 1.96ms -> 387.84ms + 99.000% 4.29ms -> 440.83ms + 99.900% 45.82ms -> 468.74ms + 99.990% 53.41ms -> 471.30ms + 99.999% 55.71ms -> 471.55ms +100.000% 56.32ms -> 471.55ms +``` + +Произошла сильная деградация производительности (для PUT запросов в ~4,4 раза, для GET запросов в ~8,5 раз), +связываю это с тем, что теперь для выполнения запроса нужно синхронно ждать ответ не от одного проксируемого узла, +как было на предыдущем этапе, а сразу от кворума узлов. +При этом, в случае, когда нужно проксировать запрос сразу к двум узлам, +мы сначала ждем ответа от первого узла, перед тем как отправить запрос ко второму узлу из-за текущей реализации в коде. + +Учитывая тот факт, что распределение данных у нас происходило равномерно (это можно увидеть по вставке ниже), +можно сделать вывод, что 2/3 запросов требовали 2 прокси вызова из-за чего эти запросы были медленнее, чем 1/3 запросов, +в которых был только один прокси вызов и один вызов на лидере + +``` +682M server_profiling_data10080 - сервер с портом 10080 +682M server_profiling_data8080 - сервер с портом 8080 +682M server_profiling_data9080 - сервер с портом 9080 +``` + +## Анализ Flame Graph + +## PUT запросы + +### CPU + +![put-cpu.png](aprof%2Fput%2Fput-cpu.png) + +Ситуация с обработкой запросов кардинально не изменилась. Мы по прежнему тратим время на отправку ответа клиенту, +ожидание ответа от проксируемых нод и выполнение логики обработки операции. +При этом процентное соотношение у операции `park` тоже не изменилось. + +Все трейсы на flame graph остались теми же, изменилось лишь соотношение семплов для них + +Время на: + +* `ThreadPoolExecutor.getTask` уменьшилось с 14.73% до 5.82%, из-за того, что время на выполнение самой задачи + увеличилось, как и говорил раньше - теперь ждем последовательно ответа от всех реплик. +* `HttpClientImpl$SelectorManager.run` увеличилось с 13.22% до 22.6%, предполагаю, что это из-за того, что теперь больше + http запросов +* `ThreadPoolExecutor.runWorker` увеличилось с 34.28% до 40.59% - отправка и ожидание запросов происходит в воркерах, + отсюда и время увеличилось + +### ALLOC + +![put-alloc.png](aprof%2Fput%2Fput-alloc.png) + +Flame graph также особо не изменился за исключением части с обработкой запроса, раньше +на операцию `Server.processRequest` уходило 20.23% общего объема семплов, сейчас 24.06%. +Сейчас дополнительные ресурсы уходят на построение http запросов сразу к нескольким узлам кластера +Также больше памяти уходит на `HttpClientImpl$SelectorManager.run` думаю это также из-за того, что стало больше работы с +http запросами + +### LOCK + +![put-lock.png](aprof%2Fput%2Fput-lock.png) + +Flame graph блокировок довольно сильно изменился + +* `java/util/concurrent/ThreadPoolExecutor.getTask` теперь занимает 60%, хотя было 12%, предполагаю, что это из-за того, + что теперь реплики тоже сабмитят задачи в свои пулы, соответственно и воркеры на них работают из-за чего общее + количество семплов увеличилось. +* `java/util/concurrent/ThreadPoolExecutor.runWorker` внутри воркера теперь также больше семплов уходит на + `one/nio/http/HttpSession.sendResponse`, из-за того что теперь при запросах также задействуется бОльшее количество + узлов. + Стоит отметить, что на графике не видно как долго простаивают наши узлы в ожидании кворума, так например метод + `ru/vk/itmo/test/proninvalentin/Server.waitResponses` занимает всего 0.85% от общего количества семплов из-за того, + что блокировку там использует только `jdk/internal/net/http/HttpClientImpl.sendAsync` метод, хотя предполагаю, что + именно из-за `waitResponse` произошла такая деградация в производительности. +* `jdk/internal/net/http/HttpClientImpl$SelectorManager.run` количество семплов увелилось с 14.91% до 21.47%, потому что + стало больше http запросов + +## GET запросы + +### CPU + +![get-cpu.png](aprof/get/get-cpu.png) + +По сравнению с предыдущим этапом сильно поменялся только график для воркеров, + +* `java/util/concurrent/ThreadPoolExecutor.getTask` также уменьшился с 15.15% до 4.68%, как и в GET запросах из-за того, +* что дольше ждем выполнения каждой таски +* `ru/vk/itmo/test/proninvalentin/Server.processRequest` увеличилось с 12.18%% до 29.25%, этот метод и раньше занимал + большую часть при GET запросах, т.к. кроме поиска необходимого значение его еще нужно записать в ответ клиенту. + Теперь здесь также присутствует ожидание ответов от кворума + +### ALLOC + +![get-alloc.png](aprof/get/get-alloc.png) + +Аналогично и GET запросам + +* `SelectorManager.run` из-за увеличения взаимодействия с http клиентом стало больше аллокаций (было 5.04% стало 7.72%) +* `ru/vk/itmo/test/proninvalentin/Server.processRequest` с 21.35% до 28.89% (больше созданий http запросов, парсинга + ответов от http и сюда же добавляется еще дополнительные поиски по бд из-за необходимости сбора кворума) + +### LOCK + +![get-lock.png](aprof/get/get-lock.png) + +Такая же ситуация, что и у GET запросов, единственное, что `java/util/concurrent/ThreadPoolExecutor.getTask` занимает +еще больше семплов - 78.84%, хотя было 12.26%, так же как и в GET предполагаю,что это из-за того, что и лидер, и реплики +сабмитят задачи в свои пулы, поэтому общее количество семплов увеличилось + +### Выводы + +* Теперь мы можем указывать необходимое количество узлов для выполнения нашего запроса, это особенно полезно, когда мы, + например, хотим пожертвовать скоростью на запись, в угоду скорости на чтение и для этого пишем сразу на все узлы, а + читаем только с одного (чтобы гарантированно прочитать актуальные данные), если знаем, что чтения в нашей системе + происходят намного чаще. +* Репликация очень сильно ухудшила производительность (для PUT запросов в ~4,4 раза, для GET запросов в ~8,5 раз), + однако теперь данные хранятся более надежно и при необходимости, например, мы опять же можем выбирать между скоростью + работы с нашей системой и надежностью хранения данных. + +### Предложения по возможным улучшениям + +* Как и в прошлом этапе, предлагаю добавить асинхронный клиент для работы с другими узлами и слать запросы не + последовательно, а параллельно к набору из from узлов, дожидаясь ответа только от ack нужных узлов, таким образом мы + уменьшим время ожидания ответа +* Добавить read repair, чтобы уменьшить процент неконсистентных данных, когда у двух нод \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/aprof/put/put-alloc-7500.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/aprof/put/put-alloc-7500.png new file mode 100644 index 000000000..76e438db1 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/aprof/put/put-alloc-7500.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/aprof/put/put-cpu-7500.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/aprof/put/put-cpu-7500.png new file mode 100644 index 000000000..b04286d01 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/aprof/put/put-cpu-7500.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/aprof/put/put-lock-7500.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/aprof/put/put-lock-7500.png new file mode 100644 index 000000000..15fcc67f9 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/aprof/put/put-lock-7500.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/report.md b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/report.md new file mode 100644 index 000000000..e4ddeb6bc --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/report.md @@ -0,0 +1,133 @@ +### Перцентили с PUT 7500 RPS (было 5500 RPS): + +``` +Перцентиль было стало +50.000% 1.17ms -> 1.17ms +75.000% 7.50ms -> 8.58ms +90.000% 327.42ms -> 52.67ms +99.000% 536.58ms -> 73.89ms +99.900% 558.59ms -> 79.39ms +99.990% 560.13ms -> 91.17ms +99.999% 560.13ms -> 91.94ms +100.000% 560.13ms -> 91.94ms +``` + +Можно сказать, что перцентили в PUT запросах уменьшились пропорционально. Сейчас не нужно отправлять запросы синхронно +на `from` количество узлов, из-за этого снизилось время ожидания, а также увеличилось количество запросов, которое может +обрабатывать сервер + +### Перцентили с GET 5200 RPS (было 3300 RPS): + +``` +Перцентиль было стало + 50.000% 1.28ms -> 0.89ms + 75.000% 12.30ms -> 1.21ms + 90.000% 387.84ms -> 24.54ms + 99.000% 440.83ms -> 39.65ms + 99.900% 468.74ms -> 68.22ms + 99.990% 471.30ms -> 72.89ms + 99.999% 471.55ms -> 73.79ms +100.000% 471.55ms -> 73.79ms +``` + +Аналогичная история произошла и с GET запросамиForkJoinWorkerThread + +## Анализ Flame Graph + +## PUT запросы + +### CPU + +![put-cpu-7500.png](aprof%2Fput%2Fput-cpu-7500.png) + +* Внутри воркера увеличилось количество семплов в `java/util/concurrent/LinkedBlockingQueue.take` с 24.48% до 29.32%. + Думаю, что это из-за того, что у воркеров теперь уходит меньше времени на выполнение задачи из-за чего они идут сразу + брать следующую задачу из пула +* Появился стек с `java/util/concurrent/ScheduledThreadPoolExecutor$DelayedWorkQueue.take` из-за того, что раньше у меня + в коде была проверка на таймаут времени выполнения запроса и находилась она в блоке с синхронным ожиданием ответа + в `httpClient`. Сейчас же из-за того, что добавили асинхронный клиент, я перенес эту проверку в + отдельный `ScheduledExecutorService`, который через заданное время смотрит завершилась ли `Future` с запросом. Отсюда + и стек, который занимает еще 4.32% в методе `java/util/concurrent/ThreadPoolExecutor.getTask` +* Увеличилось время на `java/util/concurrent/CompletableFuture$AsyncSupply.run`, т.к. увеличилось количество и + самих `Future` с callback'ами не выполнение +* Также в `java/util/concurrent/ForkJoinWorkerThread.run` добавился блок с ожиданием + кворума `ru/vk/itmo/test/proninvalentin/Server.lambda$getWaitQuorumFuture$4`, около 1%. Видимо, это и есть общий пул, + о котором мы говорили на лекции, в который могут перепрыгивать задачи `CompletableFuture` от `httpClient`. В VisualVM + видно, что ForkJoinPool связан с commonPool ![img.png](visualVM-forkJoinPool.png) +* `jdk/internal/net/http/HttpClientImpl$SelectorManager.run` упал с 22.60% до 14.47%, честно, не могу сказать из-за чего + так произошло, т.к. количество работы у httpClient осталось таким же. Разве, что на общем фоне остальных операций + уменьшился процент семплов. +* `one/nio/server/PayloadThread.run` уменьшился с 20.55% до 15.95%, тут как раз убралась часть с синхронным ожиданием + ответа от кворума, теперь воркер тратит больше времени на взятие задачи, ее обработку и отправление запросов на другие + ноды + +### ALLOC + +![put-alloc-7500.png](aprof%2Fput%2Fput-alloc-7500.png) + +Не вижу значимых изменений, добавились аллокации на `java/util/concurrent/ForkJoinWorkerThread.run`, когда шлем запросы +на другие ноды и обрабатываем CompletableFuture. + +### LOCK + +![put-lock-7500.png](aprof%2Fput%2Fput-lock-7500.png) + +* `java/util/concurrent/CompletableFuture$AsyncSupply.run` увеличился с 3.48% до 11.15%, из-за того, что стало больше + асинхронных операций. Если добраться до вершины стека, то можно увидеть метод `sun/nio/ch/KQueueSelectorImpl.wakeup`. + В [зеркале jdk7](https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/macosx/classes/sun/nio/ch/KQueueSelectorImpl.java?ysclid=luvovg3r74790465664) + можно увидеть, что для ожидания используется synchronized блок, отсюда и его отображение в FlameGraph +* В `java/util/concurrent/ThreadPoolExecutor.getTask` произошла аналогичная ситуация, что и на этапе, когда мы добавили + пул воркеров - процессорных семплов стало больше, а локов стало меньше, т.к. меньше необходимо ждать следующую задачу + из-за того, что ожидание кворума асинхронное. +* `jdk/internal/net/http/common/SequentialScheduler$SchedulableTask.run`, на удивление, очень сильно вырос с 3% до 48%. + В jdk написано, + что `This class can be used as a synchronization aid that assists a number of parties in running a task in a mutually exclusive fashion.` + Как я понимаю, этот класс как раз помогает нам составлять цепочки выполнения нашей Future (в моем случае, + использование `whenComplete` метода). А т.к. раньше я ничего не делал асинхронно, то блокировок в этом блоке не было +* В остальном без изменений, поменялись лишь пропорции стеков на FlameGraph'е + +## GET запросы + +### CPU + +* Также добавилось работы воркерам, раньше они только брали задачу и начинали ее обрабатывать, ожидая кворума ответов от + других нод и результата операции на текущей ноде. + Теперь у воркеров появились затраты на работу с Future из-за асинхронной работы с `httpClient`. А именно появились + стеки + `jdk/internal/net/http/common/SequentialScheduler$SchedulableTask.run` + и `java/util/concurrent/CompletableFuture$AsyncSupply.run`, которые были и при PUT запросах. Зато теперь + запросы отправляются на ноды кластера параллельно и ответ клиенту приходит сразу при наборе необходимого + количества `ACK` (на предыдущем этапе я дожидался ответа от всех from узлов, даже если `ACK` уже был набран). +* Увеличился объем работы по взятию задачи для воркеров, т.к. как и при PUT запросах, уменьшилось необходимое время на + обработку этой самой задачи. + +### ALLOC + +* Также как и при PUT запросах добавились аллокации на `java/util/concurrent/ForkJoinWorkerThread.run`, когда шлем + запросы на другие ноды и обрабатываем CompletableFuture. +* Заметил, что раньше у меня было два стека на FlameGraph'e `java/util/concurrent/ThreadPoolExecutor.runWorker` - один + для http клиента, другой для воркеров, сейчас же решил попробовать использовать один пул и для httpClient и для + воркеров, из-за этого два стека объединились в один, поэтому FlameGraph изменился, хотя на деле в системе ничего не + поменялось. + Попробовал нагрузить версию с разными пулами и одним общим - видимой разницы в производительности я не увидел. + +### LOCK + +* Такая же ситуация, что и при PUT запросах, неудивительно, учитывая, что логика обработки на уровне сервера особо не + различается. + +### Выводы + +* Хоть пропускная способность и увеличилась, зато сильно усложнилась логика внутри самого кода, теперь приходится + тратить ресурсы на организацию работы с CompletableFuture и его callback'ами. +* В предыдущем этапе я синхронно дожидался ответа от всех `from` узлов, даже если собрал необходимое `ack` ответов. + Теперь такой проблемы нету и ответ отправляется клиенту сразу после получения `ack` ответов. + +### Предложения по возможным улучшениям + +* На узлы, которые ответили медленнее остальных можно попробовать слать команду с отменой выполнения запроса, т.к. мы + уже не нуждаемся в их ответе. Но, кажется, что отменять нужно только тяжелые операции, когда мы уверены, что отмена + выполнения операции на узлах с долгим ответом действительно сэкономит ресурсы системы, а не будет лишней нагрузкой. + Также если нам еще понятно как отменить операции чтения с других нод, то непонятно как + отменять операции записи (что делать если мы прислали команду на отмену записи по середине этой самой записи). Т.е. + задача эта совсем нетривиальная \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/visualVM-forkJoinPool.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/visualVM-forkJoinPool.png new file mode 100644 index 000000000..862e1e696 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage5/visualVM-forkJoinPool.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/alloc.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/alloc.png new file mode 100644 index 000000000..72ddba5ff Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/aprof/profile-alloc-get-range-600.html b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/aprof/profile-alloc-get-range-600.html new file mode 100644 index 000000000..9b6a14b82 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/aprof/profile-alloc-get-range-600.html @@ -0,0 +1,707 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/aprof/profile-cpu-get-range-600.html b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/aprof/profile-cpu-get-range-600.html new file mode 100644 index 000000000..41244d9bb --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/aprof/profile-cpu-get-range-600.html @@ -0,0 +1,1778 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/aprof/profile-lock-get-range-600.html b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/aprof/profile-lock-get-range-600.html new file mode 100644 index 000000000..9f7646680 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/aprof/profile-lock-get-range-600.html @@ -0,0 +1,367 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/cpu.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/cpu.png new file mode 100644 index 000000000..4c55f1c1b Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/lock.png b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/lock.png new file mode 100644 index 000000000..d2d95e966 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/report.md b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/report.md new file mode 100644 index 000000000..15932b871 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/reports/stage6/report.md @@ -0,0 +1,78 @@ +Заполнил базу ~3gb + +``` +1,1G server_profiling_data10080 +1,1G server_profiling_data8080 +1,1G server_profiling_data9080 +``` + +### CPU + +![cpu.png](cpu.png) + +На Flame Graph можно увидеть, что `AcceptorThread` и `SelectorThread` занимаются своей работой, т.е. никакой новой +нагрузки у них с появлением стриминга у них не добавилось, как это было на лекции. Связанно это с тем, что я решил +записывать стрим напрямую в сокет. + +Это с одной стороны хорошо, т.к. при стандартном поведении `one nio`, записью стрима сначала +занимается `worker thread`. Но в случае если мы заполнили сокет и при этом у нас еще осталась часть данных, которую +нужно дозаписать, ее записью будет заниматься непосредственно `SelectorThread`. Сделано это для того, как я понимаю, +чтобы отложить запись в сокет на будущее, когда он освободится. + +С другой стороны, рушатся все абстракции `one nio`, к тому же приходится вручную закрывать соединение в конце отправки +стрима, что негативно влияет на производительность сервера, т.к. теперь нужно каждый раз переоткрывать соединения. + +Кроме `AcceptorThread` и `SelectorThread` присутствует и `PayloadThread`, который занимает львиную долю семплов (92%), +из них занимает: + +* 50% `RequestHandler.getRange` в конце стека которого видно использование бинарного поиска для нахождения начала ренджа + с данными +* 42% `CustomHttpSession.sendStreamResponse` отправка самого ответа: + * При отправке ответа можно увидеть, что 27% семплов занимает запись в сокет + * Оставшаяся часть - работа с итератором, конвертация MemorySegment'ов в массив байтов и сравнение MemorySegment'ов + между собой + +Не думаю, что в конкретной реализации можно сильно улучшить производительность, только если как-то решить вопрос с +закрытием сокета в конце стрима + +### ALLOC + +![alloc.png](alloc.png) + +На профиле аллокаций виден только метод `Server.handleRangeRequest`, в котором происходит конвертация MemorySegment'ов в +массивы байтов и упаковка их в `ByteBuffer`. Можно увидеть, что `java/nio/ByteBuffer.allocate` занимает 18%. Думаю, что +если создать пул таких буферов и переиспользовать их для разных сокетов, то можно значительно сократить объем выделяемой +памяти, а также время на GC + +### LOCK + +![lock.png](lock.png) + +Профиль блокировок получился довольно пустым, почему-то не удается набрать нормальное число семплов (если для CPU было +200K семплов, то для LOCK всего 12), возможно, это из-за того, что блокировок самих по себе мало и профайлер редко +замечает их, поэтому и не добавляет в профиль + +Из того, что есть: + +* `AcceptorThread` работает как и раньше, ничего нового нету +* `PayloadThread` берет задачу из пула и тратит время на ручное закрытие сессии после отправки стрима +* `SelectorThread` также опрашивает сессии и ставит задачи воркерам + +Из нового только ручное закрытие сессии в `PayloadThread` + +### Выводы + +* Хоть реализация стримов через прямую запись в сокет и работает, но ломает абстракции `one nio`. Думаю, что мое ручное + закрытие сессии может неожиданным образом откликнуться в других запросах где есть завязка на то, что при старте + сервера выставляется настройка `closeSessions = false`. +* Реализована запись батчами, пары key-value пишутся не поштучно, а складываются в ByteBuffer перед отправкой. + +### Предложения по возможным улучшениям + +* Переписать в соответствии с абстракциями one nio, хоть это и приведет к тому, что SelectorThread в некоторых случаях + будет заниматься отправкой оставшейся части стрима +* Продумать что делать если ключ или значение окажется больше размера буфера при стриминге. (Сейчас буфер = 1КБ, что + делать если ключ или пара ключ-значение будет больше 1КБ) +* Добавить пул буферов при записи в стрим, чтобы при необходимости брать готовый буфер из пула, а не создавать новый +* Добавить возможность остановить получение данных из стрима без закрытия сокета (сейчас единственный способ - закрыть + сокет и получить исключение с текстом "Broken pipe") \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/request_parameter/RangeRequestParameters.java b/src/main/java/ru/vk/itmo/test/proninvalentin/request_parameter/RangeRequestParameters.java new file mode 100644 index 000000000..43951f2ec --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/request_parameter/RangeRequestParameters.java @@ -0,0 +1,37 @@ +package ru.vk.itmo.test.proninvalentin.request_parameter; + +import ru.vk.itmo.test.proninvalentin.utils.MemorySegmentFactory; +import ru.vk.itmo.test.proninvalentin.utils.Utils; + +import java.lang.foreign.MemorySegment; + +public class RangeRequestParameters { + private final MemorySegment start; + private MemorySegment end; + private boolean isValid = true; + + public RangeRequestParameters(String startString, String endString) { + if (Utils.isNullOrBlank(startString)) { + isValid = false; + start = null; + return; + } + + start = MemorySegmentFactory.fromString(startString); + if (endString != null) { + end = MemorySegmentFactory.fromString(endString); + } + } + + public boolean isValid() { + return isValid; + } + + public MemorySegment start() { + return start; + } + + public MemorySegment end() { + return end; + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/request_parameter/RequestParameters.java b/src/main/java/ru/vk/itmo/test/proninvalentin/request_parameter/RequestParameters.java new file mode 100644 index 000000000..93a2b9e0c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/request_parameter/RequestParameters.java @@ -0,0 +1,49 @@ +package ru.vk.itmo.test.proninvalentin.request_parameter; + +import ru.vk.itmo.test.proninvalentin.utils.Utils; + +public final class RequestParameters { + private final String key; + private int from; + private int ack; + private boolean isValid = true; + + public RequestParameters(String keyString, String fromString, String ackString, int clusterSize) { + key = keyString; + if (Utils.isNullOrBlank(keyString)) { + isValid = false; + return; + } + + try { + from = Utils.isNullOrBlank(fromString) + ? clusterSize + : Integer.parseInt(fromString); + ack = Utils.isNullOrBlank(ackString) + ? (from + 1) / 2 + : Integer.parseInt(ackString); + } catch (NumberFormatException ex) { + isValid = false; + } + + if (from <= 0 || clusterSize < from || from < ack || ack <= 0) { + isValid = false; + } + } + + public boolean isValid() { + return isValid; + } + + public String key() { + return key; + } + + public int from() { + return from; + } + + public int ack() { + return ack; + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/sharding/ConsistentHashing.java b/src/main/java/ru/vk/itmo/test/proninvalentin/sharding/ConsistentHashing.java index 1afb23f7f..375fd31ef 100644 --- a/src/main/java/ru/vk/itmo/test/proninvalentin/sharding/ConsistentHashing.java +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/sharding/ConsistentHashing.java @@ -5,18 +5,19 @@ import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; public class ConsistentHashing implements ShardingAlgorithm { private final int[] hashes; - private final Map hashToNodeUrl = new HashMap<>(); + private final Map hashToUrl = new HashMap<>(); public ConsistentHashing(ShardingConfig config) { List clusterUrls = config.clusterUrls(); int virtualNodesNumber = config.virtualNodesNumber(); int hashesNumber = clusterUrls.size() * virtualNodesNumber; - this.hashes = new int[hashesNumber]; initVirtualNodes(clusterUrls, virtualNodesNumber); } @@ -25,38 +26,39 @@ private void initVirtualNodes(List clusterUrls, int virtualNodesNumber) for (int i = 0; i < clusterUrls.size(); i++) { String nodeUrl = clusterUrls.get(i); for (int j = 0; j < virtualNodesNumber; j++) { - String virtualNode = "[VN: " + (virtualNodesNumber * i + j) + "]" + nodeUrl; - int hash = generateUniqueHash(virtualNode); - hashes[i * virtualNodesNumber + j] = hash; - hashToNodeUrl.put(hash, nodeUrl); + int nodeNumber = virtualNodesNumber * i + j; + String virtualNode = "[VN: " + nodeNumber + "]" + nodeUrl; + int hash = hash(virtualNode); + hashes[nodeNumber] = hash; + hashToUrl.put(hash, nodeUrl); } } Arrays.sort(hashes); } - private int generateUniqueHash(String virtualNode) { - int hash = hash(virtualNode); - for (int i = 0; hashToNodeUrl.containsKey(hash) && i != Integer.MAX_VALUE; i++) { - hash = hash(virtualNode + i); - } - if (hashToNodeUrl.containsKey(hash)) { - throw new IllegalArgumentException("Can't generate unique hash for virtual node: " + virtualNode); - } - return hash; - } - @Override - public String getNodeByKey(String key) { + public List getNodesByKey(String key, int necessaryNodeNumber) { int hash = hash(key); int nodeIndex = Arrays.binarySearch(hashes, hash); if (nodeIndex >= 0) { - return hashToNodeUrl.get(hashes[nodeIndex]); + return getNecessaryNodes(nodeIndex, necessaryNodeNumber); } nodeIndex = -nodeIndex - 2; if (nodeIndex < 0) { - return hashToNodeUrl.get(hashes[hashes.length - 1]); + return getNecessaryNodes(hashes.length - 1, necessaryNodeNumber); + } + return getNecessaryNodes(nodeIndex, necessaryNodeNumber); + } + + private List getNecessaryNodes(int firstNodeIndex, int necessaryNodeNumber) { + Set nodeUrls = new HashSet<>(necessaryNodeNumber); + int virtualNodeNumber = hashes.length; + int nodeIndex = firstNodeIndex; + for (int i = 0; i < virtualNodeNumber && nodeUrls.size() < necessaryNodeNumber; i++, nodeIndex++) { + String nodeUrl = hashToUrl.get(hashes[nodeIndex % virtualNodeNumber]); + nodeUrls.add(nodeUrl); } - return hashToNodeUrl.get(hashes[nodeIndex]); + return nodeUrls.stream().toList(); } private int hash(String key) { diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/sharding/RendezvousHashing.java b/src/main/java/ru/vk/itmo/test/proninvalentin/sharding/RendezvousHashing.java deleted file mode 100644 index ee62d8966..000000000 --- a/src/main/java/ru/vk/itmo/test/proninvalentin/sharding/RendezvousHashing.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.vk.itmo.test.proninvalentin.sharding; - -import one.nio.util.Hash; - -import java.util.List; - -public class RendezvousHashing implements ShardingAlgorithm { - - private final List clusterUrls; - private final int[] urlHashes; - - public RendezvousHashing(List clusterUrls) { - this.clusterUrls = clusterUrls; - this.urlHashes = new int[clusterUrls.size()]; - - for (int i = 0; i < clusterUrls.size(); i++) { - String clusterUrl = clusterUrls.get(i); - urlHashes[i] = Hash.murmur3(clusterUrl); - } - } - - @Override - public String getNodeByKey(final String entityId) { - int maxHash = Integer.MIN_VALUE; - int nodeId = 0; - for (int i = 0; i < clusterUrls.size(); i++) { - int urlPart = urlHashes[i]; - int pairHash = Hash.murmur3(urlPart + entityId); - if (maxHash < pairHash) { - maxHash = pairHash; - nodeId = i; - } - } - return clusterUrls.get(nodeId); - } -} - diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/sharding/ShardingAlgorithm.java b/src/main/java/ru/vk/itmo/test/proninvalentin/sharding/ShardingAlgorithm.java index a10ca3419..09f7581de 100644 --- a/src/main/java/ru/vk/itmo/test/proninvalentin/sharding/ShardingAlgorithm.java +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/sharding/ShardingAlgorithm.java @@ -1,5 +1,7 @@ package ru.vk.itmo.test.proninvalentin.sharding; +import java.util.List; + public interface ShardingAlgorithm { - String getNodeByKey(String key); + List getNodesByKey(String key, int necessaryNodeNumber); } diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/streaming/StreamingResponse.java b/src/main/java/ru/vk/itmo/test/proninvalentin/streaming/StreamingResponse.java new file mode 100644 index 000000000..0b1a1832e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/streaming/StreamingResponse.java @@ -0,0 +1,90 @@ +package ru.vk.itmo.test.proninvalentin.streaming; + +import one.nio.http.Response; +import one.nio.net.Socket; +import one.nio.util.ByteArrayBuilder; +import ru.vk.itmo.test.proninvalentin.dao.ExtendedEntry; +import ru.vk.itmo.test.proninvalentin.utils.MemorySegmentFactory; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.lang.foreign.MemorySegment; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Iterator; + +public class StreamingResponse extends Response { + + private static final byte[] START_STREAM_HEADERS_BYTES = + """ + HTTP/1.1 200 OK\r + Content-Type: text/plain\r + Transfer-Encoding: chunked\r + Connection: keep-alive\r + \r + """.getBytes(StandardCharsets.UTF_8); + + private static final String CRLF = "\r\n"; + private static final int PART_SIZE = 2 << 10; + private static final byte[] FINISH_STREAM_BYTES = ("0" + CRLF + CRLF).getBytes(StandardCharsets.UTF_8); + private static final byte[] NEW_LINE_BYTES = "\n".getBytes(StandardCharsets.UTF_8); + + @Nullable + private final Iterator> iterator; + + public StreamingResponse(@Nullable Iterator> iterator) { + super(OK, EMPTY); + this.iterator = iterator; + } + + public void start(Socket socket) throws IOException { + socket.write(START_STREAM_HEADERS_BYTES, 0, START_STREAM_HEADERS_BYTES.length); + } + + public void finish(Socket socket) throws IOException { + socket.write(FINISH_STREAM_BYTES, 0, FINISH_STREAM_BYTES.length); + } + + public void writePart(Socket socket) throws IOException { + byte[] part = getNextPart(); + String dataSize = Integer.toHexString(part.length); + byte[] data = new ByteArrayBuilder(dataSize.length() + part.length + 2 * CRLF.length()) + .append(dataSize).append(CRLF) + .append(part).append(CRLF) + .toBytes(); + + socket.write(data, 0, data.length); + } + + private byte[] getNextPart() { + ByteBuffer buffer = ByteBuffer.allocate(PART_SIZE); + int offset = 0; + + while (iterator != null && remaining() > 0) { + ExtendedEntry entry = iterator.next(); + if (entry.value() == null) { + continue; + } + + byte[] key = MemorySegmentFactory.toByteArray(entry.key()); + byte[] value = MemorySegmentFactory.toByteArray(entry.value()); + + if (offset + key.length + value.length + NEW_LINE_BYTES.length > PART_SIZE) { + break; + } + + buffer.put(offset, key); + offset += key.length; + buffer.put(offset, NEW_LINE_BYTES); + offset += NEW_LINE_BYTES.length; + buffer.put(offset, value); + offset += value.length; + } + return Arrays.copyOf(buffer.array(), offset); + } + + public int remaining() { + return iterator != null && iterator.hasNext() ? 1 : 0; + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/utils/Constants.java b/src/main/java/ru/vk/itmo/test/proninvalentin/utils/Constants.java new file mode 100644 index 000000000..dd4bdf1a8 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/utils/Constants.java @@ -0,0 +1,22 @@ +package ru.vk.itmo.test.proninvalentin.utils; + +public final class Constants { + public static final String TOO_MANY_REQUESTS = "429 Too Many Requests"; + public static final String NOT_ENOUGH_REPLICAS = "504 Not Enough Replicas"; + public static final String METHOD_ADDRESS = "/v0/entity"; + public static final String ID_PARAMETER_NAME = "id="; + public static final String REQUEST_PATH = METHOD_ADDRESS + "?" + ID_PARAMETER_NAME; + public static final String RANGE_REQUEST_PATH = "/v0/entities?start="; + public static final int SERVER_ERRORS = 500; + public static final String FROM_PARAMETER_NAME = "from="; + public static final String ACK_PARAMETER_NAME = "ack="; + public static final String START_REQUEST_PARAMETER_NAME = "start="; + public static final String END_REQUEST_PARAMETER_NAME = "end="; + public static final String NIO_TIMESTAMP_HEADER = "x-timestamp:"; + public static final String HTTP_TIMESTAMP_HEADER = "X-Timestamp"; + public static final String TERMINATION_HEADER = "X-Termination"; + public static final String TERMINATION_TRUE = "true"; + + private Constants() { + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/MemorySegmentFactory.java b/src/main/java/ru/vk/itmo/test/proninvalentin/utils/MemorySegmentFactory.java similarity index 59% rename from src/main/java/ru/vk/itmo/test/proninvalentin/MemorySegmentFactory.java rename to src/main/java/ru/vk/itmo/test/proninvalentin/utils/MemorySegmentFactory.java index d920b9e58..3a3637d05 100644 --- a/src/main/java/ru/vk/itmo/test/proninvalentin/MemorySegmentFactory.java +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/utils/MemorySegmentFactory.java @@ -1,7 +1,7 @@ -package ru.vk.itmo.test.proninvalentin; +package ru.vk.itmo.test.proninvalentin.utils; -import ru.vk.itmo.dao.BaseEntry; -import ru.vk.itmo.dao.Entry; +import ru.vk.itmo.test.proninvalentin.dao.ExtendedBaseEntry; +import ru.vk.itmo.test.proninvalentin.dao.ExtendedEntry; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; @@ -28,23 +28,13 @@ public static MemorySegment fromString(String data) { return MemorySegment.ofArray(data.getBytes(StandardCharsets.UTF_8)); } - public static Entry toMemorySegment(String key, String value) { - if (key == null || value == null) { - throw new IllegalArgumentException(); - } - - MemorySegment msKey = fromString(key); - MemorySegment msValue = fromString(value); - return new BaseEntry<>(msKey, msValue); - } - - public static Entry toDeletedMemorySegment(String key) { + public static ExtendedEntry toDeletedMemorySegment(String key, long timestamp) { if (key == null) { throw new IllegalArgumentException(); } MemorySegment msKey = fromString(key); - return new BaseEntry<>(msKey, null); + return new ExtendedBaseEntry<>(msKey, null, timestamp); } } diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/utils/Utils.java b/src/main/java/ru/vk/itmo/test/proninvalentin/utils/Utils.java new file mode 100644 index 000000000..13d8de5e0 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/utils/Utils.java @@ -0,0 +1,104 @@ +package ru.vk.itmo.test.proninvalentin.utils; + +import one.nio.http.HttpServerConfig; +import one.nio.http.Request; +import one.nio.http.Response; +import one.nio.server.AcceptorConfig; +import ru.vk.itmo.ServiceConfig; + +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.http.HttpRequest; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +public final class Utils { + private static final int SOFT_SHUT_DOWN_TIME = 20; + private static final int HARD_SHUT_DOWN_TIME = 10; + private static final Set SUPPORTED_HTTP_METHODS = Set.of( + Request.METHOD_GET, + Request.METHOD_PUT, + Request.METHOD_DELETE + ); + public static final Map httpCodeMapping = Map.of( + HttpURLConnection.HTTP_OK, Response.OK, + HttpURLConnection.HTTP_ACCEPTED, Response.ACCEPTED, + HttpURLConnection.HTTP_CREATED, Response.CREATED, + HttpURLConnection.HTTP_BAD_REQUEST, Response.BAD_REQUEST, + HttpURLConnection.HTTP_INTERNAL_ERROR, Response.INTERNAL_ERROR, + HttpURLConnection.HTTP_NOT_FOUND, Response.NOT_FOUND, + HttpURLConnection.HTTP_CLIENT_TIMEOUT, Response.REQUEST_TIMEOUT + ); + + private Utils() { + } + + public static Map buildRequests(Request request, List nodeUrls, String entryId) { + HashMap httpRequests = new HashMap<>(nodeUrls.size()); + for (String nodeUrl : nodeUrls) { + httpRequests.put(nodeUrl, buildProxyRequest(request, nodeUrl, entryId)); + } + return httpRequests; + } + + public static HttpRequest buildProxyRequest(Request request, String nodeUrl, String parameter) { + byte[] body = request.getBody(); + return HttpRequest.newBuilder(URI.create(nodeUrl + Constants.REQUEST_PATH + parameter)) + .method(request.getMethodName(), body == null + ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofByteArray(body)) + .setHeader(Constants.TERMINATION_HEADER, Constants.TERMINATION_TRUE) + .build(); + } + + public static boolean hasHandler(Request request) { + return isSingleRequest(request) || isRangeRequest(request); + } + + private static boolean isSingleRequest(Request request) { + return request.getURI().startsWith(Constants.REQUEST_PATH); + } + + public static boolean isRangeRequest(Request request) { + return request.getURI().startsWith(Constants.RANGE_REQUEST_PATH); + } + + public static boolean isSupportedMethod(int httpMethod) { + return SUPPORTED_HTTP_METHODS.contains(httpMethod); + } + + public static void shutdownGracefully(ExecutorService pool) { + pool.shutdown(); + try { + pool.awaitTermination(SOFT_SHUT_DOWN_TIME, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + pool.shutdownNow(); + try { + pool.awaitTermination(HARD_SHUT_DOWN_TIME, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + public static boolean isNullOrBlank(String str) { + return str == null || str.isBlank(); + } + + public static HttpServerConfig createServerConfig(ServiceConfig serviceConfig) { + HttpServerConfig serverConfig = new HttpServerConfig(); + AcceptorConfig acceptorConfig = new AcceptorConfig(); + acceptorConfig.port = serviceConfig.selfPort(); + acceptorConfig.reusePort = true; + + serverConfig.acceptors = new AcceptorConfig[]{acceptorConfig}; + serverConfig.closeSessions = true; + return serverConfig; + } +} diff --git a/src/main/java/ru/vk/itmo/test/proninvalentin/workers/WorkerPool.java b/src/main/java/ru/vk/itmo/test/proninvalentin/workers/WorkerPool.java index 01c506011..acd082724 100644 --- a/src/main/java/ru/vk/itmo/test/proninvalentin/workers/WorkerPool.java +++ b/src/main/java/ru/vk/itmo/test/proninvalentin/workers/WorkerPool.java @@ -2,8 +2,12 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public final class WorkerPool { + private static final int SOFT_SHUT_DOWN_TIME = 20; + private static final int HARD_SHUT_DOWN_TIME = 10; + public final ExecutorService pool; public WorkerPool(WorkerPoolConfig config) { @@ -13,4 +17,20 @@ public WorkerPool(WorkerPoolConfig config) { preStartedPool.prestartAllCoreThreads(); pool = preStartedPool; } + + public void gracefulShutdown() { + pool.shutdown(); + try { + pool.awaitTermination(SOFT_SHUT_DOWN_TIME, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + pool.shutdownNow(); + try { + pool.awaitTermination(HARD_SHUT_DOWN_TIME, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } } diff --git a/src/main/java/ru/vk/itmo/test/shishiginstepan/lua_scripts/random_rw_requests.lua b/src/main/java/ru/vk/itmo/test/shishiginstepan/lua_scripts/random_rw_requests.lua index 403f2a6a2..ec72fe090 100644 --- a/src/main/java/ru/vk/itmo/test/shishiginstepan/lua_scripts/random_rw_requests.lua +++ b/src/main/java/ru/vk/itmo/test/shishiginstepan/lua_scripts/random_rw_requests.lua @@ -1,17 +1,17 @@ base_url = "/v0/entity?" headers = {} -headers["Host"] = "localhost:8080" +headers["Host"] = "localhost:8083" wrk.port = 8080 function build_query_param(param, value) return param .. "=" .. value end function generateKey() - return math.random(0, 100000) + return math.random(0, 100) end function generateValue() - key_part = math.random(10000000000, 25000000000) + key_part = math.random(10000, 25000) key = "" for i = 0, 20, 1 do key = key .. key_part @@ -34,9 +34,9 @@ end request = function() -- magic_selector = math.random(0,3) -- if magic_selector < 2 then - -- return getOneRandom() + return getOneRandom() -- elseif magic_selector == 3 then - return putOneRandom() + -- return putOneRandom() -- else -- return deleteOneRandom() -- end diff --git a/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling5.MD b/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling5.MD new file mode 100644 index 000000000..f297651b0 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling5.MD @@ -0,0 +1,60 @@ +# **Этап 5. Асинхронщина*уащ* + +## Вступление + +Этот этап оказался самым сложным для меня. Для его реализации мне понадобилось примерно в 3 раза больше времени. + +### Сложности при выполнении этапа + +1. Знакомство с Future's для меня прошло достаточно гладко, но потребовало много времени чтобы разобраться в тонкостях
+(особенно как мне не получить ошибку сборки\код климат) +2. Переход на джава клиент был не простым, но это скорее больше связано с асинхронностью, чем с самим клиентом. + +### С чем я плохо справился + +Так и не получилось исправить асбтракции которые я пытался рефакторить на основе прошлого этапа, более того рефакторинг сервера занял много времени и дебага. + + +## Профилирование +Всех эксперименты проводитились на одной машине, ноды поднимались в разных процессах с ограничением памяти по 128 mb. +Во время всех экспериментов каждая нода читала только с диска, без мем таблицы. + +Профили этого этапа заполнены джавовым клиентом, особенно много он аллоцирует (в сравнении с клиентом one nio). + + +## Нагрузочное тестирование + +**Замеры чтения:** + +| % | l | +|-------|----------| +| 90 | 6.01 ms | +| 99 | 43.39 ms | +| 99.99 | 49.66 ms | +Requests/sec: 11545.53 + +Я рассчитывал что получится понизить латенси засчет перехода на асинхронное взаимодействие,
+но переход на джава http клиент привнес его оверхед и миллионы интересных столбиков на профиле, из-за чего
+для сохранения latency прошлого этапа пришлось понизить RPS примерно в 1.5 раза (печаль) +## Выводы + +Я дуамю что если бы изначально выбрал java http клиент то мог бы провести более качественное сравнение.
+В общем я считаю что задержка запросов могла бы уменьшиться, но ценой большого количества вспомогательных потоков, так что в принципе
+это хороший способ ускорить приложение и утилизировать ресурсы. Но проблемы с синхронизацией потоков могут повлиять
+( и сильно повлияли) на работоспособность системы, так как увеличилось количество блокировок. + +**Приложение**: +### PUT запросы +##### СPU +![](profiling_artifacts_5/putCPU.png) + +##### ALLOC +![](profiling_artifacts_5/putAlloc.png) + + +### GET запросы +##### СPU +![](profiling_artifacts_5/getCpu.png) + +##### ALLOC +![](profiling_artifacts_5/getAlloc.png) diff --git a/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling_artifacts_5/getAlloc.png b/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling_artifacts_5/getAlloc.png new file mode 100644 index 000000000..3e4a942ad Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling_artifacts_5/getAlloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling_artifacts_5/getCpu.png b/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling_artifacts_5/getCpu.png new file mode 100644 index 000000000..ec9fd12f3 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling_artifacts_5/getCpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling_artifacts_5/putAlloc.png b/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling_artifacts_5/putAlloc.png new file mode 100644 index 000000000..8d7439212 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling_artifacts_5/putAlloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling_artifacts_5/putCPU.png b/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling_artifacts_5/putCPU.png new file mode 100644 index 000000000..13f1fefc3 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/shishiginstepan/profiling/profiling_artifacts_5/putCPU.png differ diff --git a/src/main/java/ru/vk/itmo/test/shishiginstepan/server/DistributedDao.java b/src/main/java/ru/vk/itmo/test/shishiginstepan/server/DistributedDao.java index 09f0623dd..ed3f3f3c0 100644 --- a/src/main/java/ru/vk/itmo/test/shishiginstepan/server/DistributedDao.java +++ b/src/main/java/ru/vk/itmo/test/shishiginstepan/server/DistributedDao.java @@ -1,5 +1,7 @@ package ru.vk.itmo.test.shishiginstepan.server; +import one.nio.async.CustomThreadFactory; +import one.nio.http.HttpSession; import one.nio.util.Hash; import org.apache.log4j.Logger; import ru.vk.itmo.dao.Dao; @@ -8,77 +10,63 @@ import java.io.IOException; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; +import java.net.URI; +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.ArrayList; import java.util.HashSet; -import java.util.Iterator; import java.util.List; -import java.util.PriorityQueue; import java.util.Set; import java.util.SortedMap; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; -public class DistributedDao implements Dao> { +public class DistributedDao { - private final Logger logger = Logger.getLogger("lsm-db-server"); + private static final String BASE_REQUEST_PATH = "/v0/entity?id="; + private static final String TIMESTAMP_HEADER = "X-timestamp"; + private static final String INNER_HEADER = "X-inner-request"; private static final int MULTIPLICATION_FACTOR = 128; - private final NodeWrapper localDao; - private final SortedMap nodeRing = new ConcurrentSkipListMap<>(); + private final Logger logger = Logger.getLogger("lsm-db-server"); + private final Dao> localDao; + private final String selfUrl; + + private final ExecutorService callbackExecutor = Executors.newFixedThreadPool( + 1, + new CustomThreadFactory("callback-workers") + ); + private final ExecutorService remoteExecutor = Executors.newFixedThreadPool( + Runtime.getRuntime().availableProcessors() / 2 + 1, + new CustomThreadFactory("remote-workers") + ); - private final List nodesUnique = new ArrayList<>(); + private final SortedMap nodeRing = new ConcurrentSkipListMap<>(); + + private final HttpClient httpClient; private int totalNodes; private int quorum; public DistributedDao(Dao> localDao, String selfUrl) { - this.localDao = new NodeWrapper(localDao, selfUrl); - } - - private static final class NotEnoughUniqueNodes extends RuntimeException { - } - - public static final class NoConsensus extends RuntimeException { + this.localDao = localDao; + this.selfUrl = selfUrl; + this.httpClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofMillis(300)) + .executor(Executors.newFixedThreadPool(1)) + .version(HttpClient.Version.HTTP_1_1) + .executor(remoteExecutor) + .build(); } - public static final class ClusterLimitExceeded extends RuntimeException { - } - - private static class NodeWrapper implements Dao> { - Dao> dao; - String realNodeKey; - - public NodeWrapper(Dao> dao, String key) { - this.dao = dao; - this.realNodeKey = key; - } - - @Override - public Iterator> get(MemorySegment from, MemorySegment to) { - return dao.get(from, to); - } - - @Override - public EntryWithTimestamp get(MemorySegment key) { - return dao.get(key); - } - - @Override - public void upsert(EntryWithTimestamp entry) { - dao.upsert(entry); - } - - @Override - public void close() throws IOException { - dao.close(); - } - } - - public void addNode(Dao> daoNode, String token) { - NodeWrapper wrapper = new NodeWrapper(daoNode, token); - nodesUnique.add(wrapper); + public void addNode(String token) { for (int i = 0; i < MULTIPLICATION_FACTOR; i++) { nodeRing.put( Hash.murmur3((token + i)), - wrapper + token ); } totalNodes++; @@ -87,28 +75,28 @@ public void addNode(Dao> daoNod // Здесь мы будем итерироваться по кольцу и выбирать ноды, причем не просто первые N, // а первые N которые памятся на уникальные реальные ноды - private List selectMultipleNodes(String key, int n) { + private List selectMultipleNodes(String key, int n) { int numberOfNodes = n; int keyHash = Hash.murmur3(key); - List chosenNodes = new ArrayList<>(numberOfNodes); - SortedMap ringPart = this.nodeRing.tailMap(keyHash); + List chosenNodes = new ArrayList<>(numberOfNodes); + SortedMap ringPart = this.nodeRing.tailMap(keyHash); Set tokensOfChosenNodes = new HashSet<>(); - for (NodeWrapper node : ringPart.values()) { + for (String node : ringPart.values()) { if (numberOfNodes == 0) { break; } - if (tokensOfChosenNodes.add(node.realNodeKey)) { + if (tokensOfChosenNodes.add(node)) { chosenNodes.add(node); numberOfNodes--; } } // здесь мы можем дойти до конца мапы которая является кольцом нод. если мы до сих пор не набрали нужное // кол-во нод, нужно теперь посмотреть другую часть кольца. ringPart = this.nodeRing.headMap(keyHash); - for (NodeWrapper node : ringPart.values()) { + for (String node : ringPart.values()) { if (numberOfNodes == 0) { break; } - if (tokensOfChosenNodes.add(node.realNodeKey)) { + if (tokensOfChosenNodes.add(node)) { chosenNodes.add(node); numberOfNodes--; } @@ -121,94 +109,167 @@ private List selectMultipleNodes(String key, int n) { return chosenNodes; } - @Override - public Iterator> get(MemorySegment from, MemorySegment to) { - return null; - } - - @Override - public EntryWithTimestamp get(MemorySegment key) { + public EntryWithTimestamp getLocal(MemorySegment key) { return localDao.get(key); } - @Override - public void upsert(EntryWithTimestamp entry) { + public void upsertLocal(EntryWithTimestamp entry) { localDao.upsert(entry); } - @Override public void close() throws IOException { - for (Dao> dao : nodesUnique) { - dao.close(); - } + this.localDao.close(); + httpClient.close(); } - public EntryWithTimestamp get(MemorySegment key, Integer from, Integer ack) { - Integer shouldAck = ack; - Integer requestFrom = from; - if (shouldAck == null) { - shouldAck = quorum; - } - if (requestFrom == null) { - requestFrom = totalNodes; - } + public void getByQuorum(MemorySegment key, Integer ack, Integer from, HttpSession session) { + Integer shouldAck = ack == null ? quorum : ack; + Integer requestFrom = from == null ? totalNodes : from; + if (shouldAck > totalNodes || requestFrom > totalNodes || shouldAck == 0 || requestFrom == 0) { throw new ClusterLimitExceeded(); } - List nodesToPoll = + List nodesToPoll = selectMultipleNodes( new String(key.toArray(ValueLayout.JAVA_BYTE), StandardCharsets.UTF_8), requestFrom ); - PriorityQueue> entries = new PriorityQueue<>( - requestFrom, - (e1, e2) -> -e1.timestamp().compareTo(e2.timestamp()) - ); + + var resultHandler = new MergeResultHandler(shouldAck, requestFrom, session); for (var node : nodesToPoll) { - try { - entries.add( - node.get(key) - ); - } catch (Exception e) { - logger.error(e); + if (node.equals(this.selfUrl)) { + getLocalAsync(resultHandler, key); + } else { + getRemoteAsync(key, node, resultHandler); } } - if (entries.size() < shouldAck) { - throw new NoConsensus(); - } - return entries.peek(); } - public void upsert(EntryWithTimestamp entry, Integer from, Integer ack) { - Integer shouldAck = ack; - Integer requestFrom = from; - if (shouldAck == null) { - shouldAck = quorum; - } - if (requestFrom == null) { - requestFrom = totalNodes; + private void getLocalAsync(MergeResultHandler resultHandler, MemorySegment key) { + EntryWithTimestamp entry = localDao.get(key); + ResponseWrapper response; + if (entry.value() == null) { + response = new ResponseWrapper(404, new byte[]{}, entry.timestamp()); + } else { + response = new ResponseWrapper( + 200, + entry.value().toArray(ValueLayout.JAVA_BYTE), + entry.timestamp()); } - if (shouldAck > totalNodes || requestFrom > totalNodes || shouldAck == 0 || requestFrom == 0) { + resultHandler.add(response); + } + + private void getRemoteAsync(MemorySegment key, String node, MergeResultHandler resultHandler) { + HttpRequest request = request(node, segmentToString(key)) + .GET() + .build(); + CompletableFuture> future = httpClient.sendAsync( + request, + HttpResponse.BodyHandlers.ofByteArray() + ); + future.whenCompleteAsync((r, e) -> { + if (e == null) { + Long timestamp = Long.parseLong(r.headers().firstValue(TIMESTAMP_HEADER).get()); + resultHandler.add(new ResponseWrapper(r.statusCode(), r.body(), timestamp)); + } else { + resultHandler.add(new ResponseWrapper(500, new byte[]{})); + logger.error(e); + } + }, + callbackExecutor).exceptionally((e) -> { + logger.error(e); + return null; + }); + } + + private String segmentToString(MemorySegment source) { + return new String(source.toArray(ValueLayout.JAVA_BYTE), StandardCharsets.UTF_8); + } + + private static final class NotEnoughUniqueNodes extends RuntimeException { + } + + public void upsertByQuorum( + EntryWithTimestamp entry, + Integer ack, + Integer from, + HttpSession session + ) { + Integer shouldAck = ack == null ? quorum : ack; + Integer requestFrom = from == null ? totalNodes : from; + + if (shouldAck > requestFrom || requestFrom > totalNodes || shouldAck == 0 || requestFrom == 0) { throw new ClusterLimitExceeded(); } - List nodesToPoll = + List nodesToPoll = selectMultipleNodes( new String(entry.key().toArray(ValueLayout.JAVA_BYTE), StandardCharsets.UTF_8), requestFrom ); - Integer n = 0; + var resultHandler = new MergeResultHandler(shouldAck, requestFrom, session); for (var node : nodesToPoll) { - try { - node.upsert(entry); - n++; - } catch (Exception e) { - logger.error(e); + if (node.equals(this.selfUrl)) { + upsertLocalAsync(entry, resultHandler); + } else { + upsertRemoteASync(entry, node, resultHandler); } } - if (n < shouldAck) { - throw new NoConsensus(); + } + + private void upsertRemoteASync(EntryWithTimestamp entry, String node, + MergeResultHandler resultHandler) { + HttpRequest.Builder requestBuilder = request(node, segmentToString(entry.key())) + .header(TIMESTAMP_HEADER, String.valueOf(entry.timestamp())); + HttpRequest request; + if (entry.value() == null) { + request = requestBuilder.DELETE().build(); + } else { + request = requestBuilder.PUT( + HttpRequest.BodyPublishers.ofByteArray(entry.value().toArray(ValueLayout.JAVA_BYTE)) + ) + .build(); + } + CompletableFuture> future = httpClient.sendAsync( + request, + HttpResponse.BodyHandlers.ofByteArray() + ); + future.whenCompleteAsync((r, e) -> { + if (e == null) { + resultHandler.add(new ResponseWrapper(r.statusCode(), new byte[]{})); + } else { + resultHandler.add(new ResponseWrapper(500, new byte[]{})); + logger.error(e); + } + }, callbackExecutor).exceptionally((e) -> { + logger.error(e); + return null; + }); + } + + private void upsertLocalAsync(EntryWithTimestamp entry, MergeResultHandler resultHandler) { + localDao.upsert(entry); + Integer status = 201; + if (entry.value() == null) { + status = 202; } + resultHandler.add( + new ResponseWrapper(status, new byte[]{}, entry.timestamp()) + ); } + + public static final class NoConsensus extends RuntimeException { + } + + public static final class ClusterLimitExceeded extends RuntimeException { + } + + private HttpRequest.Builder request(String node, String key) { + return HttpRequest + .newBuilder(URI.create(node + BASE_REQUEST_PATH + key)) + .header(INNER_HEADER, "1") + .timeout(Duration.ofMillis(500)); + } + } diff --git a/src/main/java/ru/vk/itmo/test/shishiginstepan/server/MergeResultHandler.java b/src/main/java/ru/vk/itmo/test/shishiginstepan/server/MergeResultHandler.java new file mode 100644 index 000000000..8356d8121 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/shishiginstepan/server/MergeResultHandler.java @@ -0,0 +1,78 @@ +package ru.vk.itmo.test.shishiginstepan.server; + +import one.nio.http.HttpSession; +import one.nio.http.Response; +import org.apache.log4j.Logger; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +class MergeResultHandler { + private final Logger logger = Logger.getLogger("lsm-db-server"); + private final HttpSession session; + private final AtomicInteger totalResponseCounter; + private final AtomicInteger successCounter; + + private final ConcurrentSkipListSet results; + + private final AtomicBoolean closed = new AtomicBoolean(false); + + MergeResultHandler(Integer ack, Integer from, HttpSession session) { + this.session = session; + this.totalResponseCounter = new AtomicInteger(from); + this.successCounter = new AtomicInteger(ack); + this.results = new ConcurrentSkipListSet<>((r1, r2) -> Long.compare(r2.timestamp(), r1.timestamp())); + } + + public synchronized void add(ResponseWrapper response) { + if (closed.get()) { + return; + } + int total = this.totalResponseCounter.decrementAndGet(); + int success; + if ((response.status() == HttpURLConnection.HTTP_OK + || response.status() == HttpURLConnection.HTTP_CREATED + || response.status() == HttpURLConnection.HTTP_ACCEPTED + || response.status() == HttpURLConnection.HTTP_NOT_FOUND)) { + success = successCounter.decrementAndGet(); + results.add(response); + if (success == 0) { + closed.set(true); + sendResponse(); + return; + } + } + if (total == 0) { + closed.set(true); + sendError(); + } + + } + + private void sendResponse() { + try { + ResponseWrapper response = results.getFirst(); + session.sendResponse( + new Response( + String.valueOf(response.status()), + response.data() + ) + ); + } catch (IOException e) { + logger.error(e); + session.close(); + } + } + + private void sendError() { + try { + session.sendResponse(new Response(Response.GATEWAY_TIMEOUT, Response.EMPTY)); + } catch (IOException e) { + logger.error(e); + session.close(); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/shishiginstepan/server/RemoteDaoNode.java b/src/main/java/ru/vk/itmo/test/shishiginstepan/server/RemoteDaoNode.java deleted file mode 100644 index b0a61a49f..000000000 --- a/src/main/java/ru/vk/itmo/test/shishiginstepan/server/RemoteDaoNode.java +++ /dev/null @@ -1,115 +0,0 @@ -package ru.vk.itmo.test.shishiginstepan.server; - -import one.nio.http.HttpClient; -import one.nio.http.HttpException; -import one.nio.http.Response; -import one.nio.net.ConnectionString; -import one.nio.pool.PoolException; -import ru.vk.itmo.dao.Dao; -import ru.vk.itmo.test.shishiginstepan.dao.EntryWithTimestamp; - -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 RemoteDaoNode implements Dao> { - - private final HttpClient client; - - private static final String BASE_REQUEST_PATH = "/v0/entity?id="; - - public RemoteDaoNode(String nodeUrl) { - client = new HttpClient(new ConnectionString(nodeUrl)); // возможно стоит keepalive врубить - } - - private static class RemoteDaoNodeFailure extends RuntimeException { - } - - private static class RemoteNodeAccessFailure extends RuntimeException { - public RemoteNodeAccessFailure(Exception e) { - super(e); - } - } - - private String segmentToString(MemorySegment source) { - return new String(source.toArray(ValueLayout.JAVA_BYTE), StandardCharsets.UTF_8); - } - - @Override - public Iterator> get(MemorySegment from, MemorySegment to) { - return null; - } - - @Override - public EntryWithTimestamp get(MemorySegment key) { - Response response; - String innerRequest = "X-inner-request: " + 1; - try { - response = client.get(BASE_REQUEST_PATH + segmentToString(key), innerRequest); - } catch (HttpException | PoolException | IOException e) { - throw new RemoteNodeAccessFailure(e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RemoteNodeAccessFailure(e); - } - var timestamp = getTimestampHeaderValue(response); - - if (response.getStatus() == 404) { - if (timestamp != null) { - return new EntryWithTimestamp<>( - key, - null, - timestamp - ); - } - return new EntryWithTimestamp<>(key, null, 0L); - } - MemorySegment value = MemorySegment.ofArray(response.getBody()); - return new EntryWithTimestamp<>(key, value, timestamp); - } - - private static Long getTimestampHeaderValue(Response response) { - var timestamp = response.getHeader("X-timestamp"); - if (timestamp == null) { - return null; - } - return Long.parseLong(timestamp.substring(2)); - } - - @Override - public void upsert(EntryWithTimestamp entry) { - Response response; - String timestampHeader = "X-timestamp: " + entry.timestamp(); - String innerRequest = "X-inner-request: 1"; - try { - if (entry.value() == null) { - response = client.delete( - BASE_REQUEST_PATH + segmentToString(entry.key()), - timestampHeader, innerRequest - ); - } else { - response = client.put( - BASE_REQUEST_PATH + segmentToString(entry.key()), - entry.value().toArray(ValueLayout.JAVA_BYTE), - timestampHeader, - innerRequest - ); - } - } catch (HttpException | PoolException | IOException e) { - throw new RemoteNodeAccessFailure(e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RemoteNodeAccessFailure(e); - } - if (response.getStatus() >= 400) { - throw new RemoteDaoNodeFailure(); - } - } - - @Override - public void close() { - client.close(); - } -} diff --git a/src/main/java/ru/vk/itmo/test/shishiginstepan/server/ResponseWrapper.java b/src/main/java/ru/vk/itmo/test/shishiginstepan/server/ResponseWrapper.java new file mode 100644 index 000000000..7deae1e0f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/shishiginstepan/server/ResponseWrapper.java @@ -0,0 +1,8 @@ +package ru.vk.itmo.test.shishiginstepan.server; + +public record ResponseWrapper(int status, byte[] data, long timestamp) { + + public ResponseWrapper(int status, byte[] data) { + this(status, data, 0); + } +} diff --git a/src/main/java/ru/vk/itmo/test/shishiginstepan/server/Server.java b/src/main/java/ru/vk/itmo/test/shishiginstepan/server/Server.java index cf97fff6f..4d532f9a3 100644 --- a/src/main/java/ru/vk/itmo/test/shishiginstepan/server/Server.java +++ b/src/main/java/ru/vk/itmo/test/shishiginstepan/server/Server.java @@ -1,20 +1,17 @@ package ru.vk.itmo.test.shishiginstepan.server; +import one.nio.async.CustomThreadFactory; import one.nio.http.HttpException; 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 org.apache.log4j.Logger; import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.test.shishiginstepan.dao.EntryWithTimestamp; -import javax.annotation.Nonnull; import java.io.IOException; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; @@ -27,16 +24,13 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; public class Server extends HttpServer { private final DistributedDao dao; - private static final String BASE_PATH = "/v0/entity"; - private static final String TIMESTAMP_HEADER = "X-timestamp: "; private static final String INNER_REQUEST_HEADER = "X-inner-request: "; + private static final String TIMESTAMP_HEADER = "X-timestamp: "; private final Logger logger = Logger.getLogger("lsm-db-server"); @@ -44,28 +38,19 @@ public class Server extends HttpServer { private static final Duration defaultTimeout = Duration.of(200, ChronoUnit.MILLIS); private static final ZoneId ServerZoneId = ZoneId.of("+0"); - private static final ThreadFactory threadFactory = new ThreadFactory() { - private final AtomicInteger workerNamingCounter = new AtomicInteger(0); - - @Override - public Thread newThread(@Nonnull Runnable r) { - return new Thread(r, "lsm-db-worker-" + workerNamingCounter.getAndIncrement()); - } - }; - public Server(ServiceConfig config, DistributedDao dao) throws IOException { super(configFromServiceConfig(config)); this.dao = dao; //TODO подумать какое значение будет разумным BlockingQueue requestQueue = new ArrayBlockingQueue<>(100); - int processors = Runtime.getRuntime().availableProcessors(); + int processors = Runtime.getRuntime().availableProcessors() / 2; this.executor = new ThreadPoolExecutor( - processors, - processors, + processors + 1, + processors + 1, 32, TimeUnit.SECONDS, requestQueue, - threadFactory + new CustomThreadFactory("lsm-db-workers") ); } @@ -74,6 +59,7 @@ private static HttpServerConfig configFromServiceConfig(ServiceConfig serviceCon AcceptorConfig acceptorConfig = new AcceptorConfig(); acceptorConfig.reusePort = true; acceptorConfig.port = serviceConfig.selfPort(); + serverConfig.selectors = Runtime.getRuntime().availableProcessors() / 2; serverConfig.acceptors = new AcceptorConfig[]{acceptorConfig}; serverConfig.closeSessions = true; @@ -87,11 +73,11 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti executor.execute(() -> { try { handleRequestWithExceptions(request, session, requestExpirationDate); - } catch (IOException exceptionHandlingException) { - logger.error(exceptionHandlingException.initCause(exceptionHandlingException)); - session.scheduleClose(); + } catch (IOException e) { + session.close(); } }); + } catch (RejectedExecutionException e) { logger.error(e); session.sendResponse(new Response(Response.SERVICE_UNAVAILABLE, Response.EMPTY)); @@ -104,10 +90,13 @@ private void handleRequestWithExceptions(Request request, try { handleRequestInOtherThread(request, session, requestExpirationDate); } catch (IOException e) { + logger.error(e); session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); } catch (HttpException | DistributedDao.ClusterLimitExceeded e) { + logger.error(e); session.sendError(Response.BAD_REQUEST, null); } catch (DistributedDao.NoConsensus e) { + logger.error(e); session.sendResponse(new Response(Response.GATEWAY_TIMEOUT, Response.EMPTY)); } } @@ -118,138 +107,115 @@ private void handleRequestInOtherThread( LocalDateTime requestExpirationDate ) throws IOException, HttpException { if (LocalDateTime.now(ServerZoneId).isAfter(requestExpirationDate)) { + logger.info("Too bad request timing, skipped"); session.sendResponse(new Response(Response.SERVICE_UNAVAILABLE, Response.EMPTY)); } else { - super.handleRequest(request, session); + String rawKey = request.getParameter("id="); + if (rawKey == null || rawKey.isEmpty()) { + throw new HttpException("parameter id is unfilled"); + } + MemorySegment key = MemorySegment.ofArray(rawKey.getBytes(StandardCharsets.UTF_8)); + String rawAck = request.getParameter("ack="); + String rawFrom = request.getParameter("from="); + Integer ack = rawAck == null ? null : Integer.parseInt(rawAck); + Integer from = rawFrom == null ? null : Integer.parseInt(rawFrom); + + final boolean innerRequest = request.getHeader(INNER_REQUEST_HEADER) != null; + + switch (request.getMethod()) { + case Request.METHOD_GET -> handleGet(session, innerRequest, key, ack, from); + case Request.METHOD_PUT -> handlePut(request, session, innerRequest, key, ack, from); + case Request.METHOD_DELETE -> handleDelete(request, session, innerRequest, key, ack, from); + default -> session.sendResponse(notAllowed()); + } } } - @Path(BASE_PATH) - @RequestMethod(Request.METHOD_GET) - public Response getOne( - @Param(value = "id", required = true) String id, - @Param(value = "ack") Integer ack, - @Param(value = "from") Integer from, - Request request - ) { - if (id.isEmpty()) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); - } - - MemorySegment key = MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)); - EntryWithTimestamp entry; - - if (request.getHeader(INNER_REQUEST_HEADER) == null) { - entry = dao.get( + private void handleDelete( + Request request, + HttpSession session, + boolean innerRequest, + MemorySegment key, + Integer ack, + Integer from + ) throws IOException { + if (innerRequest) { + Long timestamp = Long.parseLong(request.getHeader(TIMESTAMP_HEADER)); + EntryWithTimestamp entry = new EntryWithTimestamp<>(key, null, timestamp); + dao.upsertLocal(entry); + session.sendResponse(new Response(Response.CREATED, Response.EMPTY)); + } else { + EntryWithTimestamp entry = new EntryWithTimestamp<>( key, - from, - ack + null, + System.currentTimeMillis() ); - } else { - entry = dao.get(key); - } - - if (entry.value() == null) { - Response response = new Response(Response.NOT_FOUND, Response.EMPTY); - response.addHeader(TIMESTAMP_HEADER + entry.timestamp()); - return response; + dao.upsertByQuorum(entry, ack, from, session); } - Response response = new Response(Response.OK, entry.value().toArray(ValueLayout.JAVA_BYTE)); - response.addHeader(TIMESTAMP_HEADER + entry.timestamp()); - return response; } - @Path(BASE_PATH) - @RequestMethod(Request.METHOD_PUT) - public Response putOne( - @Param(value = "id", required = true) String id, - @Param(value = "ack") Integer ack, - @Param(value = "from") Integer from, - Request request - ) { - if (id.isEmpty()) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); - } - MemorySegment key = MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)); - MemorySegment val = MemorySegment.ofArray(request.getBody()); - if (request.getHeader(INNER_REQUEST_HEADER) == null) { - dao.upsert( - new EntryWithTimestamp<>( - key, - val, - System.currentTimeMillis()), - from, - ack + private void handlePut( + Request request, + HttpSession session, + boolean innerRequest, + MemorySegment key, + Integer ack, + Integer from + ) throws IOException { + if (innerRequest) { + Long timestamp = Long.parseLong(request.getHeader(TIMESTAMP_HEADER)); + EntryWithTimestamp entry = new EntryWithTimestamp<>( + key, + MemorySegment.ofArray(request.getBody()), timestamp ); + dao.upsertLocal(entry); + session.sendResponse(new Response(Response.CREATED, Response.EMPTY)); } else { - var timestamp = getTimestampHeaderValue(request); - if (timestamp != null) { - dao.upsert( - new EntryWithTimestamp<>( - key, - val, - timestamp - ) - ); - } + EntryWithTimestamp entry = new EntryWithTimestamp<>( + key, + MemorySegment.ofArray(request.getBody()), + System.currentTimeMillis() + ); + dao.upsertByQuorum(entry, ack, from, session); } - return new Response(Response.CREATED, Response.EMPTY); } - @Path(BASE_PATH) - @RequestMethod(Request.METHOD_DELETE) - public Response deleteOne( - @Param(value = "id", required = true) String id, - @Param(value = "ack") String ack, - @Param(value = "from") String from, - Request request - ) { - if (id.isEmpty()) { - return new Response(Response.BAD_REQUEST, Response.EMPTY); - } - MemorySegment key = MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)); - if (request.getHeader(INNER_REQUEST_HEADER) == null) { - dao.upsert( - new EntryWithTimestamp<>( - key, - null, - System.currentTimeMillis()), - from == null ? null : Integer.parseInt(from), - ack == null ? null : Integer.parseInt(ack) - ); + private void handleGet( + HttpSession session, boolean innerRequest, + MemorySegment key, + Integer ack, + Integer from + ) throws IOException { + if (innerRequest) { + handleLocalGet(session, key); } else { - var timestamp = getTimestampHeaderValue(request); - if (timestamp != null) { - dao.upsert( - new EntryWithTimestamp<>( - key, - null, - timestamp - ) - ); - } + dao.getByQuorum(key, ack, from, session); } - - return new Response(Response.ACCEPTED, Response.EMPTY); } - private static Long getTimestampHeaderValue(Request request) { - var headerRaw = request.getHeader(TIMESTAMP_HEADER).substring(2); - if (headerRaw == null) { - return null; + private void handleLocalGet(HttpSession session, MemorySegment key) throws IOException { + EntryWithTimestamp entry = dao.getLocal(key); + Response response; + if (entry.value() == null) { + response = new Response(Response.NOT_FOUND, Response.EMPTY); + response.addHeader(TIMESTAMP_HEADER + entry.timestamp()); + session.sendResponse( + response + ); + return; } - return Long.parseLong(headerRaw); + response = new Response(Response.OK, entry.value().toArray(ValueLayout.JAVA_BYTE)); + response.addHeader(TIMESTAMP_HEADER + entry.timestamp()); + session.sendResponse(response); } - @Path(BASE_PATH) public Response notAllowed() { return new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); } @Override public void handleDefault(Request request, HttpSession session) throws IOException { - Response response = new Response(Response.BAD_REQUEST, Response.EMPTY); - session.sendResponse(response); + session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); } @Override diff --git a/src/main/java/ru/vk/itmo/test/shishiginstepan/service/DatabaseService.java b/src/main/java/ru/vk/itmo/test/shishiginstepan/service/DatabaseService.java index 0577b9eee..828306bad 100644 --- a/src/main/java/ru/vk/itmo/test/shishiginstepan/service/DatabaseService.java +++ b/src/main/java/ru/vk/itmo/test/shishiginstepan/service/DatabaseService.java @@ -6,7 +6,6 @@ import ru.vk.itmo.test.shishiginstepan.dao.EntryWithTimestamp; import ru.vk.itmo.test.shishiginstepan.dao.InMemDaoImpl; import ru.vk.itmo.test.shishiginstepan.server.DistributedDao; -import ru.vk.itmo.test.shishiginstepan.server.RemoteDaoNode; import ru.vk.itmo.test.shishiginstepan.server.Server; import java.io.IOException; @@ -33,14 +32,10 @@ public CompletableFuture start() { ); distributedDao = new DistributedDao(localDao, config.selfUrl()); for (String url : config.clusterUrls()) { - if (url.equals(config.selfUrl())) { - continue; - } distributedDao.addNode( - new RemoteDaoNode(url), url + url ); } - distributedDao.addNode(localDao, config.selfUrl()); try { server = new Server(config, distributedDao); } catch (IOException e) { @@ -57,7 +52,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/smirnovandrew/MyServer.java b/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServer.java index d0a26feb2..f07cd9995 100644 --- a/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServer.java +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServer.java @@ -1,8 +1,6 @@ package ru.vk.itmo.test.smirnovandrew; import one.nio.http.Header; -import one.nio.http.HttpClient; -import one.nio.http.HttpException; import one.nio.http.HttpServer; import one.nio.http.HttpSession; import one.nio.http.Param; @@ -10,35 +8,32 @@ import one.nio.http.Request; import one.nio.http.RequestMethod; import one.nio.http.Response; -import one.nio.pool.PoolException; import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.test.reference.dao.ReferenceDao; import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Function; import java.util.logging.Logger; -import java.util.stream.Collectors; public class MyServer extends HttpServer { - private static final String ROOT = "/v0/entity"; - private static final String X_SENDER_NODE = "X-SenderNode"; - private static final String NOT_ENOUGH_REPLICAS = "504 Not Enough Replicas"; - private static final long DURATION = 1000L; - private static final int OK_STATUS = 300; - private static final int NOT_FOUND_STATUS = 404; - private static final String HEADER_DELIMITER = ": "; private final MyServerDao dao; private final MyExecutor executor; private final Logger logger; - private final Map httpClients; + private final HttpClient httpClient; private final RendezvousClusterManager rendezvousClustersManager; private final ServiceConfig config; @@ -64,15 +59,13 @@ public MyServer( this.dao = new MyServerDao(dao); this.executor = new MyExecutor(corePoolSize, availableProcessors); this.logger = Logger.getLogger(MyServer.class.getName()); - this.httpClients = config.clusterUrls().stream() - .filter(url -> !Objects.equals(url, config.selfUrl())) - .collect(Collectors.toMap(s -> s, MyServerUtil::createClient, (c, c1) -> c)); + this.httpClient = HttpClient.newHttpClient(); } @Override public void handleRequest(Request request, HttpSession session) throws IOException { try { - long exp = System.currentTimeMillis() + DURATION; + long exp = System.currentTimeMillis() + MyServerUtil.DURATION; executor.execute(() -> { try { if (System.currentTimeMillis() > exp) { @@ -98,27 +91,31 @@ private static int quorum(int from) { return from / 2 + 1; } - private Response sendToAnotherNode( + private HttpRequest toHttpRequest(Request request, String nodeUrl, String params) { + return HttpRequest.newBuilder(URI.create(nodeUrl + MyServerUtil.ROOT + "?" + params)) + .method(request.getMethodName(), request.getBody() == null + ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofByteArray(request.getBody())) + .setHeader(MyServerUtil.X_SENDER_NODE, config.selfUrl()) + .build(); + } + + private CompletableFuture sendToAnotherNode( Request request, + int ack, + int from, + String id, String clusterUrl, Function operation ) { if (Objects.equals(clusterUrl, config.selfUrl())) { - return operation.apply(dao); + return CompletableFuture.completedFuture(operation.apply(dao)); } - var httpClient = httpClients.get(clusterUrl); + var httpRequest = toHttpRequest(request, clusterUrl, String.format("id=%s&from=%d&ack=%d", id, from, ack)); - try { - return httpClient.invoke(request); - } catch (InterruptedException e) { - logger.info(e.getMessage()); - Thread.currentThread().interrupt(); - return new Response(Response.INTERNAL_ERROR, Response.EMPTY); - } catch (HttpException | IOException | PoolException e1) { - logger.info(e1.getMessage()); - return new Response(Response.INTERNAL_ERROR, Response.EMPTY); - } + return httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()) + .thenApplyAsync(MyServerUtil::processingResponse); } private Response handleLocalRequest( @@ -128,15 +125,19 @@ private Response handleLocalRequest( Integer ackParam, String senderNode, Function operation - ) { - Integer from = fromParam; - if (Objects.isNull(from)) { + ) throws ExecutionException, InterruptedException, TimeoutException { + final int from; + if (Objects.isNull(fromParam)) { from = config.clusterUrls().size(); + } else { + from = fromParam; } - Integer ack = ackParam; - if (Objects.isNull(ack)) { + final int ack; + if (Objects.isNull(ackParam)) { ack = quorum(from); + } else { + ack = ackParam; } String paramError = getParametersError(id, from, ack); @@ -154,26 +155,29 @@ private Response handleLocalRequest( return new Response(Response.BAD_REQUEST, Response.EMPTY); } - var sortedNodes = RendezvousClusterManager.getSortedNodes(from, config); + var sortedNodes = RendezvousClusterManager.getSortedNodes(id, from, config); if (sortedNodes.stream().map(config.clusterUrls()::get).noneMatch(config.selfUrl()::equals)) { - return sendToAnotherNode(request, clusterUrl, operation); + return sendToAnotherNode(request, ack, from, id, clusterUrl, operation) + .get(MyServerUtil.DURATION, TimeUnit.MILLISECONDS); } - request.addHeader(String.join(HEADER_DELIMITER, X_SENDER_NODE, config.selfUrl())); - var responses = new ArrayList(); - for (int nodeNumber : sortedNodes) { - var r = sendToAnotherNode(request, config.clusterUrls().get(nodeNumber), operation); - if (r.getStatus() < OK_STATUS - || (r.getStatus() == NOT_FOUND_STATUS && request.getMethod() == Request.METHOD_GET)) { - responses.add(r); - } - } - - if (responses.size() < ack) { - return new Response(NOT_ENOUGH_REPLICAS, Response.EMPTY); - } - return MyServerUtil.getMaxTimestampResponse(responses); + var completableResults = sortedNodes.stream() + .map(nodeNumber -> sendToAnotherNode( + request, + ack, + from, + id, + config.clusterUrls().get(nodeNumber), + operation)) + .toList(); + + return MyServerUtil.getResults( + from, + ack, + completableResults, + logger + ); } private String getParametersError(String id, Integer from, Integer ack) { @@ -201,15 +205,15 @@ private String getParametersError(String id, Integer from, Integer ack) { return null; } - @Path(ROOT) + @Path(MyServerUtil.ROOT) @RequestMethod(Request.METHOD_GET) public Response get( @Param(value = "id", required = true) String id, @Param(value = "from") Integer from, @Param(value = "ack") Integer ack, - @Header(value = X_SENDER_NODE) String senderNode, + @Header(value = MyServerUtil.X_SENDER_NODE) String senderNode, Request request - ) { + ) throws ExecutionException, InterruptedException, TimeoutException { return handleLocalRequest( request, id, @@ -220,15 +224,15 @@ public Response get( ); } - @Path(ROOT) + @Path(MyServerUtil.ROOT) @RequestMethod(Request.METHOD_DELETE) public Response delete( @Param(value = "id", required = true) String id, @Param(value = "from") Integer from, @Param(value = "ack") Integer ack, - @Header(value = X_SENDER_NODE) String senderNode, + @Header(value = MyServerUtil.X_SENDER_NODE) String senderNode, Request request - ) { + ) throws ExecutionException, InterruptedException, TimeoutException { return handleLocalRequest( request, id, @@ -239,15 +243,15 @@ public Response delete( ); } - @Path(ROOT) + @Path(MyServerUtil.ROOT) @RequestMethod(Request.METHOD_PUT) public Response put( @Param(value = "id", required = true) String id, @Param(value = "from") Integer from, @Param(value = "ack") Integer ack, - @Header(value = X_SENDER_NODE) String senderNode, + @Header(value = MyServerUtil.X_SENDER_NODE) String senderNode, Request request - ) { + ) throws ExecutionException, InterruptedException, TimeoutException { request.addHeader("Content-Length: " + request.getBody().length); request.setBody(request.getBody()); @@ -275,6 +279,6 @@ public void handleDefault(Request request, HttpSession session) throws IOExcepti public synchronized void stop() { this.executor.shutdown(); super.stop(); - httpClients.values().forEach(HttpClient::close); + httpClient.close(); } } diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServerDao.java b/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServerDao.java index f63805c8a..88829c970 100644 --- a/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServerDao.java +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServerDao.java @@ -31,11 +31,11 @@ Response getEntryFromDao(String id) { ValWithTime valueWithTimestamp = byteArrayToObject(entry.value().toArray(ValueLayout.JAVA_BYTE)); if (valueWithTimestamp.value() == null) { Response response = new Response(Response.NOT_FOUND, Response.EMPTY); - response.addHeader(MyServerUtil.X_TIMESTAMP + valueWithTimestamp.timestamp()); + response.addHeader("X-Timestamp: " + valueWithTimestamp.timestamp()); return response; } Response response = new Response(Response.OK, valueWithTimestamp.value()); - response.addHeader(MyServerUtil.X_TIMESTAMP + valueWithTimestamp.timestamp()); + response.addHeader("X-Timestamp: " + valueWithTimestamp.timestamp()); return response; } catch (IOException | ClassNotFoundException e) { return new Response(Response.INTERNAL_ERROR, Response.EMPTY); diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServerUtil.java b/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServerUtil.java index 9c6397bbf..420dd62f7 100644 --- a/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServerUtil.java +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServerUtil.java @@ -1,21 +1,40 @@ package ru.vk.itmo.test.smirnovandrew; -import one.nio.http.HttpClient; import one.nio.http.HttpServerConfig; import one.nio.http.HttpSession; import one.nio.http.Response; -import one.nio.net.ConnectionString; import one.nio.server.AcceptorConfig; import ru.vk.itmo.ServiceConfig; import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.http.HttpResponse; +import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; import java.util.logging.Logger; public final class MyServerUtil { - private static final int CONNECTION_TIMEOUT = 1000; - - public static final String X_TIMESTAMP = "X-TimeStamp: "; + public static final String ROOT = "/v0/entity"; + public static final String X_SENDER_NODE = "X-SenderNode"; + public static final String X_TIMESTAMP = "X-TimeStamp"; + public static final Map HTTP_CODES = Map.of( + HttpURLConnection.HTTP_OK, Response.OK, + HttpURLConnection.HTTP_ACCEPTED, Response.ACCEPTED, + HttpURLConnection.HTTP_CREATED, Response.CREATED, + HttpURLConnection.HTTP_NOT_FOUND, Response.NOT_FOUND, + HttpURLConnection.HTTP_BAD_REQUEST, Response.BAD_REQUEST, + HttpURLConnection.HTTP_INTERNAL_ERROR, Response.INTERNAL_ERROR + ); + public static final String NOT_ENOUGH_REPLICAS = "504 Not Enough Replicas"; + public static final long DURATION = 1000L; private MyServerUtil() { } @@ -32,12 +51,6 @@ public static HttpServerConfig generateServerConfig(ServiceConfig config) { return serverConfig; } - public static HttpClient createClient(String url) { - var client = new HttpClient(new ConnectionString(url)); - client.setConnectTimeout(CONNECTION_TIMEOUT); - return client; - } - public static void sendEmpty(HttpSession session, Logger logger, String message) { try { session.sendResponse(new Response(message, Response.EMPTY)); @@ -46,24 +59,68 @@ public static void sendEmpty(HttpSession session, Logger logger, String message) } } - private static long headerTimestampToLong(Response r) { - String header = r.getHeader(X_TIMESTAMP); + public static long headerTimestampToLong(Response r) { + String header = r.getHeader("X-TimeStamp: "); if (header == null) { return Long.MIN_VALUE; } return Long.parseLong(header); } - public static Response getMaxTimestampResponse(List responses) { - long maxTimestamp = Long.MIN_VALUE; - Response maxResponse = null; - for (var response: responses) { - long timestamp = headerTimestampToLong(response); - if (timestamp >= maxTimestamp) { - maxResponse = response; - maxTimestamp = timestamp; + public static Response processingResponse(HttpResponse response) { + String statusCode = HTTP_CODES.getOrDefault(response.statusCode(), null); + if (statusCode == null) { + return new Response(Response.INTERNAL_ERROR, response.body()); + } else { + Response newResponse = new Response(statusCode, response.body()); + long timestamp = response.headers().firstValueAsLong(X_TIMESTAMP).orElse(0); + newResponse.addHeader(X_TIMESTAMP + ": " + timestamp); + return newResponse; + } + } + + public static Response getResults( + int from, + int ack, + List> completableResults, + Logger logger + ) throws ExecutionException, InterruptedException { + var okResponses = new ConcurrentLinkedDeque(); + var okResponsesCount = new AtomicInteger(); + var failedResponsesCount = new AtomicInteger(); + var answer = new CompletableFuture(); + + BiConsumer whenComplete = (r, throwable) -> { + if (throwable == null || r.getStatus() < 500) { + okResponsesCount.incrementAndGet(); + okResponses.add(r); + } else { + failedResponsesCount.incrementAndGet(); } + + if (okResponsesCount.get() >= ack) { + answer.complete(okResponses.stream() + .max(Comparator.comparingLong(MyServerUtil::headerTimestampToLong)) + .get()); + } + + if (failedResponsesCount.get() >= from - ack + 1) { + answer.complete(new Response(MyServerUtil.NOT_ENOUGH_REPLICAS, Response.EMPTY)); + } + }; + + completableResults.forEach(completableFuture -> { + var responseFuture = completableFuture.whenComplete(whenComplete); + if (responseFuture == null) { + logger.info("Error completable future is null!"); + } + }); + + try { + return answer.get(MyServerUtil.DURATION, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + logger.info("Too long waiting for response: " + e.getMessage()); + return new Response(MyServerUtil.NOT_ENOUGH_REPLICAS, Response.EMPTY); } - return maxResponse; } } diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServiceFactory.java b/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServiceFactory.java index 81564c3f4..e41656d9a 100644 --- a/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServiceFactory.java +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/MyServiceFactory.java @@ -4,7 +4,7 @@ import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.test.ServiceFactory; -@ServiceFactory(stage = 4) +@ServiceFactory(stage = 5) public class MyServiceFactory implements ServiceFactory.Factory { @Override public Service create(ServiceConfig config) { diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/RendezvousClusterManager.java b/src/main/java/ru/vk/itmo/test/smirnovandrew/RendezvousClusterManager.java index 0f6f40f70..92799e11d 100644 --- a/src/main/java/ru/vk/itmo/test/smirnovandrew/RendezvousClusterManager.java +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/RendezvousClusterManager.java @@ -3,8 +3,10 @@ import one.nio.util.Hash; import ru.vk.itmo.ServiceConfig; -import java.util.ArrayList; +import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; public class RendezvousClusterManager { @@ -18,7 +20,7 @@ public String getCluster(String key) { int resIdx = -1; int maxHash = Integer.MIN_VALUE; for (int i = 0; i < availableClusters.size(); ++i) { - var hash = Hash.murmur3(key + availableClusters.get(i)); + var hash = Hash.murmur3(String.join("", availableClusters.get(i), key)); if (hash > maxHash) { resIdx = i; maxHash = hash; @@ -32,11 +34,12 @@ public String getCluster(String key) { return availableClusters.get(resIdx); } - public static List getSortedNodes(int amount, ServiceConfig config) { - var result = new ArrayList(); - for (int i = 0; i < config.clusterUrls().size() && result.size() < amount; i++) { - result.add(i); - } - return result; + public static List getSortedNodes(String key, int amount, ServiceConfig config) { + return IntStream.range(0, config.clusterUrls().size()) + .mapToObj(i -> Map.entry(i, Hash.murmur3(key + i))) + .sorted(Comparator.>comparingInt(Map.Entry::getValue).reversed()) + .map(Map.Entry::getKey) + .limit(amount) + .toList(); } } diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5.md b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5.md new file mode 100644 index 000000000..face2e83a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5.md @@ -0,0 +1,81 @@ +# Отчет о тестировании +## Использованные методы +Все методы реализованы так же как и методы в stage1, +как именно они реализованы можно посмотреть либо в отчете +stage1.md или в папке lua + +После прогрева выяснилось, что точка разладки происходит где-то +в районе 3000-3500, поэтому будем тестировать на 3000 rps + +### PUT запросы +``` +./wrk -d 120 -t 1 -c 64 -R 3000 -L -s /home/andrew/my-dht/2024-highload-dht/src/main/java/ru/vk/itmo/test/smirnovandrew/lua/put.lua http://localhost:8080 + ``` +```dtd +./profiler.sh --fdtransfer -d 120 -e cpu -f put_cpu_stage3.html jps +``` + +![put3000.png](stage5%2Fput3000.png) +Здесь интересно, что latency начинает расти где-то около 99.9% + +Анализ CPU: +![put_cpu.png](stage5%2Fput_cpu.png) +Наибольшая нагрузка приходится на ожидание свободных потоков + +Также на асинхронное отправление запроса на другой шард + + +Анализ ALLOC: +![put_alloc.png](stage5%2Fput_alloc.png) +Большее количество памяти так же, как и в предыдущих частях +тратится на конвертацию MemorySegment в byte[] и обратно + +Также тратится на расшифрофку headerов и на посылку Completable future + + +Анализ LOCK: +![put_lock.png](stage5%2Fput_lock.png) +Наибольная нагрузка тут приходится на ожидание таски из очереди + +Также тратится на ожидание асинхронного запроса + +### GET запросы +``` +./wrk -d 60 -t 1 -c 64 -R 3000 -L -s /home/andrew/my-dht/2024-highload-dht/src/main/java/ru/vk/itmo/test/smirnovandrew/lua/get.lua http://localhost:8080 + ``` +```dtd +./profiler.sh --fdtransfer -d 120 -e cpu -f get_cpu_stage3.html jps +``` + +![get3000.png](stage5%2Fget3000.png) +Здесь также latency серьезно растет около 99.9% + +Анализ CPU: +![get_cpu.png](stage5%2Fget_cpu.png) +Наибольшая нагрузка приходится на ожидание свободных потоков + +Также на асинхронное отправление запроса на другой шард + +Анализ ALLOC: +![get_alloc.png](stage5%2Fget_alloc.png) +Большее количество памяти так же, как и в предыдущих частях +тратится на конвертацию MemorySegment в byte[] и обратно + +Также тратится на расшифрофку headerов и на посылку Completable future + + + +Анализ LOCK: +![get_lock.png](stage5%2Fget_lock.png) +Наибольная нагрузка тут приходится на ожидание таски из очереди + +Также тратится на ожидание асинхронного запроса + + +### Сравнение с предыдущей реализацией +![compare.png](stage5%2Fcompare.png) +К сожалению я не делал графики на stage4, поэтому могу сравнить +показатели только со stage3. Здесь по графикам заметно, что относительно +stage3 есть значительные улучшения по latency + + diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/compare.png b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/compare.png new file mode 100644 index 000000000..f240f4a81 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/compare.png differ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get3000.png b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get3000.png new file mode 100644 index 000000000..d73a18162 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get3000.png differ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get3000.txt b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get3000.txt new file mode 100644 index 000000000..18be6dd37 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get3000.txt @@ -0,0 +1,117 @@ +❯ ./wrk -d 60 -t 1 -c 64 -R 3000 -L -s /Users/sandrew2003/2024-highload-dht/src/main/java/ru/vk/itmo/test/smirnovandrew/lua/get.lua http://localhost:8080 + +Running 1m test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 1.691ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.67ms 805.87us 25.26ms 78.75% + Req/Sec 3.16k 306.18 7.20k 79.01% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.55ms + 75.000% 2.10ms + 90.000% 2.54ms + 99.000% 4.02ms + 99.900% 7.93ms + 99.990% 20.06ms + 99.999% 24.93ms +100.000% 25.28ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.273 0.000000 1 1.00 + 0.876 0.100000 14961 1.11 + 1.046 0.200000 29907 1.25 + 1.201 0.300000 44746 1.43 + 1.366 0.400000 59625 1.67 + 1.546 0.500000 74525 2.00 + 1.644 0.550000 82012 2.22 + 1.746 0.600000 89445 2.50 + 1.854 0.650000 96880 2.86 + 1.974 0.700000 104362 3.33 + 2.101 0.750000 111826 4.00 + 2.169 0.775000 115567 4.44 + 2.237 0.800000 119290 5.00 + 2.307 0.825000 123074 5.71 + 2.379 0.850000 126718 6.67 + 2.457 0.875000 130463 8.00 + 2.497 0.887500 132279 8.89 + 2.541 0.900000 134163 10.00 + 2.591 0.912500 136016 11.43 + 2.647 0.925000 137866 13.33 + 2.713 0.937500 139750 16.00 + 2.749 0.943750 140665 17.78 + 2.793 0.950000 141637 20.00 + 2.841 0.956250 142535 22.86 + 2.903 0.962500 143470 26.67 + 2.991 0.968750 144390 32.00 + 3.053 0.971875 144854 35.56 + 3.139 0.975000 145325 40.00 + 3.263 0.978125 145790 45.71 + 3.419 0.981250 146250 53.33 + 3.599 0.984375 146719 64.00 + 3.709 0.985938 146950 71.11 + 3.817 0.987500 147182 80.00 + 3.935 0.989062 147415 91.43 + 4.071 0.990625 147649 106.67 + 4.235 0.992188 147887 128.00 + 4.307 0.992969 148000 142.22 + 4.427 0.993750 148116 160.00 + 4.547 0.994531 148230 182.86 + 4.679 0.995313 148346 213.33 + 4.843 0.996094 148464 256.00 + 4.967 0.996484 148524 284.44 + 5.095 0.996875 148579 320.00 + 5.275 0.997266 148637 365.71 + 5.531 0.997656 148695 426.67 + 5.911 0.998047 148753 512.00 + 6.147 0.998242 148783 568.89 + 6.415 0.998437 148812 640.00 + 6.843 0.998633 148841 731.43 + 7.403 0.998828 148870 853.33 + 7.995 0.999023 148899 1024.00 + 8.271 0.999121 148915 1137.78 + 8.551 0.999219 148928 1280.00 + 9.015 0.999316 148943 1462.86 + 9.935 0.999414 148957 1706.67 + 12.255 0.999512 148972 2048.00 + 13.287 0.999561 148979 2275.56 + 13.935 0.999609 148986 2560.00 + 15.399 0.999658 148994 2925.71 + 16.159 0.999707 149001 3413.33 + 17.023 0.999756 149008 4096.00 + 17.311 0.999780 149012 4551.11 + 17.519 0.999805 149015 5120.00 + 18.191 0.999829 149019 5851.43 + 18.655 0.999854 149023 6826.67 + 19.055 0.999878 149026 8192.00 + 19.487 0.999890 149028 9102.22 + 20.095 0.999902 149030 10240.00 + 20.959 0.999915 149032 11702.86 + 21.743 0.999927 149034 13653.33 + 21.807 0.999939 149035 16384.00 + 21.839 0.999945 149036 18204.44 + 22.431 0.999951 149037 20480.00 + 22.559 0.999957 149038 23405.71 + 22.863 0.999963 149039 27306.67 + 23.407 0.999969 149040 32768.00 + 23.407 0.999973 149040 36408.89 + 23.647 0.999976 149041 40960.00 + 23.647 0.999979 149041 46811.43 + 24.175 0.999982 149042 54613.33 + 24.175 0.999985 149042 65536.00 + 24.175 0.999986 149042 72817.78 + 24.927 0.999988 149043 81920.00 + 24.927 0.999989 149043 93622.86 + 24.927 0.999991 149043 109226.67 + 24.927 0.999992 149043 131072.00 + 24.927 0.999993 149043 145635.56 + 25.279 0.999994 149044 163840.00 + 25.279 1.000000 149044 inf +#[Mean = 1.670, StdDeviation = 0.806] +#[Max = 25.264, Total count = 149044] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 179556 requests in 1.00m, 24.30MB read +Requests/sec: 2992.55 +Transfer/sec: 414.66KB diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get3000_stage3.txt b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get3000_stage3.txt new file mode 100644 index 000000000..2cab705e2 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get3000_stage3.txt @@ -0,0 +1,115 @@ +Running 1m test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 1.893ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.81ms 1.45ms 44.51ms 87.58% + Req/Sec 3.17k 379.09 9.56k 76.06% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.59ms + 75.000% 2.36ms + 90.000% 3.17ms + 99.000% 5.27ms + 99.900% 17.63ms + 99.990% 38.53ms + 99.999% 43.33ms +100.000% 44.54ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.073 0.000000 1 1.00 + 0.591 0.100000 14907 1.11 + 0.851 0.200000 29845 1.25 + 1.081 0.300000 44735 1.43 + 1.321 0.400000 59643 1.67 + 1.587 0.500000 74547 2.00 + 1.726 0.550000 82004 2.22 + 1.869 0.600000 89446 2.50 + 2.021 0.650000 96885 2.86 + 2.179 0.700000 104352 3.33 + 2.357 0.750000 111827 4.00 + 2.453 0.775000 115573 4.44 + 2.559 0.800000 119272 5.00 + 2.677 0.825000 122973 5.71 + 2.809 0.850000 126687 6.67 + 2.967 0.875000 130432 8.00 + 3.063 0.887500 132302 8.89 + 3.167 0.900000 134137 10.00 + 3.283 0.912500 136030 11.43 + 3.407 0.925000 137883 13.33 + 3.557 0.937500 139750 16.00 + 3.637 0.943750 140659 17.78 + 3.735 0.950000 141605 20.00 + 3.845 0.956250 142532 22.86 + 3.971 0.962500 143458 26.67 + 4.143 0.968750 144397 32.00 + 4.239 0.971875 144848 35.56 + 4.343 0.975000 145327 40.00 + 4.455 0.978125 145793 45.71 + 4.591 0.981250 146255 53.33 + 4.755 0.984375 146716 64.00 + 4.875 0.985938 146954 71.11 + 4.995 0.987500 147186 80.00 + 5.155 0.989062 147410 91.43 + 5.355 0.990625 147645 106.67 + 5.643 0.992188 147875 128.00 + 5.891 0.992969 147994 142.22 + 6.179 0.993750 148108 160.00 + 6.543 0.994531 148224 182.86 + 7.091 0.995313 148341 213.33 + 7.759 0.996094 148458 256.00 + 8.367 0.996484 148516 284.44 + 9.231 0.996875 148575 320.00 + 10.839 0.997266 148632 365.71 + 12.231 0.997656 148691 426.67 + 13.727 0.998047 148748 512.00 + 14.335 0.998242 148778 568.89 + 15.015 0.998437 148808 640.00 + 15.671 0.998633 148836 731.43 + 16.447 0.998828 148865 853.33 + 17.759 0.999023 148894 1024.00 + 19.487 0.999121 148909 1137.78 + 20.575 0.999219 148923 1280.00 + 22.815 0.999316 148938 1462.86 + 24.319 0.999414 148952 1706.67 + 26.431 0.999512 148967 2048.00 + 27.391 0.999561 148974 2275.56 + 28.911 0.999609 148981 2560.00 + 30.607 0.999658 148989 2925.71 + 31.535 0.999707 148996 3413.33 + 32.799 0.999756 149003 4096.00 + 33.087 0.999780 149007 4551.11 + 34.399 0.999805 149010 5120.00 + 36.031 0.999829 149014 5851.43 + 36.511 0.999854 149018 6826.67 + 37.695 0.999878 149021 8192.00 + 38.463 0.999890 149023 9102.22 + 38.751 0.999902 149025 10240.00 + 39.519 0.999915 149027 11702.86 + 40.159 0.999927 149029 13653.33 + 40.671 0.999939 149030 16384.00 + 41.087 0.999945 149031 18204.44 + 41.183 0.999951 149032 20480.00 + 41.343 0.999957 149034 23405.71 + 41.343 0.999963 149034 27306.67 + 42.015 0.999969 149036 32768.00 + 42.015 0.999973 149036 36408.89 + 42.015 0.999976 149036 40960.00 + 42.015 0.999979 149036 46811.43 + 43.167 0.999982 149037 54613.33 + 43.167 0.999985 149037 65536.00 + 43.167 0.999986 149037 72817.78 + 43.327 0.999988 149038 81920.00 + 43.327 0.999989 149038 93622.86 + 43.327 0.999991 149038 109226.67 + 43.327 0.999992 149038 131072.00 + 43.327 0.999993 149038 145635.56 + 44.543 0.999994 149039 163840.00 + 44.543 1.000000 149039 inf +#[Mean = 1.809, StdDeviation = 1.453] +#[Max = 44.512, Total count = 149039] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 179550 requests in 1.00m, 19.16MB read +Requests/sec: 2992.53 +Transfer/sec: 326.97KB diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_alloc.html b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_alloc.html new file mode 100644 index 000000000..251971c21 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_alloc.html @@ -0,0 +1,2576 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_alloc.png b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_alloc.png new file mode 100644 index 000000000..2dd8d5ef4 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_cpu.html b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_cpu.html new file mode 100644 index 000000000..5ed9c8365 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_cpu.html @@ -0,0 +1,2472 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_cpu.png b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_cpu.png new file mode 100644 index 000000000..e20a78d0c Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_lock.html b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_lock.html new file mode 100644 index 000000000..b33e30cdc --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_lock.html @@ -0,0 +1,913 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_lock.png b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_lock.png new file mode 100644 index 000000000..1dbbfd81d Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/get_lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put3000.png b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put3000.png new file mode 100644 index 000000000..efa52124e Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put3000.png differ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put3000.txt b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put3000.txt new file mode 100644 index 000000000..08f7ea989 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put3000.txt @@ -0,0 +1,117 @@ +❯ ./wrk -d 60 -t 1 -c 64 -R 3000 -L -s /Users/sandrew2003/2024-highload-dht/src/main/java/ru/vk/itmo/test/smirnovandrew/lua/put.lua http://localhost:8080 + +Running 1m test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 1.292ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.51ms 1.08ms 62.02ms 94.14% + Req/Sec 3.16k 308.45 11.89k 86.27% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.39ms + 75.000% 1.89ms + 90.000% 2.34ms + 99.000% 3.57ms + 99.900% 12.49ms + 99.990% 40.38ms + 99.999% 60.41ms +100.000% 62.05ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.186 0.000000 1 1.00 + 0.735 0.100000 14926 1.11 + 0.912 0.200000 29884 1.25 + 1.065 0.300000 44785 1.43 + 1.223 0.400000 59642 1.67 + 1.393 0.500000 74567 2.00 + 1.484 0.550000 82017 2.22 + 1.576 0.600000 89447 2.50 + 1.675 0.650000 96905 2.86 + 1.780 0.700000 104340 3.33 + 1.892 0.750000 111835 4.00 + 1.951 0.775000 115533 4.44 + 2.015 0.800000 119250 5.00 + 2.085 0.825000 123042 5.71 + 2.161 0.850000 126688 6.67 + 2.245 0.875000 130432 8.00 + 2.291 0.887500 132306 8.89 + 2.341 0.900000 134164 10.00 + 2.395 0.912500 136002 11.43 + 2.453 0.925000 137885 13.33 + 2.519 0.937500 139773 16.00 + 2.557 0.943750 140696 17.78 + 2.597 0.950000 141601 20.00 + 2.639 0.956250 142530 22.86 + 2.691 0.962500 143464 26.67 + 2.751 0.968750 144406 32.00 + 2.785 0.971875 144864 35.56 + 2.831 0.975000 145334 40.00 + 2.881 0.978125 145796 45.71 + 2.947 0.981250 146258 53.33 + 3.049 0.984375 146714 64.00 + 3.123 0.985938 146947 71.11 + 3.245 0.987500 147182 80.00 + 3.405 0.989062 147412 91.43 + 3.673 0.990625 147645 106.67 + 3.929 0.992188 147877 128.00 + 4.091 0.992969 147996 142.22 + 4.295 0.993750 148110 160.00 + 4.547 0.994531 148227 182.86 + 4.815 0.995313 148343 213.33 + 5.191 0.996094 148459 256.00 + 5.435 0.996484 148518 284.44 + 5.835 0.996875 148578 320.00 + 6.155 0.997266 148635 365.71 + 6.539 0.997656 148695 426.67 + 7.019 0.998047 148751 512.00 + 7.527 0.998242 148782 568.89 + 8.011 0.998437 148809 640.00 + 9.047 0.998633 148838 731.43 + 10.343 0.998828 148867 853.33 + 12.743 0.999023 148896 1024.00 + 14.167 0.999121 148911 1137.78 + 15.983 0.999219 148925 1280.00 + 17.343 0.999316 148940 1462.86 + 18.799 0.999414 148954 1706.67 + 20.415 0.999512 148969 2048.00 + 21.135 0.999561 148976 2275.56 + 22.047 0.999609 148983 2560.00 + 24.847 0.999658 148991 2925.71 + 27.263 0.999707 148998 3413.33 + 32.591 0.999756 149005 4096.00 + 33.087 0.999780 149009 4551.11 + 35.167 0.999805 149012 5120.00 + 37.631 0.999829 149016 5851.43 + 39.519 0.999854 149020 6826.67 + 39.967 0.999878 149023 8192.00 + 40.319 0.999890 149025 9102.22 + 41.663 0.999902 149027 10240.00 + 42.847 0.999915 149029 11702.86 + 45.279 0.999927 149031 13653.33 + 45.567 0.999939 149032 16384.00 + 49.759 0.999945 149033 18204.44 + 50.111 0.999951 149034 20480.00 + 53.119 0.999957 149035 23405.71 + 57.951 0.999963 149036 27306.67 + 58.975 0.999969 149037 32768.00 + 58.975 0.999973 149037 36408.89 + 59.071 0.999976 149038 40960.00 + 59.071 0.999979 149038 46811.43 + 59.967 0.999982 149039 54613.33 + 59.967 0.999985 149039 65536.00 + 59.967 0.999986 149039 72817.78 + 60.415 0.999988 149040 81920.00 + 60.415 0.999989 149040 93622.86 + 60.415 0.999991 149040 109226.67 + 60.415 0.999992 149040 131072.00 + 60.415 0.999993 149040 145635.56 + 62.047 0.999994 149041 163840.00 + 62.047 1.000000 149041 inf +#[Mean = 1.514, StdDeviation = 1.085] +#[Max = 62.016, Total count = 149041] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 179556 requests in 1.00m, 14.21MB read +Requests/sec: 2992.62 +Transfer/sec: 242.57KB diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_alloc.html b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_alloc.html new file mode 100644 index 000000000..ef92a5045 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_alloc.html @@ -0,0 +1,3310 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_alloc.png b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_alloc.png new file mode 100644 index 000000000..8880afbd3 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_cpu.html b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_cpu.html new file mode 100644 index 000000000..97fdd646a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_cpu.html @@ -0,0 +1,3786 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_cpu.png b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_cpu.png new file mode 100644 index 000000000..941280d7f Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_lock.html b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_lock.html new file mode 100644 index 000000000..5a244831f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_lock.html @@ -0,0 +1,870 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_lock.png b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_lock.png new file mode 100644 index 000000000..fb85f9a2a Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/smirnovandrew/readme/stage5/put_lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/smirnovdmitrii/reports/hw6/report.md b/src/main/java/ru/vk/itmo/test/smirnovdmitrii/reports/hw6/report.md new file mode 100644 index 000000000..3ba234c0c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/smirnovdmitrii/reports/hw6/report.md @@ -0,0 +1,33 @@ +# Отчет по hw6 + +## CPU + +[картинка](https://disk.yandex.ru/i/bNwk4ElOic5LsA) + +Вся нагрузка на worker тредах. Каждый раз worker треды ждут, пока в сокете появится место, чтобы записать. + + +## MEM + +[картинка](https://disk.yandex.ru/i/-aW6SPZgR017oA) + +Можно избавиться от аллокации массива из нативной памяти. + +## Версия 2 + +Переписал работу с сессией так, чтобы worker тред только добавлял ноду, а уже selector треды писали в сокет. +При этом осталось еще не сделан вынес работу с dao в worker тред. +Расчет на то, что в этом случае selector треды будут работать в неблокирующем режиме в отличие от воркеров. + +## CPU + +[флеймграф](https://disk.yandex.ru/d/8aR55y2dg2uSjg) + +На более 5 процентов возросло cpu на сокет. Поток исполнения остался workerом. +Оказалось, что в сокете просто всегда было место, поэтому воркер продолжал писать. + +## MEM + +[флеймграф](https://disk.yandex.ru/d/CLUnkp8kf8_ggA) + +Появилась колонка с `ByteBuffer` во время записи в сокет. diff --git a/src/main/java/ru/vk/itmo/test/smirnovdmitrii/server/DaoHttpServer.java b/src/main/java/ru/vk/itmo/test/smirnovdmitrii/server/DaoHttpServer.java index 18c5d6b9f..b432cd080 100644 --- a/src/main/java/ru/vk/itmo/test/smirnovdmitrii/server/DaoHttpServer.java +++ b/src/main/java/ru/vk/itmo/test/smirnovdmitrii/server/DaoHttpServer.java @@ -4,6 +4,7 @@ import one.nio.http.HttpSession; import one.nio.http.Request; import one.nio.http.Response; +import one.nio.net.Socket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.vk.itmo.dao.Dao; @@ -17,6 +18,7 @@ import java.net.HttpURLConnection; import java.nio.charset.StandardCharsets; import java.time.Instant; +import java.util.Iterator; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; @@ -141,6 +143,9 @@ private void handleRequestTask(final Request request, final HttpSession session) if (path.equals(SERVER_STOP_PATH)) { stop(); return; + } else if (path.startsWith("/v0/entities")) { + processRange(request, session); + return; } final String id = request.getParameter("id="); final ProcessResultHandler handler = processHandler(session, request, id, path); @@ -161,6 +166,39 @@ private void handleRequestTask(final Request request, final HttpSession session) } } + private void processRange(final Request request, final HttpSession session) throws IOException { + final String startString = request.getParameter("start="); + final String endString = request.getParameter("end="); + if (startString == null || startString.isBlank()) { + session.sendError(Response.BAD_REQUEST, "Missing range parameter \"start\"."); + return; + } + if (endString != null && endString.isBlank()) { + session.sendError(Response.BAD_REQUEST, "Missing range parameter \"end\"."); + return; + } + final MemorySegment startKey = MemorySegment.ofArray(startString.getBytes(StandardCharsets.UTF_8)); + final MemorySegment endKey; + if (endString == null) { + endKey = null; + } else { + endKey = MemorySegment.ofArray(endString.getBytes(StandardCharsets.UTF_8)); + } + + final Iterator> iterator = dao.get(startKey, endKey); + if (!iterator.hasNext()) { + session.sendResponse(new Response(Response.OK, Response.EMPTY)); + return; + } + ((DaoSession) session).range(iterator); + session.close(); + } + + @Override + public HttpSession createSession(final Socket socket) { + return new DaoSession(socket, this); + } + private void processRequestAck( final Request request, final String id, @@ -246,7 +284,7 @@ private ProcessResult processRequest(final Request request, final int method, fi return response; } - public ProcessResultHandler processHandler( + private ProcessResultHandler processHandler( final HttpSession session, final Request request, final String id, @@ -292,7 +330,7 @@ public ProcessResultHandler processHandler( return handler; } - public boolean isInvalidKey(final String key) { + private boolean isInvalidKey(final String key) { return key == null || key.isEmpty(); } diff --git a/src/main/java/ru/vk/itmo/test/smirnovdmitrii/server/DaoSession.java b/src/main/java/ru/vk/itmo/test/smirnovdmitrii/server/DaoSession.java new file mode 100644 index 000000000..8f7772f34 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/smirnovdmitrii/server/DaoSession.java @@ -0,0 +1,139 @@ +package ru.vk.itmo.test.smirnovdmitrii.server; + +import one.nio.http.HttpServer; +import one.nio.http.HttpSession; +import one.nio.net.Socket; +import ru.vk.itmo.test.smirnovdmitrii.dao.TimeEntry; + +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 DaoSession extends HttpSession { + private static final byte[] CHUNKED_HEADERS = + """ + HTTP/1.1 200\r + OKContentType: application/octet-stream\r + Transfer-Encoding: chunked\r + Connection: keep-alive\r + \r + """.getBytes(StandardCharsets.UTF_8); + private static final byte[] ZERO_CHUNK_LENGTH = encodeChunkLength(0); + private static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes(StandardCharsets.UTF_8); + private static final byte[] ENTRY_SEPARATOR = "\n".getBytes(StandardCharsets.UTF_8); + + public DaoSession(final Socket socket, final HttpServer server) { + super(socket, server); + } + + public void range(final Iterator> iterator) throws IOException { + write(CHUNKED_HEADERS, 0, CHUNKED_HEADERS.length); + write(new RangeQueueItem(iterator)); + } + + private static class RangeQueueItem extends QueueItem { + + private Chunk chunk; + private boolean isEnd = false; + private final Chunk lastChunk = new Chunk(); + + private static class Chunk { + + private final byte[][] parts; + protected int offset; + public final int total; + + public Chunk() { + this.parts = new byte[3][]; + this.parts[0] = ZERO_CHUNK_LENGTH; + this.parts[1] = CHUNK_SEPARATOR; + this.parts[2] = CHUNK_SEPARATOR; + this.total = ZERO_CHUNK_LENGTH.length + 2 * CHUNK_SEPARATOR.length; + this.offset = 0; + } + + public Chunk(final TimeEntry entry) { + this.parts = new byte[6][]; + this.parts[2] = entry.key().toArray(ValueLayout.JAVA_BYTE); + this.parts[3] = ENTRY_SEPARATOR; + this.parts[4] = entry.value().toArray(ValueLayout.JAVA_BYTE); + final int chunkLength = parts[2].length + parts[3].length + parts[4].length; + this.parts[0] = encodeChunkLength(chunkLength); + this.parts[1] = CHUNK_SEPARATOR; + this.parts[5] = CHUNK_SEPARATOR; + this.total = parts[0].length + 2 * CHUNK_SEPARATOR.length + chunkLength; + this.offset = 0; + } + + public int write(final Socket socket) throws IOException { + + final int beforeOffset = offset; + int curLength = 0; + for (final byte[] arr: parts) { + if (!tryFull(curLength, arr, socket)) { + break; + } + curLength += arr.length; + } + return offset - beforeOffset; + } + + private boolean tryFull( + int offsetOffset, + byte[] array, + Socket socket + ) throws IOException { + if (offset - offsetOffset < array.length) { + final int countToWrite = array.length - (offset - offsetOffset); + if (countToWrite == 0) { + return true; + } + + offset += socket.write(array, offset - offsetOffset, countToWrite); + } + return (offset - offsetOffset) >= array.length; + } + + } + + private final Iterator> iterator; + + private RangeQueueItem(Iterator> iterator) { + this.iterator = iterator; + } + + @Override + public int remaining() { + return isEnd ? 0 : 1; + } + + @Override + public int write(final Socket socket) throws IOException { + int total = 0; + while (true) { + if (!iterator.hasNext() && chunk == null) { + final int written = lastChunk.write(socket); + if (lastChunk.offset == lastChunk.total) { + isEnd = true; + } + return total + written; + } + if (chunk == null) { + chunk = new Chunk(iterator.next()); + } + total += chunk.write(socket); + if (chunk.offset == chunk.total) { + chunk = null; + } else { + return total; + } + } + } + } + + private static byte[] encodeChunkLength(final int length) { + return Integer.toHexString(length).toUpperCase().getBytes(StandardCharsets.UTF_8); + } +} diff --git a/src/main/java/ru/vk/itmo/test/smirnovdmitrii/server/ServiceFactoryImpl.java b/src/main/java/ru/vk/itmo/test/smirnovdmitrii/server/ServiceFactoryImpl.java index 4eb84ecc0..a7f4d8d51 100644 --- a/src/main/java/ru/vk/itmo/test/smirnovdmitrii/server/ServiceFactoryImpl.java +++ b/src/main/java/ru/vk/itmo/test/smirnovdmitrii/server/ServiceFactoryImpl.java @@ -7,7 +7,7 @@ import java.io.IOException; import java.io.UncheckedIOException; -@ServiceFactory(stage = 5) +@ServiceFactory(stage = 6) public class ServiceFactoryImpl implements ServiceFactory.Factory { @Override public Service create(ServiceConfig config) { diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/ExceptionUtils.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/ExceptionUtils.java new file mode 100644 index 000000000..630904cc1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/ExceptionUtils.java @@ -0,0 +1,26 @@ +package ru.vk.itmo.test.timofeevkirill.reference; + +import one.nio.http.HttpSession; +import one.nio.http.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public final class ExceptionUtils { + private static final Logger log = LoggerFactory.getLogger(ExceptionUtils.class); + + public static void handleErrorFromHandleRequest(Exception e, HttpSession session) { + log.error("Exception during handleRequest", e); + try { + session.sendResponse(new Response(Response.INTERNAL_ERROR, Response.EMPTY)); + } catch (IOException ex) { + log.error("Exception while sending close connection", ex); + session.scheduleClose(); + } + } + + private ExceptionUtils() { + + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/HandleResult.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/HandleResult.java new file mode 100644 index 000000000..975d6244e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/HandleResult.java @@ -0,0 +1,14 @@ +package ru.vk.itmo.test.timofeevkirill.reference; + +public record HandleResult(int status, byte[] data, long timestamp) { + + public HandleResult(int status, byte[] data, long timestamp) { + this.status = status; + this.data = data; + this.timestamp = timestamp; + } + + public HandleResult(int status, byte[] data) { + this(status, data, 0); + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/MergeHandleResult.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/MergeHandleResult.java new file mode 100644 index 000000000..03f1912dc --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/MergeHandleResult.java @@ -0,0 +1,84 @@ +package ru.vk.itmo.test.timofeevkirill.reference; + +import one.nio.http.HttpSession; +import one.nio.http.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.HttpURLConnection; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; + +public class MergeHandleResult { + private static final Logger log = LoggerFactory.getLogger(MergeHandleResult.class); + private final Queue> resultFutures; + private final Queue results; + private final AtomicInteger count; + private final int ack; + private final int from; + private final HttpSession session; + + public MergeHandleResult(HttpSession session, int size, int ack) { + this.session = session; + this.resultFutures = new ConcurrentLinkedQueue<>(); + this.results = new ConcurrentLinkedQueue<>(); + this.count = new AtomicInteger(); + this.ack = ack; + this.from = size; + } + + @SuppressWarnings("FutureReturnValueIgnored") + public void add(CompletableFuture resultFuture, ExecutorService executor) { + resultFutures.add(resultFuture); + resultFuture.whenCompleteAsync((result, e) -> { + if (e == null) { + results.add(result); + int get = count.incrementAndGet(); + if (get == from) { + sendResult(); + cancelRemainingFutures(); + } + } else { + log.error("Error remote handle result: " + e); + } + }, executor); + } + + private void cancelRemainingFutures() { + CompletableFuture future; + while ((future = resultFutures.poll()) != null) { + future.cancel(true); + } + } + + private void sendResult() { + HandleResult mergedResult = new HandleResult(HttpURLConnection.HTTP_GATEWAY_TIMEOUT, null); + + int positiveCount = 0; + for (HandleResult handleResult : results) { + if (handleResult.status() == HttpURLConnection.HTTP_OK + || handleResult.status() == HttpURLConnection.HTTP_CREATED + || handleResult.status() == HttpURLConnection.HTTP_ACCEPTED + || handleResult.status() == HttpURLConnection.HTTP_NOT_FOUND) { + positiveCount++; + if (mergedResult.timestamp() <= handleResult.timestamp()) { + mergedResult = handleResult; + } + } + } + + try { + if (positiveCount < ack) { + session.sendResponse(new Response(Response.GATEWAY_TIMEOUT, Response.EMPTY)); + } else { + session.sendResponse(new Response(String.valueOf(mergedResult.status()), mergedResult.data())); + } + } catch (Exception e) { + ExceptionUtils.handleErrorFromHandleRequest(e, session); + } + + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/ReferenceServer.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/ReferenceServer.java new file mode 100644 index 000000000..e83065c85 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/ReferenceServer.java @@ -0,0 +1,347 @@ +package ru.vk.itmo.test.timofeevkirill.reference; + +import one.nio.async.CustomThreadFactory; +import one.nio.http.HttpServer; +import one.nio.http.HttpServerConfig; +import one.nio.http.HttpSession; +import one.nio.http.Path; +import one.nio.http.Request; +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; +import ru.vk.itmo.ServiceConfig; +import ru.vk.itmo.test.timofeevkirill.reference.dao.ReferenceBaseEntry; +import ru.vk.itmo.test.timofeevkirill.reference.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.time.Duration; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class ReferenceServer extends HttpServer { + + private static final String HEADER_REMOTE = "X-flag-remote-reference-server-to-node"; + private static final String HEADER_REMOTE_VALUE = "true"; + private static final String HEADER_REMOTE_ONE_NIO_HEADER = HEADER_REMOTE + ": " + HEADER_REMOTE_VALUE; + private static final String HEADER_TIMESTAMP = "X-timestamp-reference-server-to-node"; + private static final String HEADER_TIMESTAMP_ONE_NIO_HEADER = HEADER_TIMESTAMP + ": "; + private static final String BASE_PATH = "/v0/entity"; + private static final String UNPROCESSABLE_CONTENT_RESPONSE = "422 Unprocessable Content"; + private static final String ID_PARAMETER_KEY = "id="; + private static final String ACK_PARAMETER_KEY = "ack="; + private static final String FROM_PARAMETER_KEY = "from="; + private static final Logger log = LoggerFactory.getLogger(ReferenceServer.class); + private static final int COMMON_MILLIS_TIMEOUT = 500; + private final int totalThreads = Runtime.getRuntime().availableProcessors(); + + private final ExecutorService executorLocal = + Executors.newFixedThreadPool(totalThreads / 2, new CustomThreadFactory("local-work")); + private final ExecutorService executorRemote = + Executors.newFixedThreadPool(totalThreads / 2, new CustomThreadFactory("remote-work")); + private final ReferenceDao dao; + private final ServiceConfig config; + private final HttpClient httpClient; + + public ReferenceServer(ServiceConfig config, + ReferenceDao dao) throws IOException { + super(createServerConfigWithPort(config.selfPort())); + this.dao = dao; + this.config = config; + + this.httpClient = HttpClient.newBuilder() + .executor(Executors.newFixedThreadPool(totalThreads)) + .connectTimeout(Duration.ofMillis(COMMON_MILLIS_TIMEOUT)) + .version(HttpClient.Version.HTTP_1_1) + .build(); + } + + private static HttpServerConfig createServerConfigWithPort(int port) { + HttpServerConfig serverConfig = new HttpServerConfig(); + AcceptorConfig acceptorConfig = new AcceptorConfig(); + acceptorConfig.port = port; + acceptorConfig.reusePort = true; + serverConfig.selectors = Runtime.getRuntime().availableProcessors() / 2; + + serverConfig.acceptors = new AcceptorConfig[]{acceptorConfig}; + serverConfig.closeSessions = true; + return serverConfig; + } + + @Override + public void handleRequest(Request request, HttpSession session) throws IOException { + if (!BASE_PATH.equals(request.getPath())) { + session.sendError(Response.BAD_REQUEST, null); + return; + } + + if (request.getMethod() != Request.METHOD_GET + && request.getMethod() != Request.METHOD_DELETE + && request.getMethod() != Request.METHOD_PUT) { + session.sendError(Response.METHOD_NOT_ALLOWED, null); + return; + } + + String id = request.getParameter(ID_PARAMETER_KEY); + if (id == null || id.isBlank()) { + session.sendError(Response.BAD_REQUEST, null); + return; + } + + if (request.getHeader(HEADER_REMOTE_ONE_NIO_HEADER) != null) { + handleLocalAsReplica(request, session, id); + return; + } + + int ack; + int from; + try { + ack = getInt(request, ACK_PARAMETER_KEY, config.clusterUrls().size() / 2 + 1); + from = getInt(request, FROM_PARAMETER_KEY, config.clusterUrls().size()); + } catch (IllegalArgumentException e) { + session.sendError(UNPROCESSABLE_CONTENT_RESPONSE, "Invalid parameter values for ack/from"); + return; + } + + if (from <= 0 || from > config.clusterUrls().size() || ack > from || ack <= 0) { + session.sendError(Response.BAD_REQUEST, "Invalid parameter values for ack/from"); + return; + } + + int[] indexes = getIndexes(id, from); + MergeHandleResult mergeHandleResult = new MergeHandleResult(session, from, ack); + for (int index : indexes) { + String executorNode = config.clusterUrls().get(index); + if (executorNode.equals(config.selfUrl())) { + long currentTime = System.currentTimeMillis(); + handleAsync(executorLocal, mergeHandleResult, () -> local(request, id, currentTime)); + } else { + handleAsync(executorRemote, mergeHandleResult, () -> remote(request, executorNode)); + } + } + } + + @SuppressWarnings("FutureReturnValueIgnored") + private void handleLocalAsReplica(Request request, HttpSession session, String id) { + long currentTime = System.currentTimeMillis(); + executorLocal.execute(() -> local(request, id, currentTime) + .thenAcceptAsync(local -> { + try { + Response response = new Response(String.valueOf(local.status()), local.data()); + response.addHeader(HEADER_TIMESTAMP_ONE_NIO_HEADER + local.timestamp()); + session.sendResponse(response); + } catch (Exception e) { + ExceptionUtils.handleErrorFromHandleRequest(e, session); + } + }, executorLocal)); + } + + private int getInt(Request request, String param, int defaultValue) throws IllegalArgumentException { + int intParameter; + String parameterStr = request.getParameter(param); + if (parameterStr == null || parameterStr.isBlank()) { + intParameter = defaultValue; + } else { + try { + intParameter = Integer.parseInt(parameterStr); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Failed to parse parameter '" + param + "' as an integer: " + parameterStr + ); + } + } + return intParameter; + } + + private CompletableFuture remote(Request request, String executorNode) { + try { + return invokeRemote(executorNode, request); + } catch (IOException e) { + log.info("I/O exception while calling remote node", e); + return wrapCompleted(new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.info("Thread interrupted"); + return wrapCompleted(new HandleResult(HttpURLConnection.HTTP_UNAVAILABLE, Response.EMPTY)); + } + + } + + private void handleAsync(ExecutorService executor, + MergeHandleResult mergeHandleResult, + ExecutableTask runnable) { + try { + executor.execute(() -> { + CompletableFuture handleResult; + try { + handleResult = runnable.run(); + } catch (Exception e) { + log.error("Exception during handleRequest", e); + handleResult = + wrapCompleted(new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY)); + } + + mergeHandleResult.add(handleResult, executor); + }); + } catch (Exception e) { + mergeHandleResult.add( + wrapCompleted(new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY)), + executor + ); + } + } + + @Override + public void handleDefault(Request request, HttpSession session) throws IOException { + session.sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY)); + } + + @Path("/v0/status") + public Response status() { + return Response.ok("OK"); + } + + private CompletableFuture invokeRemote(String executorNode, Request request) + throws IOException, InterruptedException { + HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(executorNode + request.getURI())) + .method( + request.getMethodName(), + request.getBody() == null + ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofByteArray(request.getBody()) + ) + .header(HEADER_REMOTE, HEADER_REMOTE_VALUE) + .timeout(Duration.ofMillis(COMMON_MILLIS_TIMEOUT)) + .build(); + + return httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()) + .thenApplyAsync(httpResponse -> { + Optional timestampValue = httpResponse.headers().firstValue(HEADER_TIMESTAMP); + long timestamp; + if (timestampValue.isPresent()) { + try { + timestamp = Long.parseLong(timestampValue.get()); + } catch (Exception e) { + log.error("Failed to parse timestamp from header: {" + timestampValue.get() + "}"); + timestamp = System.currentTimeMillis(); + } + } else { + timestamp = System.currentTimeMillis(); + } + return new HandleResult(httpResponse.statusCode(), httpResponse.body(), timestamp); + }, executorRemote) + .exceptionally(e -> { + log.info("Exception while calling remote node: ", e); + return new HandleResult(HttpURLConnection.HTTP_INTERNAL_ERROR, Response.EMPTY); + }); + } + + private CompletableFuture local(Request request, String id, long coordinatorTime) { + switch (request.getMethod()) { + case Request.METHOD_GET -> { + MemorySegment key = MemorySegment.ofArray(Utf8.toBytes(id)); + ReferenceBaseEntry entry = dao.get(key); + if (entry == null) { + return wrapCompleted(new HandleResult(HttpURLConnection.HTTP_NOT_FOUND, Response.EMPTY)); + } + if (entry.value() == null) { + return wrapCompleted( + new HandleResult(HttpURLConnection.HTTP_NOT_FOUND, Response.EMPTY, entry.timestamp()) + ); + } + + return wrapCompleted( + new HandleResult( + HttpURLConnection.HTTP_OK, + entry.value().toArray(ValueLayout.JAVA_BYTE), + entry.timestamp() + ) + ); + } + case Request.METHOD_PUT -> { + MemorySegment key = MemorySegment.ofArray(Utf8.toBytes(id)); + MemorySegment value = MemorySegment.ofArray(request.getBody()); + dao.upsert(new ReferenceBaseEntry<>(key, value, coordinatorTime)); + return wrapCompleted(new HandleResult(HttpURLConnection.HTTP_CREATED, Response.EMPTY)); + } + case Request.METHOD_DELETE -> { + MemorySegment key = MemorySegment.ofArray(Utf8.toBytes(id)); + dao.upsert(new ReferenceBaseEntry<>(key, null, coordinatorTime)); + return wrapCompleted(new HandleResult(HttpURLConnection.HTTP_ACCEPTED, Response.EMPTY)); + } + default -> { + return wrapCompleted(new HandleResult(HttpURLConnection.HTTP_BAD_METHOD, Response.EMPTY)); + } + } + } + + private int[] getIndexes(String id, int count) { + assert count <= config.clusterUrls().size(); + + int[] result = new int[count]; + int[] maxHashes = new int[count]; + + for (int i = 0; i < count; i++) { + String url = config.clusterUrls().get(i); + int hash = Hash.murmur3(url + id); + result[i] = i; + maxHashes[i] = hash; + } + + for (int i = count; i < config.clusterUrls().size(); i++) { + String url = config.clusterUrls().get(i); + int hash = Hash.murmur3(url + id); + for (int j = 0; j < maxHashes.length; j++) { + int maxHash = maxHashes[j]; + if (maxHash < hash) { + maxHashes[j] = hash; + result[j] = i; + break; + } + } + } + return result; + } + + private CompletableFuture wrapCompleted(HandleResult result) { + return CompletableFuture.completedFuture(result); + } + + private interface ExecutableTask { + CompletableFuture run() throws IOException; + } + + @Override + public synchronized void stop() { + super.stop(); + shutdownAndAwaitTermination(executorLocal); + shutdownAndAwaitTermination(executorRemote); + } + + private void shutdownAndAwaitTermination(ExecutorService pool) { + pool.shutdown(); + try { + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { + pool.shutdownNow(); + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { + log.error("Pool did not terminate"); + } + } + } catch (InterruptedException ex) { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/ReferenceService.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/ReferenceService.java new file mode 100644 index 000000000..79c372b1f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/ReferenceService.java @@ -0,0 +1,108 @@ +package ru.vk.itmo.test.timofeevkirill.reference; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.vk.itmo.Service; +import ru.vk.itmo.ServiceConfig; +import ru.vk.itmo.dao.Config; +import ru.vk.itmo.test.ServiceFactory; +import ru.vk.itmo.test.timofeevkirill.reference.dao.ReferenceDao; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class ReferenceService implements Service { + private static final Logger log = LoggerFactory.getLogger(ReferenceService.class); + private static final long FLUSHING_THRESHOLD_BYTES = 1024 * 1024; + + private static final String LOCALHOST_PREFIX = "http://localhost:"; + + private final ServiceConfig config; + + private ReferenceDao dao; + private ReferenceServer server; + private boolean stopped; + + public ReferenceService(ServiceConfig config) { + this.config = config; + } + + @Override + public synchronized CompletableFuture start() throws IOException { + dao = new ReferenceDao(new Config(config.workingDir(), FLUSHING_THRESHOLD_BYTES)); + server = new ReferenceServer(config, dao); + server.start(); + stopped = false; + return CompletableFuture.completedFuture(null); + } + + @Override + public synchronized CompletableFuture stop() throws IOException { + if (stopped) { + return CompletableFuture.completedFuture(null); + } + try { + server.stop(); + } finally { + dao.close(); + } + stopped = true; + return CompletableFuture.completedFuture(null); + } + + @ServiceFactory(stage = 5) + public static class Factory implements ServiceFactory.Factory { + + @Override + public Service create(ServiceConfig config) { + return new ReferenceService(config); + } + } + + public static void main(String[] args) throws IOException { + //port -> url + Map nodes = new HashMap<>(); + int nodePort = 8080; + for (int i = 0; i < 3; i++) { + nodes.put(nodePort, LOCALHOST_PREFIX + nodePort); + nodePort += 10; + } + + List clusterUrls = new ArrayList<>(nodes.values()); + List clusterConfs = new ArrayList<>(); + for (Map.Entry entry : nodes.entrySet()) { + int port = entry.getKey(); + String url = entry.getValue(); + Path path = Paths.get("/home/aphirri/IdeaProjects/2024-highload-dht" + + "/src/main/java/ru/vk/itmo/test/timofeevkirill/tmp" + port); + Files.createDirectories(path); + ServiceConfig serviceConfig = new ServiceConfig(port, + url, + clusterUrls, + path); + clusterConfs.add(serviceConfig); + } + + for (ServiceConfig serviceConfig : clusterConfs) { + ReferenceService instance = new ReferenceService(serviceConfig); + try { + instance.start().get(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Error: " + e); + } catch (ExecutionException | TimeoutException e) { + log.error("Error: " + e); + } + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/ByteArraySegment.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/ByteArraySegment.java new file mode 100644 index 000000000..7b2479266 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/ByteArraySegment.java @@ -0,0 +1,48 @@ +package ru.vk.itmo.test.timofeevkirill.reference.dao; + +import java.io.IOException; +import java.lang.foreign.MemorySegment; +import java.nio.ByteBuffer; + +/** + * Growable buffer with {@link ByteBuffer} and {@link MemorySegment} interface. + * + * @author incubos + */ +final class ByteArraySegment { + private byte[] array; + private MemorySegment segment; + + ByteArraySegment(final int capacity) { + this.array = new byte[capacity]; + this.segment = MemorySegment.ofArray(array); + } + + void withArray(final ArrayConsumer consumer) throws IOException { + consumer.process(array); + } + + MemorySegment segment() { + return segment; + } + + void ensureCapacity(final long size) { + if (size > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Too big!"); + } + + final int capacity = (int) size; + if (array.length >= capacity) { + return; + } + + // Grow to the nearest bigger power of 2 + final int newSize = Integer.highestOneBit(capacity) << 1; + array = new byte[newSize]; + segment = MemorySegment.ofArray(array); + } + + interface ArrayConsumer { + void process(byte[] array) throws IOException; + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/LiveFilteringIterator.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/LiveFilteringIterator.java new file mode 100644 index 000000000..2cac6adc4 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/LiveFilteringIterator.java @@ -0,0 +1,52 @@ +package ru.vk.itmo.test.timofeevkirill.reference.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. + * + * @author incubos + */ +final class LiveFilteringIterator implements Iterator> { + private final Iterator> delegate; + private ReferenceBaseEntry next; + + LiveFilteringIterator(final Iterator> delegate) { + this.delegate = delegate; + skipTombstones(); + } + + private void skipTombstones() { + while (delegate.hasNext()) { + final ReferenceBaseEntry entry = delegate.next(); + if (entry.value() != null) { + this.next = entry; + break; + } + } + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public ReferenceBaseEntry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + // Consume + final ReferenceBaseEntry result = next; + next = null; + + skipTombstones(); + + return result; + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/MemTable.java new file mode 100644 index 000000000..b34f6177b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/MemTable.java @@ -0,0 +1,47 @@ +package ru.vk.itmo.test.timofeevkirill.reference.dao; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; +import java.util.NavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; + +/** + * Memory table. + * + * @author incubos + */ +final class MemTable { + private final NavigableMap> map = + new ConcurrentSkipListMap<>( + MemorySegmentComparator.INSTANCE); + + boolean isEmpty() { + return map.isEmpty(); + } + + Iterator> get( + final MemorySegment from, + final MemorySegment to) { + if (from == null && to == null) { + // All + return map.values().iterator(); + } else if (from == null) { + // Head + return map.headMap(to).values().iterator(); + } else if (to == null) { + // Tail + return map.tailMap(from).values().iterator(); + } else { + // Slice + return map.subMap(from, to).values().iterator(); + } + } + + ReferenceBaseEntry get(final MemorySegment key) { + return map.get(key); + } + + ReferenceBaseEntry upsert(final ReferenceBaseEntry entry) { + return map.put(entry.key(), entry); + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/MemorySegmentComparator.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/MemorySegmentComparator.java new file mode 100644 index 000000000..133b83f60 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/MemorySegmentComparator.java @@ -0,0 +1,89 @@ +package ru.vk.itmo.test.timofeevkirill.reference.dao; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.Comparator; + +/** + * Compares {@link MemorySegment}s. + * + * @author incubos + */ +final class MemorySegmentComparator implements Comparator { + static final Comparator INSTANCE = + new MemorySegmentComparator(); + + private MemorySegmentComparator() { + // Singleton + } + + @Override + public int compare( + final MemorySegment left, + final MemorySegment right) { + final long mismatch = left.mismatch(right); + if (mismatch == -1L) { + // No mismatch + return 0; + } + + if (mismatch == left.byteSize()) { + // left is prefix of right, so left is smaller + return -1; + } + + if (mismatch == right.byteSize()) { + // right is prefix of left, so left is greater + return 1; + } + + // Compare mismatched bytes as unsigned + return Byte.compareUnsigned( + left.getAtIndex( + ValueLayout.OfByte.JAVA_BYTE, + mismatch), + right.getAtIndex( + ValueLayout.OfByte.JAVA_BYTE, + mismatch)); + } + + static int compare( + final MemorySegment srcSegment, + final long srcFromOffset, + final long srcLength, + final MemorySegment dstSegment, + final long dstFromOffset, + final long dstLength) { + final long mismatch = + MemorySegment.mismatch( + srcSegment, + srcFromOffset, + srcFromOffset + srcLength, + dstSegment, + dstFromOffset, + dstFromOffset + dstLength); + if (mismatch == -1L) { + // No mismatch + return 0; + } + + if (mismatch == srcLength) { + // left is prefix of right, so left is smaller + return -1; + } + + if (mismatch == dstLength) { + // right is prefix of left, so left is greater + return 1; + } + + // Compare mismatched bytes as unsigned + return Byte.compareUnsigned( + srcSegment.getAtIndex( + ValueLayout.OfByte.JAVA_BYTE, + srcFromOffset + mismatch), + dstSegment.getAtIndex( + ValueLayout.OfByte.JAVA_BYTE, + dstFromOffset + mismatch)); + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/MergingEntryIterator.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/MergingEntryIterator.java new file mode 100644 index 000000000..59dfdc0d0 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/MergingEntryIterator.java @@ -0,0 +1,70 @@ +package ru.vk.itmo.test.timofeevkirill.reference.dao; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.PriorityQueue; +import java.util.Queue; + +/** + * Merges entry {@link Iterator}s. + * + * @author incubos + */ +final class MergingEntryIterator implements Iterator> { + private final Queue iterators; + + MergingEntryIterator(final List iterators) { + assert iterators.stream().allMatch(WeightedPeekingEntryIterator::hasNext); + + this.iterators = new PriorityQueue<>(iterators); + } + + @Override + public boolean hasNext() { + return !iterators.isEmpty(); + } + + @Override + public ReferenceBaseEntry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final WeightedPeekingEntryIterator top = iterators.remove(); + final ReferenceBaseEntry result = top.next(); + + if (top.hasNext()) { + // Not exhausted + iterators.add(top); + } + + // Remove older versions of the key + while (true) { + final WeightedPeekingEntryIterator iterator = iterators.peek(); + if (iterator == null) { + // Nothing left + break; + } + + // Skip entries with the same key + final ReferenceBaseEntry entry = iterator.peek(); + if (MemorySegmentComparator.INSTANCE.compare(result.key(), entry.key()) != 0) { + // Reached another key + break; + } + + // Drop + iterators.remove(); + // Skip + iterator.next(); + if (iterator.hasNext()) { + // Not exhausted + iterators.add(iterator); + } + } + + return result; + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/ReferenceBaseEntry.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/ReferenceBaseEntry.java new file mode 100644 index 000000000..428e75a31 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/ReferenceBaseEntry.java @@ -0,0 +1,10 @@ +package ru.vk.itmo.test.timofeevkirill.reference.dao; + +import ru.vk.itmo.dao.Entry; + +public record ReferenceBaseEntry(D key, D value, long timestamp) implements Entry { + @Override + public String toString() { + return "{" + key + ":" + value + "," + timestamp + "}"; + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/ReferenceDao.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/ReferenceDao.java new file mode 100644 index 000000000..6fba8b1e3 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/ReferenceDao.java @@ -0,0 +1,292 @@ +package ru.vk.itmo.test.timofeevkirill.reference.dao; + +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.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Reference implementation of {@link Dao}. + * + * @author incubos + */ +public class ReferenceDao implements Dao> { + private final Config config; + private final Arena arena; + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + // Guarded by lock + private volatile TableSet tableSet; + + private final ExecutorService flusher = + Executors.newSingleThreadExecutor(r -> { + final Thread result = new Thread(r); + result.setName("flusher"); + return result; + }); + private final ExecutorService compactor = + Executors.newSingleThreadExecutor(r -> { + final Thread result = new Thread(r); + result.setName("compactor"); + return result; + }); + + private final AtomicBoolean closed = new AtomicBoolean(); + + public ReferenceDao(final Config config) throws IOException { + this.config = config; + this.arena = Arena.ofShared(); + + // First complete promotion of compacted SSTables + SSTables.promote( + config.basePath(), + 0, + 1); + + this.tableSet = + TableSet.from( + SSTables.discover( + arena, + config.basePath())); + } + + @Override + public Iterator> get( + final MemorySegment from, + final MemorySegment to) { + return new LiveFilteringIterator( + tableSet.get( + from, + to)); + } + + @Override + public ReferenceBaseEntry get(final MemorySegment key) { + // Without lock, just snapshot of table set + return tableSet.get(key); + } + + @Override + public void upsert(final ReferenceBaseEntry entry) { + final boolean autoFlush; + lock.readLock().lock(); + try { + if (tableSet.memTableSize.get() > config.flushThresholdBytes() + && tableSet.flushingTable != null) { + throw new IllegalStateException("Can't keep up with flushing!"); + } + + // Upsert + final Entry previous = tableSet.upsert(entry); + + // Update size estimate + final long size = tableSet.memTableSize.addAndGet(sizeOf(entry) - sizeOf(previous)); + autoFlush = size > config.flushThresholdBytes(); + } finally { + lock.readLock().unlock(); + } + + if (autoFlush) { + initiateFlush(true); + } + } + + private static long sizeOf(final Entry entry) { + if (entry == null) { + return 0L; + } + + if (entry.value() == null) { + return entry.key().byteSize(); + } + + return entry.key().byteSize() + entry.value().byteSize(); + } + + private void initiateFlush(final boolean auto) { + flusher.submit(() -> { + final TableSet currentTableSet; + lock.writeLock().lock(); + try { + if (this.tableSet.memTable.isEmpty()) { + // Nothing to flush + return; + } + + if (auto && this.tableSet.memTableSize.get() < config.flushThresholdBytes()) { + // Not enough data to flush + return; + } + + // Switch memTable to flushing + currentTableSet = this.tableSet.flushing(); + this.tableSet = currentTableSet; + } finally { + lock.writeLock().unlock(); + } + + // Write + final int sequence = currentTableSet.nextSequence(); + try { + new SSTableWriter() + .write( + config.basePath(), + sequence, + currentTableSet.flushingTable.get(null, null)); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-1); + return; + } + + // Open + final SSTable flushed; + try { + flushed = SSTables.open( + arena, + config.basePath(), + sequence); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-2); + return; + } + + // Switch + lock.writeLock().lock(); + try { + this.tableSet = this.tableSet.flushed(flushed); + } finally { + lock.writeLock().unlock(); + } + }).state(); + } + + @Override + public void flush() throws IOException { + initiateFlush(false); + } + + @Override + public void compact() throws IOException { + compactor.submit(() -> { + final TableSet currentTableSet; + lock.writeLock().lock(); + try { + currentTableSet = this.tableSet; + if (currentTableSet.ssTables.size() < 2) { + // Nothing to compact + return; + } + } finally { + lock.writeLock().unlock(); + } + + // Compact to 0 + try { + new SSTableWriter() + .write( + config.basePath(), + 0, + new LiveFilteringIterator( + currentTableSet.allSSTableEntries())); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-3); + } + + // Open 0 + final SSTable compacted; + try { + compacted = + SSTables.open( + arena, + config.basePath(), + 0); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-4); + return; + } + + // Replace old SSTables with compacted one to + // keep serving requests + final Set replaced = new HashSet<>(currentTableSet.ssTables); + lock.writeLock().lock(); + try { + this.tableSet = + this.tableSet.compacted( + replaced, + compacted); + } finally { + lock.writeLock().unlock(); + } + + // Remove compacted SSTables starting from the oldest ones. + // If we crash, 0 contains all the data, and + // it will be promoted on reopen. + for (final SSTable ssTable : currentTableSet.ssTables.reversed()) { + try { + SSTables.remove( + config.basePath(), + ssTable.sequence); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-5); + } + } + + // Promote zero to one (possibly replacing) + try { + SSTables.promote( + config.basePath(), + 0, + 1); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-6); + } + + // Replace promoted SSTable + lock.writeLock().lock(); + try { + this.tableSet = + this.tableSet.compacted( + Collections.singleton(compacted), + compacted.withSequence(1)); + } finally { + lock.writeLock().unlock(); + } + }).state(); + } + + @Override + public void close() throws IOException { + if (closed.getAndSet(true)) { + // Already closed + return; + } + + // Maybe flush + flush(); + + // Stop all the threads + flusher.close(); + compactor.close(); + + // Close arena + arena.close(); + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/SSTable.java new file mode 100644 index 000000000..d4f1570f5 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/SSTable.java @@ -0,0 +1,208 @@ +package ru.vk.itmo.test.timofeevkirill.reference.dao; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Persistent SSTable in data file and index file. + * + * @author incubos + * @see SSTables + */ +final class SSTable { + final int sequence; + + private final MemorySegment index; + private final MemorySegment data; + private final long size; + + SSTable( + final int sequence, + final MemorySegment index, + final MemorySegment data) { + this.sequence = sequence; + this.index = index; + this.data = data; + this.size = index.byteSize() / Long.BYTES; + } + + SSTable withSequence(final int sequence) { + return new SSTable( + sequence, + index, + data); + } + + /** + * Returns index of the entry if found; otherwise, (-(insertion point) - 1). + * The insertion point is defined as the point at which the key would be inserted: + * the index of the first element greater than the key, + * or size if all keys are less than the specified key. + * Note that this guarantees that the return value will be >= 0 + * if and only if the key is found. + */ + private long entryBinarySearch(final MemorySegment key) { + long low = 0L; + long high = size - 1; + + while (low <= high) { + final long mid = (low + high) >>> 1; + final long midEntryOffset = entryOffset(mid); + final long midKeyLength = getLength(midEntryOffset); + final int compare = + MemorySegmentComparator.compare( + data, + midEntryOffset + Long.BYTES, // Position at key + midKeyLength, + key, + 0L, + key.byteSize()); + + if (compare < 0) { + low = mid + 1; + } else if (compare > 0) { + high = mid - 1; + } else { + return mid; + } + } + + return -(low + 1); + } + + private long entryOffset(final long entry) { + return index.get( + ValueLayout.OfLong.JAVA_LONG, + entry * Long.BYTES); + } + + private long getLength(final long offset) { + return data.get( + ValueLayout.OfLong.JAVA_LONG_UNALIGNED, + offset); + } + + Iterator> get( + final MemorySegment from, + final MemorySegment to) { + assert from == null || to == null || MemorySegmentComparator.INSTANCE.compare(from, to) <= 0; + + // Slice of SSTable in absolute offsets + final long fromOffset; + final long toOffset; + + // Left offset bound + if (from == null) { + // Start from the beginning + fromOffset = 0L; + } else { + final long fromEntry = entryBinarySearch(from); + if (fromEntry >= 0L) { + fromOffset = entryOffset(fromEntry); + } else if (-fromEntry - 1 == size) { + // No relevant data + return Collections.emptyIterator(); + } else { + // Greater but existing key found + fromOffset = entryOffset(-fromEntry - 1); + } + } + + // Right offset bound + if (to == null) { + // Up to the end + toOffset = data.byteSize(); + } else { + final long toEntry = entryBinarySearch(to); + if (toEntry >= 0L) { + toOffset = entryOffset(toEntry); + } else if (-toEntry - 1 == size) { + // Up to the end + toOffset = data.byteSize(); + } else { + // Greater but existing key found + toOffset = entryOffset(-toEntry - 1); + } + } + + return new SliceIterator(fromOffset, toOffset); + } + + ReferenceBaseEntry get(final MemorySegment key) { + final long entry = entryBinarySearch(key); + if (entry < 0) { + return null; + } + + // Skip key (will reuse the argument) + long offset = entryOffset(entry); + offset += Long.BYTES + key.byteSize(); + // Extract timestamp + long timestamp = data.get(ValueLayout.OfLong.JAVA_LONG_UNALIGNED, offset); + offset += Long.BYTES; + // Extract value length + final long valueLength = getLength(offset); + if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { + // Tombstone encountered + return new ReferenceBaseEntry<>(key, null, timestamp); + } else { + // Get value + offset += Long.BYTES; + final MemorySegment value = data.asSlice(offset, valueLength); + return new ReferenceBaseEntry<>(key, value, timestamp); + } + } + + private final class SliceIterator implements Iterator> { + private long offset; + private final long toOffset; + + private SliceIterator( + final long offset, + final long toOffset) { + this.offset = offset; + this.toOffset = toOffset; + } + + @Override + public boolean hasNext() { + return offset < toOffset; + } + + @Override + public ReferenceBaseEntry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + // Read key length + final long keyLength = getLength(offset); + offset += Long.BYTES; + + // Read key + final MemorySegment key = data.asSlice(offset, keyLength); + offset += keyLength; + + // Read timestamp + long timestamp = data.get(ValueLayout.OfLong.JAVA_LONG_UNALIGNED, offset); + offset += Long.BYTES; + + // Read value length + final long valueLength = getLength(offset); + offset += Long.BYTES; + + // Read value + if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { + // Tombstone encountered + return new ReferenceBaseEntry<>(key, null, timestamp); + } else { + final MemorySegment value = data.asSlice(offset, valueLength); + offset += valueLength; + return new ReferenceBaseEntry<>(key, value, timestamp); + } + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/SSTableWriter.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/SSTableWriter.java new file mode 100644 index 000000000..d8b439cc5 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/SSTableWriter.java @@ -0,0 +1,169 @@ +package ru.vk.itmo.test.timofeevkirill.reference.dao; + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Iterator; + +/** + * Writes {@link ReferenceBaseEntry} {@link Iterator} to SSTable on disk. + * + *

Index file {@code .index} contains {@code long} offsets to entries in data file: + * {@code [offset0, offset1, ...]} + * + *

Data file {@code .data} contains serialized entries: + * {@code } + * + *

Tombstones are encoded as {@code valueLength} {@code -1} and no subsequent value. + * + * @author incubos + */ +final class SSTableWriter { + private static final int BUFFER_SIZE = 64 * 1024; + + // Reusable buffers to eliminate allocations. + // But excessive memory copying is still there :( + // Long cell + private final ByteArraySegment longBuffer = new ByteArraySegment(Long.BYTES); + // Growable blob cell + private final ByteArraySegment blobBuffer = new ByteArraySegment(512); + + void write( + final Path baseDir, + final int sequence, + final Iterator> entries) throws IOException { + // Write to temporary files + final Path tempIndexName = SSTables.tempIndexName(baseDir, sequence); + final Path tempDataName = SSTables.tempDataName(baseDir, sequence); + + // Delete temporary files to eliminate tails + Files.deleteIfExists(tempIndexName); + Files.deleteIfExists(tempDataName); + + // Iterate in a single pass! + // Will write through FileChannel despite extra memory copying and + // no buffering (which may be implemented later). + // Looking forward to MemorySegment facilities in FileChannel! + try (OutputStream index = + new BufferedOutputStream( + new FileOutputStream( + tempIndexName.toFile()), + BUFFER_SIZE); + OutputStream data = + new BufferedOutputStream( + new FileOutputStream( + tempDataName.toFile()), + BUFFER_SIZE)) { + long entryOffset = 0L; + + // Iterate and serialize + while (entries.hasNext()) { + // First write offset to the entry + writeLong(entryOffset, index); + + // Then write the entry + final ReferenceBaseEntry entry = entries.next(); + entryOffset += writeEntry(entry, data); + } + } + + // Publish files atomically + // FIRST index, LAST data + final Path indexName = + SSTables.indexName( + baseDir, + sequence); + Files.move( + tempIndexName, + indexName, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + final Path dataName = + SSTables.dataName( + baseDir, + sequence); + Files.move( + tempDataName, + dataName, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + } + + private void writeLong( + final long value, + final OutputStream os) throws IOException { + longBuffer.segment().set( + ValueLayout.OfLong.JAVA_LONG_UNALIGNED, + 0, + value); + longBuffer.withArray(os::write); + } + + private void writeSegment( + final MemorySegment value, + final OutputStream os) throws IOException { + final long size = value.byteSize(); + blobBuffer.ensureCapacity(size); + MemorySegment.copy( + value, + 0L, + blobBuffer.segment(), + 0L, + size); + blobBuffer.withArray(array -> + os.write( + array, + 0, + (int) size)); + } + + /** + * Writes {@link ReferenceBaseEntry} to {@link FileChannel}. + * + * @return written bytes + */ + private long writeEntry( + final ReferenceBaseEntry 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 + writeLong(key.byteSize(), os); + result += Long.BYTES; + + // Key + writeSegment(key, os); + result += key.byteSize(); + + // timestamp + writeLong(timestamp, os); + result += Long.BYTES; + + // Value size and possibly value + if (value == null) { + // Tombstone + writeLong(SSTables.TOMBSTONE_VALUE_LENGTH, os); + result += Long.BYTES; + } else { + // Value length + writeLong(value.byteSize(), os); + result += Long.BYTES; + + // Value + writeSegment(value, os); + result += value.byteSize(); + } + + return result; + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/SSTables.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/SSTables.java new file mode 100644 index 000000000..150659daa --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/SSTables.java @@ -0,0 +1,162 @@ +package ru.vk.itmo.test.timofeevkirill.reference.dao; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +/** + * Provides {@link SSTable} management facilities: dumping and discovery. + * + * @author incubos + */ +final class SSTables { + public static final String INDEX_SUFFIX = ".index"; + public static final String DATA_SUFFIX = ".data"; + public static final long TOMBSTONE_VALUE_LENGTH = -1L; + + private static final String TEMP_SUFFIX = ".tmp"; + + /** + * Can't instantiate. + */ + private SSTables() { + // Only static methods + } + + static Path indexName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + INDEX_SUFFIX); + } + + static Path dataName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + DATA_SUFFIX); + } + + static Path tempIndexName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + INDEX_SUFFIX + TEMP_SUFFIX); + } + + static Path tempDataName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + DATA_SUFFIX + TEMP_SUFFIX); + } + + /** + * Returns {@link List} of {@link SSTable}s from freshest to oldest. + */ + static List discover( + final Arena arena, + final Path baseDir) throws IOException { + if (!Files.exists(baseDir)) { + return Collections.emptyList(); + } + + final List result = new ArrayList<>(); + try (Stream files = Files.list(baseDir)) { + files.forEach(file -> { + final String fileName = file.getFileName().toString(); + if (!fileName.endsWith(DATA_SUFFIX)) { + // Skip non data + return; + } + + final int sequence = + // .data -> N + Integer.parseInt( + fileName.substring( + 0, + fileName.length() - DATA_SUFFIX.length())); + + try { + result.add(open(arena, baseDir, sequence)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + + // Sort from freshest to oldest + result.sort((o1, o2) -> Integer.compare(o2.sequence, o1.sequence)); + + return Collections.unmodifiableList(result); + } + + static SSTable open( + final Arena arena, + final Path baseDir, + final int sequence) throws IOException { + final MemorySegment index = + mapReadOnly( + arena, + indexName(baseDir, sequence)); + final MemorySegment data = + mapReadOnly( + arena, + dataName(baseDir, sequence)); + + return new SSTable( + sequence, + index, + data); + } + + private static MemorySegment mapReadOnly( + final Arena arena, + final Path file) throws IOException { + try (FileChannel channel = + FileChannel.open( + file, + StandardOpenOption.READ)) { + return channel.map( + FileChannel.MapMode.READ_ONLY, + 0L, + Files.size(file), + arena); + } + } + + static void remove( + final Path baseDir, + final int sequence) throws IOException { + // First delete data file to make SSTable invisible + Files.delete(dataName(baseDir, sequence)); + Files.delete(indexName(baseDir, sequence)); + } + + static void promote( + final Path baseDir, + final int from, + final int to) throws IOException { + // Build to progress to the same outcome + if (Files.exists(indexName(baseDir, from))) { + Files.move( + indexName(baseDir, from), + indexName(baseDir, to), + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + } + if (Files.exists(dataName(baseDir, from))) { + Files.move( + dataName(baseDir, from), + dataName(baseDir, to), + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/TableSet.java new file mode 100644 index 000000000..11f53fd71 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/TableSet.java @@ -0,0 +1,199 @@ +package ru.vk.itmo.test.timofeevkirill.reference.dao; + +import java.lang.foreign.MemorySegment; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Data set in various tables. + * + * @author incubos + */ +final class TableSet { + final MemTable memTable; + final AtomicLong memTableSize; + // null or read-only + final MemTable flushingTable; + // From freshest to oldest + final List ssTables; + + private TableSet( + final MemTable memTable, + final AtomicLong memTableSize, + final MemTable flushingTable, + final List ssTables) { + this.memTable = memTable; + this.memTableSize = memTableSize; + this.flushingTable = flushingTable; + this.ssTables = ssTables; + } + + static TableSet from(final List ssTables) { + return new TableSet( + new MemTable(), + new AtomicLong(), + null, + 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"); + } + + if (flushingTable != null) { + throw new IllegalStateException("Already flushing"); + } + + return new TableSet( + new MemTable(), + new AtomicLong(), + memTable, + ssTables); + } + + TableSet flushed(final SSTable flushed) { + final List newSSTables = new ArrayList<>(ssTables.size() + 1); + newSSTables.add(flushed); + newSSTables.addAll(ssTables); + return new TableSet( + memTable, + memTableSize, + null, + newSSTables); + } + + TableSet compacted( + final Set replaced, + final SSTable with) { + final List newSsTables = new ArrayList<>(this.ssTables.size() + 1); + + // Keep not replaced SSTables + for (final SSTable ssTable : this.ssTables) { + if (!replaced.contains(ssTable)) { + newSsTables.add(ssTable); + } + } + + // Logically the oldest one + newSsTables.add(with); + + return new TableSet( + memTable, + memTableSize, + flushingTable, + newSsTables); + } + + Iterator> get( + final MemorySegment from, + final MemorySegment to) { + final List iterators = + new ArrayList<>(2 + ssTables.size()); + + // MemTable goes first + final Iterator> memTableIterator = + memTable.get(from, to); + if (memTableIterator.hasNext()) { + iterators.add( + new WeightedPeekingEntryIterator( + Integer.MIN_VALUE, + memTableIterator)); + } + + // Then goes flushing + if (flushingTable != null) { + final Iterator> flushingIterator = + flushingTable.get(from, to); + if (flushingIterator.hasNext()) { + iterators.add( + new WeightedPeekingEntryIterator( + Integer.MIN_VALUE + 1, + flushingIterator)); + } + } + + // Then go all the SSTables + for (int i = 0; i < ssTables.size(); i++) { + final SSTable ssTable = ssTables.get(i); + final Iterator> ssTableIterator = + ssTable.get(from, to); + if (ssTableIterator.hasNext()) { + iterators.add( + new WeightedPeekingEntryIterator( + i, + ssTableIterator)); + } + } + + return switch (iterators.size()) { + case 0 -> Collections.emptyIterator(); + case 1 -> iterators.get(0); + default -> new MergingEntryIterator(iterators); + }; + } + + ReferenceBaseEntry get(final MemorySegment key) { + // Slightly optimized version not to pollute the heap + + // First check MemTable + ReferenceBaseEntry result = memTable.get(key); + if (result != null) { + // Transform tombstone + return result; + } + + // Then check flushing + if (flushingTable != null) { + result = flushingTable.get(key); + if (result != null) { + // Transform tombstone + return result; + } + } + + // At last check SSTables from freshest to oldest + for (final SSTable ssTable : ssTables) { + result = ssTable.get(key); + if (result != null) { + // Transform tombstone + return result; + } + } + + // Nothing found + return null; + } + + ReferenceBaseEntry upsert(final ReferenceBaseEntry entry) { + return memTable.upsert(entry); + } + + 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 = + ssTable.get(null, null); + iterators.add( + new WeightedPeekingEntryIterator( + i, + ssTableIterator)); + } + + return new MergingEntryIterator(iterators); + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/WeightedPeekingEntryIterator.java b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/WeightedPeekingEntryIterator.java new file mode 100644 index 000000000..71aa1042a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/dao/WeightedPeekingEntryIterator.java @@ -0,0 +1,65 @@ +package ru.vk.itmo.test.timofeevkirill.reference.dao; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Peeking {@link Iterator} wrapper. + * + * @author incubos + */ +final class WeightedPeekingEntryIterator + implements Iterator>, + Comparable { + private final int weight; + private final Iterator> delegate; + private ReferenceBaseEntry next; + + WeightedPeekingEntryIterator( + final int weight, + final Iterator> delegate) { + this.weight = weight; + this.delegate = delegate; + this.next = delegate.hasNext() ? delegate.next() : null; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public ReferenceBaseEntry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final ReferenceBaseEntry result = next; + next = delegate.hasNext() ? delegate.next() : null; + return result; + } + + ReferenceBaseEntry peek() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + return next; + } + + @Override + public int compareTo(final WeightedPeekingEntryIterator other) { + // First compare keys + int result = + MemorySegmentComparator.INSTANCE.compare( + peek().key(), + other.peek().key()); + if (result != 0) { + return result; + } + + // Then compare weights if keys are equal + return Integer.compare(weight, other.weight); + } +} diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/wrk_scripts/get.lua b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/wrk_scripts/get.lua new file mode 100644 index 000000000..a194e1bba --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/wrk_scripts/get.lua @@ -0,0 +1,9 @@ +counter = 100000000 + +function request() + counter = counter + 1 + headers = {} + headers["Host"] = "localhost:8080" + return wrk.format("GET", "/v0/entity?id=" .. tostring(counter), headers) + +end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/wrk_scripts/get2.lua b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/wrk_scripts/get2.lua new file mode 100644 index 000000000..af0501626 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/wrk_scripts/get2.lua @@ -0,0 +1,9 @@ +math.randomseed(os.time()) + +function request() + counter = math.random(100000100, 100000000 + 5000000) + headers = {} + headers["Host"] = "localhost:8080" + return wrk.format("GET", "/v0/entity?id=" .. tostring(counter), headers) + +end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/wrk_scripts/put.lua b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/wrk_scripts/put.lua new file mode 100644 index 000000000..90ba7a1a7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/reference/wrk_scripts/put.lua @@ -0,0 +1,12 @@ +counter = 100000000 + +function request() + counter = counter + 1 + body = tostring(counter) .. tostring(counter) .. tostring(counter) + headers = {} + headers["Content-Type"] = "text/plain" + headers["Content-Length"] = #{string.byte(body, 1, -1)} + headers["Host"] = "localhost:8080" + return wrk.format("PUT", "/v0/entity?id=" .. tostring(counter), headers, body) + +end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/async_4.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/async_4.html new file mode 100644 index 000000000..005c1f8ac --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/async_4.html @@ -0,0 +1,3251 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/png/async_4.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/png/async_4.png new file mode 100644 index 000000000..931027608 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/png/async_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/png/sync_4.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/png/sync_4.png new file mode 100644 index 000000000..65155eab1 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/png/sync_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/sync_4.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/sync_4.html new file mode 100644 index 000000000..159d71e4b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/sync_4.html @@ -0,0 +1,3275 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/async_4.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/async_4.html new file mode 100644 index 000000000..482df9904 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/async_4.html @@ -0,0 +1,18093 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/png/async_4.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/png/async_4.png new file mode 100644 index 000000000..a09302153 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/png/async_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/png/sync_4.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/png/sync_4.png new file mode 100644 index 000000000..f7deb7360 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/png/sync_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/sync_4.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/sync_4.html new file mode 100644 index 000000000..bd95aab8f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/sync_4.html @@ -0,0 +1,17765 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/async_4.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/async_4.html new file mode 100644 index 000000000..dda40827d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/async_4.html @@ -0,0 +1,1128 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/png/async_4.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/png/async_4.png new file mode 100644 index 000000000..ce0218b8e Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/png/async_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/png/sync_4.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/png/sync_4.png new file mode 100644 index 000000000..f7fa640f8 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/png/sync_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/sync_4.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/sync_4.html new file mode 100644 index 000000000..6143f1a0b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/sync_4.html @@ -0,0 +1,1135 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/async_4.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/async_4.html new file mode 100644 index 000000000..46de08740 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/async_4.html @@ -0,0 +1,3193 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/png/async_4.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/png/async_4.png new file mode 100644 index 000000000..aea25a436 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/png/async_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/png/sync_4.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/png/sync_4.png new file mode 100644 index 000000000..1f506d498 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/png/sync_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/png/test.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/png/test.png new file mode 100644 index 000000000..d920a6ceb Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/png/test.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/sync_4.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/sync_4.html new file mode 100644 index 000000000..087b438b9 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/sync_4.html @@ -0,0 +1,3385 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/test.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/test.html new file mode 100644 index 000000000..3038824c1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/test.html @@ -0,0 +1,3244 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/async_4.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/async_4.html new file mode 100644 index 000000000..f2e9c14eb --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/async_4.html @@ -0,0 +1,17546 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/png/async_4.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/png/async_4.png new file mode 100644 index 000000000..629ef3c40 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/png/async_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/png/sync_4.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/png/sync_4.png new file mode 100644 index 000000000..8ab3f37dd Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/png/sync_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/png/test.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/png/test.png new file mode 100644 index 000000000..76a878168 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/png/test.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/sync_4.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/sync_4.html new file mode 100644 index 000000000..d438b8ce8 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/sync_4.html @@ -0,0 +1,17594 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/test.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/test.html new file mode 100644 index 000000000..53467c903 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/test.html @@ -0,0 +1,18191 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/async_4.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/async_4.html new file mode 100644 index 000000000..89217301c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/async_4.html @@ -0,0 +1,990 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/png/async_4.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/png/async_4.png new file mode 100644 index 000000000..3c3308dec Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/png/async_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/png/sync_4.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/png/sync_4.png new file mode 100644 index 000000000..7e7c1d6f0 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/png/sync_4.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/png/test.png b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/png/test.png new file mode 100644 index 000000000..4bcc1cc5e Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/png/test.png differ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/sync_4.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/sync_4.html new file mode 100644 index 000000000..369748e14 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/sync_4.html @@ -0,0 +1,1013 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/test.html b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/test.html new file mode 100644 index 000000000..788722ca3 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/test.html @@ -0,0 +1,1025 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/readme.md b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/readme.md new file mode 100644 index 000000000..be39b18f8 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/readme.md @@ -0,0 +1,143 @@ +# Этап 5. Async +## Setup +- WRK + ```wrk -d 2m -t 1 -c 1 -R {value} -s {put/get}.lua -L http://localhost:8080/v0/entry``` +- ASYNC-PROFILE + ```asprof -d 130 -e cpu,alloc -f ./{name}.jfr ApplicationServer``` +- CONVERTER + ```java -cp lib/converter.jar jfr2flame {--alloc / --state default / --lock } ./{name}.jfr ./{name}.html``` + +See cpu flag issue, fixed from v3.0 async-profiler (https://github.com/async-profiler/async-profiler/issues/740) + +## Content table +3 ноды в кластере. За основу взята референсная реализация и доработана асинхронным http клиентом. Измерения "до" пришлось измерить заново. + +## PUT Research +### 14 thds - предыдущая точка разладки +sync +``` + 50.000% 4.98ms + 75.000% 7.85ms + 90.000% 11.22ms + 99.000% 26.77ms + 99.900% 37.12ms + 99.990% 40.26ms + 99.999% 41.76ms +100.000% 42.78ms +``` + +async +Новая точка разладки - 110 thds +``` + 50.000% 1.32ms + 75.000% 2.12ms + 90.000% 3.02ms + 99.000% 21.57ms + 99.900% 44.61ms + 99.990% 47.55ms + 99.999% 48.51ms +100.000% 49.38ms +``` +120 thds +``` + 50.000% 8.03s + 75.000% 11.07s + 90.000% 13.03s + 99.000% 14.26s + 99.900% 14.41s + 99.990% 14.46s + 99.999% 14.48s +100.000% 14.49s +``` + +CPU (sync) +![put](https://github.com/NoGe4Ek/2024-highload-dht/blob/feature/task5/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/png/sync_4.png) +CPU (async) +![put](https://github.com/NoGe4Ek/2024-highload-dht/blob/feature/task5/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/cpu/png/async_4.png) + +На профиле есть аномалия, SharedRuntime занимает 26% сэмплов, SchedulableTask.run() занимает 23% CPU. +Насколько понял, в MultiExchange.responseAsyncImpl есть логика, завязанная на retries & redirect. +Внутри много регистраторов таймера и его остановок в случае timeout'а. Думаю, в нем проблема, можно попробовать увеличить timeout. +SchedulableTask.run() - по идее класс отвечает за постановку задач в очередь на потоки. Насколько я понимаю, он помогает +в обработке цепочек в completable future. Единственное, чего я не понимаю - это почему он такой же большой для sync флейм графа. + +Появился ForkJoinWorkerThread. Он отвечает за выполнение ForkJoinTask CompletableFuture. +Когда вызывается метод MergeHandleResult.sendResult(), он выполняется в контексте этого потока. + +PayloadThread.run уменьшился с 38% до 26%, тк теперь ожидание асинхронное и ноды выполняют больше полезной нагрузки и +меньше простаивают. + +MultiExchange.responseAsync переехала в Thread.run. +sync local handleRequest (4.51%) vs async handleAsReplica (5%) + +ALLOC (sync) +![put](https://github.com/NoGe4Ek/2024-highload-dht/blob/feature/task5/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/png/sync_4.png) +ALLOC (async) +![put](https://github.com/NoGe4Ek/2024-highload-dht/blob/feature/task5/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/alloc/png/async_4.png) + +Добавилась обработка ConcurrentLinkedQueue (36 + 116 сэмплов, 0.06 %+ 0.18%). + +Также появился появился ForkJoinWorkerThread, который занимает 1.66%. + +HttpClientFacade.send для синк подхода ~23000 сэмплов (44.45%), для асинк подхода HttpClientFacade.sendAsync ~8000 (12.14%). + +LOCK (sync) +![put](https://github.com/NoGe4Ek/2024-highload-dht/blob/feature/task5/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/png/sync_4.png) +LOCK (async) +![put](https://github.com/NoGe4Ek/2024-highload-dht/blob/feature/task5/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/put/lock/png/async_4.png) + +HttpClientFacade.send для синк подхода ~1_300_000 сэмплов (37.37%), для асинк подхода HttpClientFacade.sendAsync ~1_130_000 (25.29%) - +MultiExchange.responseAsync переехала в Thread.run и выросла с 22.60% до 29.69%. В сумме стало больше, что логично, тк +асинхронных операций стало больше. + +## GET Research +### 13 thds - предыдущая точка разладки +sync +``` + 50.000% 3.59ms + 75.000% 5.56ms + 90.000% 8.17ms + 99.000% 12.96ms + 99.900% 15.90ms + 99.990% 17.58ms + 99.999% 18.74ms +100.000% 19.60ms +``` + +asyc +Новая точка разладки - 60 thds +``` + 50.000% 1.39ms + 75.000% 1.82ms + 90.000% 2.29ms + 99.000% 3.38ms + 99.900% 5.80ms + 99.990% 12.28ms + 99.999% 13.49ms +100.000% 15.02ms +``` + +CPU (sync) +![get](https://github.com/NoGe4Ek/2024-highload-dht/blob/feature/task5/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/png/sync_4.png) +CPU (async) +![get](https://github.com/NoGe4Ek/2024-highload-dht/blob/feature/task5/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/cpu/png/async_4.png) + +В асинхронной версии processHttpBuffer() занимает почти 40% CPU сэмплов, а в синхронной -- меньше 4%. +processRead -> processHttpBuffer -> 22% просидели в HttpSession.closing -> это триггерило exception Bad request. +Но я подозреваю, что это также связано с маленьким timeout'ом, для http клиента. + +ALLOC (sync) +![get](https://github.com/NoGe4Ek/2024-highload-dht/blob/feature/task5/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/png/sync_4.png) +ALLOC (async) +![get](https://github.com/NoGe4Ek/2024-highload-dht/blob/feature/task5/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/alloc/png/async_4.png) + +MultiExchange.responseAsync переехала в Thread.run и почти не изменилась (30% vs 31%). + +LOCK (sync) +![get](https://github.com/NoGe4Ek/2024-highload-dht/blob/feature/task5/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/png/sync_4.png) +LOCK (async) +![get](https://github.com/NoGe4Ek/2024-highload-dht/blob/feature/task5/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/asprof/get/lock/png/async_4.png) + +HttpClientFacade.send для синк подхода ~1_550_000 сэмплов (36.97%), для асинк подхода HttpClientFacade.sendAsync ~1_200_000 (24.17%). +MultiExchange.responseAsync 11.38% -> 3.33%. Кажется, немного аномальная ситуация, тк я не ожидал такого изменения. Вероятно +связано с аномальным простоем в processHttpBuffer. diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/async_1 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/async_1 new file mode 100644 index 000000000..73d01820a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/async_1 @@ -0,0 +1,136 @@ +./wrk -d 2m -t 8 -c 64 -R 105000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/get.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 1846.677ms, rate sampling interval: 6483ms + Thread calibration: mean lat.: 1842.566ms, rate sampling interval: 6459ms + Thread calibration: mean lat.: 1845.913ms, rate sampling interval: 6475ms + Thread calibration: mean lat.: 1845.646ms, rate sampling interval: 6488ms + Thread calibration: mean lat.: 1843.490ms, rate sampling interval: 6475ms + Thread calibration: mean lat.: 1834.223ms, rate sampling interval: 6451ms + Thread calibration: mean lat.: 1848.866ms, rate sampling interval: 6488ms + Thread calibration: mean lat.: 1847.143ms, rate sampling interval: 6483ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 22.50s 10.74s 41.03s 57.87% + Req/Sec 8.67k 74.32 8.78k 71.54% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 22.66s + 75.000% 31.80s + 90.000% 37.29s + 99.000% 40.57s + 99.900% 40.89s + 99.990% 40.99s + 99.999% 41.03s +100.000% 41.06s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 3543.039 0.000000 1 1.00 + 7491.583 0.100000 763114 1.11 + 11337.727 0.200000 1526216 1.25 + 15138.815 0.300000 2288436 1.43 + 18907.135 0.400000 3052778 1.67 + 22659.071 0.500000 3813680 2.00 + 24510.463 0.550000 4196912 2.22 + 26345.471 0.600000 4578107 2.50 + 28164.095 0.650000 4957816 2.86 + 29999.103 0.700000 5340566 3.33 + 31801.343 0.750000 5719811 4.00 + 32718.847 0.775000 5912401 4.44 + 33619.967 0.800000 6101402 5.00 + 34537.471 0.825000 6293476 5.71 + 35454.975 0.850000 6485743 6.67 + 36372.479 0.875000 6676924 8.00 + 36831.231 0.887500 6773229 8.89 + 37289.983 0.900000 6869643 10.00 + 37715.967 0.912500 6958744 11.43 + 38207.487 0.925000 7060591 13.33 + 38633.471 0.937500 7149440 16.00 + 38862.847 0.943750 7197050 17.78 + 39092.223 0.950000 7245340 20.00 + 39321.599 0.956250 7292810 22.86 + 39550.975 0.962500 7341190 26.67 + 39780.351 0.968750 7389385 32.00 + 39911.423 0.971875 7416552 35.56 + 40009.727 0.975000 7436997 40.00 + 40140.799 0.978125 7464199 45.71 + 40239.103 0.981250 7484941 53.33 + 40370.175 0.984375 7512225 64.00 + 40402.943 0.985938 7519061 71.11 + 40468.479 0.987500 7532972 80.00 + 40534.015 0.989062 7546571 91.43 + 40599.551 0.990625 7560200 106.67 + 40632.319 0.992188 7566965 128.00 + 40665.087 0.992969 7573789 142.22 + 40697.855 0.993750 7580656 160.00 + 40730.623 0.994531 7587408 182.86 + 40763.391 0.995313 7594186 213.33 + 40796.159 0.996094 7601022 256.00 + 40796.159 0.996484 7601022 284.44 + 40828.927 0.996875 7607868 320.00 + 40828.927 0.997266 7607868 365.71 + 40861.695 0.997656 7614021 426.67 + 40861.695 0.998047 7614021 512.00 + 40861.695 0.998242 7614021 568.89 + 40861.695 0.998437 7614021 640.00 + 40894.463 0.998633 7619391 731.43 + 40894.463 0.998828 7619391 853.33 + 40894.463 0.999023 7619391 1024.00 + 40894.463 0.999121 7619391 1137.78 + 40927.231 0.999219 7623027 1280.00 + 40927.231 0.999316 7623027 1462.86 + 40927.231 0.999414 7623027 1706.67 + 40927.231 0.999512 7623027 2048.00 + 40927.231 0.999561 7623027 2275.56 + 40927.231 0.999609 7623027 2560.00 + 40959.999 0.999658 7624917 2925.71 + 40959.999 0.999707 7624917 3413.33 + 40959.999 0.999756 7624917 4096.00 + 40959.999 0.999780 7624917 4551.11 + 40959.999 0.999805 7624917 5120.00 + 40959.999 0.999829 7624917 5851.43 + 40959.999 0.999854 7624917 6826.67 + 40959.999 0.999878 7624917 8192.00 + 40992.767 0.999890 7625644 9102.22 + 40992.767 0.999902 7625644 10240.00 + 40992.767 0.999915 7625644 11702.86 + 40992.767 0.999927 7625644 13653.33 + 40992.767 0.999939 7625644 16384.00 + 40992.767 0.999945 7625644 18204.44 + 40992.767 0.999951 7625644 20480.00 + 40992.767 0.999957 7625644 23405.71 + 40992.767 0.999963 7625644 27306.67 + 40992.767 0.999969 7625644 32768.00 + 40992.767 0.999973 7625644 36408.89 + 40992.767 0.999976 7625644 40960.00 + 41025.535 0.999979 7625808 46811.43 + 41025.535 0.999982 7625808 54613.33 + 41025.535 0.999985 7625808 65536.00 + 41025.535 0.999986 7625808 72817.78 + 41025.535 0.999988 7625808 81920.00 + 41025.535 0.999989 7625808 93622.86 + 41025.535 0.999991 7625808 109226.67 + 41025.535 0.999992 7625808 131072.00 + 41025.535 0.999993 7625808 145635.56 + 41025.535 0.999994 7625808 163840.00 + 41025.535 0.999995 7625808 187245.71 + 41025.535 0.999995 7625808 218453.33 + 41025.535 0.999996 7625808 262144.00 + 41025.535 0.999997 7625808 291271.11 + 41025.535 0.999997 7625808 327680.00 + 41025.535 0.999997 7625808 374491.43 + 41025.535 0.999998 7625808 436906.67 + 41025.535 0.999998 7625808 524288.00 + 41025.535 0.999998 7625808 582542.22 + 41025.535 0.999998 7625808 655360.00 + 41025.535 0.999999 7625808 748982.86 + 41058.303 0.999999 7625817 873813.33 + 41058.303 1.000000 7625817 inf +#[Mean = 22497.996, StdDeviation = 10744.185] +#[Max = 41025.536, Total count = 7625817] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 8302044 requests in 2.00m, 479.01MB read + Non-2xx or 3xx responses: 7857220 +Requests/sec: 69184.58 +Transfer/sec: 3.99MB diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/async_2 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/async_2 new file mode 100644 index 000000000..3fa99fb43 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/async_2 @@ -0,0 +1,136 @@ +./wrk -d 2m -t 8 -c 64 -R 65000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/get.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 364.252ms, rate sampling interval: 1172ms + Thread calibration: mean lat.: 353.693ms, rate sampling interval: 1156ms + Thread calibration: mean lat.: 359.500ms, rate sampling interval: 1187ms + Thread calibration: mean lat.: 339.364ms, rate sampling interval: 1130ms + Thread calibration: mean lat.: 354.538ms, rate sampling interval: 1170ms + Thread calibration: mean lat.: 372.100ms, rate sampling interval: 1221ms + Thread calibration: mean lat.: 377.467ms, rate sampling interval: 1223ms + Thread calibration: mean lat.: 359.050ms, rate sampling interval: 1170ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.89s 404.77ms 2.42s 77.74% + Req/Sec 8.04k 170.57 8.30k 64.56% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.03s + 75.000% 2.16s + 90.000% 2.26s + 99.000% 2.37s + 99.900% 2.41s + 99.990% 2.42s + 99.999% 2.42s +100.000% 2.42s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 566.783 0.000000 3 1.00 + 1208.319 0.100000 706689 1.11 + 1622.015 0.200000 1414060 1.25 + 1839.103 0.300000 2120389 1.43 + 1949.695 0.400000 2833770 1.67 + 2026.495 0.500000 3541882 2.00 + 2054.143 0.550000 3889538 2.22 + 2081.791 0.600000 4242012 2.50 + 2111.487 0.650000 4594140 2.86 + 2140.159 0.700000 4955762 3.33 + 2164.735 0.750000 5315917 4.00 + 2179.071 0.775000 5494396 4.44 + 2191.359 0.800000 5656375 5.00 + 2207.743 0.825000 5847742 5.71 + 2222.079 0.850000 6016028 6.67 + 2238.463 0.875000 6198286 8.00 + 2248.703 0.887500 6286901 8.89 + 2258.943 0.900000 6371747 10.00 + 2269.183 0.912500 6459438 11.43 + 2279.423 0.925000 6537296 13.33 + 2291.711 0.937500 6626360 16.00 + 2299.903 0.943750 6681859 17.78 + 2306.047 0.950000 6718541 20.00 + 2314.239 0.956250 6765674 22.86 + 2322.431 0.962500 6803511 26.67 + 2330.623 0.968750 6845602 32.00 + 2334.719 0.971875 6872406 35.56 + 2338.815 0.975000 6891841 40.00 + 2344.959 0.978125 6912878 45.71 + 2351.103 0.981250 6933768 53.33 + 2357.247 0.984375 6956995 64.00 + 2361.343 0.985938 6968908 71.11 + 2365.439 0.987500 6980880 80.00 + 2369.535 0.989062 6990397 91.43 + 2373.631 0.990625 7001021 106.67 + 2377.727 0.992188 7011336 128.00 + 2381.823 0.992969 7019694 142.22 + 2383.871 0.993750 7023146 160.00 + 2385.919 0.994531 7027766 182.86 + 2390.015 0.995313 7034291 213.33 + 2394.111 0.996094 7039916 256.00 + 2396.159 0.996484 7042065 284.44 + 2398.207 0.996875 7044209 320.00 + 2400.255 0.997266 7047138 365.71 + 2402.303 0.997656 7050549 426.67 + 2404.351 0.998047 7053683 512.00 + 2404.351 0.998242 7053683 568.89 + 2406.399 0.998437 7056847 640.00 + 2406.399 0.998633 7056847 731.43 + 2408.447 0.998828 7059738 853.33 + 2408.447 0.999023 7059738 1024.00 + 2408.447 0.999121 7059738 1137.78 + 2410.495 0.999219 7062058 1280.00 + 2410.495 0.999316 7062058 1462.86 + 2410.495 0.999414 7062058 1706.67 + 2412.543 0.999512 7063410 2048.00 + 2412.543 0.999561 7063410 2275.56 + 2412.543 0.999609 7063410 2560.00 + 2414.591 0.999658 7064234 2925.71 + 2414.591 0.999707 7064234 3413.33 + 2414.591 0.999756 7064234 4096.00 + 2416.639 0.999780 7065106 4551.11 + 2416.639 0.999805 7065106 5120.00 + 2416.639 0.999829 7065106 5851.43 + 2416.639 0.999854 7065106 6826.67 + 2416.639 0.999878 7065106 8192.00 + 2418.687 0.999890 7065781 9102.22 + 2418.687 0.999902 7065781 10240.00 + 2418.687 0.999915 7065781 11702.86 + 2418.687 0.999927 7065781 13653.33 + 2418.687 0.999939 7065781 16384.00 + 2418.687 0.999945 7065781 18204.44 + 2418.687 0.999951 7065781 20480.00 + 2418.687 0.999957 7065781 23405.71 + 2418.687 0.999963 7065781 27306.67 + 2418.687 0.999969 7065781 32768.00 + 2418.687 0.999973 7065781 36408.89 + 2418.687 0.999976 7065781 40960.00 + 2420.735 0.999979 7065939 46811.43 + 2420.735 0.999982 7065939 54613.33 + 2420.735 0.999985 7065939 65536.00 + 2420.735 0.999986 7065939 72817.78 + 2420.735 0.999988 7065939 81920.00 + 2420.735 0.999989 7065939 93622.86 + 2420.735 0.999991 7065939 109226.67 + 2420.735 0.999992 7065939 131072.00 + 2420.735 0.999993 7065939 145635.56 + 2420.735 0.999994 7065939 163840.00 + 2420.735 0.999995 7065939 187245.71 + 2420.735 0.999995 7065939 218453.33 + 2420.735 0.999996 7065939 262144.00 + 2420.735 0.999997 7065939 291271.11 + 2420.735 0.999997 7065939 327680.00 + 2420.735 0.999997 7065939 374491.43 + 2420.735 0.999998 7065939 436906.67 + 2420.735 0.999998 7065939 524288.00 + 2420.735 0.999998 7065939 582542.22 + 2420.735 0.999998 7065939 655360.00 + 2420.735 0.999999 7065939 748982.86 + 2422.783 0.999999 7065948 873813.33 + 2422.783 1.000000 7065948 inf +#[Mean = 1886.799, StdDeviation = 404.768] +#[Max = 2420.736, Total count = 7065948] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 7675348 requests in 2.00m, 471.87MB read + Non-2xx or 3xx responses: 6177421 +Requests/sec: 63961.87 +Transfer/sec: 3.93MB \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/async_3 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/async_3 new file mode 100644 index 000000000..f98b4945a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/async_3 @@ -0,0 +1,151 @@ +./wrk -d 2m -t 8 -c 64 -R 60000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/get.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 6.864ms, rate sampling interval: 54ms + Thread calibration: mean lat.: 8.602ms, rate sampling interval: 83ms + Thread calibration: mean lat.: 8.256ms, rate sampling interval: 73ms + Thread calibration: mean lat.: 6.716ms, rate sampling interval: 54ms + Thread calibration: mean lat.: 6.732ms, rate sampling interval: 57ms + Thread calibration: mean lat.: 8.179ms, rate sampling interval: 71ms + Thread calibration: mean lat.: 7.060ms, rate sampling interval: 60ms + Thread calibration: mean lat.: 7.436ms, rate sampling interval: 62ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.48ms 669.27us 15.01ms 74.94% + Req/Sec 7.56k 99.36 8.46k 72.54% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.39ms + 75.000% 1.82ms + 90.000% 2.29ms + 99.000% 3.38ms + 99.900% 5.80ms + 99.990% 12.28ms + 99.999% 13.49ms +100.000% 15.02ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.073 0.000000 1 1.00 + 0.775 0.100000 661205 1.11 + 0.954 0.200000 1321640 1.25 + 1.101 0.300000 1980055 1.43 + 1.242 0.400000 2643206 1.67 + 1.386 0.500000 3301801 2.00 + 1.461 0.550000 3628921 2.22 + 1.541 0.600000 3959450 2.50 + 1.626 0.650000 4291207 2.86 + 1.718 0.700000 4620899 3.33 + 1.820 0.750000 4948303 4.00 + 1.878 0.775000 5114675 4.44 + 1.941 0.800000 5279427 5.00 + 2.011 0.825000 5443954 5.71 + 2.091 0.850000 5609586 6.67 + 2.183 0.875000 5775117 8.00 + 2.235 0.887500 5857059 8.89 + 2.293 0.900000 5940057 10.00 + 2.357 0.912500 6021612 11.43 + 2.429 0.925000 6102988 13.33 + 2.515 0.937500 6186248 16.00 + 2.563 0.943750 6227290 17.78 + 2.617 0.950000 6268613 20.00 + 2.677 0.956250 6309808 22.86 + 2.745 0.962500 6350188 26.67 + 2.827 0.968750 6391747 32.00 + 2.875 0.971875 6412809 35.56 + 2.927 0.975000 6433082 40.00 + 2.987 0.978125 6453401 45.71 + 3.059 0.981250 6474331 53.33 + 3.145 0.984375 6494541 64.00 + 3.197 0.985938 6504932 71.11 + 3.257 0.987500 6515166 80.00 + 3.327 0.989062 6525465 91.43 + 3.409 0.990625 6535741 106.67 + 3.515 0.992188 6546156 128.00 + 3.577 0.992969 6551214 142.22 + 3.653 0.993750 6556408 160.00 + 3.741 0.994531 6561514 182.86 + 3.853 0.995313 6566718 213.33 + 3.995 0.996094 6571793 256.00 + 4.083 0.996484 6574387 284.44 + 4.191 0.996875 6576991 320.00 + 4.315 0.997266 6579569 365.71 + 4.467 0.997656 6582120 426.67 + 4.675 0.998047 6584683 512.00 + 4.807 0.998242 6585990 568.89 + 4.971 0.998437 6587266 640.00 + 5.179 0.998633 6588560 731.43 + 5.447 0.998828 6589838 853.33 + 5.855 0.999023 6591129 1024.00 + 6.155 0.999121 6591763 1137.78 + 6.567 0.999219 6592407 1280.00 + 7.203 0.999316 6593054 1462.86 + 8.083 0.999414 6593695 1706.67 + 9.023 0.999512 6594341 2048.00 + 9.503 0.999561 6594661 2275.56 + 9.983 0.999609 6594991 2560.00 + 10.399 0.999658 6595305 2925.71 + 10.807 0.999707 6595630 3413.33 + 11.143 0.999756 6595955 4096.00 + 11.303 0.999780 6596113 4551.11 + 11.511 0.999805 6596273 5120.00 + 11.679 0.999829 6596436 5851.43 + 11.863 0.999854 6596596 6826.67 + 12.087 0.999878 6596756 8192.00 + 12.183 0.999890 6596838 9102.22 + 12.295 0.999902 6596916 10240.00 + 12.391 0.999915 6596997 11702.86 + 12.511 0.999927 6597078 13653.33 + 12.639 0.999939 6597161 16384.00 + 12.703 0.999945 6597205 18204.44 + 12.775 0.999951 6597238 20480.00 + 12.847 0.999957 6597279 23405.71 + 12.911 0.999963 6597320 27306.67 + 12.991 0.999969 6597360 32768.00 + 13.047 0.999973 6597380 36408.89 + 13.103 0.999976 6597399 40960.00 + 13.143 0.999979 6597420 46811.43 + 13.239 0.999982 6597443 54613.33 + 13.303 0.999985 6597460 65536.00 + 13.375 0.999986 6597472 72817.78 + 13.423 0.999988 6597480 81920.00 + 13.471 0.999989 6597492 93622.86 + 13.511 0.999991 6597500 109226.67 + 13.631 0.999992 6597510 131072.00 + 13.679 0.999993 6597515 145635.56 + 13.735 0.999994 6597520 163840.00 + 13.759 0.999995 6597525 187245.71 + 13.847 0.999995 6597530 218453.33 + 13.943 0.999996 6597535 262144.00 + 14.055 0.999997 6597538 291271.11 + 14.119 0.999997 6597540 327680.00 + 14.175 0.999997 6597543 374491.43 + 14.223 0.999998 6597545 436906.67 + 14.255 0.999998 6597549 524288.00 + 14.255 0.999998 6597549 582542.22 + 14.335 0.999998 6597550 655360.00 + 14.423 0.999999 6597552 748982.86 + 14.431 0.999999 6597553 873813.33 + 14.487 0.999999 6597554 1048576.00 + 14.519 0.999999 6597556 1165084.44 + 14.519 0.999999 6597556 1310720.00 + 14.519 0.999999 6597556 1497965.71 + 14.631 0.999999 6597557 1747626.67 + 14.631 1.000000 6597557 2097152.00 + 14.687 1.000000 6597558 2330168.89 + 14.687 1.000000 6597558 2621440.00 + 14.687 1.000000 6597558 2995931.43 + 14.831 1.000000 6597559 3495253.33 + 14.831 1.000000 6597559 4194304.00 + 14.831 1.000000 6597559 4660337.78 + 14.831 1.000000 6597559 5242880.00 + 14.831 1.000000 6597559 5991862.86 + 15.015 1.000000 6597560 6990506.67 + 15.015 1.000000 6597560 inf +#[Mean = 1.482, StdDeviation = 0.669] +#[Max = 15.008, Total count = 6597560] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 7198871 requests in 2.00m, 442.40MB read + Non-2xx or 3xx responses: 5800267 +Requests/sec: 59990.63 +Transfer/sec: 3.69MB \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/sync_1 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/sync_1 new file mode 100644 index 000000000..62e505cba --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/sync_1 @@ -0,0 +1,121 @@ +./wrk -d 2m -t 8 -c 64 -R 15000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/get.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 314.773ms, rate sampling interval: 1420ms + Thread calibration: mean lat.: 315.244ms, rate sampling interval: 1422ms + Thread calibration: mean lat.: 315.453ms, rate sampling interval: 1424ms + Thread calibration: mean lat.: 315.126ms, rate sampling interval: 1425ms + Thread calibration: mean lat.: 315.173ms, rate sampling interval: 1423ms + Thread calibration: mean lat.: 315.717ms, rate sampling interval: 1426ms + Thread calibration: mean lat.: 315.025ms, rate sampling interval: 1421ms + Thread calibration: mean lat.: 315.565ms, rate sampling interval: 1426ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 6.25s 2.79s 11.23s 60.46% + Req/Sec 1.76k 650.70 7.36k 97.40% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 6.50s + 75.000% 8.35s + 90.000% 10.05s + 99.000% 11.10s + 99.900% 11.21s + 99.990% 11.22s + 99.999% 11.23s +100.000% 11.24s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 814.591 0.000000 2 1.00 + 2036.735 0.100000 154629 1.11 + 3356.671 0.200000 309142 1.25 + 4800.511 0.300000 463705 1.43 + 5857.279 0.400000 618311 1.67 + 6504.447 0.500000 772749 2.00 + 6836.223 0.550000 850416 2.22 + 7143.423 0.600000 927304 2.50 + 7446.527 0.650000 1004788 2.86 + 7761.919 0.700000 1082068 3.33 + 8351.743 0.750000 1159153 4.00 + 8626.175 0.775000 1198218 4.44 + 8912.895 0.800000 1236649 5.00 + 9191.423 0.825000 1275288 5.71 + 9469.951 0.850000 1313976 6.67 + 9756.671 0.875000 1353109 8.00 + 9895.935 0.887500 1371884 8.89 + 10051.583 0.900000 1391251 10.00 + 10215.423 0.912500 1410868 11.43 + 10354.687 0.925000 1429562 13.33 + 10502.143 0.937500 1449284 16.00 + 10575.871 0.943750 1459233 17.78 + 10633.215 0.950000 1468994 20.00 + 10706.943 0.956250 1477966 22.86 + 10788.863 0.962500 1487652 26.67 + 10854.399 0.968750 1497923 32.00 + 10887.167 0.971875 1502888 35.56 + 10911.743 0.975000 1507072 40.00 + 10952.703 0.978125 1512546 45.71 + 10993.663 0.981250 1517058 53.33 + 11034.623 0.984375 1522072 64.00 + 11051.007 0.985938 1523860 71.11 + 11075.583 0.987500 1526623 80.00 + 11091.967 0.989062 1528878 91.43 + 11108.351 0.990625 1531089 106.67 + 11124.735 0.992188 1533486 128.00 + 11132.927 0.992969 1534720 142.22 + 11141.119 0.993750 1535817 160.00 + 11157.503 0.994531 1537837 182.86 + 11165.695 0.995313 1538966 213.33 + 11173.887 0.996094 1540220 256.00 + 11173.887 0.996484 1540220 284.44 + 11182.079 0.996875 1541383 320.00 + 11182.079 0.997266 1541383 365.71 + 11190.271 0.997656 1542459 426.67 + 11190.271 0.998047 1542459 512.00 + 11198.463 0.998242 1543481 568.89 + 11198.463 0.998437 1543481 640.00 + 11198.463 0.998633 1543481 731.43 + 11206.655 0.998828 1544387 853.33 + 11206.655 0.999023 1544387 1024.00 + 11206.655 0.999121 1544387 1137.78 + 11206.655 0.999219 1544387 1280.00 + 11206.655 0.999316 1544387 1462.86 + 11214.847 0.999414 1544995 1706.67 + 11214.847 0.999512 1544995 2048.00 + 11214.847 0.999561 1544995 2275.56 + 11214.847 0.999609 1544995 2560.00 + 11214.847 0.999658 1544995 2925.71 + 11214.847 0.999707 1544995 3413.33 + 11223.039 0.999756 1545303 4096.00 + 11223.039 0.999780 1545303 4551.11 + 11223.039 0.999805 1545303 5120.00 + 11223.039 0.999829 1545303 5851.43 + 11223.039 0.999854 1545303 6826.67 + 11223.039 0.999878 1545303 8192.00 + 11223.039 0.999890 1545303 9102.22 + 11223.039 0.999902 1545303 10240.00 + 11223.039 0.999915 1545303 11702.86 + 11223.039 0.999927 1545303 13653.33 + 11231.231 0.999939 1545385 16384.00 + 11231.231 0.999945 1545385 18204.44 + 11231.231 0.999951 1545385 20480.00 + 11231.231 0.999957 1545385 23405.71 + 11231.231 0.999963 1545385 27306.67 + 11231.231 0.999969 1545385 32768.00 + 11231.231 0.999973 1545385 36408.89 + 11231.231 0.999976 1545385 40960.00 + 11231.231 0.999979 1545385 46811.43 + 11231.231 0.999982 1545385 54613.33 + 11231.231 0.999985 1545385 65536.00 + 11231.231 0.999986 1545385 72817.78 + 11231.231 0.999988 1545385 81920.00 + 11231.231 0.999989 1545385 93622.86 + 11239.423 0.999991 1545400 109226.67 + 11239.423 1.000000 1545400 inf +#[Mean = 6253.902, StdDeviation = 2790.826] +#[Max = 11231.232, Total count = 1545400] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 1683433 requests in 2.00m, 99.36MB read + Non-2xx or 3xx responses: 1509884 +Requests/sec: 14028.70 +Transfer/sec: 847.84KB \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/sync_2 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/sync_2 new file mode 100644 index 000000000..989bc8ad9 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/get/sync_2 @@ -0,0 +1,140 @@ +./wrk -d 2m -t 8 -c 64 -R 13000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/get.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 3.707ms, rate sampling interval: 15ms + Thread calibration: mean lat.: 3.695ms, rate sampling interval: 15ms + Thread calibration: mean lat.: 3.841ms, rate sampling interval: 16ms + Thread calibration: mean lat.: 3.733ms, rate sampling interval: 15ms + Thread calibration: mean lat.: 4.597ms, rate sampling interval: 16ms + Thread calibration: mean lat.: 4.755ms, rate sampling interval: 18ms + Thread calibration: mean lat.: 4.741ms, rate sampling interval: 17ms + Thread calibration: mean lat.: 4.646ms, rate sampling interval: 17ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 4.23ms 2.77ms 19.58ms 71.85% + Req/Sec 1.68k 183.24 2.86k 71.80% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 3.59ms + 75.000% 5.56ms + 90.000% 8.17ms + 99.000% 12.96ms + 99.900% 15.90ms + 99.990% 17.58ms + 99.999% 18.74ms +100.000% 19.60ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.064 0.000000 1 1.00 + 1.292 0.100000 143026 1.11 + 1.918 0.200000 285927 1.25 + 2.507 0.300000 429064 1.43 + 3.051 0.400000 571955 1.67 + 3.585 0.500000 715002 2.00 + 3.871 0.550000 786347 2.22 + 4.191 0.600000 858264 2.50 + 4.555 0.650000 929591 2.86 + 5.003 0.700000 1000985 3.33 + 5.559 0.750000 1072363 4.00 + 5.887 0.775000 1108255 4.44 + 6.251 0.800000 1143968 5.00 + 6.651 0.825000 1179503 5.71 + 7.095 0.850000 1215223 6.67 + 7.591 0.875000 1251036 8.00 + 7.867 0.887500 1268847 8.89 + 8.167 0.900000 1286573 10.00 + 8.503 0.912500 1304505 11.43 + 8.879 0.925000 1322470 13.33 + 9.311 0.937500 1340353 16.00 + 9.551 0.943750 1349232 17.78 + 9.823 0.950000 1358220 20.00 + 10.111 0.956250 1366979 22.86 + 10.455 0.962500 1376074 26.67 + 10.839 0.968750 1384824 32.00 + 11.055 0.971875 1389327 35.56 + 11.303 0.975000 1393874 40.00 + 11.559 0.978125 1398263 45.71 + 11.863 0.981250 1402778 53.33 + 12.199 0.984375 1407218 64.00 + 12.383 0.985938 1409404 71.11 + 12.591 0.987500 1411676 80.00 + 12.807 0.989062 1413877 91.43 + 13.063 0.990625 1416122 106.67 + 13.351 0.992188 1418329 128.00 + 13.527 0.992969 1419488 142.22 + 13.711 0.993750 1420583 160.00 + 13.919 0.994531 1421684 182.86 + 14.151 0.995313 1422815 213.33 + 14.407 0.996094 1423931 256.00 + 14.527 0.996484 1424484 284.44 + 14.671 0.996875 1425046 320.00 + 14.815 0.997266 1425603 365.71 + 14.999 0.997656 1426172 426.67 + 15.191 0.998047 1426717 512.00 + 15.303 0.998242 1426985 568.89 + 15.431 0.998437 1427275 640.00 + 15.575 0.998633 1427541 731.43 + 15.743 0.998828 1427824 853.33 + 15.919 0.999023 1428095 1024.00 + 16.023 0.999121 1428241 1137.78 + 16.127 0.999219 1428374 1280.00 + 16.239 0.999316 1428524 1462.86 + 16.359 0.999414 1428653 1706.67 + 16.527 0.999512 1428803 2048.00 + 16.591 0.999561 1428867 2275.56 + 16.687 0.999609 1428936 2560.00 + 16.799 0.999658 1429009 2925.71 + 16.895 0.999707 1429077 3413.33 + 17.039 0.999756 1429145 4096.00 + 17.103 0.999780 1429178 4551.11 + 17.183 0.999805 1429210 5120.00 + 17.295 0.999829 1429248 5851.43 + 17.391 0.999854 1429283 6826.67 + 17.487 0.999878 1429318 8192.00 + 17.535 0.999890 1429333 9102.22 + 17.615 0.999902 1429353 10240.00 + 17.743 0.999915 1429374 11702.86 + 17.823 0.999927 1429385 13653.33 + 17.935 0.999939 1429405 16384.00 + 17.967 0.999945 1429414 18204.44 + 18.047 0.999951 1429420 20480.00 + 18.143 0.999957 1429428 23405.71 + 18.191 0.999963 1429437 27306.67 + 18.287 0.999969 1429446 32768.00 + 18.335 0.999973 1429453 36408.89 + 18.383 0.999976 1429455 40960.00 + 18.447 0.999979 1429460 46811.43 + 18.479 0.999982 1429464 54613.33 + 18.527 0.999985 1429469 65536.00 + 18.559 0.999986 1429470 72817.78 + 18.639 0.999988 1429472 81920.00 + 18.719 0.999989 1429474 93622.86 + 18.735 0.999991 1429476 109226.67 + 18.863 0.999992 1429479 131072.00 + 18.943 0.999993 1429480 145635.56 + 18.975 0.999994 1429481 163840.00 + 19.151 0.999995 1429482 187245.71 + 19.231 0.999995 1429483 218453.33 + 19.279 0.999996 1429485 262144.00 + 19.279 0.999997 1429485 291271.11 + 19.279 0.999997 1429485 327680.00 + 19.423 0.999997 1429486 374491.43 + 19.423 0.999998 1429486 436906.67 + 19.455 0.999998 1429487 524288.00 + 19.455 0.999998 1429487 582542.22 + 19.455 0.999998 1429487 655360.00 + 19.471 0.999999 1429488 748982.86 + 19.471 0.999999 1429488 873813.33 + 19.471 0.999999 1429488 1048576.00 + 19.471 0.999999 1429488 1165084.44 + 19.471 0.999999 1429488 1310720.00 + 19.599 0.999999 1429489 1497965.71 + 19.599 1.000000 1429489 inf +#[Mean = 4.229, StdDeviation = 2.765] +#[Max = 19.584, Total count = 1429489] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 1559785 requests in 2.00m, 92.05MB read + Non-2xx or 3xx responses: 1399393 +Requests/sec: 12998.07 +Transfer/sec: 785.46KB \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/async_1 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/async_1 new file mode 100644 index 000000000..fa1515b9d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/async_1 @@ -0,0 +1,126 @@ +./wrk -d 2m -t 8 -c 64 -R 300000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/put.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 3638.503ms, rate sampling interval: 11984ms + Thread calibration: mean lat.: 3637.965ms, rate sampling interval: 11984ms + Thread calibration: mean lat.: 3637.700ms, rate sampling interval: 11993ms + Thread calibration: mean lat.: 3634.505ms, rate sampling interval: 11984ms + Thread calibration: mean lat.: 3635.485ms, rate sampling interval: 11984ms + Thread calibration: mean lat.: 3636.140ms, rate sampling interval: 11984ms + Thread calibration: mean lat.: 3637.006ms, rate sampling interval: 11984ms + Thread calibration: mean lat.: 3634.396ms, rate sampling interval: 11984ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 40.97s 19.87s 1.26m 57.74% + Req/Sec 14.01k 25.90 14.05k 70.83% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 40.96s + 75.000% 0.97m + 90.000% 1.14m + 99.000% 1.25m + 99.900% 1.26m + 99.990% 1.26m + 99.999% 1.26m +100.000% 1.26m + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 6545.407 0.000000 13 1.00 + 13459.455 0.100000 1233066 1.11 + 20348.927 0.200000 2466746 1.25 + 27230.207 0.300000 3698694 1.43 + 34078.719 0.400000 4928443 1.67 + 40959.999 0.500000 6163389 2.00 + 44400.639 0.550000 6779768 2.22 + 47841.279 0.600000 7396180 2.50 + 51281.919 0.650000 8011817 2.86 + 54722.559 0.700000 8627831 3.33 + 58195.967 0.750000 9241149 4.00 + 59932.671 0.775000 9551712 4.44 + 61669.375 0.800000 9861665 5.00 + 63373.311 0.825000 10165048 5.71 + 65110.015 0.850000 10476703 6.67 + 66813.951 0.875000 10781318 8.00 + 67698.687 0.887500 10939413 8.89 + 68550.655 0.900000 11091151 10.00 + 69402.623 0.912500 11242996 11.43 + 70320.127 0.925000 11406901 13.33 + 71172.095 0.937500 11558253 16.00 + 71565.311 0.943750 11628687 17.78 + 72024.063 0.950000 11709814 20.00 + 72482.815 0.956250 11791851 22.86 + 72876.031 0.962500 11861951 26.67 + 73334.783 0.968750 11942929 32.00 + 73531.391 0.971875 11977748 35.56 + 73727.999 0.975000 12012786 40.00 + 73990.143 0.978125 12059617 45.71 + 74186.751 0.981250 12094441 53.33 + 74383.359 0.984375 12129341 64.00 + 74514.431 0.985938 12152498 71.11 + 74645.503 0.987500 12175431 80.00 + 74711.039 0.989062 12187115 91.43 + 74842.111 0.990625 12210601 106.67 + 74973.183 0.992188 12234050 128.00 + 74973.183 0.992969 12234050 142.22 + 75038.719 0.993750 12245663 160.00 + 75104.255 0.994531 12257328 182.86 + 75169.791 0.995313 12269076 213.33 + 75235.327 0.996094 12280638 256.00 + 75235.327 0.996484 12280638 284.44 + 75300.863 0.996875 12292335 320.00 + 75300.863 0.997266 12292335 365.71 + 75300.863 0.997656 12292335 426.67 + 75366.399 0.998047 12303983 512.00 + 75366.399 0.998242 12303983 568.89 + 75366.399 0.998437 12303983 640.00 + 75366.399 0.998633 12303983 731.43 + 75431.935 0.998828 12315170 853.33 + 75431.935 0.999023 12315170 1024.00 + 75431.935 0.999121 12315170 1137.78 + 75431.935 0.999219 12315170 1280.00 + 75431.935 0.999316 12315170 1462.86 + 75431.935 0.999414 12315170 1706.67 + 75431.935 0.999512 12315170 2048.00 + 75431.935 0.999561 12315170 2275.56 + 75497.471 0.999609 12319946 2560.00 + 75497.471 0.999658 12319946 2925.71 + 75497.471 0.999707 12319946 3413.33 + 75497.471 0.999756 12319946 4096.00 + 75497.471 0.999780 12319946 4551.11 + 75497.471 0.999805 12319946 5120.00 + 75497.471 0.999829 12319946 5851.43 + 75497.471 0.999854 12319946 6826.67 + 75497.471 0.999878 12319946 8192.00 + 75497.471 0.999890 12319946 9102.22 + 75497.471 0.999902 12319946 10240.00 + 75497.471 0.999915 12319946 11702.86 + 75497.471 0.999927 12319946 13653.33 + 75497.471 0.999939 12319946 16384.00 + 75497.471 0.999945 12319946 18204.44 + 75497.471 0.999951 12319946 20480.00 + 75497.471 0.999957 12319946 23405.71 + 75497.471 0.999963 12319946 27306.67 + 75497.471 0.999969 12319946 32768.00 + 75497.471 0.999973 12319946 36408.89 + 75497.471 0.999976 12319946 40960.00 + 75497.471 0.999979 12319946 46811.43 + 75497.471 0.999982 12319946 54613.33 + 75497.471 0.999985 12319946 65536.00 + 75497.471 0.999986 12319946 72817.78 + 75497.471 0.999988 12319946 81920.00 + 75497.471 0.999989 12319946 93622.86 + 75497.471 0.999991 12319946 109226.67 + 75497.471 0.999992 12319946 131072.00 + 75497.471 0.999993 12319946 145635.56 + 75497.471 0.999994 12319946 163840.00 + 75497.471 0.999995 12319946 187245.71 + 75497.471 0.999995 12319946 218453.33 + 75563.007 0.999996 12319997 262144.00 + 75563.007 1.000000 12319997 inf +#[Mean = 40972.965, StdDeviation = 19874.201] +#[Max = 75497.472, Total count = 12319997] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 13357070 requests in 2.00m, 751.56MB read +Requests/sec: 111312.03 +Transfer/sec: 6.26MB diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/async_2 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/async_2 new file mode 100644 index 000000000..848ac34a4 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/async_2 @@ -0,0 +1,155 @@ +./wrk -d 2m -t 8 -c 64 -R 110000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/put.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 27.350ms, rate sampling interval: 167ms + Thread calibration: mean lat.: 28.381ms, rate sampling interval: 167ms + Thread calibration: mean lat.: 28.647ms, rate sampling interval: 168ms + Thread calibration: mean lat.: 27.034ms, rate sampling interval: 157ms + Thread calibration: mean lat.: 25.964ms, rate sampling interval: 156ms + Thread calibration: mean lat.: 27.762ms, rate sampling interval: 165ms + Thread calibration: mean lat.: 25.990ms, rate sampling interval: 156ms + Thread calibration: mean lat.: 27.500ms, rate sampling interval: 162ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.97ms 3.61ms 49.34ms 98.29% + Req/Sec 13.79k 165.31 14.38k 93.08% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.32ms + 75.000% 2.12ms + 90.000% 3.02ms + 99.000% 21.57ms + 99.900% 44.61ms + 99.990% 47.55ms + 99.999% 48.51ms +100.000% 49.38ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.054 0.000000 1 1.00 + 0.634 0.100000 1210042 1.11 + 0.798 0.200000 2424463 1.25 + 0.957 0.300000 3630253 1.43 + 1.127 0.400000 4842838 1.67 + 1.318 0.500000 6048783 2.00 + 1.430 0.550000 6653791 2.22 + 1.563 0.600000 7260715 2.50 + 1.724 0.650000 7863542 2.86 + 1.911 0.700000 8467261 3.33 + 2.123 0.750000 9073849 4.00 + 2.239 0.775000 9374699 4.44 + 2.365 0.800000 9678350 5.00 + 2.503 0.825000 9982385 5.71 + 2.653 0.850000 10282918 6.67 + 2.823 0.875000 10585738 8.00 + 2.917 0.887500 10736324 8.89 + 3.021 0.900000 10888054 10.00 + 3.135 0.912500 11037776 11.43 + 3.267 0.925000 11189154 13.33 + 3.425 0.937500 11340860 16.00 + 3.517 0.943750 11415992 17.78 + 3.623 0.950000 11491471 20.00 + 3.745 0.956250 11566560 22.86 + 3.895 0.962500 11642707 26.67 + 4.085 0.968750 11717532 32.00 + 4.211 0.971875 11755927 35.56 + 4.371 0.975000 11793666 40.00 + 4.599 0.978125 11831404 45.71 + 5.003 0.981250 11868785 53.33 + 8.623 0.984375 11906461 64.00 + 12.303 0.985938 11925365 71.11 + 15.671 0.987500 11944257 80.00 + 19.151 0.989062 11963162 91.43 + 23.055 0.990625 11982054 106.67 + 26.927 0.992188 12000961 128.00 + 28.687 0.992969 12010431 142.22 + 30.735 0.993750 12019851 160.00 + 32.927 0.994531 12029431 182.86 + 35.263 0.995313 12038852 213.33 + 37.279 0.996094 12048227 256.00 + 38.431 0.996484 12052926 284.44 + 39.551 0.996875 12057714 320.00 + 40.351 0.997266 12062479 365.71 + 41.247 0.997656 12067198 426.67 + 42.111 0.998047 12071929 512.00 + 42.687 0.998242 12074269 568.89 + 43.199 0.998437 12076653 640.00 + 43.679 0.998633 12078989 731.43 + 44.159 0.998828 12081328 853.33 + 44.671 0.999023 12083672 1024.00 + 44.927 0.999121 12084827 1137.78 + 45.215 0.999219 12086083 1280.00 + 45.439 0.999316 12087258 1462.86 + 45.663 0.999414 12088435 1706.67 + 45.919 0.999512 12089582 2048.00 + 46.047 0.999561 12090139 2275.56 + 46.207 0.999609 12090793 2560.00 + 46.335 0.999658 12091312 2925.71 + 46.527 0.999707 12091965 3413.33 + 46.719 0.999756 12092511 4096.00 + 46.815 0.999780 12092825 4551.11 + 46.911 0.999805 12093083 5120.00 + 47.071 0.999829 12093420 5851.43 + 47.199 0.999854 12093693 6826.67 + 47.391 0.999878 12093984 8192.00 + 47.487 0.999890 12094146 9102.22 + 47.551 0.999902 12094264 10240.00 + 47.647 0.999915 12094433 11702.86 + 47.743 0.999927 12094586 13653.33 + 47.839 0.999939 12094734 16384.00 + 47.871 0.999945 12094784 18204.44 + 47.935 0.999951 12094880 20480.00 + 47.999 0.999957 12094951 23405.71 + 48.063 0.999963 12095018 27306.67 + 48.127 0.999969 12095088 32768.00 + 48.159 0.999973 12095118 36408.89 + 48.223 0.999976 12095166 40960.00 + 48.255 0.999979 12095187 46811.43 + 48.319 0.999982 12095225 54613.33 + 48.383 0.999985 12095269 65536.00 + 48.415 0.999986 12095287 72817.78 + 48.447 0.999988 12095301 81920.00 + 48.511 0.999989 12095332 93622.86 + 48.543 0.999991 12095342 109226.67 + 48.575 0.999992 12095354 131072.00 + 48.607 0.999993 12095364 145635.56 + 48.639 0.999994 12095371 163840.00 + 48.703 0.999995 12095389 187245.71 + 48.703 0.999995 12095389 218453.33 + 48.767 0.999996 12095401 262144.00 + 48.799 0.999997 12095407 291271.11 + 48.831 0.999997 12095414 327680.00 + 48.831 0.999997 12095414 374491.43 + 48.927 0.999998 12095419 436906.67 + 48.959 0.999998 12095422 524288.00 + 48.991 0.999998 12095424 582542.22 + 49.023 0.999998 12095427 655360.00 + 49.087 0.999999 12095428 748982.86 + 49.151 0.999999 12095434 873813.33 + 49.151 0.999999 12095434 1048576.00 + 49.151 0.999999 12095434 1165084.44 + 49.183 0.999999 12095436 1310720.00 + 49.183 0.999999 12095436 1497965.71 + 49.215 0.999999 12095438 1747626.67 + 49.247 1.000000 12095439 2097152.00 + 49.247 1.000000 12095439 2330168.89 + 49.279 1.000000 12095441 2621440.00 + 49.279 1.000000 12095441 2995931.43 + 49.279 1.000000 12095441 3495253.33 + 49.311 1.000000 12095443 4194304.00 + 49.311 1.000000 12095443 4660337.78 + 49.311 1.000000 12095443 5242880.00 + 49.311 1.000000 12095443 5991862.86 + 49.311 1.000000 12095443 6990506.67 + 49.311 1.000000 12095443 8388608.00 + 49.311 1.000000 12095443 9320675.55 + 49.311 1.000000 12095443 10485760.00 + 49.311 1.000000 12095443 11983725.71 + 49.375 1.000000 12095444 13981013.34 + 49.375 1.000000 12095444 inf +#[Mean = 1.970, StdDeviation = 3.614] +#[Max = 49.344, Total count = 12095444] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 13197723 requests in 2.00m, 742.59MB read +Requests/sec: 109982.55 +Transfer/sec: 6.19MB diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/async_3 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/async_3 new file mode 100644 index 000000000..d042ddc7f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/async_3 @@ -0,0 +1,149 @@ +./wrk -d 2m -t 8 -c 64 -R 105000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/put.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 5.502ms, rate sampling interval: 20ms + Thread calibration: mean lat.: 5.431ms, rate sampling interval: 17ms + Thread calibration: mean lat.: 5.434ms, rate sampling interval: 19ms + Thread calibration: mean lat.: 5.457ms, rate sampling interval: 20ms + Thread calibration: mean lat.: 5.368ms, rate sampling interval: 14ms + Thread calibration: mean lat.: 5.285ms, rate sampling interval: 15ms + Thread calibration: mean lat.: 5.325ms, rate sampling interval: 12ms + Thread calibration: mean lat.: 5.385ms, rate sampling interval: 16ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 11.37ms 47.93ms 366.08ms 95.33% + Req/Sec 13.57k 1.00k 16.73k 79.13% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.27ms + 75.000% 1.90ms + 90.000% 2.86ms + 99.000% 296.70ms + 99.900% 354.30ms + 99.990% 361.21ms + 99.999% 364.54ms +100.000% 366.33ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.059 0.000000 1 1.00 + 0.613 0.100000 1160320 1.11 + 0.777 0.200000 2314121 1.25 + 0.935 0.300000 3467404 1.43 + 1.097 0.400000 4623265 1.67 + 1.271 0.500000 5775790 2.00 + 1.367 0.550000 6355930 2.22 + 1.471 0.600000 6927878 2.50 + 1.592 0.650000 7507455 2.86 + 1.733 0.700000 8083267 3.33 + 1.899 0.750000 8660046 4.00 + 1.995 0.775000 8950820 4.44 + 2.103 0.800000 9240598 5.00 + 2.227 0.825000 9525769 5.71 + 2.381 0.850000 9814938 6.67 + 2.581 0.875000 10103682 8.00 + 2.707 0.887500 10248316 8.89 + 2.861 0.900000 10392912 10.00 + 3.059 0.912500 10536225 11.43 + 3.353 0.925000 10680721 13.33 + 3.897 0.937500 10824470 16.00 + 4.715 0.943750 10896494 17.78 + 37.215 0.950000 10968615 20.00 + 78.143 0.956250 11040796 22.86 + 119.295 0.962500 11113051 26.67 + 157.823 0.968750 11185204 32.00 + 177.535 0.971875 11221210 35.56 + 197.887 0.975000 11257305 40.00 + 219.135 0.978125 11293393 45.71 + 240.639 0.981250 11329607 53.33 + 262.399 0.984375 11365892 64.00 + 272.127 0.985938 11383855 71.11 + 281.343 0.987500 11401844 80.00 + 290.815 0.989062 11419982 91.43 + 300.543 0.990625 11437925 106.67 + 310.527 0.992188 11455723 128.00 + 315.391 0.992969 11464764 142.22 + 320.511 0.993750 11474180 160.00 + 325.375 0.994531 11483104 182.86 + 330.239 0.995313 11492108 213.33 + 334.847 0.996094 11500908 256.00 + 337.407 0.996484 11505457 284.44 + 340.223 0.996875 11510160 320.00 + 342.783 0.997266 11514788 365.71 + 345.343 0.997656 11519252 426.67 + 347.903 0.998047 11523602 512.00 + 348.927 0.998242 11525646 568.89 + 350.463 0.998437 11528234 640.00 + 351.743 0.998633 11530453 731.43 + 353.023 0.998828 11532661 853.33 + 354.303 0.999023 11534643 1024.00 + 355.071 0.999121 11536084 1137.78 + 355.583 0.999219 11537043 1280.00 + 356.095 0.999316 11538067 1462.86 + 356.863 0.999414 11539353 1706.67 + 357.631 0.999512 11540726 2048.00 + 357.887 0.999561 11541134 2275.56 + 358.143 0.999609 11541474 2560.00 + 358.655 0.999658 11542162 2925.71 + 359.167 0.999707 11542724 3413.33 + 359.679 0.999756 11543358 4096.00 + 359.935 0.999780 11543702 4551.11 + 359.935 0.999805 11543702 5120.00 + 360.191 0.999829 11544023 5851.43 + 360.447 0.999854 11544290 6826.67 + 360.703 0.999878 11544507 8192.00 + 360.959 0.999890 11544700 9102.22 + 361.215 0.999902 11544884 10240.00 + 361.471 0.999915 11545048 11702.86 + 361.727 0.999927 11545237 13653.33 + 361.727 0.999939 11545237 16384.00 + 361.983 0.999945 11545372 18204.44 + 361.983 0.999951 11545372 20480.00 + 362.239 0.999957 11545461 23405.71 + 362.495 0.999963 11545531 27306.67 + 362.751 0.999969 11545575 32768.00 + 363.007 0.999973 11545614 36408.89 + 363.263 0.999976 11545658 40960.00 + 363.519 0.999979 11545695 46811.43 + 363.775 0.999982 11545719 54613.33 + 364.031 0.999985 11545751 65536.00 + 364.287 0.999986 11545782 72817.78 + 364.287 0.999988 11545782 81920.00 + 364.543 0.999989 11545797 93622.86 + 364.799 0.999991 11545822 109226.67 + 364.799 0.999992 11545822 131072.00 + 365.055 0.999993 11545838 145635.56 + 365.311 0.999994 11545846 163840.00 + 365.567 0.999995 11545860 187245.71 + 365.567 0.999995 11545860 218453.33 + 365.823 0.999996 11545887 262144.00 + 365.823 0.999997 11545887 291271.11 + 365.823 0.999997 11545887 327680.00 + 365.823 0.999997 11545887 374491.43 + 365.823 0.999998 11545887 436906.67 + 366.079 0.999998 11545908 524288.00 + 366.079 0.999998 11545908 582542.22 + 366.079 0.999998 11545908 655360.00 + 366.079 0.999999 11545908 748982.86 + 366.079 0.999999 11545908 873813.33 + 366.079 0.999999 11545908 1048576.00 + 366.079 0.999999 11545908 1165084.44 + 366.079 0.999999 11545908 1310720.00 + 366.079 0.999999 11545908 1497965.71 + 366.079 0.999999 11545908 1747626.67 + 366.079 1.000000 11545908 2097152.00 + 366.079 1.000000 11545908 2330168.89 + 366.079 1.000000 11545908 2621440.00 + 366.079 1.000000 11545908 2995931.43 + 366.079 1.000000 11545908 3495253.33 + 366.079 1.000000 11545908 4194304.00 + 366.079 1.000000 11545908 4660337.78 + 366.079 1.000000 11545908 5242880.00 + 366.335 1.000000 11545910 5991862.86 + 366.335 1.000000 11545910 inf +#[Mean = 11.373, StdDeviation = 47.928] +#[Max = 366.080, Total count = 11545910] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 12597999 requests in 2.00m, 708.85MB read +Requests/sec: 104984.91 +Transfer/sec: 5.91MB \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/async_4 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/async_4 new file mode 100644 index 000000000..765886861 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/async_4 @@ -0,0 +1,126 @@ +./wrk -d 2m -t 8 -c 64 -R 120000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/put.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 1404.446ms, rate sampling interval: 3778ms + Thread calibration: mean lat.: 1403.431ms, rate sampling interval: 3768ms + Thread calibration: mean lat.: 1405.487ms, rate sampling interval: 3774ms + Thread calibration: mean lat.: 1407.888ms, rate sampling interval: 3774ms + Thread calibration: mean lat.: 1401.962ms, rate sampling interval: 3753ms + Thread calibration: mean lat.: 1400.376ms, rate sampling interval: 3756ms + Thread calibration: mean lat.: 1403.649ms, rate sampling interval: 3768ms + Thread calibration: mean lat.: 1405.375ms, rate sampling interval: 3774ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 8.06s 3.54s 14.48s 58.23% + Req/Sec 13.31k 69.30 13.39k 81.90% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 8.03s + 75.000% 11.07s + 90.000% 13.03s + 99.000% 14.26s + 99.900% 14.41s + 99.990% 14.46s + 99.999% 14.48s +100.000% 14.49s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 1947.647 0.000000 21 1.00 + 3192.831 0.100000 1171719 1.11 + 4399.103 0.200000 2343064 1.25 + 5603.327 0.300000 3513625 1.43 + 6811.647 0.400000 4682673 1.67 + 8028.159 0.500000 5854094 2.00 + 8634.367 0.550000 6445604 2.22 + 9232.383 0.600000 7028756 2.50 + 9838.591 0.650000 7611385 2.86 + 10452.991 0.700000 8195573 3.33 + 11067.391 0.750000 8780663 4.00 + 11386.879 0.775000 9077509 4.44 + 11706.367 0.800000 9370182 5.00 + 12025.855 0.825000 9661322 5.71 + 12353.535 0.850000 9956557 6.67 + 12689.407 0.875000 10249040 8.00 + 12853.247 0.887500 10392486 8.89 + 13025.279 0.900000 10541683 10.00 + 13189.119 0.912500 10683910 11.43 + 13361.151 0.925000 10833386 13.33 + 13533.183 0.937500 10980532 16.00 + 13615.103 0.943750 11049954 17.78 + 13705.215 0.950000 11124328 20.00 + 13787.135 0.956250 11193620 22.86 + 13877.247 0.962500 11269615 26.67 + 13967.359 0.968750 11345291 32.00 + 14008.319 0.971875 11379703 35.56 + 14049.279 0.975000 11414822 40.00 + 14090.239 0.978125 11450068 45.71 + 14139.391 0.981250 11492119 53.33 + 14180.351 0.984375 11526731 64.00 + 14204.927 0.985938 11547206 71.11 + 14221.311 0.987500 11560757 80.00 + 14245.887 0.989062 11581119 91.43 + 14270.463 0.990625 11601586 106.67 + 14286.847 0.992188 11615125 128.00 + 14303.231 0.992969 11628368 142.22 + 14311.423 0.993750 11634983 160.00 + 14327.807 0.994531 11647885 182.86 + 14335.999 0.995313 11653971 213.33 + 14344.191 0.996094 11660124 256.00 + 14352.383 0.996484 11665806 284.44 + 14360.575 0.996875 11671213 320.00 + 14368.767 0.997266 11676445 365.71 + 14376.959 0.997656 11681462 426.67 + 14385.151 0.998047 11685856 512.00 + 14385.151 0.998242 11685856 568.89 + 14393.343 0.998437 11689758 640.00 + 14393.343 0.998633 11689758 731.43 + 14401.535 0.998828 11693542 853.33 + 14409.727 0.999023 11696702 1024.00 + 14409.727 0.999121 11696702 1137.78 + 14409.727 0.999219 11696702 1280.00 + 14417.919 0.999316 11699250 1462.86 + 14417.919 0.999414 11699250 1706.67 + 14426.111 0.999512 11701184 2048.00 + 14426.111 0.999561 11701184 2275.56 + 14426.111 0.999609 11701184 2560.00 + 14434.303 0.999658 11702967 2925.71 + 14434.303 0.999707 11702967 3413.33 + 14434.303 0.999756 11702967 4096.00 + 14442.495 0.999780 11703794 4551.11 + 14442.495 0.999805 11703794 5120.00 + 14442.495 0.999829 11703794 5851.43 + 14450.687 0.999854 11704239 6826.67 + 14450.687 0.999878 11704239 8192.00 + 14458.879 0.999890 11704699 9102.22 + 14458.879 0.999902 11704699 10240.00 + 14458.879 0.999915 11704699 11702.86 + 14467.071 0.999927 11705117 13653.33 + 14467.071 0.999939 11705117 16384.00 + 14467.071 0.999945 11705117 18204.44 + 14467.071 0.999951 11705117 20480.00 + 14475.263 0.999957 11705370 23405.71 + 14475.263 0.999963 11705370 27306.67 + 14475.263 0.999969 11705370 32768.00 + 14475.263 0.999973 11705370 36408.89 + 14475.263 0.999976 11705370 40960.00 + 14483.455 0.999979 11705603 46811.43 + 14483.455 0.999982 11705603 54613.33 + 14483.455 0.999985 11705603 65536.00 + 14483.455 0.999986 11705603 72817.78 + 14483.455 0.999988 11705603 81920.00 + 14483.455 0.999989 11705603 93622.86 + 14483.455 0.999991 11705603 109226.67 + 14483.455 0.999992 11705603 131072.00 + 14483.455 0.999993 11705603 145635.56 + 14483.455 0.999994 11705603 163840.00 + 14483.455 0.999995 11705603 187245.71 + 14483.455 0.999995 11705603 218453.33 + 14491.647 0.999996 11705652 262144.00 + 14491.647 1.000000 11705652 inf +#[Mean = 8059.430, StdDeviation = 3541.444] +#[Max = 14483.456, Total count = 11705652] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 12670131 requests in 2.00m, 712.91MB read +Requests/sec: 105586.79 +Transfer/sec: 5.94MB \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/sync_1 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/sync_1 new file mode 100644 index 000000000..883b7e027 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/sync_1 @@ -0,0 +1,108 @@ +./wrk -d 2m -t 8 -c 64 -R 2500000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/put.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 3871.334ms, rate sampling interval: 17055ms + Thread calibration: mean lat.: 3871.964ms, rate sampling interval: 17055ms + Thread calibration: mean lat.: 3872.359ms, rate sampling interval: 17055ms + Thread calibration: mean lat.: 3871.534ms, rate sampling interval: 17055ms + Thread calibration: mean lat.: 3872.281ms, rate sampling interval: 17055ms + Thread calibration: mean lat.: 3870.987ms, rate sampling interval: 17055ms + Thread calibration: mean lat.: 3871.371ms, rate sampling interval: 17055ms + Thread calibration: mean lat.: 3871.924ms, rate sampling interval: 17055ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.02m 30.00s 1.98m 59.98% + Req/Sec 2.36k 602.17 3.56k 83.33% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 0.92m + 75.000% 1.45m + 90.000% 1.75m + 99.000% 1.96m + 99.900% 1.98m + 99.990% 1.98m + 99.999% 1.99m +100.000% 1.99m + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 9928.703 0.000000 9 1.00 + 22429.695 0.100000 203930 1.11 + 33816.575 0.200000 408137 1.25 + 41320.447 0.300000 612166 1.43 + 48889.855 0.400000 816288 1.67 + 55246.847 0.500000 1020202 2.00 + 59768.831 0.550000 1121848 2.22 + 66420.735 0.600000 1223718 2.50 + 73269.247 0.650000 1326188 2.86 + 80084.991 0.700000 1428005 3.33 + 87031.807 0.750000 1530081 4.00 + 90439.679 0.775000 1580985 4.44 + 93913.087 0.800000 1632046 5.00 + 96206.847 0.825000 1682684 5.71 + 99680.255 0.850000 1733116 6.67 + 103153.663 0.875000 1784090 8.00 + 103874.559 0.887500 1812306 8.89 + 105054.207 0.900000 1835564 10.00 + 106758.143 0.912500 1860784 11.43 + 108527.615 0.925000 1886437 13.33 + 110297.087 0.937500 1911752 16.00 + 111214.591 0.943750 1925155 17.78 + 112001.023 0.950000 1937073 20.00 + 112918.527 0.956250 1950394 22.86 + 113770.495 0.962500 1962533 26.67 + 114687.999 0.968750 1975557 32.00 + 115146.751 0.971875 1982139 35.56 + 115605.503 0.975000 1988777 40.00 + 115998.719 0.978125 1994583 45.71 + 116457.471 0.981250 2001210 53.33 + 116916.223 0.984375 2007753 64.00 + 117112.831 0.985938 2010463 71.11 + 117309.439 0.987500 2013489 80.00 + 117571.583 0.989062 2017250 91.43 + 117768.191 0.990625 2019975 106.67 + 118030.335 0.992188 2023749 128.00 + 118095.871 0.992969 2024660 142.22 + 118226.943 0.993750 2026527 160.00 + 118358.015 0.994531 2028493 182.86 + 118423.551 0.995313 2029501 213.33 + 118554.623 0.996094 2031351 256.00 + 118620.159 0.996484 2032254 284.44 + 118685.695 0.996875 2033167 320.00 + 118751.231 0.997266 2034139 365.71 + 118816.767 0.997656 2035047 426.67 + 118816.767 0.998047 2035047 512.00 + 118882.303 0.998242 2035951 568.89 + 118882.303 0.998437 2035951 640.00 + 118947.839 0.998633 2036955 731.43 + 118947.839 0.998828 2036955 853.33 + 118947.839 0.999023 2036955 1024.00 + 119013.375 0.999121 2037935 1137.78 + 119013.375 0.999219 2037935 1280.00 + 119013.375 0.999316 2037935 1462.86 + 119013.375 0.999414 2037935 1706.67 + 119013.375 0.999512 2037935 2048.00 + 119078.911 0.999561 2038820 2275.56 + 119078.911 0.999609 2038820 2560.00 + 119078.911 0.999658 2038820 2925.71 + 119078.911 0.999707 2038820 3413.33 + 119078.911 0.999756 2038820 4096.00 + 119078.911 0.999780 2038820 4551.11 + 119078.911 0.999805 2038820 5120.00 + 119078.911 0.999829 2038820 5851.43 + 119078.911 0.999854 2038820 6826.67 + 119078.911 0.999878 2038820 8192.00 + 119078.911 0.999890 2038820 9102.22 + 119078.911 0.999902 2038820 10240.00 + 119078.911 0.999915 2038820 11702.86 + 119078.911 0.999927 2038820 13653.33 + 119078.911 0.999939 2038820 16384.00 + 119078.911 0.999945 2038820 18204.44 + 119144.447 0.999951 2038929 20480.00 + 119144.447 1.000000 2038929 inf +#[Mean = 61168.132, StdDeviation = 29997.827] +#[Max = 119078.912, Total count = 2038929] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 2243254 requests in 2.00m, 126.22MB read +Requests/sec: 18693.91 +Transfer/sec: 1.05MB diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/sync_2 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/sync_2 new file mode 100644 index 000000000..b36fe85b3 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/sync_2 @@ -0,0 +1,92 @@ +./wrk -d 2m -t 8 -c 64 -R 100000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/put.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 3365.418ms, rate sampling interval: 14180ms + Thread calibration: mean lat.: 3364.364ms, rate sampling interval: 14180ms + Thread calibration: mean lat.: 3365.478ms, rate sampling interval: 14180ms + Thread calibration: mean lat.: 3365.064ms, rate sampling interval: 14180ms + Thread calibration: mean lat.: 3365.116ms, rate sampling interval: 14180ms + Thread calibration: mean lat.: 3364.900ms, rate sampling interval: 14180ms + Thread calibration: mean lat.: 3363.704ms, rate sampling interval: 14180ms + Thread calibration: mean lat.: 3365.292ms, rate sampling interval: 14180ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 0.92m 27.13s 1.70m 57.78% + Req/Sec 1.81k 9.09 1.83k 85.71% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 0.92m + 75.000% 1.31m + 90.000% 1.55m + 99.000% 1.69m + 99.900% 1.70m + 99.990% 1.70m + 99.999% 1.70m +100.000% 1.70m + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 8171.519 0.000000 4 1.00 + 17580.031 0.100000 159315 1.11 + 27033.599 0.200000 318659 1.25 + 36470.783 0.300000 478350 1.43 + 45842.431 0.400000 637490 1.67 + 55312.383 0.500000 796965 2.00 + 59998.207 0.550000 876163 2.22 + 64684.031 0.600000 955908 2.50 + 69468.159 0.650000 1035994 2.86 + 74252.287 0.700000 1115545 3.33 + 78774.271 0.750000 1195353 4.00 + 81068.031 0.775000 1235567 4.44 + 83361.791 0.800000 1274524 5.00 + 85655.551 0.825000 1314403 5.71 + 88014.847 0.850000 1354452 6.67 + 90374.143 0.875000 1394134 8.00 + 91553.791 0.887500 1414299 8.89 + 92733.439 0.900000 1434401 10.00 + 93913.087 0.912500 1453792 11.43 + 95092.735 0.925000 1473571 13.33 + 96337.919 0.937500 1494328 16.00 + 96927.743 0.943750 1504059 17.78 + 97517.567 0.950000 1513906 20.00 + 98107.391 0.956250 1523923 22.86 + 98697.215 0.962500 1534049 26.67 + 99287.039 0.968750 1544128 32.00 + 99549.183 0.971875 1548520 35.56 + 99876.863 0.975000 1553911 40.00 + 100139.007 0.978125 1558224 45.71 + 100466.687 0.981250 1563794 53.33 + 100728.831 0.984375 1568169 64.00 + 100925.439 0.985938 1571454 71.11 + 101056.511 0.987500 1573693 80.00 + 101187.583 0.989062 1575856 91.43 + 101384.191 0.990625 1579092 106.67 + 101515.263 0.992188 1581275 128.00 + 101580.799 0.992969 1582373 142.22 + 101646.335 0.993750 1583529 160.00 + 101711.871 0.994531 1584620 182.86 + 101777.407 0.995313 1585699 213.33 + 101908.479 0.996094 1587891 256.00 + 101908.479 0.996484 1587891 284.44 + 101974.015 0.996875 1588933 320.00 + 101974.015 0.997266 1588933 365.71 + 102039.551 0.997656 1590060 426.67 + 102039.551 0.998047 1590060 512.00 + 102105.087 0.998242 1591182 568.89 + 102105.087 0.998437 1591182 640.00 + 102105.087 0.998633 1591182 731.43 + 102105.087 0.998828 1591182 853.33 + 102170.623 0.999023 1592309 1024.00 + 102170.623 0.999121 1592309 1137.78 + 102170.623 0.999219 1592309 1280.00 + 102170.623 0.999316 1592309 1462.86 + 102170.623 0.999414 1592309 1706.67 + 102170.623 0.999512 1592309 2048.00 + 102236.159 0.999561 1593014 2275.56 + 102236.159 1.000000 1593014 inf +#[Mean = 55231.053, StdDeviation = 27129.375] +#[Max = 102170.624, Total count = 1593014] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 1776784 requests in 2.00m, 99.97MB read +Requests/sec: 14806.75 +Transfer/sec: 853.12KB diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/sync_3 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/sync_3 new file mode 100644 index 000000000..20444abbd --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/sync_3 @@ -0,0 +1,133 @@ +./wrk -d 2m -t 8 -c 64 -R 20000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/put.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 439.786ms, rate sampling interval: 2312ms + Thread calibration: mean lat.: 439.802ms, rate sampling interval: 2314ms + Thread calibration: mean lat.: 439.638ms, rate sampling interval: 2312ms + Thread calibration: mean lat.: 439.516ms, rate sampling interval: 2314ms + Thread calibration: mean lat.: 439.782ms, rate sampling interval: 2316ms + Thread calibration: mean lat.: 439.824ms, rate sampling interval: 2316ms + Thread calibration: mean lat.: 439.668ms, rate sampling interval: 2314ms + Thread calibration: mean lat.: 439.836ms, rate sampling interval: 2314ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 7.70s 6.18s 20.81s 68.22% + Req/Sec 2.06k 530.67 4.50k 85.11% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 5.66s + 75.000% 12.46s + 90.000% 17.48s + 99.000% 20.45s + 99.900% 20.76s + 99.990% 20.79s + 99.999% 20.81s +100.000% 20.82s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.085 0.000000 1 1.00 + 1305.599 0.100000 181366 1.11 + 2031.615 0.200000 362673 1.25 + 2607.103 0.300000 544621 1.43 + 3166.207 0.400000 725198 1.67 + 5664.767 0.500000 906622 2.00 + 7364.607 0.550000 997256 2.22 + 8945.663 0.600000 1088032 2.50 + 10592.255 0.650000 1178598 2.86 + 11476.991 0.700000 1270038 3.33 + 12460.031 0.750000 1359911 4.00 + 13320.191 0.775000 1405226 4.44 + 14106.623 0.800000 1450498 5.00 + 14893.055 0.825000 1495716 5.71 + 15736.831 0.850000 1541384 6.67 + 16621.567 0.875000 1587065 8.00 + 17055.743 0.887500 1609765 8.89 + 17481.727 0.900000 1631615 10.00 + 17956.863 0.912500 1654939 11.43 + 18366.463 0.925000 1677381 13.33 + 18743.295 0.937500 1700224 16.00 + 18939.903 0.943750 1711249 17.78 + 19136.511 0.950000 1723147 20.00 + 19365.887 0.956250 1734321 22.86 + 19513.343 0.962500 1745260 26.67 + 19726.335 0.968750 1756260 32.00 + 19808.255 0.971875 1762222 35.56 + 19922.943 0.975000 1768398 40.00 + 20004.863 0.978125 1773436 45.71 + 20119.551 0.981250 1779259 53.33 + 20234.239 0.984375 1784915 64.00 + 20283.391 0.985938 1787764 71.11 + 20348.927 0.987500 1790892 80.00 + 20414.463 0.989062 1793854 91.43 + 20463.615 0.990625 1796043 106.67 + 20529.151 0.992188 1798918 128.00 + 20561.919 0.992969 1800494 142.22 + 20594.687 0.993750 1802246 160.00 + 20611.071 0.994531 1803037 182.86 + 20643.839 0.995313 1804916 213.33 + 20676.607 0.996094 1806544 256.00 + 20676.607 0.996484 1806544 284.44 + 20692.991 0.996875 1807410 320.00 + 20709.375 0.997266 1808362 365.71 + 20725.759 0.997656 1809299 426.67 + 20742.143 0.998047 1810142 512.00 + 20742.143 0.998242 1810142 568.89 + 20742.143 0.998437 1810142 640.00 + 20758.527 0.998633 1811107 731.43 + 20758.527 0.998828 1811107 853.33 + 20774.911 0.999023 1812096 1024.00 + 20774.911 0.999121 1812096 1137.78 + 20774.911 0.999219 1812096 1280.00 + 20774.911 0.999316 1812096 1462.86 + 20774.911 0.999414 1812096 1706.67 + 20774.911 0.999512 1812096 2048.00 + 20791.295 0.999561 1812740 2275.56 + 20791.295 0.999609 1812740 2560.00 + 20791.295 0.999658 1812740 2925.71 + 20791.295 0.999707 1812740 3413.33 + 20791.295 0.999756 1812740 4096.00 + 20791.295 0.999780 1812740 4551.11 + 20791.295 0.999805 1812740 5120.00 + 20791.295 0.999829 1812740 5851.43 + 20791.295 0.999854 1812740 6826.67 + 20791.295 0.999878 1812740 8192.00 + 20791.295 0.999890 1812740 9102.22 + 20791.295 0.999902 1812740 10240.00 + 20807.679 0.999915 1812893 11702.86 + 20807.679 0.999927 1812893 13653.33 + 20807.679 0.999939 1812893 16384.00 + 20807.679 0.999945 1812893 18204.44 + 20807.679 0.999951 1812893 20480.00 + 20807.679 0.999957 1812893 23405.71 + 20807.679 0.999963 1812893 27306.67 + 20807.679 0.999969 1812893 32768.00 + 20807.679 0.999973 1812893 36408.89 + 20807.679 0.999976 1812893 40960.00 + 20807.679 0.999979 1812893 46811.43 + 20807.679 0.999982 1812893 54613.33 + 20807.679 0.999985 1812893 65536.00 + 20807.679 0.999986 1812893 72817.78 + 20807.679 0.999988 1812893 81920.00 + 20807.679 0.999989 1812893 93622.86 + 20807.679 0.999991 1812893 109226.67 + 20807.679 0.999992 1812893 131072.00 + 20807.679 0.999993 1812893 145635.56 + 20807.679 0.999994 1812893 163840.00 + 20807.679 0.999995 1812893 187245.71 + 20807.679 0.999995 1812893 218453.33 + 20807.679 0.999996 1812893 262144.00 + 20807.679 0.999997 1812893 291271.11 + 20807.679 0.999997 1812893 327680.00 + 20807.679 0.999997 1812893 374491.43 + 20807.679 0.999998 1812893 436906.67 + 20807.679 0.999998 1812893 524288.00 + 20807.679 0.999998 1812893 582542.22 + 20824.063 0.999998 1812896 655360.00 + 20824.063 1.000000 1812896 inf +#[Mean = 7697.581, StdDeviation = 6177.370] +#[Max = 20807.680, Total count = 1812896] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 1983844 requests in 2.00m, 111.62MB read +Requests/sec: 16531.99 +Transfer/sec: 0.93MB diff --git a/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/sync_4 b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/sync_4 new file mode 100644 index 000000000..28eaccbb1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/timofeevkirill/results/task5/wrk2/put/sync_4 @@ -0,0 +1,140 @@ +./wrk -d 2m -t 8 -c 64 -R 14000 -s /home/aphirri/IdeaProjects/2024-highload-dht/src/main/java/ru/vk/itmo/test/timofeevkirill/scripts/lua/put.lua -L http://localhost:8080/v0/entry +Running 2m test @ http://localhost:8080/v0/entry + 8 threads and 64 connections + Thread calibration: mean lat.: 7.079ms, rate sampling interval: 25ms + Thread calibration: mean lat.: 7.111ms, rate sampling interval: 25ms + Thread calibration: mean lat.: 7.206ms, rate sampling interval: 25ms + Thread calibration: mean lat.: 7.088ms, rate sampling interval: 25ms + Thread calibration: mean lat.: 7.192ms, rate sampling interval: 26ms + Thread calibration: mean lat.: 7.149ms, rate sampling interval: 25ms + Thread calibration: mean lat.: 7.199ms, rate sampling interval: 26ms + Thread calibration: mean lat.: 7.291ms, rate sampling interval: 26ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 6.14ms 4.68ms 42.75ms 83.26% + Req/Sec 1.79k 147.07 2.44k 76.24% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 4.98ms + 75.000% 7.85ms + 90.000% 11.22ms + 99.000% 26.77ms + 99.900% 37.12ms + 99.990% 40.26ms + 99.999% 41.76ms +100.000% 42.78ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.067 0.000000 1 1.00 + 1.929 0.100000 154033 1.11 + 2.753 0.200000 308285 1.25 + 3.493 0.300000 462249 1.43 + 4.219 0.400000 615873 1.67 + 4.983 0.500000 770402 2.00 + 5.399 0.550000 846887 2.22 + 5.879 0.600000 924184 2.50 + 6.439 0.650000 1000829 2.86 + 7.087 0.700000 1077877 3.33 + 7.847 0.750000 1154688 4.00 + 8.287 0.775000 1193354 4.44 + 8.759 0.800000 1231873 5.00 + 9.279 0.825000 1270196 5.71 + 9.847 0.850000 1308934 6.67 + 10.471 0.875000 1347452 8.00 + 10.823 0.887500 1366593 8.89 + 11.223 0.900000 1385746 10.00 + 11.703 0.912500 1404934 11.43 + 12.287 0.925000 1424136 13.33 + 13.031 0.937500 1443529 16.00 + 13.471 0.943750 1453049 17.78 + 13.975 0.950000 1462602 20.00 + 14.559 0.956250 1472209 22.86 + 15.271 0.962500 1481845 26.67 + 16.327 0.968750 1491501 32.00 + 17.087 0.971875 1496297 35.56 + 18.015 0.975000 1501135 40.00 + 19.103 0.978125 1505913 45.71 + 20.383 0.981250 1510705 53.33 + 22.287 0.984375 1515524 64.00 + 23.567 0.985938 1517925 71.11 + 25.103 0.987500 1520325 80.00 + 26.223 0.989062 1522724 91.43 + 27.119 0.990625 1525168 106.67 + 28.063 0.992188 1527537 128.00 + 28.671 0.992969 1528740 142.22 + 29.375 0.993750 1529952 160.00 + 30.255 0.994531 1531152 182.86 + 31.231 0.995313 1532350 213.33 + 32.399 0.996094 1533552 256.00 + 33.055 0.996484 1534158 284.44 + 33.727 0.996875 1534757 320.00 + 34.367 0.997266 1535362 365.71 + 35.007 0.997656 1535969 426.67 + 35.551 0.998047 1536580 512.00 + 35.839 0.998242 1536875 568.89 + 36.159 0.998437 1537179 640.00 + 36.415 0.998633 1537473 731.43 + 36.767 0.998828 1537765 853.33 + 37.183 0.999023 1538071 1024.00 + 37.407 0.999121 1538222 1137.78 + 37.631 0.999219 1538366 1280.00 + 37.919 0.999316 1538513 1462.86 + 38.239 0.999414 1538672 1706.67 + 38.527 0.999512 1538817 2048.00 + 38.687 0.999561 1538886 2275.56 + 38.879 0.999609 1538964 2560.00 + 39.071 0.999658 1539039 2925.71 + 39.295 0.999707 1539118 3413.33 + 39.487 0.999756 1539192 4096.00 + 39.583 0.999780 1539225 4551.11 + 39.711 0.999805 1539268 5120.00 + 39.807 0.999829 1539300 5851.43 + 39.967 0.999854 1539346 6826.67 + 40.095 0.999878 1539378 8192.00 + 40.191 0.999890 1539398 9102.22 + 40.287 0.999902 1539415 10240.00 + 40.383 0.999915 1539435 11702.86 + 40.479 0.999927 1539453 13653.33 + 40.607 0.999939 1539472 16384.00 + 40.671 0.999945 1539478 18204.44 + 40.767 0.999951 1539493 20480.00 + 40.831 0.999957 1539498 23405.71 + 40.959 0.999963 1539508 27306.67 + 41.055 0.999969 1539516 32768.00 + 41.119 0.999973 1539523 36408.89 + 41.151 0.999976 1539526 40960.00 + 41.247 0.999979 1539531 46811.43 + 41.311 0.999982 1539536 54613.33 + 41.407 0.999985 1539539 65536.00 + 41.535 0.999986 1539541 72817.78 + 41.567 0.999988 1539544 81920.00 + 41.695 0.999989 1539546 93622.86 + 41.791 0.999991 1539548 109226.67 + 41.951 0.999992 1539551 131072.00 + 41.983 0.999993 1539552 145635.56 + 42.015 0.999994 1539553 163840.00 + 42.047 0.999995 1539555 187245.71 + 42.047 0.999995 1539555 218453.33 + 42.207 0.999996 1539557 262144.00 + 42.207 0.999997 1539557 291271.11 + 42.271 0.999997 1539558 327680.00 + 42.271 0.999997 1539558 374491.43 + 42.463 0.999998 1539559 436906.67 + 42.655 0.999998 1539560 524288.00 + 42.655 0.999998 1539560 582542.22 + 42.655 0.999998 1539560 655360.00 + 42.655 0.999999 1539560 748982.86 + 42.687 0.999999 1539561 873813.33 + 42.687 0.999999 1539561 1048576.00 + 42.687 0.999999 1539561 1165084.44 + 42.687 0.999999 1539561 1310720.00 + 42.687 0.999999 1539561 1497965.71 + 42.783 0.999999 1539562 1747626.67 + 42.783 1.000000 1539562 inf +#[Mean = 6.137, StdDeviation = 4.678] +#[Max = 42.752, Total count = 1539562] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 1679729 requests in 2.00m, 94.51MB read +Requests/sec: 13997.79 +Transfer/sec: 806.51KB \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/ConsistentHashing.java b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/ConsistentHashing.java index 7d0cc0166..e2d1ee9fc 100644 --- a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/ConsistentHashing.java +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/ConsistentHashing.java @@ -3,19 +3,19 @@ import one.nio.util.Hash; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.NavigableMap; import java.util.SortedMap; import java.util.TreeMap; -import java.util.stream.Collectors; public class ConsistentHashing { private final NavigableMap circle; + private final List clusterUrls; public ConsistentHashing(List clusterUrls, int numbOfVirtualNodes) { circle = new TreeMap<>(); - + this.clusterUrls = clusterUrls; for (String clusterUrl : clusterUrls) { for (int i = 0; i < numbOfVirtualNodes; i++) { addNode(i, clusterUrl); @@ -39,28 +39,45 @@ public String getNode(String key) { } public List getNodes(String key, int from) { - if (circle.isEmpty()) { - return new ArrayList<>(); + List res = new ArrayList<>(); + + if (key != null && from > 0) { + if (from < clusterUrls.size()) { + int slot = getHash(key); + Iterator it = new ClockwiseIterator(slot); + while (it.hasNext() && res.size() < from) { + String part = it.next(); + res.add(part); + } + } else { + res.addAll(clusterUrls); + } } - final int hash = getHash(key); - SortedMap tailMap = circle.tailMap(hash); - return (tailMap.isEmpty() ? circle : tailMap).values() - .stream().limit(from).collect(Collectors.toList()); + return res; } - List getNodes(String key, List clusterUrls, int from) { - Map nodesHashes = new TreeMap<>(); + private int getHash(String key) { + return Hash.murmur3(key); + } + + private class ClockwiseIterator implements Iterator { + private final Iterator head; + private final Iterator tail; - for (String nodeUrl : clusterUrls) { - nodesHashes.put(Hash.murmur3(nodeUrl + key), nodeUrl); + public ClockwiseIterator(int slot) { + this.head = circle.headMap(slot).values().iterator(); + this.tail = circle.tailMap(slot).values().iterator(); } - return nodesHashes.values().stream().limit(from) - .collect(Collectors.toList()); - } + @Override + public boolean hasNext() { + return head.hasNext() || tail.hasNext(); + } - private int getHash(String key) { - return Hash.murmur3(key); + @Override + public String next() { + return tail.hasNext() ? tail.next() : head.next(); + } } } diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/Constants.java b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/Constants.java index c8c5e00a0..b4ad3b645 100644 --- a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/Constants.java +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/Constants.java @@ -13,7 +13,8 @@ public final class Constants { ); public static final String TOO_MANY_REQUESTS = "429 Too Many Requests"; public static final String NOT_ENOUGH_REPLICAS = "504 Not Enough Replicas"; - public static final int REQUEST_TIMEOUT = 300; + public static final int SERVER_ERROR = 500; + public static final int REQUEST_TIMEOUT = 500; public static final Map HTTP_CODE = Map.of( HttpURLConnection.HTTP_OK, Response.OK, HttpURLConnection.HTTP_ACCEPTED, Response.ACCEPTED, @@ -25,7 +26,7 @@ public final class Constants { public static final String BASE_URL = "http://localhost"; public static final int BASE_PORT = 8080; public static final long FLUSHING_THRESHOLD_BYTES = 1024 * 1024; - public static final int NUMBER_OF_VIRTUAL_NODES = 5; + public static final int NUMBER_OF_VIRTUAL_NODES = 100; public static final String HTTP_TIMESTAMP_HEADER = "X-Timestamp"; public static final String NIO_TIMESTAMP_HEADER = "x-timestamp:"; public static final String HTTP_TERMINATION_HEADER = "X-Termination"; diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/ServerImpl.java b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/ServerImpl.java index 5c74bba23..ac0216c82 100644 --- a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/ServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/ServerImpl.java @@ -23,19 +23,18 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; public class ServerImpl extends HttpServer { private final ExecutorService executorService; private final HttpClient httpClient; private final ConsistentHashing consistentHashing; - private final List clusterUrls; private final RequestHandler requestHandler; private static final Logger log = LoggerFactory.getLogger(ServerImpl.class); @@ -49,10 +48,9 @@ public ServerImpl(ServiceConfig config, Dao dao, Worker worker, this.consistentHashing = consistentHashing; this.selfUrl = config.selfUrl(); this.clusterSize = config.clusterUrls().size(); - this.clusterUrls = config.clusterUrls(); this.requestHandler = new RequestHandler(dao); this.httpClient = HttpClient.newBuilder() - .executor(Executors.newFixedThreadPool(2)).build(); + .executor(worker.getExecutorService()).build(); } private static HttpServerConfig createServerConfig(ServiceConfig serviceConfig) { @@ -86,19 +84,13 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti } String paramId = request.getParameter("id="); - - if (paramId == null || paramId.isEmpty()) { - sendResponse(session, new Response(Response.BAD_REQUEST, Response.EMPTY)); - return; - } - String fromStr = request.getParameter("from="); String ackStr = request.getParameter("ack="); int from = fromStr == null || fromStr.isEmpty() ? clusterSize : Integer.parseInt(fromStr); int ack = ackStr == null || ackStr.isEmpty() ? from / 2 + 1 : Integer.parseInt(ackStr); - if (ack == 0 || from > clusterSize || ack > from) { + if (ack == 0 || from > clusterSize || ack > from || paramId == null || paramId.isEmpty()) { sendResponse(session, new Response(Response.BAD_REQUEST, Response.EMPTY)); return; } @@ -119,16 +111,16 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti private void processingRequest(Request request, HttpSession session, long processingStartTime, String paramId, int from, int ack) throws IOException { - if (System.currentTimeMillis() - processingStartTime > 350) { + if (System.currentTimeMillis() - processingStartTime > Constants.REQUEST_TIMEOUT) { session.sendResponse(new Response(Response.REQUEST_TIMEOUT, Response.EMPTY)); return; } try { if (request.getHeader(Constants.HTTP_TERMINATION_HEADER) == null) { - session.sendResponse(handleProxyRequest(request, session, paramId, from, ack)); + sendResponse(session, handleProxyRequest(request, session, paramId, from, ack)); } else { - session.sendResponse(requestHandler.handle(request, paramId)); + sendResponse(session, requestHandler.handle(request, paramId)); } } catch (Exception e) { if (e.getClass() == HttpException.class) { @@ -140,16 +132,6 @@ private void processingRequest(Request request, HttpSession session, long proces } } - private Response sendException(Exception exception) { - String responseCode; - if (exception.getClass().equals(TimeoutException.class)) { - responseCode = Response.REQUEST_TIMEOUT; - } else { - responseCode = Response.INTERNAL_ERROR; - } - return new Response(responseCode, Response.EMPTY); - } - private void sendResponse(HttpSession session, Response response) { try { session.sendResponse(response); @@ -168,33 +150,27 @@ private HttpRequest createProxyRequest(Request request, String nodeUrl, String p .build(); } - private Response sendProxyRequest(HttpRequest httpRequest) { - try { - HttpResponse httpResponse = httpClient - .sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()) - .get(Constants.REQUEST_TIMEOUT, TimeUnit.MILLISECONDS); - - String statusCode = Constants.HTTP_CODE.getOrDefault(httpResponse.statusCode(), null); - if (statusCode == null) { - return new Response(Response.INTERNAL_ERROR, httpResponse.body()); - } else { + private CompletableFuture sendProxyRequest(HttpRequest httpRequest) { + return httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()) + .thenApplyAsync(this::processingResponse); + } - Response response = new Response(statusCode, httpResponse.body()); - long timestamp = httpRequest.headers() - .firstValueAsLong(Constants.HTTP_TIMESTAMP_HEADER).orElse(0); - response.addHeader(Constants.NIO_TIMESTAMP_HEADER + timestamp); - return response; - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return sendException(e); - } catch (ExecutionException | TimeoutException e) { - return sendException(e); + private Response processingResponse(HttpResponse response) { + String statusCode = Constants.HTTP_CODE.getOrDefault(response.statusCode(), null); + if (statusCode == null) { + return new Response(Response.INTERNAL_ERROR, response.body()); + } else { + Response newResponse = new Response(statusCode, response.body()); + long timestamp = response.headers() + .firstValueAsLong(Constants.HTTP_TIMESTAMP_HEADER).orElse(0); + newResponse.addHeader(Constants.NIO_TIMESTAMP_HEADER + timestamp); + return newResponse; } } - private List sendProxyRequests(Map httpRequests, List nodeUrls) { - List responses = new ArrayList<>(); + private List> sendProxyRequests(Map httpRequests, + List nodeUrls) { + List> responses = new ArrayList<>(); for (String nodeUrl : nodeUrls) { HttpRequest httpRequest = httpRequests.get(nodeUrl); if (!Objects.equals(selfUrl, nodeUrl)) { @@ -204,8 +180,9 @@ private List sendProxyRequests(Map httpRequests, return responses; } - private Response handleProxyRequest(Request request, HttpSession session, String paramId, int from, int ack) { - List nodeUrls = consistentHashing.getNodes(paramId, clusterUrls, from); + private Response handleProxyRequest(Request request, HttpSession session, + String paramId, int from, int ack) { + List nodeUrls = consistentHashing.getNodes(paramId, from); if (nodeUrls.size() < from) { sendResponse(session, new Response(Constants.NOT_ENOUGH_REPLICAS, Response.EMPTY)); @@ -216,31 +193,66 @@ private Response handleProxyRequest(Request request, HttpSession session, String httpRequests.put(nodeUrl, createProxyRequest(request, nodeUrl, paramId)); } - List responses = sendProxyRequests(httpRequests, nodeUrls); + List> responses = sendProxyRequests(httpRequests, nodeUrls); if (httpRequests.get(selfUrl) != null) { - responses.add(requestHandler.handle(request, paramId)); + responses.add( + CompletableFuture.supplyAsync(() -> requestHandler.handle(request, paramId)) + ); } - List successResponses = new ArrayList<>(); - for (Response response : responses) { - if (response.getStatus() < 500) { - successResponses.add(response); - } + return getQuorumResult(request, from, ack, responses); + } + + private Response getQuorumResult(Request request, int from, int ack, + List> responses) { + List successResponses = new CopyOnWriteArrayList<>(); + CompletableFuture result = new CompletableFuture<>(); + AtomicInteger successResponseCount = new AtomicInteger(); + AtomicInteger errorResponseCount = new AtomicInteger(); + + for (CompletableFuture responseFuture : responses) { + responseFuture.whenCompleteAsync((response, throwable) -> { + if (throwable == null || (response != null && response.getStatus() < Constants.SERVER_ERROR)) { + successResponseCount.incrementAndGet(); + successResponses.add(response); + } else { + errorResponseCount.incrementAndGet(); + } + + if (successResponseCount.get() >= ack) { + result.complete(getResult(request, successResponses)); + } + + if (errorResponseCount.get() == from - ack + 1) { + result.complete(new Response(Constants.NOT_ENOUGH_REPLICAS, Response.EMPTY)); + } + }).exceptionally(e -> new Response(Response.INTERNAL_ERROR, Response.EMPTY)); } - if (successResponses.size() >= ack) { - if (request.getMethod() == Request.METHOD_GET) { - successResponses.sort(Comparator.comparingLong(r -> { - String timestamp = r.getHeader(Constants.NIO_TIMESTAMP_HEADER); - return timestamp == null ? 0 : Long.parseLong(timestamp); - })); - return successResponses.getFirst(); - } else { - return successResponses.getLast(); - } + try { + return result.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return new Response(Response.INTERNAL_ERROR, Response.EMPTY); + } catch (ExecutionException e) { + return new Response(Response.INTERNAL_ERROR, Response.EMPTY); + } + } + + private Response getResult(Request request, List successResponses) { + if (request.getMethod() == Request.METHOD_GET) { + sortResponses(successResponses); + return successResponses.getLast(); } else { - return new Response(Constants.NOT_ENOUGH_REPLICAS, Response.EMPTY); + return successResponses.getFirst(); } } + + private void sortResponses(List successResponses) { + successResponses.sort(Comparator.comparingLong(r -> { + String timestamp = r.getHeader(Constants.NIO_TIMESTAMP_HEADER); + return timestamp == null ? 0 : Long.parseLong(timestamp); + })); + } } diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/ServiceImpl.java index df8cdb7da..0415227bc 100644 --- a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/ServiceImpl.java @@ -48,7 +48,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 diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/get-profile-1th-2con-5000.txt b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/get-profile-1th-2con-5000.txt new file mode 100644 index 000000000..6a4168432 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/get-profile-1th-2con-5000.txt @@ -0,0 +1,81 @@ +Running 30s test @ http://localhost:8080 + 1 threads and 2 connections + Thread calibration: mean lat.: 4858.425ms, rate sampling interval: 16023ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 15.58s 4.60s 26.33s 66.12% + Req/Sec 629.00 0.00 629.00 100.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 14.93s + 75.000% 17.96s + 90.000% 23.90s + 99.000% 26.10s + 99.900% 26.33s + 99.990% 26.35s + 99.999% 26.35s +100.000% 26.35s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 8650.751 0.000000 1 1.00 + 9986.047 0.100000 1156 1.11 + 11280.383 0.200000 2299 1.25 + 12689.407 0.300000 3448 1.43 + 13803.519 0.400000 4603 1.67 + 14925.823 0.500000 5756 2.00 + 15474.687 0.550000 6328 2.22 + 16072.703 0.600000 6899 2.50 + 16637.951 0.650000 7470 2.86 + 17399.807 0.700000 8053 3.33 + 17956.863 0.750000 8624 4.00 + 18300.927 0.775000 8920 4.44 + 18595.839 0.800000 9203 5.00 + 19333.119 0.825000 9483 5.71 + 20889.599 0.850000 9769 6.67 + 22429.695 0.875000 10068 8.00 + 23363.583 0.887500 10200 8.89 + 23904.255 0.900000 10343 10.00 + 24133.631 0.912500 10489 11.43 + 24412.159 0.925000 10633 13.33 + 24625.151 0.937500 10784 16.00 + 24723.455 0.943750 10847 17.78 + 24788.991 0.950000 10925 20.00 + 24854.527 0.956250 10990 22.86 + 25280.511 0.962500 11065 26.67 + 25624.575 0.968750 11137 32.00 + 25673.727 0.971875 11176 35.56 + 25755.647 0.975000 11214 40.00 + 25788.415 0.978125 11261 45.71 + 25804.799 0.981250 11279 53.33 + 26034.175 0.984375 11322 64.00 + 26050.559 0.985938 11340 71.11 + 26066.943 0.987500 11357 80.00 + 26083.327 0.989062 11376 91.43 + 26132.479 0.990625 11386 106.67 + 26198.015 0.992188 11405 128.00 + 26214.399 0.992969 11419 142.22 + 26230.783 0.993750 11438 160.00 + 26230.783 0.994531 11438 182.86 + 26247.167 0.995313 11452 213.33 + 26247.167 0.996094 11452 256.00 + 26247.167 0.996484 11452 284.44 + 26263.551 0.996875 11472 320.00 + 26263.551 0.997266 11472 365.71 + 26263.551 0.997656 11472 426.67 + 26263.551 0.998047 11472 512.00 + 26263.551 0.998242 11472 568.89 + 26279.935 0.998437 11477 640.00 + 26279.935 0.998633 11477 731.43 + 26296.319 0.998828 11479 853.33 + 26329.087 0.999023 11481 1024.00 + 26345.471 0.999121 11492 1137.78 + 26345.471 1.000000 11492 inf +#[Mean = 15575.994, StdDeviation = 4599.957] +#[Max = 26329.088, Total count = 11492] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 18280 requests in 30.00s, 1.46MB read + Socket errors: connect 0, read 48, write 0, timeout 0 + Non-2xx or 3xx responses: 15645 +Requests/sec: 609.33 +Transfer/sec: 49.89KB diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/get-profile-1th-alloc.html b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/get-profile-1th-alloc.html new file mode 100644 index 000000000..66f329f7d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/get-profile-1th-alloc.html @@ -0,0 +1,2718 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/get-profile-1th-cpu.html b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/get-profile-1th-cpu.html new file mode 100644 index 000000000..72450c708 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/get-profile-1th-cpu.html @@ -0,0 +1,5374 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/get-profile-1th-lock.html b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/get-profile-1th-lock.html new file mode 100644 index 000000000..326f1004f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/get-profile-1th-lock.html @@ -0,0 +1,968 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-2con-10000.txt b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-2con-10000.txt new file mode 100644 index 000000000..92af9a75f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-2con-10000.txt @@ -0,0 +1,78 @@ +Running 30s test @ http://localhost:8080 + 1 threads and 2 connections + Thread calibration: mean lat.: 5529.087ms, rate sampling interval: 17678ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 17.94s 5.63s 28.59s 57.21% + Req/Sec 463.00 0.00 463.00 100.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 16.18s + 75.000% 23.53s + 90.000% 25.76s + 99.000% 28.51s + 99.900% 28.59s + 99.990% 28.61s + 99.999% 28.61s +100.000% 28.61s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 9519.103 0.000000 2 1.00 + 10977.279 0.100000 901 1.11 + 12582.911 0.200000 1800 1.25 + 14041.087 0.300000 2698 1.43 + 15122.431 0.400000 3598 1.67 + 16179.199 0.500000 4497 2.00 + 17121.279 0.550000 4945 2.22 + 18808.831 0.600000 5394 2.50 + 20365.311 0.650000 5840 2.86 + 22134.783 0.700000 6291 3.33 + 23527.423 0.750000 6739 4.00 + 23871.487 0.775000 6974 4.44 + 24281.087 0.800000 7191 5.00 + 24657.919 0.825000 7416 5.71 + 24985.599 0.850000 7648 6.67 + 25395.199 0.875000 7863 8.00 + 25624.575 0.887500 7981 8.89 + 25755.647 0.900000 8098 10.00 + 26329.087 0.912500 8199 11.43 + 27099.135 0.925000 8320 13.33 + 27836.415 0.937500 8423 16.00 + 27901.951 0.943750 8485 17.78 + 27951.103 0.950000 8535 20.00 + 28016.639 0.956250 8593 22.86 + 28082.175 0.962500 8651 26.67 + 28229.631 0.968750 8714 32.00 + 28262.399 0.971875 8734 35.56 + 28311.551 0.975000 8769 40.00 + 28344.319 0.978125 8789 45.71 + 28426.239 0.981250 8823 53.33 + 28459.007 0.984375 8855 64.00 + 28475.391 0.985938 8872 71.11 + 28475.391 0.987500 8872 80.00 + 28491.775 0.989062 8888 91.43 + 28508.159 0.990625 8906 106.67 + 28524.543 0.992188 8919 128.00 + 28540.927 0.992969 8934 142.22 + 28540.927 0.993750 8934 160.00 + 28557.311 0.994531 8949 182.86 + 28557.311 0.995313 8949 213.33 + 28557.311 0.996094 8949 256.00 + 28573.695 0.996484 8964 284.44 + 28573.695 0.996875 8964 320.00 + 28573.695 0.997266 8964 365.71 + 28573.695 0.997656 8964 426.67 + 28590.079 0.998047 8975 512.00 + 28590.079 0.998242 8975 568.89 + 28590.079 0.998437 8975 640.00 + 28590.079 0.998633 8975 731.43 + 28590.079 0.998828 8975 853.33 + 28606.463 0.999023 8984 1024.00 + 28606.463 1.000000 8984 inf +#[Mean = 17942.594, StdDeviation = 5634.925] +#[Max = 28590.080, Total count = 8984] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 13929 requests in 30.03s, 0.89MB read +Requests/sec: 463.84 +Transfer/sec: 30.37KB diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-2con-5000.txt b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-2con-5000.txt new file mode 100644 index 000000000..683606a08 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-2con-5000.txt @@ -0,0 +1,94 @@ +Running 30s test @ http://localhost:8080 + 1 threads and 2 connections + Thread calibration: mean lat.: 5196.393ms, rate sampling interval: 16498ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 17.34s 4.75s 25.53s 58.53% + Req/Sec 821.00 0.00 821.00 100.00% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 17.25s + 75.000% 21.48s + 90.000% 23.95s + 99.000% 25.41s + 99.900% 25.53s + 99.990% 25.53s + 99.999% 25.54s +100.000% 25.54s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 8855.551 0.000000 1 1.00 + 10756.095 0.100000 1654 1.11 + 12476.415 0.200000 3311 1.25 + 14114.815 0.300000 4963 1.43 + 15646.719 0.400000 6617 1.67 + 17252.351 0.500000 8275 2.00 + 18055.167 0.550000 9089 2.22 + 18989.055 0.600000 9925 2.50 + 19791.871 0.650000 10745 2.86 + 20676.607 0.700000 11574 3.33 + 21479.423 0.750000 12410 4.00 + 21921.791 0.775000 12818 4.44 + 22298.623 0.800000 13234 5.00 + 22757.375 0.825000 13637 5.71 + 23166.975 0.850000 14059 6.67 + 23511.039 0.875000 14479 8.00 + 23756.799 0.887500 14676 8.89 + 23953.407 0.900000 14878 10.00 + 24133.631 0.912500 15082 11.43 + 24346.623 0.925000 15303 13.33 + 24526.847 0.937500 15503 16.00 + 24657.919 0.943750 15600 17.78 + 24788.991 0.950000 15720 20.00 + 24854.527 0.956250 15819 22.86 + 24920.063 0.962500 15913 26.67 + 25001.983 0.968750 16012 32.00 + 25051.135 0.971875 16073 35.56 + 25165.823 0.975000 16115 40.00 + 25214.975 0.978125 16178 45.71 + 25247.743 0.981250 16219 53.33 + 25329.663 0.984375 16273 64.00 + 25346.047 0.985938 16295 71.11 + 25378.815 0.987500 16326 80.00 + 25411.583 0.989062 16363 91.43 + 25427.967 0.990625 16387 106.67 + 25444.351 0.992188 16414 128.00 + 25444.351 0.992969 16414 142.22 + 25460.735 0.993750 16437 160.00 + 25460.735 0.994531 16437 182.86 + 25477.119 0.995313 16462 213.33 + 25477.119 0.996094 16462 256.00 + 25493.503 0.996484 16484 284.44 + 25493.503 0.996875 16484 320.00 + 25493.503 0.997266 16484 365.71 + 25509.887 0.997656 16506 426.67 + 25509.887 0.998047 16506 512.00 + 25509.887 0.998242 16506 568.89 + 25509.887 0.998437 16506 640.00 + 25509.887 0.998633 16506 731.43 + 25509.887 0.998828 16506 853.33 + 25526.271 0.999023 16522 1024.00 + 25526.271 0.999121 16522 1137.78 + 25526.271 0.999219 16522 1280.00 + 25526.271 0.999316 16522 1462.86 + 25526.271 0.999414 16522 1706.67 + 25526.271 0.999512 16522 2048.00 + 25526.271 0.999561 16522 2275.56 + 25526.271 0.999609 16522 2560.00 + 25526.271 0.999658 16522 2925.71 + 25526.271 0.999707 16522 3413.33 + 25526.271 0.999756 16522 4096.00 + 25526.271 0.999780 16522 4551.11 + 25526.271 0.999805 16522 5120.00 + 25526.271 0.999829 16522 5851.43 + 25526.271 0.999854 16522 6826.67 + 25526.271 0.999878 16522 8192.00 + 25542.655 0.999890 16524 9102.22 + 25542.655 1.000000 16524 inf +#[Mean = 17336.704, StdDeviation = 4750.625] +#[Max = 25526.272, Total count = 16524] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 22297 requests in 30.00s, 1.43MB read +Requests/sec: 743.33 +Transfer/sec: 48.66KB diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-alloc.html b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-alloc.html new file mode 100644 index 000000000..3dc57ef27 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-alloc.html @@ -0,0 +1,2860 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-cpu.html b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-cpu.html new file mode 100644 index 000000000..cca7273ab --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-cpu.html @@ -0,0 +1,5573 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-lock.html b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-lock.html new file mode 100644 index 000000000..5955eb096 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/put-profile-1th-lock.html @@ -0,0 +1,897 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/report.md b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/report.md new file mode 100644 index 000000000..baae08c51 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/tuzikovalexandr/results/stage5/report.md @@ -0,0 +1,107 @@ +## Начальные сведения +* Виртуалке были даны 4 ядра (будем надеяться, что не помрет) +* Конфигурация кластера: 3 ноды +* Реплицирование по умолчанию (2/3) + +## PUT 5000 30s 1 thread 2 con + +Работающие входные данные остаются с прошлого этапа. Результаты эксперимента в сравнении с прошлым этапом при +1 поток, 2 соединение, 5000 запросов приведены ниже. [put-profile-1th-2con-5000.txt](put-profile-1th-2con-5000.txt) + +``` + было стало + 50.000% 5.12s 17.25s + 75.000% 7.24s 21.48s + 90.000% 7.24s 23.95s + 99.000% 8.55s 25.41s + 99.900% 9.33s 25.53s + 99.990% 9.33s 25.53s + 99.999% 9.33s 25.54s +100.000% 9.33s 25.54s +``` + +Результаты получились не те, которые ожидались. Время обработки вырасло почти в 3 раза. +Но по результатам другого эксперимента, когда входные данные 1 поток, 2 соединение, 10000. Время начинает уменьшаться. +Результаты в файле [put-profile-1th-2con-10000.txt](put-profile-1th-2con-10000.txt). + +Отмечу еще, что при увеличении количества соединений возникали проблемы с тестированием. Все улетало в таймаут. + +## CPU + +[put-profile-1th-cpu.html](put-profile-1th-cpu.html) + +- Добавился ForkJoinWorkerThread.run 16.40%; +- Изменились пропорции некоторые семплов. + +Что-то координальных изменений я не выявил, за исключением того, что добавлась работа у воркеров с CompletableFuture. + +## ALLOC + +[put-profile-1th-alloc.html](put-profile-1th-alloc.html) + +При работе с CompletableFuture появилась новая аллокация ForkJoinWorkerThread.run. + +При работе над 5 этапом я изменил ConsistentHashing. На профиле видно, что в сравнении с предыдущим этапом аллокаций +стало меньше. Но тут так получилось из-за базового количества ack и from. Получалось, что не срабатывал итератор. А +так аллокаций должно было быть даже больше. + +В остальном ничего другого не заметил. Хотя, как мне казалось, должна была быть аллокация на дефолтный экзекьютор, так +как сам я их не создавал. Но этого не нашел. + +## LOCK + +[put-profile-1th-lock.html](put-profile-1th-lock.html) + +- ThreadPoolExecutor.runWorker изменился 3.25% до 37.07%; +- ThreadPoolExecutor.getTask 1.98%. Вырос не так сильно; +- CompletableFuture$AsyncSupply.run 10.62%. На 3 этапе занимал всего 0.36%; +- SequentialScheduler$SchedulableTask.run занимает больше всего 21.07%. Вырос в сравнении с предыдущими этапами. +На 3 этапе было всего 0.58%. + +Такие изменения связаны с тем, что добавилось много асинхронного работы. + +## GET 5000 30s 1 thread 2 con + +Аналогично, как и с PUT запросами входные данные остались те же, только количество соединений увеличил до 2. +Однако, в отличии от PUT запросов время стало лучше. + +``` + было стало + 50.000% 19.96s 14.93s + 75.000% 24.76s 17.96s + 90.000% 27.25s 23.90s + 99.000% 28.48s 26.10s + 99.900% 28.56s 26.33s + 99.990% 28.57s 26.35s + 99.999% 28.57s 26.35s +100.000% 28.57s 26.35s +``` + +## CPU + +[get-profile-1th-cpu.html](get-profile-1th-cpu.html) + +Аналогично, как и в случае с PUT запросами. Только немного другие пропорции. + +## ALLOC + +[get-profile-1th-alloc.html](get-profile-1th-alloc.html) + +Аналогично, как и в случае с PUT запросами в том числе и ConsistentHashing. + +## LOCK + +[get-profile-1th-lock.html](get-profile-1th-lock.html) + +Аналогично, как и в случае с PUT запросами. Только немного другие пропорции. + +## Выводы + +- При добавлении логики асинхронной работы с CompletableFuture, соответсвенно выросла и трата ресуросв на работу с ними. +Соответсвенно, самыми показательными были LOCK фреймы. + +## Улучшения + +- Предложений нет. + +P.S. Снова спасибо большое виртуалке, что за время написания отчета и проведения экспериментов она ниразу не умерла. \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/DaoHttpServer.java b/src/main/java/ru/vk/itmo/test/vadimershov/DaoHttpServer.java index 827c8c7e9..d8d2555b5 100644 --- a/src/main/java/ru/vk/itmo/test/vadimershov/DaoHttpServer.java +++ b/src/main/java/ru/vk/itmo/test/vadimershov/DaoHttpServer.java @@ -1,5 +1,6 @@ package ru.vk.itmo.test.vadimershov; +import one.nio.http.Header; import one.nio.http.HttpServer; import one.nio.http.HttpServerConfig; import one.nio.http.HttpSession; @@ -14,6 +15,7 @@ import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.dao.Config; import ru.vk.itmo.test.vadimershov.exceptions.DaoException; +import ru.vk.itmo.test.vadimershov.exceptions.FailedSharding; import ru.vk.itmo.test.vadimershov.exceptions.NotFoundException; import ru.vk.itmo.test.vadimershov.exceptions.RemoteServiceException; @@ -39,6 +41,7 @@ public DaoHttpServer( Config daoConfig ) throws IOException { this(config, daoConfig, new RequestThreadExecutor.Config()); + } public DaoHttpServer( @@ -86,6 +89,9 @@ public void handleRequest(Request request, HttpSession session) { } catch (RemoteServiceException e) { logger.error("Exception in remote service: {}", e.getUrl(), e); sessionSendResponse(session, e.getHttpCode()); + } catch (FailedSharding e) { + logger.error("Exception sharding service: {}", e.getMessage(), e); + sessionSendResponse(session, e.getHttpCode()); } catch (Exception e) { logger.error("Exception from one nio handle", e); sessionSendResponse(session, DaoResponse.BAD_REQUEST); @@ -93,7 +99,7 @@ public void handleRequest(Request request, HttpSession session) { }); } catch (RejectedExecutionException e) { logger.error(e.getMessage()); - sessionSendResponse(session, DaoResponse.TOO_MANY_REQUESTS); + sessionSendResponse(session, DaoResponse.NOT_ENOUGH_REPLICAS); } } @@ -115,43 +121,69 @@ public synchronized void stop() { @Path("/v0/entity") @RequestMethod(Request.METHOD_GET) public Response getMapping( - @Param(value = "id", required = true) String id - ) throws DaoException, RemoteServiceException { + @Param(value = "id", required = true) String id, + @Param(value = "ack") Integer ack, + @Param(value = "from") Integer from, + @Header(value = "X-inner") boolean inner + ) throws DaoException, RemoteServiceException, NotFoundException { if (id.isBlank()) { return DaoResponse.empty(DaoResponse.BAD_REQUEST); } - byte[] value; - try { - value = dao.get(id); - } catch (NotFoundException e) { - return DaoResponse.empty(DaoResponse.NOT_FOUND); + + ResultResponse response; + if (inner) { + response = dao.get(id); + } else { + response = dao.get(id, ack, from); + } + + if (response.httpCode() == 404) { + return DaoResponse.empty(DaoResponse.NOT_FOUND, response.timestamp()); } - return DaoResponse.ok(value); + return DaoResponse.ok(response.value(), response.timestamp()); } @Path("/v0/entity") @RequestMethod(Request.METHOD_PUT) public Response upsertMapping( @Param(value = "id", required = true) String id, + @Param(value = "ack") Integer ack, + @Param(value = "from") Integer from, + @Header(value = "X-inner") boolean inner, + @Header(value = "X-timestamp") Long timestamp, Request request ) throws DaoException, RemoteServiceException { if (id.isBlank() || request.getBody() == null) { return DaoResponse.empty(DaoResponse.BAD_REQUEST); } - dao.upsert(id, request.getBody()); + if (inner) { + dao.upsert(id, request.getBody(), timestamp); + } else { + dao.upsert(id, request.getBody(), ack, from); + } + return DaoResponse.empty(DaoResponse.CREATED); } @Path("/v0/entity") @RequestMethod(Request.METHOD_DELETE) public Response deleteMapping( - @Param(value = "id", required = true) String id + @Param(value = "id", required = true) String id, + @Param(value = "ack") Integer ack, + @Param(value = "from") Integer from, + @Header(value = "X-inner") boolean inner, + @Header(value = "X-timestamp") Long timestamp ) throws DaoException, RemoteServiceException { if (id.isBlank()) { return DaoResponse.empty(DaoResponse.BAD_REQUEST); } - dao.delete(id); + + if (inner) { + dao.delete(id, timestamp); + } else { + dao.delete(id, ack, from); + } return DaoResponse.empty(DaoResponse.ACCEPTED); } diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/DaoResponse.java b/src/main/java/ru/vk/itmo/test/vadimershov/DaoResponse.java index 3e0d53e9b..0f986fec6 100644 --- a/src/main/java/ru/vk/itmo/test/vadimershov/DaoResponse.java +++ b/src/main/java/ru/vk/itmo/test/vadimershov/DaoResponse.java @@ -5,17 +5,33 @@ public class DaoResponse extends Response { public static final String TOO_MANY_REQUESTS = "429 Too Many Requests"; + public static final String NOT_ENOUGH_REPLICAS = "504 Not Enough Replicas"; + + public static final String X_TIMESTAMP = "X-timestamp: "; public DaoResponse(String resultCode, byte[] body) { super(resultCode, body); } + public DaoResponse(String resultCode, byte[] body, long timestamp) { + super(resultCode, body); + this.addHeader(X_TIMESTAMP + timestamp); + } + public static DaoResponse ok(byte[] body) { return new DaoResponse(OK, body); } + public static DaoResponse ok(byte[] body, long timestamp) { + return new DaoResponse(OK, body, timestamp); + } + public static DaoResponse empty(String code) { return new DaoResponse(code, DaoResponse.EMPTY); } + public static DaoResponse empty(String code, long timestamp) { + return new DaoResponse(code, DaoResponse.EMPTY, timestamp); + } + } diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/Pair.java b/src/main/java/ru/vk/itmo/test/vadimershov/Pair.java new file mode 100644 index 000000000..ddd8c0a5a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/Pair.java @@ -0,0 +1,3 @@ +package ru.vk.itmo.test.vadimershov; + +public record Pair(F first, S second) { } diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/ResultResponse.java b/src/main/java/ru/vk/itmo/test/vadimershov/ResultResponse.java new file mode 100644 index 000000000..fc2f77fd9 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/ResultResponse.java @@ -0,0 +1,3 @@ +package ru.vk.itmo.test.vadimershov; + +public record ResultResponse(long httpCode, byte[] value, Long timestamp) { } diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/ServiceImpl.java b/src/main/java/ru/vk/itmo/test/vadimershov/ServiceImpl.java index c9836dee5..17efc7a6e 100644 --- a/src/main/java/ru/vk/itmo/test/vadimershov/ServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/vadimershov/ServiceImpl.java @@ -44,7 +44,7 @@ public synchronized CompletableFuture stop() throws IOException { return CompletableFuture.completedFuture(null); } - @ServiceFactory(stage = 3) + @ServiceFactory(stage = 5) public static class Factory implements ServiceFactory.Factory { @Override public Service create(ServiceConfig config) { diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/ShardingDao.java b/src/main/java/ru/vk/itmo/test/vadimershov/ShardingDao.java index 6d4f77fd8..75c352132 100644 --- a/src/main/java/ru/vk/itmo/test/vadimershov/ShardingDao.java +++ b/src/main/java/ru/vk/itmo/test/vadimershov/ShardingDao.java @@ -1,16 +1,12 @@ package ru.vk.itmo.test.vadimershov; -import one.nio.http.HttpException; -import one.nio.http.Response; -import one.nio.pool.PoolException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.dao.Config; import ru.vk.itmo.dao.Dao; -import ru.vk.itmo.dao.Entry; -import ru.vk.itmo.test.reference.dao.ReferenceDao; +import ru.vk.itmo.test.vadimershov.dao.ReferenceDao; +import ru.vk.itmo.test.vadimershov.dao.TimestampEntry; import ru.vk.itmo.test.vadimershov.exceptions.DaoException; +import ru.vk.itmo.test.vadimershov.exceptions.FailedSharding; import ru.vk.itmo.test.vadimershov.exceptions.NotFoundException; import ru.vk.itmo.test.vadimershov.exceptions.RemoteServiceException; import ru.vk.itmo.test.vadimershov.hash.ConsistentHashing; @@ -19,109 +15,150 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.lang.foreign.MemorySegment; - -import static java.lang.StringTemplate.STR; -import static ru.vk.itmo.test.vadimershov.utils.MemorySegmentUtil.toByteArray; -import static ru.vk.itmo.test.vadimershov.utils.MemorySegmentUtil.toDeletedEntity; -import static ru.vk.itmo.test.vadimershov.utils.MemorySegmentUtil.toEntity; -import static ru.vk.itmo.test.vadimershov.utils.MemorySegmentUtil.toMemorySegment; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; public class ShardingDao { - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - public static final String ENTITY_URI = "/v0/entity?id="; + private final int nodeCount; + private final int nodeQuorum; - private final String selfUrl; - private final Dao> localDao; + private final Dao> localDao; private final ConsistentHashing consistentHashing; public ShardingDao(ServiceConfig serviceConfig, Config daoConfig) throws IOException { - this.selfUrl = serviceConfig.selfUrl(); + this.nodeCount = serviceConfig.clusterUrls().size(); + this.nodeQuorum = (serviceConfig.clusterUrls().size() / 2) + 1; this.localDao = new ReferenceDao(daoConfig); - this.consistentHashing = new ConsistentHashing(serviceConfig.clusterUrls()); + this.consistentHashing = new ConsistentHashing( + serviceConfig.selfUrl(), + serviceConfig.clusterUrls(), + this.localDao + ); + } + + public ResultResponse get(String key) throws NotFoundException, DaoException { + return consistentHashing.getLocalNode().get(key).join(); } - public byte[] get(String key) throws NotFoundException, DaoException, RemoteServiceException { - VirtualNode virtualNode = consistentHashing.findVNode(key); - if (virtualNode.url().equals(selfUrl)) { - Entry entry; - try { - entry = localDao.get(toMemorySegment(key)); - } catch (Exception e) { - logger.error("Can't get value by key={}", key, e); - throw new DaoException("Can't get value from local dao", e); - } - if (entry == null) { - throw new NotFoundException(); - } - return toByteArray(entry.value()); + public ResultResponse get( + String key, + Integer ack, + Integer from + ) throws NotFoundException, DaoException, RemoteServiceException, FailedSharding { + int correctAck = validate(ack, nodeQuorum); + int correctFrom = validate(from, nodeCount); + + Collection virtualNodes = consistentHashing.findVNodes(key, correctFrom); + List> requestsFutures = new ArrayList<>(); + for (var node : virtualNodes) { + requestsFutures.add(node.get(key)); } - Response response; try { - response = virtualNode.httpClient().get(ENTITY_URI + key); - } catch (InterruptedException e) { - logger.error("Can't get with key={} in remote node url={}", key, virtualNode.url(), e); - Thread.currentThread().interrupt(); - throw new DaoException("Can't get value from remote node", e); - } catch (PoolException | IOException | HttpException e) { - logger.error("Can't get with key={} in remote node url={}", key, virtualNode.url(), e); - throw new DaoException("Can't get value from remote node", e); + return waitFuture(correctAck, correctFrom, requestsFutures); + } catch (CompletionException e) { + throw (FailedSharding) new FailedSharding().initCause(e); } - checkCodeInRemoteResp(virtualNode.url(), response); - return response.getBody(); } - public void upsert(String key, byte[] value) throws DaoException, RemoteServiceException { - VirtualNode virtualNode = consistentHashing.findVNode(key); - if (virtualNode.url().equals(selfUrl)) { - try { - localDao.upsert(toEntity(key, value)); - return; - } catch (Exception e) { - logger.error("Can't upsert value by key={}", key, e); - throw new DaoException("Can't upsert value in local dao", e); - } + @SuppressWarnings("FutureReturnValueIgnored") + private ResultResponse waitFuture( + int ack, + int from, + List> requestsFutures + ) { + CompletableFuture waitQuorumFuture = new CompletableFuture<>(); + AtomicInteger countFailures = new AtomicInteger(from - ack + 1); + AtomicInteger countAcks = new AtomicInteger(ack); + final AtomicReference response = new AtomicReference<>(new ResultResponse(-1, null, -1L)); + + for (CompletableFuture requestFuture : requestsFutures) { + requestFuture.whenComplete((resultResponse, throwable) -> { + boolean positiveResponse = throwable == null && resultResponse.timestamp() >= 0; + if (positiveResponse) { + countAcks.decrementAndGet(); + if (response.get().timestamp() < resultResponse.timestamp()) { + response.set(resultResponse); + } + } else { + countFailures.decrementAndGet(); + } + + if (countAcks.get() <= 0) { + waitQuorumFuture.complete(response.get()); + } else if (countFailures.get() <= 0) { + waitQuorumFuture.completeExceptionally(new FailedSharding()); + } + }); + } + return waitQuorumFuture.join(); + } + + public void upsert( + String key, + byte[] value, + Long timestamp + ) throws DaoException, RemoteServiceException, FailedSharding { + consistentHashing.getLocalNode().upsert(key, value, timestamp) + .join(); + } + + public void upsert( + String key, + byte[] value, + Integer ack, + Integer from + ) throws DaoException, RemoteServiceException { + int correctAck = validate(ack, nodeQuorum); + int correctFrom = validate(from, nodeCount); + + Collection virtualNodes = consistentHashing.findVNodes(key, correctFrom); + List> requestsFutures = new ArrayList<>(); + + long timestamp = System.currentTimeMillis(); + for (var node : virtualNodes) { + requestsFutures.add(node.upsert(key, value, timestamp)); } - Response response; try { - response = virtualNode.httpClient().put(ENTITY_URI + key, value); - } catch (InterruptedException e) { - logger.error("InterruptedException upsert by key={} in remote node url={}", key, virtualNode.url(), e); - Thread.currentThread().interrupt(); - throw new DaoException("Can't upsert value in remote node", e); - } catch (PoolException | IOException | HttpException e) { - logger.error("Exception upsert by key={} in service url={}", key, virtualNode.url(), e); - throw new DaoException("Can't upsert value in remote node", e); + waitFuture(correctAck, correctFrom, requestsFutures); + } catch (CompletionException e) { + throw (FailedSharding) new FailedSharding().initCause(e); } - checkCodeInRemoteResp(virtualNode.url(), response); } - public void delete(String key) throws DaoException, RemoteServiceException { - VirtualNode virtualNode = consistentHashing.findVNode(key); - if (virtualNode.url().equals(selfUrl)) { - try { - localDao.upsert(toDeletedEntity(key)); - return; - } catch (Exception e) { - logger.error("Can't delete by key={}", key, e); - throw new DaoException("Can't delete value in local dao", e); - } + public void delete(String key, Long timestamp) throws DaoException, RemoteServiceException { + consistentHashing.getLocalNode().delete(key, timestamp) + .join(); + } + + public void delete( + String key, + Integer ack, + Integer from + ) throws DaoException, RemoteServiceException, FailedSharding { + int correctAck = validate(ack, nodeQuorum); + int correctFrom = validate(from, nodeCount); + + Collection virtualNodes = consistentHashing.findVNodes(key, correctFrom); + List> requestsFutures = new ArrayList<>(); + + long timestamp = System.currentTimeMillis(); + for (var node : virtualNodes) { + requestsFutures.add(node.delete(key, timestamp)); } - Response response; try { - response = virtualNode.httpClient().delete(ENTITY_URI + key); - } catch (InterruptedException e) { - logger.error("InterruptedException delete by key={} in service url={}", key, virtualNode.url(), e); - Thread.currentThread().interrupt(); - throw new DaoException("Can't delete value in remote node", e); - } catch (PoolException | IOException | HttpException e) { - logger.error("Exception delete by key={} in service url={}", key, virtualNode.url(), e); - throw new DaoException("Can't delete value in remote node", e); + waitFuture(correctAck, correctFrom, requestsFutures); + } catch (CompletionException e) { + throw (FailedSharding) new FailedSharding().initCause(e); } - checkCodeInRemoteResp(virtualNode.url(), response); } public void close() { @@ -133,16 +170,13 @@ public void close() { this.consistentHashing.close(); } - private void checkCodeInRemoteResp(String url, Response response) throws RemoteServiceException { - switch (response.getStatus()) { - case 200, 201, 202 -> { /* correct http code */ } - case 400 -> throw new RemoteServiceException(DaoResponse.BAD_REQUEST, url); - case 404 -> throw new RemoteServiceException(DaoResponse.NOT_FOUND, url); - case 405 -> throw new RemoteServiceException(DaoResponse.METHOD_NOT_ALLOWED, url); - case 429 -> throw new RemoteServiceException(DaoResponse.TOO_MANY_REQUESTS, url); - case 503 -> throw new RemoteServiceException(DaoResponse.SERVICE_UNAVAILABLE, url); - default -> throw new RemoteServiceException(DaoResponse.INTERNAL_ERROR, url); + private int validate(Integer value, Integer defaultValue) { + int correctValue = value == null ? defaultValue : value; + + if (correctValue > nodeCount || correctValue <= 0) { + throw new FailedSharding(DaoResponse.BAD_REQUEST); } + return correctValue; } } diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/ByteArraySegment.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/ByteArraySegment.java new file mode 100644 index 000000000..cc3df0acf --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/ByteArraySegment.java @@ -0,0 +1,48 @@ +package ru.vk.itmo.test.vadimershov.dao; + +import java.io.IOException; +import java.lang.foreign.MemorySegment; +import java.nio.ByteBuffer; + +/** + * Growable buffer with {@link ByteBuffer} and {@link MemorySegment} interface. + * + * @author incubos + */ +final class ByteArraySegment { + private byte[] array; + private MemorySegment segment; + + ByteArraySegment(final int capacity) { + this.array = new byte[capacity]; + this.segment = MemorySegment.ofArray(array); + } + + void withArray(final ArrayConsumer consumer) throws IOException { + consumer.process(array); + } + + MemorySegment segment() { + return segment; + } + + void ensureCapacity(final long size) { + if (size > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Too big!"); + } + + final int capacity = (int) size; + if (array.length >= capacity) { + return; + } + + // Grow to the nearest bigger power of 2 + final int newSize = Integer.highestOneBit(capacity) << 1; + array = new byte[newSize]; + segment = MemorySegment.ofArray(array); + } + + interface ArrayConsumer { + void process(byte[] array) throws IOException; + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/LiveFilteringIterator.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/LiveFilteringIterator.java new file mode 100644 index 000000000..76726a841 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/LiveFilteringIterator.java @@ -0,0 +1,52 @@ +package ru.vk.itmo.test.vadimershov.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. + * + * @author incubos + */ +final class LiveFilteringIterator implements Iterator> { + private final Iterator> delegate; + private TimestampEntry next; + + LiveFilteringIterator(final Iterator> delegate) { + this.delegate = delegate; + skipTombstones(); + } + + private void skipTombstones() { + while (delegate.hasNext()) { + final TimestampEntry entry = delegate.next(); + if (entry.value() != null) { + this.next = entry; + break; + } + } + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public TimestampEntry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + // Consume + final TimestampEntry result = next; + next = null; + + skipTombstones(); + + return result; + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/MemTable.java new file mode 100644 index 000000000..002f73f98 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/MemTable.java @@ -0,0 +1,47 @@ +package ru.vk.itmo.test.vadimershov.dao; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; +import java.util.NavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; + +/** + * Memory table. + * + * @author incubos + */ +final class MemTable { + private final NavigableMap> map = + new ConcurrentSkipListMap<>( + MemorySegmentComparator.INSTANCE); + + boolean isEmpty() { + return map.isEmpty(); + } + + Iterator> get( + final MemorySegment from, + final MemorySegment to) { + if (from == null && to == null) { + // All + return map.values().iterator(); + } else if (from == null) { + // Head + return map.headMap(to).values().iterator(); + } else if (to == null) { + // Tail + return map.tailMap(from).values().iterator(); + } else { + // Slice + return map.subMap(from, to).values().iterator(); + } + } + + TimestampEntry get(final MemorySegment key) { + return map.get(key); + } + + TimestampEntry upsert(final TimestampEntry entry) { + return map.put(entry.key(), entry); + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/MemorySegmentComparator.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/MemorySegmentComparator.java new file mode 100644 index 000000000..a91ac4492 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/MemorySegmentComparator.java @@ -0,0 +1,89 @@ +package ru.vk.itmo.test.vadimershov.dao; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.Comparator; + +/** + * Compares {@link MemorySegment}s. + * + * @author incubos + */ +final class MemorySegmentComparator implements Comparator { + static final Comparator INSTANCE = + new MemorySegmentComparator(); + + private MemorySegmentComparator() { + // Singleton + } + + @Override + public int compare( + final MemorySegment left, + final MemorySegment right) { + final long mismatch = left.mismatch(right); + if (mismatch == -1L) { + // No mismatch + return 0; + } + + if (mismatch == left.byteSize()) { + // left is prefix of right, so left is smaller + return -1; + } + + if (mismatch == right.byteSize()) { + // right is prefix of left, so left is greater + return 1; + } + + // Compare mismatched bytes as unsigned + return Byte.compareUnsigned( + left.getAtIndex( + ValueLayout.OfByte.JAVA_BYTE, + mismatch), + right.getAtIndex( + ValueLayout.OfByte.JAVA_BYTE, + mismatch)); + } + + static int compare( + final MemorySegment srcSegment, + final long srcFromOffset, + final long srcLength, + final MemorySegment dstSegment, + final long dstFromOffset, + final long dstLength) { + final long mismatch = + MemorySegment.mismatch( + srcSegment, + srcFromOffset, + srcFromOffset + srcLength, + dstSegment, + dstFromOffset, + dstFromOffset + dstLength); + if (mismatch == -1L) { + // No mismatch + return 0; + } + + if (mismatch == srcLength) { + // left is prefix of right, so left is smaller + return -1; + } + + if (mismatch == dstLength) { + // right is prefix of left, so left is greater + return 1; + } + + // Compare mismatched bytes as unsigned + return Byte.compareUnsigned( + srcSegment.getAtIndex( + ValueLayout.OfByte.JAVA_BYTE, + srcFromOffset + mismatch), + dstSegment.getAtIndex( + ValueLayout.OfByte.JAVA_BYTE, + dstFromOffset + mismatch)); + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/MergingEntryIterator.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/MergingEntryIterator.java new file mode 100644 index 000000000..3489ee360 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/MergingEntryIterator.java @@ -0,0 +1,70 @@ +package ru.vk.itmo.test.vadimershov.dao; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.PriorityQueue; +import java.util.Queue; + +/** + * Merges entry {@link Iterator}s. + * + * @author incubos + */ +final class MergingEntryIterator implements Iterator> { + private final Queue iterators; + + MergingEntryIterator(final List iterators) { + assert iterators.stream().allMatch(WeightedPeekingEntryIterator::hasNext); + + this.iterators = new PriorityQueue<>(iterators); + } + + @Override + public boolean hasNext() { + return !iterators.isEmpty(); + } + + @Override + public TimestampEntry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final WeightedPeekingEntryIterator top = iterators.remove(); + final TimestampEntry result = top.next(); + + if (top.hasNext()) { + // Not exhausted + iterators.add(top); + } + + // Remove older versions of the key + while (true) { + final WeightedPeekingEntryIterator iterator = iterators.peek(); + if (iterator == null) { + // Nothing left + break; + } + + // Skip entries with the same key + final TimestampEntry entry = iterator.peek(); + if (MemorySegmentComparator.INSTANCE.compare(result.key(), entry.key()) != 0) { + // Reached another key + break; + } + + // Drop + iterators.remove(); + // Skip + iterator.next(); + if (iterator.hasNext()) { + // Not exhausted + iterators.add(iterator); + } + } + + return result; + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/ReferenceDao.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/ReferenceDao.java new file mode 100644 index 000000000..d908cd826 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/ReferenceDao.java @@ -0,0 +1,287 @@ +package ru.vk.itmo.test.vadimershov.dao; + +import ru.vk.itmo.dao.Config; +import ru.vk.itmo.dao.Dao; + +import java.io.IOException; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Reference implementation of {@link Dao}. + * + * @author incubos + */ +public class ReferenceDao implements Dao> { + private final Config config; + private final Arena arena; + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + // Guarded by lock + private volatile TableSet tableSet; + + private final ExecutorService flusher = + Executors.newSingleThreadExecutor(r -> { + final Thread result = new Thread(r); + result.setName("flusher"); + return result; + }); + private final ExecutorService compactor = + Executors.newSingleThreadExecutor(r -> { + final Thread result = new Thread(r); + result.setName("compactor"); + return result; + }); + + private final AtomicBoolean closed = new AtomicBoolean(); + + public ReferenceDao(final Config config) throws IOException { + this.config = config; + this.arena = Arena.ofShared(); + + // First complete promotion of compacted SSTables + SSTables.promote( + config.basePath(), + 0, + 1); + + this.tableSet = + TableSet.from( + SSTables.discover( + arena, + config.basePath())); + } + + @Override + public Iterator> get( + final MemorySegment from, + final MemorySegment to) { + return new LiveFilteringIterator(tableSet.get(from, to)); + } + + @Override + public TimestampEntry get(final MemorySegment key) { + // Without lock, just snapshot of table set + return tableSet.get(key); + } + + @Override + public void upsert(final TimestampEntry entry) { + final boolean autoFlush; + lock.readLock().lock(); + try { + if (tableSet.memTableSize.get() > config.flushThresholdBytes() + && tableSet.flushingTable != null) { + throw new IllegalStateException("Can't keep up with flushing!"); + } + + // Upsert + final TimestampEntry previous = tableSet.upsert(entry); + + // Update size estimate + final long size = tableSet.memTableSize.addAndGet(sizeOf(entry) - sizeOf(previous)); + autoFlush = size > config.flushThresholdBytes(); + } finally { + lock.readLock().unlock(); + } + + if (autoFlush) { + initiateFlush(true); + } + } + + private static long sizeOf(final TimestampEntry entry) { + if (entry == null) { + return 0L; + } + + if (entry.value() == null) { + return entry.key().byteSize() + Long.BYTES; + } + + return entry.key().byteSize() + entry.value().byteSize() + Long.BYTES; + } + + private void initiateFlush(final boolean auto) { + flusher.submit(() -> { + final TableSet currentTableSet; + lock.writeLock().lock(); + try { + if (this.tableSet.memTable.isEmpty()) { + // Nothing to flush + return; + } + + if (auto && this.tableSet.memTableSize.get() < config.flushThresholdBytes()) { + // Not enough data to flush + return; + } + + // Switch memTable to flushing + currentTableSet = this.tableSet.flushing(); + this.tableSet = currentTableSet; + } finally { + lock.writeLock().unlock(); + } + + // Write + final int sequence = currentTableSet.nextSequence(); + try { + new SSTableWriter() + .write( + config.basePath(), + sequence, + currentTableSet.flushingTable.get(null, null)); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-1); + return; + } + + // Open + final SSTable flushed; + try { + flushed = SSTables.open( + arena, + config.basePath(), + sequence); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-2); + return; + } + + // Switch + lock.writeLock().lock(); + try { + this.tableSet = this.tableSet.flushed(flushed); + } finally { + lock.writeLock().unlock(); + } + }).state(); + } + + @Override + public void flush() throws IOException { + initiateFlush(false); + } + + @Override + public void compact() throws IOException { + compactor.submit(() -> { + final TableSet currentTableSet; + lock.writeLock().lock(); + try { + currentTableSet = this.tableSet; + if (currentTableSet.ssTables.size() < 2) { + // Nothing to compact + return; + } + } finally { + lock.writeLock().unlock(); + } + + // Compact to 0 + try { + new SSTableWriter() + .write( + config.basePath(), + 0, + new LiveFilteringIterator(currentTableSet.allSSTableEntries())); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-3); + } + + // Open 0 + final SSTable compacted; + try { + compacted = + SSTables.open( + arena, + config.basePath(), + 0); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-4); + return; + } + + // Replace old SSTables with compacted one to + // keep serving requests + final Set replaced = new HashSet<>(currentTableSet.ssTables); + lock.writeLock().lock(); + try { + this.tableSet = + this.tableSet.compacted( + replaced, + compacted); + } finally { + lock.writeLock().unlock(); + } + + // Remove compacted SSTables starting from the oldest ones. + // If we crash, 0 contains all the data, and + // it will be promoted on reopen. + for (final SSTable ssTable : currentTableSet.ssTables.reversed()) { + try { + SSTables.remove( + config.basePath(), + ssTable.sequence); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-5); + } + } + + // Promote zero to one (possibly replacing) + try { + SSTables.promote( + config.basePath(), + 0, + 1); + } catch (IOException e) { + e.printStackTrace(); + Runtime.getRuntime().halt(-6); + } + + // Replace promoted SSTable + lock.writeLock().lock(); + try { + this.tableSet = + this.tableSet.compacted( + Collections.singleton(compacted), + compacted.withSequence(1)); + } finally { + lock.writeLock().unlock(); + } + }).state(); + } + + @Override + public void close() throws IOException { + if (closed.getAndSet(true)) { + // Already closed + return; + } + + // Maybe flush + flush(); + + // Stop all the threads + flusher.close(); + compactor.close(); + + // Close arena + arena.close(); + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/SSTable.java new file mode 100644 index 000000000..963c83d19 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/SSTable.java @@ -0,0 +1,208 @@ +package ru.vk.itmo.test.vadimershov.dao; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Persistent SSTable in data file and index file. + * + * @author incubos + * @see SSTables + */ +final class SSTable { + final int sequence; + + private final MemorySegment index; + private final MemorySegment data; + private final long size; + + SSTable( + final int sequence, + final MemorySegment index, + final MemorySegment data) { + this.sequence = sequence; + this.index = index; + this.data = data; + this.size = index.byteSize() / Long.BYTES; + } + + SSTable withSequence(final int sequence) { + return new SSTable( + sequence, + index, + data); + } + + /** + * Returns index of the entry if found; otherwise, (-(insertion point) - 1). + * The insertion point is defined as the point at which the key would be inserted: + * the index of the first element greater than the key, + * or size if all keys are less than the specified key. + * Note that this guarantees that the return value will be >= 0 + * if and only if the key is found. + */ + private long entryBinarySearch(final MemorySegment key) { + long low = 0L; + long high = size - 1; + + while (low <= high) { + final long mid = (low + high) >>> 1; + final long midEntryOffset = entryOffset(mid); + final long midKeyLength = getLength(midEntryOffset); + final int compare = + MemorySegmentComparator.compare( + data, + midEntryOffset + Long.BYTES, // Position at key + midKeyLength, + key, + 0L, + key.byteSize()); + + if (compare < 0) { + low = mid + 1; + } else if (compare > 0) { + high = mid - 1; + } else { + return mid; + } + } + + return -(low + 1); + } + + private long entryOffset(final long entry) { + return index.get( + ValueLayout.OfLong.JAVA_LONG, + entry * Long.BYTES); + } + + private long getLength(final long offset) { + return data.get( + ValueLayout.OfLong.JAVA_LONG_UNALIGNED, + offset); + } + + private long getTimestamp(final long offset) { + return getLength(offset); + } + + Iterator> get( + final MemorySegment from, + final MemorySegment to) { + assert from == null || to == null || MemorySegmentComparator.INSTANCE.compare(from, to) <= 0; + + // Slice of SSTable in absolute offsets + final long fromOffset; + final long toOffset; + + // Left offset bound + if (from == null) { + // Start from the beginning + fromOffset = 0L; + } else { + final long fromEntry = entryBinarySearch(from); + if (fromEntry >= 0L) { + fromOffset = entryOffset(fromEntry); + } else if (-fromEntry - 1 == size) { + // No relevant data + return Collections.emptyIterator(); + } else { + // Greater but existing key found + fromOffset = entryOffset(-fromEntry - 1); + } + } + + // Right offset bound + if (to == null) { + // Up to the end + toOffset = data.byteSize(); + } else { + final long toEntry = entryBinarySearch(to); + if (toEntry >= 0L) { + toOffset = entryOffset(toEntry); + } else if (-toEntry - 1 == size) { + // Up to the end + toOffset = data.byteSize(); + } else { + // Greater but existing key found + toOffset = entryOffset(-toEntry - 1); + } + } + + return new SliceIterator(fromOffset, toOffset); + } + + TimestampEntry get(final MemorySegment key) { + final long entry = entryBinarySearch(key); + if (entry < 0) { + return null; + } + + // Skip key (will reuse the argument) + long offset = entryOffset(entry); + offset += Long.BYTES + key.byteSize(); + + // Extract value length + final long valueLength = getLength(offset); + offset += Long.BYTES; + + if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { + // Tombstone encountered + return new TimestampBaseEntry<>(key, null, getTimestamp(offset)); + } else { + // Get value + final MemorySegment value = data.asSlice(offset, valueLength); + offset += valueLength; + return new TimestampBaseEntry<>(key, value, getTimestamp(offset)); + } + } + + private final class SliceIterator implements Iterator> { + private long offset; + private final long toOffset; + + private SliceIterator( + final long offset, + final long toOffset) { + this.offset = offset; + this.toOffset = toOffset; + } + + @Override + public boolean hasNext() { + return offset < toOffset; + } + + @Override + public TimestampEntry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + // Read key length + final long keyLength = getLength(offset); + offset += Long.BYTES; + + // Read key + final MemorySegment key = data.asSlice(offset, keyLength); + offset += keyLength; + + // Read value length + final long valueLength = getLength(offset); + offset += Long.BYTES; + + // Read value + if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { + // Tombstone encountered + return new TimestampBaseEntry<>(key, null, getTimestamp(offset)); + } else { + final MemorySegment value = data.asSlice(offset, valueLength); + offset += valueLength; + return new TimestampBaseEntry<>(key, value, getTimestamp(offset)); + } + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/SSTableWriter.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/SSTableWriter.java new file mode 100644 index 000000000..5620c72b5 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/SSTableWriter.java @@ -0,0 +1,170 @@ +package ru.vk.itmo.test.vadimershov.dao; + +import ru.vk.itmo.dao.Entry; + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Iterator; + +/** + * Writes {@link Entry} {@link Iterator} to SSTable on disk. + * + *

Index file {@code .index} contains {@code long} offsets to entries in data file: + * {@code [offset0, offset1, ...]} + * + *

Data file {@code .data} contains serialized entries: + * {@code } + * + *

Tombstones are encoded as {@code valueLength} {@code -1} and no subsequent value. + * + * @author incubos + */ +final class SSTableWriter { + private static final int BUFFER_SIZE = 64 * 1024; + + // Reusable buffers to eliminate allocations. + // But excessive memory copying is still there :( + // Long cell + private final ByteArraySegment longBuffer = new ByteArraySegment(Long.BYTES); + // Growable blob cell + private final ByteArraySegment blobBuffer = new ByteArraySegment(512); + + void write( + final Path baseDir, + final int sequence, + final Iterator> entries) throws IOException { + // Write to temporary files + final Path tempIndexName = SSTables.tempIndexName(baseDir, sequence); + final Path tempDataName = SSTables.tempDataName(baseDir, sequence); + + // Delete temporary files to eliminate tails + Files.deleteIfExists(tempIndexName); + Files.deleteIfExists(tempDataName); + + // Iterate in a single pass! + // Will write through FileChannel despite extra memory copying and + // no buffering (which may be implemented later). + // Looking forward to MemorySegment facilities in FileChannel! + try (OutputStream index = + new BufferedOutputStream( + new FileOutputStream( + tempIndexName.toFile()), + BUFFER_SIZE); + OutputStream data = + new BufferedOutputStream( + new FileOutputStream( + tempDataName.toFile()), + BUFFER_SIZE)) { + long entryOffset = 0L; + + // Iterate and serialize + while (entries.hasNext()) { + // First write offset to the entry + writeLong(entryOffset, index); + + // Then write the entry + final TimestampEntry entry = entries.next(); + entryOffset += writeEntry(entry, data); + } + } + + // Publish files atomically + // FIRST index, LAST data + final Path indexName = + SSTables.indexName( + baseDir, + sequence); + Files.move( + tempIndexName, + indexName, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + final Path dataName = + SSTables.dataName( + baseDir, + sequence); + Files.move( + tempDataName, + dataName, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + } + + private void writeLong( + final long value, + final OutputStream os) throws IOException { + longBuffer.segment().set( + ValueLayout.OfLong.JAVA_LONG_UNALIGNED, + 0, + value); + longBuffer.withArray(os::write); + } + + private void writeSegment( + final MemorySegment value, + final OutputStream os) throws IOException { + final long size = value.byteSize(); + blobBuffer.ensureCapacity(size); + MemorySegment.copy( + value, + 0L, + blobBuffer.segment(), + 0L, + size); + blobBuffer.withArray(array -> + os.write( + array, + 0, + (int) size)); + } + + /** + * Writes {@link Entry} to {@link FileChannel}. + * + * @return written bytes + */ + private long writeEntry( + final TimestampEntry entry, + final OutputStream os) throws IOException { + final MemorySegment key = entry.key(); + final MemorySegment value = entry.value(); + long result = 0L; + + // Key size + writeLong(key.byteSize(), os); + result += Long.BYTES; + + // Key + writeSegment(key, os); + result += key.byteSize(); + + // Value size and possibly value + if (value == null) { + // Tombstone + writeLong(SSTables.TOMBSTONE_VALUE_LENGTH, os); + result += Long.BYTES; + } else { + // Value length + writeLong(value.byteSize(), os); + result += Long.BYTES; + + // Value + writeSegment(value, os); + result += value.byteSize(); + } + + // Timestamp + writeLong(entry.timestamp(), os); + result += Long.BYTES; + + return result; + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/SSTables.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/SSTables.java new file mode 100644 index 000000000..bc23b4ea1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/SSTables.java @@ -0,0 +1,162 @@ +package ru.vk.itmo.test.vadimershov.dao; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +/** + * Provides {@link SSTable} management facilities: dumping and discovery. + * + * @author incubos + */ +final class SSTables { + public static final String INDEX_SUFFIX = ".index"; + public static final String DATA_SUFFIX = ".data"; + public static final long TOMBSTONE_VALUE_LENGTH = -1L; + + private static final String TEMP_SUFFIX = ".tmp"; + + /** + * Can't instantiate. + */ + private SSTables() { + // Only static methods + } + + static Path indexName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + INDEX_SUFFIX); + } + + static Path dataName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + DATA_SUFFIX); + } + + static Path tempIndexName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + INDEX_SUFFIX + TEMP_SUFFIX); + } + + static Path tempDataName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + DATA_SUFFIX + TEMP_SUFFIX); + } + + /** + * Returns {@link List} of {@link SSTable}s from freshest to oldest. + */ + static List discover( + final Arena arena, + final Path baseDir) throws IOException { + if (!Files.exists(baseDir)) { + return Collections.emptyList(); + } + + final List result = new ArrayList<>(); + try (Stream files = Files.list(baseDir)) { + files.forEach(file -> { + final String fileName = file.getFileName().toString(); + if (!fileName.endsWith(DATA_SUFFIX)) { + // Skip non data + return; + } + + final int sequence = + // .data -> N + Integer.parseInt( + fileName.substring( + 0, + fileName.length() - DATA_SUFFIX.length())); + + try { + result.add(open(arena, baseDir, sequence)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + + // Sort from freshest to oldest + result.sort((o1, o2) -> Integer.compare(o2.sequence, o1.sequence)); + + return Collections.unmodifiableList(result); + } + + static SSTable open( + final Arena arena, + final Path baseDir, + final int sequence) throws IOException { + final MemorySegment index = + mapReadOnly( + arena, + indexName(baseDir, sequence)); + final MemorySegment data = + mapReadOnly( + arena, + dataName(baseDir, sequence)); + + return new SSTable( + sequence, + index, + data); + } + + private static MemorySegment mapReadOnly( + final Arena arena, + final Path file) throws IOException { + try (FileChannel channel = + FileChannel.open( + file, + StandardOpenOption.READ)) { + return channel.map( + FileChannel.MapMode.READ_ONLY, + 0L, + Files.size(file), + arena); + } + } + + static void remove( + final Path baseDir, + final int sequence) throws IOException { + // First delete data file to make SSTable invisible + Files.delete(dataName(baseDir, sequence)); + Files.delete(indexName(baseDir, sequence)); + } + + static void promote( + final Path baseDir, + final int from, + final int to) throws IOException { + // Build to progress to the same outcome + if (Files.exists(indexName(baseDir, from))) { + Files.move( + indexName(baseDir, from), + indexName(baseDir, to), + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + } + if (Files.exists(dataName(baseDir, from))) { + Files.move( + dataName(baseDir, from), + dataName(baseDir, to), + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/TableSet.java new file mode 100644 index 000000000..4af0c0b98 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/TableSet.java @@ -0,0 +1,200 @@ +package ru.vk.itmo.test.vadimershov.dao; + +import java.lang.foreign.MemorySegment; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Data set in various tables. + * + * @author incubos + */ +final class TableSet { + final MemTable memTable; + final AtomicLong memTableSize; + // null or read-only + final MemTable flushingTable; + // From freshest to oldest + final List ssTables; + + private TableSet( + final MemTable memTable, + final AtomicLong memTableSize, + final MemTable flushingTable, + final List ssTables) { + this.memTable = memTable; + this.memTableSize = memTableSize; + this.flushingTable = flushingTable; + this.ssTables = ssTables; + } + + static TableSet from(final List ssTables) { + return new TableSet( + new MemTable(), + new AtomicLong(), + null, + 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"); + } + + if (flushingTable != null) { + throw new IllegalStateException("Already flushing"); + } + + return new TableSet( + new MemTable(), + new AtomicLong(), + memTable, + ssTables); + } + + TableSet flushed(final SSTable flushed) { + final List newSSTables = new ArrayList<>(ssTables.size() + 1); + newSSTables.add(flushed); + newSSTables.addAll(ssTables); + return new TableSet( + memTable, + memTableSize, + null, + newSSTables); + } + + TableSet compacted( + final Set replaced, + final SSTable with) { + final List newSsTables = new ArrayList<>(this.ssTables.size() + 1); + + // Keep not replaced SSTables + for (final SSTable ssTable : this.ssTables) { + if (!replaced.contains(ssTable)) { + newSsTables.add(ssTable); + } + } + + // Logically the oldest one + newSsTables.add(with); + + return new TableSet( + memTable, + memTableSize, + flushingTable, + newSsTables); + } + + Iterator> get( + final MemorySegment from, + final MemorySegment to) { + final List iterators = + new ArrayList<>(2 + ssTables.size()); + + // MemTable goes first + final Iterator> memTableIterator = + memTable.get(from, to); + if (memTableIterator.hasNext()) { + iterators.add( + new WeightedPeekingEntryIterator( + Integer.MIN_VALUE, + memTableIterator + ) + ); + } + + // Then goes flushing + if (flushingTable != null) { + final Iterator> flushingIterator = + flushingTable.get(from, to); + if (flushingIterator.hasNext()) { + iterators.add( + new WeightedPeekingEntryIterator( + Integer.MIN_VALUE + 1, + flushingIterator)); + } + } + + // Then go all the SSTables + for (int i = 0; i < ssTables.size(); i++) { + final SSTable ssTable = ssTables.get(i); + final Iterator> ssTableIterator = + ssTable.get(from, to); + if (ssTableIterator.hasNext()) { + iterators.add(new WeightedPeekingEntryIterator(i, ssTableIterator)); + } + } + + return switch (iterators.size()) { + case 0 -> Collections.emptyIterator(); + case 1 -> iterators.get(0); + default -> new MergingEntryIterator(iterators); + }; + } + + TimestampEntry get(final MemorySegment key) { + // Slightly optimized version not to pollute the heap + + // First check MemTable + TimestampEntry result = memTable.get(key); + if (result != null) { + // Transform tombstone + return swallowTombstone(result); + } + + // Then check flushing + if (flushingTable != null) { + result = flushingTable.get(key); + if (result != null) { + // Transform tombstone + return swallowTombstone(result); + } + } + + // At last check SSTables from freshest to oldest + for (final SSTable ssTable : ssTables) { + result = ssTable.get(key); + if (result != null) { + // Transform tombstone + return swallowTombstone(result); + } + } + + // Nothing found + return new TimestampBaseEntry<>(key, null, 0L); + } + + private static TimestampEntry swallowTombstone(final TimestampEntry entry) { + return entry.value() == null ? new TimestampBaseEntry<>(entry.key(), null, entry.timestamp()) : entry; + } + + TimestampEntry upsert(final TimestampEntry entry) { + return memTable.upsert(entry); + } + + 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 = + ssTable.get(null, null); + iterators.add( + new WeightedPeekingEntryIterator(i, ssTableIterator)); + } + + return new MergingEntryIterator(iterators); + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/TimestampBaseEntry.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/TimestampBaseEntry.java new file mode 100644 index 000000000..ccefe772c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/TimestampBaseEntry.java @@ -0,0 +1,8 @@ +package ru.vk.itmo.test.vadimershov.dao; + +public record TimestampBaseEntry(D key, D value, Long timestamp) implements TimestampEntry { + @Override + public String toString() { + return "[" + timestamp + "]{ " + key + " : " + value + " }"; + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/TimestampEntry.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/TimestampEntry.java new file mode 100644 index 000000000..1ab92de33 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/TimestampEntry.java @@ -0,0 +1,8 @@ +package ru.vk.itmo.test.vadimershov.dao; + +import ru.vk.itmo.dao.Entry; + +public interface TimestampEntry extends Entry { + Long timestamp(); + +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/dao/WeightedPeekingEntryIterator.java b/src/main/java/ru/vk/itmo/test/vadimershov/dao/WeightedPeekingEntryIterator.java new file mode 100644 index 000000000..5fe71b8dc --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/dao/WeightedPeekingEntryIterator.java @@ -0,0 +1,65 @@ +package ru.vk.itmo.test.vadimershov.dao; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Peeking {@link Iterator} wrapper. + * + * @author incubos + */ +final class WeightedPeekingEntryIterator + implements Iterator>, + Comparable { + private final int weight; + private final Iterator> delegate; + private TimestampEntry next; + + WeightedPeekingEntryIterator( + final int weight, + final Iterator> delegate) { + this.weight = weight; + this.delegate = delegate; + this.next = delegate.hasNext() ? delegate.next() : null; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public TimestampEntry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final TimestampEntry result = next; + next = delegate.hasNext() ? delegate.next() : null; + return result; + } + + TimestampEntry peek() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + return next; + } + + @Override + public int compareTo(final WeightedPeekingEntryIterator other) { + // First compare keys + int result = + MemorySegmentComparator.INSTANCE.compare( + peek().key(), + other.peek().key()); + if (result != 0) { + return result; + } + + // Then compare weights if keys are equal + return Integer.compare(weight, other.weight); + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/exceptions/FailedSharding.java b/src/main/java/ru/vk/itmo/test/vadimershov/exceptions/FailedSharding.java new file mode 100644 index 000000000..dea163473 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/exceptions/FailedSharding.java @@ -0,0 +1,20 @@ +package ru.vk.itmo.test.vadimershov.exceptions; + +import ru.vk.itmo.test.vadimershov.DaoResponse; + +public class FailedSharding extends RuntimeException { + + private final String httpCode; + + public FailedSharding(String httpCode) { + this.httpCode = httpCode; + } + + public FailedSharding() { + this.httpCode = DaoResponse.NOT_ENOUGH_REPLICAS; + } + + public String getHttpCode() { + return httpCode; + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/hash/ConsistentHashing.java b/src/main/java/ru/vk/itmo/test/vadimershov/hash/ConsistentHashing.java index e256a364a..c3d8a2472 100644 --- a/src/main/java/ru/vk/itmo/test/vadimershov/hash/ConsistentHashing.java +++ b/src/main/java/ru/vk/itmo/test/vadimershov/hash/ConsistentHashing.java @@ -1,11 +1,17 @@ package ru.vk.itmo.test.vadimershov.hash; -import one.nio.http.HttpClient; -import one.nio.net.ConnectionString; import one.nio.util.Hash; +import ru.vk.itmo.dao.Dao; +import ru.vk.itmo.test.vadimershov.dao.TimestampEntry; +import ru.vk.itmo.test.vadimershov.exceptions.FailedSharding; +import java.lang.foreign.MemorySegment; +import java.net.http.HttpClient; +import java.time.Duration; +import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Objects; import java.util.SortedMap; import java.util.TreeMap; @@ -15,32 +21,71 @@ public class ConsistentHashing { private final SortedMap ring; - public ConsistentHashing(List clusterUrls) { + private final VirtualNode localNode; + + public ConsistentHashing( + String localUrl, + List clusterUrls, + Dao> localDao + ) { this.ring = new TreeMap<>(); + this.localNode = new LocalNode(localUrl, localDao, -1); HashMap httpClientMap = new HashMap<>(); for (String currentUrl : clusterUrls) { - HttpClient currentHttpClient = httpClientMap.get(currentUrl); - if (currentHttpClient == null) { - currentHttpClient = new HttpClient(new ConnectionString(currentUrl)); - httpClientMap.put(currentUrl, currentHttpClient); - } - for (int j = 0; j < VIRTUAL_NODE_COUNT; j++) { - VirtualNode virtualNode = new VirtualNode(currentUrl, currentHttpClient, j); - ring.put(Hash.murmur3(virtualNode.key()), virtualNode); + if (Objects.equals(currentUrl, localUrl)) { + for (int i = 0; i < VIRTUAL_NODE_COUNT; i++) { + VirtualNode virtualNode = new LocalNode(currentUrl, localDao, i); + ring.put(Hash.murmur3(virtualNode.key(currentUrl)), virtualNode); + } + } else { + HttpClient httpClient = httpClientMap.computeIfAbsent( + currentUrl, + key -> HttpClient.newBuilder() + .connectTimeout(Duration.ofMillis(100)) + .build()); + for (int i = 0; i < VIRTUAL_NODE_COUNT; i++) { + VirtualNode virtualNode = new RemoteNode(currentUrl, httpClient, i); + ring.put(Hash.murmur3(virtualNode.key(currentUrl)), virtualNode); + } } } } - public VirtualNode findVNode(String key) { + public VirtualNode getLocalNode() { + return this.localNode; + } + + public Collection findVNodes(String key, int count) { + int currentCount = count; Integer hashKey = Hash.murmur3(key); - SortedMap tailMap = ring.tailMap(hashKey); - Integer nodeHashVal; - if (tailMap.isEmpty()) { - nodeHashVal = ring.firstKey(); - } else { - nodeHashVal = tailMap.firstKey(); + HashMap selectedNodes = new HashMap<>(); + + SortedMap sliceMap = ring.tailMap(hashKey); + for (VirtualNode node : sliceMap.values()) { + if (currentCount == 0) { + return selectedNodes.values(); + } + + if (selectedNodes.get(node.url()) != null) { + continue; + } + selectedNodes.put(node.url(), node); + currentCount--; + } + + sliceMap = ring.headMap(hashKey); + for (VirtualNode node : sliceMap.values()) { + if (currentCount == 0) { + return selectedNodes.values(); + } + + if (selectedNodes.get(node.url()) != null) { + continue; + } + selectedNodes.put(node.url(), node); + currentCount--; } - return ring.get(nodeHashVal); + throw new FailedSharding(); } public void close() { diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/hash/LocalNode.java b/src/main/java/ru/vk/itmo/test/vadimershov/hash/LocalNode.java new file mode 100644 index 000000000..373edfc35 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/hash/LocalNode.java @@ -0,0 +1,78 @@ +package ru.vk.itmo.test.vadimershov.hash; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.vk.itmo.dao.Dao; +import ru.vk.itmo.test.vadimershov.ResultResponse; +import ru.vk.itmo.test.vadimershov.dao.TimestampEntry; + +import java.lang.foreign.MemorySegment; +import java.net.HttpURLConnection; +import java.util.concurrent.CompletableFuture; + +import static ru.vk.itmo.test.vadimershov.utils.MSUtil.toByteArray; +import static ru.vk.itmo.test.vadimershov.utils.MSUtil.toDeletedEntity; +import static ru.vk.itmo.test.vadimershov.utils.MSUtil.toEntity; +import static ru.vk.itmo.test.vadimershov.utils.MSUtil.toMemorySegment; + +public class LocalNode extends VirtualNode { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final Dao> dao; + + public LocalNode(String url, Dao> dao, int replicaIndex) { + super(url, replicaIndex); + this.dao = dao; + } + + @Override + public void close() { + /* ничего не делаем так как закрываем локальную бд выше по иерархии */ + } + + @Override + public CompletableFuture get(String key) { + return CompletableFuture.supplyAsync(() -> { + TimestampEntry entry; + try { + entry = dao.get(toMemorySegment(key)); + } catch (Exception e) { + logger.error("Can't get value by key={}", key, e); + return new ResultResponse(HttpURLConnection.HTTP_INTERNAL_ERROR, null, 0L); + } + if (entry.value() == null) { + long timestamp = entry.timestamp() == null ? 0L : entry.timestamp(); + return new ResultResponse(HttpURLConnection.HTTP_NOT_FOUND, null, timestamp); + } + + return new ResultResponse(HttpURLConnection.HTTP_OK, toByteArray(entry.value()), entry.timestamp()); + }); + } + + @Override + public CompletableFuture upsert(String key, byte[] value, Long timestamp) { + return CompletableFuture.supplyAsync(() -> { + try { + dao.upsert(toEntity(key, value, timestamp)); + } catch (Exception e) { + logger.error("Can't upsert value by key={}", key, e); + return new ResultResponse(HttpURLConnection.HTTP_INTERNAL_ERROR, null, 0L); + } + return new ResultResponse(HttpURLConnection.HTTP_CREATED, null, 0L); + }); + } + + @Override + public CompletableFuture delete(String key, Long timestamp) { + return CompletableFuture.supplyAsync(() -> { + try { + dao.upsert(toDeletedEntity(key, timestamp)); + } catch (Exception e) { + logger.error("Can't upsert value by key={}", key, e); + return new ResultResponse(HttpURLConnection.HTTP_INTERNAL_ERROR, null, 0L); + } + return new ResultResponse(HttpURLConnection.HTTP_ACCEPTED, null, 0L); + }); + } +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/hash/RemoteNode.java b/src/main/java/ru/vk/itmo/test/vadimershov/hash/RemoteNode.java new file mode 100644 index 000000000..46ec8d3c1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/hash/RemoteNode.java @@ -0,0 +1,82 @@ +package ru.vk.itmo.test.vadimershov.hash; + +import ru.vk.itmo.test.vadimershov.ResultResponse; + +import javax.annotation.Nonnull; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +public class RemoteNode extends VirtualNode { + + public static final String X_INNER = "X-inner"; + public static final String ENTITY_URI = "/v0/entity?id="; + private static final String X_TIMESTAMP = "X-timestamp"; + + private final String uriPrefix; + private final HttpClient httpClient; + + public RemoteNode(String url, @Nonnull HttpClient httpClient, int replicaIndex) { + super(url, replicaIndex); + this.httpClient = httpClient; + this.uriPrefix = url + ENTITY_URI; + } + + @Override + public void close() { + if (!this.httpClient.isTerminated()) { + this.httpClient.close(); + } + } + + @Override + public CompletableFuture get(String key) { + return this.httpClient.sendAsync( + HttpRequest.newBuilder(URI.create(uriPrefix + key)) + .GET() + .header(X_INNER, "true") + .timeout(Duration.ofMillis(100)) + .build(), + HttpResponse.BodyHandlers.ofByteArray()) + .thenApply((httpResponse) -> + new ResultResponse(httpResponse.statusCode(), httpResponse.body(), getTimestamp(httpResponse)) + ); + } + + private static Long getTimestamp(HttpResponse response) { + return response.headers() + .firstValueAsLong(X_TIMESTAMP) + .orElse(0L); + } + + @Override + public CompletableFuture upsert(String key, byte[] value, Long timestamp) { + return this.httpClient.sendAsync( + HttpRequest.newBuilder(URI.create(uriPrefix + key)) + .PUT(HttpRequest.BodyPublishers.ofByteArray(value)) + .header(X_INNER, "true") + .header(X_TIMESTAMP, timestamp.toString()) + .build(), + HttpResponse.BodyHandlers.ofByteArray()) + .thenApply((httpResponse) -> + new ResultResponse(httpResponse.statusCode(), null, 0L) + ); + } + + @Override + public CompletableFuture delete(String key, Long timestamp) { + return this.httpClient.sendAsync( + HttpRequest.newBuilder(URI.create(uriPrefix + key)) + .DELETE() + .headers(X_INNER, "true") + .build(), + HttpResponse.BodyHandlers.ofByteArray()) + .thenApply((httpResponse) -> + new ResultResponse(httpResponse.statusCode(), null, 0L) + ); + } + +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/hash/VirtualNode.java b/src/main/java/ru/vk/itmo/test/vadimershov/hash/VirtualNode.java index cc9bcf822..92759e5c2 100644 --- a/src/main/java/ru/vk/itmo/test/vadimershov/hash/VirtualNode.java +++ b/src/main/java/ru/vk/itmo/test/vadimershov/hash/VirtualNode.java @@ -1,33 +1,32 @@ package ru.vk.itmo.test.vadimershov.hash; -import one.nio.http.HttpClient; +import ru.vk.itmo.test.vadimershov.ResultResponse; -import javax.annotation.Nonnull; +import java.util.concurrent.CompletableFuture; -public class VirtualNode { - private final HttpClient httpClient; - private final String url; - private final int replicaIndex; +public abstract class VirtualNode { + protected final String url; + protected final int replicaIndex; - public VirtualNode(String url, @Nonnull HttpClient httpClient, int replicaIndex) { - this.httpClient = httpClient; + protected VirtualNode(String url, int replicaIndex) { this.url = url; this.replicaIndex = replicaIndex; } - public HttpClient httpClient() { - return this.httpClient; - } - public String url() { return this.url; } - public String key() { - return "Virtual node: " + this.url + "-" + this.replicaIndex; + public String key(String url) { + return "Virtual node: " + url + "-" + this.replicaIndex; } - public void close() { - this.httpClient.close(); - } + public abstract void close(); + + public abstract CompletableFuture get(String key); + + public abstract CompletableFuture upsert(String key, byte[] value, Long timestamp); + + public abstract CompletableFuture delete(String key, Long timestamp); + } diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/utils/MSUtil.java b/src/main/java/ru/vk/itmo/test/vadimershov/utils/MSUtil.java new file mode 100644 index 000000000..918b4443c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/utils/MSUtil.java @@ -0,0 +1,36 @@ +package ru.vk.itmo.test.vadimershov.utils; + +import ru.vk.itmo.test.vadimershov.dao.TimestampBaseEntry; +import ru.vk.itmo.test.vadimershov.dao.TimestampEntry; + +import javax.annotation.Nonnull; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; + +public final class MSUtil { + + private MSUtil() { + } + + public static MemorySegment toMemorySegment(@Nonnull String data) { + return MemorySegment.ofArray(data.getBytes(StandardCharsets.UTF_8)); + } + + public static TimestampEntry toDeletedEntity(@Nonnull String key, @Nonnull Long timestamp) { + return new TimestampBaseEntry<>(toMemorySegment(key), null, timestamp); + } + + public static TimestampEntry toEntity( + @Nonnull String key, + @Nonnull byte[] value, + @Nonnull Long timestamp + ) { + return new TimestampBaseEntry<>(toMemorySegment(key), MemorySegment.ofArray(value), timestamp); + } + + public static byte[] toByteArray(@Nonnull MemorySegment data) { + return data.toArray(ValueLayout.JAVA_BYTE); + } + +} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/utils/MemorySegmentUtil.java b/src/main/java/ru/vk/itmo/test/vadimershov/utils/MemorySegmentUtil.java deleted file mode 100644 index df916dec4..000000000 --- a/src/main/java/ru/vk/itmo/test/vadimershov/utils/MemorySegmentUtil.java +++ /dev/null @@ -1,32 +0,0 @@ -package ru.vk.itmo.test.vadimershov.utils; - -import ru.vk.itmo.dao.BaseEntry; -import ru.vk.itmo.dao.Entry; - -import javax.annotation.Nonnull; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; -import java.nio.charset.StandardCharsets; - -public final class MemorySegmentUtil { - - private MemorySegmentUtil() { - } - - public static MemorySegment toMemorySegment(@Nonnull String data) { - return MemorySegment.ofArray(data.getBytes(StandardCharsets.UTF_8)); - } - - public static Entry toDeletedEntity(@Nonnull String key) { - return new BaseEntry<>(toMemorySegment(key), null); - } - - public static Entry toEntity(@Nonnull String key, @Nonnull byte[] value) { - return new BaseEntry<>(toMemorySegment(key), MemorySegment.ofArray(value)); - } - - public static byte[] toByteArray(@Nonnull MemorySegment data) { - return data.toArray(ValueLayout.JAVA_BYTE); - } - -} diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/alloc.html b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/alloc.html new file mode 100644 index 000000000..2d758be22 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/alloc.html @@ -0,0 +1,667 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/alloc.png b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/alloc.png new file mode 100644 index 000000000..a7e53742c Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/cpu.html b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/cpu.html new file mode 100644 index 000000000..9326f8fcd --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/cpu.html @@ -0,0 +1,1113 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/cpu.png b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/cpu.png new file mode 100644 index 000000000..d1263e3d0 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/lock.html b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/lock.html new file mode 100644 index 000000000..4767e1aab --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/lock.html @@ -0,0 +1,280 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/lock.png b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/lock.png new file mode 100644 index 000000000..8e4daf6a6 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/wrk_2100.txt b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/wrk_2100.txt new file mode 100644 index 000000000..bc673b128 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/get/wrk_2100.txt @@ -0,0 +1,109 @@ +./wrk -d 60 -t 1 -c 1 -R 2100 -L -s ./get.lua http://localhost:9080/v0/entry +Running 1m test @ http://localhost:9080/v0/entry + 1 threads and 1 connections + Thread calibration: mean lat.: 11.691ms, rate sampling interval: 107ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 8.71ms 13.59ms 65.09ms 86.10% + Req/Sec 2.11k 85.36 2.43k 80.94% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 2.82ms + 75.000% 6.63ms + 90.000% 30.69ms + 99.000% 55.26ms + 99.900% 62.98ms + 99.990% 64.93ms + 99.999% 65.12ms +100.000% 65.12ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.179 0.000000 1 1.00 + 1.024 0.100000 10521 1.11 + 1.384 0.200000 21004 1.25 + 1.726 0.300000 31512 1.43 + 2.173 0.400000 41994 1.67 + 2.819 0.500000 52492 2.00 + 3.243 0.550000 57763 2.22 + 3.725 0.600000 62995 2.50 + 4.299 0.650000 68253 2.86 + 5.171 0.700000 73494 3.33 + 6.635 0.750000 78740 4.00 + 7.967 0.775000 81365 4.44 + 10.007 0.800000 83998 5.00 + 13.823 0.825000 86614 5.71 + 19.263 0.850000 89244 6.67 + 25.103 0.875000 91866 8.00 + 27.823 0.887500 93176 8.89 + 30.687 0.900000 94486 10.00 + 33.695 0.912500 95802 11.43 + 37.407 0.925000 97121 13.33 + 43.263 0.937500 98436 16.00 + 44.767 0.943750 99088 17.78 + 46.175 0.950000 99741 20.00 + 47.231 0.956250 100396 22.86 + 48.127 0.962500 101059 26.67 + 49.311 0.968750 101711 32.00 + 50.079 0.971875 102036 35.56 + 50.815 0.975000 102361 40.00 + 51.647 0.978125 102696 45.71 + 52.351 0.981250 103027 53.33 + 53.247 0.984375 103353 64.00 + 53.599 0.985938 103521 71.11 + 54.047 0.987500 103675 80.00 + 54.911 0.989062 103836 91.43 + 55.583 0.990625 104005 106.67 + 56.383 0.992188 104170 128.00 + 56.735 0.992969 104253 142.22 + 56.991 0.993750 104334 160.00 + 57.279 0.994531 104413 182.86 + 57.631 0.995313 104492 213.33 + 58.111 0.996094 104575 256.00 + 58.335 0.996484 104615 284.44 + 58.623 0.996875 104658 320.00 + 58.911 0.997266 104699 365.71 + 59.167 0.997656 104746 426.67 + 59.615 0.998047 104780 512.00 + 60.287 0.998242 104800 568.89 + 61.023 0.998437 104820 640.00 + 61.503 0.998633 104841 731.43 + 62.335 0.998828 104861 853.33 + 63.071 0.999023 104882 1024.00 + 63.615 0.999121 104893 1137.78 + 63.839 0.999219 104904 1280.00 + 64.031 0.999316 104913 1462.86 + 64.159 0.999414 104927 1706.67 + 64.255 0.999512 104936 2048.00 + 64.351 0.999561 104939 2275.56 + 64.383 0.999609 104943 2560.00 + 64.543 0.999658 104949 2925.71 + 64.607 0.999707 104954 3413.33 + 64.703 0.999756 104959 4096.00 + 64.735 0.999780 104961 4551.11 + 64.799 0.999805 104964 5120.00 + 64.831 0.999829 104967 5851.43 + 64.863 0.999854 104969 6826.67 + 64.895 0.999878 104972 8192.00 + 64.927 0.999890 104974 9102.22 + 64.927 0.999902 104974 10240.00 + 64.959 0.999915 104977 11702.86 + 64.959 0.999927 104977 13653.33 + 64.991 0.999939 104978 16384.00 + 65.023 0.999945 104980 18204.44 + 65.023 0.999951 104980 20480.00 + 65.023 0.999957 104980 23405.71 + 65.055 0.999963 104981 27306.67 + 65.055 0.999969 104981 32768.00 + 65.087 0.999973 104982 36408.89 + 65.087 0.999976 104982 40960.00 + 65.087 0.999979 104982 46811.43 + 65.119 0.999982 104984 54613.33 + 65.119 1.000000 104984 inf +#[Mean = 8.707, StdDeviation = 13.589] +#[Max = 65.088, Total count = 104984] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 125992 requests in 1.00m, 11.53MB read + Non-2xx or 3xx responses: 1 +Requests/sec: 2099.96 +Transfer/sec: 196.87KB diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/report.md b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/report.md new file mode 100644 index 000000000..848594be5 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/report.md @@ -0,0 +1,119 @@ +# Анализ программы с использованием wrk2 и async-profiler + +Для проведения анализов использовал 3 ноды (from) и кворумом равный 2 нодам. +То есть сохранение и получение данных происходило минимум из двух нод. +Для выбора нод использовал тот же алгоритм, что и в 3 лабораточной, пришлось его доделать, +чтоб получать не одну конкретную ноду. То есть я нахожу конкретную ноду для данного ключа и начинаю идти по кольцу, +находя не первые попавшиеся виртуальные ноды, а уникальные (с уникальным url). + +Распределение между нода осталось таким же равномерным + +| size | port | +|----------|------| +| 160.5 Mb | 9080 | +| 160.5 Mb | 9081 | +| 160.5 Mb | 9082 | + + +### Точка разладки для upsert является 6.5к запросов в секунду + +Это значительно меньше чем в прошлой реализации, в прошлой значение было 35к. +Связано это со многим факторами: +- самое главное, что мы сохраняем реплики на смежные ноды +- запрос может прийти вообще не на ноду, которую выберет алгоритм, +и придется отправлять запрос на сохранение в другие две ноды (вероятность такого попадания примерно 33% +(при нашей реализации, конечное процент будет другой в зависимости от количества нод и указывания кворума)). +В идеальном случае мы отправим только один запрос в смежную ноду (66%). +- Выбор нод тоже занимает чуть больше по сравнению с прошлым вариантом, +так как нам нужно найти кворум уникальных нод по кольцу +- Сохраняем в дао время + +``` + 50.000% 0.87ms + 75.000% 1.21ms + 90.000% 3.84ms + 99.000% 87.17ms + 99.900% 108.80ms + 99.990% 111.17ms + 99.999% 111.49ms +100.000% 111.55ms +``` + +Получаем достаточно не плохой результат работы, количество запросов уменьшилось в примерно 5 раз, +но при этом мы получили хорошо реплицируемый сервис, и не боимся за потерю сохранения данных при отказе ноды + +#### CPU +![cpu](upsert/cpu.png) + +На основе диаграммы cpu видно, что больше всего времени уходит на отправку put запросов +в смежные ноды (RemoteNode) 25.19%. +Сохранение в локальную ноду занимает всего 1.51%. +Поиск нод занимает всего 0.25%. +Можно сделать вывод, больше всего времени уходит на работу по сити и с отправкой ответов на запросы. + +#### ALLOC +![alloc](upsert/alloc.png) + +Тут все также, как и в прошлой лабе, получем больше всего аллокаций на чтение ответа из ноды, +которая уже может быть не одна (77.40%), хоть мы и добавили в бд сохранение времени с типом Long. +Для сохранения в локальную бд 2.97%. +Поиск нод не увличился, хоть нам нужно найти не одну ноду. + +#### LOCK +![lock](upsert/lock.png) + +Увеличилось количество локов для отправки ответа, это логично так как нам нужно отправить +ответ уже не с одной ноды, а с нескольких (одной или двух) + + +### Точка разладки для get является 2.1к запросов в секунду + +Получаем 2.1к rps это значительно меньше чем в прошлой реализации, +но это связано с реплицированием данных и запрос на несколько нод, и получение самых актуальных данных из всех ответов + +``` + 50.000% 2.82ms + 75.000% 6.63ms + 90.000% 30.69ms + 99.000% 55.26ms + 99.900% 62.98ms + 99.990% 64.93ms + 99.999% 65.12ms +100.000% 65.12ms +``` + +#### CPU +![cpu](get/cpu.png) + +Из диаграммы видим что увеличились проценты для получение данных. +Стоит отметить, что больше всего уходит на получение из локальной бд 63.59%, видно, что не произошло оптимизаций +кода для SSTable.get, как в прошлой лабораторной. +Также стоит отметить, что мы получаем также время upsert записи, также так как мы сохраняем время, +то и количество файлов увеличилось. +Ну и основной фактор влияющий на проценты в том что мы читаем с локальных бд всегда с 2 нод. +Получение данных со смежных нод всего 11.25% + +#### ALLOC +![alloc](get/alloc.png) + +Тут все стандартно, больше всего аллокаций идет на чтение ответа от смежных нод (79.72%), +и теперь их может быть две, а не одна. +2.86% процентов уходит для поиска в локальной dao, это значение больше чем в прошлой лабораторной, +так как мы читаем две записи +Поиск нод всего 1.63% +Для определения самой новой записи (PriorityQueue) 0.31% + +#### LOCK +![lock](get/lock.png) + +Все (100%) локов уходит на отправку ответа + +### Вывод +Вывод который можно сделать, при реализации репликаций мы проигрываем в скорости как upsert так и get запросов, +но при этом мы обезопасили себя от выключения и недоступности определенного количества нод, знаем что получим только +актуальные данные даже если в какой-то ноде есть неактуальные данные. +Можно реализовать систему быстрого чтения, путем сохранения записи на все ноды, а чтения только из нескольких нод, +или из одной если мы будем уверены в том что ни одна нода не была выключена + +Улучшения которые я вижу, сделать параллельный вызов смежных сервисов и дожидаться только ack ответов + diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/alloc.html b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/alloc.html new file mode 100644 index 000000000..8ab1f20cb --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/alloc.html @@ -0,0 +1,786 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/alloc.png b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/alloc.png new file mode 100644 index 000000000..b362b598b Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/cpu.html b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/cpu.html new file mode 100644 index 000000000..a9de99c51 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/cpu.html @@ -0,0 +1,1741 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/cpu.png b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/cpu.png new file mode 100644 index 000000000..6cb94de7e Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/lock.html b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/lock.html new file mode 100644 index 000000000..f533a1302 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/lock.html @@ -0,0 +1,329 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/lock.png b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/lock.png new file mode 100644 index 000000000..1066a7708 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/wrk_6500.txt b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/wrk_6500.txt new file mode 100644 index 000000000..a6ebf17ac --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result4/upsert/wrk_6500.txt @@ -0,0 +1,121 @@ +./wrk -d 60 -t 1 -c 1 -R 6500 -L -s ./upsert.lua http://localhost:9080/v0/entry +Running 1m test @ http://localhost:9080/v0/entry + 1 threads and 1 connections + Thread calibration: mean lat.: 4.656ms, rate sampling interval: 34ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.93ms 12.93ms 111.49ms 93.63% + Req/Sec 6.60k 378.69 7.76k 91.63% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 0.87ms + 75.000% 1.21ms + 90.000% 3.84ms + 99.000% 87.17ms + 99.900% 108.80ms + 99.990% 111.17ms + 99.999% 111.49ms +100.000% 111.55ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.101 0.000000 1 1.00 + 0.312 0.100000 32654 1.11 + 0.463 0.200000 65169 1.25 + 0.603 0.300000 97659 1.43 + 0.740 0.400000 130211 1.67 + 0.874 0.500000 162566 2.00 + 0.940 0.550000 178717 2.22 + 1.008 0.600000 195101 2.50 + 1.075 0.650000 211355 2.86 + 1.141 0.700000 227444 3.33 + 1.209 0.750000 243924 4.00 + 1.241 0.775000 251866 4.44 + 1.274 0.800000 260068 5.00 + 1.336 0.825000 268072 5.71 + 1.437 0.850000 276198 6.67 + 1.681 0.875000 284286 8.00 + 2.113 0.887500 288356 8.89 + 3.843 0.900000 292403 10.00 + 7.867 0.912500 296467 11.43 + 12.359 0.925000 300529 13.33 + 17.279 0.937500 304598 16.00 + 19.567 0.943750 306625 17.78 + 21.919 0.950000 308651 20.00 + 24.431 0.956250 310680 22.86 + 27.679 0.962500 312714 26.67 + 30.911 0.968750 314749 32.00 + 33.439 0.971875 315771 35.56 + 36.671 0.975000 316782 40.00 + 41.055 0.978125 317786 45.71 + 50.975 0.981250 318801 53.33 + 62.943 0.984375 319817 64.00 + 69.439 0.985938 320324 71.11 + 74.687 0.987500 320832 80.00 + 83.199 0.989062 321341 91.43 + 87.999 0.990625 321856 106.67 + 92.287 0.992188 322358 128.00 + 94.271 0.992969 322621 142.22 + 96.575 0.993750 322868 160.00 + 99.135 0.994531 323118 182.86 + 102.463 0.995313 323380 213.33 + 103.807 0.996094 323624 256.00 + 104.127 0.996484 323766 284.44 + 104.575 0.996875 323899 320.00 + 104.831 0.997266 324015 365.71 + 105.471 0.997656 324131 426.67 + 105.919 0.998047 324265 512.00 + 106.303 0.998242 324324 568.89 + 107.007 0.998437 324387 640.00 + 108.031 0.998633 324454 731.43 + 108.479 0.998828 324524 853.33 + 108.799 0.999023 324576 1024.00 + 108.927 0.999121 324618 1137.78 + 108.991 0.999219 324646 1280.00 + 109.119 0.999316 324684 1462.86 + 109.439 0.999414 324702 1706.67 + 109.759 0.999512 324734 2048.00 + 110.015 0.999561 324761 2275.56 + 110.143 0.999609 324772 2560.00 + 110.207 0.999658 324784 2925.71 + 110.399 0.999707 324798 3413.33 + 110.591 0.999756 324816 4096.00 + 110.655 0.999780 324823 4551.11 + 110.783 0.999805 324839 5120.00 + 110.783 0.999829 324839 5851.43 + 110.911 0.999854 324847 6826.67 + 111.039 0.999878 324854 8192.00 + 111.103 0.999890 324859 9102.22 + 111.167 0.999902 324862 10240.00 + 111.231 0.999915 324867 11702.86 + 111.295 0.999927 324873 13653.33 + 111.295 0.999939 324873 16384.00 + 111.359 0.999945 324878 18204.44 + 111.359 0.999951 324878 20480.00 + 111.423 0.999957 324883 23405.71 + 111.423 0.999963 324883 27306.67 + 111.423 0.999969 324883 32768.00 + 111.487 0.999973 324891 36408.89 + 111.487 0.999976 324891 40960.00 + 111.487 0.999979 324891 46811.43 + 111.487 0.999982 324891 54613.33 + 111.487 0.999985 324891 65536.00 + 111.487 0.999986 324891 72817.78 + 111.487 0.999988 324891 81920.00 + 111.487 0.999989 324891 93622.86 + 111.487 0.999991 324891 109226.67 + 111.487 0.999992 324891 131072.00 + 111.487 0.999993 324891 145635.56 + 111.487 0.999994 324891 163840.00 + 111.487 0.999995 324891 187245.71 + 111.487 0.999995 324891 218453.33 + 111.487 0.999996 324891 262144.00 + 111.487 0.999997 324891 291271.11 + 111.551 0.999997 324892 327680.00 + 111.551 1.000000 324892 inf +#[Mean = 3.932, StdDeviation = 12.928] +#[Max = 111.488, Total count = 324892] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 389912 requests in 1.00m, 24.91MB read +Requests/sec: 6498.81 +Transfer/sec: 425.22KB \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/alloc.html b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/alloc.html new file mode 100644 index 000000000..0fe86d012 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/alloc.html @@ -0,0 +1,3506 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/alloc.png b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/alloc.png new file mode 100644 index 000000000..e437f74e6 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/cpu.html b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/cpu.html new file mode 100644 index 000000000..76426635d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/cpu.html @@ -0,0 +1,6237 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/cpu.png b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/cpu.png new file mode 100644 index 000000000..e3d8bb975 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/lock.html b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/lock.html new file mode 100644 index 000000000..9551a41ee --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/lock.html @@ -0,0 +1,1106 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/lock.png b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/lock.png new file mode 100644 index 000000000..2868ac94e Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/wrk_10000.txt b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/wrk_10000.txt new file mode 100644 index 000000000..60b00c5b7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/get/wrk_10000.txt @@ -0,0 +1,110 @@ +./wrk -d 60 -t 1 -c 10 -R 10000 -L -s ./get.lua http://localhost:9080/v0/entry +Running 1m test @ http://localhost:9080/v0/entry + 1 threads and 10 connections + Thread calibration: mean lat.: 8.896ms, rate sampling interval: 51ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 154.36ms 125.80ms 548.35ms 67.03% + Req/Sec 10.09k 791.53 13.00k 77.83% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 128.45ms + 75.000% 234.75ms + 90.000% 359.68ms + 99.000% 476.67ms + 99.900% 535.04ms + 99.990% 547.33ms + 99.999% 548.86ms +100.000% 548.86ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.349 0.000000 1 1.00 + 18.095 0.100000 49897 1.11 + 33.727 0.200000 99777 1.25 + 58.655 0.300000 149695 1.43 + 101.311 0.400000 199538 1.67 + 128.447 0.500000 249419 2.00 + 143.871 0.550000 274603 2.22 + 155.775 0.600000 299321 2.50 + 174.335 0.650000 324306 2.86 + 205.183 0.700000 349229 3.33 + 234.751 0.750000 374166 4.00 + 244.607 0.775000 386639 4.44 + 255.615 0.800000 399130 5.00 + 271.103 0.825000 411594 5.71 + 289.279 0.850000 424150 6.67 + 324.095 0.875000 436503 8.00 + 341.759 0.887500 442722 8.89 + 359.679 0.900000 448929 10.00 + 372.223 0.912500 455262 11.43 + 388.863 0.925000 461417 13.33 + 402.687 0.937500 467727 16.00 + 409.087 0.943750 470897 17.78 + 414.463 0.950000 474014 20.00 + 421.119 0.956250 477104 22.86 + 427.007 0.962500 480172 26.67 + 435.455 0.968750 483291 32.00 + 439.295 0.971875 484872 35.56 + 442.879 0.975000 486397 40.00 + 449.023 0.978125 487919 45.71 + 457.215 0.981250 489490 53.33 + 463.359 0.984375 491032 64.00 + 467.711 0.985938 491810 71.11 + 471.295 0.987500 492585 80.00 + 474.879 0.989062 493352 91.43 + 477.439 0.990625 494190 106.67 + 483.839 0.992188 494932 128.00 + 487.167 0.992969 495328 142.22 + 491.775 0.993750 495699 160.00 + 496.639 0.994531 496085 182.86 + 503.295 0.995313 496480 213.33 + 506.623 0.996094 496888 256.00 + 507.903 0.996484 497078 284.44 + 508.927 0.996875 497248 320.00 + 510.463 0.997266 497467 365.71 + 511.743 0.997656 497637 426.67 + 514.559 0.998047 497839 512.00 + 516.351 0.998242 497932 568.89 + 519.935 0.998437 498027 640.00 + 525.823 0.998633 498135 731.43 + 531.455 0.998828 498224 853.33 + 536.063 0.999023 498319 1024.00 + 538.623 0.999121 498387 1137.78 + 541.183 0.999219 498419 1280.00 + 543.231 0.999316 498472 1462.86 + 544.255 0.999414 498524 1706.67 + 544.767 0.999512 498572 2048.00 + 545.279 0.999561 498609 2275.56 + 545.791 0.999609 498658 2560.00 + 545.791 0.999658 498658 2925.71 + 545.791 0.999707 498658 3413.33 + 546.303 0.999756 498724 4096.00 + 546.303 0.999780 498724 4551.11 + 546.303 0.999805 498724 5120.00 + 546.303 0.999829 498724 5851.43 + 546.815 0.999854 498748 6826.67 + 546.815 0.999878 498748 8192.00 + 547.327 0.999890 498767 9102.22 + 547.327 0.999902 498767 10240.00 + 547.327 0.999915 498767 11702.86 + 547.839 0.999927 498783 13653.33 + 547.839 0.999939 498783 16384.00 + 547.839 0.999945 498783 18204.44 + 547.839 0.999951 498783 20480.00 + 547.839 0.999957 498783 23405.71 + 548.351 0.999963 498796 27306.67 + 548.351 0.999969 498796 32768.00 + 548.351 0.999973 498796 36408.89 + 548.351 0.999976 498796 40960.00 + 548.351 0.999979 498796 46811.43 + 548.351 0.999982 498796 54613.33 + 548.863 0.999985 498804 65536.00 + 548.863 1.000000 498804 inf +#[Mean = 154.364, StdDeviation = 125.799] +#[Max = 548.352, Total count = 498804] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 598897 requests in 1.00m, 54.83MB read + Non-2xx or 3xx responses: 3 +Requests/sec: 9981.81 +Transfer/sec: 0.91MB \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/report.md b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/report.md new file mode 100644 index 000000000..65cb889ee --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/report.md @@ -0,0 +1,104 @@ +# Анализ программы с использованием wrk2 и async-profiler + +Был использован httpClient java с асинхронным вызовом запросов, после идет объединение всех Future. +В объединении я добавлял метод whenComplete в котором считал количество положительных и ошибочных ответов, +при достижении порога ошибочных и положительных результатов отправлял самый свежий из данных ответ (timestamp) +Распределение между надами как в прошлой реализации не изменилось данные кладутся равномерно + + +### Точка разладки для upsert является 11.5к запросов в секунду + +Значение с прошлого раза увеличилось в чуть меньше чем в 2 раза, и при этом остаемся со всеми плюсами из лабораторной 4 + +``` + 50.000% 1.58ms + 75.000% 19.14ms + 90.000% 66.37ms + 99.000% 214.27ms + 99.900% 273.41ms + 99.990% 279.81ms + 99.999% 280.57ms +100.000% 281.09ms +``` + +Получаем хороший результат при асинхронной реализации, добавляя скорости. +Плюсом ускорения является то, что мы можем сделать сразу все запросы ко всем нужным нам нодам, +потом просто ждать ответа. При получении ответа считаем положительные/отрицательные и при пороговом значении сразу +отдавать ответ, что все ок или нет. + +#### CPU +![cpu](upsert/cpu.png) + +На основе диаграммы cpu видно, что на работу нашего DAO сервера уходит значительно меньше процентов 9% вместо 46%. +Из которых сохранение в локальную DAO занимает 0.8% на создание запроса в смежные ноды 1.4%. +На метод waitFuture в котором есть join на получение положительного или отрицательного результата, +чтоб отправить ответ уходит 2.61%, а на join для внутренних запросов 2.38%. +Отправка ответа занимает 1.9%. Все остальное время занимает асинхронные взаимодействия и работа библиотеки one nio + +#### ALLOC +![alloc](upsert/alloc.png) +Уменьшилось количество аллокаций для работы DAO c 80% до 20%, но при этом появились аллокации +для работы асинхронных запросов и их обработок. Отправка асинхронного запроса занимает 10%. +Можно сказать что аллокации самой DAO значительно не существенней чем асинхронное взаимодействие. + +#### LOCK +![lock](upsert/lock.png) + +Самое интересно мы должны получить в локах. 48% уделяется на SchedulableTask.run так как он, вроде как, +отвечает за построение цепочек выполнения асинхронных процессов (whenComplete). +Для AsyncSupply.run 30%, который используется для ожидания через синхронный блок. +5.3 % уходил для пула воркеров. +И только 0.33% нужно для отправки ответа. + + +### Точка разладки для get является 10к запросов в секунду + +``` + 50.000% 128.45ms + 75.000% 234.75ms + 90.000% 359.68ms + 99.000% 476.67ms + 99.900% 535.04ms + 99.990% 547.33ms + 99.999% 548.86ms +100.000% 548.86ms +``` + +Из-за асинхронного взаимодействия получаем прирост точки разладки в 5 раз вместо 2.1 к получаем 10к, +при этом получаем все те же плюсы, что и в лабораторной работе 4. + +#### CPU +![cpu](get/cpu.png) + +Из диаграммы видно, что есть разделение выполнения get из локального DAO, +так как процесс происходит также в ComputableFuture, составляет все 27% вместо 64%. +Отправка ответа 1.5%, join для внутренних запросов 1.7%, ожидание выполнения всех запросов 1%, +создания задачи для локальной dao 0.18%, отправка запроса в смежную ноду 1%. +Все остальные ресурсы уходят на работы one nio, пула потоков и внутренние процессы java + +#### ALLOC +![alloc](get/alloc.png) + +Тут такая же картина, как и с upsert, больше всего аллокаций уходит на асинхронные процессы. +По процентам для работы dao сервера 16%, вместо 84% +(с учетом асинхронной отправки запросов, созданием URI, созданием запроса и проставления заголовков) +get в локальную dao 0.6%, в смежную ноду 16%, поиск ноды 0.8%, ожидание ответа от смежных нод 1%, +join для отправки ответа 0.23%. + +#### LOCK +![lock](get/lock.png) + +Локи на локальной дао занимают всего 4.38%, AsyncSupply.run 30%, SchedulableTask.run 50%, 1 % уходил для пула воркеров. +По определяем тут все также, как и при upsert, потому что влияние работы дао совсем незначительное + + +### Вывод + +Переделав реализация на асинхронную мы получили значительный прирост в latency. +Из-за этого мы не дожидаемся всех from, а только если положительно то ack, либо from - ack + 1 +если отрицательно + +Возможные улучшения: если получили достаточное количество +отрицательных или положительных ответов, то можно не дожидаться ответа от серверов и завершать запрос, +чтоб не уходило время на коннект и выполнение. + diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/alloc.html b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/alloc.html new file mode 100644 index 000000000..bc2b88fa6 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/alloc.html @@ -0,0 +1,3505 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/alloc.png b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/alloc.png new file mode 100644 index 000000000..de1661cb8 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/alloc.png differ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/cpu.html b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/cpu.html new file mode 100644 index 000000000..ccd546177 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/cpu.html @@ -0,0 +1,5878 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/cpu.png b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/cpu.png new file mode 100644 index 000000000..39cbe99ee Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/cpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/lock.html b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/lock.html new file mode 100644 index 000000000..d8040f539 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/lock.html @@ -0,0 +1,1103 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/lock.png b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/lock.png new file mode 100644 index 000000000..deff1feda Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/lock.png differ diff --git a/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/wrk_11500.txt b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/wrk_11500.txt new file mode 100644 index 000000000..7c9ee1700 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/vadimershov/wrk/result5/upsert/wrk_11500.txt @@ -0,0 +1,125 @@ +./wrk -d 60 -t 1 -c 10 -R 11500 -L -s ./upsert.lua http://localhost:9080/v0/entry +Running 1m test @ http://localhost:9080/v0/entry + 1 threads and 10 connections + Thread calibration: mean lat.: 3.973ms, rate sampling interval: 14ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 22.04ms 46.75ms 280.83ms 90.35% + Req/Sec 11.94k 0.93k 14.62k 74.99% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.58ms + 75.000% 19.14ms + 90.000% 66.37ms + 99.000% 214.27ms + 99.900% 273.41ms + 99.990% 279.81ms + 99.999% 280.57ms +100.000% 281.09ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.182 0.000000 1 1.00 + 0.748 0.100000 57759 1.11 + 0.917 0.200000 115139 1.25 + 1.091 0.300000 172539 1.43 + 1.299 0.400000 230027 1.67 + 1.585 0.500000 287295 2.00 + 1.826 0.550000 315986 2.22 + 2.239 0.600000 344762 2.50 + 2.905 0.650000 373431 2.86 + 7.979 0.700000 402133 3.33 + 19.135 0.750000 430865 4.00 + 25.535 0.775000 445216 4.44 + 31.071 0.800000 459591 5.00 + 36.607 0.825000 473936 5.71 + 43.903 0.850000 488306 6.67 + 54.335 0.875000 502664 8.00 + 59.839 0.887500 509873 8.89 + 66.367 0.900000 517034 10.00 + 75.391 0.912500 524224 11.43 + 90.239 0.925000 531397 13.33 + 111.743 0.937500 538566 16.00 + 126.591 0.943750 542157 17.78 + 145.151 0.950000 545751 20.00 + 161.919 0.956250 549349 22.86 + 175.103 0.962500 552927 26.67 + 184.063 0.968750 556599 32.00 + 187.519 0.971875 558324 35.56 + 190.847 0.975000 560136 40.00 + 194.175 0.978125 561960 45.71 + 197.759 0.981250 563719 53.33 + 201.087 0.984375 565558 64.00 + 202.879 0.985938 566406 71.11 + 206.463 0.987500 567294 80.00 + 212.095 0.989062 568184 91.43 + 216.063 0.990625 569100 106.67 + 221.567 0.992188 569995 128.00 + 224.511 0.992969 570453 142.22 + 227.583 0.993750 570889 160.00 + 230.655 0.994531 571344 182.86 + 232.831 0.995313 571777 213.33 + 238.079 0.996094 572224 256.00 + 245.375 0.996484 572447 284.44 + 254.591 0.996875 572671 320.00 + 262.015 0.997266 572902 365.71 + 263.167 0.997656 573130 426.67 + 264.959 0.998047 573348 512.00 + 268.031 0.998242 573457 568.89 + 270.079 0.998437 573569 640.00 + 271.871 0.998633 573703 731.43 + 272.895 0.998828 573825 853.33 + 273.663 0.999023 573928 1024.00 + 273.919 0.999121 573969 1137.78 + 274.431 0.999219 574048 1280.00 + 274.687 0.999316 574082 1462.86 + 275.455 0.999414 574137 1706.67 + 276.223 0.999512 574194 2048.00 + 276.479 0.999561 574229 2275.56 + 276.735 0.999609 574252 2560.00 + 276.991 0.999658 574276 2925.71 + 277.503 0.999707 574312 3413.33 + 277.759 0.999756 574327 4096.00 + 278.015 0.999780 574351 4551.11 + 278.271 0.999805 574380 5120.00 + 278.271 0.999829 574380 5851.43 + 278.527 0.999854 574399 6826.67 + 278.527 0.999878 574399 8192.00 + 279.295 0.999890 574405 9102.22 + 279.807 0.999902 574417 10240.00 + 279.807 0.999915 574417 11702.86 + 280.063 0.999927 574440 13653.33 + 280.063 0.999939 574440 16384.00 + 280.063 0.999945 574440 18204.44 + 280.063 0.999951 574440 20480.00 + 280.319 0.999957 574458 23405.71 + 280.319 0.999963 574458 27306.67 + 280.319 0.999969 574458 32768.00 + 280.319 0.999973 574458 36408.89 + 280.319 0.999976 574458 40960.00 + 280.319 0.999979 574458 46811.43 + 280.319 0.999982 574458 54613.33 + 280.319 0.999985 574458 65536.00 + 280.575 0.999986 574464 72817.78 + 280.575 0.999988 574464 81920.00 + 280.575 0.999989 574464 93622.86 + 280.575 0.999991 574464 109226.67 + 280.575 0.999992 574464 131072.00 + 280.575 0.999993 574464 145635.56 + 280.575 0.999994 574464 163840.00 + 280.575 0.999995 574464 187245.71 + 280.575 0.999995 574464 218453.33 + 280.575 0.999996 574464 262144.00 + 280.831 0.999997 574465 291271.11 + 280.831 0.999997 574465 327680.00 + 280.831 0.999997 574465 374491.43 + 280.831 0.999998 574465 436906.67 + 280.831 0.999998 574465 524288.00 + 281.087 0.999998 574466 582542.22 + 281.087 1.000000 574466 inf +#[Mean = 22.035, StdDeviation = 46.745] +#[Max = 280.832, Total count = 574466] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 689708 requests in 1.00m, 44.07MB read +Requests/sec: 11495.53 +Transfer/sec: 752.15KB \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/LSMServerImpl.java b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/LSMServerImpl.java index b5e3c72b3..fcbe7b035 100644 --- a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/LSMServerImpl.java +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/LSMServerImpl.java @@ -18,11 +18,12 @@ import ru.vk.itmo.test.viktorkorotkikh.dao.exceptions.LSMDaoOutOfMemoryException; import ru.vk.itmo.test.viktorkorotkikh.dao.exceptions.TooManyFlushesException; import ru.vk.itmo.test.viktorkorotkikh.http.LSMCustomSession; +import ru.vk.itmo.test.viktorkorotkikh.http.LSMRangeWriter; import ru.vk.itmo.test.viktorkorotkikh.http.LSMServerResponseWithMemorySegment; +import ru.vk.itmo.test.viktorkorotkikh.util.ClusterResponseMerger; import ru.vk.itmo.test.viktorkorotkikh.util.HttpResponseNodeResponse; import ru.vk.itmo.test.viktorkorotkikh.util.LSMConstantResponse; import ru.vk.itmo.test.viktorkorotkikh.util.LsmServerUtil; -import ru.vk.itmo.test.viktorkorotkikh.util.NodeResponse; import ru.vk.itmo.test.viktorkorotkikh.util.OneNioNodeResponse; import ru.vk.itmo.test.viktorkorotkikh.util.ReplicaEmptyResponse; import ru.vk.itmo.test.viktorkorotkikh.util.RequestParameters; @@ -35,18 +36,14 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.time.Instant; -import java.util.ArrayList; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.SequencedSet; import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import static java.net.HttpURLConnection.HTTP_GATEWAY_TIMEOUT; import static java.net.HttpURLConnection.HTTP_UNAVAILABLE; @@ -66,13 +63,17 @@ public class LSMServerImpl extends HttpServer { private final String selfUrl; private final HttpClient clusterClient; private final ServiceConfig serviceConfig; + private final ExecutorService clusterResponseProcessor; + private final ExecutorService localProcessor; public LSMServerImpl( ServiceConfig serviceConfig, Dao> dao, ExecutorService executorService, ConsistentHashingManager consistentHashingManager, - HttpClient clusterClient + HttpClient clusterClient, + ExecutorService clusterResponseProcessor, + ExecutorService localProcessor ) throws IOException { super(createServerConfig(serviceConfig)); this.dao = dao; @@ -81,6 +82,8 @@ public LSMServerImpl( this.selfUrl = serviceConfig.selfUrl(); this.clusterClient = clusterClient; this.serviceConfig = serviceConfig; + this.clusterResponseProcessor = clusterResponseProcessor; + this.localProcessor = localProcessor; } private static HttpServerConfig createServerConfig(ServiceConfig serviceConfig) { @@ -103,6 +106,12 @@ public void handleRequest(Request request, HttpSession session) throws IOExcepti executorService.execute(() -> { try { final String path = request.getPath(); + + if (path.startsWith("/v0/entities") && request.getMethod() == METHOD_GET) { + handleEntitiesRangeRequest(request, (LSMCustomSession) session); + return; + } + if (path.startsWith("/v0/entity")) { handleEntityRequest(request, session); } else { @@ -186,15 +195,15 @@ public void handleEntityRequest(Request request, HttpSession session) throws IOE final SequencedSet replicas = consistentHashingManager.getReplicasSet(from, key); - final Response response = getResponseFromReplicas( + getResponseFromReplicas( request, from, replicas, key, id, - ack + ack, + session ); - session.sendResponse(response); } private static RequestParameters getRequestParameters(Request request) { @@ -220,34 +229,38 @@ private static RequestParameters getRequestParameters(Request request) { return new RequestParameters(id, ack, from); } - private Response getResponseFromReplicas( + private void getResponseFromReplicas( Request request, - Integer from, + int from, SequencedSet replicas, byte[] key, String id, - Integer ack + int ack, + HttpSession session ) { - final List responses = new ArrayList<>(from); + final ClusterResponseMerger clusterResponseMerger = new ClusterResponseMerger(ack, from, request, session); final long requestTimestamp = Instant.now().toEpochMilli(); + int i = 0; for (final String replicaUrl : replicas) { if (replicaUrl.equals(selfUrl)) { - responses.add(processLocal(request, key, id, requestTimestamp)); + processLocalAsync(request, key, id, requestTimestamp, i, clusterResponseMerger); } else { - responses.add(processRemote(request, replicaUrl, id, requestTimestamp)); + processRemote(request, replicaUrl, id, requestTimestamp, i, clusterResponseMerger); } + i++; } - return LsmServerUtil.mergeReplicasResponses(request, responses, ack); } - private NodeResponse processRemote( + private void processRemote( final Request originalRequest, final String server, final String id, - long requestTimestamp + long requestTimestamp, + int index, + ClusterResponseMerger clusterResponseMerge ) { final HttpRequest request = createClusterRequest(originalRequest, server, id, requestTimestamp); - return sendClusterRequest(request); + sendClusterRequest(request, index, clusterResponseMerge); } private static HttpRequest createClusterRequest( @@ -259,7 +272,8 @@ private static HttpRequest createClusterRequest( HttpRequest.Builder builder = HttpRequest.newBuilder() .uri(URI.create(server + ENTITY_V0_PATH_WITH_ID_PARAM + id)) .header(REPLICA_REQUEST_HEADER, "") - .header(LsmServerUtil.TIMESTAMP_HEADER, String.valueOf(requestTimestamp)); + .header(LsmServerUtil.TIMESTAMP_HEADER, String.valueOf(requestTimestamp)) + .timeout(Duration.ofMillis(CLUSTER_REQUEST_TIMEOUT_MILLISECONDS)); switch (originalRequest.getMethod()) { case METHOD_GET -> builder.GET(); case METHOD_PUT -> { @@ -275,29 +289,47 @@ private static HttpRequest createClusterRequest( return builder.build(); } - private NodeResponse sendClusterRequest( - final HttpRequest request + private void sendClusterRequest( + final HttpRequest request, + final int index, + final ClusterResponseMerger clusterResponseMerger ) { - try { - return new HttpResponseNodeResponse( - clusterClient - .sendAsync(request, HttpResponse.BodyHandlers.ofByteArray()) - .get(CLUSTER_REQUEST_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS) - ); - } catch (InterruptedException e) { - final String clusterUrl = request.uri().toString(); - Thread.currentThread().interrupt(); - log.warn("Current thread was interrupted during processing request to cluster {}", clusterUrl); - return new ReplicaEmptyResponse(HTTP_UNAVAILABLE); - } catch (ExecutionException e) { - final String clusterUrl = request.uri().toString(); - log.error("Unexpected exception occurred while sending request to cluster {}", clusterUrl, e); - return new ReplicaEmptyResponse(HTTP_UNAVAILABLE); - } catch (TimeoutException e) { - final String clusterUrl = request.uri().toString(); - log.warn("Request timeout to cluster server {}", clusterUrl); - return new ReplicaEmptyResponse(HTTP_GATEWAY_TIMEOUT); - } + clusterClient + .sendAsync(request, HttpResponse.BodyHandlers.ofByteArray()) + .thenAcceptAsync(response -> + clusterResponseMerger.addToMerge( + index, + new HttpResponseNodeResponse(response) + ), clusterResponseProcessor) + .exceptionallyAsync(throwable -> { + if (throwable.getCause() instanceof java.net.http.HttpTimeoutException) { + final String clusterUrl = request.uri().toString(); + log.warn("Request timeout to cluster server {}", clusterUrl); + clusterResponseMerger.addToMerge(index, new ReplicaEmptyResponse(HTTP_GATEWAY_TIMEOUT)); + } else { + final String clusterUrl = request.uri().toString(); + log.error( + "Unexpected exception occurred while sending request to cluster {}", + clusterUrl, + throwable + ); + clusterResponseMerger.addToMerge(index, new ReplicaEmptyResponse(HTTP_UNAVAILABLE)); + } + return null; + }, clusterResponseProcessor).state(); + } + + private void processLocalAsync( + Request request, + byte[] key, + String id, + long requestTimestamp, + int i, + ClusterResponseMerger clusterResponseMerger + ) { + localProcessor.execute(() -> + clusterResponseMerger.addToMerge(i, processLocal(request, key, id, requestTimestamp)) + ); } private OneNioNodeResponse processLocal( @@ -406,6 +438,21 @@ public Response handleCompact(Request request) throws IOException { return LSMConstantResponse.ok(request); } + public void handleEntitiesRangeRequest(Request request, LSMCustomSession session) throws IOException { + final String start = request.getParameter("start="); + final String end = request.getParameter("end="); + if (start == null || start.isEmpty() || (end != null && end.isEmpty())) { + log.debug("Bad request: start parameter and end parameter (if it present) should not be empty"); + session.sendResponse(LSMConstantResponse.badRequest(request)); + return; + } + + final MemorySegment startMemorySegment = fromByteArray(start.getBytes(StandardCharsets.UTF_8)); + final MemorySegment endMemorySegment = end == null ? null : fromByteArray(end.getBytes(StandardCharsets.UTF_8)); + Iterator> iterator = dao.get(startMemorySegment, endMemorySegment); + session.sendRangeResponse(new LSMRangeWriter(iterator, LSMConstantResponse.keepAlive(request))); + } + private static MemorySegment fromByteArray(final byte[] data) { return MemorySegment.ofArray(data); } diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/LSMServiceImpl.java b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/LSMServiceImpl.java index 51794a728..f84e566d2 100644 --- a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/LSMServiceImpl.java +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/LSMServiceImpl.java @@ -30,6 +30,14 @@ public class LSMServiceImpl implements Service { private static final long FLUSH_THRESHOLD = 1 << 20; // 1 MB private static final int TERMINATION_TIMEOUT_SECONDS = 20; + private static final int SERVER_EXECUTOR_SERVICE_THREADS_COUNT = 16; + private static final int SERVER_EXECUTOR_SERVICE_QUEUE_SIZE = 1024; + private static final int CLUSTER_HTTP_CLIENT_EXECUTOR_SERVICE_THREADS_COUNT = 16; + private static final int CLUSTER_HTTP_CLIENT_EXECUTOR_SERVICE_QUEUE_SIZE = 1024; + private static final int CLUSTER_RESPONSE_EXECUTOR_SERVICE_THREADS_COUNT = 16; + private static final int CLUSTER_RESPONSE_EXECUTOR_SERVICE_QUEUE_SIZE = 1024; + private static final int LOCAL_REQUEST_EXECUTOR_SERVICE_THREADS_COUNT = 16; + private static final int LOCAL_REQUEST_EXECUTOR_SERVICE_QUEUE_SIZE = 1024; private final ServiceConfig serviceConfig; private LSMServerImpl httpServer; private boolean isRunning; @@ -38,6 +46,8 @@ public class LSMServiceImpl implements Service { private final ConsistentHashingManager consistentHashingManager; private HttpClient clusterClient; private ExecutorService clusterClientExecutorService; + private ExecutorService clusterResponseProcessor; + private ExecutorService localProcessor; public static void main(String[] args) throws IOException, ExecutionException, InterruptedException { Path baseWorkingDir = Path.of("daoWorkingDir"); @@ -96,14 +106,43 @@ public LSMServiceImpl(ServiceConfig serviceConfig) { this.consistentHashingManager = new ConsistentHashingManager(10, serviceConfig.clusterUrls()); } - private static LSMServerImpl createServer( - ServiceConfig serviceConfig, - Dao> dao, - ExecutorService executorService, - ConsistentHashingManager consistentHashingManager, - HttpClient clusterClient + private LSMServerImpl createServer( + Dao> dao ) throws IOException { - return new LSMServerImpl(serviceConfig, dao, executorService, consistentHashingManager, clusterClient); + executorService = createExecutorService( + SERVER_EXECUTOR_SERVICE_THREADS_COUNT, + SERVER_EXECUTOR_SERVICE_QUEUE_SIZE, + "worker" + ); + clusterClientExecutorService = createExecutorService( + CLUSTER_HTTP_CLIENT_EXECUTOR_SERVICE_THREADS_COUNT, + CLUSTER_HTTP_CLIENT_EXECUTOR_SERVICE_QUEUE_SIZE, + "cluster-request" + ); + clusterResponseProcessor = createExecutorService( + CLUSTER_RESPONSE_EXECUTOR_SERVICE_THREADS_COUNT, + CLUSTER_RESPONSE_EXECUTOR_SERVICE_QUEUE_SIZE, + "cluster-response-processor" + ); + localProcessor = createExecutorService( + LOCAL_REQUEST_EXECUTOR_SERVICE_THREADS_COUNT, + LOCAL_REQUEST_EXECUTOR_SERVICE_QUEUE_SIZE, + "local-processor" + ); + + clusterClient = HttpClient.newBuilder() + .executor(clusterClientExecutorService) + .build(); + + return new LSMServerImpl( + serviceConfig, + dao, + executorService, + consistentHashingManager, + clusterClient, + clusterResponseProcessor, + localProcessor + ); } private static Dao> createLSMDao(Path workingDir) { @@ -146,14 +185,7 @@ public synchronized CompletableFuture start() throws IOException { if (isRunning) return CompletableFuture.completedFuture(null); dao = createLSMDao(serviceConfig.workingDir()); - executorService = createExecutorService(16, 1024, "worker"); - clusterClientExecutorService = createExecutorService(16, 1024, "cluster-worker"); - - clusterClient = HttpClient.newBuilder() - .executor(clusterClientExecutorService) - .build(); - - httpServer = createServer(serviceConfig, dao, executorService, consistentHashingManager, clusterClient); + httpServer = createServer(dao); httpServer.start(); isRunning = true; @@ -167,6 +199,8 @@ public synchronized CompletableFuture stop() throws IOException { shutdownHttpClient(clusterClient); shutdownExecutorService(clusterClientExecutorService); + shutdownExecutorService(clusterResponseProcessor); + shutdownExecutorService(localProcessor); shutdownExecutorService(executorService); executorService = null; clusterClient = null; @@ -211,7 +245,7 @@ private static void shutdownHttpClient(HttpClient httpClient) { } } - @ServiceFactory(stage = 4) + @ServiceFactory(stage = 6) public static class LSMServiceFactoryImpl implements ServiceFactory.Factory { @Override public Service create(ServiceConfig config) { diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/dao/MemTable.java index 1d606f38a..c227375f6 100644 --- a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/dao/MemTable.java +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/dao/MemTable.java @@ -4,6 +4,7 @@ import java.lang.foreign.MemorySegment; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.NavigableMap; import java.util.NoSuchElementException; @@ -39,7 +40,12 @@ private Iterator> storageIterator(MemorySegment return storage.tailMap(from).sequencedValues().iterator(); } - return storage.subMap(from, to).sequencedValues().iterator(); + try { + return storage.subMap(from, to).sequencedValues().iterator(); + } catch (IllegalArgumentException illegalArgumentException) { + // we get inconsistent range error when from > to + return Collections.emptyIterator(); + } } public MemTableIterator iterator(MemorySegment from, MemorySegment to, int priorityReduction) { diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/Chunk.java b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/Chunk.java new file mode 100644 index 000000000..10fa2108f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/Chunk.java @@ -0,0 +1,27 @@ +package ru.vk.itmo.test.viktorkorotkikh.http; + +import one.nio.util.ByteArrayBuilder; + +public class Chunk { + private final ByteArrayBuilder byteArrayBuilder; + private final int offset; + private final int length; + + public Chunk(ByteArrayBuilder buffer, int offset) { + this.byteArrayBuilder = buffer; + this.offset = offset; + this.length = buffer.length() - offset; + } + + public byte[] getBytes() { + return byteArrayBuilder.buffer(); + } + + public int offset() { + return offset; + } + + public int length() { + return length; + } +} diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/LSMCustomSession.java b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/LSMCustomSession.java index 7cb755352..dd616a890 100644 --- a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/LSMCustomSession.java +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/LSMCustomSession.java @@ -10,6 +10,8 @@ import java.io.IOException; public class LSMCustomSession extends HttpSession { + private LSMRangeWriter lsmRangeWriter; + public LSMCustomSession(Socket socket, HttpServer server) { super(socket, server); } @@ -39,4 +41,40 @@ public synchronized void sendResponse(Response response) throws IOException { } } } + + @Override + protected void processWrite() throws Exception { + super.processWrite(); + sendNextRangeChunks(); + } + + public void sendRangeResponse(LSMRangeWriter lsmRangeWriter) throws IOException { + Request handling = this.handling; + if (handling == null) { + throw new IOException("Out of order response"); + } + server.incRequestsProcessed(); + + this.lsmRangeWriter = lsmRangeWriter; + sendNextRangeChunks(); + } + + private void sendNextRangeChunks() throws IOException { + if (lsmRangeWriter == null) return; + while (queueHead == null && lsmRangeWriter.hasChunks()) { + Chunk chunk = lsmRangeWriter.nextChunk(); + write(chunk.getBytes(), chunk.offset(), chunk.length()); + } + + if (!lsmRangeWriter.hasChunks()) { + this.handling = pipeline.pollFirst(); + if (handling != null) { + if (handling == FIN) { + scheduleClose(); + } else { + server.handleRequest(handling, this); + } + } + } + } } diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/LSMRangeWriter.java b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/LSMRangeWriter.java new file mode 100644 index 000000000..67ec9ef76 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/LSMRangeWriter.java @@ -0,0 +1,168 @@ +package ru.vk.itmo.test.viktorkorotkikh.http; + +import one.nio.util.ByteArrayBuilder; +import one.nio.util.Utf8; +import ru.vk.itmo.test.viktorkorotkikh.dao.TimestampedEntry; +import ru.vk.itmo.test.viktorkorotkikh.util.LsmServerUtil; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; + +import static ru.vk.itmo.test.viktorkorotkikh.util.LSMConstantResponse.CHUNKED_RESPONSE_CLOSE_WITH_HEADERS_BYTES; +import static ru.vk.itmo.test.viktorkorotkikh.util.LSMConstantResponse.CHUNKED_RESPONSE_KEEP_ALIVE_WITH_HEADERS_BYTES; + +public class LSMRangeWriter { + private static final int BUFFER_SIZE = 8192; + private static final int CHUNK_SIZE_BYTES = 16; // long 8 bytes in hex + private static final byte[] CRLF_BYTES = new byte[]{'\r', '\n'}; + private final Iterator> entryIterator; + private final boolean keepAlive; + private final ByteArrayBuilder buffer; + private long chunkSize = 0; + private TimestampedEntry lastEntry; + private int lastEntryOffset; + private NextOperation nextOperation = NextOperation.WRITE_HEADERS; + + public LSMRangeWriter(Iterator> entryIterator, boolean keepAlive) { + this.entryIterator = entryIterator; + this.keepAlive = keepAlive; + this.buffer = new ByteArrayBuilder(BUFFER_SIZE); + } + + public boolean hasChunks() { + return entryIterator.hasNext() || nextOperation != null; + } + + public Chunk nextChunk() { + chunkSize = 0; + int chunkHeaderOffset = CHUNK_SIZE_BYTES + CRLF_BYTES.length; + boolean writeHttpHeaders = false; + if (nextOperation == NextOperation.WRITE_HEADERS) { + chunkHeaderOffset += keepAlive + ? CHUNKED_RESPONSE_KEEP_ALIVE_WITH_HEADERS_BYTES.length + : CHUNKED_RESPONSE_CLOSE_WITH_HEADERS_BYTES.length; + writeHttpHeaders = true; + } + buffer.setLength(chunkHeaderOffset); + + if (!appendNextOperationBytes()) { + int chunkOffset = writeChunkHeader(chunkHeaderOffset, writeHttpHeaders); + return new Chunk(buffer, chunkOffset); + } + + while (entryIterator.hasNext()) { + lastEntry = entryIterator.next(); + lastEntryOffset = 0; + + nextOperation = NextOperation.WRITE_KEY; + if (!appendNextOperationBytes()) { + int chunkOffset = writeChunkHeader(chunkHeaderOffset, writeHttpHeaders); + return new Chunk(buffer, chunkOffset); + } + } + + int chunkOffset = writeChunkHeader(chunkHeaderOffset, writeHttpHeaders); + if (chunkSize == 0) { + appendCLRF(buffer); + } else { + buffer.append(Long.toHexString(0)); + appendCLRF(buffer); + appendCLRF(buffer); + } + + nextOperation = null; + + return new Chunk(buffer, chunkOffset); + } + + private int writeChunkHeader(int chunkSizeOffset, boolean writeHttpHeaders) { + String chunkSizeHexString = Long.toHexString(chunkSize); + int chunkSizeHexStringLength = Utf8.length(chunkSizeHexString); + int chunkSizeStart = chunkSizeOffset - chunkSizeHexStringLength - CRLF_BYTES.length; + Utf8.write(chunkSizeHexString, buffer.buffer(), chunkSizeStart); + + System.arraycopy( + CRLF_BYTES, + 0, + buffer.buffer(), + chunkSizeStart + chunkSizeHexStringLength, + CRLF_BYTES.length + ); + + int headersOffset = 0; + if (writeHttpHeaders) { + byte[] headers = keepAlive + ? CHUNKED_RESPONSE_KEEP_ALIVE_WITH_HEADERS_BYTES + : CHUNKED_RESPONSE_CLOSE_WITH_HEADERS_BYTES; + System.arraycopy(headers, 0, buffer.buffer(), chunkSizeStart - headers.length, headers.length); + headersOffset = headers.length; + } + appendCLRF(buffer); + return chunkSizeStart - headersOffset; + } + + private boolean appendNextOperationBytes() { + return switch (nextOperation) { + case WRITE_HEADERS -> true; // we write headers later in writeChunkSize method + case WRITE_KEY -> { + if (buffer.length() + CRLF_BYTES.length >= buffer.capacity()) { + yield false; + } + + if (appendMemorySegment(lastEntry.key(), lastEntryOffset, true)) { + nextOperation = NextOperation.WRITE_DELIMITERS_AFTER_KEY; + yield appendNextOperationBytes(); + } + yield false; + } + case WRITE_DELIMITERS_AFTER_KEY -> { + if (buffer.length() + 1 + CRLF_BYTES.length <= buffer.capacity()) { + chunkSize += 1; + buffer.append('\n'); + nextOperation = NextOperation.WRITE_VALUE; + yield appendNextOperationBytes(); + } + yield false; + } + case WRITE_VALUE -> { + if (buffer.length() + CRLF_BYTES.length >= buffer.capacity()) { + yield false; + } + if (appendMemorySegment(lastEntry.value(), lastEntryOffset, false)) { + nextOperation = null; + yield true; + } + yield false; + } + case null -> true; + }; + } + + private boolean appendMemorySegment(MemorySegment memorySegment, int memorySegmentOffset, boolean isKey) { + int writtenMemorySegment = LsmServerUtil.copyMemorySegmentToByteArrayBuilder( + memorySegment, + memorySegmentOffset, + buffer, + buffer.capacity() - CRLF_BYTES.length + ); + chunkSize += writtenMemorySegment; + + if (lastEntryOffset + writtenMemorySegment < memorySegment.byteSize()) { + nextOperation = isKey ? NextOperation.WRITE_KEY : NextOperation.WRITE_VALUE; + lastEntryOffset += writtenMemorySegment; + return false; + } + lastEntryOffset = 0; + return true; + } + + private static void appendCLRF(final ByteArrayBuilder builder) { + builder.append(CRLF_BYTES); + } + + private enum NextOperation { + WRITE_HEADERS, + WRITE_KEY, WRITE_DELIMITERS_AFTER_KEY, + WRITE_VALUE + } +} diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/LSMServerResponseWithMemorySegment.java b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/LSMServerResponseWithMemorySegment.java index ddfc00463..408f2437f 100644 --- a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/LSMServerResponseWithMemorySegment.java +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/http/LSMServerResponseWithMemorySegment.java @@ -3,6 +3,7 @@ import one.nio.http.Response; import one.nio.util.ByteArrayBuilder; import one.nio.util.Utf8; +import ru.vk.itmo.test.viktorkorotkikh.util.LsmServerUtil; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; @@ -46,19 +47,8 @@ public byte[] toBytes(boolean includeBody) { } builder.append('\r').append('\n'); if (includeBody) { - writeBody(memorySegmentBody, builder); + LsmServerUtil.copyMemorySegmentToByteArrayBuilder(memorySegmentBody, builder); } return builder.buffer(); } - - private static void writeBody(MemorySegment memorySegmentBody, ByteArrayBuilder builder) { - MemorySegment.copy( - memorySegmentBody, - ValueLayout.JAVA_BYTE, - 0L, - builder.buffer(), - builder.length(), - (int) memorySegmentBody.byteSize() - ); - } } diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-30k.txt b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-30k.txt new file mode 100644 index 000000000..9495e0c3e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-30k.txt @@ -0,0 +1,130 @@ +Running 1m test @ http://localhost:8080 + 4 threads and 64 connections + Thread calibration: mean lat.: 2737.519ms, rate sampling interval: 10002ms + Thread calibration: mean lat.: 2751.135ms, rate sampling interval: 10027ms + Thread calibration: mean lat.: 2748.293ms, rate sampling interval: 10010ms + Thread calibration: mean lat.: 2738.065ms, rate sampling interval: 10010ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 19.15s 7.91s 33.08s 57.83% + Req/Sec 3.40k 14.14 3.42k 62.50% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 19.12s + 75.000% 25.99s + 90.000% 30.21s + 99.000% 32.65s + 99.900% 32.92s + 99.990% 33.01s + 99.999% 33.08s +100.000% 33.10s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 5480.447 0.000000 2 1.00 + 8237.055 0.100000 67690 1.11 + 10919.935 0.200000 135297 1.25 + 13664.255 0.300000 202876 1.43 + 16375.807 0.400000 270561 1.67 + 19120.127 0.500000 338390 2.00 + 20512.767 0.550000 372151 2.22 + 21856.255 0.600000 405822 2.50 + 23216.127 0.650000 439825 2.86 + 24608.767 0.700000 473661 3.33 + 25985.023 0.750000 507440 4.00 + 26656.767 0.775000 524135 4.44 + 27328.511 0.800000 541004 5.00 + 28033.023 0.825000 558037 5.71 + 28721.151 0.850000 575082 6.67 + 29392.895 0.875000 591835 8.00 + 29868.031 0.887500 600424 8.89 + 30212.095 0.900000 608795 10.00 + 30556.159 0.912500 617331 11.43 + 30900.223 0.925000 625807 13.33 + 31227.903 0.937500 634113 16.00 + 31391.743 0.943750 638349 17.78 + 31571.967 0.950000 642788 20.00 + 31735.807 0.956250 646720 22.86 + 31916.031 0.962500 651240 26.67 + 32079.871 0.968750 655309 32.00 + 32161.791 0.971875 657371 35.56 + 32243.711 0.975000 659403 40.00 + 32325.631 0.978125 661460 45.71 + 32407.551 0.981250 663545 53.33 + 32505.855 0.984375 666066 64.00 + 32538.623 0.985938 666853 71.11 + 32587.775 0.987500 668100 80.00 + 32620.543 0.989062 668938 91.43 + 32669.695 0.990625 670189 106.67 + 32702.463 0.992188 671000 128.00 + 32735.231 0.992969 671833 142.22 + 32751.615 0.993750 672240 160.00 + 32767.999 0.994531 672639 182.86 + 32800.767 0.995313 673431 213.33 + 32817.151 0.996094 673820 256.00 + 32833.535 0.996484 674200 284.44 + 32833.535 0.996875 674200 320.00 + 32849.919 0.997266 674555 365.71 + 32866.303 0.997656 674889 426.67 + 32882.687 0.998047 675210 512.00 + 32882.687 0.998242 675210 568.89 + 32882.687 0.998437 675210 640.00 + 32899.071 0.998633 675452 731.43 + 32899.071 0.998828 675452 853.33 + 32915.455 0.999023 675655 1024.00 + 32915.455 0.999121 675655 1137.78 + 32931.839 0.999219 675812 1280.00 + 32931.839 0.999316 675812 1462.86 + 32948.223 0.999414 675942 1706.67 + 32948.223 0.999512 675942 2048.00 + 32948.223 0.999561 675942 2275.56 + 32964.607 0.999609 676038 2560.00 + 32964.607 0.999658 676038 2925.71 + 32964.607 0.999707 676038 3413.33 + 32980.991 0.999756 676093 4096.00 + 32980.991 0.999780 676093 4551.11 + 32980.991 0.999805 676093 5120.00 + 32997.375 0.999829 676131 5851.43 + 32997.375 0.999854 676131 6826.67 + 33013.759 0.999878 676155 8192.00 + 33013.759 0.999890 676155 9102.22 + 33013.759 0.999902 676155 10240.00 + 33030.143 0.999915 676174 11702.86 + 33030.143 0.999927 676174 13653.33 + 33046.527 0.999939 676202 16384.00 + 33046.527 0.999945 676202 18204.44 + 33046.527 0.999951 676202 20480.00 + 33046.527 0.999957 676202 23405.71 + 33046.527 0.999963 676202 27306.67 + 33046.527 0.999969 676202 32768.00 + 33062.911 0.999973 676213 36408.89 + 33062.911 0.999976 676213 40960.00 + 33062.911 0.999979 676213 46811.43 + 33062.911 0.999982 676213 54613.33 + 33062.911 0.999985 676213 65536.00 + 33062.911 0.999986 676213 72817.78 + 33062.911 0.999988 676213 81920.00 + 33079.295 0.999989 676220 93622.86 + 33079.295 0.999991 676220 109226.67 + 33079.295 0.999992 676220 131072.00 + 33079.295 0.999993 676220 145635.56 + 33079.295 0.999994 676220 163840.00 + 33079.295 0.999995 676220 187245.71 + 33079.295 0.999995 676220 218453.33 + 33079.295 0.999996 676220 262144.00 + 33079.295 0.999997 676220 291271.11 + 33079.295 0.999997 676220 327680.00 + 33079.295 0.999997 676220 374491.43 + 33079.295 0.999998 676220 436906.67 + 33079.295 0.999998 676220 524288.00 + 33079.295 0.999998 676220 582542.22 + 33079.295 0.999998 676220 655360.00 + 33095.679 0.999999 676221 748982.86 + 33095.679 1.000000 676221 inf +#[Mean = 19147.619, StdDeviation = 7905.395] +#[Max = 33079.296, Total count = 676221] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 811430 requests in 1.00m, 39.88MB read + Non-2xx or 3xx responses: 31216 +Requests/sec: 13523.69 +Transfer/sec: 680.56KB diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-af-alloc.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-af-alloc.html new file mode 100644 index 000000000..96bea451c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-af-alloc.html @@ -0,0 +1,3279 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-af-cpu.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-af-cpu.html new file mode 100644 index 000000000..fa936084b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-af-cpu.html @@ -0,0 +1,5748 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-af-lock.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-af-lock.html new file mode 100644 index 000000000..362d8107d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-af-lock.html @@ -0,0 +1,1059 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-after-fix.txt b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-after-fix.txt new file mode 100644 index 000000000..8fa882837 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-after-fix.txt @@ -0,0 +1,126 @@ +Running 1m test @ http://localhost:8080 + 4 threads and 64 connections + Thread calibration: mean lat.: 1.370ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 1.458ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 1.332ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 1.344ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.35ms 607.24us 17.31ms 69.39% + Req/Sec 2.11k 161.56 3.44k 69.74% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.30ms + 75.000% 1.72ms + 90.000% 2.13ms + 99.000% 2.95ms + 99.900% 3.86ms + 99.990% 10.17ms + 99.999% 13.06ms +100.000% 17.33ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.116 0.000000 1 1.00 + 0.627 0.100000 40026 1.11 + 0.829 0.200000 79998 1.25 + 0.994 0.300000 120015 1.43 + 1.147 0.400000 159823 1.67 + 1.297 0.500000 199814 2.00 + 1.374 0.550000 219856 2.22 + 1.453 0.600000 239738 2.50 + 1.536 0.650000 259632 2.86 + 1.626 0.700000 279634 3.33 + 1.723 0.750000 299547 4.00 + 1.775 0.775000 309515 4.44 + 1.833 0.800000 319574 5.00 + 1.895 0.825000 329614 5.71 + 1.964 0.850000 339565 6.67 + 2.042 0.875000 349468 8.00 + 2.087 0.887500 354520 8.89 + 2.135 0.900000 359530 10.00 + 2.189 0.912500 364522 11.43 + 2.249 0.925000 369560 13.33 + 2.317 0.937500 374402 16.00 + 2.357 0.943750 376981 17.78 + 2.399 0.950000 379429 20.00 + 2.449 0.956250 381888 22.86 + 2.505 0.962500 384397 26.67 + 2.569 0.968750 386929 32.00 + 2.605 0.971875 388166 35.56 + 2.645 0.975000 389378 40.00 + 2.691 0.978125 390633 45.71 + 2.743 0.981250 391873 53.33 + 2.803 0.984375 393133 64.00 + 2.835 0.985938 393745 71.11 + 2.875 0.987500 394396 80.00 + 2.921 0.989062 395000 91.43 + 2.973 0.990625 395623 106.67 + 3.035 0.992188 396249 128.00 + 3.071 0.992969 396559 142.22 + 3.113 0.993750 396874 160.00 + 3.157 0.994531 397186 182.86 + 3.209 0.995313 397497 213.33 + 3.267 0.996094 397807 256.00 + 3.311 0.996484 397959 284.44 + 3.355 0.996875 398114 320.00 + 3.405 0.997266 398272 365.71 + 3.467 0.997656 398427 426.67 + 3.539 0.998047 398580 512.00 + 3.579 0.998242 398658 568.89 + 3.623 0.998437 398739 640.00 + 3.675 0.998633 398816 731.43 + 3.765 0.998828 398893 853.33 + 3.875 0.999023 398970 1024.00 + 3.943 0.999121 399009 1137.78 + 4.077 0.999219 399049 1280.00 + 4.187 0.999316 399087 1462.86 + 4.319 0.999414 399126 1706.67 + 4.807 0.999512 399165 2048.00 + 5.103 0.999561 399185 2275.56 + 5.491 0.999609 399204 2560.00 + 6.031 0.999658 399224 2925.71 + 6.495 0.999707 399243 3413.33 + 7.491 0.999756 399263 4096.00 + 7.903 0.999780 399273 4551.11 + 8.311 0.999805 399282 5120.00 + 8.999 0.999829 399292 5851.43 + 9.223 0.999854 399302 6826.67 + 9.775 0.999878 399313 8192.00 + 9.863 0.999890 399317 9102.22 + 10.191 0.999902 399321 10240.00 + 10.567 0.999915 399326 11702.86 + 10.799 0.999927 399331 13653.33 + 11.055 0.999939 399336 16384.00 + 11.223 0.999945 399339 18204.44 + 11.391 0.999951 399341 20480.00 + 11.559 0.999957 399344 23405.71 + 11.623 0.999963 399346 27306.67 + 11.727 0.999969 399348 32768.00 + 12.159 0.999973 399350 36408.89 + 12.175 0.999976 399351 40960.00 + 12.335 0.999979 399352 46811.43 + 12.551 0.999982 399353 54613.33 + 12.639 0.999985 399354 65536.00 + 12.927 0.999986 399355 72817.78 + 13.055 0.999988 399356 81920.00 + 13.055 0.999989 399356 93622.86 + 13.391 0.999991 399357 109226.67 + 13.391 0.999992 399357 131072.00 + 13.495 0.999993 399358 145635.56 + 13.495 0.999994 399358 163840.00 + 13.495 0.999995 399358 187245.71 + 13.759 0.999995 399359 218453.33 + 13.759 0.999996 399359 262144.00 + 13.759 0.999997 399359 291271.11 + 13.759 0.999997 399359 327680.00 + 13.759 0.999997 399359 374491.43 + 17.327 0.999998 399360 436906.67 + 17.327 1.000000 399360 inf +#[Mean = 1.354, StdDeviation = 0.607] +#[Max = 17.312, Total count = 399360] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 479718 requests in 1.00m, 23.57MB read + Non-2xx or 3xx responses: 18126 +Requests/sec: 7995.25 +Transfer/sec: 402.26KB diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-alloc.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-alloc.html new file mode 100644 index 000000000..25099f087 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-alloc.html @@ -0,0 +1,3209 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-cpu.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-cpu.html new file mode 100644 index 000000000..67c0127ab --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-cpu.html @@ -0,0 +1,6163 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-histogram.png b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-histogram.png new file mode 100644 index 000000000..27a487777 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-histogram.png differ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-lock.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-lock.html new file mode 100644 index 000000000..19079183b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k-lock.html @@ -0,0 +1,1070 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k.txt b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k.txt new file mode 100644 index 000000000..81c083ede --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/GET-8k.txt @@ -0,0 +1,126 @@ +Running 1m test @ http://localhost:8080 + 4 threads and 64 connections + Thread calibration: mean lat.: 1.395ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 1.466ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 1.339ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 1.433ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.24ms 620.90us 15.56ms 70.23% + Req/Sec 2.11k 153.09 4.00k 73.39% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.17ms + 75.000% 1.60ms + 90.000% 2.03ms + 99.000% 2.96ms + 99.900% 3.96ms + 99.990% 10.31ms + 99.999% 13.86ms +100.000% 15.57ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.114 0.000000 3 1.00 + 0.520 0.100000 40128 1.11 + 0.709 0.200000 79905 1.25 + 0.872 0.300000 119981 1.43 + 1.023 0.400000 159959 1.67 + 1.172 0.500000 199919 2.00 + 1.248 0.550000 219762 2.22 + 1.328 0.600000 239629 2.50 + 1.413 0.650000 259611 2.86 + 1.504 0.700000 279710 3.33 + 1.604 0.750000 299618 4.00 + 1.659 0.775000 309589 4.44 + 1.719 0.800000 319579 5.00 + 1.784 0.825000 329543 5.71 + 1.855 0.850000 339467 6.67 + 1.938 0.875000 349530 8.00 + 1.984 0.887500 354494 8.89 + 2.034 0.900000 359435 10.00 + 2.091 0.912500 364575 11.43 + 2.155 0.925000 369434 13.33 + 2.233 0.937500 374521 16.00 + 2.277 0.943750 376997 17.78 + 2.327 0.950000 379504 20.00 + 2.381 0.956250 381926 22.86 + 2.445 0.962500 384414 26.67 + 2.519 0.968750 386912 32.00 + 2.561 0.971875 388144 35.56 + 2.611 0.975000 389431 40.00 + 2.663 0.978125 390664 45.71 + 2.723 0.981250 391885 53.33 + 2.795 0.984375 393158 64.00 + 2.833 0.985938 393779 71.11 + 2.877 0.987500 394389 80.00 + 2.927 0.989062 395011 91.43 + 2.989 0.990625 395635 106.67 + 3.059 0.992188 396251 128.00 + 3.101 0.992969 396561 142.22 + 3.145 0.993750 396873 160.00 + 3.199 0.994531 397184 182.86 + 3.257 0.995313 397498 213.33 + 3.325 0.996094 397810 256.00 + 3.373 0.996484 397968 284.44 + 3.421 0.996875 398118 320.00 + 3.475 0.997266 398277 365.71 + 3.535 0.997656 398431 426.67 + 3.619 0.998047 398587 512.00 + 3.665 0.998242 398665 568.89 + 3.731 0.998437 398743 640.00 + 3.795 0.998633 398821 731.43 + 3.869 0.998828 398902 853.33 + 3.979 0.999023 398976 1024.00 + 4.063 0.999121 399015 1137.78 + 4.179 0.999219 399058 1280.00 + 4.379 0.999316 399096 1462.86 + 4.639 0.999414 399134 1706.67 + 5.339 0.999512 399171 2048.00 + 5.751 0.999561 399191 2275.56 + 6.127 0.999609 399210 2560.00 + 6.687 0.999658 399230 2925.71 + 7.347 0.999707 399249 3413.33 + 8.015 0.999756 399269 4096.00 + 8.567 0.999780 399279 4551.11 + 8.927 0.999805 399288 5120.00 + 9.119 0.999829 399298 5851.43 + 9.703 0.999854 399308 6826.67 + 10.063 0.999878 399318 8192.00 + 10.175 0.999890 399323 9102.22 + 10.335 0.999902 399327 10240.00 + 10.503 0.999915 399332 11702.86 + 10.695 0.999927 399337 13653.33 + 10.927 0.999939 399342 16384.00 + 11.119 0.999945 399345 18204.44 + 11.231 0.999951 399347 20480.00 + 11.519 0.999957 399349 23405.71 + 11.799 0.999963 399352 27306.67 + 11.991 0.999969 399354 32768.00 + 12.367 0.999973 399356 36408.89 + 12.703 0.999976 399357 40960.00 + 12.719 0.999979 399358 46811.43 + 13.103 0.999982 399359 54613.33 + 13.263 0.999985 399360 65536.00 + 13.527 0.999986 399361 72817.78 + 13.863 0.999988 399362 81920.00 + 13.863 0.999989 399362 93622.86 + 14.231 0.999991 399363 109226.67 + 14.231 0.999992 399363 131072.00 + 14.271 0.999993 399364 145635.56 + 14.271 0.999994 399364 163840.00 + 14.271 0.999995 399364 187245.71 + 14.895 0.999995 399365 218453.33 + 14.895 0.999996 399365 262144.00 + 14.895 0.999997 399365 291271.11 + 14.895 0.999997 399365 327680.00 + 14.895 0.999997 399365 374491.43 + 15.567 0.999998 399366 436906.67 + 15.567 1.000000 399366 inf +#[Mean = 1.244, StdDeviation = 0.621] +#[Max = 15.560, Total count = 399366] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 479724 requests in 1.00m, 23.58MB read + Non-2xx or 3xx responses: 18709 +Requests/sec: 7995.27 +Transfer/sec: 402.43KB diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-25k-histogram.png b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-25k-histogram.png new file mode 100644 index 000000000..559d7307a Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-25k-histogram.png differ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-25k.txt b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-25k.txt new file mode 100644 index 000000000..339fa7c53 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-25k.txt @@ -0,0 +1,134 @@ +Running 1m test @ http://localhost:8080 + 4 threads and 64 connections + Thread calibration: mean lat.: 216.928ms, rate sampling interval: 1547ms + Thread calibration: mean lat.: 219.273ms, rate sampling interval: 1563ms + Thread calibration: mean lat.: 51.288ms, rate sampling interval: 470ms + Thread calibration: mean lat.: 217.368ms, rate sampling interval: 1554ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.35ms 609.01us 8.03ms 70.39% + Req/Sec 6.25k 8.63 6.28k 76.12% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.27ms + 75.000% 1.70ms + 90.000% 2.13ms + 99.000% 3.20ms + 99.900% 4.40ms + 99.990% 5.68ms + 99.999% 7.04ms +100.000% 8.03ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.107 0.000000 1 1.00 + 0.645 0.100000 125277 1.11 + 0.826 0.200000 249850 1.25 + 0.978 0.300000 375123 1.43 + 1.123 0.400000 499386 1.67 + 1.271 0.500000 624719 2.00 + 1.348 0.550000 687198 2.22 + 1.427 0.600000 749034 2.50 + 1.511 0.650000 811413 2.86 + 1.601 0.700000 874023 3.33 + 1.701 0.750000 936253 4.00 + 1.756 0.775000 967440 4.44 + 1.816 0.800000 998699 5.00 + 1.880 0.825000 1029609 5.71 + 1.953 0.850000 1061037 6.67 + 2.035 0.875000 1092070 8.00 + 2.081 0.887500 1107614 8.89 + 2.133 0.900000 1123722 10.00 + 2.189 0.912500 1139263 11.43 + 2.251 0.925000 1154665 13.33 + 2.323 0.937500 1170175 16.00 + 2.365 0.943750 1177968 17.78 + 2.411 0.950000 1185622 20.00 + 2.465 0.956250 1193543 22.86 + 2.527 0.962500 1201322 26.67 + 2.603 0.968750 1209133 32.00 + 2.649 0.971875 1212961 35.56 + 2.701 0.975000 1216901 40.00 + 2.765 0.978125 1220762 45.71 + 2.839 0.981250 1224590 53.33 + 2.939 0.984375 1228503 64.00 + 2.999 0.985938 1230467 71.11 + 3.069 0.987500 1232421 80.00 + 3.147 0.989062 1234353 91.43 + 3.239 0.990625 1236288 106.67 + 3.345 0.992188 1238243 128.00 + 3.409 0.992969 1239214 142.22 + 3.481 0.993750 1240195 160.00 + 3.555 0.994531 1241174 182.86 + 3.645 0.995313 1242144 213.33 + 3.741 0.996094 1243107 256.00 + 3.797 0.996484 1243602 284.44 + 3.865 0.996875 1244089 320.00 + 3.933 0.997266 1244579 365.71 + 4.007 0.997656 1245063 426.67 + 4.099 0.998047 1245561 512.00 + 4.147 0.998242 1245783 568.89 + 4.199 0.998437 1246032 640.00 + 4.263 0.998633 1246275 731.43 + 4.331 0.998828 1246519 853.33 + 4.407 0.999023 1246767 1024.00 + 4.451 0.999121 1246880 1137.78 + 4.519 0.999219 1247006 1280.00 + 4.587 0.999316 1247125 1462.86 + 4.659 0.999414 1247246 1706.67 + 4.755 0.999512 1247367 2048.00 + 4.803 0.999561 1247429 2275.56 + 4.867 0.999609 1247490 2560.00 + 4.935 0.999658 1247550 2925.71 + 5.031 0.999707 1247611 3413.33 + 5.119 0.999756 1247672 4096.00 + 5.183 0.999780 1247702 4551.11 + 5.235 0.999805 1247733 5120.00 + 5.299 0.999829 1247764 5851.43 + 5.403 0.999854 1247795 6826.67 + 5.523 0.999878 1247824 8192.00 + 5.595 0.999890 1247839 9102.22 + 5.715 0.999902 1247856 10240.00 + 5.759 0.999915 1247871 11702.86 + 5.815 0.999927 1247885 13653.33 + 5.935 0.999939 1247900 16384.00 + 5.979 0.999945 1247908 18204.44 + 6.051 0.999951 1247916 20480.00 + 6.143 0.999957 1247923 23405.71 + 6.315 0.999963 1247931 27306.67 + 6.531 0.999969 1247938 32768.00 + 6.667 0.999973 1247942 36408.89 + 6.691 0.999976 1247946 40960.00 + 6.735 0.999979 1247950 46811.43 + 6.795 0.999982 1247954 54613.33 + 6.895 0.999985 1247957 65536.00 + 6.963 0.999986 1247959 72817.78 + 6.991 0.999988 1247961 81920.00 + 7.015 0.999989 1247963 93622.86 + 7.135 0.999991 1247965 109226.67 + 7.231 0.999992 1247967 131072.00 + 7.335 0.999993 1247968 145635.56 + 7.375 0.999994 1247969 163840.00 + 7.379 0.999995 1247970 187245.71 + 7.471 0.999995 1247971 218453.33 + 7.519 0.999996 1247972 262144.00 + 7.519 0.999997 1247972 291271.11 + 7.563 0.999997 1247973 327680.00 + 7.563 0.999997 1247973 374491.43 + 7.627 0.999998 1247974 436906.67 + 7.627 0.999998 1247974 524288.00 + 7.627 0.999998 1247974 582542.22 + 7.795 0.999998 1247975 655360.00 + 7.795 0.999999 1247975 748982.86 + 7.795 0.999999 1247975 873813.33 + 7.795 0.999999 1247975 1048576.00 + 7.795 0.999999 1247975 1165084.44 + 8.035 0.999999 1247976 1310720.00 + 8.035 1.000000 1247976 inf +#[Mean = 1.348, StdDeviation = 0.609] +#[Max = 8.032, Total count = 1247976] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 1492888 requests in 1.00m, 95.39MB read + Non-2xx or 3xx responses: 7 +Requests/sec: 24881.45 +Transfer/sec: 1.59MB diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-af-alloc.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-af-alloc.html new file mode 100644 index 000000000..27c1f4187 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-af-alloc.html @@ -0,0 +1,3276 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-af-cpu.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-af-cpu.html new file mode 100644 index 000000000..8113f6d91 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-af-cpu.html @@ -0,0 +1,7960 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-af-lock.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-af-lock.html new file mode 100644 index 000000000..61761229b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-af-lock.html @@ -0,0 +1,956 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-after-fix.txt b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-after-fix.txt new file mode 100644 index 000000000..eb7018905 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-after-fix.txt @@ -0,0 +1,135 @@ +Running 1m test @ http://localhost:8080 + 4 threads and 64 connections + Thread calibration: mean lat.: 5.188ms, rate sampling interval: 14ms + Thread calibration: mean lat.: 5.123ms, rate sampling interval: 14ms + Thread calibration: mean lat.: 5.185ms, rate sampling interval: 13ms + Thread calibration: mean lat.: 5.544ms, rate sampling interval: 24ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.25ms 1.34ms 13.90ms 82.68% + Req/Sec 8.16k 672.58 10.50k 71.96% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.87ms + 75.000% 2.62ms + 90.000% 3.92ms + 99.000% 7.36ms + 99.900% 9.93ms + 99.990% 11.80ms + 99.999% 12.97ms +100.000% 13.91ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.142 0.000000 1 1.00 + 1.076 0.100000 157624 1.11 + 1.313 0.200000 314774 1.25 + 1.504 0.300000 472309 1.43 + 1.683 0.400000 629840 1.67 + 1.870 0.500000 786517 2.00 + 1.977 0.550000 865134 2.22 + 2.099 0.600000 944424 2.50 + 2.241 0.650000 1022662 2.86 + 2.411 0.700000 1101193 3.33 + 2.623 0.750000 1179734 4.00 + 2.753 0.775000 1218685 4.44 + 2.909 0.800000 1258248 5.00 + 3.093 0.825000 1297506 5.71 + 3.311 0.850000 1336732 6.67 + 3.581 0.875000 1376003 8.00 + 3.739 0.887500 1395671 8.89 + 3.919 0.900000 1415277 10.00 + 4.131 0.912500 1434906 11.43 + 4.383 0.925000 1454585 13.33 + 4.695 0.937500 1474328 16.00 + 4.875 0.943750 1484062 17.78 + 5.079 0.950000 1493773 20.00 + 5.307 0.956250 1503714 22.86 + 5.555 0.962500 1513515 26.67 + 5.827 0.968750 1523292 32.00 + 5.983 0.971875 1528249 35.56 + 6.147 0.975000 1533100 40.00 + 6.327 0.978125 1537997 45.71 + 6.535 0.981250 1542934 53.33 + 6.779 0.984375 1547833 64.00 + 6.919 0.985938 1550276 71.11 + 7.075 0.987500 1552741 80.00 + 7.251 0.989062 1555206 91.43 + 7.447 0.990625 1557684 106.67 + 7.667 0.992188 1560101 128.00 + 7.791 0.992969 1561365 142.22 + 7.931 0.993750 1562591 160.00 + 8.083 0.994531 1563794 182.86 + 8.271 0.995313 1565038 213.33 + 8.503 0.996094 1566263 256.00 + 8.615 0.996484 1566862 284.44 + 8.743 0.996875 1567487 320.00 + 8.887 0.997266 1568104 365.71 + 9.055 0.997656 1568700 426.67 + 9.263 0.998047 1569329 512.00 + 9.375 0.998242 1569623 568.89 + 9.503 0.998437 1569943 640.00 + 9.647 0.998633 1570241 731.43 + 9.783 0.998828 1570544 853.33 + 9.951 0.999023 1570857 1024.00 + 10.047 0.999121 1571012 1137.78 + 10.143 0.999219 1571166 1280.00 + 10.263 0.999316 1571310 1462.86 + 10.407 0.999414 1571469 1706.67 + 10.583 0.999512 1571623 2048.00 + 10.663 0.999561 1571700 2275.56 + 10.751 0.999609 1571769 2560.00 + 10.847 0.999658 1571846 2925.71 + 10.991 0.999707 1571923 3413.33 + 11.143 0.999756 1572002 4096.00 + 11.215 0.999780 1572039 4551.11 + 11.327 0.999805 1572077 5120.00 + 11.415 0.999829 1572117 5851.43 + 11.543 0.999854 1572153 6826.67 + 11.663 0.999878 1572194 8192.00 + 11.719 0.999890 1572213 9102.22 + 11.823 0.999902 1572230 10240.00 + 11.887 0.999915 1572252 11702.86 + 11.967 0.999927 1572268 13653.33 + 12.031 0.999939 1572289 16384.00 + 12.071 0.999945 1572297 18204.44 + 12.135 0.999951 1572307 20480.00 + 12.239 0.999957 1572317 23405.71 + 12.295 0.999963 1572327 27306.67 + 12.407 0.999969 1572336 32768.00 + 12.495 0.999973 1572340 36408.89 + 12.559 0.999976 1572345 40960.00 + 12.583 0.999979 1572350 46811.43 + 12.735 0.999982 1572355 54613.33 + 12.831 0.999985 1572360 65536.00 + 12.847 0.999986 1572362 72817.78 + 12.927 0.999988 1572364 81920.00 + 12.967 0.999989 1572368 93622.86 + 13.079 0.999991 1572369 109226.67 + 13.207 0.999992 1572372 131072.00 + 13.223 0.999993 1572373 145635.56 + 13.247 0.999994 1572374 163840.00 + 13.287 0.999995 1572375 187245.71 + 13.383 0.999995 1572376 218453.33 + 13.527 0.999996 1572378 262144.00 + 13.527 0.999997 1572378 291271.11 + 13.631 0.999997 1572379 327680.00 + 13.631 0.999997 1572379 374491.43 + 13.679 0.999998 1572380 436906.67 + 13.767 0.999998 1572381 524288.00 + 13.767 0.999998 1572381 582542.22 + 13.767 0.999998 1572381 655360.00 + 13.767 0.999999 1572381 748982.86 + 13.807 0.999999 1572382 873813.33 + 13.807 0.999999 1572382 1048576.00 + 13.807 0.999999 1572382 1165084.44 + 13.807 0.999999 1572382 1310720.00 + 13.807 0.999999 1572382 1497965.71 + 13.911 0.999999 1572383 1747626.67 + 13.911 1.000000 1572383 inf +#[Mean = 2.251, StdDeviation = 1.344] +#[Max = 13.904, Total count = 1572383] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 1881040 requests in 1.00m, 120.19MB read +Requests/sec: 31351.04 +Transfer/sec: 2.00MB diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-histogram.png b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-histogram.png new file mode 100644 index 000000000..119a1faad Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k-histogram.png differ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k.txt b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k.txt new file mode 100644 index 000000000..831ae28a1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31.5k.txt @@ -0,0 +1,136 @@ +Running 1m test @ http://localhost:8080 + 4 threads and 64 connections + Thread calibration: mean lat.: 1.803ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 1.809ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 1.765ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 1.798ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.79ms 0.88ms 12.33ms 77.46% + Req/Sec 8.30k 813.43 12.00k 72.55% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.63ms + 75.000% 2.19ms + 90.000% 2.82ms + 99.000% 4.61ms + 99.900% 8.77ms + 99.990% 11.05ms + 99.999% 11.90ms +100.000% 12.34ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.151 0.000000 1 1.00 + 0.912 0.100000 157489 1.11 + 1.121 0.200000 314672 1.25 + 1.297 0.300000 472163 1.43 + 1.463 0.400000 629627 1.67 + 1.632 0.500000 786782 2.00 + 1.724 0.550000 865672 2.22 + 1.822 0.600000 943707 2.50 + 1.932 0.650000 1022565 2.86 + 2.055 0.700000 1101504 3.33 + 2.195 0.750000 1180344 4.00 + 2.271 0.775000 1219478 4.44 + 2.353 0.800000 1258149 5.00 + 2.445 0.825000 1297882 5.71 + 2.547 0.850000 1336623 6.67 + 2.669 0.875000 1376147 8.00 + 2.739 0.887500 1395692 8.89 + 2.819 0.900000 1415677 10.00 + 2.907 0.912500 1435119 11.43 + 3.011 0.925000 1454895 13.33 + 3.135 0.937500 1474363 16.00 + 3.209 0.943750 1484202 17.78 + 3.293 0.950000 1494044 20.00 + 3.389 0.956250 1503798 22.86 + 3.505 0.962500 1513645 26.67 + 3.643 0.968750 1523428 32.00 + 3.723 0.971875 1528261 35.56 + 3.817 0.975000 1533243 40.00 + 3.921 0.978125 1538110 45.71 + 4.049 0.981250 1543033 53.33 + 4.203 0.984375 1547987 64.00 + 4.295 0.985938 1550409 71.11 + 4.403 0.987500 1552910 80.00 + 4.527 0.989062 1555334 91.43 + 4.679 0.990625 1557761 106.67 + 4.883 0.992188 1560225 128.00 + 5.003 0.992969 1561454 142.22 + 5.159 0.993750 1562657 160.00 + 5.363 0.994531 1563900 182.86 + 5.635 0.995313 1565117 213.33 + 6.039 0.996094 1566343 256.00 + 6.303 0.996484 1566961 284.44 + 6.619 0.996875 1567573 320.00 + 6.959 0.997266 1568188 365.71 + 7.295 0.997656 1568798 426.67 + 7.663 0.998047 1569411 512.00 + 7.883 0.998242 1569722 568.89 + 8.107 0.998437 1570026 640.00 + 8.327 0.998633 1570336 731.43 + 8.567 0.998828 1570643 853.33 + 8.799 0.999023 1570946 1024.00 + 8.959 0.999121 1571100 1137.78 + 9.135 0.999219 1571255 1280.00 + 9.327 0.999316 1571411 1462.86 + 9.495 0.999414 1571564 1706.67 + 9.687 0.999512 1571714 2048.00 + 9.815 0.999561 1571790 2275.56 + 9.935 0.999609 1571870 2560.00 + 10.087 0.999658 1571947 2925.71 + 10.231 0.999707 1572023 3413.33 + 10.383 0.999756 1572098 4096.00 + 10.463 0.999780 1572136 4551.11 + 10.567 0.999805 1572174 5120.00 + 10.663 0.999829 1572214 5851.43 + 10.775 0.999854 1572252 6826.67 + 10.919 0.999878 1572290 8192.00 + 10.983 0.999890 1572309 9102.22 + 11.063 0.999902 1572332 10240.00 + 11.111 0.999915 1572347 11702.86 + 11.167 0.999927 1572367 13653.33 + 11.255 0.999939 1572388 16384.00 + 11.279 0.999945 1572396 18204.44 + 11.327 0.999951 1572405 20480.00 + 11.375 0.999957 1572415 23405.71 + 11.479 0.999963 1572426 27306.67 + 11.543 0.999969 1572434 32768.00 + 11.567 0.999973 1572438 36408.89 + 11.615 0.999976 1572443 40960.00 + 11.647 0.999979 1572448 46811.43 + 11.719 0.999982 1572454 54613.33 + 11.775 0.999985 1572458 65536.00 + 11.783 0.999986 1572460 72817.78 + 11.831 0.999988 1572462 81920.00 + 11.903 0.999989 1572465 93622.86 + 11.943 0.999991 1572467 109226.67 + 11.999 0.999992 1572470 131072.00 + 12.015 0.999993 1572471 145635.56 + 12.023 0.999994 1572472 163840.00 + 12.039 0.999995 1572473 187245.71 + 12.047 0.999995 1572474 218453.33 + 12.111 0.999996 1572476 262144.00 + 12.111 0.999997 1572476 291271.11 + 12.119 0.999997 1572477 327680.00 + 12.119 0.999997 1572477 374491.43 + 12.175 0.999998 1572478 436906.67 + 12.191 0.999998 1572479 524288.00 + 12.191 0.999998 1572479 582542.22 + 12.191 0.999998 1572479 655360.00 + 12.191 0.999999 1572479 748982.86 + 12.295 0.999999 1572480 873813.33 + 12.295 0.999999 1572480 1048576.00 + 12.295 0.999999 1572480 1165084.44 + 12.295 0.999999 1572480 1310720.00 + 12.295 0.999999 1572480 1497965.71 + 12.335 0.999999 1572481 1747626.67 + 12.335 1.000000 1572481 inf +#[Mean = 1.794, StdDeviation = 0.878] +#[Max = 12.328, Total count = 1572481] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 1888766 requests in 1.00m, 120.68MB read + Non-2xx or 3xx responses: 1 +Requests/sec: 31479.48 +Transfer/sec: 2.01MB diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31k-alloc.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31k-alloc.html new file mode 100644 index 000000000..453ca2b90 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31k-alloc.html @@ -0,0 +1,3270 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31k-cpu.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31k-cpu.html new file mode 100644 index 000000000..5c8690069 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31k-cpu.html @@ -0,0 +1,7859 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31k-lock.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31k-lock.html new file mode 100644 index 000000000..fae255f47 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-31k-lock.html @@ -0,0 +1,941 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-60k.txt b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-60k.txt new file mode 100644 index 000000000..3de1bc93c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/PUT-60k.txt @@ -0,0 +1,119 @@ +Running 1m test @ http://localhost:8080 + 4 threads and 64 connections + Thread calibration: mean lat.: 1509.704ms, rate sampling interval: 6045ms + Thread calibration: mean lat.: 1505.639ms, rate sampling interval: 6037ms + Thread calibration: mean lat.: 1508.103ms, rate sampling interval: 6045ms + Thread calibration: mean lat.: 1504.514ms, rate sampling interval: 6037ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 12.97s 5.67s 23.43s 58.22% + Req/Sec 9.02k 283.19 9.27k 87.50% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 12.80s + 75.000% 17.84s + 90.000% 20.92s + 99.000% 23.17s + 99.900% 23.38s + 99.990% 23.41s + 99.999% 23.43s +100.000% 23.45s + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 3379.199 0.000000 1 1.00 + 5283.839 0.100000 179915 1.11 + 7159.807 0.200000 359597 1.25 + 8994.815 0.300000 539218 1.43 + 10854.399 0.400000 718700 1.67 + 12795.903 0.500000 898600 2.00 + 13795.327 0.550000 988613 2.22 + 14794.751 0.600000 1078193 2.50 + 15769.599 0.650000 1168130 2.86 + 16793.599 0.700000 1257615 3.33 + 17842.175 0.750000 1348155 4.00 + 18350.079 0.775000 1392525 4.44 + 18841.599 0.800000 1437455 5.00 + 19349.503 0.825000 1482145 5.71 + 19857.407 0.850000 1528086 6.67 + 20381.695 0.875000 1572833 8.00 + 20643.839 0.887500 1594873 8.89 + 20922.367 0.900000 1618081 10.00 + 21184.511 0.912500 1640664 11.43 + 21430.271 0.925000 1662417 13.33 + 21708.799 0.937500 1684268 16.00 + 21872.639 0.943750 1696596 17.78 + 22069.247 0.950000 1707074 20.00 + 22282.239 0.956250 1718382 22.86 + 22462.463 0.962500 1729517 26.67 + 22659.071 0.968750 1741005 32.00 + 22740.991 0.971875 1746397 35.56 + 22822.911 0.975000 1752111 40.00 + 22888.447 0.978125 1757217 45.71 + 22970.367 0.981250 1763934 53.33 + 23035.903 0.984375 1769307 64.00 + 23068.671 0.985938 1771929 71.11 + 23101.439 0.987500 1774467 80.00 + 23134.207 0.989062 1776937 91.43 + 23183.359 0.990625 1781001 106.67 + 23216.127 0.992188 1783702 128.00 + 23232.511 0.992969 1784949 142.22 + 23248.895 0.993750 1786133 160.00 + 23265.279 0.994531 1787247 182.86 + 23281.663 0.995313 1788338 213.33 + 23314.431 0.996094 1790645 256.00 + 23314.431 0.996484 1790645 284.44 + 23330.815 0.996875 1791879 320.00 + 23330.815 0.997266 1791879 365.71 + 23347.199 0.997656 1793065 426.67 + 23347.199 0.998047 1793065 512.00 + 23363.583 0.998242 1794199 568.89 + 23363.583 0.998437 1794199 640.00 + 23363.583 0.998633 1794199 731.43 + 23379.967 0.998828 1795212 853.33 + 23379.967 0.999023 1795212 1024.00 + 23379.967 0.999121 1795212 1137.78 + 23379.967 0.999219 1795212 1280.00 + 23396.351 0.999316 1796004 1462.86 + 23396.351 0.999414 1796004 1706.67 + 23396.351 0.999512 1796004 2048.00 + 23396.351 0.999561 1796004 2275.56 + 23396.351 0.999609 1796004 2560.00 + 23396.351 0.999658 1796004 2925.71 + 23396.351 0.999707 1796004 3413.33 + 23412.735 0.999756 1796376 4096.00 + 23412.735 0.999780 1796376 4551.11 + 23412.735 0.999805 1796376 5120.00 + 23412.735 0.999829 1796376 5851.43 + 23412.735 0.999854 1796376 6826.67 + 23412.735 0.999878 1796376 8192.00 + 23412.735 0.999890 1796376 9102.22 + 23412.735 0.999902 1796376 10240.00 + 23412.735 0.999915 1796376 11702.86 + 23412.735 0.999927 1796376 13653.33 + 23429.119 0.999939 1796494 16384.00 + 23429.119 0.999945 1796494 18204.44 + 23429.119 0.999951 1796494 20480.00 + 23429.119 0.999957 1796494 23405.71 + 23429.119 0.999963 1796494 27306.67 + 23429.119 0.999969 1796494 32768.00 + 23429.119 0.999973 1796494 36408.89 + 23429.119 0.999976 1796494 40960.00 + 23429.119 0.999979 1796494 46811.43 + 23429.119 0.999982 1796494 54613.33 + 23429.119 0.999985 1796494 65536.00 + 23429.119 0.999986 1796494 72817.78 + 23429.119 0.999988 1796494 81920.00 + 23429.119 0.999989 1796494 93622.86 + 23429.119 0.999991 1796494 109226.67 + 23429.119 0.999992 1796494 131072.00 + 23429.119 0.999993 1796494 145635.56 + 23445.503 0.999994 1796505 163840.00 + 23445.503 1.000000 1796505 inf +#[Mean = 12973.783, StdDeviation = 5667.175] +#[Max = 23429.120, Total count = 1796505] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 2194003 requests in 1.00m, 140.19MB read + Non-2xx or 3xx responses: 1 +Requests/sec: 36566.85 +Transfer/sec: 2.34MB diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/report.md b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/report.md new file mode 100644 index 000000000..514acbefa --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage5/report.md @@ -0,0 +1,291 @@ +# Stage 5 + +- [Stage 5](#stage-5) + * [Конфигурация](#Конфигурация) + * [PUT](#put) + + [CPU profile](#cpu-profile) + + [Alloc profile](#alloc-profile) + + [Lock profile](#lock-profile) + * [GET](#get) + + [CPU profile](#cpu-profile-1) + + [Alloc profile](#alloc-profile-1) + + [Lock profile](#lock-profile-1) + * [Асинхронное выполнение локального запроса](#асинхронное-выполнение-локального-запроса) + + [PUT](#put-1) + + [GET](#get-1) + +## Конфигурация + +wrk2 - 64 connections, 4 threads + +Конфигурация кластера - 3 ноды, запущенные в отдельных процессах. Профилируем ноду, на которую шлём все запросы. + +Запросы без параметров ack и from -> по умолчанию реплицирование 2 из 3 + +## PUT + +[PUT-60k.txt](PUT-60k.txt) + +``` + Thread Stats Avg Stdev Max +/- Stdev + Latency 12.97s 5.67s 23.43s 58.22% + Req/Sec 9.02k 283.19 9.27k 87.50% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 12.80s + 75.000% 17.84s + 90.000% 20.92s + 99.000% 23.17s + 99.900% 23.38s + 99.990% 23.41s + 99.999% 23.43s +100.000% 23.45s +---------------------------------------------------------- + 2194003 requests in 1.00m, 140.19MB read + Non-2xx or 3xx responses: 1 +Requests/sec: 36566.85 +Transfer/sec: 2.34MB +``` + +60k RPS мы всё ещё не держим, но показатели лучше, чем в [прошлой реализации](../stage4/PUT-60k.txt) - wrk удалось +пропихнуть на 6.5k RPS больше. Посмотрим, что происходит на нагрузке в 25k RPS: + +[PUT-25k.txt](PUT-25k.txt) + +``` + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.35ms 609.01us 8.03ms 70.39% + Req/Sec 6.25k 8.63 6.28k 76.12% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.27ms + 75.000% 1.70ms + 90.000% 2.13ms + 99.000% 3.20ms + 99.900% 4.40ms + 99.990% 5.68ms + 99.999% 7.04ms +100.000% 8.03ms +``` + +![PUT-25k-histogram.png](PUT-25k-histogram.png) + +Latency на нагрузке в 25k RPS уменьшились в 2 раза, а график теперь похож на прямую зависимость. +Имеет смысл найти новую точку разладки. Очевидно, что это где-то между 25k и 36.5k (столько максимум смог впихнуть wrk +при требуемой от него нагрузки в 60k RPS). + +Путём экспериментов была выбрана точка разладки в 31.5к RPS. + +[PUT-31.5k.txt](PUT-31.5k.txt) + +``` + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.79ms 0.88ms 12.33ms 77.46% + Req/Sec 8.30k 813.43 12.00k 72.55% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.63ms + 75.000% 2.19ms + 90.000% 2.82ms + 99.000% 4.61ms + 99.900% 8.77ms + 99.990% 11.05ms + 99.999% 11.90ms +100.000% 12.34ms +``` + +![PUT-31.5k-histogram.png](PUT-31.5k-histogram.png) + +Благодаря асинхронным операциям удалось нарастить пропускную способность нашего кластера на 26%. Теперь мы по-настоящему +ждём 2 успешных ответа из 3 и после этого отвечаем на запрос, соответственно, 1/3 самых медленных запросов выполняется, +но мы не ждём их полного завершения. Это в целом коррелирует с приростом в 26%. + +### CPU profile + +[PUT-31k-cpu.html](PUT-31k-cpu.html) + +На профиле также, как и в [прошлый раз](../stage4/report.md#cpu-profile) видно работу GC - 6% от общего числа сэмплов. + +Уменьшилось количество сэмплов метода `handleEntityRequest` по сравнению +с [предыдущим stage](../stage4/PUT-25k-cpu.html): + +1. В 2 раза меньше сэмплов в методе `processRemote`, так как теперь мы обрабатываем ответы в + отдельном `ExecutorService`. +2. `LsmCustomSession::sendResponse` выполняется тоже асинхронно в отдельном `ExecutorService`. + +В остальном профиль в целом такой же. + +### Alloc profile + +Аллокации не изменились (в рамках погрешности) (раньше было так [PUT-25k-alloc.html](../stage4/PUT-25k-alloc.html)): + +[PUT-31k-alloc.html](PUT-31k-alloc.html) + +### Lock profile + +[PUT-31k-lock.html](PUT-31k-lock.html) + +Блокировки относительно [прошлого результата](../stage4/PUT-25k-lock.html) изменились: + +1. Появились явные блокировки на `HttpClient` при вызове метода `sendAsync`. +2. Блокировки на методе `HttpClient::cancelTimer` из-за того, что таймаут теперь реализуется не с помощью + метода `CompletableFututre::get`, а при создании запроса через builder. +3. Блокировки на внутренних локах `HttpClient` +4. Почти исчезли блокировки на `EPollSelectorImpl::wakeup` - вероятно из-за лучшей утилизации пула потоков, который мы + передали в `HttpClient`, ведь теперь мы не последовательно выполняем 1, а потом второй запрос (в рамках одного + пришедшего запроса), а 2 запроса параллельно, что, вероятно, позволяет потокам не ждать события сокета, а заниматься + обработкой других запросов. + +## GET + +База объемом ~1.5G, каждая нода хранит около 517mb. + +[GET-30k.txt](GET-30k.txt) + +``` + Thread Stats Avg Stdev Max +/- Stdev + Latency 19.15s 7.91s 33.08s 57.83% + Req/Sec 3.40k 14.14 3.42k 62.50% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 19.12s + 75.000% 25.99s + 90.000% 30.21s + 99.000% 32.65s + 99.900% 32.92s + 99.990% 33.01s + 99.999% 33.08s +100.000% 33.10s +---------------------------------------------------------- + 811430 requests in 1.00m, 39.88MB read + Non-2xx or 3xx responses: 31216 +Requests/sec: 13523.69 +Transfer/sec: 680.56KB +``` + +Ошибки Non-2xx or 3xx responses связаны с тем, что иногда мы ищем ключ, которого нет в нашем dao. + +Показатели latency GET запросов тоже стали лучше относительно [прошлого результата](../stage4/GET-30k.txt). wrk уже смог +пропихнуть не 10k RPS, а 13.5k. + +[GET-8k.txt](GET-8k.txt) + +``` + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.24ms 620.90us 15.56ms 70.23% + Req/Sec 2.11k 153.09 4.00k 73.39% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.17ms + 75.000% 1.60ms + 90.000% 2.03ms + 99.000% 2.96ms + 99.900% 3.96ms + 99.990% 10.31ms + 99.999% 13.86ms +100.000% 15.57ms +``` + +Но точка разладки по всей видимости примерно такая же, как и была (либо поднялась, но на незначительные показатели). + +![GET-8k-histogram.png](GET-8k-histogram.png) + +### CPU profile + +[GET-8k-cpu.html](GET-8k-cpu.html) + +1. В 2 раза меньше сэмплов в методе `processRemote`, так как теперь мы обрабатываем ответы в + отдельном `ExecutorService`. +2. `LsmCustomSession::sendResponse` выполняется тоже асинхронно в отдельном `ExecutorService` - пропали столбики из + метода `handleEntityRequest`. +3. Чуть-чуть больше стало (~1% против 0.33% ранее) сэмплов GC - вероятно из-за того, что асинхронные операции добавили + разных аллокаций внутреннего мира `HttpClient`. + +В остальном профиль не поменялся относительно [прошлого](../stage4/GET-8k-cpu-2.html). + +### Alloc profile + +[GET-8k-alloc.html](GET-8k-alloc.html) + +С точки зрения аллокаций тут всё снова примерно также, как и [было](../stage4/GET-8k-alloc-2.html). + +Меньше аллокаций в методе `purgeExpiredConnectionsAndReturnNextDeadline` - 2% сэмплов было, а стало 0.09%. Скорее всего +мы теперь лучше переиспользуем соединения. + +### Lock profile + +[GET-8k-lock.html](GET-8k-lock.html) + +Локи изменились сильнее: + +1. Также, как и в PUT, появились явные блокировки на `HttpClient` при вызове метода `sendAsync` из-за асинхронности - + ранее 2 запроса к кластеру выполнялись последовательно, а теперь параллельно и один из них всегда ждёт лок. +2. Появились блокировки на методе `HttpClient::registerTimer` из-за того, что таймаут теперь реализуется не с помощью + метода `CompletableFututre::get`, а при создании запроса через builder. +3. Количество сэмплов на `purgeExpiredConnectionsAndReturnNextDeadline` тоже увеличилось. Вероятно, стало больше гонок + из-за параллельного выполнения запросов к кластеру - отсюда и локи. + +## Асинхронное выполнение локального запроса + +### PUT + +[PUT-31.5k-after-fix.txt](PUT-31.5k-after-fix.txt) + +``` + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.25ms 1.34ms 13.90ms 82.68% + Req/Sec 8.16k 672.58 10.50k 71.96% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.87ms + 75.000% 2.62ms + 90.000% 3.92ms + 99.000% 7.36ms + 99.900% 9.93ms + 99.990% 11.80ms + 99.999% 12.97ms +100.000% 13.91ms +``` + +Относительно результатов выше новые показатели отличаются незначительно - latency 99.999-100.000% увеличились на 1ms. +Вероятнее всего это может быть связано с дополнительными переключениями контекста при отправке запроса на выполнение в +отдельный `ExecutorService`. + +[PUT-31.5k-af-cpu.html](PUT-31.5k-af-cpu.html) + +Профиль CPU практически не изменился. Единственное, что поменялось - теперь "столбик" с вызовом `processLocal` отделился +от `processRemote`. Это естественно, ведь теперь мы запускаем локальную обработку асинхронно. + +[PUT-31.5k-af-alloc.html](PUT-31.5k-af-alloc.html) + +[PUT-31.5k-af-lock.html](PUT-31.5k-af-lock.html) + +Остальные профили изменились в рамках погрешности. + +### GET + +[GET-8k-after-fix.txt](GET-8k-after-fix.txt) + +``` + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.35ms 607.24us 17.31ms 69.39% + Req/Sec 2.11k 161.56 3.44k 69.74% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.30ms + 75.000% 1.72ms + 90.000% 2.13ms + 99.000% 2.95ms + 99.900% 3.86ms + 99.990% 10.17ms + 99.999% 13.06ms +100.000% 17.33ms +``` + +У GET запросов изменился лишь 100-й перцентиль задержек - он стал на 2ms больше. Опять же вероятно связано с +переключениями контекстов, но на GET запросы не такое влияние, как на PUT, так как тут нагрузка почти в 4 раза меньше, +да и сами GET запросы тяжелее. + +[GET-8k-af-cpu.html](GET-8k-af-cpu.html) + +Профиль CPU практически не изменился. Единственное, что поменялось - теперь "столбик" с вызовом `processLocal` отделился +от `processRemote`. Это естественно, ведь теперь мы запускаем локальную обработку асинхронно. + +[GET-8k-af-alloc.html](GET-8k-af-alloc.html) + +[GET-8k-af-lock.html](GET-8k-af-lock.html) + +Остальные профили практически не изменились, показатели одинаковы в рамках погрешности. diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage6/RANGE-alloc.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage6/RANGE-alloc.html new file mode 100644 index 000000000..b911a3e3c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage6/RANGE-alloc.html @@ -0,0 +1,306 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage6/RANGE-cpu.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage6/RANGE-cpu.html new file mode 100644 index 000000000..f4eb898be --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage6/RANGE-cpu.html @@ -0,0 +1,434 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage6/RANGE-lock.html b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage6/RANGE-lock.html new file mode 100644 index 000000000..ef7bf570d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage6/RANGE-lock.html @@ -0,0 +1,260 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage6/report.md b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage6/report.md new file mode 100644 index 000000000..33a652d2a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/reports/stage6/report.md @@ -0,0 +1,41 @@ +# Stage 6 + +- [Stage 6](#stage-6) + * [Конфигурация](#Конфигурация) + * [CPU profile](#cpu-profile) + * [Alloc profile](#alloc-profile) + * [Lock profile](#lock-profile) + +## Конфигурация + +Наполнение одной ноды - 1335Mb (1.3Gb). Все ключи имеют вид `key%d+`. + +Запрос: `curl -vvv 'http://localhost:8080/v0/entities?start=key'` + +## CPU profile + +[RANGE-cpu.html](RANGE-cpu.html) + +Почти все сэмплы занимает операция сдвига итераторов. Во время сдвига мы удаляем и добавляем итераторы обратно в очередь. +Каждая такая операция сопровождается сравнением ключей `MemorySegment`. Сравнения были заинлайнены компилятором. +Сравнения также были оптимизированы с помощью векторных инструкций. + +Из интересного - `MemorySegment::copy` использует avx512 при копировании данных в `ByteArrayBuilder`. + +Такое количество сэмплов на операцию сдвига итераторов объясняется тем, что у нас дублируются данные в Sstable, поэтому +нам приходится скипать ключи. + +## Alloc profile + +[RANGE-alloc.html](RANGE-alloc.html) + +Аллокации невероятно красивые - всё, что нам приходится тут аллоцировать, так это `TimestampedEntry` (но без этого никак, +такой у нас интерфейс) и `MemorySegment`. При этом аллокации `MemorySegment` происходят лишь в методе +`MergeIterator::next`, то есть при сдвигах итераторов мы не аллоцируем память, а сравниваем `MemorySegment`'ы прямиком +на диске (а точнее скорее всего в page cache). Достойно уважения. + +## Lock profile + +[RANGE-lock.html](RANGE-lock.html) + +Локи пустые. Блокирующих операций у нас действительно нет. diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/util/ClusterResponseMerger.java b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/util/ClusterResponseMerger.java new file mode 100644 index 000000000..8b0e5d128 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/util/ClusterResponseMerger.java @@ -0,0 +1,70 @@ +package ru.vk.itmo.test.viktorkorotkikh.util; + +import one.nio.http.HttpSession; +import one.nio.http.Request; +import one.nio.http.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReferenceArray; + +public class ClusterResponseMerger { + private static final Logger log = LoggerFactory.getLogger(ClusterResponseMerger.class); + private final int ack; + private final int allowedUnsuccessfulResponses; + private final Request originalRequest; + private final HttpSession session; + private final AtomicReferenceArray nodeResponses; + private final AtomicInteger unsuccessfulResponsesCount; + private final AtomicInteger successfulResponsesCount; + + public ClusterResponseMerger(int ack, int from, Request originalRequest, HttpSession session) { + this.ack = ack; + this.allowedUnsuccessfulResponses = from - ack; + this.originalRequest = originalRequest; + this.session = session; + this.nodeResponses = new AtomicReferenceArray<>(from); + this.unsuccessfulResponsesCount = new AtomicInteger(); + this.successfulResponsesCount = new AtomicInteger(); + } + + public void addToMerge(int index, NodeResponse response) { + // we can write in plain semantic because we read in opaque in mergeReplicasResponses method + nodeResponses.setPlain(index, response); + if (isSuccessfulResponse(response.statusCode())) { + int newSuccessfulResponsesCount = successfulResponsesCount.incrementAndGet(); + if (newSuccessfulResponsesCount == ack) { + sendResponse(); + } + return; + } + int newUnsuccessfulResponsesCount = unsuccessfulResponsesCount.incrementAndGet(); + if (newUnsuccessfulResponsesCount > allowedUnsuccessfulResponses && successfulResponsesCount.get() < ack) { + sendResponseAndCloseSessionOnError(LSMConstantResponse.notEnoughReplicas(originalRequest)); + } + } + + private void sendResponse() { + Response response = LsmServerUtil.mergeReplicasResponses(originalRequest, nodeResponses, ack); + sendResponseAndCloseSessionOnError(response); + } + + private void sendResponseAndCloseSessionOnError(final Response response) { + try { + session.sendResponse(response); + } catch (IOException ex) { + log.error("I/O error occurred when sending response"); + session.scheduleClose(); + } + } + + private static boolean isSuccessfulResponse(int responseStatusCode) { + return responseStatusCode == HttpURLConnection.HTTP_OK + || responseStatusCode == HttpURLConnection.HTTP_CREATED + || responseStatusCode == HttpURLConnection.HTTP_ACCEPTED + || responseStatusCode == HttpURLConnection.HTTP_NOT_FOUND; + } +} diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/util/LSMConstantResponse.java b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/util/LSMConstantResponse.java index 79cca831e..ea460842b 100644 --- a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/util/LSMConstantResponse.java +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/util/LSMConstantResponse.java @@ -22,6 +22,7 @@ public final class LSMConstantResponse { public static final Response GATEWAY_TIMEOUT_CLOSE = new Response(Response.GATEWAY_TIMEOUT, Response.EMPTY); public static final Response NOT_ENOUGH_REPLICAS_CLOSE = new Response(NOT_ENOUGH_REPLICAS, Response.EMPTY); + public static final byte[] CHUNKED_RESPONSE_CLOSE_WITH_HEADERS_BYTES; private static final String CONNECTION_KEEP_ALIVE_HEADER = "Connection: Keep-Alive"; public static final Response BAD_REQUEST_KEEP_ALIVE = new Response(Response.BAD_REQUEST, Response.EMPTY); @@ -40,6 +41,7 @@ public final class LSMConstantResponse { new Response(Response.GATEWAY_TIMEOUT, Response.EMPTY); public static final Response NOT_ENOUGH_REPLICAS_KEEP_ALIVE = new Response(NOT_ENOUGH_REPLICAS, Response.EMPTY); + public static final byte[] CHUNKED_RESPONSE_KEEP_ALIVE_WITH_HEADERS_BYTES; static { BAD_REQUEST_CLOSE.addHeader(CONNECTION_CLOSE_HEADER); @@ -65,6 +67,16 @@ public final class LSMConstantResponse { SERVICE_UNAVAILABLE_KEEP_ALIVE.addHeader(CONNECTION_KEEP_ALIVE_HEADER); GATEWAY_TIMEOUT_KEEP_ALIVE.addHeader(CONNECTION_KEEP_ALIVE_HEADER); NOT_ENOUGH_REPLICAS_KEEP_ALIVE.addHeader(CONNECTION_KEEP_ALIVE_HEADER); + + Response chunkedResponseKeepAlive = new Response(Response.OK); + chunkedResponseKeepAlive.addHeader("Transfer-Encoding: chunked"); + chunkedResponseKeepAlive.addHeader(CONNECTION_KEEP_ALIVE_HEADER); + CHUNKED_RESPONSE_KEEP_ALIVE_WITH_HEADERS_BYTES = chunkedResponseKeepAlive.toBytes(false); + + Response chunkedResponseClose = new Response(Response.OK); + chunkedResponseKeepAlive.addHeader("Transfer-Encoding: chunked"); + chunkedResponseKeepAlive.addHeader(CONNECTION_CLOSE_HEADER); + CHUNKED_RESPONSE_CLOSE_WITH_HEADERS_BYTES = chunkedResponseClose.toBytes(false); } public static Response ok(final Request request) { diff --git a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/util/LsmServerUtil.java b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/util/LsmServerUtil.java index ebd45d1d5..4c1e8b707 100644 --- a/src/main/java/ru/vk/itmo/test/viktorkorotkikh/util/LsmServerUtil.java +++ b/src/main/java/ru/vk/itmo/test/viktorkorotkikh/util/LsmServerUtil.java @@ -2,8 +2,12 @@ import one.nio.http.Request; import one.nio.http.Response; +import one.nio.util.ByteArrayBuilder; -import java.util.List; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicReferenceArray; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_ENTITY_TOO_LARGE; @@ -24,7 +28,7 @@ public static String timestampToHeader(long timestamp) { public static Response mergeReplicasResponses( final Request originalRequest, - final List responses, + final AtomicReferenceArray responses, final int ack ) { switch (originalRequest.getMethod()) { @@ -41,11 +45,17 @@ public static Response mergeReplicasResponses( } } - private static Response mergeGetResponses(Request originalRequest, List responses, int ack) { + private static Response mergeGetResponses( + Request originalRequest, + AtomicReferenceArray responses, + int ack + ) { long maxTimestamp = -1; NodeResponse lastValue = null; int successfulResponses = 0; - for (NodeResponse response : responses) { + for (int i = 0; i < responses.length(); i++) { + final NodeResponse response = responses.getOpaque(i); + if (response == null) continue; final long valueTimestamp = getTimestamp(response); if (valueTimestamp > maxTimestamp) { maxTimestamp = valueTimestamp; @@ -59,10 +69,10 @@ private static Response mergeGetResponses(Request originalRequest, List lastValue.okResponse(); + case HTTP_OK -> Response.ok(lastValue.body()); case HTTP_BAD_REQUEST -> LSMConstantResponse.badRequest(originalRequest); case HTTP_NOT_FOUND -> LSMConstantResponse.notFound(originalRequest); case HTTP_ENTITY_TOO_LARGE -> LSMConstantResponse.entityTooLarge(originalRequest); @@ -74,7 +84,7 @@ private static Response mergeGetResponses(Request originalRequest, List responses, + AtomicReferenceArray responses, int ack ) { if (hasNotEnoughReplicas(responses, ack)) { @@ -85,7 +95,7 @@ private static Response mergePutResponses( private static Response mergeDeleteResponses( Request originalRequest, - List responses, + AtomicReferenceArray responses, int ack ) { if (hasNotEnoughReplicas(responses, ack)) { @@ -94,9 +104,11 @@ private static Response mergeDeleteResponses( return LSMConstantResponse.accepted(originalRequest); } - private static boolean hasNotEnoughReplicas(List responses, int ack) { + private static boolean hasNotEnoughReplicas(AtomicReferenceArray responses, int ack) { int successfulResponses = 0; - for (NodeResponse response : responses) { + for (int i = 0; i < responses.length(); i++) { + final NodeResponse response = responses.getOpaque(i); + if (response == null) continue; if (response.statusCode() >= 200 && response.statusCode() < 300) { successfulResponses++; } @@ -111,4 +123,38 @@ private static long getTimestamp(final NodeResponse response) { } return Long.parseLong(timestamp); } + + private static NodeResponse firstNotNull(AtomicReferenceArray responses) { + for (int i = 0; i < responses.length(); i++) { + final NodeResponse response = responses.getOpaque(i); + if (response != null) return response; + } + throw new NoSuchElementException(); + } + + public static int copyMemorySegmentToByteArrayBuilder(MemorySegment memorySegmentBody, ByteArrayBuilder builder) { + return copyMemorySegmentToByteArrayBuilder(memorySegmentBody, 0, builder, builder.capacity()); + } + + public static int copyMemorySegmentToByteArrayBuilder( + MemorySegment memorySegment, + int memorySegmentOffset, + ByteArrayBuilder builder, + int builderCapacity + ) { + int estimatedCapacityInBuffer = builderCapacity - builder.length(); + int toWrite = memorySegment.byteSize() > estimatedCapacityInBuffer + ? estimatedCapacityInBuffer + : (int) memorySegment.byteSize() - memorySegmentOffset; + MemorySegment.copy( + memorySegment, + ValueLayout.JAVA_BYTE, + memorySegmentOffset, + builder.buffer(), + builder.length(), + toWrite + ); + builder.setLength(builder.length() + toWrite); + return toWrite; + } }