Files
hse-2026/homework/06-membership/readme.md
T
2026-10-18 22:39:08 +03:00

26 KiB
Raw Blame History

Group Membership

В этом задании вам предстоит реализовать распределённый сервис для отслеживания состава группы. Он должен позволять процессам вступать в группу, покидать её и узнавать её текущий состав.

В системе N узлов, на каждом из которых запущен процесс с уникальным идентификатором id. Процессы вступают в группу по команде JOIN и покидают её по команде LEAVE. Сам по себе запуск процесса не делает его участником группы.

Помимо добровольного выхода, состав группы может меняться из-за отказов. В нашей системе возможны:

  • Падение процесса или всего узла. Процесс прекращает выполнение и теряет состояние в памяти. Позднее он может быть перезапущен с тем же id и чистым состоянием. Это модель crash-recovery из лекции 6. Работающий процесс будем называть живым.
  • Потери сообщений между некоторыми парами узлов, в обоих или только в одном направлении. Живые процессы могут оказаться полностью или частично изолированы от остальных. Сетевые сбои могут быть временными, после чего связь восстанавливается.

Для обнаружения недоступных участников вам потребуется детектор отказов (см. лекцию 6). Он сообщает о подозрении: отсутствие ответа может быть вызвано как падением процесса, так и проблемами сети. От детектора требуется полнота: падение участника должно в конечном счёте обнаруживаться всеми оставшимися участниками. Подберите баланс между скоростью обнаружения, числом ложных подозрений и расходами на обмен сообщениями.

Списки участников на разных процессах могут временно различаться после входа, выхода, отказа или восстановления. Требуется согласованность в конечном счёте: когда условия работы перестают меняться, списки должны стабилизироваться. Каждый участник должен видеть в своём списке себя и всех живых участников, с которыми возможен обмен сообщениями в обе стороны, в том числе через других участников.

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

Если сеть распалась на подгруппы, между которыми нет двусторонней связи, происходит разделение сети. В этом случае списки должны совпадать внутри каждой подгруппы, включая меньшую, и содержать всех её участников, без процессов из других подгрупп. В списке участников изолированного процесса должен остаться только он сам. Односторонней связи между подгруппами недостаточно для сохранения общей группы.

После перезапуска процесс снова вступает в группу по команде JOIN, и остальные участники должны вернуть его в свои списки. При восстановлении связи с временно недоступным живым участником они должны сделать это автоматически, без новой команды JOIN. Это нужно учитывать и при использовании идей SWIM из лекции: исключение из локального списка не должно навсегда препятствовать возвращению участника.

Чтобы получить максимальный балл, нужно также обеспечить масштабируемость решения. При увеличении числа узлов в X раз сетевой трафик и число сообщений в единицу времени должны увеличиваться не более чем в 2X раз. Нагрузка на процессы измеряется суммой отправленных и полученных сообщений; отношение максимальной нагрузки к минимальной не должно превышать 5.

Подробные сценарии проверок приведены в тестах — они являются частью условия.

Реализация

Для реализации и тестирования решения используется фреймворк AnySystem (см. материалы первого семинара). В папке solution размещена заготовка для решения member.py. Вам надо доработать реализацию процесса в классе GroupMember так, чтобы проходили все тесты.

Из библиотек разрешены только AnySystem и стандартная библиотека Python. Можно добавлять собственные вспомогательные модули. Сжимать сетевые сообщения запрещено, в том числе с помощью zlib, gzip, bz2, lzma или собственного алгоритма сжатия. Снижайте сетевые расходы за счёт устройства протокола: например, передавайте только изменения состава группы вместо полного списка.

Ваши реализации процессов AnySystem должны соблюдать правило изоляции процессов: изменяемое состояние каждого процесса должно храниться только в self, а общая изменяемая память между процессами запрещена. Все взаимодействия между процессами должны происходить через сообщения, как и в реальной распределённой системе.

При инициализации процессу передается его уникальный id. Процесс должен поддерживать обработку следующих локальных сообщений:

  • JOIN - команда присоединиться к группе. В результате обработки этой команды процесс должен стать участником группы. В поле seed передается идентификатор одного из живых участников группы. Если в seed находится идентификатор самого процесса, то он должен создать новую пустую группу и добавить в неё себя. Ответ на это сообщение не нужен.
  • LEAVE - команда покинуть группу. В результате обработки этой команды процесс должен перестать быть участником группы. Ответ на это сообщение не нужен.
  • GET_MEMBERS - запрос локального списка участников группы. Процесс должен сразу ответить одним локальным сообщением MEMBERS с хранимым списком идентификаторов (см. заготовку). Обращаться к другим процессам или ждать таймера для ответа нельзя. Эта семантика проверяется отдельным тестом.

Для взаимодействия между процессами вы можете использовать любые собственные типы сообщений.

Важно! Для измерения прошедшего времени в коде процесса используйте метод ctx.time(), возвращающий локальное время "внутри симуляции". Использовать обычные способы, например time.time(), некорректно, так как скорость течения времени в симуляции отличается от реального времени. Также не стоит опираться на синхронизацию часов в системе - у каждого узла свои локальные часы, и показания ctx.time() у разных процессов могут расходиться. После перезапуска узла смещение его часов может измениться.

