8 мая 2022 г.

Mystery of link imbalance (metastable failure)

Я люблю системы, где из простых элементов возникает какое-то нетривиальное поведение. Последний месяц я игрался с симуляцией одной такой системы из статьи "Metastable Failures in Distributed Systems"1

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

И это продолжалось почти два года, прежде чем коллективными усилиями инженеров разных отделов удалось понять, что именно происходит.

Как оказалось, агрегированный сетевой линк соединял базу данных с приложениями. Приложения использовали пул соединений с БД, причем пул со стратегией MRU – в первую очередь выдаются соединения, которые последними в пул вернули.

А еще приложения периодически выполняли некую задачу, которая порождала единомоментно пачку почти одинаковых мелких запросов в БД.

Эти три элемента: многоканальный сетевой линк, MRU-пул соединений с БД, и периодические всплески нагрузки в виде пачки почти одинаковых мелких запросов – довольно неожиданным образом взаимодействуют между собой.

Когда приложение порождает пачку почти-одинаковых-запросов, эти запросы извлекают из пула сразу много соединений. И назад в пул соединения возвращаются в том порядке, в котором соответствующие запросы завершаются – а время исполнения запроса включает в себя время в БД, плюс сетевая задержка на пути к БД и обратно.

И если время в БД у всех запросов примерно одинаково (или просто мало), то порядок завершения запросов будет определяться сетевой задержкой: последними в пул вернутся те соединения, на которых почему-то сетевая задержка оказалась больше.

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

Например: агрегированный линк состоит из 4-х физических каналов, а в MRU-пуле 20 соединений. Допустим, регулярная нагрузка использует обычно верхние 5 соединений в пуле, а пиковая нагрузка забирает все 20.

При создании пула соответствующие сетевые соединения распределены маршрутизатором по каналам равномерно, поэтому верхние 5 соединений в пуле скорее всего назначены марштутизатором на разные каналы – то есть нагрузка распределяется по разным каналам.

Теперь прилетела пачка одинаковых запросов, после обработки которой соединения в пуле отсортировались по сетевой задержке. Наверху пула оказались 5 соединений, идущих через самый медленный (на этот момент) канал. Теперь почти вся регулярная нарузка будет идти через эти 5 соединений, то есть физически через один канал.

Если регулярная нагрузка составляла 30% от общей пропускной способности агрегированного линка, то теперь, когда вся эта нагрузка упала на один из 4-х каналов, для этого канала это будет >100% от его пропускной способности – то есть канал захлебнется, пойдут потери пакетов.

Именно это и случалось в дата-центре фейсбука4: всплески нагрузки возникают когда пользователь логинится в фейсбук – бекэнд загружает его профиль и социальный граф, и это несколько десятков мелких запросов в БД одномоментно. БД хорошо оптимизирована, запросы выполняет быстро, поэтому сетевая задержка составляет заметную часть общего времени выполнения запроса для клиента. Если прямо сейчас какой-то из каналов агрегированного линка чуть более загружен, чем остальные – то соединения, идущие через этот канал, после обработки пачки запросов окажутся наверху MRU-пула, и будут использоваться для всех остальных регулярных запросов. И временно чуть более загруженный канал теперь окажется стабильно перегруженным.

Мне кажется, это очень красивая динамика: 3 на первый взгляд совершенно независимых элемента – MRU-пул, агрегированный сетевой линк, всплески нагрузки из множества похожих запросов – вместе образуют совершенно неожиданную синергию. Это так красиво, что мне захотелось сделать симуляцию этой системы, чтобы иметь возможность "потрогать ее руками" – поиграться с параметрами, и посмотреть, что и как влияет5.

Как всегда: все не так просто

На бумаге (точнее, в pdf) все выглядело очень просто и логично, и я ожидал, что эффект легко воспроизведется в симуляции – но это оказалось не так.

Например, одна из первых попыток: агрегированный линк из 5 каналов, MRU-пул из 25 соединений, регулярная нагрузка (между всплесками) в 30% от общей пропускной способности агрегированного линка. В какой-то момент единовременно генерируется 25 одинаковых запросов – чтобы гарантированно заиспользовать все соединения в пуле. В соответствии с написанным в статье я ожидал увидеть, как после этой пачки запросов среднее время ожидания обычных, регулярных запросов резко вырастет.

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

