Files
hse-2026/homework/04-broadcast/readme.md
T
2026-10-03 11:42:32 +03:00

27 KiB
Raw Blame History

Надежная упорядоченная рассылка

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

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

Процессы взаимодействуют путем обмена сообщениями по сети. Изначально вам доступна только отправка сообщения одному процессу (unicast), поверх чего вам надо реализовать рассылку сообщения всем процессам (broadcast).

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

Вам предоставлен надежный транспорт: каждое сетевое сообщение между корректными процессами в конце концов будет получено ровно один раз (exactly once). Транспорт не создаёт и не дублирует сообщения, но порядок их получения не гарантируется. Однако если отправитель упал до получения сообщения адресатом, доставка этого сообщения уже не гарантируется, даже если оно было отправлено задолго до отказа. Обеспечивать надежность отдельных сетевых передач в решении не нужно. При этом ваш алгоритм рассылки может использовать собственные подтверждения и другие служебные сообщения.

Различайте три события: пользователь отправляет сообщение своему процессу (SEND), процесс получает сетевое сообщение от другого процесса, процесс доставляет сообщение своему пользователю (DELIVER). Получение по сети само по себе не является доставкой пользователю. В свойствах ниже под доставкой понимается именно DELIVER.

Ваша реализация рассылки должна удовлетворять следующим свойствам (см. лекцию 4):

  1. No Duplication: Сообщения доставляются пользователю не более одного раза (нет повторов).
  2. No Creation: Если пользователю доставлено сообщение m от пользователя u, то m было ранее отправлено u.
  3. Validity: Если пользователь корректного процесса n отправил сообщение m, то n должен в конце концов доставить m пользователю.
  4. Uniform Agreement: Если сообщение m было доставлено некоторым (необязательно корректным) процессом, то m будет в конце концов доставлено каждым корректным процессом.
  5. Causal Order: Сообщения доставляются с сохранением причинного порядка - если процесс n доставил сообщение m от пользователя u, то перед этим n должен доставить все сообщения, которые могли повлиять на m (сообщения, отправленные или доставленные u до отправки им m).

Дополнительная часть задания на 9-10 баллов посвящена масштабируемой рассылке.

Реализация

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

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

При инициализации процессу передается его уникальный id (он же является id локального пользователя), а также список id всех процессов в системе. Отправка сообщения пользователем реализована с помощью локального сообщения SEND со строковым полем text. Все пользовательские сообщения в пределах одного выполнения системы имеют разные значения text. Для доставки сообщения пользователю используйте локальное сообщение DELIVER с исходным значением text (см. заготовку). Для взаимодействия между процессами вы можете использовать любые собственные типы сообщений.

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

Дополнительная часть: масштабируемая рассылка

Реализуйте отдельный вариант рассылки из лекции 4: например, gossip, рассылку по дереву или flooding по разреженному графу. Для дерева обязателен механизм восстановления доставки при отказе посредника. Можно предложить другую сопоставимую схему. Новизна идеи не требуется. Экономия памяти или уменьшение константы без изменения схемы распространения сами по себе эту часть не заменяют.

Сдайте дополнительный вариант в solution/broadcast_scalable.py: класс BroadcastProcess с теми же аргументами конструктора и сообщениями SEND/DELIVER, что и в основном решении. Основное решение остаётся в solution/broadcast.py, отчёт для обеих версий - в solution/readme.md.

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

Первый дополнительный балл даётся за работающий масштабируемый протокол с таким описанием. Второй - за воспроизводимое сравнение с основным решением на предоставленном тесте OPTIMIZATION: приведите команды и параметры запуска, таблицу или график результатов и объясните, как рост системы, отказы и задержки влияют на затраты и доставку. Сокращение трафика рассматривайте вместе с охватом: меньше отправленных сообщений за счёт недоставленных сообщений ещё не означает улучшение. Единого порога выигрыша в процентах нет.

Оценивание

Компонент Баллы
Основная реализация удовлетворяет свойствам 1–4 5
Дополнительно обеспечен Causal Order до +2
В отчёте обосновано достижение заявленных свойств +1
Реализован масштабируемый вариант рассылки +1
Проведено экспериментальное сравнение и объяснены компромиссы +1

За Causal Order начисляются 2 балла при прохождении всех тестов. Если нарушений не найдено, но MODEL CHECKING CAUSAL ORDER не завершён из-за ресурсного лимита (INCOMPLETE), начисляется 1 балл из 2. Обнаруженное нарушение Causal Order в любом тесте означает 0 баллов за этот компонент. Эти баллы доступны при выполнении свойств 1–4.

Отчёт с описанием вашего решения в solution/readme.md обязателен, см. общие правила. Для балла за обоснование отдельно объясните в отчёте, почему алгоритм обеспечивает каждое заявленное свойство. Строгих доказательств не требуется, достаточно понятного и убедительного объяснения, связанного с вашим кодом и учитывающего отказы процессов.

Полное основное решение с обоснованием получает 8 баллов. Баллы за дополнительную часть доступны только после получения всех 8 основных; балл за эксперимент требует реализации масштабируемого варианта. Автоматический SCORE равен 0, 5, 6 или 7 и учитывает только основную реализацию. Остальные баллы выставляются по коду и отчёту.

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

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

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

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

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

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

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

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

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

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

