Домашние задание курса "Распределенные вычисления" 2026 года
Форкните проект, склонируйте и добавьте upstream:
$ git clone git@github.com:<username>/2026-home-work.git
Cloning into '2026-home-work'...
...
$ git remote add upstream git@github.com:vk-edu-distrib-compute/2026-home-work.git
$ git fetch upstream
From github.com:vk-edu-distrib-compute/2026-home-work
* [new branch] master -> upstream/master
Так можно запустить тесты:
$ ./gradlew check
А вот так -- сервер:
$ ./gradlew run
$ ./gradlew codeStyleChecks
Откройте в IDE -- OpenIDE нам будет достаточно.
ВНИМАНИЕ! При запуске тестов или сервера в IDE необходимо передавать Java опцию -Xmx128m.
В своём Java package company.vk.edu.distrib.compute.<username> реализуйте интерфейс KVService и поддержите следующий HTTP REST API протокол:
- HTTP
GET /v0/status--200в нормальной ситуации,503в случае проблем. - 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.
- Сделать наследника
KVServiceFactoryв пакете со своим именем/ником. - Ваша реализация интерфейса
KVService, возвращаемая изKVServiceFactory, должна запускать HttpServer из JDK. - Ваш
KVServiceдолжен работать с вашей же реализацией интерфейсаDaoи делегировать непосредственную работу с данными хранилища. - В минимальной реализации
Daoдостаточно хранить данные в памяти. Для первого этапаTвDaoбудетbyte[]. - Добавить своего наследника
KVServiceFactoryв полеKVServiceFactoryArgumentsProvider.factories
Продолжайте запускать тесты и исправлять ошибки, не забывая подтягивать новые тесты и фиксы из upstream.
Если заметите ошибку в upstream, заводите баг и присылайте pull request ;)
Когда всё будет готово, присылайте pull request со своей реализацией на review. Не забывайте отвечать на комментарии в PR и исправлять замечания!
Cделать Dao, хранящие данные на файловой системе. Если сделали так, напишите коммент об этом к своему PR'у.
Проведите нагрузочное тестирование с помощью wrk в одно соединение:
PUTзапросами на стабильной нагрузке (wrkдолжен обеспечивать заданный с помощью-Rrate запросов) наполните базуGETзапросами на стабильной нагрузке по наполненной БД
- wrk2 можно собрать из исходников, взять в вашем дистре линукса или взять готовый докер, например тут
- Для докера инструкция запуска wrk есть прямо по ссылке, помните, что в случае докера надо указать IP вашего сетевого интерфейса созданного докером (обычно это
docker0), а не просто localhost - Запускайте со след. параметрами
-t1 -c1 -R200 -d30s --latency -s /data/request.lua - Сохраните выведенную статистику (начинается после строчки Detailed Percentile spectrum:) в файл и отобразите здесь
- Скрины графиков (PUT, GET, кнопка Export Image на сайте) надо приложить к вашему PR'у
Реализуем горизонтальное масштабирование через поддержку кластерных конфигураций, состоящих из нескольких узлов, взаимодействующих друг с другом через реализованный HTTP API.
Так можно запустить тесты:
./gradlew check
Запустить сервер в режиме кластера:
./gradlew run --args="cluster"
/gradlew codeStyleChecks
-
Кластер распределяет ключи между узлами детерминированным образом.
-
В кластере хранится только одна копия данных.
-
Нода, получившая запрос, проксирует его на узел, отвечающий за обслуживание соответствующего ключа.
-
Таким образом, общая ёмкость кластера равна суммарной ёмкости входящих в него узлов.
-
Реализуйте один из алгоритмов распределения данных между узлами, например, consistent hashing, rendezvous hashing.
-
Используйте свою реализацию KVCluster и KVClusterFactory
-
В качестве хранилища можете либо переиспользовать созданные в первом задании, либо добавить реализацию dao с использованием h2
Когда всё будет готово, присылайте pull request со своей реализацией на review. Не забывайте отвечать на комментарии в PR и исправлять замечания!
Реализуйте ещё один алгоритм распределения данных. Например, если в основном задании сделали через rendezvous hashing, сделайте через consistent hashing и наоборот.
- Провести нагрузочное тестирование с помощью wrk2 на распределенный кластер с большим количеством соединений >= 64
- Запускайте со след. параметрами
-t2 -c100 -R200 -d30s --latency -s /data/request.lua - Сравнить с предыдущей (монолитной/не распределенной) версией.
- Результаты и анализ сравнения приложить к PR.
- В сервис
KVServiceилиKVClusterдобавить поддержку хранения нескольких копий данных для каждого ключа. - Можно реализовать существующий интерфейс
ReplicatedService. - Фактор репликации
n(количество узлов-реплик) настраивается при запуске сервиса (через CLI-аргумент, ENV-переменную или конфиг-файл). - В качестве узлов хранения допустимо использовать файлы на диске (по одному файлу на реплику/узел) или массив, где индекс -- это номер узла, а значение -- "узел" в виде хранилища в памяти.
- Добавить query-параметр
ackк эндпоинтам чтения/записи. Он определяет, какое число реплик должно успешно подтвердить операцию. - Обеспечить полную обратную совместимость с предыдущей версией API (запросы без
ackдолжны обрабатываться корректно, с дефолтным поведением, напримерack = 1).
| Метод | Поведение |
|---|---|
GET /v0/entity?id=ID&ack=X |
Возвращать данные, если подтвердили ответ ≥ ack реплик. Если доступно меньше – возвращать 50x. |
PUT /v0/entity?id=ID&ack=X DELETE /v0/entity?id=ID&ack=X |
Ждать подтверждений от ≥ ack реплик. При недоступности нужного числа – возвращать 50x. |
| Валидация | Если ack > n, немедленно возвращать 400 Bad Request. |
Если сервис реализует интерфейс ReplicatedService, то основные сценарии можно проверить, запустив интеграционный тест
ReplicationTest, который совместим с этим интерфейсом. Допускается написание собственного набора тестов.
- Множество узлов-реплик для конкретного ключа должно определяться детерминировано.
- Значения по одному и тому же ключу всегда должны попадать на одни и те же реплики.
- Реализовать корректную синхронизацию данных между репликами при записи/удалении.
- Обеспечить graceful degradation: система должна корректно обрабатывать запросы при частичной недоступности реплик, соблюдая заданный уровень ack.
С помощью утилиты wrk провести нагрузочное тестирование и сравнить два сценария:
- Режим с кворумом: ack_w + ack_r > n
- Режим без кворума: ack_w + ack_r ≤ n Метрики для сравнения:
- Время выполнения запросов (p50, p95, p99, RPS)
- Процент актуальности/консистентности данных при чтении (например, доля запросов, вернувших последнюю записанную версию) Когда всё будет готово, присылайте pull request со своей реализацией на review. Не забывайте отвечать на комментарии в PR и исправлять замечания!
- Параллельный I/O: Реализовать асинхронное/параллельное чтение и запись в файлы-реплики.
- Сбор статистики: Добавить вспомогательные эндпоинты:
GET /stats/replica/{id}→ количество ключей/данных в конкретной репликеGET /stats/replica/{id}/access→ частота обращений на чтение и запись
Допускается хранение статистики в памяти с периодическим сбросом или без него, если не указано иное.
Цель - переделать транспорт общения между нодами кластера на gRPC. Переделать нужно только внутренний транспорт, для общения с внешними клиентами транспорт должен остаться прежний - HTTP.
- Переделать ваши реализации
KVServiceтак, чтобы для проксирования запросов в другие ноды использовался gRPC. - Адреса нод кластера, которые возвращаются из
KVCluster.getEndpoints, должны содержать информацию о порте, на котором запущен gRPC-сервер ноды. Пример:localhost:8080?grpcPort=9090. Это только пример, сделать можно как вам кажется лучше-удобнее. Так как это внутренние адреса - то внешним клиентам кластера понимать несущественные для них параметры не обязательно. - В остальном ничего измениться не должно, все интеграционные тесты должны работать как раньше.
- Нужно разработать gRPC-апи в виде proto-файла способное передавать всю нужную информацию между нодами.
- Серверные заглушки и gRPC-клиент будут сгенерированы автоматически на основе вашего proto-файла.
- Не забудьте в вашем proto-файле указать своё имя пакета (сгенерированные файлы будут его использовать), чтобы не мешать другим участникам проекта.
Каких-то особенных тестов пока не предполагается, старые должны все работать
С помощью утилиты wrk провести нагрузочное тестирование до переделки на HTTP и после и сравнить результаты:
- На графике должно быть две линии - до и после
- График должен отображать время выполнения запросов (p50, p95, p99, RPS)
- Основное задание - 8 балов
- Бонусное - 2 бала