Вряд ли это вам удастся, потому потому что эти два сценария друг от друга почти не отличаются. (Для справки: всплеск был в 0.25, на оранжевом графике). То есть link imbalance metastable issue здесь не воспроизводится – хотя, казалось бы, в симуляции со всплеском нагрузки все условия для него, вроде бы, созданы.

У меня ушло изрядно времени, чтобы более-менее разобраться, как все-таки воспроизвести link imbalance issue, и что вообще происходит в этой системе: парочка подсказок нашлась в оригинальной статье инженеров фейсбука, но в основном это стоило мне и моему macbook-у большого количества процессорного времени. Описывать как я разбирался долго, поэтому я лучше расскажу о результатах.

Вкратце: механизм link imbalance issue описанный в обоих статьях – судя по всему, упрощен. Воспроизвести этот сценарий так, как описан его механизм, довольно сложно – можно, но потребуются нетипичные значения параметров. Это не значит, что в статье все наврали: сценарий легко воспроизводится, но немного в других условиях, и ключевой механизм выглядит немного иначе. Вероятно, в статье описание упрощено для наглядности изложения.

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

В статьях ключевым механизмом возникновения link imbalance issue названа эта самая sleep-sort-like сортировка соединений в MRU-пуле: после обработки пачки мелких похожих запросов соединения в пуле оказываются отсортированными по времени выполнения, поэтому наверху пула оказывается группа соединений, связанных с БД через один, наиболее загруженный в данный момент канал, почти все запросы теперь идут через эту группу соединений, т.е. через один-единственный канал, который оказывается перегружен.

Но в симуляции видно, что этого не происходит:

  1. После пачки почти-одинаковых-запросов наверху пула не образуется однородной группы соединений, идущих через самый медленный канал
  2. Зато иногда (и не очень редко) такая группа образуется и без всяких пачек почти одинаковых запросов, просто под действием обычного случайного трафика
  3. В обоих случаях, если такая группа соединений наверху пула образуется – она не живет там долго, ее довольно быстро размывает-перетасовывает регулярный трафик

Теперь подробнее: по описанию в статье, после обработки пачки одинаковых запросов соединения в пуле должны выстроиться (само-отсортироваться) как-то так:

4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1

где 4 самый медленный из каналов на данный момент, а 1 – самый быстрый. В симуляции же получается что-то вроде такой картины:

4 4 2 3 4 2 4 2 ...

Если знать результат, то уже легко понять, почему так получается: если мы посылаем один запрос, то его время выполнения зависит только от скорости работы того сетевого канала, через который ему пришлось лететь (оставим за скобками время исполнения запроса в БД). Но если мы одновременно посылаем много запросов, то они не полетят по сети все одновременно – сетевая подсистема выстроит их в очередь(-и). И время исполнения конкретного запроса зависит уже не только от того, через быстрый или медленный канал его направили, но и от того, на какое место в очереди в этот канал он попал.

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

А теперь в той же ситуации к кассам подходят 20 покупателей. Они так же распределяются по кассам, но теперь уже к каждой кассе образуется очередь из 10 покупателей. Покупатель, который попал первым в очередь в быструю кассу – уйдет первым. Покупатель, который попал последним в очередь в медленную кассу – уйдет последним. Но покупатель, который попал первым в очередь в медленную кассу – скорее всего уйдет все же быстрее, чем тот, что попал 5-м в очередь в быструю кассу.

В итоге, эффект от прохождения пачки почти одинаковых запросов через MRU-пул больше похож на 1-2 итерации сортировки пузырьком, чем на полноценный sort(): наверху пула скорее всего окажется одно из самых медленных соединений (=идущих через самый медленный канал), и в верхней части пула окажется больше медленных соединений, чем в среднем – но никакой строгой отсортированности не получится, более медленные соединения будут стоять вперемешку с более быстрыми.

