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..9c08294b4 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtServer.java @@ -0,0 +1,111 @@ +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 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]; + 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 Response entity(@Param(value = "id") String id) { + if (isKeyIncorrect(id)) { + return new Response(Response.BAD_REQUEST, EMPTY_BODY); + } + Entry entry = dao.get(keyFor(id)); + if (entry == null) { + return new Response(Response.NOT_FOUND, EMPTY_BODY); + } + return new Response(Response.OK, valueFor(entry)); + } + + @RequestMethod(METHOD_PUT) + @Path("/v0/entity") + public Response putEntity(@Param(value = "id") String id, Request request) { + if (isKeyIncorrect(id)) { + return new Response(Response.BAD_REQUEST, EMPTY_BODY); + } + dao.upsert(new BaseEntry<>(keyFor(id), MemorySegment.ofArray(request.getBody()))); + return new Response(Response.CREATED, EMPTY_BODY); + } + + @RequestMethod(METHOD_DELETE) + @Path("/v0/entity") + public Response deleteEntity(@Param("id") String id) { + if (isKeyIncorrect(id)) { + return new Response(Response.BAD_REQUEST, EMPTY_BODY); + } + dao.upsert(new BaseEntry<>(keyFor(id), null)); + return new Response(Response.ACCEPTED, EMPTY_BODY); + } + + @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(); + 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/DhtService.java b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtService.java new file mode 100644 index 000000000..77c85a304 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/DhtService.java @@ -0,0 +1,39 @@ +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 { + server.stop(); + server = null; + return CompletableFuture.completedFuture(null); + } + + @ServiceFactory(stage = 1) + 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/reports/1/alloc_read.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/alloc_read.html new file mode 100644 index 000000000..f9e93e527 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/alloc_read.html @@ -0,0 +1,420 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

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

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/cpu_read.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/cpu_read.html new file mode 100644 index 000000000..179246b1e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/cpu_read.html @@ -0,0 +1,1304 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/cpu_write.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/cpu_write.html new file mode 100644 index 000000000..7b4805bf0 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/cpu_write.html @@ -0,0 +1,2132 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/lock_read.html b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/lock_read.html new file mode 100644 index 000000000..24b7ef49f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/lock_read.html @@ -0,0 +1,260 @@ + + + + + + + +

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

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

Flame Graph

+
  
+
Produced by async-profiler
+ +
+

+

Matched:

+ diff --git a/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/report.md b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/report.md new file mode 100644 index 000000000..f3f62975e --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/emelyanovvitaliy/reports/1/report.md @@ -0,0 +1,100 @@ +# Отчет по модулю 1 + +## Наполнение +Для наполнения был использован следующий скрипт на lua + +```lua + +counter = 6000000 + +body = function() + ans = '' + for i = 1, 100, 1 + do + ans = ans .. math.random(1, 100) + end + return ans +end + +request = function() + path = "/v0/entity?id=" .. counter + counter = counter + 1 + wrk.body = body() + return wrk.format("PUT", path) +end +``` + +Таким образом было записано ~27'000'000 ключей, +а общий занимаемый объем составил примерно 5.8 GiB по 327 SSTable + +## Профилирование на чтение + +Проведем профилирование системы при 1'500 RPS (точка разладки ~2000 RPS) +в течение 240 сек при работе на чтение + +Нагрузка производится при помощи следующего скрипта + +```lua +request = function() +path = "/v0/entity?id=" .. math.random(0, 50000000) +return wrk.format("GET", path) +end +``` + +Диапазон выбран так, чтобы обеспечивать примерно равномерное соотношение +существующими и несуществующими ключами. + +[ЦПУ на чтение](cpu_read.html) + +Нетрудно заметить, что подавляющее +большинство времени уходит на поиск ключа +по всем SSTable, что решается применением bloom фильтра, а так +же агрессивным автоматическим compact'ом + +[Аллокации на чтение](alloc_read.html) + +Все аллокации выполняются исключительно для возврата/получения значения +для представления пользователю/интерфейсу DAO, промежуточных избыточных аллокаций не наблюдается + +[Блокировки на чтение](lock_read.html) + +Блокировок нет + +## Профилирование на запись + +Профилирование на запись проводилось при 12'000 RPS (точка разладки примерно +15'000 RPS) в течение 240 сек + +Нагрузка производилась при помощи следующего скрипта + +```lua +body = function() + ans = '' + for i = 1, 100, 1 + do + ans = ans .. math.random(1, 100) + end + return ans +end + +request = function() + path = "/v0/entity?id=" .. math.random(0, 50000000) + wrk.body = body() + return wrk.format("PUT", path) +end +``` + +[ЦПУ на запись](cpu_write.html) + +Основное время уходит на операции сетевого ввода-вывода. +Его можно сократить, используя другие протоколы, которые будут +обеспечивать меньшие служебные нагрузки + +[Аллокации на запись](alloc_write.html) + +Нагрузки по аллокациям аналогичны тем, что проявляются при загрузках на чтение + +[Блокировки на запись](lock_write.html) + +При записи срабатывают лишь блокировка +при изменении набора таблиц (flush / compact) \ No newline at end of file