Тестер выводит пояснения к сценариям, найденные нарушения и финальную сводку. Подробную трассу событий и истории SEND/DELIVER можно включить флагом -d, например: distsys-broadcast -t "TWO CRASHES" -d. Все доступные опции показаны в справке -h. Основные тесты по умолчанию запускаются на системе из пяти процессов; это число можно изменить опцией -p. При тестировании в системе число прогонов CHAOS MONKEY увеличено до 100 (опция -m 100).

Если найдено нарушение свойств 1–4 или превышен ресурсный лимит до причинного model checking, основной результат уже равен нулю: тестер прекращает проверку и помечает оставшиеся тесты как SKIPPED. При нарушении только Causal Order тестер продолжает проверять остальные свойства, а причинный MC пропускает. Опция --keep-going позволяет собрать больше ошибок, в том числе запустить причинный MC после найденного нарушения. Превышение лимита или аварийное завершение процесса останавливает проверку и с этой опцией.

Обычный тест ограничен 15 секундами, вся серия CHAOS MONKEY - 30 секундами, SCALABILITY с системами до 50 процессов - 60 секундами. На основные тесты без OPTIMIZATION отводится суммарно 300 секунд. Общий лимит запуска в Docker - 420 секунд. Лимиты защищают от бесконечных обработчиков, таймеров и обмена сообщениями.

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

В CHAOS MONKEY перед каждым прогоном указаны два процесса, которые позднее упадут: потери исходящих сообщений включаются только для них. Для повторения прогона с номером k используйте указанный базовый seed через -s и запустите первые k прогонов через -m k.

В тесте TWO CRASHES процессы 0 и 1 могут общаться друг с другом, но изолированы от остальных; связь внутри оставшейся группы сохранена. Процесс 0 начинает рассылку, затем 0 и 1 падают. Если никто не доставил сообщение пользователю, свойства не нарушены: отправитель отказал, а предпосылка Uniform Agreement не выполнена. Если же 0 или 1 доставил сообщение, все корректные процессы тоже должны его доставить. Поэтому ошибка Uniform Agreement показывает обе стороны нарушения: кто уже доставил сообщение и какие корректные процессы его не доставили.

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

Тесты MODEL CHECKING ... используют три процесса независимо от -p. В MODEL CHECKING CAUSAL ORDER два пользователя последовательно отправляют по одному сообщению. Тест перебирает возможные порядки доставки сообщений и останавливается, если при этом требуется проверить больше 100 000 состояний системы. Этот лимит общий для обоих этапов теста. На каждый этап поиска отводится до 55 секунд, на весь тест - до 60 секунд. Превышение одного из этих пределов помечается INCOMPLETE: причинная проверка не завершилась, но контрпример не найден. Если предыдущие тесты прошли, автоматический результат равен 6 баллам: 5 за свойства 1–4 и 1 из 2 за Causal Order.

Частая причина INCOMPLETE - избыточный сетевой обмен: model checking перебирает разные порядки сообщений, и число состояний быстро растёт. Проверьте, не повторяет ли инициатор свою первоначальную рассылку после получения сетевой копии и не вызывает ли каждая новая копия сообщения повторную рассылку без новой информации. Сократите лишний обмен, сохранив требуемые свойства рассылки. Для ориентира: известные нам решения проходят этот тест за несколько десятков секунд или быстрее.

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

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

Сравнение масштабируемого варианта

При полном запуске тестер автоматически выполняет OPTIMIZATION, если основной результат равен SCORE: 7 и рядом с основным файлом есть broadcast_scalable.py. На сравнение обеих версий отводится общий бюджет 90 секунд. Результаты сохраняются в журнале тестирующей системы. Ошибка или превышение лимита в измерениях не меняет основной SCORE. Для отдельного запуска используйте:

distsys-broadcast -t OPTIMIZATION
docker run --pull always --rm -t -v ./solution:/solution distsys.ru/course/broadcast:latest -t OPTIMIZATION

Основной файл можно указать через -i, дополнительный - через --optimized-impl. Тест сравнивает версии на одинаковой нагрузке при разных размерах системы: без отказов, с отказами и с большими сетевыми задержками. Рядом выводятся число и объём сетевых сообщений, включая служебные, максимальное число отправок одним процессом, охват корректных процессов, задержки и ошибки доставки.

Наблюдение ограничено по модельному времени, поэтому периодические таймеры допустимы. При сравнении задержек и сетевых затрат учитывайте долю доставленных сообщений: быстрая доставка части сообщений с небольшими затратами может сопровождаться недоставкой остальных. При этом сообщения, не доставленные за время теста, могут быть доставлены позже — тест этого не проверяет. Превышение лимитов помечается как INCOMPLETE, невыполненные измерения - NOT MEASURED.

Параметры сценариев и расчёт метрик можно изучить в коде теста, опции запуска - в справке -h. Результаты используются для анализа в отчёте; автоматического порога для получения дополнительных баллов нет.

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

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