Это первый неожиданный результат симуляции – несложный, но не очень ожидаемый. Второй несложный, но тоже не вполне очевидный сходу факт – это что пулу со стратегией MRU вообще свойственно неравномерно распределять нагрузку.

Эта неравномерность – свойство самой стратегии MRU, никаких ухищрений вроде специально подобранных всплесков нагрузки для этого не нужно. Ведь MRU-пул пытается как можно больше нагрузки пропустить через минимальное количество most-recently-used соединений. Но чем меньше активных соединений, тем менее вероятно, что они равномерно распределятся по каналам – закон больших чисел работает только для больших чисел. Если наиболее активно используются только 5 most-recently-used соединений наверху пула, то маловероятно, что эти 5 соединений идеальненько распределятся по 5 разным каналам. Вероятнее, что чисто случайно 2-3 соединения будут идти через 1 канал, а значит и нагрузка на разные каналы уже будет отличаться в разы.

Конкретное распределение нагрузки по каналам зависит от того, какие именно соединения попали в небольшое подмножество most-recently-used – а это, во многом, случайность. Поэтому результаты симуляции очень нестабильны: от запуска к запуску результат может отличаться кардинально, присутствует большая run-to-run variance6.

Но и внутри одного запуска эта нестабильность тоже проявляется. Она отлично видна на графиках выше: возможно, вы задавались вопросом, почему графики такие дерганные? Каждая точка на графике это среднее по сотням тысяч запросов – разве время ожидания не должно хорошо усредниться на такой выборке?

Нет, потому что скачки среднего времени ожидания на графике – это не случайные флуктуации трафика, это как раз моменты, когда меняется состав "горячих" most-recently-used соединений. Когда одно соединение выбывает из списка MRU, а взамен туда попадает другое – такая перетасовка может заметно поменять распределение нагрузки по каналам, и, как следствие, среднее время ожидания.

Например: пусть сейчас порядок соединений в пуле такой:

4 4 2 | 3 3 1 ...

и первые 3 соединения, до разделителя – most-recently-used, регулярный трафик в 90% случаев проходит через них. Так как среди них 2 соединения идут через канал 4, и одно через канал 2, то 60% всего трафика идет по каналу 4, и 30% по каналу 2 – т.е. канал 4 довольно сильно загружен, и дает большой вклад в среднее время ожидания запросов.

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

4 3 2 | 4 3 1 ...

т.е. поменялись местами 2-е и 4-е соединения. Теперь 90% трафика, идущие через верхние 3 соединения, делятся ровно по 30% по каналам 4, 3, и 2 – то есть нагрузка распределяется по трем каналам равномерно, и среднее время ожидания сильно упадет.

И такие перетасовки в составе "верхушки" MRU-пула происходят довольно регулярно – просто не все они приводят к существенным изменениям во внешне наблюдаемых параметрах, таких как время ожидания.

Но важно, что никакой конкретный набор соединений не остается наверху MRU-пула навсегда – случайный трафик хоть и медленно, но постоянно перетасовывает MRU-соединения. Если наверху пула образовался какой-то не-типичный набор соединений – не важно, возник ли он случайно, или был создан в ходе обработки пачки почти-одинаковых-запросов – он не проживет там вечно. Случайный трафик через какое-то время перетасует соединения, и верхушка пула потеряет потеряет свою не-типичность7.

Именно поэтому у меня и не получилось сходу воспроизвести эффект: с теми параметрами, что я задал, основной трафик шел через 3-4 верхних соединения в пуле. 3-4 соединения нередко будут идти через один канал по чистой случайности – то есть эффект перегрузки одного из каналов периодически воспроизводится без дополнительных усилий, без специально подготовленных пачек почти-одинаковых-запросов.

С другой стороны, даже если эффект воспроизвелся – он не задерживается надолго, случайный трафик довольно быстро размывает нетипичный набор соединений наверху пула. Мета-стабильного сбоя не получается – получаются только довольно короткие периоды увеличения среднего времени ожидания. Да, их можно стриггерить специально подготовленной пачкой запросов, но время от времени они возникают и сами собой – то есть сценарии с пачками одинаковых запросов и без них сходу и не отличить друг от друга.