В коде процесса можно использовать генератор случайных чисел из random, но не следует его явно инициализировать с помощью random.seed(). Это уже делается в тестах на основе переданного random seed, а дополнительная инициализация может нарушить воспроизводимость результатов.

Тестирование

Локальное тестирование

Тесты находятся в папке tests. Есть два варианта их запуска.

Рекомендуемый вариант запуска тестов - через готовый Docker-образ. В этом случае используемое окружение будет аналогично тестирующей системе. Для запуска тестов выполните команду:

docker run --pull always --rm -t -v ./solution:/solution distsys.ru/course/membership:latest [ЗДЕСЬ МОЖНО УКАЗАТЬ ОПЦИИ]

Для запуска полного набора тестов с теми же параметрами, что и в тестирующей системе, выполните команду:

docker run --pull always --rm -t -v ./solution:/solution distsys.ru/course/membership:latest -m 1000

Вы также можете собрать и запустить тесты непосредственно в Linux с помощью компилятора Rust. На Windows можно использовать WSL или Docker, на macOS — Docker. Сборка из исходников может быть удобна, если вы хотите лучше изучить или доработать тесты. Скомпилируйте тесты с помощью команды cargo install --locked --path tests. Для запуска тестов выполните команду:

distsys-membership [ЗДЕСЬ МОЖНО УКАЗАТЬ ОПЦИИ]

Запустить один тест можно с помощью опции -t (например, ... -t "PROCESS CRASH"). По умолчанию тестер выводит результаты проверок, диагностику долгих запусков и итоговую сводку. Флаг -d добавляет трассу событий, а -h показывает все доступные опции. При отдельном запуске теста масштабируемости через -t предварительное прохождение функциональных тестов не требуется.

Что проверяют тесты

Сначала выполняются обычные функциональные тесты, затем CHAOS MONKEY, MODEL CHECKING и тесты масштабируемости.

На каждом этапе обычных тестов списки участников должны сойтись к ожидаемому составу за 300 секунд симуляции. В тестах изоляции проверяются и оставшаяся группа, и изолированный участник: в его списке должен остаться только он сам.

Тесты NETWORK PARTITION UNEQUAL и NETWORK PARTITION UNEQUAL RECOVER разделяют сеть на две непустые подгруппы разного размера: 4 и 6 при десяти процессах. Состав группы должен стабилизироваться в обеих подгруппах, включая меньшую. Второй тест затем восстанавливает связь и проверяет, что все процессы снова видят общую группу.

Тесты TWO PROCESSES CANNOT COMMUNICATE и TWO PROCESSES CANNOT COMMUNICATE IN TRIANGLE отключают связь между парой узлов, сохраняя связь через остальных участников. Во втором тесте всегда три узла. Все участники должны остаться в группе.

В этих двух тестах, а также после восстановления в PROCESS CRASH RECOVER, PROCESS OFFLINE RECOVER, NETWORK PARTITION RECOVER и NETWORK PARTITION UNEQUAL RECOVER проверяется непрерывная стабильность состава группы. За 300 секунд симуляции должен найтись интервал длительностью 30 секунд, в котором списки участников остаются корректными. Внутри этого интервала списки проверяются после каждого события. Переходные ошибки до стабилизации допускаются.

CHAOS MONKEY проверяет последовательности отказов. По умолчанию выполняются 100 прогонов, а в тестирующей системе — 1000 (-m 1000). Каждый прогон включает до шести шагов с отказами; при стандартных десяти процессах в проверяемой группе может остаться четыре участника.

Для отладки CHAOS MONKEY используйте флаг -v (--verbose): он выводит параметры запуска, порядок входа процессов и отказы с номерами процессов и временем в симуляции. Этот вывод также включается с -d. Чтобы повторить запуск, укажите то же значение -s (--seed) и то же число процессов -p, а в -m — число не меньше номера нужного прогона. Например, ... -t "CHAOS MONKEY (run 850)" -m 1000 -s 123 -p 10 -v. В тестах выбор процессов воспроизводим при одинаковых параметрах; случайность внутри вашего решения также должна быть воспроизводимой.

MODEL CHECKING (MC) проверяет сборку группы из трёх процессов без отказов и ответы на GET_MEMBERS при разных порядках доставки сообщений. Полный состав ожидается в состояниях, где каждый процесс получил не менее трёх сетевых сообщений или больше нет событий. В тестирующей системе MC включён; для быстрой локальной проверки его можно отключить флагом --disable-mc-tests.

Тесты SCALABILITY NORMAL и SCALABILITY CRASH измеряют расходы на поддержание группы. В первом тесте измерение длится 10 секунд симуляции после сборки группы, во втором — от падения одного процесса до стабилизации состава. Расходы на первоначальную сборку группы не учитываются; во втором тесте равномерность нагрузки проверяется среди оставшихся живых процессов.

