Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Кислов Данил ИТМО ФИТиП HW6 #203

Closed
wants to merge 68 commits into from
Closed
Show file tree
Hide file tree
Changes from 67 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
539a511
Stage 1
Feb 18, 2024
d1b578c
linter fixes
Feb 18, 2024
358cb52
linter fixes
Feb 18, 2024
027bb2e
Правки в отчёте после ревью
Feb 25, 2024
d4290ec
Merge branch 'main' into Stage1
HKTRp Feb 25, 2024
86ef1b8
Async handling implementation
Feb 25, 2024
ede656b
Async handling implementation
Feb 28, 2024
9526d2f
Merge branch 'main' into Stage2
HKTRp Feb 28, 2024
509c89d
Remove new strings formatting
Feb 28, 2024
b36c845
Remove new strings formatting
Feb 28, 2024
efb1235
linter
Feb 28, 2024
fdad055
linter
Feb 28, 2024
90040e7
linter
Feb 28, 2024
37d8300
linter
Feb 29, 2024
100ef07
linter
Feb 29, 2024
7690126
Review fixes
Mar 6, 2024
eb160dd
Merge branch 'main' into Stage2
HKTRp Mar 6, 2024
f1f9441
Histogram
Mar 6, 2024
13e06b4
codeclimate
Mar 6, 2024
88a9bf1
Merge branch 'main' into Stage2
HKTRp Mar 8, 2024
87071b6
Stage 3 Randevouz hashing sharder implementation
Mar 9, 2024
e529b8d
linter fix
Mar 9, 2024
7da9134
Стэк вместо очереди
Mar 13, 2024
57ef90b
Дополнение отчёта
Mar 13, 2024
088f7d8
Stage title fix
Mar 13, 2024
f4d9204
Merge branch 'Stage2' into Stage3
Mar 18, 2024
1169831
Add timeout + report
Mar 18, 2024
b6e87d8
Add timeout + report
Mar 19, 2024
b24abec
Merge branch 'main' into Stage3
HKTRp Mar 19, 2024
82e5a8a
Linter fix
Mar 19, 2024
deaf6c0
Merge remote-tracking branch 'origin/Stage3' into Stage3
Mar 19, 2024
bb9693a
Linter fix
Mar 19, 2024
bde8718
Merge branch 'main' into Stage3
HKTRp Mar 23, 2024
cbafa19
Stage 4
Mar 24, 2024
1d82a96
Still stage 4
Mar 24, 2024
5ebcc2d
Merge branch 'main' into Stage4
HKTRp Mar 24, 2024
e301c0d
Linter fixes
Mar 24, 2024
de11933
Merge remote-tracking branch 'origin/Stage4' into Stage4
Mar 24, 2024
fc75fe3
Linter fixes
Mar 24, 2024
b6b12a7
Report stage 4
Mar 31, 2024
8865e46
Merge branch 'main' into Stage4
Mar 31, 2024
4b307a4
Add more statuses handling
Mar 31, 2024
3abbeb1
Improvements
Apr 8, 2024
8098cf7
Merge branch 'main' into Stage4
HKTRp Apr 8, 2024
ac5066e
fix
Apr 8, 2024
c1e7684
Merge remote-tracking branch 'origin/Stage4' into Stage4
Apr 8, 2024
8aa31ca
fix
Apr 8, 2024
c5d88ef
Stage 5
Apr 8, 2024
2625f87
linter
Apr 8, 2024
ca8e3f7
Merge https://github.com/polis-vk/2024-highload-dht into Stage5
Apr 9, 2024
c123bed
.
Apr 9, 2024
cba8894
Report
Apr 13, 2024
2336b9d
Merge branch 'main' into Stage5
HKTRp Apr 13, 2024
b0b5320
HTML profiler output (GET requests)
Apr 21, 2024
b1b9f26
fix lint
Apr 21, 2024
f288aef
Merge branch 'main' into Stage5
HKTRp Apr 21, 2024
6e6c1f0
Solution
Apr 21, 2024
40861fc
Merge branch 'refs/heads/Stage5' into Stage6
Apr 21, 2024
049c1e1
Fix
Apr 21, 2024
7cd7b19
Fix lint
Apr 21, 2024
59999cb
Fix lint
Apr 21, 2024
08c7d3a
Fix lint
Apr 21, 2024
6825310
Fix
Apr 21, 2024
947a1ea
Merge branch 'main' into Stage6
HKTRp Apr 22, 2024
d93a444
Report
May 2, 2024
08a6cdc
Merge branch 'main' into Stage6
HKTRp May 2, 2024
c4f1e91
fix
May 2, 2024
500c21d
Merge branch 'main' into Stage6
daniil-ushkov May 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/main/java/ru/vk/itmo/test/kislovdanil/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ private Main() {

public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
DatabaseServiceFactory factory = new DatabaseServiceFactory();
List<String> nodes = List.of("http://localhost:8080",
"http://localhost:8081",
"http://localhost:8082");
List<String> nodes = List.of("http://localhost:8080");
ServiceConfig config = getConfig(args, nodes);
Service service = factory.create(config);
service.start().get();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
wrk.method = "GET"

math.randomseed(os.time())

request = function()
path = "/v0/entities?start=0"
return wrk.format(nil, path)
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
./wrk http://localhost:8080 -s getrange.lua -d 1m -R 10000 -t 4 -c 64 -L
Running 1m test @ http://localhost:8080
4 threads and 64 connections
Thread calibration: mean lat.: 6892.544ms, rate sampling interval: 18874ms
Thread calibration: mean lat.: 6873.429ms, rate sampling interval: 18907ms
Thread calibration: mean lat.: 6961.834ms, rate sampling interval: 18907ms
Thread calibration: mean lat.: 6934.528ms, rate sampling interval: 18825ms
Thread Stats Avg Stdev Max +/- Stdev
Latency 12.84s 87.18ms 12.97s 62.50%
Req/Sec 0.00 0.00 0.00 100.00%
Latency Distribution (HdrHistogram - Recorded Latency)
50.000% 12.85s
75.000% 12.89s
90.000% 12.94s
99.000% 12.98s
99.900% 12.98s
99.990% 12.98s
99.999% 12.98s
100.000% 12.98s

Detailed Percentile spectrum:
Value Percentile TotalCount 1/(1-Percentile)

12656.639 0.000000 1 1.00
12681.215 0.100000 2 1.11
12795.903 0.200000 4 1.25
12828.671 0.300000 5 1.43
12853.247 0.400000 8 1.67
12853.247 0.500000 8 2.00
12877.823 0.550000 9 2.22
12886.015 0.600000 12 2.50
12886.015 0.650000 12 2.86
12886.015 0.700000 12 3.33
12886.015 0.750000 12 4.00
12894.207 0.775000 13 4.44
12894.207 0.800000 13 5.00
12943.359 0.825000 14 5.71
12943.359 0.850000 14 6.67
12943.359 0.875000 14 8.00
12959.743 0.887500 15 8.89
12959.743 0.900000 15 10.00
12959.743 0.912500 15 11.43
12959.743 0.925000 15 13.33
12959.743 0.937500 15 16.00
12976.127 0.943750 16 17.78
12976.127 1.000000 16 inf
#[Mean = 12844.544, StdDeviation = 87.177]
#[Max = 12967.936, Total count = 16]
#[Buckets = 27, SubBuckets = 2048]
----------------------------------------------------------
64 requests in 1.00m, 9.07GB read
Socket errors: connect 0, read 0, write 0, timeout 1856
Requests/sec: 1.07
Transfer/sec: 154.59MB
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
./wrk http://localhost:8080 -s getrange.lua -d 1m -R 10000 -t 4 -c 64 -L
Running 1m test @ http://localhost:8080
4 threads and 64 connections
Thread calibration: mean lat.: 9223372036854776.000ms, rate sampling interval: 10ms
Thread calibration: mean lat.: 9223372036854776.000ms, rate sampling interval: 10ms
Thread calibration: mean lat.: 9223372036854776.000ms, rate sampling interval: 10ms
Thread calibration: mean lat.: 9223372036854776.000ms, rate sampling interval: 10ms
Thread Stats Avg Stdev Max +/- Stdev
Latency 34.19s 13.56s 0.92m 40.62%
Req/Sec 0.34 6.14 200.00 99.68%
Latency Distribution (HdrHistogram - Recorded Latency)
50.000% 37.26s
75.000% 41.68s
90.000% 0.90m
99.000% 0.92m
99.900% 0.92m
99.990% 0.92m
99.999% 0.92m
100.000% 0.92m

Detailed Percentile spectrum:
Value Percentile TotalCount 1/(1-Percentile)

16318.463 0.000000 1 1.00
18317.311 0.100000 7 1.11
19333.119 0.200000 13 1.25
19464.191 0.300000 20 1.43
27508.735 0.400000 26 1.67
37257.215 0.500000 32 2.00
38305.791 0.550000 36 2.22
38567.935 0.600000 39 2.50
39288.831 0.650000 42 2.86
40108.031 0.700000 45 3.33
41680.895 0.750000 48 4.00
47579.135 0.775000 50 4.44
51085.311 0.800000 52 5.00
51544.063 0.825000 53 5.71
53051.391 0.850000 55 6.67
53084.159 0.875000 56 8.00
53870.591 0.887500 57 8.89
54263.807 0.900000 58 10.00
54329.343 0.912500 59 11.43
54558.719 0.925000 60 13.33
54558.719 0.937500 60 16.00
54657.023 0.943750 62 17.78
54657.023 0.950000 62 20.00
54657.023 0.956250 62 22.86
54657.023 0.962500 62 26.67
54657.023 0.968750 62 32.00
55050.239 0.971875 63 35.56
55050.239 0.975000 63 40.00
55050.239 0.978125 63 45.71
55050.239 0.981250 63 53.33
55050.239 0.984375 63 64.00
55181.311 0.985938 64 71.11
55181.311 1.000000 64 inf
#[Mean = 34186.048, StdDeviation = 13561.120]
#[Max = 55148.544, Total count = 64]
#[Buckets = 27, SubBuckets = 2048]
----------------------------------------------------------
64 requests in 1.00m, 9.07GB read
Socket errors: connect 0, read 0, write 0, timeout 1840
Requests/sec: 1.06
Transfer/sec: 154.47MB
48 changes: 48 additions & 0 deletions src/main/java/ru/vk/itmo/test/kislovdanil/report/stage6/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Отчёт о нагрузочном тестировании

## Этап 6

* Тестирование производилось при 10000 RPS(GET) на 4 потока с одним 64 соединяниями.
* Один инстанс базы данных
* flushThresholdBytes 10Mb
* База заполнена на 190 Mb всеми ключами от 0 до 100000.
* Обработкой запросов занимется ThreadPoolExecutor с очередью на 100000 задач,
пулом 24 потока
* Для тестирования была использована утилита wrk2.
* Для профилирования был использован async-profiler внутри IntelliJ IDEA

### Скрипты

* [getrange.lua](../scripts/getrange.lua)

### Результаты

[Вывод wrk2 для GET](getrange.txt)

![](Histogram.png)

#### Флеймграфы для GET запросов

##### CPU

![](getCpu.png)

##### Allocations

![](getMemory.png)

### Вывод

Изначальная реализация DAO не читала в оперативную память
весь range, поэтому проблем с решением для этапа не возникло.
Большая часть времени уходит на передачу данных; чтобы уменьшить latency
можно попробовать добавить сжатие данных или как-то ускорить передачу
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Непонятно поможет ли сжатие, так как тогда добавится время на компрессию и декомпрессию.

Из того, что я видел, обычно сжатие используют не для уменьшения latency, а для реализации холодного хранилища. В этом случае о latency как раз не думают, оно напротив увеличивается. Здесь оптимизируется занимаемое место.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В целом да, хотя я не уверен что нет алгоритмов которые бы сжимали бы быстрее чем было бы передавать, просто гипотетическое предположение

данных

Можно заметить, что довольно много памяти выделяется при отправке
чанка, т.к. его содержание копируется в буфер. Это осознанное решение,
если отправлять данные последовательно вызывая session.write на массивах байтов
[уходит в 4-5 раз больше времени](getrange_sqeuntial_write.txt).
Нагрузочное тестирование с wrk2 на данном этапе имеет мало смысла т.к. почти всё
время уходит на запись 190Mb в сессию и для всех запросов уходит одинаковое время,
приложил вывод только чтобы можно было посмотреть latency такого запроса.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ru.vk.itmo.test.kislovdanil.service;

import one.nio.http.HttpSession;
import ru.vk.itmo.test.kislovdanil.dao.Entry;

import java.io.IOException;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

public class ChunkTransformUtility {
private static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes(StandardCharsets.UTF_8);
private static final byte[] KEY_VALUE_SEPARATOR = "\n".getBytes(StandardCharsets.UTF_8);
public static final byte[] EMPTY_CONTENT = "0\r\n\r\n".getBytes(StandardCharsets.UTF_8);
public static final byte[] HEADERS = """
HTTP/1.1 200 OK\r
Transfer-Encoding: chunked\r
\r
""".getBytes(StandardCharsets.UTF_8);

private ChunkTransformUtility() {

}

private static void writeFull(byte[] data, HttpSession session) throws IOException {
session.write(data, 0, data.length);
}

public static void writeContent(Entry<MemorySegment> entry, HttpSession session) throws IOException {
int entrySize = (int) (entry.key().byteSize() + entry.value().byteSize()) + KEY_VALUE_SEPARATOR.length;
byte[] entrySizeHex = Long.toHexString(entrySize).getBytes(StandardCharsets.UTF_8);
byte[] content = new byte[entrySize + entrySizeHex.length + CHUNK_SEPARATOR.length * 2];
ByteBuffer buffer = ByteBuffer.wrap(content);
buffer.put(entrySizeHex)
.put(CHUNK_SEPARATOR)
.put(entry.key().toArray(ValueLayout.JAVA_BYTE))
.put(KEY_VALUE_SEPARATOR)
.put(entry.value().toArray(ValueLayout.JAVA_BYTE))
.put(CHUNK_SEPARATOR);
writeFull(content, session);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
Expand All @@ -30,8 +31,9 @@ public class DatabaseHttpServer extends HttpServer {
private final PersistentDao dao;
private final Sharder sharder;
private static final String ENTITY_ACCESS_URL = "/v0/entity";
private static final int CORE_POOL_SIZE = 12;
private static final int MAX_POOL_SIZE = 12;
private static final String ENTITIES_ACCESS_URL = "/v0/entities";
private static final int CORE_POOL_SIZE = 24;
private static final int MAX_POOL_SIZE = 24;
private static final int KEEP_ALIVE_TIME_MS = 50;
private final ThreadPoolExecutor queryExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE,
KEEP_ALIVE_TIME_MS, TimeUnit.MILLISECONDS, new LinkedBlockingStack<>());
Expand Down Expand Up @@ -110,7 +112,7 @@ public void handleEntityRequest(Request request, HttpSession session,
from = fromParam == null ? clusterSize : fromParam;
acknowledge = acknowledgeParam == null ? from / 2 + 1 : acknowledgeParam;
final boolean notProxy = notProxyParam != null && notProxyParam;
if (acknowledge <= 0 || acknowledge > from || from > clusterSize) {
if (acknowledge <= 0 || acknowledge > from || from > clusterSize || entityKey.isEmpty()) {
sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY), session);
}
try {
Expand All @@ -123,6 +125,37 @@ public void handleEntityRequest(Request request, HttpSession session,
}
}

private void handleEntitiesRequestTask(MemorySegment start, MemorySegment end, HttpSession session) {
try {
session.write(ChunkTransformUtility.HEADERS, 0, ChunkTransformUtility.HEADERS.length);
for (Iterator<Entry<MemorySegment>> it = dao.get(start, end); it.hasNext(); ) {
ChunkTransformUtility.writeContent(it.next(), session);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Так, а если в какой-то записи лежит очень большое value. Ты будешь его целиком в сокет запихивать?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, а какие варианты? Доставать куски из MemorySegment и их по очереди записывать чтобы не переполнить память? Настолько большой value в текущей логике, кажется, просто не получится записать в бд

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поговорил с коллегами. Обработку данного случая, мы от вас требовать не будем.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C session.write вроде бы есть еще проблема. Вроде как там внутри неограниченная очередь, из-за чего теоертически клиент может медленнее отправлять чанки, чем твой код их туда пушит. И это в конечном итоге может привести к тому, что мы таки выгрузим много данных в оперативную память.

Однако я считаю, что перебор от вас такое требовать)

}
session.write(ChunkTransformUtility.EMPTY_CONTENT, 0, ChunkTransformUtility.EMPTY_CONTENT.length);
session.close();
} catch (IOException e) {
throw new NetworkException();
}
}

@Path(ENTITIES_ACCESS_URL)
public void handleEntitiesRequest(Request request, HttpSession session,
@Param(value = "start", required = true) String start,
@Param(value = "end") String end) {
if (start.isBlank()) {
sendResponse(new Response(Response.BAD_REQUEST, Response.EMPTY), session);
}
try {
queryExecutor.execute(() -> handleEntitiesRequestTask(fromString(start),
fromString(end), session));
} catch (RejectedExecutionException e) {
sendResponse(new Response(Response.SERVICE_UNAVAILABLE,
"Service temporary unavailable, retry later"
.getBytes(StandardCharsets.UTF_8)), session);
}

}

private Response putEntity(MemorySegment entityKey, byte[] entityValue) {
dao.upsert(new BaseEntry<>(entityKey, MemorySegment.ofArray(entityValue), System.currentTimeMillis()));
return new Response(Response.CREATED, Response.EMPTY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import java.io.UncheckedIOException;
import java.nio.file.Paths;

@ServiceFactory(stage = 5)
@ServiceFactory(stage = 6)
public class DatabaseServiceFactory implements ServiceFactory.Factory {
@Override
public Service create(ServiceConfig serverConfig) {
Expand Down
Loading