В каких же условиях все-таки происходит link imbalance? Есть два сценария, в которых мне удалось его воспроизвести в симуляции: очень глубокий пул, или много независимых клиентов с не очень глубоким пулом каждый.

Очень глубокий пул: если в примере из начала поста вместо пула в 25 соединений взять 100, то примерно в 1 случае из 5 link imbalance будет воспроизводиться. Если взять пул в 200 соединений, то воспроизводиться будет уже 2-3 раза из 5, и начиная с 500-700 соединений воспроизведение близко к 100%.

График ниже построен по результатам симуляции с пулом в 700 соединений. Здесь сложно не заметить момент прихода пачки одинаковых запросов, и так же сложно не заметить, что после него в системе что-то сильно и надолго сломалось – поведение до и после всплеска очень разное, это бросается в глаза. До всплеска нагрузки, до прихода пачки одинаковых запросов время ожидания составляло единицы процентов от чистого времени исполнения запроса, а после него речь уже идет о сотне процентов – запросы проводят больше времени в сетевых буферах, чем в работе.

Почему большой пул что-то меняет? Хотя, как выяснилось, пачка почти одинаковых запросов и не сортирует пул идеально, как вызов sort(), но после нее на верхушке пула все-таки оказываются сколько-то соединений, идущих через один, самый медленный сейчас, канал. И чем больше вообще соединений в пуле, тем больше оказывается это сколько-то. Если соединений в пуле достаточно много, то "сколько-то" может оказаться приличным числом, достаточным, чтобы почти весь трафик пошел в один-единственный канал.

А еще, если наверху пула соберется достаточно много соединений, идущих через один канал, то перетасовываться случайным трафиком они будут достаточно медленно – потому что вероятность задействовать соединения в глубине пула падает экспоненциально (геометрически) с глубиной.

Например, после пачки одинаковых запросов соединения в пуле отсортировались так:

4 4 4 | 4 4 4 3 4 4 2 3 4 2 1 ...

и регулярный трафик задействует обычно верхние 3 соединения. Даже если регулярный трафик перетасует третье и четвертоее соединения – ничего не изменится, весь трафик все равно будет идти через один-единственный канал 4. Нужно перетащить наверх пула хотя бы седьмое соединение, чтобы что-то поменялось в распределении нагрузки.

Но вероятность использовать более глубокие соединения уменьшается в геометрической прогрессии от глубины: вероятность задействовать N-е соединение в пуле примерно пропорциональна $$\rho^N$$, где $$\rho$$ это загрузка (утилизация).

И если загрузка далека от 100%, то эта вероятность быстро падает с ростом N – то есть скорость "перетряхивания" пула случайным трафиком быстро падает с ростом глубины, которую нужно перетряхнуть. Это и придает ситуации мета-стабильность: любой набор соединений на верхушке пула постепенно перетасуется и размоется случайным трафиком – но это "постепенно" может быть о-о-очень долгим, если перетасовать нужно достаточно большой набор соединений.

Хотя metastable link imbalance issue и воспроизводится с таким большим пулом, но выглядит этот сценарий очень искусственно. Я бы удивился, увидев приложение со средней нагрузкой в 3-4 одновременных запроса, и пулом соединений размером в ~500. И если бы в такой системе возникли проблемы с перегрузкой сети, то размер пула был бы одним из первых подозреваемых.

Нужный для воспроизведения размер пула можно сократить, если сделать его эластичным (auto-sizing) – это одна из подсказок в оригинальной статье инженеров фейсбука. Эластичный пул, который создает новые соединения по необходимости, и закрывает неактивные соединения по таймауту, облегчает воспроизведение link imbalance issue потому что новые соединения открываются равновероятно распределенными по каналам – а вот закрываются наименее активные соединения, которые чаще принадлежат более быстрым каналам. Поэтому через несколько циклов раздувания-сдувания в пуле накопится переизбыток соединений через медленные каналы, и недостаток соединений через быстрые. С эластичным пулом метастабильный сбой удается воспроизвести уже начиная с размера в 300-400 соединений.

Но это все равно нетипично большое число.

