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

HW2 #50

Merged
merged 22 commits into from
Mar 15, 2024
Merged

HW2 #50

Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@
public class DaoServerConfig extends HttpServerConfig {
public Path basePath;
public long flushThresholdBytes;
public int corePoolSize = 2;
public int maximumPoolSize = 20;
public long keepAliveTime = 100;
public int queueCapacity = 1000;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
local threads = {}

function setup(thread)
table.insert(threads, thread)
end

function init(args)
count_404 = 0
count_503 = 0
end

function request()
key = math.random(1000000)
key = math.random(1750000)

return wrk.format("GET", "/v0/entity?id=k" .. key)
end

function response(status, headers, body)
local nstatus = tonumber(status)
if nstatus == 404 then count_404 = count_404 + 1
elseif nstatus == 503 then count_503 = count_503 + 1
end
end

function done(summary, latency, requests)
total_404 = 0
total_503 = 0

for index, thread in ipairs(threads) do
total_404 = total_404 + thread:get("count_404")
total_503 = total_503 + thread:get("count_503")
end

print("------------------------------\n")
local msg_status = "HTTP Status %s Count: %d"
print(msg_status:format("404", total_404))
print(msg_status:format("503", total_503))
end
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function request()
key = math.random(1000000)
key = math.random(1750000)
value = ""
for i = 1, math.random(1, 100) do
value = value .. math.random(1, 100000)
Expand Down
10 changes: 6 additions & 4 deletions src/main/java/ru/vk/itmo/test/kovalevigor/reports/scripts/sh/base.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#!/bin/bash
NAME=$(date +"%s")
JFR="../../info-$NAME.jfr"
(wrk2 -c 1 -t 1 -L -d $1 -R $2 -s "../lua/$3.lua" http://localhost:8080 > "../../html/${NAME}_wrk.txt") &
./ap/bin/asprof -e cpu,alloc -d $1 -f $JFR MainServer
PREFIX="../../html/stage2"
(wrk2 -c 128 -t 8 -L -d $1 -R $2 -s "../lua/$3.lua" http://localhost:8080 > "${PREFIX}/${NAME}_wrk.txt") &
./ap/bin/asprof -t -e cpu,alloc,lock -d $1 -f $JFR MainServer

wait
java -cp ./ap/lib/converter.jar jfr2flame $JFR > "../../html/${NAME}_cpu.html"
java -cp ./ap/lib/converter.jar jfr2flame --alloc $JFR > "../../html/${NAME}_alloc.html"
java -cp ./ap/lib/converter.jar jfr2flame --threads $JFR > "${PREFIX}/${NAME}_cpu.html"
java -cp ./ap/lib/converter.jar jfr2flame --threads --alloc $JFR > "${PREFIX}/${NAME}_alloc.html"
java -cp ./ap/lib/converter.jar jfr2flame --threads --lock $JFR > "${PREFIX}/${NAME}_lock.html"
rm $JFR
54 changes: 27 additions & 27 deletions src/main/java/ru/vk/itmo/test/kovalevigor/reports/stage1.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,28 @@ Macbook Pro M1 16GB после перезапуска
#### PUT-запросы
Изначально мы прощупываем точку разладки для **PUT** запросов

[1](html/put_lower_wrk.txt),
[2](html/put_upper_wrk.txt),
[3](html/put_middle_1.txt),
[4](html/put_middle_2.txt),
[5](html/put_middle_3.txt),
[6](html/put_middle_4.txt)
[1](html/stage1/put_lower_wrk.txt),
[2](html/stage1/put_upper_wrk.txt),
[3](html/stage1/put_middle_1.txt),
[4](html/stage1/put_middle_2.txt),
[5](html/stage1/put_middle_3.txt),
[6](html/stage1/put_middle_4.txt)

Можно обратить внимание, что flamegraph'ы при этом не сильно отличаются

[1](html/put_lower_cpu.html),
[2](html/put_upper_cpu.html)
[1](html/stage1/put_lower_cpu.html),
[2](html/stage1/put_upper_cpu.html)

Видимо, это просто может сказать нам о некоем пределе нашей производительности

В итоге остановился на 13000 RPS, т.к. там есть более ощутимый скачок, чем на 11500

[300 секунд по 13000 RPS на пустую базу](html/put_request_wrk.txt)
[300 секунд по 13000 RPS на пустую базу](html/stage1/put_request_wrk.txt)

Если посмотреть на [аллокации](html/put_request_alloc.html),
Если посмотреть на [аллокации](html/stage1/put_request_alloc.html),
то наше DAO почти ничего не аллоцирует, все уходит на сервер

На [cpu](html/put_request_cpu.html), т.к. мы вынесли flush в отдельный поток (самая левая гора),
На [cpu](html/stage1/put_request_cpu.html), т.к. мы вынесли flush в отдельный поток (самая левая гора),
то он незаметно, что он как либо блокирует, сохранение наших записей.

Однако, можно заметить, что 30% от времени отправки ответа занимает помещенеие entity в мапу
Expand All @@ -75,29 +75,29 @@ Macbook Pro M1 16GB после перезапуска

*Как можно заметить после первой же нагрузки, что происходит огромная просадка на существенной части запросов*

*[wrk](html/get_many_misses_wrk.txt)*
*[wrk](html/stage1/get_many_misses_wrk.txt)*

*И это оказывается фейком, потому что одновременно с запросами работал optimizer*

Из-за того, что **PUT** запросы создают записи в слишком большом диапазоне у нас много 400.
Однако, давайте и тут найдем точку разладки

[7000rps](html/get_7000_wrk.txt),
[8000rps](html/get_8000_wrk.txt),
[9000rps](html/get_9000_wrk.txt)
[10000rps](html/get_10000_wrk.txt)
[7000rps](html/stage1/get_7000_wrk.txt),
[8000rps](html/stage1/get_8000_wrk.txt),
[9000rps](html/stage1/get_9000_wrk.txt)
[10000rps](html/stage1/get_10000_wrk.txt)

Возьмем 8000, т.к. на мой взгляд наиболее оптимальное падение производительности

[300 секунд по 8000 RPS](html/get_misses_request_wrk.txt)
[300 секунд по 8000 RPS](html/stage1/get_misses_request_wrk.txt)

Как мы видим из [cpu](html/get_misses_request_cpu.html) огромное количество времени уходит на бинарный поиск
Как мы видим из [cpu](html/stage1/get_misses_request_cpu.html) огромное количество времени уходит на бинарный поиск

*Вероятнее всего, это связано с запросами к несуществующим ключам,
т.к. для для проверки нам будет необходимо обойти все таблицы
*(20 по 45MB)**

Также можно заметить,что теперь у нас присутсвует огромное количество [аллокаций](html/get_misses_request_alloc.html)
Также можно заметить,что теперь у нас присутсвует огромное количество [аллокаций](html/stage1/get_misses_request_alloc.html)
для бинарного поиска

Также из интересного можно заметить, что столбик с созданием итератора занимает `3%`,
Expand All @@ -108,16 +108,16 @@ Macbook Pro M1 16GB после перезапуска

Так 400 будет намного-намного меньше)

[10000_wrk](html/get_n_10000_wrk.txt),
[11000_wrk](html/get_n_11000_wrk.txt),
[13000_wrk](html/get_n_13000_wrk.txt),
[15000_wrk](html/get_n_15000_wrk.txt)
[10000_wrk](html/stage1/get_n_10000_wrk.txt),
[11000_wrk](html/stage1/get_n_11000_wrk.txt),
[13000_wrk](html/stage1/get_n_13000_wrk.txt),
[15000_wrk](html/stage1/get_n_15000_wrk.txt)

Как мы видим, примерно 25% - это промахи. Возьмем 10000 RPS

[300 секунд по 10000 RPS](html/get_n_request_wrk.txt)
[300 секунд по 10000 RPS](html/stage1/get_n_request_wrk.txt)

[cpu](html/get_n_request_cpu.html)
[cpu](html/stage1/get_n_request_cpu.html)
Как мы видим время поиска на диске сократилось в процентном соотношении, т.е.
мы стали чаще попадать в память.

Expand All @@ -127,7 +127,7 @@ Macbook Pro M1 16GB после перезапуска

Однако, проблема с поиском никуда не делась. Что впринципе видно из графиков

[Аллокации](html/get_n_request_alloc.html) также демонстрируют нам, что искать мы стали существенно меньше,
[Аллокации](html/stage1/get_n_request_alloc.html) также демонстрируют нам, что искать мы стали существенно меньше,
т.к. теперь столбик `nio` хотя бы различим
#### Other

Expand Down Expand Up @@ -196,6 +196,6 @@ B+-tree - круто, блюм-фитр - респект

Хотя при этом время ответа значительно увеличилось. Возможно, комп уже загадился, либо я что-то не понял

PUT: [wrk](html/put_without_gen_wrk.txt), [alloc](html/put_without_gen_alloc.html), [cpu](html/put_without_gen_cpu.html)
PUT: [wrk](html/stage1/put_without_gen_wrk.txt), [alloc](html/stage1/put_without_gen_alloc.html), [cpu](html/stage1/put_without_gen_cpu.html)
GET: [wrk](html/Fget_without_gen_wrk.txt), [alloc](html%2Fget_without_gen_alloc.html), [cpu](html%2Fget_without_gen_cpu.html)

198 changes: 198 additions & 0 deletions src/main/java/ru/vk/itmo/test/kovalevigor/reports/stage2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
## Факты

Была обновлена система с Monterey на Sonora, а также почищен диск

В связи с чем результаты значительно выросли,
особеннно в области **GET** запросов, которые очень много работают с диском

Получается достаточное количества места на диске важный параметр

Либо стоит благословить Купертино

## Нагрузка
### Характеристики
Macbook Pro M1 16GB после перезапуска


## Старый код
До этого мы тестировали при 1 подключении и 1 потоке

Соответственно у нас было 2 ограничения:
1. Мы могли упираться в производительность wrk2
2. Наш код работал только в одном потоке, несмотря на количество селекторов, т.к. подключение 1

Поэтому перезапустим старый код и найдем новые rps
### PUT

По [локам](old_base_lock.html) переодически можно заметить flush, который тормозит селекторы

Как мы видим [15000](old_base_wrk.txt) нагрузка настолько слабая,
что нам не понадобились даже все селекторы (их только 4)
[cpu](old_base_cpu.html)

Немного поискав
* [30000](old_30000_wrk.txt)
* [60000](old_60000_wrk.txt)
* [52000](old_52000_wrk.txt)

остановился на [48000](old_stable_wrk.txt) RPS

[lock](old_stable_lock.html) из интересного можно заметить,
что по окончании работы закрывается множество сессий

информация об этом пишется в лог и можно сделать выводы о том, что:
1. не стоить злоупотреблять логами
2. по этому столбику в целом можно смотреть о проблемности остальных локов
*(чем он больше, тем менее значимы остальные локи)*

По [cpu](old_stable_cpu.html) видно, что большая часть ресурсов уходит на обработку подключений

5 min: [rps](old_stable_long_wrk.txt),
[alloc](old_stable_long_alloc.html)
[cpu](old_stable_long_cpu.html),
[lock](old_stable_long_lock.html)

Как мы видим просадки теперь намного значительнее, т.к. flush блокирует все потоки

### GET

Немного поискав
[60000](old_get_60000_wrk.txt)
[80000](old_get_80000_wrk.txt)
[85000](old_get_85000_wrk.txt)
[90000](old_get_90000_wrk.txt)
[100000](old_get_100000_wrk.txt)

Остановился на 80000 rps
5 min:
[alloc](old_get_stable_long_alloc.html)
[cpu](old_get_stable_long_cpu.html)
[lock](old_get_stable_long_lock.html)
[rps](old_get_stable_long_wrk.txt)

Как мы видим из [lock](old_get_stable_long_lock.html) никаких локов на гет запросах нет, как и из кода

В целом ничего, кроме огромного роста производительности отметить нечего

И если в случае с **PUT** запросами он был всего в несколько раз,
то здесь рост достаточно линеен, что может говорить о высоком параллелизме кода

## Новый код

### ArrayBlockingQueue

Производительность только упала

#### PUT

Как показали опыты: poolSize имеет значение.
* 16:
* * 43000: [alloc](new_put_a_16_43000_alloc.html),
[cpu](new_put_a_16_43000_cpu.html),
[lock](new_put_a_16_43000_lock.html),
[rps](new_put_a_16_43000_wrk.txt)
* * 48000: [alloc](new_put_a_16_48000_alloc.html),
[cpu](new_put_a_16_48000_cpu.html),
[lock](new_put_a_16_48000_lock.html),
[rps](new_put_a_16_48000_wrk.txt)
* * [50000](new_put_a_16_50000_wrk.txt)
* 32
* * [42000](new_put_a_32_42000_wrk.txt)
* * 43000: [alloc](new_put_a_32_43000_alloc.html),
[cpu](new_put_a_32_43000_cpu.html),
[lock](new_put_a_32_43000_lock.html),
[rps](new_put_a_32_43000_wrk.txt),
* * [45000](new_put_a_32_45000_wrk.txt)
* * [50000](new_put_a_32_50000_wrk.txt)

Чем больше потоков, тем сильнее они мешают друг-другу, что приводит к значительному снижению производительности

Так мы добрались до 8 максимальных (т.е. по числу ядер).
* [alloc](new_put_a_2_8_48000_alloc.html)
* [cpu](new_put_a_2_8_48000_cpu.html)
* [lock](new_put_a_2_8_48000_lock.html)
* [rps](new_put_a_2_8_48000_wrk.txt)

5 min: [alloc](new_put_a_2_8_stable_long_alloc.html),
[cpu](new_put_a_2_8_stable_long_cpu.html),
[lock](new_put_a_2_8_stable_long_lock.html),
[rps](new_put_a_2_8_stable_long_wrk.txt)

Основные проблемы и заполнение очереди происходили при flush'ах таблицы, что вполне ожидаемо

#### GET

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

И чем больше poolSize, тем больше допустимый RPS

### LinkedBlockingQueue

Несложно заметить, что у нас большие проблемы с локами. Связано это с тем, что в `ArrayBlockingQueue`
одна блокировка на чтение и запись, поэтому окончательно разделить селекторы от воркеров не получилось

Воспользуемся `LinkedBlockingQueue`, у которой уже 2 разные блокировки на чтение и запись

#### PUT

Как мы видим, несмотря на то, что время самих блокировок для селекторов значительно уменьшилось

Что повысило скорость поступления новых задач (поэтому был повышел queueCapacity до 200, т.к. он набирался слишком быстро).

Однако, сама очередь достаточно медлена. Из-за чего общая производительность страдает.

* [40000](new_put_l_32_40000_wrk.txt)
* 45000: [alloc](new_put_l_32_45000_alloc.html),
[cpu](new_put_l_32_45000_cpu.html),
[lock](new_put_l_32_45000_lock.html),
[rps](new_put_l_32_45000_wrk.txt)
* [48000](new_put_l_32_48000_wrk.txt)
* [60000](new_put_l_32_60000_wrk.txt)

#### GET

На удивление, **GET'у** стало чуть лучше, но мне кажется, все в рамках погрешности

[rps](new_get_l_32_60000_wrk.txt),
[alloc](new_get_l_32_60000_alloc.html),
[cpu](new_get_l_32_60000_cpu.html),
[lock](new_get_l_32_60000_lock.html)

### Давайте попробуем поделить

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

Как мы видим, мы смогли замедлить downgrade касательно времени работы

Однако, как-то значительно улучшить производительность это нам не помогло

#### PUT


* 40000:
[alloc.html](1709758164_alloc.html)
[cpu.html](1709758164_cpu.html)
[lock.html](1709758164_lock.html)
[wrk.txt](1709758164_wrk.txt)
* 50000:
[alloc](1709758118_alloc.html),
[cpu](1709758118_cpu.html),
[lock](1709758118_lock.html),
[rps](1709758118_wrk.txt)

* 60000: [alloc](1709758089_alloc.html),
[cpu](1709758089_cpu.html),
[lock](1709758089_lock.html),
[rps](1709758089_wrk.txt)

Локи в селекторах теперь незначительно, но тольку то ;)

## Вывод

Несмотря, на очевидность преимуществ, которые нам может подарить разделение селекторов

Оказывается, что на самом деле это довольно тонкая тема, в связи с многопоточностью и блокировками,
которые нужно не забывать учитывать

К сожалению, не успел посмотреть `DiscardPolicy` с обрывом сокета для первой в очереди.
Это вполне могло повлиять на среднее время ответа
Loading
Loading