При стандартных параметрах оба теста масштабируемости запускаются для групп из 10, 20, 50 и 100 узлов. Сетевой трафик и число сообщений в единицу времени сравниваются между последовательными запусками: 10 → 20, 20 → 50 и 50 → 100. По правилу 2X допустимый рост расходов составляет соответственно 4, 5 и 4 раза.

Лимиты и диагностика

Тесты выполняются в отдельных процессах; прогоны CHAOS MONKEY объединены в серию с сохранением результата каждого прогона. При зависании, исчерпании памяти или аварийном завершении исполнителя проверка продолжается, пока остаётся общий бюджет времени. Баллы за пройденные функциональные компоненты и сетевые подгруппы сохраняются. Непроверенные тесты отмечаются в сводке и не считаются пройденными.

По умолчанию и в тестирующей системе действуют следующие ограничения реального времени. Все проверки делят общий бюджет 540 секунд:

Проверка Лимит Опция
Обычный тест или один прогон CHAOS MONKEY 15 секунд --test-timeout
Все прогоны CHAOS MONKEY вместе 300 секунд --chaos-timeout
Обе фазы MODEL CHECKING вместе 300 секунд --mc-timeout
Каждый тест SCALABILITY 60 секунд --scalability-timeout
Полный набор тестов 540 секунд --suite-timeout

Адресное пространство исполнителя ограничено 4 ГиБ для обычных тестов, CHAOS MONKEY и SCALABILITY (--memory-limit 4096) и 14 ГиБ для MODEL CHECKING (--mc-memory-limit 14336). В тестирующей системе эти пределы фиксированы. Окружение должно также иметь запас 256 МиБ для управляющего процесса.

Если памяти окружения недостаточно для заданного предела и запаса для управляющего процесса, тестер завершится с сообщением об ошибке окружения без оценки. Для локального запуска в меньшем объёме памяти можно явно уменьшить соответствующий предел, но результат может отличаться от проверки на сервере.

При остановке тестер сообщает причину и последний этап проверки: например, размер собираемой группы или фазу model checking. Долгие проверки также периодически выводят текущий этап и расход памяти.

Бонусы за пробелы в тестах начисляются по общим правилам.

Проверка в тестирующей системе

Отправьте решение в тестирующую систему по инструкции и дождитесь результатов.

Оценивание

Это задание имеет повышенный вес 1.5, то есть максимум можно набрать 15 баллов.

Базовый функционал, отказы процессов и четыре сетевые подгруппы оцениваются независимо друг от друга. Внутри каждого компонента или подгруппы нужно пройти все перечисленные обычные тесты; исключения для MC описаны ниже. Масштабируемость даёт баллы только при зачёте всей функциональной части.

  • Базовый функционал без отказов — до 4 баллов:
    • 3 балла за прохождение всех пяти тестов: SIMPLE, GET MEMBERS SEMANTICS, RANDOM SEED, PROCESS JOIN, PROCESS LEAVE.
    • Ещё 1 балл, если пройдены и все пять обычных тестов, и MODEL CHECKING. Отключение MC при локальном запуске или остановка уже запущенного MC по лимиту времени (в том числе общего бюджета) или памяти сохраняет первые 3 балла.
    • 0 баллов за весь компонент, если MC обнаружил ошибку, не запустился из-за исчерпания общего бюджета или его исполнитель аварийно завершился с неизвестной причиной.
  • Отказы процессов — 3 балла: PROCESS CRASH, SEED PROCESS CRASH, PROCESS CRASH RECOVER.
  • Сетевые и комбинированные отказы — до 4 баллов, по 1 баллу за каждую подгруппу:
    • Отключения и разделения сети с восстановлением: PROCESS OFFLINE, SEED PROCESS OFFLINE, PROCESS OFFLINE RECOVER, NETWORK PARTITION, NETWORK PARTITION RECOVER, NETWORK PARTITION UNEQUAL, NETWORK PARTITION UNEQUAL RECOVER.
    • Односторонняя и частичная связность: PROCESS CANNOT RECEIVE, PROCESS JOIN CANNOT RECEIVE, PROCESS CANNOT SEND, TWO PROCESSES CANNOT COMMUNICATE, TWO PROCESSES CANNOT COMMUNICATE IN TRIANGLE.
    • Задержки и потери сообщений: SLOW NETWORK, FLAKY NETWORK, FLAKY NETWORK ON START, FLAKY NETWORK AND CRASH.
    • Последовательности отказов: все 1000 прогонов CHAOS MONKEY.
  • Масштабируемость — 4 балла: SCALABILITY NORMAL, SCALABILITY CRASH. Эти тесты запускаются в полном наборе только после зачёта всех функциональных компонентов: базовой группы (не менее 3 баллов), отказов процессов и всех четырёх сетевых подгрупп. Остановка уже запущенного MC по времени или памяти и его отключение при локальном запуске не блокируют масштабируемость. Если функциональные компоненты не зачтены, тесты масштабируемости пропускаются с указанием причины. Для получения 4 баллов нужно пройти оба теста; при исчерпании общего бюджета незапущенные тесты не считаются пройденными.

К решению приложите отчёт в файле solution/readme.md по общим требованиям. Без отчёта автоматические тесты запускаются, но защита не проводится и решение не засчитывается.