Проще и естественнее всего metastable link imbalance issue воспроизводится если взять много независимых клиентов (приложений). С точки зрения симуляции "много приложений" значит, что есть много независимых источников запросов, которые идут каждый через свой MRU-пул – но, в конечном счете, через общую сеть (=многоканальный сетевой линк).

Ретроспективно, идея попробовать такой сценарий довольно очевидна – в реальности, конечно, сетевой линк не обслуживает одно-единственное приложение. Но объяснения механизма возникновения link imbalance в обоих статьях пляшут вокруг одиночного пула, поэтому я долго пытался воспроизвести именно этот сценарий.

Так, в сценарии с 25 клиентами и 50-100 соединениями в пуле link imbalance воспроизводится достаточно стабильно:

Интересно, что механизм link imbalance здесь отличается от механизма в сценарии одного клиента: когда клиентов много, то трафик от каждого из них мал (иначе вместе они превысят емкость сетевого линка безо всяких дополнительных ухищрений). Но чем меньше трафик, проходящий через пул, тем меньшее число MRU-соединений играет в нем главную скрипку. Уже начиная с десятка клиентов главную скрипку играет одно-единственное, самое верхнее соединение в пуле – >90% запросов идут через него.

Это приводит к двум эффектам: с одной стороны, верхние соединения в пуле у разных клиентов будут разными, и это будет создавать естественную балансировку нагрузки по каналам – неравномерность распределения, создаваемая единичным MRU-пулом, будет компенсироваться большим количеством этих пулов (до всплеска нагрузки график гораздо более ровный, чем в сценарии с одним клиентом)

Но как только в системе появляются пачки мелких одинаковых запросов – все меняется, потому что на первую позицию во всех пулах всплывут соединения, идущие через один и тот же канал.

Когда я рассказывал про то, как упорядочивается пул под действием пачки одинаковых запросов, я упоминал, что на самый верх почти всегда всплывает соединение, идущее через самый медленный на данный момент канал – на втором, третьем, и прочих местах могут оказаться соединения других каналов, но на самом первом месте практически 100% будет самое медленное соединение. И у всех клиентов, во всех их пулах это произойдет одинаковым образом – наверху окажется соединение, идущее через один и тот же, самый медленный сейчас, канал.

То есть в сценарии многих клиентов механизм link imbalance не в том, что соединения в каждом пуле сортируются, и наверху пула оказывается группа соединений, идущих через один и тот же канал – нет, группы здесь не возникает, да она и не нужна. Достаточно того, что одно-единственное самое верхнее соединение во всех разных пулах идет через один и тот же канал. То есть пулы разных клиентов неявно координируются через видимую ими всеми сетевую задержку.

…Каждый раз, как сажусь помоделировать какую-нибудь интересную систему, я думаю, что это займет меня на несколько вечеров. Через пару недель я начинаю подозревать, что тут потенциально материала на магистерский диплом, а то и на кандидатскую – если действительно глубоко разбираться, что происходит, и что с чем связано.

То, что описано выше – это же только несколько степеней свободы из многих, доступных в этой системе.

Например: интересно, как зависит эффект от соотношения между временем исполнения запроса в БД и временем путешествия по сети? В при каком соотношении этих времен эффект link imbalance перестанет проявляться? А если регулярные запросы "тяжелые" для БД, но легкие для сети, а запросы в пачке однотипных запросов наоборот, сверхлегкие для БД, но относительно тяжелые для сети – изменит ли это что-то?

Или, например: в статье инженеры фейсбука "порешали" проблему тем, что перешли на LRU стратегию пула. У LRU есть свои преимущества, но есть и недостаток – LRU-пул держит открытыми гораздо бОльшее число соединений, чем MRU-пул. Что, если попытаться оставить стратегию MRU, но поменять смысл, вкладываемый в "used" – вместо момента возвращения соединения в пул за "used" считать момент изымания соединения из пула? Кажется, что это может разрушить механизм sleep-like-сортировки и координации, то есть link imbalance в такой реализации не будет возникать?

UPD: Похоже, так и есть, подробнее здесь

