Курсовой проект 2024 года курса «Разработка high-load систем» Корпоративной магистерской программы «Распределённые веб-сервисы / Web scale systems».
Форкните проект, склонируйте и добавьте upstream:
$ git clone [email protected]:<username>/2024-highload-dht.git
Cloning into '2024-highload-dht'...
...
$ git remote add upstream [email protected]:polis-vk/2024-highload-dht.git
$ git fetch upstream
From github.com:polis-vk/2024-highload-dht
* [new branch] main -> upstream/main
Так можно запустить тесты:
$ ./gradlew test
А вот так -- сервер:
$ ./gradlew run
Откройте в IDE -- IntelliJ IDEA Community Edition нам будет достаточно.
ВНИМАНИЕ! При запуске тестов или сервера в IDE необходимо передавать Java опцию -Xmx128m.
В своём Java package ru.vk.itmo.test.<username> реализуйте интерфейсы Service и ServiceFactory.Factory и поддержите следующий 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.
Используйте свою реализацию Dao из предыдущего курса 2023-nosql-lsm или референсную реализацию, если своей нет.
Проведите нагрузочное тестирование с помощью wrk2 в одно соединение:
PUTзапросами на стабильной нагрузке (wrk2должен обеспечивать заданный с помощью-Rrate запросов) ниже точки разладкиGETзапросами на стабильной нагрузке по наполненной БД ниже точки разладки
Нагрузочное тестирование и профилирование должны проводиться в одинаковых условиях (при одинаковой нагрузке на CPU). А почему не curl/F5, можно узнать здесь и здесь.
Приложите полученный консольный вывод wrk2 для обоих видов нагрузки.
Отпрофилируйте приложение (CPU и alloc) под PUT и GET нагрузкой с помощью async-profiler.
Приложите SVG-файлы FlameGraph cpu/alloc для PUT/GET нагрузки.
Объясните результаты нагрузочного тестирования и профилирования и приложите текстовый отчёт (в Markdown). Все используемые инструменты были рассмотрены на лекции -- смотрите видео запись.
Продолжайте запускать тесты и исправлять ошибки, не забывая подтягивать новые тесты и фиксы из upstream.
Если заметите ошибку в upstream, заводите баг и присылайте pull request ;)
Когда всё будет готово, присылайте pull request со своей реализацией, результатами профилирования, отчётом с их анализом и проведёнными по результату профилирования оптимизациями на review. На всех этапах оценивается и код, и анализ (отчёт) -- без анализа полученных результатов работа оценивается минимальным количеством баллов. Не забывайте отвечать на комментарии в PR (в том числе автоматизированные) и исправлять замечания!
Этап 2. Асинхронный сервер (soft deadline 2024-02-29 18:29:59 MSK, hard deadline 2024-03-06 23:59:59 MSK)
Вынесите обработку запросов в отдельный ExecutorService с ограниченной очередью, чтобы разгрузить SelectorThreadы HTTP сервера. Подумайте о параметрах ExecutorService (тип и размер очереди, количество потоков, обработка переполнений очереди и ошибок при обработке запросов) -- результаты всех экспериментов опишите в отчёте. Проанализируйте, стало ли лучше, чем раньше?
Проведите нагрузочное тестирование с помощью wrk2 с большим количеством соединений (не меньше 64) PUT и GET запросами.
Отпрофилируйте приложение (CPU, alloc и lock) под PUT и GET нагрузкой с помощью async-profiler.
Когда всё будет готово, присылайте pull request с изменениями, результатами нагрузочного тестирования и профилирования, а также анализом результатов по сравнению с предыдущей (синхронной) версией. На всех этапах оценивается и код, и анализ (отчёт) -- без анализа полученных результатов работа оценивается минимальным количеством баллов. Не забывайте отвечать на комментарии в PR (в том числе автоматизированные) и исправлять замечания!
Реализуем горизонтальное масштабирование через поддержку кластерных конфигураций, состоящих из нескольких узлов, взаимодействующих друг с другом через реализованный HTTP API.
Для этого в ServiceConfig передаётся статическая "топология", представленная в виде множества координат всех узлов кластера в формате http://<host>:<port>.
Кластер распределяет ключи между узлами детерминированным образом. В кластере хранится только одна копия данных. Нода, получившая запрос, проксирует его на узел, отвечающий за обслуживание соответствующего ключа. Таким образом, общая ёмкость кластера равна суммарной ёмкости входящих в него узлов.
Реализуйте один из алгоритмов распределения данных между узлами, например, consistent hashing, rendezvous hashing или что-то другое по согласованию с преподавателем.
Когда всё будет готово, присылайте pull request с изменениями, результатами нагрузочного тестирования и профилирования, а также анализом результатов по сравнению с предыдущей (нераспределённой) версией. На всех этапах оценивается и код, и анализ (отчёт) -- без анализа полученных результатов работа оценивается минимальным количеством баллов. Не забывайте отвечать на комментарии в PR (в том числе автоматизированные) и исправлять замечания! С учётом шардирования набор тестов расширяется, поэтому не забывайте подмёрдживать upstream.
Реализуем поддержку хранения нескольких реплик данных в кластере для обеспечения отказоустойчивости.
HTTP API расширяется query-параметрами from и ack, содержащими количество узлов, которые должны подтвердить операцию, чтобы она считалась выполненной успешно.
ack-- сколько ответов нужно получитьfrom-- от какого количества узлов
Таким образом, теперь узлы должны поддерживать расширенный протокол (совместимый с предыдущей версией):
-
HTTP
GET /v0/entity?id=<ID>[&ack=<ACK>&from=<FROM>]-- получить данные по ключу<ID>. Возвращает:200 OKи данные, если ответили хотя быackизfromреплик404 Not Found, если ни одна изackреплик, вернувших ответ, не содержит данные (либо самая свежая версия средиackответов -- это tombstone)504 Not Enough Replicas, если не получили200/404отackреплик из всего множестваfromреплик
-
HTTP
PUT /v0/entity?id=<ID>[&ack=<ACK>&from=<FROM>]-- создать/перезаписать (upsert) данные по ключу<ID>. Возвращает:201 Created, если хотя быackизfromреплик подтвердили операцию504 Not Enough Replicas, если не набралосьackподтверждений из всего множестваfromреплик
-
HTTP
DELETE /v0/entity?id=<ID>[&ack=<ACK>&from=<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)реплик
Этап 5. Асинхронное взаимодействие (soft deadline 2024-04-11 18:29:59 MSK, hard deadline 2024-04-17 23:59:59 MSK)
Переключаем внутреннее сетевое взаимодействие узлов на асинхронный java.net.http.HttpClient (если ещё нет).
Параллельно отправляем запросы репликам и собираем самые быстрые ответы на CompletableFuture.
Потоки не должны блокироваться совсем (кроме синхронных методов локального DAO).
Проведите нагрузочное тестирование с помощью wrk2 в несколько соединений.
Отпрофилируйте приложение (CPU, alloc и особенно lock) под нагрузкой и сравните latency и результаты профилирования с предыдущей неасинхронной версией.
Когда всё будет готово, присылайте pull request с изменениями, результатами нагрузочного тестирования и профилирования, а также анализом результатов по сравнению с предыдущей (синхронной) версией. На всех этапах оценивается и код, и анализ (отчёт) -- без анализа полученных результатов работа оценивается минимальным количеством баллов. Не забывайте отвечать на комментарии в PR (в том числе автоматизированные) и исправлять замечания!
Этап 6. Range-запросы (soft deadline 2024-04-25 18:29:59 MSK, hard deadline 2024-05-01 23:59:59 MSK)
Реализуйте получение диапазона данных текущего узла с помощью HTTP GET /v0/entities?start=<ID>[&end=<ID>], который возвращает:
- Статус код
200 OK - Возможно пустой отсортированный (по ключу) набор ключей и значений в диапазоне ключей от обязательного
start(включая) до опциональногоend(не включая) - Используйте Chunked transfer encoding
- Чанки в формате
<key>\n<value>
Диапазон должен отдаваться в потоковом режиме без формирования всего ответа в памяти. Проверить корректность можно, запросив весь диапазон данных предварительно наполненной БД размером больше Java Heap.
После прохождения модульных тестов, присылайте pull request с изменениями. Наполните БД большим объёмом данных и отпрофилируйте cpu, alloc и lock при получении range всей базы одним запросом curl'ом. Присылайте отчёт с анализом результатов и оптимизаций.
Фичи, которые позволяют получить дополнительные баллы (при условии добавления набора тестов, демонстрирующих корректность, где применимо):
- Развёрнутая конструктивная обратная связь по курсу: достоинства и недостатки курса, сложность тем, предложения по улучшению
- Кластерные range-запросы с учётом шардирования и репликации
- Read repair при обнаружении расхождений между репликами
- Expire: возможность указания времени жизни записей
- Server-side processing: трансформация данных с помощью скрипта, запускаемого на узлах кластера через API
- Нагрузочное тестирование при помощи Y!CSB
- Нагрузочное тестирование при помощи Yandex.Tank
- Регулярный автоматический фоновый compaction (модульные и нагрузочные тесты)
- Hinted handoff по аналогии с Cassandra
- Устранение неконсистентностей между репликами по аналогии с Cassandra nodetool repair, например, на основе Merkle Tree
- Блочная компрессия данных на основе LZ4/zSTD/...
- Что-нибудь своё?
Перед началом работ продумайте и согласуйте с преподавателем её технический дизайн и получите вспомогательные материалы.