Тестрирование полностью производилось на машине с 2-х ядерным процессором Intel Core i5-4200H 2.8 ГГц, на котором 4 логических процессора. Память - 8 ГБ. Диск - SSD SATA 6 Гб/c.
3 инстанса БД запускались на localhost (порты 8081, 8082, 8083) на Windows 10 на jre-9.0.1.
Нагрузка подавалась при помощи инструмента yandex-tank (overload), запущенного в виртуальной машине с Ubuntu 16.04.3 LTS, которой было выделено 4 вируальных процессора и 2 ГБ памяти.
Для генерации патронов использовался готовый py скрипт из туториала, который принимает на вход строки вида GET||/url||case_tag||body(optional)
.
Самостоятельно был написан py скрипт для генерации данных, подающихся на вход генератора патронов. Данный скрипт позволяет создать файл с N
(только PUT
, только GET
, смесь PUT
и GET
) запросами с/без перезаписи и replicas=K/M
.
Далее были сгенерированы патроны для всех случаев, описанных в задании.
Исходный проект был примерно таким: для обработки нодой запросов /v0/entity?...
от клиента использовался newFixedThreadPool()
с 5-ю потоками. Запросы между нодами обрабатывались при помощи newSingleThreadExecutor()
, т.к. такие запросы подразумевали чтение и запись с/на диск. После того, как запрос приходил на ноду в newSingleThreadExecutor()
, пораждалось еще столько потоков, сколько нод в кластере, чтобы нода могла отправлять данные на другие ноды параллельно. Осуществлено это было при помощи механизма асинхронной работы Future
. Нода запросы на другие ноды отправляла параллельно, а ответы принимала последовательно.
Было определено, что БД выдерживает нагрузку примерно 200 GET
запросов в секунду и 100 PUT
запросов в секунду. Далее подавалась примерно такая нагрузка с replicas
2/3
и 3/3
с/без перезаписи.
Ниже приведена информация о пропускной способности для каждого случая. А более подробная информация с графиками доступна по соответстующим ссылкам.
-
GET 3/3 без перезаписи
- 1 поток соединения - 181 rps (ссылка на overload)
- 2 потока соединения - 205 rps (ссылка на overload)
- 4 потока соединения - 258 rps (ссылка на overload)
-
GET 3/3 с перезаписью
- 1 поток соединения - 191 rps (ссылка на overload)
- 2 потока соединения - 210 rps (ссылка на overload)
- 4 потока соединения - 225 rps (ссылка на overload)
-
GET 2/3 без перезаписи
- 1 поток соединения - 211 rps (ссылка на overload)
- 2 потока соединения - 247 rps (ссылка на overload)
- 4 потока соединения - 325 rps (ссылка на overload)
-
GET 2/3 с перезаписью
- 1 поток соединения - 225 rps (ссылка на overload)
- 2 потока соединения - 269 rps (ссылка на overload)
- 4 потока соединения - 273 rps (ссылка на overload)
-
PUT 3/3 без перезаписи
- 1 поток соединения - 46 rps (ссылка на overload)
- 2 потока соединения - 77 rps (ссылка на overload)
- 4 потока соединения - 44 rps (ссылка на overload)
-
PUT 3/3 с перезаписью
- 1 поток соединения - 67 rps (ссылка на overload)
- 2 потока соединения - 82 rps (ссылка на overload)
- 4 потока соединения - 93 rps (ссылка на overload)
-
PUT 2/3 без перезаписи
- 1 поток соединения - 67 rps (ссылка на overload)
- 2 потока соединения - 67 rps (ссылка на overload)
- 4 потока соединения - 66 rps (ссылка на overload)
-
PUT 2/3 с перезаписью
- 1 поток соединения - 71 rps (ссылка на overload)
- 2 потока соединения - 83 rps (ссылка на overload)
- 4 потока соединения - 74 rps (ссылка на overload)
-
PUTGET 3/3 без перезаписи
- 4 потока соединения - 98 rps (ссылка на overload)
-
PUTGET 3/3 с перезаписью
- 4 потока соединения - 125 rps (ссылка на overload)
-
PUTGET 2/3 без перезаписи
- 4 потока соединения - 109 rps (ссылка на overload)
-
PUTGET 2/3 с перезаписью
- 4 потока соединения - 114 rps (ссылка на overload)
Было решено, что слабые места в первой реализации это:
- Использование
newSingleThreadExecutor()
при обработке запросов, которые пишут/читают на/с диска. - Последовательная обработка ответов от других нод.
В исправленном варианте используется StampedLock
для умной блокировки на запись/чтение и для оптимистичного чтения, если запись происходит редко. И вместо однопоточного пула используется newFixedThreadPool()
с числом потоков равному Runtime.getRuntime().availableProcessors() + 1
.
Вместо Future
используются CompletableFuture
, которые засчёт колбэков позволяют легко асинхронно обрабатывать ноде ответы от других нод.
По схожему принципу подавалась нагрузка. Пропускная способность выросла в среднем на 30%. Ниже приведены некоторые ссылки на результаты тестирования.
Проводить тестирование в таком окружении (БД на host os, а yandex-tank на guest os) было не очень просто. Возникали различные проблемы. И результаты, полученные до и после оптимизаций, кажутся весьма невысокими. Далее в свободное время планируется запустить приложение на разных машинах. И возможно, даже на линуксе, чтобы была возможность профилировать в jvisualvm
(на windows нет поддержки профилирования локальных процессов). И посмотреть как разработанная БД поведёт себя в более боевых условиях.
С другой стороны, был получен опыт настройки и использования инструмента yandex-tank
, а также были изучены разные механизмы асинхронных вычислений на java
. В целом было интересно.