Примечания и литература

  1. https://doi.org/10.1145/3458336.3465286 "Metastable Failures in Distributed Systems", Nathan Bronson, Abutalib Aghayev, Aleksey Charapko, Timothy Zhu

    Статья описывает особый паттерн сбоев в распределенных системах. Паттерн выглядит так: внешнее возмущение (скачок нагрузки, падение какого-то из компонентов) вводит систему в какой-то патологический режим, который, по-идее, должен быть проходным, временным – система должна сама из него выйти в нормальный режим функционирования. Но этого не происходит: система застревает в патологическом режиме неожиданно надолго, и требуется внешнее воздействие, чтобы вернуть систему к нормальному функционированию. Авторы назвали это мета-стабильным сбоем (metastable failure).

    Пример, про который я дальше говорю, взят из главы 2.4 "Link Imbalance"↩︎

  2. MRU-пул логически выглядит как стек: соединения берутся с вершины стека, и возвращаются туда же. Я часто буду упоминать про "верхушку" пула, и про позиции "выше"/"ниже" в пуле – в том же смысле, в котором можно сказать это про стек.↩︎

  3. По-сути, речь идет о том, что соединения сортируются "спящей сортировкой" (sleep sort)↩︎

  4. "Solving the Mystery of Link Imbalance: A Metastable Failure State at Scale" https://engineering.fb.com/2014/11/14/production-engineering/solving-the-mystery-of-link-imbalance-a-metastable-failure-state-at-scale/↩︎

  5. https://github.com/cheremin/link-imbalance-metastable-issue-simulation↩︎

  6. Эта run-to-run variance изрядно попортила мне жизнь: я не сразу сообразил, что насколько система неустойчива, и долго не мог понять, почему на одном и том же наборе параметров вчера эффект, вроде, воспроизводился, а сегодня что-то уже нет.↩︎

  7. Более того: такая перетасовка происходит тем быстрее, чем больше тормозят верхние соединения в пуле. Ведь чем дольше обрабатываются запросы, тем вероятнее, что следующие запросы придут до того, как текущие завершатся, и для обработки этих новых запросов придется рекрутировать более глубокие соединения из пула – после чего более глубокие соединения могут оказаться наверху.

    Это своего рода отрицательная обратная связь (backpressure): чем более "не типичная" (не среднестатистическая) конфигурация MRU-соединений сложилась в пуле, тем медленнее пул обрабатывает запросы, тем больше запросов приходит за время обработки предыдущих, тем более глубокие соединения приходится задействовать – тем активнее происходит перетасовка группы MRU-соединений, тем быстрее нетипичная конфигурация сменится более типичной (более равномерно распределяющей нагрузку)↩︎

2 комментария:

  1. Руслан, очень интересная статья, спасибо! Один технический вопрос - как ты реализовал симуляцию физических соединений?

    ОтветитьУдалить
    Ответы
    1. Для целей симуляции физическое соединение (=кабель) это просто ресурс с емкостью 1 -- т.е. в каждый момент времени он либо свободен, либо он занят передачей данных какого-то одного запроса. Если кто-то еще захочет использовать кабель, когда он занят -- должен будет подождать в очереди.

      В kalasim (да и других движках discrete event simulation) уже из коробки есть реализация таких ресурсов

      Собственно, вот весь код "исполнения запроса": https://github.com/cheremin/link-imbalance-metastable-issue-simulation/blob/5096234da20876680a4ffde19e51828a9f9ddd17/src/main/kotlin/ru/cheremin/mrupool/MultiFibersMRUPoolSimulation.kt#L20

      а вот класс Fiber: https://github.com/cheremin/link-imbalance-metastable-issue-simulation/blob/5096234da20876680a4ffde19e51828a9f9ddd17/src/main/kotlin/ru/cheremin/mrupool/MultiFibersMRUPoolSimulation.kt#L239

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

      Но меня не очень интересует детальное поведение когда канал уже перегружен, меня интересует когда/как система вообще входит в перегруженное состояние (если входит). Я предполагаю, что для этого вопроса детальное поведение физического канала/сетевого протокола не очень важно.

      Удалить