Курсовой проект 2019 года курса "Highload системы" в Технополис.
Форкните проект, склонируйте и добавьте upstream:
$ git clone git@github.com:<username>/2019-highload-dht.git
Cloning into '2019-highload-dht'...
...
$ git remote add upstream git@github.com:polis-mail-ru/2019-highload-dht.git
$ git fetch upstream
From github.com:polis-mail-ru/2019-highload-dht
* [new branch] master -> upstream/master
Так можно запустить тесты:
$ gradle test
А вот так -- сервер:
$ gradle run
Откройте в IDE -- IntelliJ IDEA Community Edition нам будет достаточно.
ВНИМАНИЕ! При запуске тестов или сервера в IDE необходимо передавать Java опцию -Xmx128m.
В своём Java package ru.mail.polis.service.<username> реализуйте интерфейс Service и поддержите следующий HTTP REST API протокол:
- HTTP
GET /v0/entity?id=<ID>-- получить данные по ключу<ID>. Возвращает200 OKи данные или404 Not Found. - HTTP
PUT /v0/entity?id=<ID>-- создать/перезаписать (upsert) данные по ключу<ID>. Возвращает201 Created. - HTTP
DELETE /v0/entity?id=<ID>-- удалить данные по ключу<ID>. Возвращает202 Accepted.
Возвращайте реализацию интерфейса в ServiceFactory.
Реализацию DAO берём из весеннего курса 2019-db-lsm, либо запиливаем adapter к уже готовой реализации LSM с биндингами на Java (например, RocksDB, LevelDB или любой другой).
Проведите нагрузочное тестирование с помощью wrk в одно соединение.
Почему не curl/F5, можно узнать здесь и здесь.
Попрофилируйте (CPU и alloc) под нагрузкой с помощью async-profiler и проанализируйте результаты.
Продолжайте запускать тесты и исправлять ошибки, не забывая подтягивать новые тесты и фиксы из upstream.
Если заметите ошибку в upstream, заводите баг и присылайте pull request ;)
Когда всё будет готово, присылайте pull request со своей реализацией и оптимизациями на review. Не забывайте отвечать на комментарии в PR (в том числе автоматизированные) и исправлять замечания!
Обеспечьте потокобезопасность реализации DAO с помощью synchronized, а лучше -- с использованием примитивов java.util.concurrent.*.
Прокачаться можно с руководством Java Concurrency in Practice.
Сконфигурируйте HTTP сервер, чтобы он обрабатывал запросы с помощью пула из нескольких потоков.
Проведите нагрузочное тестирование с помощью wrk в несколько соединений.
Отпрофилируйте приложение (CPU, alloc и lock) под нагрузкой с помощью async-profiler и проанализируйте результаты.
Когда всё будет готово, присылайте pull request со своей реализацией и оптимизациями на review.
Реализуйте асинхронный HTTP сервер на основе one-nio.
Проведите нагрузочное тестирование с помощью wrk в несколько соединений с разными видами запросов.
Попрофилируйте приложение (CPU, alloc и lock) под нагрузкой с помощью async-profiler и проанализируйте результаты.
Реализуйте получение диапазона данных с помощью HTTP GET /v0/entities?start=<ID>[&end=<ID>], который возвращает:
- Статус код
200 OK - Возможно пустой отсортированный (по ключу) набор ключей и значений в диапазоне ключей от обязательного
start(включая) до опциональногоend(не включая) - Использует Chunked transfer encoding
- Чанки в формате
<key>\n<value>
Диапазон должен отдаваться в потоковом режиме без формирования всего ответа в памяти.
Когда всё будет готово, присылайте pull request с изменениями, результатами нагрузочного тестирования и профилирования, а также анализом результатом по сравнению с предыдущей (блокирующей) версией.
Реализуем горизонтальное масштабирование через поддержку кластерных конфигураций, состоящих из нескольких узлов, взаимодействующих друг с другом через реализованный HTTP API.
Для этого в ServiceFactory передаётся статическая "топология", представленная в виде множества координат всех узлов кластера в формате http://<host>:<port>.
gradle run теперь стартует Cluster из трёх нод.
Кластер распределяет ключи между узлами детерминированным образом. В кластере хранится только одна копия данных. Нода, получившая запрос, проксирует его на узел, отвечающий за обслуживание соответствующего ключа. Таким образом, общая ёмкость кластера равна суммарной ёмкости входящих в него узлов.
Реализуйте один из алгоритмов распределения данных между узлами, например, consistent hashing и rendezvous hashing.
Присылайте pull request со своей реализацией поддержки кластерной конфигурации на review. Не забудьте нагрузить, отпрофилировать и проанализировать результаты профилирования под нагрузкой. С учётом шардирования набор тестов расширяется, поэтому не забывайте подмёрдживать upstream.
Реализуем поддержку хранения нескольких реплик данных в кластере для обеспечения отказоустойчивости.
HTTP API расширяется query-параметром replicas, содержащим количество узлов, которые должны подтвердить операцию, чтобы она считалась выполненной успешно.
Значение параметра replicas указывается в формате ack/from, где:
ack-- сколько ответов нужно получитьfrom-- от какого количества узлов
Таким образом, теперь узлы должны поддерживать расширенный протокол (совместимый с предыдущей версией):
-
HTTP
GET /v0/entity?id=<ID>[&replicas=ack/from]-- получить данные по ключу<ID>. Возвращает:200 OKи данные, если ответили хотя быackизfromреплик404 Not Found, если ни одна изackреплик, вернувших ответ, не содержит данные (либо данные удалены хотя бы на одной изackответивших реплик)504 Not Enough Replicas, если не получили200/404отackреплик из всего множестваfromреплик
-
HTTP
PUT /v0/entity?id=<ID>[&replicas=ack/from]-- создать/перезаписать (upsert) данные по ключу<ID>. Возвращает:201 Created, если хотя быackизfromреплик подтвердили операцию504 Not Enough Replicas, если не набралосьackподтверждений из всего множестваfromреплик
-
HTTP
DELETE /v0/entity?id=<ID>[&replicas=ack/from]-- удалить данные по ключу<ID>. Возвращает:202 Accepted, если хотя быackизfromреплик подтвердили операцию504 Not Enough Replicas, если не набралосьackподтверждений из всего множестваfromреплик
Если параметр replicas не указан, то в качестве ack используется значение по умолчанию, равное кворуму от количества узлов в кластере,
а from равен общему количеству узлов в кластере, например:
1/1для кластера из одного узла2/2для кластера из двух узлов2/3для кластера из трёх узлов3/4для кластера из четырёх узлов3/5для кластера из пяти узлов
Выбор узлов-реплик (множества from) для каждого <ID> является детерминированным:
- Множество узлов-реплик для фиксированного ID и меньшего значения
fromявляется строгим подмножеством для большего значенияfrom - При
PUTне сохраняется больше копий данных, чем указано вfrom(т.е. не стоит писать лишние копии данных на все реплики)
Фактически, с помощью параметра replicas клиент выбирает, сколько копий данных он хочет хранить, а также
уровень консистентности при выполнении последовательности операций для одного ID.
Таким образом, обеспечиваются следующие примеры инвариантов (список не исчерпывающий):
GETс1/2всегда вернёт данные, сохранённые с помощьюPUTс2/2(даже при недоступности одной реплики приGET)GETс2/3всегда вернёт данные, сохранённые с помощьюPUTс2/3(даже при недоступности одной реплики приGET)GETс1/2"увидит" результатDELETEс2/2(даже при недоступности одной реплики приGET)GETс2/3"увидит" результатDELETEс2/3(даже при недоступности одной реплики приGET)GETс1/2может не "увидеть" результатPUTс1/2GETс1/3может не "увидеть" результатPUTс2/3GETс1/2может вернуть данные несмотря на предшествующийDELETEс1/2GETс1/3может вернуть данные несмотря на предшествующийDELETEс2/3GETсackравнымquorum(from)"увидит" результатPUT/DELETEсackравнымquorum(from)даже при недоступности <quorum(from)реплик
Присылайте pull request со своей реализацией поддержки кластерной конфигурации на review. Не забудьте нагрузить, отпрофилировать и проанализировать результаты профилирования под нагрузкой. С учётом репликации набор тестов расширяется, поэтому не забывайте подмёрдживать upstream.
Переключаем внутреннее взаимодействие узлов на асинхронный java.net.http.HttpClient.
Параллельно отправляем запросы репликам и собираем подтверждения на CompletableFuture.
Проведите нагрузочное тестирование с помощью wrk в несколько соединений.
Отпрофилируйте приложение (CPU, alloc и lock) под нагрузкой с помощью async-profiler и сравните результаты latency и профилирования по сравнению с неасинхронной версией.
Присылайте pull request со своей реализацией на review.
Освоим Яндекс.Танк.
Пишем простой генератор патронов:
- Лента с
PUTами с уникальными ключами - Лента с
PUTами с частичной перезаписью ключей (вероятность 10%) - Лента с
GETами существующих ключей с равномерным распределением (стреляем по наполненной БД) - То же самое, но со смещением распределения
GETов к недавно добавленным ключам (частый случай на практике) - Наконец, лента со смешанной нагрузкой с 50%
PUTы новых ключей и 50%GETы существующих ключей (равномерное распределение)
Генерируем патроны для стрельбы не меньше 5 мин (не забываем про JIT и прогрев JVM процесса).
Логинимся и настраиваем клиент.
Не забываем получить и вписать свой токен для overload, а также указать свой IP машины в load.yaml, чтобы танк смог получить доступ к API.
Возможно, потребуется отключить логгирование входящих запросов на нодах, чтобы выжать из кластера максимум.
Перезапускаем кластер из трёх нод с помощью ./gradlew run перед каждой стрельбой.
Обстреливаем разными лентами на плавно возрастающей линейной нагрузке, чтобы найти точку разладки.
После этого стреляем разными лентами постоянной нагрузкой (line + const) на 30% ниже точки разладки, чтобы определить стабильную latency системы.
Присылайте PR с исходниками генератора патронов, подробным отчётом с описанием предельной пропускной способности системы и гистограммы времён ответа на стабильной нагрузке для каждого типа ленты патронов и анализом результатов.
Индивидуальные фичи, которые позволяют получить дополнительные баллы (10-30):
- Expire: возможность указания времени жизни записей
- Hints: сохранение модификаций для недоступных нод (hints) и доставка hints, как только нода станет доступной
- Server-side processing: трансформация данных с помощью скрипта (например, на JavaScript), запускаемого на узлах кластера через API
- Read-repair: починка данных на нодах, по какой-то причине пропустивших модификации и отдающих устаревшее значение
Background Compaction: автоматический запуск compaction по мере накопленияSSTables- Write-Ahead Log (WAL): запись модификаций в лог перед ответом клиенту, ротация WAL по мере flush и их проигрывание после рестарта
- Нагрузочное тестирование при помощи Y!CSB
Распределённые range запросы: streaming и объединение данных со всех нод кластера без OutOfMemory- Предложите что-то своё
Одна бонусная фича на одного человека. Если хотите реализовать какую-то фичу, подумайте, как именно, и согласуйте с преподавателем.
Реализация любой фичи подразумевает написание дополнительных модульных тестов, демонстрирующих корректность реализации.