From 0af7602d3a4c92809782d16b56d441b1918aa553 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Wed, 28 Feb 2024 22:17:21 +0300 Subject: [PATCH 01/19] Stage 2 Signed-off-by: vitaliy.emelyanov --- .../itmo/test/emelyanovvitaliy/DhtServer.java | 168 ++++++++++++++++++ .../REDLinkedBlockingQueue.java | 89 ++++++++++ 2 files changed, 257 insertions(+) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/REDLinkedBlockingQueue.java diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java new file mode 100644 index 000000000..47f00fa0f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -0,0 +1,168 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +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 ru.vk.itmo.ServiceConfig; +import ru.vk.itmo.dao.BaseEntry; +import ru.vk.itmo.dao.Config; +import ru.vk.itmo.dao.Entry; +import ru.vk.itmo.test.reference.dao.ReferenceDao; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static one.nio.http.Request.METHOD_DELETE; +import static one.nio.http.Request.METHOD_GET; +import static one.nio.http.Request.METHOD_PUT; + +public class DhtServer extends HttpServer { + public static final byte[] EMPTY_BODY = new byte[0]; + public static final int THREADS_PER_PROCESSOR = 2; + public static final long KEEP_ALIVE_TIME_MS = 1000; + public static final int REQUEST_TIMEOUT_MS = 1024; + private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + Runtime.getRuntime().availableProcessors(), + Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, + KEEP_ALIVE_TIME_MS, + TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(1 << 16) + ); + private final ReferenceDao dao; + + public DhtServer(ServiceConfig config) throws IOException { + super(createConfig(config)); + dao = new ReferenceDao(new Config(config.workingDir(), 1 << 24)); + } + + @Override + public synchronized void stop() { + super.stop(); + try { + dao.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @RequestMethod(METHOD_GET) + @Path("/v0/entity") + public void entity(@Param(value = "id") String id, HttpSession session) throws IOException { + if (isKeyIncorrect(id)) { + session.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); + } else { + long start = System.currentTimeMillis(); + threadPoolExecutor.execute( + () -> { + try { + if (System.currentTimeMillis() - start >= REQUEST_TIMEOUT_MS) { + session.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); + return; + } + Entry entry = dao.get(keyFor(id)); + if (entry == null) { + session.sendResponse(new Response(Response.NOT_FOUND, EMPTY_BODY)); + } else { + session.sendResponse(new Response(Response.OK, valueFor(entry))); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + ); + } + } + + @RequestMethod(METHOD_PUT) + @Path("/v0/entity") + public void putEntity(@Param(value = "id") String id, HttpSession httpSession, Request request) throws IOException { + if (isKeyIncorrect(id)) { + httpSession.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); + } else { + long start = System.currentTimeMillis(); + threadPoolExecutor.execute( + () -> { + try { + if (System.currentTimeMillis() - start > KEEP_ALIVE_TIME_MS) { + httpSession.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); + return; + } + dao.upsert(new BaseEntry<>(keyFor(id), MemorySegment.ofArray(request.getBody()))); + httpSession.sendResponse(new Response(Response.CREATED, EMPTY_BODY)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + ); + } + } + + @RequestMethod(METHOD_DELETE) + @Path("/v0/entity") + public void deleteEntity(@Param("id") String id, HttpSession httpSession) throws IOException { + if (isKeyIncorrect(id)) { + httpSession.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); + } else { + long start = System.currentTimeMillis(); + threadPoolExecutor.execute( + () -> { + try { + if (System.currentTimeMillis() - start > KEEP_ALIVE_TIME_MS) { + httpSession.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); + return; + } + dao.upsert(new BaseEntry<>(keyFor(id), null)); + httpSession.sendResponse(new Response(Response.ACCEPTED, EMPTY_BODY)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + ); + } + } + + @Override + public void handleDefault(Request request, HttpSession session) throws IOException { + int requestMethod = request.getMethod(); + if (requestMethod == METHOD_GET || requestMethod == METHOD_PUT || requestMethod == METHOD_DELETE) { + session.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); + } else { + session.sendResponse(new Response(Response.METHOD_NOT_ALLOWED, EMPTY_BODY)); + } + } + + private static boolean isKeyIncorrect(String key) { + return key == null || key.isEmpty(); + } + + private static HttpServerConfig createConfig(ServiceConfig serviceConfig) { + HttpServerConfig config = new HttpServerConfig(); + AcceptorConfig acceptorConfig = new AcceptorConfig(); + acceptorConfig.port = serviceConfig.selfPort(); + acceptorConfig.reusePort = true; + config.acceptors = new AcceptorConfig[] {acceptorConfig}; + config.closeSessions = true; + return config; + } + + private static MemorySegment keyFor(String id) { + return MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)); + } + + private static byte[] valueFor(Entry entry) { + return entry.value().toArray(ValueLayout.JAVA_BYTE); + } + +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/REDLinkedBlockingQueue.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/REDLinkedBlockingQueue.java new file mode 100644 index 000000000..72b972c4d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/REDLinkedBlockingQueue.java @@ -0,0 +1,89 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; + +// Small experiment to implement LIFO queue, which randomly takes oldest element from the queue when it is too big +// Inspired by switching WRED algorithm +public class REDLinkedBlockingQueue extends LinkedBlockingDeque implements BlockingQueue { + // constants for linear congruential method + private static final long A = Integer.MAX_VALUE - 7; + private static final long C = Integer.MAX_VALUE >>> 1; + private static final long M = Integer.MAX_VALUE - 3; + private long randomValue = (long) (2 * (Math.random() - 0.5) * Long.MAX_VALUE); + + public REDLinkedBlockingQueue(int capacity) { + super(capacity); + } + @Override + public T remove() { + if (shouldTakeLast()) { + return removeLast(); + } else { + return removeFirst(); + } + } + + @Override + public T poll() { + if (shouldTakeLast()) { + return pollLast(); + } else { + return pollFirst(); + } + } + + @Override + public T element() { + if (shouldTakeLast()) { + return getLast(); + } else { + return getFirst(); + } + } + + @Override + public T peek() { + if (shouldTakeLast()) { + return peekLast(); + } else { + return peekFirst(); + } + } + + @Override + public T take() throws InterruptedException { + if (shouldTakeLast()) { + return takeLast(); + } else { + return takeFirst(); + } + } + + @Override + public T poll(long timeout, TimeUnit unit) throws InterruptedException { + if (shouldTakeLast()) { + return pollLast(timeout, unit); + } else { + return pollFirst(timeout, unit); + } + } + + private boolean shouldTakeLast() { + int remainingCapacity = remainingCapacity(); + int size = size(); + // if less than half of capacity exceeded then take the first element + if (remainingCapacity > size) { + return false; + } + randomValue = getNewRandomValue(randomValue); + return Math.abs(randomValue) % size >= remainingCapacity; + } + + private long getNewRandomValue(long previousValue) { + return (previousValue * A + C) % M; + } +} From 2a912175fc765a43bc6e017860ee4082265e1e2c Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Wed, 6 Mar 2024 23:30:12 +0300 Subject: [PATCH 02/19] Report 4 2nd stage Signed-off-by: vitaliy.emelyanov --- .../itmo/test/emelyanovvitaliy/DhtServer.java | 6 +- .../reports/2/profile_read_alloc.html | 1460 +++ .../reports/2/profile_read_alloc_sync_q.html | 1296 +++ .../reports/2/profile_read_cpu.html | 7888 +++++++++++++++++ .../reports/2/profile_read_cpu_sync_q.html | 4351 +++++++++ .../reports/2/profile_read_lock.html | 886 ++ .../reports/2/profile_read_lock_sync_q.html | 484 + .../reports/2/profile_write_alloc.html | 1979 +++++ .../reports/2/profile_write_alloc_sync_q.html | 1343 +++ .../reports/2/profile_write_cpu.html | 5388 +++++++++++ .../reports/2/profile_write_cpu_sync_q.html | 5113 +++++++++++ .../test/emelyanovvitaliy/reports/2/report.md | 100 + .../emelyanovvitaliy/reports/2/wrk_get.txt | 130 + .../reports/2/wrk_get_synchronous_queue.txt | 116 + .../emelyanovvitaliy/reports/2/wrk_put.txt | 133 + .../reports/2/wrk_put_synchronous_queue.txt | 128 + 16 files changed, 30798 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_alloc_sync_q.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_cpu.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_cpu_sync_q.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_lock.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_lock_sync_q.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_alloc_sync_q.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_cpu.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_cpu_sync_q.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/report.md create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_get.txt create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_get_synchronous_queue.txt create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_put.txt create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_put_synchronous_queue.txt diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java index 47f00fa0f..33700eb8b 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -20,7 +20,7 @@ import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.nio.charset.StandardCharsets; -import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -34,11 +34,11 @@ public class DhtServer extends HttpServer { public static final long KEEP_ALIVE_TIME_MS = 1000; public static final int REQUEST_TIMEOUT_MS = 1024; private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( - Runtime.getRuntime().availableProcessors(), + Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, KEEP_ALIVE_TIME_MS, TimeUnit.MILLISECONDS, - new ArrayBlockingQueue<>(1 << 16) + new SynchronousQueue<>() ); private final ReferenceDao dao; diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_alloc.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_alloc.html new file mode 100644 index 000000000..8f1112d88 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_alloc.html @@ -0,0 +1,1460 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_alloc_sync_q.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_alloc_sync_q.html new file mode 100644 index 000000000..c53b5a7f1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_alloc_sync_q.html @@ -0,0 +1,1296 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_cpu.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_cpu.html new file mode 100644 index 000000000..44815904a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_cpu.html @@ -0,0 +1,7888 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_cpu_sync_q.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_cpu_sync_q.html new file mode 100644 index 000000000..456a5eeb7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_cpu_sync_q.html @@ -0,0 +1,4351 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_lock.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_lock.html new file mode 100644 index 000000000..25061ab99 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_lock.html @@ -0,0 +1,886 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_lock_sync_q.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_lock_sync_q.html new file mode 100644 index 000000000..bf75e9156 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_read_lock_sync_q.html @@ -0,0 +1,484 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_alloc.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_alloc.html new file mode 100644 index 000000000..84e666073 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_alloc.html @@ -0,0 +1,1979 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_alloc_sync_q.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_alloc_sync_q.html new file mode 100644 index 000000000..59ffc1d0d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_alloc_sync_q.html @@ -0,0 +1,1343 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_cpu.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_cpu.html new file mode 100644 index 000000000..164f60d51 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_cpu.html @@ -0,0 +1,5388 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_cpu_sync_q.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_cpu_sync_q.html new file mode 100644 index 000000000..0b5fcee5d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/profile_write_cpu_sync_q.html @@ -0,0 +1,5113 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/report.md b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/report.md new file mode 100644 index 000000000..3d99a337e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/report.md @@ -0,0 +1,100 @@ +# Отчёт по модулю 2 +## Наполнение +Наполнения производилось скриптом, аналогичным используемому +в модуле 1, общий объём данных составил ~6.6 GiB, количество +SSTable = 377 + +## Профилирование на запись +Профилирование производилось при нагрузке в 12'000 RPS +(точка разладки ~15'000 RPS), 1 поток, 64 соединения + +[Логи wrk2 на запись](wrk_put.txt) + +### ЦПУ +[Профиль ЦПУ на запись](profile_write_cpu.html) + +Профили ЦПУ не изменились, за исключением разделения функций получения данных +и их обработки в разные потоки + +### Аллокации +[Профиль аллокаций на запись](profile_write_alloc.html) + +Появились дополнительные аллокации, необходимые для +обслуживания очереди запросов +(н-р, ConditionNode), которые по некоторым потокам занимают +до трети всей аллоцируемой памяти. + +### Блокировки +[Профиль блокировок на запись](profile_write_lock.html) + +Появились блокировки очереди потоков, которые и составляют подавляющее большинство. + +## Профилирование на чтение + +Нагрузка при работе на чтение производилось скриптом, аналогичным используемому +для тех же задач в предыдущем модуле (50% ключей существуют, 50% - нет) + +Профилирование производилось при нагрузке в 7'500 RPS (точка разладки ~9'000RPS), +8 потоков, 1024 подключения + +[Логи wrk2 на чтение](wrk_get.txt) + +### ЦПУ + +[Профиль ЦПУ на чтение](profile_read_cpu.html) + +Профили ЦПУ не изменились, за исключением разделения функций получения данных и их обработки в разные потоки + +### Аллокации + +[Профиль аллокаций на чтение](profile_read_alloc.html) + +В профилях ЦПУ появились аллокации под лямбда-функции. + +### Блокировки + +[Профиль блокировок на чтение](profile_read_lock.html) + +Блокировки связаны с вводом-выводом, которые появились, +теперь запросы исполняются в разных потоках, и ответ на более поздний запрос может быть +готов к отправке раньше, чем на более ранний, пришедший в рамках того же запроса. + +# Замена ArrayBlockingQueue на SynchronousQueue + +Тесты на wrk2 проводятся при тех же или более "трудных" условиях. + +[Логи wrk2 на чтение](wrk_get_synchronous_queue.txt) + +[Логи wrk2 на запись](wrk_put_synchronous_queue.txt) + +Заметен прирост по всем перцентилям + +Будем рассматривать только те изменения профили, по которым есть заметные изменения + +# Запись + +[Профиль ЦПУ на запись](profile_write_cpu_sync_q.html) + +Основное отличие по ЦПУ - снижение объема работы GC. +Можно заметить снижение обработки блокировок очереди при добавлении в очередь. + +# Чтение +[Профиль блокировок на чтение](profile_read_alloc_sync_q.html) + +Можно заметить, что очередь больше не блокируется, остались лишь сетевые блокировки. + + +# WRED-вдохновленная очередь + +Результатов, достойных упоминания, не принесла) + +Связано это в первую очередь с неоптимальностью использования связного списка +для подобных очередей с сильно ограниченным (если не сказать константным) размером. + +Тем более в условиях, когда себя очень хорошо проявляет SynchronousQueue. + +--- + +### Заметка на полях + +Надо будет попробовать реализовать ArrayBlockingDequeue и уже на нем попробовать снова diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_get.txt b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_get.txt new file mode 100644 index 000000000..2110d332d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_get.txt @@ -0,0 +1,130 @@ +Running 1m test @ http://localhost:8080 + 8 threads and 1024 connections + Thread calibration: mean lat.: 111.524ms, rate sampling interval: 298ms + Thread calibration: mean lat.: 111.673ms, rate sampling interval: 301ms + Thread calibration: mean lat.: 111.708ms, rate sampling interval: 299ms + Thread calibration: mean lat.: 130.911ms, rate sampling interval: 347ms + Thread calibration: mean lat.: 112.215ms, rate sampling interval: 304ms + Thread calibration: mean lat.: 111.999ms, rate sampling interval: 301ms + Thread calibration: mean lat.: 124.552ms, rate sampling interval: 310ms + Thread calibration: mean lat.: 124.267ms, rate sampling interval: 309ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 126.56ms 20.72ms 229.76ms 68.58% + Req/Sec 0.93k 74.45 1.17k 69.87% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 125.57ms + 75.000% 139.90ms + 90.000% 153.21ms + 99.000% 183.81ms + 99.900% 203.01ms + 99.990% 214.53ms + 99.999% 224.13ms +100.000% 229.89ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 71.039 0.000000 1 1.00 + 101.759 0.100000 36663 1.11 + 107.903 0.200000 73371 1.25 + 113.279 0.300000 110247 1.43 + 118.911 0.400000 146431 1.67 + 125.567 0.500000 183229 2.00 + 128.767 0.550000 201699 2.22 + 131.455 0.600000 220259 2.50 + 134.015 0.650000 238252 2.86 + 136.831 0.700000 256326 3.33 + 139.903 0.750000 275230 4.00 + 141.439 0.775000 283928 4.44 + 143.231 0.800000 293262 5.00 + 145.151 0.825000 302142 5.71 + 147.455 0.850000 311475 6.67 + 150.015 0.875000 320290 8.00 + 151.551 0.887500 324942 8.89 + 153.215 0.900000 329489 10.00 + 155.135 0.912500 334119 11.43 + 157.311 0.925000 338786 13.33 + 159.871 0.937500 343359 16.00 + 161.279 0.943750 345472 17.78 + 163.071 0.950000 347889 20.00 + 164.991 0.956250 350155 22.86 + 167.167 0.962500 352431 26.67 + 169.727 0.968750 354677 32.00 + 171.135 0.971875 355764 35.56 + 172.799 0.975000 356955 40.00 + 174.591 0.978125 358053 45.71 + 176.639 0.981250 359214 53.33 + 178.815 0.984375 360347 64.00 + 180.095 0.985938 360926 71.11 + 181.503 0.987500 361504 80.00 + 182.911 0.989062 362075 91.43 + 184.319 0.990625 362627 106.67 + 186.111 0.992188 363211 128.00 + 187.135 0.992969 363480 142.22 + 188.287 0.993750 363775 160.00 + 189.439 0.994531 364044 182.86 + 190.847 0.995313 364332 213.33 + 192.383 0.996094 364630 256.00 + 193.279 0.996484 364755 284.44 + 194.431 0.996875 364901 320.00 + 195.583 0.997266 365043 365.71 + 196.607 0.997656 365190 426.67 + 198.015 0.998047 365331 512.00 + 198.783 0.998242 365400 568.89 + 199.807 0.998437 365475 640.00 + 200.703 0.998633 365540 731.43 + 201.855 0.998828 365619 853.33 + 203.263 0.999023 365684 1024.00 + 203.903 0.999121 365719 1137.78 + 204.671 0.999219 365757 1280.00 + 205.823 0.999316 365790 1462.86 + 206.591 0.999414 365826 1706.67 + 207.743 0.999512 365865 2048.00 + 208.255 0.999561 365883 2275.56 + 208.895 0.999609 365898 2560.00 + 209.535 0.999658 365918 2925.71 + 210.047 0.999707 365933 3413.33 + 210.943 0.999756 365952 4096.00 + 211.583 0.999780 365966 4551.11 + 211.711 0.999805 365969 5120.00 + 212.223 0.999829 365978 5851.43 + 212.991 0.999854 365988 6826.67 + 213.503 0.999878 365996 8192.00 + 214.015 0.999890 366000 9102.22 + 214.655 0.999902 366005 10240.00 + 215.423 0.999915 366009 11702.86 + 216.575 0.999927 366014 13653.33 + 217.087 0.999939 366018 16384.00 + 217.215 0.999945 366020 18204.44 + 218.111 0.999951 366024 20480.00 + 218.239 0.999957 366025 23405.71 + 219.135 0.999963 366028 27306.67 + 219.647 0.999969 366029 32768.00 + 220.415 0.999973 366030 36408.89 + 222.079 0.999976 366032 40960.00 + 222.207 0.999979 366033 46811.43 + 222.335 0.999982 366035 54613.33 + 222.335 0.999985 366035 65536.00 + 222.335 0.999986 366035 72817.78 + 224.127 0.999988 366036 81920.00 + 224.895 0.999989 366037 93622.86 + 224.895 0.999991 366037 109226.67 + 225.151 0.999992 366038 131072.00 + 225.151 0.999993 366038 145635.56 + 225.151 0.999994 366038 163840.00 + 227.071 0.999995 366039 187245.71 + 227.071 0.999995 366039 218453.33 + 227.071 0.999996 366039 262144.00 + 227.071 0.999997 366039 291271.11 + 227.071 0.999997 366039 327680.00 + 229.887 0.999997 366040 374491.43 + 229.887 1.000000 366040 inf +#[Mean = 126.565, StdDeviation = 20.725] +#[Max = 229.760, Total count = 366040] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 440494 requests in 1.00m, 61.84MB read + Socket errors: connect 11, read 0, write 0, timeout 319 + Non-2xx or 3xx responses: 256282 +Requests/sec: 7341.41 +Transfer/sec: 1.03MB diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_get_synchronous_queue.txt b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_get_synchronous_queue.txt new file mode 100644 index 000000000..8d112692a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_get_synchronous_queue.txt @@ -0,0 +1,116 @@ +Running 1m test @ http://localhost:8080 + 8 threads and 1024 connections + Thread calibration: mean lat.: 140.236ms, rate sampling interval: 1297ms + Thread calibration: mean lat.: 76.341ms, rate sampling interval: 367ms + Thread calibration: mean lat.: 6.815ms, rate sampling interval: 16ms + Thread calibration: mean lat.: 67.141ms, rate sampling interval: 273ms + Thread calibration: mean lat.: 61.064ms, rate sampling interval: 216ms + Thread calibration: mean lat.: 205.357ms, rate sampling interval: 1595ms + Thread calibration: mean lat.: 236.865ms, rate sampling interval: 1687ms + Thread calibration: mean lat.: 191.014ms, rate sampling interval: 1506ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.91ms 2.10ms 19.09ms 71.97% + Req/Sec 264.33 107.73 533.00 55.11% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 3.25ms + 75.000% 5.03ms + 90.000% 6.98ms + 99.000% 10.12ms + 99.900% 12.33ms + 99.990% 15.42ms + 99.999% 17.95ms +100.000% 19.10ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.364 0.000000 1 1.00 + 1.705 0.100000 5883 1.11 + 2.293 0.200000 11764 1.25 + 2.613 0.300000 17646 1.43 + 2.917 0.400000 23536 1.67 + 3.249 0.500000 29412 2.00 + 3.531 0.550000 32342 2.22 + 3.873 0.600000 35287 2.50 + 4.243 0.650000 38240 2.86 + 4.615 0.700000 41166 3.33 + 5.031 0.750000 44098 4.00 + 5.275 0.775000 45580 4.44 + 5.551 0.800000 47033 5.00 + 5.867 0.825000 48505 5.71 + 6.203 0.850000 49986 6.67 + 6.575 0.875000 51445 8.00 + 6.771 0.887500 52184 8.89 + 6.983 0.900000 52911 10.00 + 7.243 0.912500 53651 11.43 + 7.515 0.925000 54386 13.33 + 7.819 0.937500 55117 16.00 + 7.995 0.943750 55486 17.78 + 8.175 0.950000 55851 20.00 + 8.351 0.956250 56218 22.86 + 8.559 0.962500 56587 26.67 + 8.807 0.968750 56955 32.00 + 8.935 0.971875 57139 35.56 + 9.079 0.975000 57322 40.00 + 9.231 0.978125 57503 45.71 + 9.407 0.981250 57687 53.33 + 9.615 0.984375 57870 64.00 + 9.727 0.985938 57963 71.11 + 9.855 0.987500 58056 80.00 + 10.023 0.989062 58148 91.43 + 10.175 0.990625 58238 106.67 + 10.335 0.992188 58333 128.00 + 10.415 0.992969 58376 142.22 + 10.559 0.993750 58424 160.00 + 10.687 0.994531 58467 182.86 + 10.839 0.995313 58514 213.33 + 11.015 0.996094 58559 256.00 + 11.119 0.996484 58582 284.44 + 11.279 0.996875 58606 320.00 + 11.391 0.997266 58628 365.71 + 11.551 0.997656 58651 426.67 + 11.719 0.998047 58675 512.00 + 11.799 0.998242 58685 568.89 + 11.911 0.998437 58697 640.00 + 12.039 0.998633 58708 731.43 + 12.199 0.998828 58720 853.33 + 12.335 0.999023 58731 1024.00 + 12.455 0.999121 58738 1137.78 + 12.599 0.999219 58743 1280.00 + 12.759 0.999316 58749 1462.86 + 12.919 0.999414 58754 1706.67 + 13.055 0.999512 58760 2048.00 + 13.191 0.999561 58763 2275.56 + 13.255 0.999609 58766 2560.00 + 13.335 0.999658 58768 2925.71 + 13.495 0.999707 58771 3413.33 + 13.775 0.999756 58774 4096.00 + 13.895 0.999780 58776 4551.11 + 14.151 0.999805 58777 5120.00 + 14.231 0.999829 58778 5851.43 + 14.823 0.999854 58780 6826.67 + 15.127 0.999878 58781 8192.00 + 15.423 0.999890 58782 9102.22 + 15.607 0.999902 58783 10240.00 + 15.607 0.999915 58783 11702.86 + 15.863 0.999927 58784 13653.33 + 15.983 0.999939 58785 16384.00 + 15.983 0.999945 58785 18204.44 + 16.847 0.999951 58786 20480.00 + 16.847 0.999957 58786 23405.71 + 16.847 0.999963 58786 27306.67 + 17.951 0.999969 58787 32768.00 + 17.951 0.999973 58787 36408.89 + 17.951 0.999976 58787 40960.00 + 17.951 0.999979 58787 46811.43 + 17.951 0.999982 58787 54613.33 + 19.103 0.999985 58788 65536.00 + 19.103 1.000000 58788 inf +#[Mean = 3.910, StdDeviation = 2.100] +#[Max = 19.088, Total count = 58788] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 78046 requests in 1.00m, 11.02MB read + Socket errors: connect 11, read 0, write 0, timeout 26901 +Requests/sec: 1300.31 +Transfer/sec: 188.03KB diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_put.txt b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_put.txt new file mode 100644 index 000000000..96e7ee32c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_put.txt @@ -0,0 +1,133 @@ +Running 3m test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 4.520ms, rate sampling interval: 12ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.98ms 1.47ms 31.54ms 72.16% + Req/Sec 12.59k 2.53k 19.67k 64.47% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 3.81ms + 75.000% 4.79ms + 90.000% 5.93ms + 99.000% 7.98ms + 99.900% 11.70ms + 99.990% 16.27ms + 99.999% 20.46ms +100.000% 31.55ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.135 0.000000 1 1.00 + 2.375 0.100000 204041 1.11 + 2.771 0.200000 407924 1.25 + 3.105 0.300000 611421 1.43 + 3.439 0.400000 814694 1.67 + 3.811 0.500000 1018466 2.00 + 3.995 0.550000 1120693 2.22 + 4.183 0.600000 1222431 2.50 + 4.379 0.650000 1325565 2.86 + 4.575 0.700000 1426590 3.33 + 4.787 0.750000 1528039 4.00 + 4.911 0.775000 1579219 4.44 + 5.059 0.800000 1629630 5.00 + 5.235 0.825000 1680977 5.71 + 5.431 0.850000 1731101 6.67 + 5.659 0.875000 1782049 8.00 + 5.795 0.887500 1807866 8.89 + 5.931 0.900000 1832983 10.00 + 6.071 0.912500 1858373 11.43 + 6.227 0.925000 1883544 13.33 + 6.399 0.937500 1909144 16.00 + 6.491 0.943750 1922082 17.78 + 6.587 0.950000 1934935 20.00 + 6.687 0.956250 1947163 22.86 + 6.811 0.962500 1959962 26.67 + 6.967 0.968750 1972700 32.00 + 7.059 0.971875 1979073 35.56 + 7.163 0.975000 1985400 40.00 + 7.275 0.978125 1991844 45.71 + 7.391 0.981250 1998139 53.33 + 7.523 0.984375 2004419 64.00 + 7.623 0.985938 2007640 71.11 + 7.763 0.987500 2010861 80.00 + 7.899 0.989062 2013993 91.43 + 8.043 0.990625 2017185 106.67 + 8.239 0.992188 2020414 128.00 + 8.383 0.992969 2021981 142.22 + 8.583 0.993750 2023524 160.00 + 8.895 0.994531 2025128 182.86 + 9.319 0.995313 2026709 213.33 + 9.679 0.996094 2028309 256.00 + 9.815 0.996484 2029086 284.44 + 9.951 0.996875 2029904 320.00 + 10.087 0.997266 2030669 365.71 + 10.223 0.997656 2031467 426.67 + 10.391 0.998047 2032259 512.00 + 10.535 0.998242 2032668 568.89 + 10.727 0.998437 2033060 640.00 + 10.975 0.998633 2033453 731.43 + 11.319 0.998828 2033852 853.33 + 11.759 0.999023 2034249 1024.00 + 11.975 0.999121 2034451 1137.78 + 12.255 0.999219 2034650 1280.00 + 12.567 0.999316 2034847 1462.86 + 12.919 0.999414 2035043 1706.67 + 13.319 0.999512 2035241 2048.00 + 13.559 0.999561 2035342 2275.56 + 13.863 0.999609 2035440 2560.00 + 14.191 0.999658 2035541 2925.71 + 14.495 0.999707 2035639 3413.33 + 14.863 0.999756 2035738 4096.00 + 15.063 0.999780 2035788 4551.11 + 15.287 0.999805 2035838 5120.00 + 15.583 0.999829 2035890 5851.43 + 15.791 0.999854 2035939 6826.67 + 15.983 0.999878 2035987 8192.00 + 16.167 0.999890 2036012 9102.22 + 16.351 0.999902 2036038 10240.00 + 16.623 0.999915 2036064 11702.86 + 16.895 0.999927 2036086 13653.33 + 17.103 0.999939 2036111 16384.00 + 17.247 0.999945 2036124 18204.44 + 17.359 0.999951 2036136 20480.00 + 17.551 0.999957 2036150 23405.71 + 17.727 0.999963 2036161 27306.67 + 18.191 0.999969 2036173 32768.00 + 18.511 0.999973 2036181 36408.89 + 18.591 0.999976 2036186 40960.00 + 18.719 0.999979 2036192 46811.43 + 18.815 0.999982 2036198 54613.33 + 19.231 0.999985 2036204 65536.00 + 19.775 0.999986 2036208 72817.78 + 20.143 0.999988 2036211 81920.00 + 20.303 0.999989 2036214 93622.86 + 20.799 0.999991 2036217 109226.67 + 21.551 0.999992 2036220 131072.00 + 21.999 0.999993 2036222 145635.56 + 22.143 0.999994 2036223 163840.00 + 25.519 0.999995 2036225 187245.71 + 26.079 0.999995 2036226 218453.33 + 27.167 0.999996 2036228 262144.00 + 28.303 0.999997 2036229 291271.11 + 28.303 0.999997 2036229 327680.00 + 28.751 0.999997 2036230 374491.43 + 28.991 0.999998 2036231 436906.67 + 29.311 0.999998 2036232 524288.00 + 29.311 0.999998 2036232 582542.22 + 29.311 0.999998 2036232 655360.00 + 30.383 0.999999 2036233 748982.86 + 30.383 0.999999 2036233 873813.33 + 30.399 0.999999 2036234 1048576.00 + 30.399 0.999999 2036234 1165084.44 + 30.399 0.999999 2036234 1310720.00 + 30.399 0.999999 2036234 1497965.71 + 30.399 0.999999 2036234 1747626.67 + 31.551 1.000000 2036235 2097152.00 + 31.551 1.000000 2036235 inf +#[Mean = 3.980, StdDeviation = 1.467] +#[Max = 31.536, Total count = 2036235] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 2147949 requests in 3.00m, 137.25MB read +Requests/sec: 11932.85 +Transfer/sec: 780.76KB diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_put_synchronous_queue.txt b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_put_synchronous_queue.txt new file mode 100644 index 000000000..71c30ec95 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/2/wrk_put_synchronous_queue.txt @@ -0,0 +1,128 @@ +Running 3m test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 2.299ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.44ms 525.73us 27.06ms 82.88% + Req/Sec 4.75k 0.90k 15.50k 84.20% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.40ms + 75.000% 1.68ms + 90.000% 1.94ms + 99.000% 2.65ms + 99.900% 5.69ms + 99.990% 17.47ms + 99.999% 25.47ms +100.000% 27.07ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.253 0.000000 1 1.00 + 0.952 0.100000 76610 1.11 + 1.108 0.200000 153141 1.25 + 1.217 0.300000 229522 1.43 + 1.313 0.400000 306313 1.67 + 1.404 0.500000 382322 2.00 + 1.455 0.550000 420085 2.22 + 1.512 0.600000 458347 2.50 + 1.567 0.650000 496455 2.86 + 1.623 0.700000 534727 3.33 + 1.679 0.750000 573200 4.00 + 1.707 0.775000 592254 4.44 + 1.738 0.800000 611132 5.00 + 1.775 0.825000 630194 5.71 + 1.825 0.850000 649265 6.67 + 1.884 0.875000 668371 8.00 + 1.912 0.887500 677801 8.89 + 1.938 0.900000 687353 10.00 + 1.968 0.912500 696834 11.43 + 2.002 0.925000 706337 13.33 + 2.042 0.937500 715829 16.00 + 2.067 0.943750 720615 17.78 + 2.097 0.950000 725432 20.00 + 2.129 0.956250 730299 22.86 + 2.167 0.962500 734988 26.67 + 2.223 0.968750 739723 32.00 + 2.281 0.971875 742124 35.56 + 2.383 0.975000 744462 40.00 + 2.475 0.978125 746868 45.71 + 2.527 0.981250 749316 53.33 + 2.575 0.984375 751625 64.00 + 2.603 0.985938 752871 71.11 + 2.625 0.987500 754106 80.00 + 2.645 0.989062 755296 91.43 + 2.663 0.990625 756407 106.67 + 2.685 0.992188 757603 128.00 + 2.699 0.992969 758269 142.22 + 2.713 0.993750 758827 160.00 + 2.729 0.994531 759385 182.86 + 2.751 0.995313 760015 213.33 + 2.775 0.996094 760572 256.00 + 2.789 0.996484 760868 284.44 + 2.805 0.996875 761165 320.00 + 2.825 0.997266 761477 365.71 + 2.859 0.997656 761766 426.67 + 2.953 0.998047 762058 512.00 + 3.117 0.998242 762208 568.89 + 3.439 0.998437 762358 640.00 + 3.955 0.998633 762506 731.43 + 4.659 0.998828 762656 853.33 + 5.923 0.999023 762804 1024.00 + 6.999 0.999121 762878 1137.78 + 7.643 0.999219 762953 1280.00 + 8.123 0.999316 763028 1462.86 + 8.607 0.999414 763102 1706.67 + 9.151 0.999512 763177 2048.00 + 9.551 0.999561 763214 2275.56 + 10.271 0.999609 763252 2560.00 + 11.575 0.999658 763289 2925.71 + 12.655 0.999707 763326 3413.33 + 13.399 0.999756 763363 4096.00 + 13.735 0.999780 763382 4551.11 + 14.199 0.999805 763400 5120.00 + 14.887 0.999829 763419 5851.43 + 16.071 0.999854 763438 6826.67 + 16.767 0.999878 763456 8192.00 + 17.247 0.999890 763466 9102.22 + 17.519 0.999902 763475 10240.00 + 17.839 0.999915 763484 11702.86 + 18.495 0.999927 763494 13653.33 + 20.191 0.999939 763503 16384.00 + 20.735 0.999945 763508 18204.44 + 20.799 0.999951 763512 20480.00 + 21.247 0.999957 763517 23405.71 + 21.839 0.999963 763522 27306.67 + 23.183 0.999969 763526 32768.00 + 24.175 0.999973 763529 36408.89 + 24.527 0.999976 763531 40960.00 + 25.039 0.999979 763533 46811.43 + 25.151 0.999982 763536 54613.33 + 25.311 0.999985 763539 65536.00 + 25.311 0.999986 763539 72817.78 + 25.391 0.999988 763540 81920.00 + 25.471 0.999989 763541 93622.86 + 25.567 0.999991 763543 109226.67 + 25.583 0.999992 763544 131072.00 + 25.583 0.999993 763544 145635.56 + 25.775 0.999994 763545 163840.00 + 25.775 0.999995 763545 187245.71 + 26.127 0.999995 763546 218453.33 + 26.143 0.999996 763547 262144.00 + 26.143 0.999997 763547 291271.11 + 26.143 0.999997 763547 327680.00 + 26.143 0.999997 763547 374491.43 + 26.175 0.999998 763548 436906.67 + 26.175 0.999998 763548 524288.00 + 26.175 0.999998 763548 582542.22 + 26.175 0.999998 763548 655360.00 + 26.175 0.999999 763548 748982.86 + 27.071 0.999999 763549 873813.33 + 27.071 1.000000 763549 inf +#[Mean = 1.444, StdDeviation = 0.526] +#[Max = 27.056, Total count = 763549] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 805510 requests in 3.00m, 51.47MB read + Socket errors: connect 0, read 0, write 0, timeout 3520 +Requests/sec: 4475.04 +Transfer/sec: 292.80KB From 58a3b0ae4666b6f75e9268676b668c45fbad4f29 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Wed, 13 Mar 2024 18:42:05 +0300 Subject: [PATCH 03/19] 3rd stage Signed-off-by: vitaliy.emelyanov --- .../itmo/test/emelyanovvitaliy/DhtServer.java | 90 ++++++++++++++++--- .../test/emelyanovvitaliy/DhtService.java | 42 +++++++++ .../REDLinkedBlockingQueue.java | 3 +- .../UncheckedInterruptedException.java | 8 ++ .../UncheckedTimeoutException.java | 7 ++ 5 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtService.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/UncheckedInterruptedException.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/UncheckedTimeoutException.java diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java index 33700eb8b..58fba5745 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -1,5 +1,7 @@ package ru.vk.itmo.test.emelyanovvitaliy; +import one.nio.http.HttpClient; +import one.nio.http.HttpException; import one.nio.http.HttpServer; import one.nio.http.HttpServerConfig; import one.nio.http.HttpSession; @@ -8,6 +10,8 @@ import one.nio.http.Request; import one.nio.http.RequestMethod; import one.nio.http.Response; +import one.nio.net.ConnectionString; +import one.nio.pool.PoolException; import one.nio.server.AcceptorConfig; import ru.vk.itmo.ServiceConfig; import ru.vk.itmo.dao.BaseEntry; @@ -20,9 +24,14 @@ import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; import static one.nio.http.Request.METHOD_DELETE; import static one.nio.http.Request.METHOD_GET; @@ -33,6 +42,8 @@ public class DhtServer extends HttpServer { public static final int THREADS_PER_PROCESSOR = 2; public static final long KEEP_ALIVE_TIME_MS = 1000; public static final int REQUEST_TIMEOUT_MS = 1024; + public static final int MAX_RETRIES_TO_STOP_EXECUTOR = 256; + protected final Map hashToClientMapping; private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, @@ -44,6 +55,7 @@ public class DhtServer extends HttpServer { public DhtServer(ServiceConfig config) throws IOException { super(createConfig(config)); + hashToClientMapping = getHashToUrlMap(config.clusterUrls(), config.selfUrl()); dao = new ReferenceDao(new Config(config.workingDir(), 1 << 24)); } @@ -51,15 +63,31 @@ public DhtServer(ServiceConfig config) throws IOException { public synchronized void stop() { super.stop(); try { + for (HttpClient httpClient : hashToClientMapping.values()) { + if (httpClient != null && !httpClient.isClosed()) { + httpClient.close(); + } + } + threadPoolExecutor.shutdown(); + int cnt = 0; + while (!threadPoolExecutor.awaitTermination(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + Logger.getGlobal().warning("Awaiting for termination too long"); + if (++cnt >= MAX_RETRIES_TO_STOP_EXECUTOR) { + Logger.getGlobal().severe("Awaited to terminate for too long, throwing exception!"); + throw new UncheckedTimeoutException("Awaited to terminate for too long, throwing exception!"); + } + } dao.close(); } catch (IOException e) { throw new UncheckedIOException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } } @RequestMethod(METHOD_GET) @Path("/v0/entity") - public void entity(@Param(value = "id") String id, HttpSession session) throws IOException { + public void entity(@Param(value = "id") String id, HttpSession session, Request request) throws IOException { if (isKeyIncorrect(id)) { session.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); } else { @@ -69,13 +97,13 @@ public void entity(@Param(value = "id") String id, HttpSession session) throws I try { if (System.currentTimeMillis() - start >= REQUEST_TIMEOUT_MS) { session.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); - return; - } - Entry entry = dao.get(keyFor(id)); - if (entry == null) { - session.sendResponse(new Response(Response.NOT_FOUND, EMPTY_BODY)); - } else { - session.sendResponse(new Response(Response.OK, valueFor(entry))); + } else if (!tryForward(session, request, id)) { + Entry entry = dao.get(keyFor(id)); + if (entry == null) { + session.sendResponse(new Response(Response.NOT_FOUND, EMPTY_BODY)); + } else { + session.sendResponse(new Response(Response.OK, valueFor(entry))); + } } } catch (IOException e) { throw new UncheckedIOException(e); @@ -85,6 +113,26 @@ public void entity(@Param(value = "id") String id, HttpSession session) throws I } } + private HttpClient getHttpClientByKey(String key) { + return hashToClientMapping.get(Math.abs(key.hashCode()) % hashToClientMapping.size()); + } + + private boolean tryForward(HttpSession session, Request request, String key) throws IOException { + HttpClient httpClient = getHttpClientByKey(key); + if (httpClient == null) { + return false; + } + try { + session.sendResponse(httpClient.invoke(request)); + } catch (PoolException | HttpException e) { + session.sendResponse(new Response(Response.INTERNAL_ERROR, EMPTY_BODY)); + } catch (InterruptedException e) { + session.sendResponse(new Response(Response.INTERNAL_ERROR, EMPTY_BODY)); + Thread.currentThread().interrupt(); + } + return true; + } + @RequestMethod(METHOD_PUT) @Path("/v0/entity") public void putEntity(@Param(value = "id") String id, HttpSession httpSession, Request request) throws IOException { @@ -99,8 +147,10 @@ public void putEntity(@Param(value = "id") String id, HttpSession httpSession, R httpSession.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); return; } - dao.upsert(new BaseEntry<>(keyFor(id), MemorySegment.ofArray(request.getBody()))); - httpSession.sendResponse(new Response(Response.CREATED, EMPTY_BODY)); + if (!tryForward(httpSession, request, id)) { + dao.upsert(new BaseEntry<>(keyFor(id), MemorySegment.ofArray(request.getBody()))); + httpSession.sendResponse(new Response(Response.CREATED, EMPTY_BODY)); + } } catch (IOException e) { throw new UncheckedIOException(e); } @@ -111,7 +161,7 @@ public void putEntity(@Param(value = "id") String id, HttpSession httpSession, R @RequestMethod(METHOD_DELETE) @Path("/v0/entity") - public void deleteEntity(@Param("id") String id, HttpSession httpSession) throws IOException { + public void deleteEntity(@Param("id") String id, HttpSession httpSession, Request request) throws IOException { if (isKeyIncorrect(id)) { httpSession.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); } else { @@ -123,8 +173,10 @@ public void deleteEntity(@Param("id") String id, HttpSession httpSession) throws httpSession.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); return; } - dao.upsert(new BaseEntry<>(keyFor(id), null)); - httpSession.sendResponse(new Response(Response.ACCEPTED, EMPTY_BODY)); + if (!tryForward(httpSession, request, id)) { + dao.upsert(new BaseEntry<>(keyFor(id), null)); + httpSession.sendResponse(new Response(Response.ACCEPTED, EMPTY_BODY)); + } } catch (IOException e) { throw new UncheckedIOException(e); } @@ -157,6 +209,18 @@ private static HttpServerConfig createConfig(ServiceConfig serviceConfig) { return config; } + private static Map getHashToUrlMap(List urls, String thisUrl) { + + Map hashToUrlMapping = new HashMap<>(); + int cnt = 0; + List tmpList = new ArrayList<>(urls); + tmpList.sort(String::compareTo); + for (String url : tmpList) { + hashToUrlMapping.put(cnt++, url.equals(thisUrl) ? null : new HttpClient(new ConnectionString(url))); + } + return hashToUrlMapping; + } + private static MemorySegment keyFor(String id) { return MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)); } diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtService.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtService.java new file mode 100644 index 000000000..2bd0ed2c0 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtService.java @@ -0,0 +1,42 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import ru.vk.itmo.Service; +import ru.vk.itmo.ServiceConfig; +import ru.vk.itmo.test.ServiceFactory; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +public class DhtService implements Service { + private final ServiceConfig serviceConfig; + private DhtServer server; + + public DhtService(ServiceConfig serviceConfig) { + this.serviceConfig = serviceConfig; + } + + @Override + public CompletableFuture start() throws IOException { + server = new DhtServer(serviceConfig); + server.start(); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture stop() throws IOException { + if (server != null) { + server.stop(); + server = null; + } + return CompletableFuture.completedFuture(null); + } + + @ServiceFactory(stage = 3) + public static class Factory implements ServiceFactory.Factory { + + @Override + public Service create(ServiceConfig config) { + return new DhtService(config); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/REDLinkedBlockingQueue.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/REDLinkedBlockingQueue.java index 72b972c4d..17f60c90f 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/REDLinkedBlockingQueue.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/REDLinkedBlockingQueue.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.emelyanovvitaliy; -import java.util.Collection; -import java.util.Iterator; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; @@ -18,6 +16,7 @@ public class REDLinkedBlockingQueue extends LinkedBlockingDeque implements public REDLinkedBlockingQueue(int capacity) { super(capacity); } + @Override public T remove() { if (shouldTakeLast()) { diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/UncheckedInterruptedException.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/UncheckedInterruptedException.java new file mode 100644 index 000000000..a23689a3a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/UncheckedInterruptedException.java @@ -0,0 +1,8 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +public class UncheckedInterruptedException extends RuntimeException { + + public UncheckedInterruptedException(InterruptedException e) { + super(e); + } +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/UncheckedTimeoutException.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/UncheckedTimeoutException.java new file mode 100644 index 000000000..13a4dc2c0 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/UncheckedTimeoutException.java @@ -0,0 +1,7 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +public class UncheckedTimeoutException extends RuntimeException { + public UncheckedTimeoutException(String s) { + super(s); + } +} From e867ae31da412b3f19eab549817a754f2dcfdc39 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Sun, 17 Mar 2024 19:47:35 +0300 Subject: [PATCH 04/19] 2nd stage fix after review Signed-off-by: vitaliy.emelyanov --- .../itmo/test/emelyanovvitaliy/DhtServer.java | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java index 58fba5745..9562219f2 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -28,10 +28,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; +import java.util.concurrent.atomic.AtomicInteger; import static one.nio.http.Request.METHOD_DELETE; import static one.nio.http.Request.METHOD_GET; @@ -40,48 +40,53 @@ public class DhtServer extends HttpServer { public static final byte[] EMPTY_BODY = new byte[0]; public static final int THREADS_PER_PROCESSOR = 2; - public static final long KEEP_ALIVE_TIME_MS = 1000; - public static final int REQUEST_TIMEOUT_MS = 1024; - public static final int MAX_RETRIES_TO_STOP_EXECUTOR = 256; protected final Map hashToClientMapping; + public static final long KEEP_ALIVE_TIME_MILLIS = 1000; + public static final int REQUEST_TIMEOUT_MILLIS = 1024; + public static final int FLUSH_THRESHOLD_BYTES = 1 << 24; + public static final int THREAD_POOL_TERMINATION_TIMEOUT_SECONDS = 600; + public static final int TASK_QUEUE_SIZE = Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR; + public final AtomicInteger threadsInPool = new AtomicInteger(0); private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, - KEEP_ALIVE_TIME_MS, + KEEP_ALIVE_TIME_MILLIS, TimeUnit.MILLISECONDS, - new SynchronousQueue<>() + new ArrayBlockingQueue<>(TASK_QUEUE_SIZE), + r -> { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("DhtServerThreadPool-Thread-" + threadsInPool.incrementAndGet()); + return t; + } ); private final ReferenceDao dao; public DhtServer(ServiceConfig config) throws IOException { super(createConfig(config)); hashToClientMapping = getHashToUrlMap(config.clusterUrls(), config.selfUrl()); - dao = new ReferenceDao(new Config(config.workingDir(), 1 << 24)); + dao = new ReferenceDao(new Config(config.workingDir(), FLUSH_THRESHOLD_BYTES)); } @Override public synchronized void stop() { super.stop(); try { + threadPoolExecutor.shutdown(); + if (!threadPoolExecutor.awaitTermination(THREAD_POOL_TERMINATION_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + throw new UncheckedTimeoutException("Waited too lot to stop the thread pool"); + } for (HttpClient httpClient : hashToClientMapping.values()) { if (httpClient != null && !httpClient.isClosed()) { httpClient.close(); } } - threadPoolExecutor.shutdown(); - int cnt = 0; - while (!threadPoolExecutor.awaitTermination(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { - Logger.getGlobal().warning("Awaiting for termination too long"); - if (++cnt >= MAX_RETRIES_TO_STOP_EXECUTOR) { - Logger.getGlobal().severe("Awaited to terminate for too long, throwing exception!"); - throw new UncheckedTimeoutException("Awaited to terminate for too long, throwing exception!"); - } - } dao.close(); } catch (IOException e) { throw new UncheckedIOException(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); + throw new UncheckedInterruptedException(e); } } @@ -89,13 +94,13 @@ public synchronized void stop() { @Path("/v0/entity") public void entity(@Param(value = "id") String id, HttpSession session, Request request) throws IOException { if (isKeyIncorrect(id)) { - session.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); + sendBadRequestResponse(session); } else { - long start = System.currentTimeMillis(); + long startTimeInMillis = System.currentTimeMillis(); threadPoolExecutor.execute( () -> { try { - if (System.currentTimeMillis() - start >= REQUEST_TIMEOUT_MS) { + if (System.currentTimeMillis() - startTimeInMillis > REQUEST_TIMEOUT_MILLIS) { session.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); } else if (!tryForward(session, request, id)) { Entry entry = dao.get(keyFor(id)); @@ -136,14 +141,14 @@ private boolean tryForward(HttpSession session, Request request, String key) thr @RequestMethod(METHOD_PUT) @Path("/v0/entity") public void putEntity(@Param(value = "id") String id, HttpSession httpSession, Request request) throws IOException { - if (isKeyIncorrect(id)) { - httpSession.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); + if (isKeyIncorrect(id) || request.getBody() == null) { + sendBadRequestResponse(httpSession); } else { - long start = System.currentTimeMillis(); + long startTimeInMillis = System.currentTimeMillis(); threadPoolExecutor.execute( () -> { try { - if (System.currentTimeMillis() - start > KEEP_ALIVE_TIME_MS) { + if (System.currentTimeMillis() - startTimeInMillis > REQUEST_TIMEOUT_MILLIS) { httpSession.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); return; } @@ -163,13 +168,13 @@ public void putEntity(@Param(value = "id") String id, HttpSession httpSession, R @Path("/v0/entity") public void deleteEntity(@Param("id") String id, HttpSession httpSession, Request request) throws IOException { if (isKeyIncorrect(id)) { - httpSession.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); + sendBadRequestResponse(httpSession); } else { - long start = System.currentTimeMillis(); + long startTimeInMillis = System.currentTimeMillis(); threadPoolExecutor.execute( () -> { try { - if (System.currentTimeMillis() - start > KEEP_ALIVE_TIME_MS) { + if (System.currentTimeMillis() - startTimeInMillis > REQUEST_TIMEOUT_MILLIS) { httpSession.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); return; } @@ -189,12 +194,16 @@ public void deleteEntity(@Param("id") String id, HttpSession httpSession, Reques public void handleDefault(Request request, HttpSession session) throws IOException { int requestMethod = request.getMethod(); if (requestMethod == METHOD_GET || requestMethod == METHOD_PUT || requestMethod == METHOD_DELETE) { - session.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); + sendBadRequestResponse(session); } else { session.sendResponse(new Response(Response.METHOD_NOT_ALLOWED, EMPTY_BODY)); } } + private static void sendBadRequestResponse(HttpSession httpSession) throws IOException { + httpSession.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); + } + private static boolean isKeyIncorrect(String key) { return key == null || key.isEmpty(); } From 6a3a352f4f55a3497d20b31757f8a880e6aa5a58 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Wed, 20 Mar 2024 22:41:23 +0300 Subject: [PATCH 05/19] 3nd stage fix after pre-review Signed-off-by: vitaliy.emelyanov --- .../itmo/test/emelyanovvitaliy/DhtServer.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java index 9562219f2..fa6998b4a 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -25,9 +25,7 @@ import java.lang.foreign.ValueLayout; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -40,10 +38,10 @@ public class DhtServer extends HttpServer { public static final byte[] EMPTY_BODY = new byte[0]; public static final int THREADS_PER_PROCESSOR = 2; - protected final Map hashToClientMapping; + protected final HttpClient[] httpClients; public static final long KEEP_ALIVE_TIME_MILLIS = 1000; public static final int REQUEST_TIMEOUT_MILLIS = 1024; - public static final int FLUSH_THRESHOLD_BYTES = 1 << 24; + public static final int FLUSH_THRESHOLD_BYTES = 1 << 24; // 16 MiB public static final int THREAD_POOL_TERMINATION_TIMEOUT_SECONDS = 600; public static final int TASK_QUEUE_SIZE = Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR; public final AtomicInteger threadsInPool = new AtomicInteger(0); @@ -64,7 +62,7 @@ public class DhtServer extends HttpServer { public DhtServer(ServiceConfig config) throws IOException { super(createConfig(config)); - hashToClientMapping = getHashToUrlMap(config.clusterUrls(), config.selfUrl()); + httpClients = getHttpClients(config.clusterUrls(), config.selfUrl()); dao = new ReferenceDao(new Config(config.workingDir(), FLUSH_THRESHOLD_BYTES)); } @@ -76,7 +74,7 @@ public synchronized void stop() { if (!threadPoolExecutor.awaitTermination(THREAD_POOL_TERMINATION_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { throw new UncheckedTimeoutException("Waited too lot to stop the thread pool"); } - for (HttpClient httpClient : hashToClientMapping.values()) { + for (HttpClient httpClient : httpClients) { if (httpClient != null && !httpClient.isClosed()) { httpClient.close(); } @@ -119,7 +117,7 @@ public void entity(@Param(value = "id") String id, HttpSession session, Request } private HttpClient getHttpClientByKey(String key) { - return hashToClientMapping.get(Math.abs(key.hashCode()) % hashToClientMapping.size()); + return httpClients[Math.abs(key.hashCode()) % httpClients.length]; } private boolean tryForward(HttpSession session, Request request, String key) throws IOException { @@ -218,16 +216,16 @@ private static HttpServerConfig createConfig(ServiceConfig serviceConfig) { return config; } - private static Map getHashToUrlMap(List urls, String thisUrl) { + private static HttpClient[] getHttpClients(List urls, String thisUrl) { - Map hashToUrlMapping = new HashMap<>(); + HttpClient[] clients = new HttpClient[urls.size()]; int cnt = 0; List tmpList = new ArrayList<>(urls); tmpList.sort(String::compareTo); for (String url : tmpList) { - hashToUrlMapping.put(cnt++, url.equals(thisUrl) ? null : new HttpClient(new ConnectionString(url))); + clients[cnt++] = url.equals(thisUrl) ? null : new HttpClient(new ConnectionString(url)); } - return hashToUrlMapping; + return clients; } private static MemorySegment keyFor(String id) { From 6937aa0537ea70c7ea401c04dadcb2daeb7e8bab Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Wed, 20 Mar 2024 23:53:37 +0300 Subject: [PATCH 06/19] 3rd stage report Signed-off-by: vitaliy.emelyanov --- .../emelyanovvitaliy/reports/3/get_alloc.html | 2349 +++ .../emelyanovvitaliy/reports/3/get_cpu.html | 12390 +++++++++++++++ .../emelyanovvitaliy/reports/3/get_lock.html | 1982 +++ .../emelyanovvitaliy/reports/3/put_alloc.html | 4080 +++++ .../emelyanovvitaliy/reports/3/put_cpu.html | 12832 ++++++++++++++++ .../emelyanovvitaliy/reports/3/put_lock.html | 1458 ++ .../emelyanovvitaliy/reports/3/wrk2_get.txt | 140 + .../emelyanovvitaliy/reports/3/wrk2_put.txt | 123 + 8 files changed, 35354 insertions(+) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/get_alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/get_cpu.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/get_lock.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/put_alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/put_cpu.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/put_lock.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/wrk2_get.txt create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/wrk2_put.txt diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/get_alloc.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/get_alloc.html new file mode 100644 index 000000000..e6fa098af --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/get_alloc.html @@ -0,0 +1,2349 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/get_cpu.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/get_cpu.html new file mode 100644 index 000000000..50e6863c4 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/get_cpu.html @@ -0,0 +1,12390 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/get_lock.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/get_lock.html new file mode 100644 index 000000000..3b0d802c1 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/get_lock.html @@ -0,0 +1,1982 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/put_alloc.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/put_alloc.html new file mode 100644 index 000000000..67fac4dab --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/put_alloc.html @@ -0,0 +1,4080 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/put_cpu.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/put_cpu.html new file mode 100644 index 000000000..8b8f79b06 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/put_cpu.html @@ -0,0 +1,12832 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/put_lock.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/put_lock.html new file mode 100644 index 000000000..2a58de543 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/put_lock.html @@ -0,0 +1,1458 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/wrk2_get.txt b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/wrk2_get.txt new file mode 100644 index 000000000..fc20dc3d6 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/wrk2_get.txt @@ -0,0 +1,140 @@ +Running 10m test @ http://localhost:8080 + 8 threads and 1024 connections + Thread calibration: mean lat.: 2.308ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 2.463ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 19.287ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 66.588ms, rate sampling interval: 268ms + Thread calibration: mean lat.: 2.234ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 2.518ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 2.462ms, rate sampling interval: 10ms + Thread calibration: mean lat.: 79.836ms, rate sampling interval: 686ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.75ms 1.00ms 52.22ms 89.70% + Req/Sec 242.46 167.88 1.67k 65.81% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.58ms + 75.000% 2.01ms + 90.000% 2.57ms + 99.000% 5.36ms + 99.900% 11.21ms + 99.990% 24.82ms + 99.999% 44.00ms +100.000% 52.26ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.330 0.000000 1 1.00 + 0.951 0.100000 150480 1.11 + 1.143 0.200000 300006 1.25 + 1.303 0.300000 449759 1.43 + 1.445 0.400000 600157 1.67 + 1.581 0.500000 750328 2.00 + 1.649 0.550000 824623 2.22 + 1.723 0.600000 900340 2.50 + 1.805 0.650000 974556 2.86 + 1.901 0.700000 1049350 3.33 + 2.011 0.750000 1124414 4.00 + 2.073 0.775000 1162316 4.44 + 2.143 0.800000 1200078 5.00 + 2.221 0.825000 1237006 5.71 + 2.313 0.850000 1274809 6.67 + 2.423 0.875000 1311938 8.00 + 2.489 0.887500 1330568 8.89 + 2.567 0.900000 1349321 10.00 + 2.659 0.912500 1367914 11.43 + 2.773 0.925000 1386729 13.33 + 2.917 0.937500 1405434 16.00 + 3.009 0.943750 1414788 17.78 + 3.119 0.950000 1424123 20.00 + 3.251 0.956250 1433504 22.86 + 3.417 0.962500 1442801 26.67 + 3.641 0.968750 1452212 32.00 + 3.779 0.971875 1456916 35.56 + 3.933 0.975000 1461561 40.00 + 4.123 0.978125 1466243 45.71 + 4.355 0.981250 1470939 53.33 + 4.643 0.984375 1475599 64.00 + 4.811 0.985938 1477955 71.11 + 4.999 0.987500 1480280 80.00 + 5.211 0.989062 1482632 91.43 + 5.463 0.990625 1484982 106.67 + 5.763 0.992188 1487302 128.00 + 5.947 0.992969 1488477 142.22 + 6.159 0.993750 1489650 160.00 + 6.407 0.994531 1490820 182.86 + 6.723 0.995313 1491987 213.33 + 7.115 0.996094 1493166 256.00 + 7.371 0.996484 1493745 284.44 + 7.655 0.996875 1494335 320.00 + 7.975 0.997266 1494915 365.71 + 8.423 0.997656 1495506 426.67 + 8.943 0.998047 1496096 512.00 + 9.247 0.998242 1496383 568.89 + 9.583 0.998437 1496671 640.00 + 10.039 0.998633 1496964 731.43 + 10.567 0.998828 1497263 853.33 + 11.319 0.999023 1497554 1024.00 + 11.735 0.999121 1497697 1137.78 + 12.311 0.999219 1497844 1280.00 + 12.991 0.999316 1497991 1462.86 + 13.687 0.999414 1498135 1706.67 + 14.359 0.999512 1498282 2048.00 + 14.791 0.999561 1498355 2275.56 + 15.343 0.999609 1498428 2560.00 + 16.103 0.999658 1498501 2925.71 + 16.719 0.999707 1498574 3413.33 + 17.615 0.999756 1498649 4096.00 + 18.015 0.999780 1498684 4551.11 + 18.639 0.999805 1498721 5120.00 + 19.871 0.999829 1498757 5851.43 + 20.975 0.999854 1498794 6826.67 + 22.543 0.999878 1498831 8192.00 + 23.935 0.999890 1498849 9102.22 + 25.151 0.999902 1498867 10240.00 + 26.191 0.999915 1498885 11702.86 + 27.791 0.999927 1498904 13653.33 + 29.567 0.999939 1498922 16384.00 + 31.183 0.999945 1498931 18204.44 + 32.367 0.999951 1498940 20480.00 + 33.631 0.999957 1498950 23405.71 + 35.519 0.999963 1498959 27306.67 + 37.663 0.999969 1498968 32768.00 + 38.207 0.999973 1498972 36408.89 + 40.127 0.999976 1498977 40960.00 + 40.895 0.999979 1498981 46811.43 + 42.015 0.999982 1498986 54613.33 + 42.943 0.999985 1498991 65536.00 + 43.167 0.999986 1498994 72817.78 + 43.359 0.999988 1498995 81920.00 + 43.967 0.999989 1498997 93622.86 + 44.223 0.999991 1499000 109226.67 + 44.479 0.999992 1499002 131072.00 + 44.959 0.999993 1499003 145635.56 + 45.247 0.999994 1499004 163840.00 + 45.471 0.999995 1499005 187245.71 + 46.367 0.999995 1499007 218453.33 + 46.559 0.999996 1499008 262144.00 + 46.559 0.999997 1499008 291271.11 + 46.623 0.999997 1499009 327680.00 + 46.623 0.999997 1499009 374491.43 + 46.655 0.999998 1499010 436906.67 + 47.103 0.999998 1499011 524288.00 + 47.103 0.999998 1499011 582542.22 + 47.103 0.999998 1499011 655360.00 + 47.103 0.999999 1499011 748982.86 + 49.503 0.999999 1499012 873813.33 + 49.503 0.999999 1499012 1048576.00 + 49.503 0.999999 1499012 1165084.44 + 49.503 0.999999 1499012 1310720.00 + 49.503 0.999999 1499012 1497965.71 + 52.255 0.999999 1499013 1747626.67 + 52.255 1.000000 1499013 inf +#[Mean = 1.749, StdDeviation = 0.998] +#[Max = 52.224, Total count = 1499013] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 1565700 requests in 10.01m, 153.27MB read + Socket errors: connect 11, read 0, write 0, timeout 297893 +Requests/sec: 2606.71 +Transfer/sec: 261.31KB diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/wrk2_put.txt b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/wrk2_put.txt new file mode 100644 index 000000000..381d73011 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/wrk2_put.txt @@ -0,0 +1,123 @@ +Running 1m test @ http://localhost:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 48.038ms, rate sampling interval: 327ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.16ms 644.11us 13.18ms 98.46% + Req/Sec 8.46k 548.23 10.66k 78.81% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.08ms + 75.000% 1.38ms + 90.000% 1.57ms + 99.000% 1.99ms + 99.900% 9.99ms + 99.990% 12.05ms + 99.999% 12.94ms +100.000% 13.18ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.213 0.000000 1 1.00 + 0.704 0.100000 41942 1.11 + 0.792 0.200000 83968 1.25 + 0.876 0.300000 125861 1.43 + 0.963 0.400000 167823 1.67 + 1.081 0.500000 209896 2.00 + 1.152 0.550000 230663 2.22 + 1.211 0.600000 251683 2.50 + 1.271 0.650000 272630 2.86 + 1.326 0.700000 293684 3.33 + 1.376 0.750000 314554 4.00 + 1.402 0.775000 325184 4.44 + 1.430 0.800000 335679 5.00 + 1.461 0.825000 346238 5.71 + 1.492 0.850000 356472 6.67 + 1.530 0.875000 367102 8.00 + 1.551 0.887500 372193 8.89 + 1.574 0.900000 377494 10.00 + 1.599 0.912500 382675 11.43 + 1.623 0.925000 387928 13.33 + 1.648 0.937500 393221 16.00 + 1.661 0.943750 395849 17.78 + 1.674 0.950000 398384 20.00 + 1.688 0.956250 401049 22.86 + 1.703 0.962500 403710 26.67 + 1.719 0.968750 406250 32.00 + 1.728 0.971875 407492 35.56 + 1.738 0.975000 408815 40.00 + 1.751 0.978125 410132 45.71 + 1.767 0.981250 411442 53.33 + 1.791 0.984375 412766 64.00 + 1.808 0.985938 413387 71.11 + 1.835 0.987500 414049 80.00 + 1.891 0.989062 414691 91.43 + 2.217 0.990625 415347 106.67 + 3.249 0.992188 416000 128.00 + 3.915 0.992969 416328 142.22 + 4.623 0.993750 416655 160.00 + 5.355 0.994531 416985 182.86 + 6.059 0.995313 417311 213.33 + 6.767 0.996094 417638 256.00 + 7.135 0.996484 417801 284.44 + 7.507 0.996875 417967 320.00 + 7.903 0.997266 418130 365.71 + 8.327 0.997656 418298 426.67 + 8.775 0.998047 418458 512.00 + 8.975 0.998242 418538 568.89 + 9.207 0.998437 418624 640.00 + 9.455 0.998633 418702 731.43 + 9.743 0.998828 418784 853.33 + 10.023 0.999023 418869 1024.00 + 10.167 0.999121 418907 1137.78 + 10.343 0.999219 418949 1280.00 + 10.511 0.999316 418991 1462.86 + 10.631 0.999414 419030 1706.67 + 10.783 0.999512 419071 2048.00 + 10.879 0.999561 419092 2275.56 + 10.975 0.999609 419112 2560.00 + 11.159 0.999658 419132 2925.71 + 11.367 0.999707 419154 3413.33 + 11.495 0.999756 419173 4096.00 + 11.559 0.999780 419184 4551.11 + 11.663 0.999805 419194 5120.00 + 11.719 0.999829 419204 5851.43 + 11.823 0.999854 419214 6826.67 + 11.935 0.999878 419225 8192.00 + 12.007 0.999890 419229 9102.22 + 12.071 0.999902 419235 10240.00 + 12.127 0.999915 419240 11702.86 + 12.183 0.999927 419245 13653.33 + 12.247 0.999939 419250 16384.00 + 12.303 0.999945 419252 18204.44 + 12.327 0.999951 419255 20480.00 + 12.375 0.999957 419258 23405.71 + 12.447 0.999963 419260 27306.67 + 12.495 0.999969 419263 32768.00 + 12.551 0.999973 419264 36408.89 + 12.559 0.999976 419265 40960.00 + 12.663 0.999979 419267 46811.43 + 12.727 0.999982 419268 54613.33 + 12.751 0.999985 419269 65536.00 + 12.847 0.999986 419270 72817.78 + 12.847 0.999988 419270 81920.00 + 12.935 0.999989 419271 93622.86 + 12.983 0.999991 419272 109226.67 + 12.983 0.999992 419272 131072.00 + 13.007 0.999993 419273 145635.56 + 13.007 0.999994 419273 163840.00 + 13.007 0.999995 419273 187245.71 + 13.103 0.999995 419274 218453.33 + 13.103 0.999996 419274 262144.00 + 13.103 0.999997 419274 291271.11 + 13.103 0.999997 419274 327680.00 + 13.103 0.999997 419274 374491.43 + 13.183 0.999998 419275 436906.67 + 13.183 1.000000 419275 inf +#[Mean = 1.157, StdDeviation = 0.644] +#[Max = 13.176, Total count = 419275] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 550887 requests in 1.00m, 43.60MB read + Socket errors: connect 0, read 0, write 0, timeout 977 +Requests/sec: 9181.36 +Transfer/sec: 744.17KB From 5d1dce7b3cbd7354d0087d6fa97b4d7d166e33a9 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Sat, 23 Mar 2024 17:12:29 +0300 Subject: [PATCH 07/19] Add forgotten markdown file I checked gst today and seen there uncommited report file I understand if you wouldn't check that, it's my mistake Signed-off-by: vitaliy.emelyanov --- .../itmo/test/emelyanovvitaliy/reports/3/3.md | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/3.md diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/3.md b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/3.md new file mode 100644 index 000000000..08632060a --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/3/3.md @@ -0,0 +1,44 @@ +# Емельянов Виталий, 3 этап + +## Запись + +Нагрузка на запись производится тем же, скриптом, что использовался на этапах 1, 2 +в 1 поток, 64 подключения, 20'000 RPS (точка разладки ~25'000 RPS) + +[Логи wrk2](wrk2_put.txt) + +### Alloc + +[Профиль по аллокациям](put_alloc.html) + +Нетрудно заметить, что появилась большая часть сэмплов, связанная с работой HttpClient. +В частности, всего ~66% сэмплов связано с выделением буффера для ResponseReader, суммарно +же с работой HttpClient связано ~75% сэмплов. + +### ЦПУ + +[Профиль ЦПУ](put_cpu.html) + +Ожидаемо, вслед за повышением количества аллокаций выросла и работа GC (c ~7% сэмплов до ~12%). +С работой HttpClient связано ~19% сэмплов. + +### Блокировки + +[Профиль блокировок](put_lock.html) + +Изменений по блокировкам не наблюдается + +## Чтение + +Нагрузка на запись производится тем же, скриптом, что использовался на этапах 1, 2 +в 1 поток, 64 подключения, 100'000 RPS (точка разладки ~120'000 RPS). +Такой резкий сдвиг точки разладки связан с тем, что, во-первых, снизился общий объем данных +(раньше он составлял ~6 ГиБ, теперь ~4), так еще и на каждую из 3х нод приходится лишь ее часть +(~1.33 ГиБ), что сокращает время поиска. + +[Логи wrk2](wrk2_get.txt) + +Профили изменились полностью аналогично тому, как они изменились при работе на запись +(с отличием лишь в конкретных процентах сэмплов, поскольку полезная нагрузка идет другого рода), +поскольку логика измененная логика работы одинаково затрагивает и чтение и запись, т.е. пересылка +запросов выполняется одинаково, а непосредственная логика работы не менялась. \ No newline at end of file From 089100c1a0b0a4a70e33b118c21ed9c2dab3a13a Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Sat, 23 Mar 2024 18:40:54 +0300 Subject: [PATCH 08/19] Copy reference dao to own dir Signed-off-by: vitaliy.emelyanov --- .../itmo/test/emelyanovvitaliy/DhtServer.java | 2 +- .../dao/ByteArraySegment.java | 48 +++ .../dao/LiveFilteringIterator.java | 52 ++++ .../test/emelyanovvitaliy/dao/MemTable.java | 49 +++ .../dao/MemorySegmentComparator.java | 89 ++++++ .../dao/MergingEntryIterator.java | 68 ++++ .../emelyanovvitaliy/dao/ReferenceDao.java | 292 ++++++++++++++++++ .../test/emelyanovvitaliy/dao/SSTable.java | 204 ++++++++++++ .../emelyanovvitaliy/dao/SSTableWriter.java | 166 ++++++++++ .../test/emelyanovvitaliy/dao/SSTables.java | 162 ++++++++++ .../test/emelyanovvitaliy/dao/TableSet.java | 201 ++++++++++++ .../dao/WeightedPeekingEntryIterator.java | 67 ++++ 12 files changed, 1399 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/ByteArraySegment.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/LiveFilteringIterator.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemTable.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemorySegmentComparator.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MergingEntryIterator.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/ReferenceDao.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTableWriter.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTables.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TableSet.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/WeightedPeekingEntryIterator.java diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java index fa6998b4a..7ddc6e4cb 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -17,7 +17,7 @@ import ru.vk.itmo.dao.BaseEntry; import ru.vk.itmo.dao.Config; import ru.vk.itmo.dao.Entry; -import ru.vk.itmo.test.reference.dao.ReferenceDao; +import ru.vk.itmo.test.emelyanovvitaliy.dao.ReferenceDao; import java.io.IOException; import java.io.UncheckedIOException; diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/ByteArraySegment.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/ByteArraySegment.java new file mode 100644 index 000000000..340d39c81 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/ByteArraySegment.java @@ -0,0 +1,48 @@ +package ru.vk.itmo.test.emelyanovvitaliy.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/emelyanovvitaliy/dao/LiveFilteringIterator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/LiveFilteringIterator.java new file mode 100644 index 000000000..fe109a587 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/LiveFilteringIterator.java @@ -0,0 +1,52 @@ +package ru.vk.itmo.test.emelyanovvitaliy.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 Entry next; + + LiveFilteringIterator(final Iterator> delegate) { + this.delegate = delegate; + skipTombstones(); + } + + private void skipTombstones() { + while (delegate.hasNext()) { + final Entry entry = delegate.next(); + if (entry.value() != null) { + this.next = entry; + break; + } + } + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + // Consume + final Entry result = next; + next = null; + + skipTombstones(); + + return result; + } +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemTable.java new file mode 100644 index 000000000..a7c9adfcb --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemTable.java @@ -0,0 +1,49 @@ +package ru.vk.itmo.test.emelyanovvitaliy.dao; + +import ru.vk.itmo.dao.Entry; + +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(); + } + } + + Entry get(final MemorySegment key) { + return map.get(key); + } + + Entry upsert(final Entry entry) { + return map.put(entry.key(), entry); + } +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemorySegmentComparator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemorySegmentComparator.java new file mode 100644 index 000000000..6ad2e6d2c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemorySegmentComparator.java @@ -0,0 +1,89 @@ +package ru.vk.itmo.test.emelyanovvitaliy.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/emelyanovvitaliy/dao/MergingEntryIterator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MergingEntryIterator.java new file mode 100644 index 000000000..f8cb0c627 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MergingEntryIterator.java @@ -0,0 +1,68 @@ +package ru.vk.itmo.test.emelyanovvitaliy.dao; + +import ru.vk.itmo.dao.Entry; + +import java.lang.foreign.MemorySegment; +import java.util.*; + +/** + * 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 Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final WeightedPeekingEntryIterator top = iterators.remove(); + final Entry 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 Entry 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/emelyanovvitaliy/dao/ReferenceDao.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/ReferenceDao.java new file mode 100644 index 000000000..722ccca20 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/ReferenceDao.java @@ -0,0 +1,292 @@ +package ru.vk.itmo.test.emelyanovvitaliy.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 Entry get(final MemorySegment key) { + // Without lock, just snapshot of table set + return tableSet.get(key); + } + + @Override + public void upsert(final Entry 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/emelyanovvitaliy/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java new file mode 100644 index 000000000..a30283b16 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java @@ -0,0 +1,204 @@ +package ru.vk.itmo.test.emelyanovvitaliy.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; +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); + } + + Entry 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); + if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { + // Tombstone encountered + return new BaseEntry<>(key, null); + } else { + // Get value + offset += Long.BYTES; + final MemorySegment value = data.asSlice(offset, valueLength); + return new BaseEntry<>(key, value); + } + } + + 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 Entry 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 BaseEntry<>(key, null); + } else { + final MemorySegment value = data.asSlice(offset, valueLength); + offset += valueLength; + return new BaseEntry<>(key, value); + } + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTableWriter.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTableWriter.java new file mode 100644 index 000000000..bc90daffe --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTableWriter.java @@ -0,0 +1,166 @@ +package ru.vk.itmo.test.emelyanovvitaliy.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 Entry 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 Entry 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(); + } + + return result; + } +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTables.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTables.java new file mode 100644 index 000000000..a8232f9fd --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTables.java @@ -0,0 +1,162 @@ +package ru.vk.itmo.test.emelyanovvitaliy.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/emelyanovvitaliy/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TableSet.java new file mode 100644 index 000000000..f309a7c51 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TableSet.java @@ -0,0 +1,201 @@ +package ru.vk.itmo.test.emelyanovvitaliy.dao; + +import ru.vk.itmo.dao.Entry; + +import java.lang.foreign.MemorySegment; +import java.util.*; +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); + }; + } + + Entry get(final MemorySegment key) { + // Slightly optimized version not to pollute the heap + + // First check MemTable + Entry 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 null; + } + + private static Entry swallowTombstone(final Entry entry) { + return entry.value() == null ? null : entry; + } + + Entry upsert(final Entry 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/emelyanovvitaliy/dao/WeightedPeekingEntryIterator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/WeightedPeekingEntryIterator.java new file mode 100644 index 000000000..1e4797f1f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/WeightedPeekingEntryIterator.java @@ -0,0 +1,67 @@ +package ru.vk.itmo.test.emelyanovvitaliy.dao; + +import ru.vk.itmo.dao.Entry; + +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 Entry 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 Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final Entry result = next; + next = delegate.hasNext() ? delegate.next() : null; + return result; + } + + Entry 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); + } +} From e80716bed0de746c59060e2353323667f06a7b33 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Sat, 23 Mar 2024 22:02:52 +0300 Subject: [PATCH 09/19] Add timestamps to dao Signed-off-by: vitaliy.emelyanov --- .../dao/LiveFilteringIterator.java | 14 +++---- .../test/emelyanovvitaliy/dao/MemTable.java | 8 ++-- .../dao/MergingEntryIterator.java | 8 ++-- .../emelyanovvitaliy/dao/ReferenceDao.java | 8 ++-- .../test/emelyanovvitaliy/dao/SSTable.java | 39 +++++++++++++------ .../emelyanovvitaliy/dao/SSTableWriter.java | 25 ++++++++++-- .../test/emelyanovvitaliy/dao/SSTables.java | 18 +++++++++ .../test/emelyanovvitaliy/dao/TableSet.java | 33 +++++++--------- .../dao/TimestampedEntry.java | 37 ++++++++++++++++++ .../dao/WeightedPeekingEntryIterator.java | 14 +++---- 10 files changed, 145 insertions(+), 59 deletions(-) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TimestampedEntry.java diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/LiveFilteringIterator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/LiveFilteringIterator.java index fe109a587..3775a7919 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/LiveFilteringIterator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/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 TimestampedEntry 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 TimestampedEntry entry = delegate.next(); if (entry.value() != null) { this.next = entry; break; @@ -36,13 +36,13 @@ public boolean hasNext() { } @Override - public Entry next() { + public TimestampedEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } // Consume - final Entry result = next; + final TimestampedEntry result = next; next = null; skipTombstones(); diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemTable.java index a7c9adfcb..a26d4f953 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemTable.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemTable.java @@ -13,7 +13,7 @@ * @author incubos */ final class MemTable { - private final NavigableMap> map = + private final NavigableMap> map = new ConcurrentSkipListMap<>( MemorySegmentComparator.INSTANCE); @@ -21,7 +21,7 @@ boolean isEmpty() { return map.isEmpty(); } - Iterator> get( + Iterator> get( final MemorySegment from, final MemorySegment to) { if (from == null && to == null) { @@ -39,11 +39,11 @@ Iterator> get( } } - Entry get(final MemorySegment key) { + TimestampedEntry get(final MemorySegment key) { return map.get(key); } - Entry upsert(final Entry entry) { + TimestampedEntry upsert(final TimestampedEntry entry) { return map.put(entry.key(), entry); } } diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MergingEntryIterator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MergingEntryIterator.java index f8cb0c627..4309806ca 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MergingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MergingEntryIterator.java @@ -10,7 +10,7 @@ * * @author incubos */ -final class MergingEntryIterator implements Iterator> { +final class MergingEntryIterator implements Iterator> { private final Queue iterators; MergingEntryIterator(final List iterators) { @@ -25,13 +25,13 @@ public boolean hasNext() { } @Override - public Entry next() { + public TimestampedEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } final WeightedPeekingEntryIterator top = iterators.remove(); - final Entry result = top.next(); + final TimestampedEntry result = top.next(); if (top.hasNext()) { // Not exhausted @@ -47,7 +47,7 @@ public Entry next() { } // Skip entries with the same key - final Entry entry = iterator.peek(); + final TimestampedEntry 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/emelyanovvitaliy/dao/ReferenceDao.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/ReferenceDao.java index 722ccca20..01a9cf6fd 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/ReferenceDao.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/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 TimestampedEntry 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 TimestampedEntry entry) { final boolean autoFlush; lock.readLock().lock(); try { diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java index a30283b16..4f282e8f0 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java @@ -1,7 +1,7 @@ package ru.vk.itmo.test.emelyanovvitaliy.dao; +import one.nio.os.Mem; import ru.vk.itmo.dao.BaseEntry; -import ru.vk.itmo.dao.Entry; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; @@ -19,15 +19,18 @@ final class SSTable { final int sequence; private final MemorySegment index; + private final MemorySegment time; private final MemorySegment data; private final long size; SSTable( final int sequence, final MemorySegment index, + final MemorySegment time, final MemorySegment data) { this.sequence = sequence; this.index = index; + this.time = time; this.data = data; this.size = index.byteSize() / Long.BYTES; } @@ -36,6 +39,7 @@ SSTable withSequence(final int sequence) { return new SSTable( sequence, index, + time, data); } @@ -82,27 +86,36 @@ private long entryOffset(final long entry) { entry * Long.BYTES); } + private long getTime(final long entry) { + return time.get( + ValueLayout.OfLong.JAVA_LONG_UNALIGNED, + entry * Long.BYTES + ); + } + private long getLength(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; // Slice of SSTable in absolute offsets + final long fromEntry; final long fromOffset; final long toOffset; // Left offset bound if (from == null) { // Start from the beginning + fromEntry = 0L; fromOffset = 0L; } else { - final long fromEntry = entryBinarySearch(from); + fromEntry = entryBinarySearch(from); if (fromEntry >= 0L) { fromOffset = entryOffset(fromEntry); } else if (-fromEntry - 1 == size) { @@ -131,10 +144,10 @@ Iterator> get( } } - return new SliceIterator(fromOffset, toOffset); + return new SliceIterator(fromEntry, fromOffset, toOffset); } - Entry get(final MemorySegment key) { + TimestampedEntry get(final MemorySegment key) { final long entry = entryBinarySearch(key); if (entry < 0) { return null; @@ -147,23 +160,26 @@ Entry get(final MemorySegment key) { final long valueLength = getLength(offset); if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { // Tombstone encountered - return new BaseEntry<>(key, null); + return new TimestampedEntry<>(key, null, getTime(entry)); } else { // Get value offset += Long.BYTES; final MemorySegment value = data.asSlice(offset, valueLength); - return new BaseEntry<>(key, value); + return new TimestampedEntry<>(key, value, getTime(entry)); } } - private final class SliceIterator implements Iterator> { + private final class SliceIterator implements Iterator> { + private long entry; private long offset; private final long toOffset; private SliceIterator( + final long fromEntry, final long offset, final long toOffset) { this.offset = offset; + this.entry = fromEntry; this.toOffset = toOffset; } @@ -173,7 +189,7 @@ public boolean hasNext() { } @Override - public Entry next() { + public TimestampedEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } @@ -193,11 +209,12 @@ public Entry next() { // Read value if (valueLength == SSTables.TOMBSTONE_VALUE_LENGTH) { // Tombstone encountered - return new BaseEntry<>(key, null); + return new TimestampedEntry<>(key, null); } else { final MemorySegment value = data.asSlice(offset, valueLength); offset += valueLength; - return new BaseEntry<>(key, value); + entry += 1; + return new TimestampedEntry<>(key, value, getTime(entry)); } } } diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTableWriter.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTableWriter.java index bc90daffe..2f6c2868e 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTableWriter.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTableWriter.java @@ -40,10 +40,11 @@ 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); + final Path tempTimeName = SSTables.tempTimeName(baseDir, sequence); // Delete temporary files to eliminate tails Files.deleteIfExists(tempIndexName); @@ -62,7 +63,13 @@ void write( new BufferedOutputStream( new FileOutputStream( tempDataName.toFile()), - BUFFER_SIZE)) { + BUFFER_SIZE); + OutputStream time = + new BufferedOutputStream( + new FileOutputStream( + tempTimeName.toFile()), + BUFFER_SIZE) + ){ long entryOffset = 0L; // Iterate and serialize @@ -71,7 +78,8 @@ void write( writeLong(entryOffset, index); // Then write the entry - final Entry entry = entries.next(); + final TimestampedEntry entry = entries.next(); + writeLong(entry.timestamp(), time); entryOffset += writeEntry(entry, data); } } @@ -87,6 +95,17 @@ void write( indexName, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); + + final Path timeName = + SSTables.timeName( + baseDir, + sequence); + Files.move( + tempTimeName, + timeName, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + final Path dataName = SSTables.dataName( baseDir, diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTables.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTables.java index a8232f9fd..1c3c5656b 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTables.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTables.java @@ -22,6 +22,7 @@ final class SSTables { public static final String INDEX_SUFFIX = ".index"; public static final String DATA_SUFFIX = ".data"; + public static final String TIME_SUFFIX = ".time"; public static final long TOMBSTONE_VALUE_LENGTH = -1L; private static final String TEMP_SUFFIX = ".tmp"; @@ -45,6 +46,12 @@ static Path dataName( return baseDir.resolve(sequence + DATA_SUFFIX); } + static Path timeName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + TIME_SUFFIX); + } + static Path tempIndexName( final Path baseDir, final int sequence) { @@ -57,6 +64,12 @@ static Path tempDataName( return baseDir.resolve(sequence + DATA_SUFFIX + TEMP_SUFFIX); } + public static Path tempTimeName( + final Path baseDir, + final int sequence) { + return baseDir.resolve(sequence + TIME_SUFFIX + TEMP_SUFFIX); + } + /** * Returns {@link List} of {@link SSTable}s from freshest to oldest. */ @@ -105,6 +118,10 @@ static SSTable open( mapReadOnly( arena, indexName(baseDir, sequence)); + final MemorySegment time = + mapReadOnly( + arena, + timeName(baseDir, sequence)); final MemorySegment data = mapReadOnly( arena, @@ -113,6 +130,7 @@ static SSTable open( return new SSTable( sequence, index, + time, data); } diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TableSet.java index f309a7c51..67d142e28 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TableSet.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TableSet.java @@ -3,6 +3,7 @@ import ru.vk.itmo.dao.Entry; import java.lang.foreign.MemorySegment; +import java.sql.Time; import java.util.*; import java.util.concurrent.atomic.AtomicLong; @@ -94,14 +95,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( @@ -112,7 +113,7 @@ Iterator> get( // Then goes flushing if (flushingTable != null) { - final Iterator> flushingIterator = + final Iterator> flushingIterator = flushingTable.get(from, to); if (flushingIterator.hasNext()) { iterators.add( @@ -125,7 +126,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( @@ -137,27 +138,25 @@ Iterator> get( return switch (iterators.size()) { case 0 -> Collections.emptyIterator(); - case 1 -> iterators.get(0); + case 1 -> iterators.getFirst(); default -> new MergingEntryIterator(iterators); }; } - Entry get(final MemorySegment key) { + TimestampedEntry get(final MemorySegment key) { // Slightly optimized version not to pollute the heap // First check MemTable - Entry result = memTable.get(key); + TimestampedEntry 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; } } @@ -166,7 +165,7 @@ Entry get(final MemorySegment key) { result = ssTable.get(key); if (result != null) { // Transform tombstone - return swallowTombstone(result); + return result; } } @@ -174,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) { + TimestampedEntry upsert(final TimestampedEntry 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/emelyanovvitaliy/dao/TimestampedEntry.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TimestampedEntry.java new file mode 100644 index 000000000..d8aee78f3 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TimestampedEntry.java @@ -0,0 +1,37 @@ +package ru.vk.itmo.test.emelyanovvitaliy.dao; + +import ru.vk.itmo.dao.Entry; + +public class TimestampedEntry implements Entry { + private final D key; + private final D value; + private final long timestamp; + + public TimestampedEntry(Entry entry) { + this(entry.key(), entry.value()); + } + + public TimestampedEntry(D key, D value) { + this(key, value, System.currentTimeMillis()); + } + + public TimestampedEntry(D key, D value, long timestamp) { + this.key = key; + this.value = value; + this.timestamp = timestamp; + } + + public long timestamp() { + return timestamp; + } + + @Override + public D key() { + return key; + } + + @Override + public D value() { + return value; + } +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/WeightedPeekingEntryIterator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/WeightedPeekingEntryIterator.java index 1e4797f1f..c74bab166 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/WeightedPeekingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/WeightedPeekingEntryIterator.java @@ -12,15 +12,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 TimestampedEntry 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 +32,17 @@ public boolean hasNext() { } @Override - public Entry next() { + public TimestampedEntry next() { if (!hasNext()) { throw new NoSuchElementException(); } - final Entry result = next; + final TimestampedEntry result = next; next = delegate.hasNext() ? delegate.next() : null; return result; } - Entry peek() { + TimestampedEntry peek() { if (!hasNext()) { throw new NoSuchElementException(); } From 81b68f7d6aa365de9c6d01ce124ffdedfbd0ad88 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Wed, 27 Mar 2024 23:52:15 +0300 Subject: [PATCH 10/19] 4th stage Signed-off-by: vitaliy.emelyanov --- .../test/emelyanovvitaliy/DaoMediator.java | 37 +++ .../itmo/test/emelyanovvitaliy/DhtServer.java | 160 ++++++------- .../test/emelyanovvitaliy/DhtService.java | 2 +- .../emelyanovvitaliy/LocalDaoMediator.java | 56 +++++ .../emelyanovvitaliy/MergeDaoMediator.java | 216 ++++++++++++++++++ .../emelyanovvitaliy/RemoteDaoMediator.java | 97 ++++++++ 6 files changed, 474 insertions(+), 94 deletions(-) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DaoMediator.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DaoMediator.java new file mode 100644 index 000000000..1672c17c7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DaoMediator.java @@ -0,0 +1,37 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import one.nio.http.HttpSession; +import one.nio.http.Request; +import one.nio.http.Response; +import ru.vk.itmo.dao.Entry; +import ru.vk.itmo.test.emelyanovvitaliy.dao.TimestampedEntry; + +import java.io.IOException; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; + +public abstract class DaoMediator { + public static final long NEVER_TIMESTAMP = Long.MIN_VALUE; + protected static MemorySegment keyFor(String id) { + return MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)); + } + + protected static byte[] valueFor(Entry entry) { + return entry.value().toArray(ValueLayout.JAVA_BYTE); + } + + abstract void stop(); + abstract boolean isStopped(); + + abstract boolean put(Request request); + + /** + * @return null if error, + * entry with null value and timestamp if the answer is tombstone + * entry with null value and timestamp equals NEVER_TIMESTAMP if no answer + * entry if it exists + */ + abstract TimestampedEntry get(Request request); + abstract boolean delete(Request request); +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java index 7ddc6e4cb..11c987d9f 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.emelyanovvitaliy; -import one.nio.http.HttpClient; -import one.nio.http.HttpException; import one.nio.http.HttpServer; import one.nio.http.HttpServerConfig; import one.nio.http.HttpSession; @@ -10,22 +8,15 @@ import one.nio.http.Request; import one.nio.http.RequestMethod; import one.nio.http.Response; -import one.nio.net.ConnectionString; -import one.nio.pool.PoolException; import one.nio.server.AcceptorConfig; import ru.vk.itmo.ServiceConfig; -import ru.vk.itmo.dao.BaseEntry; -import ru.vk.itmo.dao.Config; import ru.vk.itmo.dao.Entry; -import ru.vk.itmo.test.emelyanovvitaliy.dao.ReferenceDao; +import ru.vk.itmo.test.emelyanovvitaliy.dao.TimestampedEntry; import java.io.IOException; import java.io.UncheckedIOException; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -36,34 +27,36 @@ import static one.nio.http.Request.METHOD_PUT; public class DhtServer extends HttpServer { - public static final byte[] EMPTY_BODY = new byte[0]; - public static final int THREADS_PER_PROCESSOR = 2; - protected final HttpClient[] httpClients; - public static final long KEEP_ALIVE_TIME_MILLIS = 1000; - public static final int REQUEST_TIMEOUT_MILLIS = 1024; - public static final int FLUSH_THRESHOLD_BYTES = 1 << 24; // 16 MiB - public static final int THREAD_POOL_TERMINATION_TIMEOUT_SECONDS = 600; - public static final int TASK_QUEUE_SIZE = Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR; - public final AtomicInteger threadsInPool = new AtomicInteger(0); - private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( - Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, - Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, - KEEP_ALIVE_TIME_MILLIS, - TimeUnit.MILLISECONDS, - new ArrayBlockingQueue<>(TASK_QUEUE_SIZE), - r -> { - Thread t = new Thread(r); - t.setDaemon(true); - t.setName("DhtServerThreadPool-Thread-" + threadsInPool.incrementAndGet()); - return t; - } - ); - private final ReferenceDao dao; + public static final String NOT_ENOUGH_REPLICAS_STATUS = "504 Not Enough Replicas"; + public static final String TIMESTAMP_HEADER = "X-Timestamp: "; + public static final String ID_KEY = "id="; + protected static final int THREADS_PER_PROCESSOR = 2; + protected static final byte[] EMPTY_BODY = new byte[0]; + protected static final long KEEP_ALIVE_TIME_MILLIS = 1000; + protected static final int REQUEST_TIMEOUT_MILLIS = 1024; + protected static final int THREAD_POOL_TERMINATION_TIMEOUT_SECONDS = 600; + protected static final int TASK_QUEUE_SIZE = Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR; + protected final MergeDaoMediator mergeDaoMediator; + protected final AtomicInteger threadsInPool; + protected final ThreadPoolExecutor threadPoolExecutor; public DhtServer(ServiceConfig config) throws IOException { super(createConfig(config)); - httpClients = getHttpClients(config.clusterUrls(), config.selfUrl()); - dao = new ReferenceDao(new Config(config.workingDir(), FLUSH_THRESHOLD_BYTES)); + mergeDaoMediator = new MergeDaoMediator(config.workingDir(), config.selfUrl(), config.clusterUrls()); + threadsInPool = new AtomicInteger(0); + threadPoolExecutor = new ThreadPoolExecutor( + Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, + Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, + KEEP_ALIVE_TIME_MILLIS, + TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(TASK_QUEUE_SIZE), + r -> { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("DhtServerThreadPool-Thread-" + threadsInPool.incrementAndGet()); + return t; + } + ); } @Override @@ -74,14 +67,7 @@ public synchronized void stop() { if (!threadPoolExecutor.awaitTermination(THREAD_POOL_TERMINATION_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { throw new UncheckedTimeoutException("Waited too lot to stop the thread pool"); } - for (HttpClient httpClient : httpClients) { - if (httpClient != null && !httpClient.isClosed()) { - httpClient.close(); - } - } - dao.close(); - } catch (IOException e) { - throw new UncheckedIOException(e); + mergeDaoMediator.stop(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new UncheckedInterruptedException(e); @@ -90,7 +76,8 @@ public synchronized void stop() { @RequestMethod(METHOD_GET) @Path("/v0/entity") - public void entity(@Param(value = "id") String id, HttpSession session, Request request) throws IOException { + public void entity(HttpSession session, Request request) throws IOException { + String id = request.getParameter(ID_KEY); if (isKeyIncorrect(id)) { sendBadRequestResponse(session); } else { @@ -100,41 +87,32 @@ public void entity(@Param(value = "id") String id, HttpSession session, Request try { if (System.currentTimeMillis() - startTimeInMillis > REQUEST_TIMEOUT_MILLIS) { session.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); - } else if (!tryForward(session, request, id)) { - Entry entry = dao.get(keyFor(id)); - if (entry == null) { - session.sendResponse(new Response(Response.NOT_FOUND, EMPTY_BODY)); + } + TimestampedEntry entry = mergeDaoMediator.get(request); + if (entry == null) { + sendNotEnoughReplicas(session); + } else if (entry.timestamp() == DaoMediator.NEVER_TIMESTAMP) { + session.sendResponse(new Response(Response.NOT_FOUND, EMPTY_BODY)); + } else { + Response response; + if (entry.value() == null) { + response = new Response(Response.NOT_FOUND, EMPTY_BODY); } else { - session.sendResponse(new Response(Response.OK, valueFor(entry))); + response = new Response(Response.OK, ((Entry) entry).value().toArray(ValueLayout.JAVA_BYTE)); } + response.addHeader(TIMESTAMP_HEADER + entry.timestamp()); + session.sendResponse(response); } } catch (IOException e) { throw new UncheckedIOException(e); + } catch (IllegalArgumentException e) { + sendBadRequestResponseUnchecked(session); } } ); } } - private HttpClient getHttpClientByKey(String key) { - return httpClients[Math.abs(key.hashCode()) % httpClients.length]; - } - - private boolean tryForward(HttpSession session, Request request, String key) throws IOException { - HttpClient httpClient = getHttpClientByKey(key); - if (httpClient == null) { - return false; - } - try { - session.sendResponse(httpClient.invoke(request)); - } catch (PoolException | HttpException e) { - session.sendResponse(new Response(Response.INTERNAL_ERROR, EMPTY_BODY)); - } catch (InterruptedException e) { - session.sendResponse(new Response(Response.INTERNAL_ERROR, EMPTY_BODY)); - Thread.currentThread().interrupt(); - } - return true; - } @RequestMethod(METHOD_PUT) @Path("/v0/entity") @@ -150,12 +128,15 @@ public void putEntity(@Param(value = "id") String id, HttpSession httpSession, R httpSession.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); return; } - if (!tryForward(httpSession, request, id)) { - dao.upsert(new BaseEntry<>(keyFor(id), MemorySegment.ofArray(request.getBody()))); + if (mergeDaoMediator.put(request)) { httpSession.sendResponse(new Response(Response.CREATED, EMPTY_BODY)); + } else { + sendNotEnoughReplicas(httpSession); } } catch (IOException e) { throw new UncheckedIOException(e); + } catch (IllegalArgumentException e) { + sendBadRequestResponseUnchecked(httpSession); } } ); @@ -176,12 +157,15 @@ public void deleteEntity(@Param("id") String id, HttpSession httpSession, Reques httpSession.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); return; } - if (!tryForward(httpSession, request, id)) { - dao.upsert(new BaseEntry<>(keyFor(id), null)); + if (mergeDaoMediator.delete(request)) { httpSession.sendResponse(new Response(Response.ACCEPTED, EMPTY_BODY)); + } else { + sendNotEnoughReplicas(httpSession); } } catch (IOException e) { throw new UncheckedIOException(e); + } catch (IllegalArgumentException e) { + sendBadRequestResponseUnchecked(httpSession); } } ); @@ -198,9 +182,20 @@ public void handleDefault(Request request, HttpSession session) throws IOExcepti } } + private static void sendNotEnoughReplicas(HttpSession session) throws IOException { + session.sendResponse(new Response(NOT_ENOUGH_REPLICAS_STATUS, EMPTY_BODY)); + } + private static void sendBadRequestResponse(HttpSession httpSession) throws IOException { httpSession.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); } + private void sendBadRequestResponseUnchecked(HttpSession session) { + try { + sendBadRequestResponse(session); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } private static boolean isKeyIncorrect(String key) { return key == null || key.isEmpty(); @@ -215,25 +210,4 @@ private static HttpServerConfig createConfig(ServiceConfig serviceConfig) { config.closeSessions = true; return config; } - - private static HttpClient[] getHttpClients(List urls, String thisUrl) { - - HttpClient[] clients = new HttpClient[urls.size()]; - int cnt = 0; - List tmpList = new ArrayList<>(urls); - tmpList.sort(String::compareTo); - for (String url : tmpList) { - clients[cnt++] = url.equals(thisUrl) ? null : new HttpClient(new ConnectionString(url)); - } - return clients; - } - - private static MemorySegment keyFor(String id) { - return MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)); - } - - private static byte[] valueFor(Entry entry) { - return entry.value().toArray(ValueLayout.JAVA_BYTE); - } - } diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtService.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtService.java index 2bd0ed2c0..f9326bc7f 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtService.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtService.java @@ -31,7 +31,7 @@ public CompletableFuture stop() throws IOException { return CompletableFuture.completedFuture(null); } - @ServiceFactory(stage = 3) + @ServiceFactory(stage = 4) public static class Factory implements ServiceFactory.Factory { @Override diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java new file mode 100644 index 000000000..deb85f623 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java @@ -0,0 +1,56 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import one.nio.http.Request; +import ru.vk.itmo.test.emelyanovvitaliy.dao.ReferenceDao; +import ru.vk.itmo.test.emelyanovvitaliy.dao.TimestampedEntry; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.foreign.MemorySegment; +import java.util.concurrent.atomic.AtomicBoolean; + +public class LocalDaoMediator extends DaoMediator { + protected final ReferenceDao dao; + protected final AtomicBoolean isClosed = new AtomicBoolean(false); + LocalDaoMediator(ReferenceDao dao) { + this.dao = dao; + } + + @Override + void stop() { + if (isClosed.getAndSet(true)) { + return; + } + try { + dao.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + boolean isStopped() { + return isClosed.get(); + } + + @Override + boolean put(Request request) { + String id = request.getParameter(DhtServer.ID_KEY); + dao.upsert(new TimestampedEntry<>(keyFor(id), MemorySegment.ofArray(request.getBody()))); + return true; + } + + @Override + TimestampedEntry get(Request request) { + MemorySegment id = keyFor(request.getParameter(DhtServer.ID_KEY)); + TimestampedEntry entry = dao.get(id); + return entry != null ? entry : new TimestampedEntry<>(id, null, NEVER_TIMESTAMP); + } + + @Override + boolean delete(Request request) { + String id = request.getParameter(DhtServer.ID_KEY); + dao.upsert(new TimestampedEntry<>(keyFor(id), null)); + return true; + } +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java new file mode 100644 index 000000000..0a7564bfc --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java @@ -0,0 +1,216 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import one.nio.http.HttpClient; +import one.nio.http.HttpSession; +import one.nio.http.Request; +import one.nio.http.Response; +import one.nio.net.ConnectionString; +import one.nio.util.Hash; +import ru.vk.itmo.dao.Config; +import ru.vk.itmo.test.emelyanovvitaliy.dao.ReferenceDao; +import ru.vk.itmo.test.emelyanovvitaliy.dao.TimestampedEntry; + +import java.io.IOException; +import java.lang.foreign.MemorySegment; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; + +public class MergeDaoMediator extends DaoMediator { + public static final String FINAL_EXECUTION_HEADER = "X-Final-Execution: "; + public static final String ACK_KEY = "ack="; + public static final String FROM_KEY = "from="; + protected static final String TRUE_STRING = "true"; + protected static final int FLUSH_THRESHOLD_BYTES = 1 << 24; // 16 MiB + protected final AtomicBoolean isStopped = new AtomicBoolean(false); + protected final DaoMediator[] daoMediators; + protected final int[] mediatorsHashes; + private final LocalDaoMediator localDaoMediator; + MergeDaoMediator(Path localDir, String thisUrl, List urls) throws IOException { + localDaoMediator = new LocalDaoMediator( + new ReferenceDao( + new Config( + localDir, + FLUSH_THRESHOLD_BYTES + ) + ) + ); + daoMediators = getDaoMediators(urls, thisUrl, localDaoMediator); + mediatorsHashes = getMediatorsHashes(urls); + } + + @Override + void stop() { + if (isStopped.getAndSet(true)) { + return; + } + for (DaoMediator daoMediator : daoMediators) { + daoMediator.stop(); + } + } + + @Override + boolean isStopped() { + return isStopped.get(); + } + + @Override + boolean put(Request request) throws IllegalArgumentException { + return simpleReplicate(request, false); + } + + @Override + TimestampedEntry get(Request request) throws IllegalArgumentException { + if (Objects.equals(request.getHeader(FINAL_EXECUTION_HEADER), TRUE_STRING)) { + return localDaoMediator.get(request); + } else { + int ack, from, answered = 0; + try { + ack = getAck(request); + from = getFrom(request); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(e); + } + if (!isAckFromCorrect(ack, from)) { + throw new IllegalArgumentException("Wrong ack/from: " + ack + "/" + from); + } + String id = request.getParameter("id="); + request.addHeader(FINAL_EXECUTION_HEADER + TRUE_STRING); + int currentMediatorIndex = getFirstMediatorIndex(id); + TimestampedEntry lastEntry = null; + for (int i = 0; i < from; i++) { + TimestampedEntry entry = + daoMediators[currentMediatorIndex].get(request); + lastEntry = findLastEntry(lastEntry, entry); + if (entry != null) { + answered += 1; + } + currentMediatorIndex = (currentMediatorIndex + 1) % daoMediators.length; + } + return answered >= ack ? lastEntry : null; + } + } + + @Override + boolean delete(Request request) throws IllegalArgumentException { + return simpleReplicate(request, true); + } + + private static DaoMediator[] getDaoMediators(List urls, String thisUrl, LocalDaoMediator localDaoMediator) { + DaoMediator[] mediators = new DaoMediator[urls.size()]; + int cnt = 0; + List tmpList = new ArrayList<>(urls); + tmpList.sort(String::compareTo); + for (String url : tmpList) { + if (url.equals(thisUrl)) { + mediators[cnt] = localDaoMediator; + } else { + mediators[cnt] = new RemoteDaoMediator(new HttpClient(new ConnectionString(url))); + } + cnt++; + } + return mediators; + } + + private static int[] getMediatorsHashes(List urls) { + int[] hashes = new int[urls.size()]; + int cnt = 0; + List tmpList = new ArrayList<>(urls); + tmpList.sort(String::compareTo); + for (String url : tmpList) { + // cantor pairing function works nicely only with non-negatives + hashes[cnt++] = Math.abs(Hash.murmur3(url)); + } + return hashes; + } + + private static TimestampedEntry findLastEntry( + TimestampedEntry a, + TimestampedEntry b + ) { + if (a == null) { + return b; + } + if (b == null) { + return a; + } + if (a.timestamp() > b.timestamp()) { + return a; + } + return b; + } + + + private boolean simpleReplicate(Request request, boolean delete) + throws IllegalArgumentException { + int ack = 1; + int answered = 0; + String id = request.getParameter("id="); + if (Objects.equals(request.getHeader(FINAL_EXECUTION_HEADER), TRUE_STRING)) { + if (delete) { + localDaoMediator.delete(request); + } else { + localDaoMediator.put(request); + } + answered = 1; + } else { + int from; + try { + ack = getAck(request); + from = getFrom(request); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(e); + } + if (!isAckFromCorrect(ack, from)) { + throw new IllegalArgumentException("Wrong ack/from: " + ack + "/" + from); + } + request.addHeader(FINAL_EXECUTION_HEADER + TRUE_STRING); + int currentMediatorIndex = getFirstMediatorIndex(id); + for (int i = 0; i < from; i++) { + boolean res; + if (delete) { + res = daoMediators[currentMediatorIndex].delete(request); + } else { + res = daoMediators[currentMediatorIndex].put(request); + } + answered += res ? 1 : 0; + currentMediatorIndex = (currentMediatorIndex + 1) % daoMediators.length; + } + } + return answered >= ack; + } + + + private int getAck(Request request) throws NumberFormatException { + String rawAck = request.getParameter(ACK_KEY); + return rawAck == null ? (daoMediators.length / 2) + 1 : Integer.parseInt(rawAck); + } + + private int getFrom(Request request) throws NumberFormatException { + String rawFrom = request.getParameter(FROM_KEY); + return rawFrom == null ? daoMediators.length : Integer.parseInt(rawFrom); + } + + private boolean isAckFromCorrect(int ack, int from) { + return ack > 0 && from > 0 && ack <= from && from <= daoMediators.length; + } + + + private int getFirstMediatorIndex(String key) { + int maxHash = Integer.MIN_VALUE; + int choosen = 0; + for (int i = 0; i < mediatorsHashes.length; i++) { + // cantor pairing function works nicely only with non-negatives + int keyHash = Math.abs(Hash.murmur3(key)); + int totalHash = (mediatorsHashes[i] + keyHash) * (mediatorsHashes[i] + keyHash + 1) / 2 + keyHash; + if (totalHash > maxHash) { + maxHash = totalHash; + choosen = i; + } + } + return choosen; + } + +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java new file mode 100644 index 000000000..41b1d96b8 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java @@ -0,0 +1,97 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import one.nio.http.*; +import one.nio.pool.PoolException; +import ru.vk.itmo.test.emelyanovvitaliy.dao.TimestampedEntry; + +import java.io.IOException; +import java.lang.foreign.MemorySegment; + +import static ru.vk.itmo.test.emelyanovvitaliy.DhtServer.TIMESTAMP_HEADER; + +public class RemoteDaoMediator extends DaoMediator { + protected static final int DEFAULT_TIMEOUT = 100; + protected final HttpClient client; + protected final int timeout; + RemoteDaoMediator(HttpClient httpClient) { + this(httpClient, DEFAULT_TIMEOUT); + } + + RemoteDaoMediator(HttpClient httpClient, int timeout) { + this.client = httpClient; + this.timeout = timeout; + } + + protected boolean isStatusCodeCorrect(int statusCode) { + return statusCode >= 200 && statusCode < 300; + } + + @Override + void stop() { + if (!client.isClosed()) { + client.close(); + } + } + + @Override + boolean isStopped() { + return client.isClosed(); + } + + @Override + protected boolean put(Request request) { + return simpleForward(request); + } + + protected boolean simpleForward(Request request) { + try { + Response response = client.invoke(request); + return isStatusCodeCorrect(response.getStatus()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new UncheckedInterruptedException(e); + } catch (PoolException | IOException | HttpException e) { + return false; + } + } + + @Override + TimestampedEntry get(Request request) { + Response response = null; + try { + response = client.invoke(request, timeout); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new UncheckedInterruptedException(e); + } catch (PoolException | IOException | HttpException e) { + return null; + } + String timestampHeader = response.getHeader(TIMESTAMP_HEADER); + long timestamp; + if (timestampHeader == null) { + timestamp = NEVER_TIMESTAMP; + } else { + timestamp = Long.parseLong(timestampHeader); + } + if (isStatusCodeCorrect(response.getStatus()) && timestampHeader != null) { + return new TimestampedEntry<>( + keyFor(request.getParameter(DhtServer.ID_KEY)), + MemorySegment.ofArray(response.getBody()), + timestamp + ); + } else if (response.getStatus() == 404) { + return new TimestampedEntry<>( + keyFor(request.getParameter(DhtServer.ID_KEY)), + null, + timestamp + ); + } else { + return null; + } + } + + @Override + boolean delete(Request request) { + return simpleForward(request); + } +} From 5ab3f3e42b5be5b1e25265466dfcd6c43be021f4 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Thu, 28 Mar 2024 01:22:38 +0300 Subject: [PATCH 11/19] 4th stage stylefix Signed-off-by: vitaliy.emelyanov --- .../test/emelyanovvitaliy/DaoMediator.java | 16 +- .../itmo/test/emelyanovvitaliy/DhtServer.java | 140 +++++++++--------- .../emelyanovvitaliy/LocalDaoMediator.java | 6 +- .../emelyanovvitaliy/MergeDaoMediator.java | 67 ++++----- .../emelyanovvitaliy/RemoteDaoMediator.java | 6 +- .../test/emelyanovvitaliy/dao/MemTable.java | 2 - .../dao/MergingEntryIterator.java | 8 +- .../test/emelyanovvitaliy/dao/SSTable.java | 3 - .../emelyanovvitaliy/dao/SSTableWriter.java | 2 +- .../test/emelyanovvitaliy/dao/TableSet.java | 9 +- .../dao/WeightedPeekingEntryIterator.java | 2 - 11 files changed, 129 insertions(+), 132 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DaoMediator.java index 1672c17c7..d037d205b 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DaoMediator.java @@ -1,18 +1,16 @@ package ru.vk.itmo.test.emelyanovvitaliy; -import one.nio.http.HttpSession; import one.nio.http.Request; -import one.nio.http.Response; import ru.vk.itmo.dao.Entry; import ru.vk.itmo.test.emelyanovvitaliy.dao.TimestampedEntry; -import java.io.IOException; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.nio.charset.StandardCharsets; public abstract class DaoMediator { public static final long NEVER_TIMESTAMP = Long.MIN_VALUE; + protected static MemorySegment keyFor(String id) { return MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)); } @@ -22,16 +20,16 @@ protected static byte[] valueFor(Entry entry) { } abstract void stop(); + abstract boolean isStopped(); abstract boolean put(Request request); - /** - * @return null if error, - * entry with null value and timestamp if the answer is tombstone - * entry with null value and timestamp equals NEVER_TIMESTAMP if no answer - * entry if it exists - */ + // returns null if error, + // entry with null value and timestamp if the answer is tombstone + // entry with null value and timestamp equals NEVER_TIMESTAMP if no answer + // entry if it exists abstract TimestampedEntry get(Request request); + abstract boolean delete(Request request); } diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java index 11c987d9f..8cf073db3 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -78,110 +78,107 @@ public synchronized void stop() { @Path("/v0/entity") public void entity(HttpSession session, Request request) throws IOException { String id = request.getParameter(ID_KEY); - if (isKeyIncorrect(id)) { - sendBadRequestResponse(session); - } else { - long startTimeInMillis = System.currentTimeMillis(); - threadPoolExecutor.execute( - () -> { - try { - if (System.currentTimeMillis() - startTimeInMillis > REQUEST_TIMEOUT_MILLIS) { - session.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); - } - TimestampedEntry entry = mergeDaoMediator.get(request); - if (entry == null) { - sendNotEnoughReplicas(session); - } else if (entry.timestamp() == DaoMediator.NEVER_TIMESTAMP) { - session.sendResponse(new Response(Response.NOT_FOUND, EMPTY_BODY)); + requestProccessing(id, session, + () -> { + try { + TimestampedEntry entry = mergeDaoMediator.get(request); + if (entry == null) { + sendNotEnoughReplicas(session); + } else if (entry.timestamp() == DaoMediator.NEVER_TIMESTAMP) { + session.sendResponse(new Response(Response.NOT_FOUND, EMPTY_BODY)); + } else { + Response response; + if (entry.value() == null) { + response = new Response(Response.NOT_FOUND, EMPTY_BODY); } else { - Response response; - if (entry.value() == null) { - response = new Response(Response.NOT_FOUND, EMPTY_BODY); - } else { - response = new Response(Response.OK, ((Entry) entry).value().toArray(ValueLayout.JAVA_BYTE)); - } - response.addHeader(TIMESTAMP_HEADER + entry.timestamp()); - session.sendResponse(response); + response = new Response( + Response.OK, + ((Entry) entry).value().toArray(ValueLayout.JAVA_BYTE) + ); } - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (IllegalArgumentException e) { - sendBadRequestResponseUnchecked(session); + response.addHeader(TIMESTAMP_HEADER + entry.timestamp()); + session.sendResponse(response); } + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (IllegalArgumentException e) { + sendBadRequestResponseUnchecked(session); } - ); - } + } + ); } - @RequestMethod(METHOD_PUT) @Path("/v0/entity") public void putEntity(@Param(value = "id") String id, HttpSession httpSession, Request request) throws IOException { - if (isKeyIncorrect(id) || request.getBody() == null) { - sendBadRequestResponse(httpSession); - } else { - long startTimeInMillis = System.currentTimeMillis(); - threadPoolExecutor.execute( - () -> { - try { - if (System.currentTimeMillis() - startTimeInMillis > REQUEST_TIMEOUT_MILLIS) { - httpSession.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); - return; - } - if (mergeDaoMediator.put(request)) { - httpSession.sendResponse(new Response(Response.CREATED, EMPTY_BODY)); - } else { - sendNotEnoughReplicas(httpSession); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (IllegalArgumentException e) { - sendBadRequestResponseUnchecked(httpSession); + requestProccessing(id, httpSession, + () -> { + try { + if (mergeDaoMediator.put(request)) { + httpSession.sendResponse(new Response(Response.CREATED, EMPTY_BODY)); + } else { + sendNotEnoughReplicas(httpSession); } + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (IllegalArgumentException e) { + sendBadRequestResponseUnchecked(httpSession); } - ); - } + } + ); } @RequestMethod(METHOD_DELETE) @Path("/v0/entity") public void deleteEntity(@Param("id") String id, HttpSession httpSession, Request request) throws IOException { + requestProccessing(id, httpSession, + () -> { + try { + if (mergeDaoMediator.delete(request)) { + httpSession.sendResponse(new Response(Response.ACCEPTED, EMPTY_BODY)); + } else { + sendNotEnoughReplicas(httpSession); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (IllegalArgumentException e) { + sendBadRequestResponseUnchecked(httpSession); + } + } + ); + } + + @Override + public void handleDefault(Request request, HttpSession session) throws IOException { + int requestMethod = request.getMethod(); + if (requestMethod == METHOD_GET || requestMethod == METHOD_PUT || requestMethod == METHOD_DELETE) { + sendBadRequestResponse(session); + } else { + session.sendResponse(new Response(Response.METHOD_NOT_ALLOWED, EMPTY_BODY)); + } + } + + private void requestProccessing(String id, HttpSession session, Runnable runnable) throws IOException { if (isKeyIncorrect(id)) { - sendBadRequestResponse(httpSession); + sendBadRequestResponse(session); } else { long startTimeInMillis = System.currentTimeMillis(); threadPoolExecutor.execute( () -> { try { if (System.currentTimeMillis() - startTimeInMillis > REQUEST_TIMEOUT_MILLIS) { - httpSession.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); + session.sendResponse(new Response(Response.PAYMENT_REQUIRED, EMPTY_BODY)); return; } - if (mergeDaoMediator.delete(request)) { - httpSession.sendResponse(new Response(Response.ACCEPTED, EMPTY_BODY)); - } else { - sendNotEnoughReplicas(httpSession); - } + runnable.run(); } catch (IOException e) { throw new UncheckedIOException(e); - } catch (IllegalArgumentException e) { - sendBadRequestResponseUnchecked(httpSession); } } ); } } - @Override - public void handleDefault(Request request, HttpSession session) throws IOException { - int requestMethod = request.getMethod(); - if (requestMethod == METHOD_GET || requestMethod == METHOD_PUT || requestMethod == METHOD_DELETE) { - sendBadRequestResponse(session); - } else { - session.sendResponse(new Response(Response.METHOD_NOT_ALLOWED, EMPTY_BODY)); - } - } - private static void sendNotEnoughReplicas(HttpSession session) throws IOException { session.sendResponse(new Response(NOT_ENOUGH_REPLICAS_STATUS, EMPTY_BODY)); } @@ -189,6 +186,7 @@ private static void sendNotEnoughReplicas(HttpSession session) throws IOExceptio private static void sendBadRequestResponse(HttpSession httpSession) throws IOException { httpSession.sendResponse(new Response(Response.BAD_REQUEST, EMPTY_BODY)); } + private void sendBadRequestResponseUnchecked(HttpSession session) { try { sendBadRequestResponse(session); diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java index deb85f623..5d8edbcdc 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java @@ -12,6 +12,7 @@ public class LocalDaoMediator extends DaoMediator { protected final ReferenceDao dao; protected final AtomicBoolean isClosed = new AtomicBoolean(false); + LocalDaoMediator(ReferenceDao dao) { this.dao = dao; } @@ -44,7 +45,10 @@ boolean put(Request request) { TimestampedEntry get(Request request) { MemorySegment id = keyFor(request.getParameter(DhtServer.ID_KEY)); TimestampedEntry entry = dao.get(id); - return entry != null ? entry : new TimestampedEntry<>(id, null, NEVER_TIMESTAMP); + if (entry == null) { + return new TimestampedEntry<>(id, null, NEVER_TIMESTAMP); + } + return entry; } @Override diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java index 0a7564bfc..cf69257d6 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java @@ -1,9 +1,7 @@ package ru.vk.itmo.test.emelyanovvitaliy; import one.nio.http.HttpClient; -import one.nio.http.HttpSession; import one.nio.http.Request; -import one.nio.http.Response; import one.nio.net.ConnectionString; import one.nio.util.Hash; import ru.vk.itmo.dao.Config; @@ -24,10 +22,12 @@ public class MergeDaoMediator extends DaoMediator { public static final String FROM_KEY = "from="; protected static final String TRUE_STRING = "true"; protected static final int FLUSH_THRESHOLD_BYTES = 1 << 24; // 16 MiB + public static final String ID_KEY = "id="; protected final AtomicBoolean isStopped = new AtomicBoolean(false); protected final DaoMediator[] daoMediators; protected final int[] mediatorsHashes; private final LocalDaoMediator localDaoMediator; + MergeDaoMediator(Path localDir, String thisUrl, List urls) throws IOException { localDaoMediator = new LocalDaoMediator( new ReferenceDao( @@ -66,7 +66,9 @@ TimestampedEntry get(Request request) throws IllegalArgumentExcep if (Objects.equals(request.getHeader(FINAL_EXECUTION_HEADER), TRUE_STRING)) { return localDaoMediator.get(request); } else { - int ack, from, answered = 0; + int ack; + int from; + int answered = 0; try { ack = getAck(request); from = getFrom(request); @@ -76,7 +78,7 @@ TimestampedEntry get(Request request) throws IllegalArgumentExcep if (!isAckFromCorrect(ack, from)) { throw new IllegalArgumentException("Wrong ack/from: " + ack + "/" + from); } - String id = request.getParameter("id="); + String id = request.getParameter(ID_KEY); request.addHeader(FINAL_EXECUTION_HEADER + TRUE_STRING); int currentMediatorIndex = getFirstMediatorIndex(id); TimestampedEntry lastEntry = null; @@ -142,47 +144,43 @@ private static TimestampedEntry findLastEntry( return b; } - private boolean simpleReplicate(Request request, boolean delete) throws IllegalArgumentException { - int ack = 1; - int answered = 0; - String id = request.getParameter("id="); if (Objects.equals(request.getHeader(FINAL_EXECUTION_HEADER), TRUE_STRING)) { if (delete) { - localDaoMediator.delete(request); + return localDaoMediator.delete(request); } else { - localDaoMediator.put(request); - } - answered = 1; - } else { - int from; - try { - ack = getAck(request); - from = getFrom(request); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(e); - } - if (!isAckFromCorrect(ack, from)) { - throw new IllegalArgumentException("Wrong ack/from: " + ack + "/" + from); + return localDaoMediator.put(request); } - request.addHeader(FINAL_EXECUTION_HEADER + TRUE_STRING); - int currentMediatorIndex = getFirstMediatorIndex(id); - for (int i = 0; i < from; i++) { - boolean res; - if (delete) { - res = daoMediators[currentMediatorIndex].delete(request); - } else { - res = daoMediators[currentMediatorIndex].put(request); - } - answered += res ? 1 : 0; - currentMediatorIndex = (currentMediatorIndex + 1) % daoMediators.length; + } + int ack; + int from; + int answered = 0; + try { + ack = getAck(request); + from = getFrom(request); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(e); + } + if (!isAckFromCorrect(ack, from)) { + throw new IllegalArgumentException("Wrong ack/from: " + ack + "/" + from); + } + request.addHeader(FINAL_EXECUTION_HEADER + TRUE_STRING); + String id = request.getParameter(ID_KEY); + int currentMediatorIndex = getFirstMediatorIndex(id); + for (int i = 0; i < from; i++) { + boolean res; + if (delete) { + res = daoMediators[currentMediatorIndex].delete(request); + } else { + res = daoMediators[currentMediatorIndex].put(request); } + answered += res ? 1 : 0; + currentMediatorIndex = (currentMediatorIndex + 1) % daoMediators.length; } return answered >= ack; } - private int getAck(Request request) throws NumberFormatException { String rawAck = request.getParameter(ACK_KEY); return rawAck == null ? (daoMediators.length / 2) + 1 : Integer.parseInt(rawAck); @@ -197,7 +195,6 @@ private boolean isAckFromCorrect(int ack, int from) { return ack > 0 && from > 0 && ack <= from && from <= daoMediators.length; } - private int getFirstMediatorIndex(String key) { int maxHash = Integer.MIN_VALUE; int choosen = 0; diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java index 41b1d96b8..c2559433f 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java @@ -1,6 +1,9 @@ package ru.vk.itmo.test.emelyanovvitaliy; -import one.nio.http.*; +import one.nio.http.HttpClient; +import one.nio.http.HttpException; +import one.nio.http.Request; +import one.nio.http.Response; import one.nio.pool.PoolException; import ru.vk.itmo.test.emelyanovvitaliy.dao.TimestampedEntry; @@ -13,6 +16,7 @@ public class RemoteDaoMediator extends DaoMediator { protected static final int DEFAULT_TIMEOUT = 100; protected final HttpClient client; protected final int timeout; + RemoteDaoMediator(HttpClient httpClient) { this(httpClient, DEFAULT_TIMEOUT); } diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemTable.java index a26d4f953..31f23e29b 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemTable.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MemTable.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.emelyanovvitaliy.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; import java.util.Iterator; import java.util.NavigableMap; diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MergingEntryIterator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MergingEntryIterator.java index 4309806ca..cb6f13965 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MergingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/MergingEntryIterator.java @@ -1,9 +1,11 @@ package ru.vk.itmo.test.emelyanovvitaliy.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; -import java.util.*; +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. diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java index 4f282e8f0..9102bf2d4 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java @@ -1,8 +1,5 @@ package ru.vk.itmo.test.emelyanovvitaliy.dao; -import one.nio.os.Mem; -import ru.vk.itmo.dao.BaseEntry; - import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.util.Collections; diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTableWriter.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTableWriter.java index 2f6c2868e..972ec2013 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTableWriter.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTableWriter.java @@ -69,7 +69,7 @@ void write( new FileOutputStream( tempTimeName.toFile()), BUFFER_SIZE) - ){ + ) { long entryOffset = 0L; // Iterate and serialize diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TableSet.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TableSet.java index 67d142e28..2fc4ac1c4 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TableSet.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/TableSet.java @@ -1,10 +1,11 @@ package ru.vk.itmo.test.emelyanovvitaliy.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; -import java.sql.Time; -import java.util.*; +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; /** diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/WeightedPeekingEntryIterator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/WeightedPeekingEntryIterator.java index c74bab166..c90861f66 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/WeightedPeekingEntryIterator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/WeightedPeekingEntryIterator.java @@ -1,7 +1,5 @@ package ru.vk.itmo.test.emelyanovvitaliy.dao; -import ru.vk.itmo.dao.Entry; - import java.lang.foreign.MemorySegment; import java.util.Iterator; import java.util.NoSuchElementException; From d2d4e0c953002f7103602dfdab4ca4f4adfd8901 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Thu, 4 Apr 2024 00:35:26 +0300 Subject: [PATCH 12/19] 4th stage report Signed-off-by: vitaliy.emelyanov --- .../emelyanovvitaliy/MergeDaoMediator.java | 2 +- .../itmo/test/emelyanovvitaliy/reports/4/4.md | 60 + .../emelyanovvitaliy/reports/4/get_alloc.html | 3158 ++++ .../emelyanovvitaliy/reports/4/get_cpu.html | 15279 ++++++++++++++++ .../emelyanovvitaliy/reports/4/get_lock.html | 2115 +++ .../emelyanovvitaliy/reports/4/put_alloc.html | 6018 ++++++ .../emelyanovvitaliy/reports/4/put_cpu.html | 15250 +++++++++++++++ .../emelyanovvitaliy/reports/4/put_lock.html | 3268 ++++ .../emelyanovvitaliy/reports/4/wrk2_get.png | Bin 0 -> 98925 bytes .../emelyanovvitaliy/reports/4/wrk2_get.txt | 107 + .../emelyanovvitaliy/reports/4/wrk2_put.png | Bin 0 -> 101378 bytes .../emelyanovvitaliy/reports/4/wrk2_put.txt | 112 + 12 files changed, 45368 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/4.md create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/get_alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/get_cpu.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/get_lock.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/put_alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/put_cpu.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/put_lock.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/wrk2_get.png create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/wrk2_get.txt create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/wrk2_put.png create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/wrk2_put.txt diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java index cf69257d6..ffa3d993d 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java @@ -198,9 +198,9 @@ private boolean isAckFromCorrect(int ack, int from) { private int getFirstMediatorIndex(String key) { int maxHash = Integer.MIN_VALUE; int choosen = 0; + int keyHash = Math.abs(Hash.murmur3(key)); for (int i = 0; i < mediatorsHashes.length; i++) { // cantor pairing function works nicely only with non-negatives - int keyHash = Math.abs(Hash.murmur3(key)); int totalHash = (mediatorsHashes[i] + keyHash) * (mediatorsHashes[i] + keyHash + 1) / 2 + keyHash; if (totalHash > maxHash) { maxHash = totalHash; diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/4.md b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/4.md new file mode 100644 index 000000000..a96805561 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/4.md @@ -0,0 +1,60 @@ +# 4 этап, отчет, Емельянов Виталий +## Запись +Профилирование проводилось под нагрузкой +20'000 RPS (точка разладки ~22'000 RPS) +скриптом, аналогичным использовавшемуся ранее +(т.е. необходимо собрать кворум 2/3). + +[Логи wrk2](wrk2_put.txt) + +![](wrk2_put.png) +Виден качественный переход после ~95.6%, выяснить его природу не удалось. + +### ЦПУ +[Профиль](put_cpu.html) + +По ЦПУ наиболее ощутим резкий рост работы, связанной с HttpClient, ожидаемый, +так как теперь требуется 1-2 Http запроса на одну запись вместо старых 0-1. +В процессе анализа профилей было замечено, что хэш ключа вычисляется для каждой ноды, +хотя это и не необходимо. Это было исправлено, но это составляет очень малую долю работы, +из-за чего сильного падения замечено не было. + +### Аллокации +[Профиль](put_alloc.html) +Видимых изменений нет + +### Локи +[Профиль](put_lock.html) +Видимых изменений нет + + +## Чтение + +Профилирование на чтение производилось под нагрузкой 50'000 RPS +(точка разладки ~55'000 RPS) скриптом, аналогичным использовавшемуся ранее +(т.е. необходимо собрать кворум 2/3). + +[Логи wrk2](wrk2_get.txt) +![](wrk2_get.png) + +### ЦПУ +[Профиль](get_cpu.html) + +Судить о росте работы HttpClient, поскольку она составляет очень малую +долю по сравнению с работой Dao (в каждой ноде SSTable'ов на 1.4 ГиБ) + +### Аллокации +[Профиль](get_alloc.html) + +Заметен рост аллокаций на TimestampedEntry (ранее - BaseEntry) +с 0.13% сэмплов до ~1%. + +### Локи +[Профиль](get_lock.html) + +При том, что сами блокировки полностью идентичны, +распределение времени между ними теперь качественно другое. +Если ранее блокировки на selector потоках составляли лишь 30% сэмплов, то +теперь - все 95%. Вероятнее всего, это связано с тем, что в этот +раз профилирование проходит ближе к точке разладки, из-за чего +ThreadPoolExecutor'ы теперь блокируют работу куда заметнее \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/get_alloc.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/get_alloc.html new file mode 100644 index 000000000..e6657db0b --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/get_alloc.html @@ -0,0 +1,3158 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/get_cpu.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/get_cpu.html new file mode 100644 index 000000000..2ebc7ff74 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/get_cpu.html @@ -0,0 +1,15279 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/get_lock.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/get_lock.html new file mode 100644 index 000000000..47dc7c139 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/get_lock.html @@ -0,0 +1,2115 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/put_alloc.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/put_alloc.html new file mode 100644 index 000000000..bfee3ce2d --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/put_alloc.html @@ -0,0 +1,6018 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/put_cpu.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/put_cpu.html new file mode 100644 index 000000000..0926a98a2 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/put_cpu.html @@ -0,0 +1,15250 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/put_lock.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/put_lock.html new file mode 100644 index 000000000..5416c43b6 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/put_lock.html @@ -0,0 +1,3268 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/wrk2_get.png b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/wrk2_get.png new file mode 100644 index 0000000000000000000000000000000000000000..18c91289b439adf8cb9aa06c189e56cd1f85e6ab GIT binary patch literal 98925 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 Zy0HQmrP13rO;^GHPN-_Cq$!(U{Xa=FR^k8v literal 0 HcmV?d00001 diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/wrk2_get.txt b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/wrk2_get.txt new file mode 100644 index 000000000..c6e5d9355 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/wrk2_get.txt @@ -0,0 +1,107 @@ +Running 20s test @ http://localhost:8080 + 8 threads and 1024 connections + Thread calibration: mean lat.: 18.161ms, rate sampling interval: 84ms + Thread calibration: mean lat.: 21.714ms, rate sampling interval: 94ms + Thread calibration: mean lat.: 21.111ms, rate sampling interval: 81ms + Thread calibration: mean lat.: 17.584ms, rate sampling interval: 64ms + Thread calibration: mean lat.: 18.997ms, rate sampling interval: 76ms + Thread calibration: mean lat.: 25.712ms, rate sampling interval: 123ms + Thread calibration: mean lat.: 240.876ms, rate sampling interval: 1851ms + Thread calibration: mean lat.: 160.935ms, rate sampling interval: 1470ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 9.34ms 3.95ms 36.54ms 68.72% + Req/Sec 82.41 164.00 1.21k 88.25% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 9.22ms + 75.000% 11.81ms + 90.000% 14.11ms + 99.000% 20.56ms + 99.900% 30.50ms + 99.990% 34.24ms + 99.999% 36.58ms +100.000% 36.58ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.900 0.000000 1 1.00 + 4.383 0.100000 1806 1.11 + 5.691 0.200000 3603 1.25 + 6.947 0.300000 5401 1.43 + 8.147 0.400000 7200 1.67 + 9.215 0.500000 9003 2.00 + 9.711 0.550000 9902 2.22 + 10.215 0.600000 10800 2.50 + 10.711 0.650000 11700 2.86 + 11.247 0.700000 12599 3.33 + 11.807 0.750000 13508 4.00 + 12.087 0.775000 13956 4.44 + 12.391 0.800000 14411 5.00 + 12.727 0.825000 14860 5.71 + 13.111 0.850000 15304 6.67 + 13.559 0.875000 15750 8.00 + 13.823 0.887500 15975 8.89 + 14.111 0.900000 16202 10.00 + 14.455 0.912500 16428 11.43 + 14.815 0.925000 16652 13.33 + 15.303 0.937500 16874 16.00 + 15.591 0.943750 16988 17.78 + 15.871 0.950000 17104 20.00 + 16.231 0.956250 17211 22.86 + 16.639 0.962500 17326 26.67 + 17.103 0.968750 17436 32.00 + 17.471 0.971875 17499 35.56 + 17.775 0.975000 17551 40.00 + 18.047 0.978125 17605 45.71 + 18.575 0.981250 17661 53.33 + 19.151 0.984375 17717 64.00 + 19.423 0.985938 17747 71.11 + 19.647 0.987500 17774 80.00 + 20.159 0.989062 17803 91.43 + 20.767 0.990625 17830 106.67 + 21.391 0.992188 17858 128.00 + 21.743 0.992969 17872 142.22 + 22.271 0.993750 17886 160.00 + 23.103 0.994531 17901 182.86 + 23.743 0.995313 17914 213.33 + 24.351 0.996094 17928 256.00 + 25.087 0.996484 17935 284.44 + 25.631 0.996875 17942 320.00 + 26.287 0.997266 17949 365.71 + 26.879 0.997656 17956 426.67 + 28.111 0.998047 17963 512.00 + 28.543 0.998242 17967 568.89 + 28.991 0.998437 17970 640.00 + 29.551 0.998633 17974 731.43 + 30.031 0.998828 17977 853.33 + 30.607 0.999023 17981 1024.00 + 30.975 0.999121 17983 1137.78 + 30.991 0.999219 17984 1280.00 + 31.935 0.999316 17986 1462.86 + 32.319 0.999414 17988 1706.67 + 32.735 0.999512 17990 2048.00 + 32.991 0.999561 17991 2275.56 + 32.991 0.999609 17991 2560.00 + 33.023 0.999658 17992 2925.71 + 33.855 0.999707 17993 3413.33 + 34.207 0.999756 17995 4096.00 + 34.207 0.999780 17995 4551.11 + 34.207 0.999805 17995 5120.00 + 34.207 0.999829 17995 5851.43 + 34.239 0.999854 17996 6826.67 + 34.239 0.999878 17996 8192.00 + 36.287 0.999890 17997 9102.22 + 36.287 0.999902 17997 10240.00 + 36.287 0.999915 17997 11702.86 + 36.287 0.999927 17997 13653.33 + 36.287 0.999939 17997 16384.00 + 36.575 0.999945 17998 18204.44 + 36.575 1.000000 17998 inf +#[Mean = 9.342, StdDeviation = 3.954] +#[Max = 36.544, Total count = 17998] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 42854 requests in 20.64s, 3.99MB read + Socket errors: connect 11, read 0, write 0, timeout 8697 +Requests/sec: 2076.28 +Transfer/sec: 198.01KB diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/wrk2_put.png b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/4/wrk2_put.png new file mode 100644 index 0000000000000000000000000000000000000000..d753e44775352d51bd5fa86ad1ecfe5f41af8edd GIT binary patch literal 101378 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 Date: Tue, 9 Apr 2024 00:01:18 +0300 Subject: [PATCH 13/19] 5th stage Signed-off-by: vitaliy.emelyanov --- .../test/emelyanovvitaliy/DaoMediator.java | 13 +- .../itmo/test/emelyanovvitaliy/DhtServer.java | 102 ++++++++------ .../emelyanovvitaliy/LocalDaoMediator.java | 35 +++-- .../emelyanovvitaliy/MergeDaoMediator.java | 128 ++++++++++++++---- .../emelyanovvitaliy/RemoteDaoMediator.java | 94 +++++++++---- 5 files changed, 253 insertions(+), 119 deletions(-) diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DaoMediator.java index d037d205b..a363e1761 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DaoMediator.java @@ -1,12 +1,11 @@ package ru.vk.itmo.test.emelyanovvitaliy; import one.nio.http.Request; -import ru.vk.itmo.dao.Entry; import ru.vk.itmo.test.emelyanovvitaliy.dao.TimestampedEntry; import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; public abstract class DaoMediator { public static final long NEVER_TIMESTAMP = Long.MIN_VALUE; @@ -15,21 +14,17 @@ protected static MemorySegment keyFor(String id) { return MemorySegment.ofArray(id.getBytes(StandardCharsets.UTF_8)); } - protected static byte[] valueFor(Entry entry) { - return entry.value().toArray(ValueLayout.JAVA_BYTE); - } - abstract void stop(); abstract boolean isStopped(); - abstract boolean put(Request request); + abstract CompletableFuture put(Request request); // returns null if error, // entry with null value and timestamp if the answer is tombstone // entry with null value and timestamp equals NEVER_TIMESTAMP if no answer // entry if it exists - abstract TimestampedEntry get(Request request); + abstract CompletableFuture> get(Request request); - abstract boolean delete(Request request); + abstract CompletableFuture delete(Request request); } diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java index 8cf073db3..70fc16811 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -18,6 +18,7 @@ import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -28,22 +29,21 @@ public class DhtServer extends HttpServer { public static final String NOT_ENOUGH_REPLICAS_STATUS = "504 Not Enough Replicas"; - public static final String TIMESTAMP_HEADER = "X-Timestamp: "; + public static final String TIMESTAMP_HEADER = "X-Timestamp"; public static final String ID_KEY = "id="; - protected static final int THREADS_PER_PROCESSOR = 2; + protected static final int THREADS_PER_PROCESSOR = 1; protected static final byte[] EMPTY_BODY = new byte[0]; protected static final long KEEP_ALIVE_TIME_MILLIS = 1000; protected static final int REQUEST_TIMEOUT_MILLIS = 1024; protected static final int THREAD_POOL_TERMINATION_TIMEOUT_SECONDS = 600; protected static final int TASK_QUEUE_SIZE = Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR; protected final MergeDaoMediator mergeDaoMediator; - protected final AtomicInteger threadsInPool; protected final ThreadPoolExecutor threadPoolExecutor; public DhtServer(ServiceConfig config) throws IOException { super(createConfig(config)); mergeDaoMediator = new MergeDaoMediator(config.workingDir(), config.selfUrl(), config.clusterUrls()); - threadsInPool = new AtomicInteger(0); + AtomicInteger threadsInPool = new AtomicInteger(0); threadPoolExecutor = new ThreadPoolExecutor( Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR, @@ -65,7 +65,7 @@ public synchronized void stop() { try { threadPoolExecutor.shutdown(); if (!threadPoolExecutor.awaitTermination(THREAD_POOL_TERMINATION_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { - throw new UncheckedTimeoutException("Waited too lot to stop the thread pool"); + threadPoolExecutor.shutdownNow(); } mergeDaoMediator.stop(); } catch (InterruptedException e) { @@ -74,6 +74,7 @@ public synchronized void stop() { } } + @SuppressWarnings("FutureReturnValueIgnored") @RequestMethod(METHOD_GET) @Path("/v0/entity") public void entity(HttpSession session, Request request) throws IOException { @@ -81,26 +82,33 @@ public void entity(HttpSession session, Request request) throws IOException { requestProccessing(id, session, () -> { try { - TimestampedEntry entry = mergeDaoMediator.get(request); - if (entry == null) { - sendNotEnoughReplicas(session); - } else if (entry.timestamp() == DaoMediator.NEVER_TIMESTAMP) { - session.sendResponse(new Response(Response.NOT_FOUND, EMPTY_BODY)); - } else { - Response response; - if (entry.value() == null) { - response = new Response(Response.NOT_FOUND, EMPTY_BODY); - } else { - response = new Response( - Response.OK, - ((Entry) entry).value().toArray(ValueLayout.JAVA_BYTE) - ); - } - response.addHeader(TIMESTAMP_HEADER + entry.timestamp()); - session.sendResponse(response); - } - } catch (IOException e) { - throw new UncheckedIOException(e); + CompletableFuture> future = mergeDaoMediator.get(request); + future.whenComplete( + (entry, _) -> { + try { + if (entry == null) { + sendNotEnoughReplicas(session); + } else if (entry.timestamp() == DaoMediator.NEVER_TIMESTAMP) { + session.sendResponse(new Response(Response.NOT_FOUND, EMPTY_BODY)); + } else { + Response response; + if (entry.value() == null) { + response = new Response(Response.NOT_FOUND, EMPTY_BODY); + } else { + response = new Response( + Response.OK, + ((Entry) entry).value() + .toArray(ValueLayout.JAVA_BYTE) + ); + } + response.addHeader(TIMESTAMP_HEADER + ": " + entry.timestamp()); + session.sendResponse(response); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + ); } catch (IllegalArgumentException e) { sendBadRequestResponseUnchecked(session); } @@ -108,19 +116,26 @@ public void entity(HttpSession session, Request request) throws IOException { ); } + @SuppressWarnings("FutureReturnValueIgnored") @RequestMethod(METHOD_PUT) @Path("/v0/entity") public void putEntity(@Param(value = "id") String id, HttpSession httpSession, Request request) throws IOException { requestProccessing(id, httpSession, () -> { try { - if (mergeDaoMediator.put(request)) { - httpSession.sendResponse(new Response(Response.CREATED, EMPTY_BODY)); - } else { - sendNotEnoughReplicas(httpSession); - } - } catch (IOException e) { - throw new UncheckedIOException(e); + mergeDaoMediator.put(request).whenComplete( + (success, _) -> { + try { + if (success) { + httpSession.sendResponse(new Response(Response.CREATED, EMPTY_BODY)); + } else { + sendNotEnoughReplicas(httpSession); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + ); } catch (IllegalArgumentException e) { sendBadRequestResponseUnchecked(httpSession); } @@ -128,19 +143,26 @@ public void putEntity(@Param(value = "id") String id, HttpSession httpSession, R ); } + @SuppressWarnings("FutureReturnValueIgnored") @RequestMethod(METHOD_DELETE) @Path("/v0/entity") public void deleteEntity(@Param("id") String id, HttpSession httpSession, Request request) throws IOException { requestProccessing(id, httpSession, () -> { try { - if (mergeDaoMediator.delete(request)) { - httpSession.sendResponse(new Response(Response.ACCEPTED, EMPTY_BODY)); - } else { - sendNotEnoughReplicas(httpSession); - } - } catch (IOException e) { - throw new UncheckedIOException(e); + mergeDaoMediator.delete(request).whenComplete( + (success, _) -> { + try { + if (success) { + httpSession.sendResponse(new Response(Response.ACCEPTED, EMPTY_BODY)); + } else { + sendNotEnoughReplicas(httpSession); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + ); } catch (IllegalArgumentException e) { sendBadRequestResponseUnchecked(httpSession); } @@ -205,7 +227,7 @@ private static HttpServerConfig createConfig(ServiceConfig serviceConfig) { acceptorConfig.port = serviceConfig.selfPort(); acceptorConfig.reusePort = true; config.acceptors = new AcceptorConfig[] {acceptorConfig}; - config.closeSessions = true; + config.keepAlive = 1000; return config; } } diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java index 5d8edbcdc..ce10347ac 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java @@ -7,14 +7,25 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.lang.foreign.MemorySegment; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; public class LocalDaoMediator extends DaoMediator { + protected final Executor executor; protected final ReferenceDao dao; protected final AtomicBoolean isClosed = new AtomicBoolean(false); LocalDaoMediator(ReferenceDao dao) { this.dao = dao; + this.executor = Executors.newSingleThreadExecutor(); // stub for testing, don't use in production + } + + LocalDaoMediator(ReferenceDao dao, Executor executor) { + this.dao = dao; + this.executor = executor; } @Override @@ -35,26 +46,28 @@ boolean isStopped() { } @Override - boolean put(Request request) { + CompletableFuture put(Request request) { + // upsert is a really fast operation, so as I think we can do it on main thread String id = request.getParameter(DhtServer.ID_KEY); dao.upsert(new TimestampedEntry<>(keyFor(id), MemorySegment.ofArray(request.getBody()))); - return true; + return CompletableFuture.completedFuture(true); } @Override - TimestampedEntry get(Request request) { - MemorySegment id = keyFor(request.getParameter(DhtServer.ID_KEY)); - TimestampedEntry entry = dao.get(id); - if (entry == null) { - return new TimestampedEntry<>(id, null, NEVER_TIMESTAMP); - } - return entry; + CompletableFuture> get(Request request) { + CompletableFuture> ans = new CompletableFuture<>(); + executor.execute(() -> { + MemorySegment id = keyFor(request.getParameter(DhtServer.ID_KEY)); + TimestampedEntry entry = dao.get(id); + ans.complete(Objects.requireNonNullElseGet(entry, () -> new TimestampedEntry<>(id, null, NEVER_TIMESTAMP))); + }); + return ans; } @Override - boolean delete(Request request) { + CompletableFuture delete(Request request) { String id = request.getParameter(DhtServer.ID_KEY); dao.upsert(new TimestampedEntry<>(keyFor(id), null)); - return true; + return CompletableFuture.completedFuture(true); } } diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java index ffa3d993d..b3bc19956 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java @@ -1,8 +1,6 @@ package ru.vk.itmo.test.emelyanovvitaliy; -import one.nio.http.HttpClient; import one.nio.http.Request; -import one.nio.net.ConnectionString; import one.nio.util.Hash; import ru.vk.itmo.dao.Config; import ru.vk.itmo.test.emelyanovvitaliy.dao.ReferenceDao; @@ -10,34 +8,63 @@ import java.io.IOException; import java.lang.foreign.MemorySegment; +import java.net.http.HttpClient; import java.nio.file.Path; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; public class MergeDaoMediator extends DaoMediator { - public static final String FINAL_EXECUTION_HEADER = "X-Final-Execution: "; + public static final String FINAL_EXECUTION_HEADER = "X-Final-Execution"; public static final String ACK_KEY = "ack="; public static final String FROM_KEY = "from="; - protected static final String TRUE_STRING = "true"; - protected static final int FLUSH_THRESHOLD_BYTES = 1 << 24; // 16 MiB public static final String ID_KEY = "id="; - protected final AtomicBoolean isStopped = new AtomicBoolean(false); + protected static final int DAO_MEDIATOR_THREADS = 4*Runtime.getRuntime().availableProcessors(); + protected static final Duration HTTP_TIMEOUT = Duration.ofMillis(100); + protected static final int KEEP_ALIVE_TIME_MS = 1000; + protected static final String HEADER_TRUE_STRING = ": true"; + protected static final int FLUSH_THRESHOLD_BYTES = 1 << 20; // 1 MiB + public static final int DAO_THREADS_CAPACITY = 1024; + protected final Executor daoExecutor; + protected final AtomicBoolean isStopped; protected final DaoMediator[] daoMediators; protected final int[] mediatorsHashes; private final LocalDaoMediator localDaoMediator; MergeDaoMediator(Path localDir, String thisUrl, List urls) throws IOException { + isStopped = new AtomicBoolean(false); + AtomicInteger threadCount = new AtomicInteger(); + daoExecutor = new ThreadPoolExecutor( + DAO_MEDIATOR_THREADS, + DAO_MEDIATOR_THREADS, + KEEP_ALIVE_TIME_MS, + TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(DAO_THREADS_CAPACITY), + r -> { + Thread thread = new Thread(r); + thread.setName("Dao-Executor-" + threadCount.getAndIncrement()); + return thread; + } + ); localDaoMediator = new LocalDaoMediator( new ReferenceDao( new Config( localDir, FLUSH_THRESHOLD_BYTES ) - ) + ), + daoExecutor ); - daoMediators = getDaoMediators(urls, thisUrl, localDaoMediator); + daoMediators = getDaoMediators(urls, thisUrl, localDaoMediator, daoExecutor); mediatorsHashes = getMediatorsHashes(urls); } @@ -57,18 +84,18 @@ boolean isStopped() { } @Override - boolean put(Request request) throws IllegalArgumentException { + CompletableFuture put(Request request) throws IllegalArgumentException { return simpleReplicate(request, false); } @Override - TimestampedEntry get(Request request) throws IllegalArgumentException { - if (Objects.equals(request.getHeader(FINAL_EXECUTION_HEADER), TRUE_STRING)) { + @SuppressWarnings("FutureReturnValueIgnored") + CompletableFuture> get(Request request) throws IllegalArgumentException { + if (Objects.equals(request.getHeader(FINAL_EXECUTION_HEADER), HEADER_TRUE_STRING)) { return localDaoMediator.get(request); } else { int ack; int from; - int answered = 0; try { ack = getAck(request); from = getFrom(request); @@ -79,37 +106,65 @@ TimestampedEntry get(Request request) throws IllegalArgumentExcep throw new IllegalArgumentException("Wrong ack/from: " + ack + "/" + from); } String id = request.getParameter(ID_KEY); - request.addHeader(FINAL_EXECUTION_HEADER + TRUE_STRING); + request.addHeader(FINAL_EXECUTION_HEADER + HEADER_TRUE_STRING); int currentMediatorIndex = getFirstMediatorIndex(id); - TimestampedEntry lastEntry = null; + AtomicReference> lastEntry = new AtomicReference<>(); + AtomicInteger foundSmth = new AtomicInteger(0); + AtomicInteger notFound = new AtomicInteger(0); + CompletableFuture> ans = new CompletableFuture<>(); for (int i = 0; i < from; i++) { - TimestampedEntry entry = + CompletableFuture> future = daoMediators[currentMediatorIndex].get(request); - lastEntry = findLastEntry(lastEntry, entry); - if (entry != null) { - answered += 1; - } + future.whenComplete( + (entry, _) -> { + if (entry == null) { + if (notFound.incrementAndGet() == from - ack + 1) { + ans.complete(null); + } + } else { + TimestampedEntry current; + TimestampedEntry lastOf2; + do { + current = lastEntry.get(); + lastOf2 = findLastEntry(current, entry); + if (lastOf2 == current) { // if ours variant is older + break; + } + // try again if lastEntry updated + } while (lastEntry.compareAndExchange(current, lastOf2) != current); + if (foundSmth.incrementAndGet() == ack) { + ans.complete(lastEntry.get()); + } + } + } + ); currentMediatorIndex = (currentMediatorIndex + 1) % daoMediators.length; } - return answered >= ack ? lastEntry : null; + return ans; } } @Override - boolean delete(Request request) throws IllegalArgumentException { + CompletableFuture delete(Request request) throws IllegalArgumentException { return simpleReplicate(request, true); } - private static DaoMediator[] getDaoMediators(List urls, String thisUrl, LocalDaoMediator localDaoMediator) { + private static DaoMediator[] getDaoMediators(List urls, String thisUrl, LocalDaoMediator localDaoMediator, + Executor executor) { DaoMediator[] mediators = new DaoMediator[urls.size()]; int cnt = 0; + HttpClient httpClient = HttpClient.newBuilder() + .executor(executor) + .connectTimeout(HTTP_TIMEOUT) + .version(HttpClient.Version.HTTP_1_1) + .build(); List tmpList = new ArrayList<>(urls); tmpList.sort(String::compareTo); for (String url : tmpList) { if (url.equals(thisUrl)) { mediators[cnt] = localDaoMediator; } else { - mediators[cnt] = new RemoteDaoMediator(new HttpClient(new ConnectionString(url))); + mediators[cnt] = new RemoteDaoMediator(httpClient, url, HTTP_TIMEOUT); } cnt++; } @@ -144,9 +199,10 @@ private static TimestampedEntry findLastEntry( return b; } - private boolean simpleReplicate(Request request, boolean delete) + @SuppressWarnings("FutureReturnValueIgnored") + private CompletableFuture simpleReplicate(Request request, boolean delete) throws IllegalArgumentException { - if (Objects.equals(request.getHeader(FINAL_EXECUTION_HEADER), TRUE_STRING)) { + if (Objects.equals(request.getHeader(FINAL_EXECUTION_HEADER), HEADER_TRUE_STRING)) { if (delete) { return localDaoMediator.delete(request); } else { @@ -155,7 +211,6 @@ private boolean simpleReplicate(Request request, boolean delete) } int ack; int from; - int answered = 0; try { ack = getAck(request); from = getFrom(request); @@ -165,20 +220,33 @@ private boolean simpleReplicate(Request request, boolean delete) if (!isAckFromCorrect(ack, from)) { throw new IllegalArgumentException("Wrong ack/from: " + ack + "/" + from); } - request.addHeader(FINAL_EXECUTION_HEADER + TRUE_STRING); + request.addHeader(FINAL_EXECUTION_HEADER + HEADER_TRUE_STRING); String id = request.getParameter(ID_KEY); int currentMediatorIndex = getFirstMediatorIndex(id); + AtomicInteger answeredOk = new AtomicInteger(0); + AtomicInteger answeredNotOk = new AtomicInteger(0); + CompletableFuture ans = new CompletableFuture<>(); for (int i = 0; i < from; i++) { - boolean res; + CompletableFuture res; if (delete) { res = daoMediators[currentMediatorIndex].delete(request); } else { res = daoMediators[currentMediatorIndex].put(request); } - answered += res ? 1 : 0; + res.thenAccept(success -> { + if (success) { + if (answeredOk.incrementAndGet() == ack) { + ans.complete(true); + } + } else { + if (answeredNotOk.incrementAndGet() == from - ack + 1) { + ans.complete(false); + } + } + }); currentMediatorIndex = (currentMediatorIndex + 1) % daoMediators.length; } - return answered >= ack; + return ans; } private int getAck(Request request) throws NumberFormatException { diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java index c2559433f..2909c8245 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java @@ -1,29 +1,35 @@ package ru.vk.itmo.test.emelyanovvitaliy; -import one.nio.http.HttpClient; -import one.nio.http.HttpException; import one.nio.http.Request; -import one.nio.http.Response; -import one.nio.pool.PoolException; import ru.vk.itmo.test.emelyanovvitaliy.dao.TimestampedEntry; 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.time.Duration; +import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; -import static ru.vk.itmo.test.emelyanovvitaliy.DhtServer.TIMESTAMP_HEADER; +import static ru.vk.itmo.test.emelyanovvitaliy.MergeDaoMediator.FINAL_EXECUTION_HEADER; public class RemoteDaoMediator extends DaoMediator { - protected static final int DEFAULT_TIMEOUT = 100; + protected static final Duration DEFAULT_TIMEOUT = Duration.ofMillis(100); + public static final String TIMESTAMP_HEADER = "X-Timestamp"; protected final HttpClient client; - protected final int timeout; + protected final String url; + protected final Duration timeout; - RemoteDaoMediator(HttpClient httpClient) { - this(httpClient, DEFAULT_TIMEOUT); + RemoteDaoMediator(HttpClient httpClient, String url) { + this(httpClient, url, DEFAULT_TIMEOUT); } - RemoteDaoMediator(HttpClient httpClient, int timeout) { + RemoteDaoMediator(HttpClient httpClient, String url, Duration timeout) { this.client = httpClient; this.timeout = timeout; + this.url = url; } protected boolean isStatusCodeCorrect(int statusCode) { @@ -32,58 +38,74 @@ protected boolean isStatusCodeCorrect(int statusCode) { @Override void stop() { - if (!client.isClosed()) { + if (!client.isTerminated()) { client.close(); } } @Override boolean isStopped() { - return client.isClosed(); + return client.isTerminated(); } @Override - protected boolean put(Request request) { + protected CompletableFuture put(Request request) { return simpleForward(request); } - protected boolean simpleForward(Request request) { + protected CompletableFuture simpleForward(Request request) { try { - Response response = client.invoke(request); - return isStatusCodeCorrect(response.getStatus()); + return invoke(request).handle((response, ex) -> + { + if (response == null) { + return false; + } + return isStatusCodeCorrect(response.statusCode()); + } + ); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new UncheckedInterruptedException(e); - } catch (PoolException | IOException | HttpException e) { - return false; + } catch (IOException e) { + return CompletableFuture.completedFuture(false); } } @Override - TimestampedEntry get(Request request) { - Response response = null; + CompletableFuture> get(Request request) { try { - response = client.invoke(request, timeout); + return invoke(request).handle( + (response, ex) -> { + if (response == null) { + return null; + } + return getTimestampedEntry(request, response); + } + ); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new UncheckedInterruptedException(e); - } catch (PoolException | IOException | HttpException e) { + } catch (IOException e) { return null; } - String timestampHeader = response.getHeader(TIMESTAMP_HEADER); + + } + + private TimestampedEntry getTimestampedEntry(Request request, HttpResponse response) { + OptionalLong timestampHeader = response.headers().firstValueAsLong(TIMESTAMP_HEADER); long timestamp; - if (timestampHeader == null) { + if (timestampHeader.isEmpty()) { timestamp = NEVER_TIMESTAMP; } else { - timestamp = Long.parseLong(timestampHeader); + timestamp = timestampHeader.getAsLong(); } - if (isStatusCodeCorrect(response.getStatus()) && timestampHeader != null) { + if (isStatusCodeCorrect(response.statusCode()) && timestampHeader.isPresent()) { return new TimestampedEntry<>( keyFor(request.getParameter(DhtServer.ID_KEY)), - MemorySegment.ofArray(response.getBody()), + MemorySegment.ofArray(response.body()), timestamp ); - } else if (response.getStatus() == 404) { + } else if (response.statusCode() == 404) { return new TimestampedEntry<>( keyFor(request.getParameter(DhtServer.ID_KEY)), null, @@ -95,7 +117,21 @@ TimestampedEntry get(Request request) { } @Override - boolean delete(Request request) { + CompletableFuture delete(Request request) { return simpleForward(request); } + + private CompletableFuture> invoke(Request request) throws IOException, InterruptedException { + HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(url + request.getURI())) + .method( + request.getMethodName(), + request.getBody() == null + ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofByteArray(request.getBody()) + ) + .setHeader(FINAL_EXECUTION_HEADER, "true") + .timeout(timeout) + .build(); + return client.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofByteArray()); + } } From f21cad517e5fdbac389cccd3e5f55a78fbae43c7 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Tue, 9 Apr 2024 00:54:22 +0300 Subject: [PATCH 14/19] 5th stage - fix reused connection to old server Signed-off-by: vitaliy.emelyanov --- src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java index 70fc16811..c2c25acd2 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -228,6 +228,7 @@ private static HttpServerConfig createConfig(ServiceConfig serviceConfig) { acceptorConfig.reusePort = true; config.acceptors = new AcceptorConfig[] {acceptorConfig}; config.keepAlive = 1000; + config.closeSessions = true; return config; } } From 7d6d6f13b3a28e808012fcb50512a6211ae29b62 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Tue, 9 Apr 2024 01:18:05 +0300 Subject: [PATCH 15/19] 5th stage - stylefix Signed-off-by: vitaliy.emelyanov --- .../test/emelyanovvitaliy/BoolMerger.java | 38 ++++++ .../itmo/test/emelyanovvitaliy/DhtServer.java | 6 +- .../test/emelyanovvitaliy/EntryMerger.java | 68 ++++++++++ .../emelyanovvitaliy/MergeDaoMediator.java | 116 +++--------------- .../vk/itmo/test/emelyanovvitaliy/Merger.java | 11 ++ .../emelyanovvitaliy/RemoteDaoMediator.java | 3 +- 6 files changed, 138 insertions(+), 104 deletions(-) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/BoolMerger.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/EntryMerger.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/Merger.java diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/BoolMerger.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/BoolMerger.java new file mode 100644 index 000000000..9e9f37dbc --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/BoolMerger.java @@ -0,0 +1,38 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; + +public class BoolMerger implements Merger { + private final int from; + private final int ack; + private final AtomicInteger answeredOk; + private final AtomicInteger answeredNotOk; + private final CompletableFuture futureToComplete; + + public BoolMerger(int from, int ack) { + this.from = from; + this.ack = ack; + answeredOk = new AtomicInteger(0); + answeredNotOk = new AtomicInteger(0); + futureToComplete = new CompletableFuture<>(); + } + + @Override + public void acceptResult(Boolean success, Throwable throwable) { + if (success) { + if (answeredOk.incrementAndGet() == ack) { + futureToComplete.complete(true); + } + } else { + if (answeredNotOk.incrementAndGet() == from - ack + 1) { + futureToComplete.complete(false); + } + } + } + + @Override + public CompletableFuture getCompletableFuture() { + return futureToComplete; + } +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java index c2c25acd2..f76bdc6e3 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -84,7 +84,7 @@ public void entity(HttpSession session, Request request) throws IOException { try { CompletableFuture> future = mergeDaoMediator.get(request); future.whenComplete( - (entry, _) -> { + (entry, ex) -> { try { if (entry == null) { sendNotEnoughReplicas(session); @@ -124,7 +124,7 @@ public void putEntity(@Param(value = "id") String id, HttpSession httpSession, R () -> { try { mergeDaoMediator.put(request).whenComplete( - (success, _) -> { + (success, ex) -> { try { if (success) { httpSession.sendResponse(new Response(Response.CREATED, EMPTY_BODY)); @@ -151,7 +151,7 @@ public void deleteEntity(@Param("id") String id, HttpSession httpSession, Reques () -> { try { mergeDaoMediator.delete(request).whenComplete( - (success, _) -> { + (success, ex) -> { try { if (success) { httpSession.sendResponse(new Response(Response.ACCEPTED, EMPTY_BODY)); diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/EntryMerger.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/EntryMerger.java new file mode 100644 index 000000000..746c38aba --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/EntryMerger.java @@ -0,0 +1,68 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import ru.vk.itmo.test.emelyanovvitaliy.dao.TimestampedEntry; + +import java.lang.foreign.MemorySegment; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +public class EntryMerger implements Merger> { + private final int from; + private final int ack; + private final AtomicReference> lastEntry; + private final AtomicInteger foundSmthCnt; + private final AtomicInteger notFoundCnt; + private final CompletableFuture> futureToComplete; + + public EntryMerger(int from, int ack) { + this.from = from; + this.ack = ack; + lastEntry = new AtomicReference<>(); + foundSmthCnt = new AtomicInteger(0); + notFoundCnt = new AtomicInteger(0); + futureToComplete = new CompletableFuture<>(); + } + + private static TimestampedEntry findLastEntry( + TimestampedEntry a, + TimestampedEntry b + ) { + if (a == null) { + return b; + } + if (b == null) { + return a; + } + return a.timestamp() > b.timestamp() ? a : b; + } + + @Override + public void acceptResult(TimestampedEntry entry, Throwable throwable) { + if (entry == null) { + if (notFoundCnt.incrementAndGet() == from - ack + 1) { + futureToComplete.complete(null); + } + } else { + TimestampedEntry current; + TimestampedEntry lastOf2; + do { + current = lastEntry.get(); + lastOf2 = findLastEntry(current, entry); + if (Objects.equals(lastOf2, current)) { // if ours variant is older + break; + } + // try again if lastEntry updated + } while (lastEntry.compareAndExchange(current, lastOf2) != current); + if (foundSmthCnt.incrementAndGet() == ack) { + futureToComplete.complete(lastEntry.get()); + } + } + } + + @Override + public CompletableFuture> getCompletableFuture() { + return futureToComplete; + } +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java index b3bc19956..9642e3fe4 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java @@ -21,14 +21,13 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; public class MergeDaoMediator extends DaoMediator { public static final String FINAL_EXECUTION_HEADER = "X-Final-Execution"; public static final String ACK_KEY = "ack="; public static final String FROM_KEY = "from="; public static final String ID_KEY = "id="; - protected static final int DAO_MEDIATOR_THREADS = 4*Runtime.getRuntime().availableProcessors(); + protected static final int DAO_MEDIATOR_THREADS = 4 * Runtime.getRuntime().availableProcessors(); protected static final Duration HTTP_TIMEOUT = Duration.ofMillis(100); protected static final int KEEP_ALIVE_TIME_MS = 1000; protected static final String HEADER_TRUE_STRING = ": true"; @@ -56,12 +55,7 @@ public class MergeDaoMediator extends DaoMediator { } ); localDaoMediator = new LocalDaoMediator( - new ReferenceDao( - new Config( - localDir, - FLUSH_THRESHOLD_BYTES - ) - ), + new ReferenceDao(new Config(localDir, FLUSH_THRESHOLD_BYTES)), daoExecutor ); daoMediators = getDaoMediators(urls, thisUrl, localDaoMediator, daoExecutor); @@ -96,51 +90,20 @@ CompletableFuture> get(Request request) throws I } else { int ack; int from; - try { - ack = getAck(request); - from = getFrom(request); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(e); - } + ack = getAck(request); + from = getFrom(request); if (!isAckFromCorrect(ack, from)) { throw new IllegalArgumentException("Wrong ack/from: " + ack + "/" + from); } String id = request.getParameter(ID_KEY); request.addHeader(FINAL_EXECUTION_HEADER + HEADER_TRUE_STRING); int currentMediatorIndex = getFirstMediatorIndex(id); - AtomicReference> lastEntry = new AtomicReference<>(); - AtomicInteger foundSmth = new AtomicInteger(0); - AtomicInteger notFound = new AtomicInteger(0); - CompletableFuture> ans = new CompletableFuture<>(); + EntryMerger entryMerger = new EntryMerger(from, ack); for (int i = 0; i < from; i++) { - CompletableFuture> future = - daoMediators[currentMediatorIndex].get(request); - future.whenComplete( - (entry, _) -> { - if (entry == null) { - if (notFound.incrementAndGet() == from - ack + 1) { - ans.complete(null); - } - } else { - TimestampedEntry current; - TimestampedEntry lastOf2; - do { - current = lastEntry.get(); - lastOf2 = findLastEntry(current, entry); - if (lastOf2 == current) { // if ours variant is older - break; - } - // try again if lastEntry updated - } while (lastEntry.compareAndExchange(current, lastOf2) != current); - if (foundSmth.incrementAndGet() == ack) { - ans.complete(lastEntry.get()); - } - } - } - ); + daoMediators[currentMediatorIndex].get(request).whenComplete(entryMerger::acceptResult); currentMediatorIndex = (currentMediatorIndex + 1) % daoMediators.length; } - return ans; + return entryMerger.getCompletableFuture(); } } @@ -161,11 +124,8 @@ private static DaoMediator[] getDaoMediators(List urls, String thisUrl, List tmpList = new ArrayList<>(urls); tmpList.sort(String::compareTo); for (String url : tmpList) { - if (url.equals(thisUrl)) { - mediators[cnt] = localDaoMediator; - } else { - mediators[cnt] = new RemoteDaoMediator(httpClient, url, HTTP_TIMEOUT); - } + mediators[cnt] = url.equals(thisUrl) ? localDaoMediator : + new RemoteDaoMediator(httpClient, url, HTTP_TIMEOUT); cnt++; } return mediators; @@ -183,70 +143,30 @@ private static int[] getMediatorsHashes(List urls) { return hashes; } - private static TimestampedEntry findLastEntry( - TimestampedEntry a, - TimestampedEntry b - ) { - if (a == null) { - return b; - } - if (b == null) { - return a; - } - if (a.timestamp() > b.timestamp()) { - return a; - } - return b; - } - @SuppressWarnings("FutureReturnValueIgnored") private CompletableFuture simpleReplicate(Request request, boolean delete) throws IllegalArgumentException { if (Objects.equals(request.getHeader(FINAL_EXECUTION_HEADER), HEADER_TRUE_STRING)) { - if (delete) { - return localDaoMediator.delete(request); - } else { - return localDaoMediator.put(request); - } + return delete ? localDaoMediator.delete(request) : localDaoMediator.put(request); } int ack; int from; - try { - ack = getAck(request); - from = getFrom(request); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(e); - } + ack = getAck(request); + from = getFrom(request); if (!isAckFromCorrect(ack, from)) { throw new IllegalArgumentException("Wrong ack/from: " + ack + "/" + from); } request.addHeader(FINAL_EXECUTION_HEADER + HEADER_TRUE_STRING); String id = request.getParameter(ID_KEY); int currentMediatorIndex = getFirstMediatorIndex(id); - AtomicInteger answeredOk = new AtomicInteger(0); - AtomicInteger answeredNotOk = new AtomicInteger(0); - CompletableFuture ans = new CompletableFuture<>(); + BoolMerger boolMerger = new BoolMerger(from, ack); for (int i = 0; i < from; i++) { - CompletableFuture res; - if (delete) { - res = daoMediators[currentMediatorIndex].delete(request); - } else { - res = daoMediators[currentMediatorIndex].put(request); - } - res.thenAccept(success -> { - if (success) { - if (answeredOk.incrementAndGet() == ack) { - ans.complete(true); - } - } else { - if (answeredNotOk.incrementAndGet() == from - ack + 1) { - ans.complete(false); - } - } - }); + CompletableFuture res = delete ? daoMediators[currentMediatorIndex].delete(request) + : daoMediators[currentMediatorIndex].put(request); + res.whenComplete(boolMerger::acceptResult); currentMediatorIndex = (currentMediatorIndex + 1) % daoMediators.length; } - return ans; + return boolMerger.getCompletableFuture(); } private int getAck(Request request) throws NumberFormatException { @@ -268,7 +188,6 @@ private int getFirstMediatorIndex(String key) { int choosen = 0; int keyHash = Math.abs(Hash.murmur3(key)); for (int i = 0; i < mediatorsHashes.length; i++) { - // cantor pairing function works nicely only with non-negatives int totalHash = (mediatorsHashes[i] + keyHash) * (mediatorsHashes[i] + keyHash + 1) / 2 + keyHash; if (totalHash > maxHash) { maxHash = totalHash; @@ -277,5 +196,4 @@ private int getFirstMediatorIndex(String key) { } return choosen; } - } diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/Merger.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/Merger.java new file mode 100644 index 000000000..f000319f5 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/Merger.java @@ -0,0 +1,11 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import java.util.concurrent.CompletableFuture; + +public interface Merger { + + void acceptResult(T result, Throwable exception); + + CompletableFuture getCompletableFuture(); + +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java index 2909c8245..dfd8c16dc 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java @@ -55,8 +55,7 @@ protected CompletableFuture put(Request request) { protected CompletableFuture simpleForward(Request request) { try { - return invoke(request).handle((response, ex) -> - { + return invoke(request).handle((response, ex) -> { if (response == null) { return false; } From 621996cde23b51f9f0259b7218b398fe81361820 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Thu, 18 Apr 2024 00:39:39 +0300 Subject: [PATCH 16/19] 5th stage - report Signed-off-by: vitaliy.emelyanov --- .../itmo/test/emelyanovvitaliy/DhtServer.java | 8 +- .../emelyanovvitaliy/MergeDaoMediator.java | 24 +- .../emelyanovvitaliy/RemoteDaoMediator.java | 2 +- .../itmo/test/emelyanovvitaliy/reports/5/5.md | 88 + .../emelyanovvitaliy/reports/5/get_alloc.html | 5489 +++ .../emelyanovvitaliy/reports/5/get_cpu.html | 10657 ++++++ .../emelyanovvitaliy/reports/5/get_lock.html | 17903 ++++++++++ .../emelyanovvitaliy/reports/5/put_alloc.html | 21863 ++++++++++++ .../emelyanovvitaliy/reports/5/put_cpu.html | 29227 ++++++++++++++++ .../emelyanovvitaliy/reports/5/put_lock.html | 7177 ++++ .../emelyanovvitaliy/reports/5/wrk_get.png | Bin 0 -> 154361 bytes .../emelyanovvitaliy/reports/5/wrk_get.txt | 110 + .../emelyanovvitaliy/reports/5/wrk_put.png | Bin 0 -> 151799 bytes .../emelyanovvitaliy/reports/5/wrk_put.txt | 116 + 14 files changed, 92652 insertions(+), 12 deletions(-) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/5.md create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/get_alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/get_cpu.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/get_lock.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/put_alloc.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/put_cpu.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/put_lock.html create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_get.png create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_get.txt create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_put.png create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_put.txt diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java index f76bdc6e3..dad419363 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -83,7 +83,7 @@ public void entity(HttpSession session, Request request) throws IOException { () -> { try { CompletableFuture> future = mergeDaoMediator.get(request); - future.whenComplete( + future.whenCompleteAsync( (entry, ex) -> { try { if (entry == null) { @@ -107,7 +107,7 @@ public void entity(HttpSession session, Request request) throws IOException { } catch (IOException e) { throw new UncheckedIOException(e); } - } + }, threadPoolExecutor ); } catch (IllegalArgumentException e) { sendBadRequestResponseUnchecked(session); @@ -123,7 +123,7 @@ public void putEntity(@Param(value = "id") String id, HttpSession httpSession, R requestProccessing(id, httpSession, () -> { try { - mergeDaoMediator.put(request).whenComplete( + mergeDaoMediator.put(request).whenCompleteAsync( (success, ex) -> { try { if (success) { @@ -134,7 +134,7 @@ public void putEntity(@Param(value = "id") String id, HttpSession httpSession, R } catch (IOException e) { throw new UncheckedIOException(e); } - } + }, threadPoolExecutor ); } catch (IllegalArgumentException e) { sendBadRequestResponseUnchecked(httpSession); diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java index 9642e3fe4..f510dd2d0 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java @@ -52,6 +52,9 @@ public class MergeDaoMediator extends DaoMediator { Thread thread = new Thread(r); thread.setName("Dao-Executor-" + threadCount.getAndIncrement()); return thread; + }, + (_, _) -> { + // ignore, made to not close HttpClient } ); localDaoMediator = new LocalDaoMediator( @@ -100,7 +103,10 @@ CompletableFuture> get(Request request) throws I int currentMediatorIndex = getFirstMediatorIndex(id); EntryMerger entryMerger = new EntryMerger(from, ack); for (int i = 0; i < from; i++) { - daoMediators[currentMediatorIndex].get(request).whenComplete(entryMerger::acceptResult); + daoMediators[currentMediatorIndex].get(request).whenCompleteAsync( + entryMerger::acceptResult, + daoExecutor + ); currentMediatorIndex = (currentMediatorIndex + 1) % daoMediators.length; } return entryMerger.getCompletableFuture(); @@ -116,14 +122,18 @@ private static DaoMediator[] getDaoMediators(List urls, String thisUrl, Executor executor) { DaoMediator[] mediators = new DaoMediator[urls.size()]; int cnt = 0; - HttpClient httpClient = HttpClient.newBuilder() - .executor(executor) - .connectTimeout(HTTP_TIMEOUT) - .version(HttpClient.Version.HTTP_1_1) - .build(); List tmpList = new ArrayList<>(urls); tmpList.sort(String::compareTo); for (String url : tmpList) { + // create own client for each remote mediator + // made to have more independent SelectorManager threads + // hypothesis that didn't make things worse, + // so I decided to leave it here (。◕‿‿◕。) + HttpClient httpClient = HttpClient.newBuilder() + .executor(executor) + .connectTimeout(HTTP_TIMEOUT) + .version(HttpClient.Version.HTTP_1_1) + .build(); mediators[cnt] = url.equals(thisUrl) ? localDaoMediator : new RemoteDaoMediator(httpClient, url, HTTP_TIMEOUT); cnt++; @@ -163,7 +173,7 @@ private CompletableFuture simpleReplicate(Request request, boolean dele for (int i = 0; i < from; i++) { CompletableFuture res = delete ? daoMediators[currentMediatorIndex].delete(request) : daoMediators[currentMediatorIndex].put(request); - res.whenComplete(boolMerger::acceptResult); + res.whenCompleteAsync(boolMerger::acceptResult, daoExecutor); currentMediatorIndex = (currentMediatorIndex + 1) % daoMediators.length; } return boolMerger.getCompletableFuture(); diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java index dfd8c16dc..b096576b6 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/RemoteDaoMediator.java @@ -16,7 +16,7 @@ import static ru.vk.itmo.test.emelyanovvitaliy.MergeDaoMediator.FINAL_EXECUTION_HEADER; public class RemoteDaoMediator extends DaoMediator { - protected static final Duration DEFAULT_TIMEOUT = Duration.ofMillis(100); + protected static final Duration DEFAULT_TIMEOUT = Duration.ofMillis(250); public static final String TIMESTAMP_HEADER = "X-Timestamp"; protected final HttpClient client; protected final String url; diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/5.md b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/5.md new file mode 100644 index 000000000..f814ee7c2 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/5.md @@ -0,0 +1,88 @@ +# Емельянов Виталий, 5 этап + +## Запись + +### Совершенные в процессе исследования работы: +* все нагрузки, которые по каким-то причинам уходили на ForkJoinPool, +были вынесены в предназначенные для нее executor'ы +* исправлена проблема с закрытием HttpClient в случае RejectedExecutionException +* для каждой удаленной ноды создается свой клиент (предположение, влияния на эффективность не оказало, +но потоки SelectorManager подразгрузились). + +Тестирование при работе на запись производилось под нагрузкой +в 32'000 RPS (точка разладки ~35'000 RPS) + +[Логи wrk2 на запись](wrk_put.txt) + +![](wrk_put.png) + +Перелом примерно на 98%, скорее всего, +связан с работой gc, которая, судя по логам программы, +и блокирует потоки на время до 3 мс. + +### Профили ЦПУ + +[ЦПУ на запись](put_cpu.html) + +На профилях можно заметить ForkJoinPool, +который явно в коде не создавался. +При этом этот пул не совершает полезной работы, +только задачи по его собственному поддержанию, +задачи на него самого не приходят. Могу лишь предположить, что на +него возлагается задача по перекладыванию задач в ThreadPoolExecutor +в методах CompletableFuture, для которых указывался executor. + + +### Профили аллокации +[Аллокации на запись](put_alloc.html) + +92% аллокаций происходят в http библиотеке jdk + + +### Профили блокировок +[Блокировки на запись](put_lock.html) + +Большинство блокировок происходят в очереди executor'а и внутри http клиента +(в основном при взаимодействии с пулом соединений). + +## Чтение + +Тестирование при работе на чтение производилось +под нагрузкой в 800 RPS (точка разладки ~1'000) RPS. + +Такой резкий сдвиг точки разладки связан с тем, что +был сильно сокращен размер SSTable (c 16 МиБ до 1 МиБ), +из-за чего теперь все те же 1.4 ГиБ это ~10'000 SSTable. +Объем SSTable был сокращен, поскольку java HttpClient +более емкий по аллокациям, из-за чего 128 МиБ уже становилось +тесновато, из-за чего было решено сократить объем MemTable + +При этом данный предел четко виден, например, в htop. +Нагрузка на ЦПУ распределяется с большой линейностью, +где 100% - 1'000 RPS. + +[Логи wrk2 на чтение](wrk_get.txt) + +![](wrk_get.png) + +Виден (хоть и не так явно) все тот же сдвиг на ~99.8% + +### Профили ЦПУ +[ЦПУ на чтение](get_cpu.html) + +95% сэмплов уходит на взаимодействие с SSTables. +В данных обстоятельствах может иметь смысл +выделить взаимодействие с локальной Dao в отдельный +executor, чтобы конкурировать за ресурсы ЦПУ средствами +ОСи. + +### Профили аллокации +[Аллокации на чтение](get_alloc.html) + +90% TLAB'ов было выделено на нужды http библиотеки. + +### Профили блокировок +[Блокировки на чтение](get_lock.html) + +Выводы аналогичны таковым для профиля блокировок на запись. + diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/get_alloc.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/get_alloc.html new file mode 100644 index 000000000..aef107d2c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/get_alloc.html @@ -0,0 +1,5489 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/get_cpu.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/get_cpu.html new file mode 100644 index 000000000..556e295a6 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/get_cpu.html @@ -0,0 +1,10657 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/get_lock.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/get_lock.html new file mode 100644 index 000000000..c7dc205d7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/get_lock.html @@ -0,0 +1,17903 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/put_alloc.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/put_alloc.html new file mode 100644 index 000000000..6762541b8 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/put_alloc.html @@ -0,0 +1,21863 @@ + + + + + + + +

Allocation profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/put_cpu.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/put_cpu.html new file mode 100644 index 000000000..1db5d459e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/put_cpu.html @@ -0,0 +1,29227 @@ + + + + + + + +

CPU profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/put_lock.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/put_lock.html new file mode 100644 index 000000000..53973fa8f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/put_lock.html @@ -0,0 +1,7177 @@ + + + + + + + +

Lock profile

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_get.png b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_get.png new file mode 100644 index 0000000000000000000000000000000000000000..87c15cbfe2b63a00e243724124041254a389ec34 GIT binary patch literal 154361 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%0DZ~Q}v<><}#Fg22kEh=tWp7tk_{g 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 J?jzs*KLMR8g!TXc literal 0 HcmV?d00001 diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_get.txt b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_get.txt new file mode 100644 index 000000000..44429d946 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_get.txt @@ -0,0 +1,110 @@ +Running 40s test @ http://localhost:8080 + 8 threads and 128 connections + Thread calibration: mean lat.: 24.295ms, rate sampling interval: 91ms + Thread calibration: mean lat.: 24.267ms, rate sampling interval: 88ms + Thread calibration: mean lat.: 24.830ms, rate sampling interval: 87ms + Thread calibration: mean lat.: 24.450ms, rate sampling interval: 83ms + Thread calibration: mean lat.: 26.780ms, rate sampling interval: 95ms + Thread calibration: mean lat.: 19.382ms, rate sampling interval: 66ms + Thread calibration: mean lat.: 26.833ms, rate sampling interval: 93ms + Thread calibration: mean lat.: 25.792ms, rate sampling interval: 91ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 21.01ms 11.99ms 61.76ms 65.13% + Req/Sec 100.06 47.93 246.00 82.84% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 19.26ms + 75.000% 29.69ms + 90.000% 39.17ms + 99.000% 48.16ms + 99.900% 54.01ms + 99.990% 57.18ms + 99.999% 61.79ms +100.000% 61.79ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.378 0.000000 1 1.00 + 7.455 0.100000 2395 1.11 + 9.759 0.200000 4791 1.25 + 12.231 0.300000 7186 1.43 + 15.671 0.400000 9577 1.67 + 19.263 0.500000 11969 2.00 + 20.799 0.550000 13173 2.22 + 22.863 0.600000 14369 2.50 + 24.815 0.650000 15560 2.86 + 27.167 0.700000 16763 3.33 + 29.695 0.750000 17954 4.00 + 30.991 0.775000 18560 4.44 + 32.015 0.800000 19150 5.00 + 34.175 0.825000 19756 5.71 + 35.583 0.850000 20358 6.67 + 37.823 0.875000 20944 8.00 + 38.463 0.887500 21248 8.89 + 39.167 0.900000 21548 10.00 + 39.775 0.912500 21847 11.43 + 40.383 0.925000 22142 13.33 + 42.015 0.937500 22450 16.00 + 42.399 0.943750 22597 17.78 + 42.911 0.950000 22748 20.00 + 43.551 0.956250 22894 22.86 + 44.159 0.962500 23039 26.67 + 45.215 0.968750 23192 32.00 + 45.535 0.971875 23265 35.56 + 45.951 0.975000 23341 40.00 + 46.463 0.978125 23415 45.71 + 46.751 0.981250 23490 53.33 + 47.167 0.984375 23567 64.00 + 47.327 0.985938 23607 71.11 + 47.551 0.987500 23641 80.00 + 47.935 0.989062 23677 91.43 + 48.287 0.990625 23716 106.67 + 48.927 0.992188 23749 128.00 + 49.535 0.992969 23768 142.22 + 49.887 0.993750 23788 160.00 + 50.111 0.994531 23806 182.86 + 50.527 0.995313 23825 213.33 + 50.783 0.996094 23844 256.00 + 50.847 0.996484 23852 284.44 + 50.943 0.996875 23862 320.00 + 51.103 0.997266 23871 365.71 + 51.199 0.997656 23881 426.67 + 51.519 0.998047 23890 512.00 + 51.775 0.998242 23894 568.89 + 52.031 0.998437 23899 640.00 + 52.223 0.998633 23904 731.43 + 52.959 0.998828 23909 853.33 + 54.303 0.999023 23913 1024.00 + 55.359 0.999121 23915 1137.78 + 55.615 0.999219 23918 1280.00 + 55.647 0.999316 23920 1462.86 + 55.679 0.999414 23922 1706.67 + 55.711 0.999512 23925 2048.00 + 55.743 0.999561 23926 2275.56 + 55.775 0.999609 23928 2560.00 + 55.775 0.999658 23928 2925.71 + 55.807 0.999707 23930 3413.33 + 55.871 0.999756 23931 4096.00 + 55.871 0.999780 23931 4551.11 + 56.255 0.999805 23932 5120.00 + 56.255 0.999829 23932 5851.43 + 56.287 0.999854 23933 6826.67 + 57.183 0.999878 23934 8192.00 + 57.183 0.999890 23934 9102.22 + 57.183 0.999902 23934 10240.00 + 57.183 0.999915 23934 11702.86 + 59.871 0.999927 23935 13653.33 + 59.871 0.999939 23935 16384.00 + 59.871 0.999945 23935 18204.44 + 59.871 0.999951 23935 20480.00 + 59.871 0.999957 23935 23405.71 + 61.791 0.999963 23936 27306.67 + 61.791 1.000000 23936 inf +#[Mean = 21.006, StdDeviation = 11.987] +#[Max = 61.760, Total count = 23936] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 31912 requests in 40.05s, 2.32MB read + Non-2xx or 3xx or 404 responses: 1818 +Requests/sec: 796.85 +Transfer/sec: 59.36KB diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_put.png b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_put.png new file mode 100644 index 0000000000000000000000000000000000000000..1baf57f1410b8a50f2d3ce0bc0eef1111f047193 GIT binary patch literal 151799 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%0DfZ@vIm>Csn8^z>UF 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 YObPSViD^YG*8w#l{p(_KgY071HRJOBUy literal 0 HcmV?d00001 diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_put.txt b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_put.txt new file mode 100644 index 000000000..ffb305a96 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/5/wrk_put.txt @@ -0,0 +1,116 @@ +Running 30s test @ http://192.168.0.193:8080 + 1 threads and 64 connections + Thread calibration: mean lat.: 125.116ms, rate sampling interval: 1057ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.21ms 453.97us 8.70ms 82.58% + Req/Sec 7.35k 436.02 7.51k 88.89% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.13ms + 75.000% 1.37ms + 90.000% 1.70ms + 99.000% 2.88ms + 99.900% 5.15ms + 99.990% 7.80ms + 99.999% 8.69ms +100.000% 8.71ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.278 0.000000 1 1.00 + 0.802 0.100000 14418 1.11 + 0.887 0.200000 28911 1.25 + 0.981 0.300000 43130 1.43 + 1.062 0.400000 57528 1.67 + 1.127 0.500000 71896 2.00 + 1.159 0.550000 79147 2.22 + 1.196 0.600000 86226 2.50 + 1.242 0.650000 93490 2.86 + 1.298 0.700000 100618 3.33 + 1.372 0.750000 107858 4.00 + 1.419 0.775000 111424 4.44 + 1.480 0.800000 114992 5.00 + 1.544 0.825000 118571 5.71 + 1.598 0.850000 122190 6.67 + 1.646 0.875000 125815 8.00 + 1.671 0.887500 127591 8.89 + 1.696 0.900000 129353 10.00 + 1.723 0.912500 131135 11.43 + 1.755 0.925000 132962 13.33 + 1.798 0.937500 134721 16.00 + 1.826 0.943750 135633 17.78 + 1.854 0.950000 136529 20.00 + 1.879 0.956250 137446 22.86 + 1.904 0.962500 138337 26.67 + 1.942 0.968750 139216 32.00 + 1.975 0.971875 139668 35.56 + 2.036 0.975000 140111 40.00 + 2.149 0.978125 140563 45.71 + 2.339 0.981250 141014 53.33 + 2.539 0.984375 141455 64.00 + 2.621 0.985938 141683 71.11 + 2.707 0.987500 141906 80.00 + 2.807 0.989062 142136 91.43 + 2.953 0.990625 142353 106.67 + 3.177 0.992188 142579 128.00 + 3.311 0.992969 142692 142.22 + 3.437 0.993750 142802 160.00 + 3.587 0.994531 142915 182.86 + 3.741 0.995313 143027 213.33 + 3.909 0.996094 143139 256.00 + 4.007 0.996484 143196 284.44 + 4.099 0.996875 143252 320.00 + 4.235 0.997266 143308 365.71 + 4.343 0.997656 143364 426.67 + 4.551 0.998047 143420 512.00 + 4.643 0.998242 143448 568.89 + 4.763 0.998437 143476 640.00 + 4.875 0.998633 143504 731.43 + 4.995 0.998828 143532 853.33 + 5.171 0.999023 143560 1024.00 + 5.287 0.999121 143574 1137.78 + 5.471 0.999219 143588 1280.00 + 5.595 0.999316 143602 1462.86 + 5.975 0.999414 143616 1706.67 + 6.163 0.999512 143630 2048.00 + 6.379 0.999561 143637 2275.56 + 6.627 0.999609 143644 2560.00 + 6.727 0.999658 143652 2925.71 + 6.887 0.999707 143658 3413.33 + 7.051 0.999756 143665 4096.00 + 7.247 0.999780 143669 4551.11 + 7.315 0.999805 143672 5120.00 + 7.387 0.999829 143676 5851.43 + 7.523 0.999854 143679 6826.67 + 7.707 0.999878 143683 8192.00 + 7.799 0.999890 143686 9102.22 + 7.799 0.999902 143686 10240.00 + 7.827 0.999915 143688 11702.86 + 7.939 0.999927 143690 13653.33 + 8.019 0.999939 143692 16384.00 + 8.135 0.999945 143693 18204.44 + 8.135 0.999951 143693 20480.00 + 8.207 0.999957 143694 23405.71 + 8.279 0.999963 143695 27306.67 + 8.375 0.999969 143696 32768.00 + 8.495 0.999973 143697 36408.89 + 8.495 0.999976 143697 40960.00 + 8.495 0.999979 143697 46811.43 + 8.623 0.999982 143698 54613.33 + 8.623 0.999985 143698 65536.00 + 8.687 0.999986 143699 72817.78 + 8.687 0.999988 143699 81920.00 + 8.687 0.999989 143699 93622.86 + 8.687 0.999991 143699 109226.67 + 8.687 0.999992 143699 131072.00 + 8.711 0.999993 143700 145635.56 + 8.711 1.000000 143700 inf +#[Mean = 1.211, StdDeviation = 0.454] +#[Max = 8.704, Total count = 143700] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 231977 requests in 30.00s, 14.82MB read + Socket errors: connect 0, read 0, write 0, timeout 651 + Non-2xx or 3xx or 404 responses: 74 +Requests/sec: 7732.37 +Transfer/sec: 505.91KB From 022c5767421f3326d54cdf17325f4f1fc219e4c3 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Thu, 18 Apr 2024 00:47:51 +0300 Subject: [PATCH 17/19] 5th stage - report stylefix Signed-off-by: vitaliy.emelyanov --- .../java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java index f510dd2d0..3ac6bb1d4 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java @@ -53,7 +53,7 @@ public class MergeDaoMediator extends DaoMediator { thread.setName("Dao-Executor-" + threadCount.getAndIncrement()); return thread; }, - (_, _) -> { + (r, t) -> { // ignore, made to not close HttpClient } ); From 804e0be4a19b7f294ab2dae5baebee18ec43f348 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Wed, 24 Apr 2024 23:49:26 +0300 Subject: [PATCH 18/19] 6th stage Signed-off-by: vitaliy.emelyanov --- .../test/emelyanovvitaliy/ByteQueueItem.java | 20 ++++++++ .../itmo/test/emelyanovvitaliy/DhtServer.java | 35 +++++++++++++ .../test/emelyanovvitaliy/EmptyChunk.java | 12 +++++ .../emelyanovvitaliy/HttpChunkedEntry.java | 50 +++++++++++++++++++ .../HttpChunkedHeaderQueueItem.java | 18 +++++++ .../emelyanovvitaliy/LocalDaoMediator.java | 16 ++++++ .../emelyanovvitaliy/MergeDaoMediator.java | 5 ++ .../test/emelyanovvitaliy/dao/SSTable.java | 2 +- 8 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/ByteQueueItem.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/EmptyChunk.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/HttpChunkedEntry.java create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/HttpChunkedHeaderQueueItem.java diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/ByteQueueItem.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/ByteQueueItem.java new file mode 100644 index 000000000..6d70f6ce8 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/ByteQueueItem.java @@ -0,0 +1,20 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import one.nio.net.Session; +import one.nio.net.Socket; + +import java.io.IOException; + +public class ByteQueueItem extends Session.QueueItem { + private byte[] bytes; + public ByteQueueItem(byte[] bytes) { + this.bytes = bytes; + } + + + @Override + public int write(Socket socket) throws IOException { + socket.write(bytes, 0, bytes.length); + return bytes.length; + } +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java index dad419363..464e50e91 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -22,6 +22,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; import static one.nio.http.Request.METHOD_DELETE; import static one.nio.http.Request.METHOD_GET; @@ -31,12 +32,15 @@ public class DhtServer extends HttpServer { public static final String NOT_ENOUGH_REPLICAS_STATUS = "504 Not Enough Replicas"; public static final String TIMESTAMP_HEADER = "X-Timestamp"; public static final String ID_KEY = "id="; + public static final String START_KEY = "start="; + public static final String END_KEY = "end="; protected static final int THREADS_PER_PROCESSOR = 1; protected static final byte[] EMPTY_BODY = new byte[0]; protected static final long KEEP_ALIVE_TIME_MILLIS = 1000; protected static final int REQUEST_TIMEOUT_MILLIS = 1024; protected static final int THREAD_POOL_TERMINATION_TIMEOUT_SECONDS = 600; protected static final int TASK_QUEUE_SIZE = Runtime.getRuntime().availableProcessors() * THREADS_PER_PROCESSOR; + private static final Logger LOGGER = Logger.getLogger(DhtServer.class.getName()); protected final MergeDaoMediator mergeDaoMediator; protected final ThreadPoolExecutor threadPoolExecutor; @@ -116,6 +120,37 @@ public void entity(HttpSession session, Request request) throws IOException { ); } + @SuppressWarnings("FutureReturnValueIgnored") + @RequestMethod(METHOD_GET) + @Path("/v0/entities") + public void entities(@Param(value="start") String start, HttpSession httpSession, Request request) + throws IOException { + requestProccessing(start, httpSession, + () -> mergeDaoMediator.getRange(request).whenCompleteAsync( + (result, ex) -> { + if (ex == null) { + try { + if (result != null) { + httpSession.write(new HttpChunkedHeaderQueueItem()); + while (result.hasNext()) { + httpSession.write(new HttpChunkedEntry(result.next())); + } + httpSession.write(new EmptyChunk()); + httpSession.scheduleClose(); + } else { + httpSession.sendResponse(new Response(Response.INTERNAL_ERROR, EMPTY_BODY)); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } else { + LOGGER.warning(ex.toString()); + } + }, threadPoolExecutor + ) + ); + } + @SuppressWarnings("FutureReturnValueIgnored") @RequestMethod(METHOD_PUT) @Path("/v0/entity") diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/EmptyChunk.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/EmptyChunk.java new file mode 100644 index 000000000..b2b5c75c4 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/EmptyChunk.java @@ -0,0 +1,12 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import java.nio.charset.StandardCharsets; + +public class EmptyChunk extends ByteQueueItem { + private static final byte[] HTTP_CHUNKED_HEADER = "0\r\n\r\n".getBytes(StandardCharsets.UTF_8); + + public EmptyChunk() { + super(HTTP_CHUNKED_HEADER); + } +} + diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/HttpChunkedEntry.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/HttpChunkedEntry.java new file mode 100644 index 000000000..9aef6decf --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/HttpChunkedEntry.java @@ -0,0 +1,50 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import one.nio.net.Session; +import one.nio.net.Socket; +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 class HttpChunkedEntry extends Session.QueueItem { + private static final byte[] SEPARATOR = "\n".getBytes(StandardCharsets.UTF_8); + Entry entry; + + public HttpChunkedEntry(Entry entry) { + this.entry = entry; + } + + + @Override + public int write(Socket socket) throws IOException { + byte[] key = getBytes(entry.key()); + byte[] value = getBytes(entry.value()); + String length = Long.toHexString(key.length + value.length + SEPARATOR.length); + int written = 0; + written += writeWithBreak(socket, length.getBytes(StandardCharsets.UTF_8)); + written += writeWithBreak(socket, key); + written += writeWithBreak(socket, value); + return written; + } + + private static int br(Socket socket) throws IOException { + socket.write(SEPARATOR, 0, SEPARATOR.length); + return SEPARATOR.length; + } + + private static int write(Socket socket, byte[] bytes) throws IOException { + socket.write(bytes, 0, bytes.length); + return bytes.length; + } + + private static int writeWithBreak(Socket socket, byte[] bytes) throws IOException { + return write(socket, bytes) + br(socket); + } + + private static byte[] getBytes(MemorySegment segment) { + return segment.toArray(ValueLayout.JAVA_BYTE); + } +} diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/HttpChunkedHeaderQueueItem.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/HttpChunkedHeaderQueueItem.java new file mode 100644 index 000000000..d1b77bd64 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/HttpChunkedHeaderQueueItem.java @@ -0,0 +1,18 @@ +package ru.vk.itmo.test.emelyanovvitaliy; + +import java.nio.charset.StandardCharsets; + +public class HttpChunkedHeaderQueueItem extends ByteQueueItem { + private static final byte[] HTTP_CHUNKED_HEADER = """ + HTTP/1.1 200 OK + Content-Type: text/plain + Transfer-Encoding: chunked + Connection: close + + """.getBytes(StandardCharsets.UTF_8); + + public HttpChunkedHeaderQueueItem() { + super(HTTP_CHUNKED_HEADER); + } +} + diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java index ce10347ac..4fb1eac70 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/LocalDaoMediator.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.lang.foreign.MemorySegment; +import java.util.Iterator; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -64,6 +65,21 @@ CompletableFuture> get(Request request) { return ans; } + CompletableFuture>> getRange(Request request) { + CompletableFuture>> ans = new CompletableFuture<>(); + executor.execute(() -> { + MemorySegment start = keyFor(request.getParameter(DhtServer.START_KEY)); + String endKey = request.getParameter(DhtServer.END_KEY); + MemorySegment end = null; + if (endKey != null) { + end = keyFor(endKey); + } + Iterator> entries = dao.get(start, end); + ans.complete(entries); + }); + return ans; + } + @Override CompletableFuture delete(Request request) { String id = request.getParameter(DhtServer.ID_KEY); diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java index 3ac6bb1d4..38a5f8a2d 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/MergeDaoMediator.java @@ -12,6 +12,7 @@ import java.nio.file.Path; import java.time.Duration; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.concurrent.ArrayBlockingQueue; @@ -113,6 +114,10 @@ CompletableFuture> get(Request request) throws I } } + public CompletableFuture>> getRange(Request request) { + return localDaoMediator.getRange(request); + } + @Override CompletableFuture delete(Request request) throws IllegalArgumentException { return simpleReplicate(request, true); diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java index 9102bf2d4..9e6299fd2 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/dao/SSTable.java @@ -211,7 +211,7 @@ public TimestampedEntry next() { final MemorySegment value = data.asSlice(offset, valueLength); offset += valueLength; entry += 1; - return new TimestampedEntry<>(key, value, getTime(entry)); + return new TimestampedEntry<>(key, value, getTime(entry >= 0 ? entry : -entry - 1)); } } } From 493e151861767fd8858778608865dcd267881da9 Mon Sep 17 00:00:00 2001 From: "vitaliy.emelyanov" Date: Thu, 25 Apr 2024 02:48:27 +0300 Subject: [PATCH 19/19] 6th stage report and fixes Signed-off-by: vitaliy.emelyanov --- .../emelyanovvitaliy/HttpChunkedEntry.java | 33 +++++---- .../itmo/test/emelyanovvitaliy/reports/6/6.md | 67 +++++++++++++++++++ 2 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/6/6.md diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/HttpChunkedEntry.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/HttpChunkedEntry.java index 67201c321..88d3dc82a 100644 --- a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/HttpChunkedEntry.java +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/HttpChunkedEntry.java @@ -21,26 +21,23 @@ public HttpChunkedEntry(Entry entry) { public int write(Socket socket) throws IOException { byte[] key = getBytes(entry.key()); byte[] value = getBytes(entry.value()); - String length = Long.toHexString(key.length + value.length + SEPARATOR.length); - int written = 0; - written += writeWithBreak(socket, length.getBytes(StandardCharsets.UTF_8)); - written += writeWithBreak(socket, key); - written += writeWithBreak(socket, value); - return written; + byte[] length = Long.toHexString(key.length + value.length + SEPARATOR.length) + .getBytes(StandardCharsets.UTF_8); + int offset = 0; + byte[] output = new byte[length.length + key.length + value.length + 3 * SEPARATOR.length]; + offset += copy(length, output, offset); + offset += copy(SEPARATOR, output, offset); + offset += copy(key, output, offset); + offset += copy(SEPARATOR, output, offset); + offset += copy(value, output, offset); + offset += copy(SEPARATOR, output, offset); + socket.write(output, 0, output.length); + return offset; } - private static int br(Socket socket) throws IOException { - socket.write(SEPARATOR, 0, SEPARATOR.length); - return SEPARATOR.length; - } - - private static int write(Socket socket, byte[] bytes) throws IOException { - socket.write(bytes, 0, bytes.length); - return bytes.length; - } - - private static int writeWithBreak(Socket socket, byte[] bytes) throws IOException { - return write(socket, bytes) + br(socket); + private static int copy(byte[] from, byte[] to, int destOffset) { + System.arraycopy(from, 0, to, destOffset, from.length); + return from.length; } private static byte[] getBytes(MemorySegment segment) { diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/6/6.md b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/6/6.md new file mode 100644 index 000000000..c97f69e13 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/6/6.md @@ -0,0 +1,67 @@ +# 6 стадия, Емельянов Виталий + +Профилирование проводилось на +данных объемом 1.4 ГиБ, ~10'000 SSTable. + +Полезная нагрузка (объем данных, считанных curl'ом) - +408 МиБ. + +## ЦПУ + +[Профиль ЦПУ](range_cpu.html) + +Из наших тредов (т.е. без gc и без компилятора) +можно выделить 4 основных сотавляющих - отправка полезной нагрузки (43%), +отправка знака переноса (43%), поиск следующего вхождения в итераторе (7.5%), +исходное позиционирование (0.95%). + +Четко видно, что перенос строки, несмотря на то, что данных в нем намного меньше, +чем в полезной нагрузке, занимает схожее значение сэмплов, поэтому попробуем +отправлять их разом. + +[Профиль ЦПУ после модификации](range_cpu_new.html) + +Здесь и далее на профилях могут работать несколько потоков. Так происходит, потому +что curl запускался автоматически в цикле и время профилирования было больше, нежели время работы curl. + +Скорость загрузки (по curl) выросла в 4-5 раз (до 20 МиБ/c на первый запуск и 25 МиБ/c на последующие). + +Теперь на работу с сокетом ушло 63% сэмплов, на работу компаратора 25%, на выделение нового MemorySegment +из файлового - 6%. + +Далее мы будем рассматривать только модифицированную версию, потому что изначальная была глупостью, +вызванной надеждой на то, что алгоритм Нейгла или что-нибудь аналогичное сработает на цепочке работы с сетвым стеком. +Это было наивно. + +## Аллокации + +[Профиль аллокаций](range_alloc_new.html) + +Здесь мне очень не понравилось, что мы сначала копируем из MemorySegment в массив, +из которого перекладываем в другой массив. В HttpChunkedEntry ввел такую функцию. + +```java +private static int copy(MemorySegment from, byte[] to, int destOffset) { + for (int i = 0; i < from.byteSize(); i++) { + to[i + destOffset] = from.getAtIndex(ValueLayout.JAVA_BYTE, i); + } + return (int) from.byteSize(); +} +``` + +Ей пришлось пользоваться, поскольку MemorySegment не имеет метода для копирования +в существующий массив. Но это в свою очередь ударило по ЦПУ, что видно про [профилю](range_cpu_new_2.html), +где данный метод использует ~7.5% сэмплов. Это приводит не к прям трагичному падению (~0.5 МиБ/с по curl), но неприятно, +поэтому в репозитории было решено оставить изначальное решение с таким двойным копированием. + +С другой стороны, это сильно сокращает потребление по аллокациям у HttpChunkedEntry.write (60% -> 40%). + +[Профиль по аллокациям с применением этой функции](range_alloc_new_2.html) + + +Оставшиеся аллокации происходят в dao и на создание самого объекта HttpChunkedEntry. + +## Локи + +[Локов нету](range_lock_new.html) +