Deep Engineering
Продвинутый·Опубликовано·45 МИН

Балансировка нагрузки: кто выбирает сервер и что он в этот момент знает

Чем раньше принято решение, тем меньше о нём известно. DNS выбирает сервер за минуты до запроса и не знает про него ничего; L7-балансировщик выбирает в момент запроса и знает всё. Из этого следует и разделение слоёв, и главный результат модели очередей: least connections и least response time — лучший выбор ровно до тех пор, пока сигнал о загрузке свежий.

Полное техническое изложение

TL;DR

Балансировка — это цепочка решений, и у каждого своя цена. Чем раньше выбран сервер, тем меньше о нём известно в момент выбора. DNS отвечает за минуты до запроса и не знает ни загрузки, ни живости; L4 видит адреса и порты; L7 читает запрос целиком и знает про бэкенды всё.

Алгоритм имеет значение сильно больше, чем принято думать. В модели из шестнадцати бэкендов при загрузке 80% задержка p99 отличается вчетверо: 25,07 у случайного выбора против 6,06 у least connections. Round robin — 17,00, то есть ближе к случайному, чем к разумному.

Но у least connections есть условие, и оно важнее самого алгоритма: сигнал о загрузке должен быть свежим. Как только состояние собирается в общий снимок с задержкой, least connections деградирует — при задержке в две средние обработки его p99 (19,95) хуже, чем у round robin (17,00). Выбор лучшего из двух случайных за то же время сдвигается с 8,86 до 10,52 и становится лучшим вариантом на столе.

Зачем это знать

Балансировщик выглядит инфраструктурой, которую настраивают один раз. Это верно ровно до первого инцидента, в котором «нагрузка распределена равномерно», а хвост задержек утроился, — и оказывается, что равномерно распределены запросы, а не работа.

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

Всё, что здесь названо числом, получено одним из трёх способов: живым запросом к DNS, прогоном модели очередей или взято из документа, ссылка на который стоит в источниках. Числа из модели помечены как модельные — им нельзя верить как замеру живого кластера, и ниже сказано, почему и в чём именно.

Клиентская и серверная балансировка

Разделение проходит по тому, у кого лежит список бэкендов.

При серверной балансировке клиент знает один адрес. За ним стоит балансировщик, у которого есть список, проверки живости и алгоритм. Клиент не участвует и не может ошибиться — но и не может обойти балансировщик, если тот стал узким местом.

При клиентской балансировке список получает сам клиент — из service discovery, из xDS, из SRV-записей — и выбирает сервер сам. Лишнего сетевого перехода нет, отказ одного клиента не задевает остальных. Цена — в том, что логика балансировки размазана по всем клиентам сразу: чтобы поменять алгоритм, нужно выкатить их все. Отсюда и sidecar-прокси: список и алгоритм остаются рядом с клиентом, но обновляются отдельно от него.

Есть и третий случай, который клиентской балансировкой обычно не называют, а это она и есть. Когда DNS возвращает несколько адресов, выбор делает клиент.

DNS: выбор за минуты до запроса

DNS-балансировка — самый ранний и самый слепой слой. Замер на живых доменах, двадцать запросов подряд к каждому:

доменадресов в ответеTTLкакой адрес оказался первым
www.google.com82…253 свсе восемь, 1–4 раза каждый
github.com137…60 счетыре разных адреса за 20 запросов
www.cloudflare.com2300 сдва адреса, 12 и 8 раз

Видно две разные стратегии. Google отдаёт весь список и перемешивает порядок — решение оставлено клиенту. GitHub отдаёт по одному адресу, но разный: решение принято сервером имён, клиенту выбирать не из чего.

Оба приёма старше многих читателей. RFC 1794 описал их в апреле 1995 года и там же назвал ограничение, которое никуда не делось:

the DNS protocol doesn't guarantee ordering

перевод

протокол DNS не гарантирует порядок

RFC 1794 — DNS Support for Load Balancing

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

There is no use in handing out information with TTLs of an hour, when the conditions for ordering the RRs changes minutely.

перевод

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

RFC 1794 — DNS Support for Load Balancing

Здесь всё противоречие DNS-балансировки. Чтобы решение было свежим, TTL должен быть маленьким. Чтобы кеширование работало, он должен быть большим. И даже маленький TTL не спасает: никто не обязан его соблюдать — ни резолвер провайдера, ни браузер с собственным кешем, ни соединение, которое уже установлено и живёт часами.

Последнее стоит проговорить отдельно, потому что оно ломает интуицию. DNS-балансировка распределяет не запросы, а обращения за адресом. Клиент, который открыл HTTP/2-соединение и держит его открытым, обратился за адресом один раз на тысячу запросов. Балансировка по DNS в этом случае разложила по серверам не нагрузку, а моменты установки соединений.

geoDNS: и как проверить, настоящий ли он

Идея geoDNS проста: отвечать разными адресами в зависимости от того, откуда пришёл вопрос. Сложность в том, что авторитетный сервер видит адрес не клиента, а резолвера — и если человек в Токио пользуется публичным резолвером в США, географическая близость определяется неправильно.

Для этого и придуман RFC 7871: резолвер добавляет к запросу кусок адреса клиента. Ответ содержит SCOPE PREFIX-LENGTHthe leftmost number of significant bits of ADDRESS that the response covers (число старших значащих бит адреса, которые покрывает ответ). Ноль означает, что ответ подходит всем.

Значит, настоящий geoDNS отличается от ненастоящего одним наблюдаемым полем. Проверка: один и тот же вопрос с подстановкой четырёх разных клиентских подсетей.

доменНью-ЙоркФранкфуртТокиоСан-Паулуscope
d1.awsstatic.com18.164.124.…3.160.150.…3.166.244.…18.67.145.…/24
www.bing.com23.195.81.144тот жетот жетот же/0
www.microsoft.com23.217.78.102тот жетот жетот же/0
www.netflix.com207.45.72.1тот жетот жетот же/0

CloudFront отвечает четырьмя разными наборами адресов и честно помечает ответ /24: «этот ответ годится только для этой подсети». Остальные три отвечают одинаково и ставят /0.

Из этого не следует, что Bing и Netflix не умеют в географию. Следует ровно одно: они не используют для этого EDNS Client Subnet. Anycast решает ту же задачу на уровне маршрутизации, и тогда одному адресу соответствуют разные машины в разных точках мира — а DNS-ответ действительно один на всех.

Стоит помнить и то, что сам RFC 7871 относится к своему механизму без энтузиазма: он рекомендует выключать его по умолчанию и усекать адрес до /24, потому что иначе запрос к DNS начинает рассказывать о местоположении пользователя больше, чем нужно.

L4 и L7: сколько балансировщик видит и сколько за это платит

Оба слоя стоят между клиентом и бэкендом, но смотрят на разное.

L4 работает с транспортом: адрес, порт, флаги. Он не разбирает HTTP-запрос — часто и не может, потому что тот зашифрован. Решение принимается один раз, при установке соединения, и все последующие пакеты идут по уже выбранному пути. Отсюда и производительность: Maglev, балансировщик Google, is able to saturate a 10Gbps link with small packets (способен насытить канал 10 Гбит/с мелкими пакетами) на одной обычной машине без специального железа.

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

Разница, которую чаще всего упускают, — единица балансировки.

На L4 единица — соединение. Клиент с одним долгим HTTP/2-соединением попадает на один бэкенд и остаётся там навсегда, сколько бы запросов он ни отправил. Десять клиентов, из которых один шлёт 90% трафика, разложатся по десяти бэкендам поровну — по соединению на каждый, и один бэкенд получит девять десятых работы.

На L7 единица — запрос, и та же нагрузка разложится ровно. Цена — расшифровка, разбор и повторная сборка каждого запроса.

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

Алгоритмы: модель и что она показывает

Дальше числа из модели. Устройство её такое: шестнадцать бэкендов, каждый — одна очередь FIFO с одним обслуживающим прибором. Это классическая модель G/G/1: запросы ждут своей очереди и обрабатываются по одному. Никакой придуманной «формулы деградации» здесь нет — замедление под нагрузкой возникает само, из ожидания.

Поток заявок пуассоновский, время обработки логнормальное (тяжёлый хвост — обычная форма для веб-запросов). Балансировщик видит ровно то, что видел бы настоящий: число незавершённых запросов и скользящее среднее времени ответа. Будущего он не знает. Отброшены первые 20 000 запросов из 300 000, результат усреднён по пяти прогонам.

Чего эта модель не умеет и о чём по ней нельзя судить: у неё нет сети и её задержек, нет разрывов соединений, нет проверок живости, нет кеша на бэкенде (а он делает повторный запрос к тому же серверу дешевле), нет ограничений по памяти. Она отвечает на один вопрос — как распределение запросов влияет на ожидание в очередях, — и только на него.

Как читать числа в таблицах ниже. В ячейке — полное время запроса в системе: сколько он ждал в очереди плюс сколько обрабатывался, — измеренное в единицах одной средней обработки. Значит, 1,0 — это «столько, сколько в среднем занимает обработать один запрос»; 0,83 — быстрее средней обработки (запрос почти не стоял в очереди), 25,07 — в двадцать пять раз дольше. Абсолютные секунды тут не нужны: в этих единицах числа не зависят от того, быстрое железо или медленное.

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

Одинаковые серверы, загрузка 80%

алгоритмp50p95p99
random3,3616,1525,07
round robin1,7810,0917,00
least connections0,833,486,06
least response time0,833,245,50
power of two choices1,495,578,86

Первое, что стоит заметить: разброс вчетверо на одинаковых серверах. Обычное объяснение «если серверы одинаковые, сойдёт и round robin» неверно, и причина не в серверах, а в запросах: они разной длины. Round robin распределяет их поровну по счёту, а не по работе, и сервер, которому подряд достались три длинных запроса, встаёт в очередь, пока сосед простаивает. Least connections этого не допускает, потому что смотрит на то, что ещё не завершилось.

Случайный выбор хуже round robin ровно по той причине, по которой шары падают в корзины неравномерно, — к этому вернёмся в разделе про выбор из двух.

Взвешенный round robin и цена ошибки в весе

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

настройкаp50p95p99
round robin, веса не трогали1,9813 05043 946
взвешенный, вес угадан (1 против 4)3,4213,8522,63
взвешенный, медленному оставили вдвое больше веса, чем нужно (1 против 2 вместо 1 против 4)2,5116,1310 969
least connections, весов нет вообще0,914,037,70

Числа в тысячах — не задержка, а отсутствие устойчивости. Арифметика простая: шестнадцать серверов при загрузке 80% дают 0,8 запроса на сервер за время одной средней обработки; медленному серверу каждый запрос обходится вчетверо дороже, то есть его собственная загрузка — 3,2. Больше единицы. Очередь на нём растёт без предела, и напечатанное число говорит лишь о том, где остановилась модель, а не о том, сколько ждал бы читатель.

Дальше видно две вещи. Правильно выставленный вес чинит устойчивость, но проигрывает least connections втрое по p99 — статический вес не знает, что происходит прямо сейчас. А вес, выставленный неточно, — худший из вариантов: p95 выглядит здоровым (16,13), и только p99 показывает, что сервер балансирует на грани. Мониторинг по среднему и по p95 такую настройку не поймает.

Least connections и least response time

Least connections в nginx описан дословно так: запрос идёт to the server with the least number of active connections, taking into account weights of servers (серверу с наименьшим числом активных соединений, с учётом весов серверов). Least response time добавляет к этому время: у nginx это least_time с выбором, считать ли время до заголовка (header) или до последнего байта (last_byte).

В модели выше разница между ними невелика: 6,06 против 5,50 по p99 на одинаковых серверах и 7,70 против 6,48 на кластере с медленным сервером. Least response time чуть лучше — и это ожидаемо: число соединений не отличает быстрый бэкенд от медленного, а время отличает.

Разница вырастает там, где число соединений вообще перестаёт быть мерой работы. Соединение, по которому качают файл на гигабайт, и соединение с пустым health-check выглядят для least connections одинаково.

Least bandwidth и least packets

Это уже не про очереди, а про канал. NetScaler определяет least bandwidth как выбор сервиса, that is currently serving the least amount of traffic, measured in megabits per seconds (который в данный момент обслуживает наименьший объём трафика, измеряемый в мегабитах в секунду), а least packets — the least packets in the last 14 seconds (наименьшее число пакетов за последние 14 секунд).

Мерить стоит то, что кончается первым. Для API, где ответ — килобайт JSON, кончится процессор, и мерить надо очереди. Для раздачи видео кончится полоса, и там least connections введёт в заблуждение: десять соединений с битрейтом 100 кбит/с и одно соединение на 50 Мбит/с — это не «десять против одного», это «один против пятидесяти».

Модели для этих двух алгоритмов здесь нет намеренно: чтобы её построить, пришлось бы моделировать сеть, а не очереди, и результат говорил бы больше о предположениях про сеть, чем о балансировке. Поэтому здесь только определение из документации вендора и правило выбора, а числа — нет.

Sticky sessions: сколько стоит закрепление

Закрепление сессии за бэкендом нужно, когда состояние лежит на сервере. Цена — балансировщик перестаёт балансировать: он раскладывает не запросы, а сессии.

что балансируемp50p95p99
запросы (least connections)0,833,486,06
5000 живых сессий3,3816,7626,90
500 живых сессий3,77118,75206,92
50 живых сессий30,9060258408

Даже при пяти тысячах сессий p99 хуже в четыре с половиной раза. Дальше зависимость обваливается: чем меньше сессий, тем крупнее «зерно», которым раскладывается нагрузка, и тем сильнее случайный перекос. При пятидесяти сессиях на шестнадцать серверов балансировка перестаёт существовать — работает закон малых чисел.

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

Технически закрепление делают по-разному, и разница существенна. ip_hash в nginx берёт the first three octets of the client IPv4 address (первые три октета IPv4-адреса клиента) — то есть все клиенты за одним NAT попадают на один бэкенд. Закрепление по куке точнее, но работает только для браузеров. Consistent hashing (hash ... consistent в nginx, ring hash и Maglev в Envoy) отличается от обычного хеша тем, что при добавлении или удалении сервера переезжает малая часть ключей, а не почти все: в документации nginx это сказано прямо — only a few keys will be remapped (переотображены будут лишь немногие ключи).

Power of two choices: почему два, а не все

Начнём с чистой задачи: n шаров бросают в n корзин. Если каждый шар летит в случайную корзину, максимальная загрузка is approximately log n / log log n (составляет примерно log n / log log n). Если каждый шар смотрит на две случайные корзины и падает в менее полную, она становится log log n / log d + O(1). Считаем на модели шаров и корзин — пятнадцать прогонов на каждое n, среднее и стандартное отклонение:

В ячейке — максимальная загрузка: сколько шаров оказалось в самой полной корзине (среднее по пятнадцати прогонам; число после ± — стандартное отклонение). n — это и число шаров, и число корзин сразу. Первые два столбца — два способа бросать шар: в случайную корзину и в менее полную из двух случайных. Последние два — асимптотические формулы из обзора, посчитанные по натуральному логарифму: с ними сравниваем.

nслучайнолучшая из двухln n / ln ln nln ln n / ln 2
1004,40 ± 0,632,60 ± 0,633,022,20
1 0005,47 ± 0,523,00 ± 0,003,572,79
10 0006,67 ± 0,623,13 ± 0,354,153,20
100 0007,67 ± 0,623,53 ± 0,524,713,53

Тысячекратный рост числа корзин почти удваивает максимум при случайном выборе — и практически не двигает его при выборе из двух: 2,60 → 3,53. Асимптотическая формула на конечных n занижает оценку для случайного выбора (она асимптотическая, это нормально), а для выбора из двух держится рядом с моделью и на ста тысячах корзин сходится с ней вплотную: 3,53 против 3,53.

Почему берут именно два, а не три и не десять, сказано в том же обзоре: each additional choice beyond two decreases the maximum load by just a constant factor (каждый следующий выбор сверх двух уменьшает максимальную загрузку лишь на постоянный множитель). Первый шаг от одного к двум меняет асимптотику, все следующие — только множитель. HAProxy это и закрепил: число жеребьёвок настраивается, а по умолчанию равно двум. Envoy формулирует так же: selects N random available hosts as specified in the configuration (2 by default) and picks the host which has the fewest active requests (выбирает N случайных доступных узлов, как задано в настройках (по умолчанию два), и берёт тот, у которого меньше всего активных запросов).

Но в очередях, а не в корзинах, картина другая — и в этом главная неожиданность всего разбора. Вернитесь к первой таблице: p99 у power of two — 8,86, у least connections — 6,06. Выбор из двух проигрывает. И это не артефакт модели: автор HAProxy, измеряя на живом стенде из шести машин, получил тот же знак — least connections дал about 4% higher (примерно на 4 % выше) запросов в секунду, а пики соединений у него оказались about 30% lower (примерно на 30 % ниже).

Так зачем тогда power of two?

Главный результат: свежесть сигнала важнее алгоритма

Least connections требует знать, сколько запросов сейчас в работе у каждого бэкенда. Пока балансировщик один, он это знает точно — он сам их и отправил. Как только балансировщиков много, точного знания нет ни у кого.

Обычное объяснение — стадный эффект: все балансировщики видят один и тот же «самый свободный» сервер и одновременно бросаются на него. Owen Garrett описывает это через очередь на паспортном контроле: all the guides notice that one queue is momentarily shorter and faster, and all send travelers to that queue (все сотрудники разом замечают, что одна очередь сейчас короче и движется быстрее, и все направляют людей именно в неё). Envoy называет ту же причину как основание для выбора: resistance to herding behavior (устойчивость к стадному эффекту).

Объяснение звучит убедительно, поэтому оно проверено двумя опытами. Верным оно оказалось наполовину.

Опыт первый: у каждого балансировщика свои счётчики. Шестнадцать бэкендов, несколько независимых балансировщиков; каждый видит только собственные отправленные запросы, про соседей не знает ничего.

В ячейках — p99 в тех же единицах средней обработки, что и выше; строка — сколько независимых балансировщиков работает одновременно.

балансировщиковround robinleast connectionspower of two
116,776,018,95
416,898,7510,99
1617,7613,2714,28
6419,7218,3818,87

Стада нет. Least connections плавно теряет преимущество и к шестидесяти четырём балансировщикам сравнивается с round robin — 18,38 против 19,72, — но ни разу не становится хуже него. Причина понятна, если сформулировать: приватный счётчик — это несмещённая выборка из общей картины. Балансировщик судит по неполным данным, но не по неправильным, и ошибки разных балансировщиков не сговариваются.

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

В ячейках снова p99; строка — насколько устарел общий снимок: 0 — мгновенный, 2,0 — отстаёт на две средние обработки.

задержка снимкаrandomround robinleast connectionspower of two
0 (мгновенно)25,0717,006,068,86
0,125,0717,006,888,94
0,525,0717,0010,299,26
2,025,0717,0019,9510,52

Вот теперь видно всё. При задержке в две средние обработки least connections даёт p99 = 19,95 — хуже round robin и почти на уровне случайного выбора. Power of two за то же время сдвигается с 8,86 до 10,52, то есть на пятую часть.

Значит, дело не в количестве балансировщиков как таковом. Разрушает least connections общий устаревший сигнал: все читают одно и то же неверное число и делают одну и ту же ошибку одновременно. Приватные неполные счётчики такого не дают, а общий снимок — даёт, и чем он старше, тем хуже.

Отсюда правило, которое сформулировано в терминах данных, а не в терминах названий алгоритмов:

  • Балансировщик один и считает сам — least connections или least response time, и ничего лучше не нужно.
  • Балансировщиков много, но каждый считает свои запросы — least connections всё ещё разумен; его преимущество тает, но до round robin он не опускается.
  • Состояние приходит со стороны и с задержкой — выбор лучшего из двух, и чем больше задержка, тем очевиднее выигрыш.
  • Состояния нет вовсе — взвешенный round robin, и тогда за весами придётся следить руками; ошибка в весе стоит дороже, чем кажется.

Когда балансировать уже нечего: отбрасывать нагрузку

Модель выше показала точку, за которой алгоритм перестаёт что-либо решать: загрузка больше единицы, очередь растёт без предела. Это не край модели, а рабочая ситуация, и у неё есть отдельный ответ, к балансировке отношения не имеющий.

Avoiding overload is a goal of load balancing policies. But no matter how efficient your load balancing policy, eventually some part of your system will become overloaded. Gracefully handling overload conditions is fundamental to running a reliable serving system.

перевод

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

Google SRE Book, гл. 21 — Handling Overload

Ответ называется отбрасыванием нагрузки, и определение у него короткое:

Load shedding drops some proportion of load by dropping traffic as the server approaches overload conditions.

перевод

Отбрасывание нагрузки сбрасывает некоторую долю нагрузки, отбрасывая трафик по мере приближения сервера к условиям перегрузки.

Google SRE Book, гл. 22 — Addressing Cascading Failures

Зачем отказывать, если можно поставить в очередь, объясняет вторая половина нашей же модели. Очередь — это не бесплатное ожидание, а потраченная память и добавленная задержка:

Queued requests consume memory and increase latency. For example, if the queue size is 10x the number of threads, the time to handle the request on a thread is 100 milliseconds. If the queue is full, then a request will take 1.1 seconds to handle, most of which time is spent on the queue.

перевод

Запросы в очереди потребляют память и увеличивают задержку. Например, если размер очереди в 10 раз больше числа потоков, а время обработки запроса потоком — 100 миллисекунд, то при полной очереди обработка запроса займёт 1,1 секунды, бо́льшая часть которых проведена в очереди.

Там же, раздел Queue Management

И — главное — работа в такой очереди уже никому не нужна: If a user's web search is slow because an RPC has been queued for 10 seconds, there's a good chance the user has given up and refreshed their browser, issuing another request: there's no point in responding to the first one, since it will be ignored (Если пользовательский веб-поиск медленный, потому что RPC простоял в очереди 10 секунд, велика вероятность, что пользователь сдался и обновил браузер, отправив новый запрос: отвечать на первый нет смысла — ответ проигнорируют). То есть за порогом сервер тратит ресурсы на ответы, которые никто не прочтёт. Отброс возвращает их тем, кто ещё ждёт:

The goal of load shedding is to keep latency low for the requests that the server decides to accept so that the service replies before the client times out.

перевод

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

AWS Builders' Library — Using load shedding to avoid overload

Механика. Она использует ровно ту величину, которой уже оперирует least connections из таблицы выше — число незавершённых запросов:

One straightforward way to shed load is to do per-task throttling based on CPU, memory, or queue length […] For example, one effective approach is to return an HTTP 503 (service unavailable) to any incoming request when there are more than a given number of client requests in flight.

перевод

Прямолинейный способ отбрасывать нагрузку — ограничивать её на уровне задачи по процессору, памяти или длине очереди […] Например, один действенный подход — возвращать HTTP 503 (сервис недоступен) на любой входящий запрос, когда число клиентских запросов в работе превышает заданное.

Google SRE Book, гл. 22

В Envoy это два разных механизма, и различать их стоит. Размыкание цепи — max_requests и max_pending_requests на кластер — защищает того, к кому идут. Менеджер перегрузки — того, кто стоит посередине: This is distinct from circuit breaking which is primarily aimed at protecting upstream services (Это отличается от размыкания цепи, которое нацелено прежде всего на защиту вышестоящих сервисов). У второго пороги заданы по давлению ресурса: сливать соединения при 92 % использования кучи, прекращать принимать запросы при 95 %.

Порог можно и не выбирать вручную. Адаптивная конкурентность выводит его из задержки: измеряет идеальное время обхода minRTT, сравнивает с текущим и двигает предел по градиенту — This gradient value has a useful property, such that it decreases as the sampled latencies increase (У этого градиента есть полезное свойство: он уменьшается по мере роста измеренных задержек). Цена честно названа там же: во время окна измерения возможен заметный рост числа 503, потому что предел на это время прижимается к минимуму.

Две оговорки, без которых совет вреден.

Первая — про метрику. Считать ёмкость в запросах в секунду ненадёжно: modeling capacity as "queries per second" … often makes for a poor metric (моделирование ёмкости как „запросов в секунду“ … часто оказывается плохой метрикой). Причина та же, что в нашей модели: запросы неодинаковы по стоимости, и именно разброс стоимости, а не их число, растил хвост.

Вторая — про то, что отброс сам должен работать. Ветка, которой не пользуются, обычно сломана:

Remember that the code path you never use is the code path that (often) doesn't work. In steady-state operation, graceful degradation mode won't be used, implying that you'll have much less operational experience with this mode and any of its quirks, which increases the level of risk.

перевод

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

Google SRE Book, гл. 22

Повторы: почему «попробовать ещё раз» умножается

В разделе про L4 и L7 выше повтор на другом бэкенде назван достоинством L7 — и это верно. Не сказана вторая половина: под перегрузкой повтор перестаёт помогать и начинает вредить.

When failures are caused by overload, retries that increase load can make matters significantly worse. They can even delay recovery by keeping the load high long after the original issue is resolved.

перевод

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

AWS Builders' Library — Timeouts, retries, and backoff with jitter

Опаснее всего то, что повторы перемножаются по слоям, а не складываются:

Avoid amplifying retries by issuing retries at multiple levels: a single request at the highest layer may produce a number of attempts as large as the product of the number of attempts at each layer to the lowest layer. If the database can't service requests because it's overloaded, and the backend, frontend, and JavaScript layers all issue 3 retries (4 attempts), then a single user action may create 64 attempts (4^3) on the database.

перевод

Избегайте усиления повторов, выполняя их на нескольких уровнях: один запрос на верхнем слое может породить число попыток, равное произведению числа попыток на каждом слое вплоть до нижнего. Если база не может обслуживать запросы из-за перегрузки, а слои бэкенда, фронтенда и JavaScript делают по 3 повтора (4 попытки), то одно действие пользователя может создать 64 попытки (4³) к базе.

Google SRE Book, гл. 22, раздел Retries

AWS считает тот же множитель на пять слоёв и получает 243: If each layer retries independently, the load on the database will increase 243x, making it unlikely to ever recover (Если каждый слой повторяет независимо, нагрузка на базу вырастет в 243 раза, и восстановиться она вряд ли сможет). Оба числа — арифметика, а не замер, и это здесь важнее замера: показатель степени равен числу слоёв, а слои в реальной системе никто не считает.

Соедините это с моделью выше. Загрузка выросла до единицы, задержки поползли, клиенты начали повторять — и нагрузка на нижний слой выросла не на проценты, а в несколько раз: множитель здесь возводится в степень числа слоёв, а не умножается на него. Три слоя по три повтора — это 64 попытки вместо одной; пять слоёв — 243. Точка невозврата, которая на графике выглядела далёкой, оказывается за этим множителем.

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

Разнести повторы во времени. Always use randomized exponential backoff when scheduling retries (Всегда используйте рандомизированную экспоненциальную выдержку при планировании повторов). Случайность здесь не украшение: без неё повторы синхронизируются и приходят пачкой — Jitter adds some amount of randomness to the backoff to spread the retries around in time (Джиттер добавляет к выдержке некоторую случайность, чтобы разнести повторы во времени).

Ограничить повторы бюджетом, а не счётчиком на запрос. Consider having a server-wide retry budget. For example, only allow 60 retries per minute in a process, and if the retry budget is exceeded, don't retry; just fail the request (Подумайте о бюджете повторов на уровне сервера. Например, разрешайте только 60 повторов в минуту в процессе, а если бюджет исчерпан — не повторяйте, просто отказывайте в запросе). Разница принципиальна: счётчик на запрос ограничивает одного клиента, бюджет — всех сразу, а умножается именно суммарный объём.

Повторять только на одном слое. Это следствие первой цитаты, и в настройке Envoy оно записано так:

In general we recommend using retry budgets; however, if static circuit breaking is preferred it should aggressively circuit break retries. This is so that retries for sporadic failures are allowed, but the overall retry volume cannot explode and cause large scale cascading failure.

перевод

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

Envoy — Circuit breaking

Близость против равномерности: маршрутизация по зонам

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

The purpose of zone aware routing is to send as much traffic to the local zone in the upstream cluster as possible while roughly maintaining the same number of requests per second across all upstream hosts (depending on load balancing policy).

перевод

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

Envoy — Zone aware routing

«Как можно больше» и «примерно одинаково» — это две цели, и вторая ограничивает первую. Что происходит, когда они расходятся, описано конкретно:

The originating cluster local zone percentage is greater than the one in the upstream cluster. In this case we cannot route all requests from the local zone of the originating cluster to the local zone of the upstream cluster because that will lead to request imbalance across all upstream hosts. Instead, Envoy calculates the percentage of requests that can be routed directly to the local zone of the upstream cluster. The rest of the requests are routed cross zone.

перевод

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

Там же

То есть локальность — не переключатель, а доля, и считается она из соотношения ёмкостей зон.

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

This approach is mutually exclusive with zone aware routing, since in the case of locality aware LB, we rely on the management server to provide the locality weighting, rather than the Envoy-side heuristics used in zone aware routing.

перевод

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

Envoy — Locality weighted load balancing

Здесь важно, как веса ведут себя при отказах. Вес корректируется не сразу, а с запасом — коэффициент избыточного выделения 1,4, — и таблица из документации показывает, где именно начинается перелив (расположение X с весом 1 против Y с весом 2):

здоровых узлов в Xдоля трафика в Xдоля трафика в Y
100 %33 %67 %
70 %33 %67 %
69 %32 %68 %
50 %26 %74 %
25 %15 %85 %
0 %0 %100 %

Между 100 % и 70 % не меняется ничего: коэффициент 1,4 и означает «пока недоступно меньше 30 %, считаем расположение целым». Дальше доля падает пропорционально. Это числа документации Envoy, не наш замер.

И самое важное для главной оси статьи — про момент решения. Алгоритм из таблицы выше выбирает узел не первым, а третьим:

  1. Pick priority level. 2. Pick locality (as described in this section) within priority level from (1). 3. Pick endpoint using cluster specified load balancer within locality from (2).
перевод

1. Выбрать уровень приоритета. 2. Выбрать расположение внутри уровня приоритета из (1). 3. Выбрать конечную точку заданным для кластера балансировщиком внутри расположения из (2).

Там же

То есть спор «round-robin или least connections», которому посвящена модель, разыгрывается внутри уже выбранного расположения. Два предыдущих шага отсекают большую часть узлов до того, как алгоритм вообще получит слово, — и влияют на итоговое распределение сильнее, чем выбор между ним и соседним.

Немного истории

ВерсияИзменениеЧто стало возможным
1995RFC 1794 описывает распределение нагрузки через DNS и там же называет его пределы: порядок записей не гарантирован, а TTL в час бессмысленен, если условия меняются ежеминутно.Балансировка без обратной связи
1999Azar, Broder, Karlin и Upfal доказывают, что выбор лучшей из d корзин даёт максимум log log n / log d + O(1) вместо log n / log log n.Два выбора вместо одного
2008Google запускает Maglev — балансировщик четвёртого уровня на обычных серверах. Статья выйдет только в 2016-м, на NSDI.L4 без специального железа
2016RFC 7871 вводит EDNS Client Subnet и поле SCOPE PREFIX-LENGTH — по нему теперь можно проверить, зависит ли ответ от подсети клиента.geoDNS стал наблюдаемым
2018nginx получает random two, а вместе с ним и публичное объяснение, зачем это нужно при нескольких балансировщиках.Power of two в проксях
2019HAProxy добавляет число жеребьёвок в balance random; значение по умолчанию — два. Автор тут же публикует замеры, из которых следует, что при одном балансировщике least connections всё равно лучше.Настраиваемое число выборов
2025В nginx 1.31.0 least_time перестаёт быть частью платной подписки.Least response time всем

Что здесь стандарт, а что решение конкретного продукта

Разделение того же рода, что «гарантия языка против детали реализации», и путать их так же опасно.

Стандарт, на который можно опираться: формат DNS-ответа и семантика TTL (RFC 1035, RFC 1794); формат EDNS Client Subnet и смысл SCOPE PREFIX-LENGTH (RFC 7871); теоретические границы для случайного выбора и выбора из двух — это математика, она не зависит от продукта.

Решение конкретного продукта, которое меняется от версии к версии: набор алгоритмов и их названия; что именно nginx считает «активным соединением»; сколько жеребьёвок делает HAProxy по умолчанию (два — но это настройка); формула Envoy для неравных весов; то, что ip_hash берёт первые три октета; размер таблицы Maglev (65537). Ни одно из этих чисел не свойство балансировки как таковой — все они выбраны авторами и записаны в документации, на которую стоит ссылаться при споре.

Как воспроизвести числа

Все числа в статье получены тремя способами.

Живые DNS-замеры — обычными запросами к резолверу, с подстановкой EDNS Client Subnet для четырёх подсетей; результат зависит от того, где вы находитесь и через какой резолвер ходите, поэтому у вас адреса будут другие, а поле scope — тем же: именно оно и проверяется. Скрипты: bench/load-balancing/dns2.py и bench/load-balancing/ecs.py.

Модель очередей — дискретное событийное моделирование, описанное выше: шестнадцать очередей FIFO, пуассоновский поток, логнормальное время обработки, 300 000 запросов, первые 20 000 отброшены, пять прогонов с разными зёрнами. Алгоритмы считает bench/load-balancing/sim2.py, веса и закрепление — bench/load-balancing/sim3.py, много балансировщиков сразу — bench/load-balancing/herd.py и bench/load-balancing/herd2.py.

Задача о шарах и корзинах — bench/load-balancing/bins.py.

Числа из модели воспроизводимы точно; числа из DNS — только по смыслу.

Частые заблуждения

Утверждение

«Если серверы одинаковые, сойдёт и round robin».

На самом деле

Серверы одинаковые, а запросы — нет. Round robin раскладывает их поровну по счёту, а не по работе, и сервер, которому подряд достались три длинных запроса, стоит в очереди, пока сосед простаивает. В модели из шестнадцати одинаковых бэкендов при загрузке 80% p99 у round robin — 17,00, у least connections — 6,06. Втрое, при полностью одинаковом железе.

Утверждение

«Least connections всегда лучше, это же учёт реальной загрузки».

На самом деле

Только пока сигнал свежий. В модели с общим снимком состояния, обновляемым раз в две средние обработки, least connections даёт p99 = 19,95 — хуже round robin (17,00) и почти вровень со случайным выбором (25,07). Выбор лучшего из двух за то же время сдвигается с 8,86 всего до 10,52. Ломает least connections не количество балансировщиков, а общий устаревший сигнал: все читают одно и то же неверное число и ошибаются одинаково.

Утверждение

«Power of two choices лучше least connections — это же современный алгоритм».

На самом деле

При одном балансировщике с точными счётчиками он проигрывает: p99 = 8,86 против 6,06. Тот же знак получил автор HAProxy на живом стенде — least connections дал about 4% higher (примерно на 4 % выше) запросов в секунду и пики соединений about 30% lower (примерно на 30 % ниже). Выбор из двух выигрывает не точностью, а устойчивостью к плохим данным: он не полагается на полную картину и потому не рушится, когда она устаревает.

Утверждение

«Взвешенный round robin решает проблему медленного сервера».

На самом деле

Решает, если вес угадан, и создаёт худший из вариантов, если промахнуться. В модели с сервером вчетверо медленнее: правильный вес даёт p99 = 22,63, отсутствие веса — расходящуюся очередь, а вес занижённый вдвое — p95 = 16,13 при p99 = 10 969. То есть по среднему и по p95 настройка выглядит здоровой, а сервер стоит на самой границе устойчивости. Least connections тот же случай закрывает без единого веса: p99 = 7,70.

Утверждение

«DNS-балансировка распределяет запросы между серверами».

На самом деле

Она распределяет обращения за адресом, а не запросы. Клиент, открывший HTTP/2-соединение и держащий его часами, обратился за адресом один раз на тысячи запросов. Плюс TTL никто не обязан соблюдать: RFC 1794 ещё в 1995 году отметил, что the DNS protocol doesn't guarantee ordering (протокол DNS не гарантирует порядок), а кеши резолверов, операционной системы и браузера живут своей жизнью. DNS годится, чтобы развести трафик по площадкам, и не годится, чтобы выравнивать нагрузку между машинами.

Утверждение

«Если сайт отвечает разными адресами в разных странах, это geoDNS».

На самом деле

Не обязательно, и это проверяется одним полем. RFC 7871 требует, чтобы ответ нёс SCOPE PREFIX-LENGTH: ноль означает «ответ годится всем адресам». Замер с подстановкой четырёх клиентских подсетей: d1.awsstatic.com отвечает четырьмя разными наборами адресов и ставит scope /24, а www.bing.com, www.microsoft.com и www.netflix.com отвечают всем одинаково и ставят /0. Последнее не значит, что у них нет географии, — значит, что она сделана не через DNS, а маршрутизацией (anycast).

Утверждение

«L4 быстрее L7, поэтому лучше».

На самом деле

Они балансируют разные единицы. На L4 решение принимается один раз, при установке соединения, поэтому единица — соединение: десять клиентов, из которых один шлёт 90% трафика, разложатся по десяти бэкендам поровну, и один получит девять десятых работы. На L7 единица — запрос, и та же нагрузка разложится ровно. Скорость L4 при этом реальна: Maglev на одной обычной машине is able to saturate a 10Gbps link with small packets (способен насытить канал 10 Гбит/с мелкими пакетами). Поэтому их обычно ставят вместе, а не выбирают между ними.

Утверждение

«Sticky sessions — это просто настройка, она ничего не стоит».

На самом деле

Она превращает балансировку запросов в балансировку сессий, и цена видна сразу. В модели на одинаковых серверах: без закрепления p99 = 6,06; при пяти тысячах живых сессий — 26,90; при пятистах — 206,92; при пятидесяти — 8408. Чем меньше сессий, тем крупнее зерно раскладки и тем сильнее случайный перекос. Закрепление — компенсация за состояние на сервере, а не способ распределять нагрузку.

Проверка знаний

Вопрос 1 из 6

Сервис за L4-балансировщиком. Клиенты используют HTTP/2 и держат соединения открытыми. Нагрузка на бэкенды разошлась в разы, хотя соединений у всех поровну. Наиболее вероятная причина?

Источники и что читать дальше

13 ИСТОЧНИКОВ

  1. RFC 1794 — DNS Support for Load BalancingОфициальная документация. Informational, апрель 1995, T. Brisco (Rutgers). Документ, в котором распределение нагрузки через DNS названо и разобрано впервые. Там же названо и его ограничение: «the DNS protocol doesn't guarantee ordering» (протокол DNS не гарантирует порядок), и отдельно — про TTL: «There is no use in handing out information with TTLs of an hour, when the conditions for ordering the RRs changes minutely» (Нет смысла раздавать сведения с часовым TTL, когда условия, определяющие порядок записей, меняются ежеминутно).https://www.rfc-editor.org/rfc/rfc1794.html
  2. RFC 7871 — Client Subnet in DNS QueriesОфициальная документация. Informational, май 2016. Определяет SCOPE PREFIX-LENGTH — «the leftmost number of significant bits of ADDRESS that the response covers» (число старших значащих бит адреса, которые покрывает ответ) — и оговаривает, что ноль «indicates that the answer is suitable for all addresses in FAMILY» (означает, что ответ пригоден для всех адресов данного семейства). Именно это поле отличает настоящий geoDNS от одинакового ответа всем. Там же — рекомендация выключать механизм по умолчанию и усекать адрес до /24.https://www.rfc-editor.org/rfc/rfc7871.html
  3. nginx — модуль ngx_http_upstream_moduleОфициальная документация. Формулировки алгоритмов дословно: least_conn «passes a request to the server with the least number of active connections, taking into account weights» (передаёт запрос серверу с наименьшим числом активных соединений, с учётом весов); random «two»«randomly select two servers and then choose a server using the specified method» (случайно выбирает два сервера, а затем выбирает между ними указанным методом), по умолчанию least_conn. Там же least_time с режимами header и last_byte и оговорка, что до версии 1.31.0 он был только в платной подписке.https://nginx.org/en/docs/http/ngx_http_upstream_module.html
  4. HAProxy — коммит, добавивший число жеребьёвок в balance randomИсходный код. «MINOR: backend: make the random algorithm support a number of draws» (MINOR: backend: научить случайный алгоритм произвольному числу жеребьёвок). Значение по умолчанию — два (`lbprm.arg_opt1 = 2`), и в самом сообщении коммита приём назван своим именем: Power of Two Random Choices.https://github.com/haproxy/haproxy/commit/21c741a665f
  5. Willy Tarreau — Test driving «power of two random choices» (пер.: «сила двух случайных выборов») load balancingИсточник. Блог HAProxy, 15 февраля 2019. Автор HAProxy меряет на шести ARM-машинах и приходит к неудобному выводу: при одном балансировщике least connections остаётся лучше — «about 4% higher» (примерно на 4 % выше) по запросам в секунду, — а пики соединений у него «about 30% lower» (примерно на 30 % ниже). Смысл power of two раскрывается не здесь, а в распределённой схеме.https://www.haproxy.com/blog/power-of-two-load-balancing
  6. Owen Garrett — NGINX and the «Power of Two Choices» Load-Balancing AlgorithmИсточник. 12 ноября 2018. Объяснение стадного эффекта на аналогии с очередями на паспортном контроле: «all the guides notice that one queue is momentarily shorter and faster, and all send travelers to that queue» (все сотрудники разом замечают, что одна очередь сейчас короче и движется быстрее, и все направляют людей именно в неё). Рекомендация — «for very high-performance environments and for distributed load-balancing scenarios» (для сред с очень высокими требованиями к производительности и для распределённой балансировки), прямо назван случай нескольких ingress-контроллеров.https://www.f5.com/company/blog/nginx/nginx-power-of-two-choices-load-balancing-algorithm
  7. Envoy — Supported load balancersОфициальная документация. «An O(1) algorithm which selects N random available hosts as specified in the configuration (2 by default) and picks the host which has the fewest active requests» (Алгоритм за O(1): выбирает N случайных доступных узлов, как задано в настройках (по умолчанию два), и берёт тот, у которого меньше всего активных запросов), и там же названа причина выбора: «P2C selection is particularly useful for load balancer implementations due to its resistance to herding behavior» (Выбор из двух особенно полезен в балансировщиках нагрузки благодаря устойчивости к стадному эффекту). Отдельно — что при неравных весах Envoy переключается на другую формулу.https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/load_balancers
  8. Mitzenmacher, Richa, Sitaraman — The Power of Two Random Choices: A Survey of Techniques and ResultsКнига. Теоретическая часть: при случайном выборе максимальная загрузка «is approximately log n/ log log n with high probability» (составляет примерно log n / log log n с высокой вероятностью), при выборе лучшей из d — «log log n/ log d + O(1)», результат Azar, Broder, Karlin и Upfal. И вывод, который объясняет, почему берут именно два: «each additional choice beyond two decreases the maximum load by just a constant factor» (каждый следующий выбор сверх двух уменьшает максимальную загрузку лишь на постоянный множитель).https://www.eecs.harvard.edu/~michaelm/postscripts/handbook2001.pdf
  9. Maglev: A Fast and Reliable Software Network Load BalancerИсточник. USENIX NSDI 16, Eisenbud и др. Балансировщик четвёртого уровня на обычных серверах, без специального железа: «A single Maglev machine is able to saturate a 10Gbps link with small packets» (Одна машина Maglev способна насытить канал 10 Гбит/с мелкими пакетами). В строю у Google с 2008 года; устойчивость к переездам соединений обеспечивают consistent hashing и трекинг соединений вместе, а не по отдельности.https://research.google/pubs/maglev-a-fast-and-reliable-software-network-load-balancer/
  10. Citrix NetScaler Load Balancing Algorithms (воспроизведение документации вендора)Источник. База знаний Висконсинского университета. Отсюда определения least bandwidth — «the service that is currently serving the least amount of traffic, measured in megabits per seconds» (сервис, который в данный момент обслуживает наименьший объём трафика, измеряемый в мегабитах в секунду) — и least packets, «the least packets in the last 14 seconds» (наименьшее число пакетов за последние 14 секунд). Помечено как воспроизведение: собственная страница вендора на момент написания не открылась, и выдавать пересказ за первоисточник нельзя.https://kb.wisc.edu/ns/page.php?id=13201
  11. Google SRE Book — Handling Overload и Addressing Cascading FailuresИсточник. Мост от балансировки к перегрузке: «no matter how efficient your load balancing policy, eventually some part of your system will become overloaded» (какой бы эффективной ни была ваша политика балансировки, рано или поздно какая-то часть системы окажется перегруженной). Определение отброса: «Load shedding drops some proportion of load by dropping traffic as the server approaches overload conditions» (Отбрасывание нагрузки сбрасывает некоторую долю нагрузки, отбрасывая трафик по мере приближения сервера к условиям перегрузки), конкретная механика через число незавершённых запросов и число 64 = 4³ для усиления повторов — оттуда же. Плюс предупреждение про метрику: «modeling capacity as „queries per second“ … often makes for a poor metric» (моделирование ёмкости как „запросов в секунду“ … часто оказывается плохой метрикой).https://sre.google/sre-book/addressing-cascading-failures/
  12. AWS Builders' Library — Using load shedding to avoid overload; Timeouts, retries, and backoff with jitterИсточник. Цель отброса, сформулированная через таймаут клиента: «The goal of load shedding is to keep latency low for the requests that the server decides to accept so that the service replies before the client times out» (Цель отбрасывания нагрузки — удерживать низкую задержку для тех запросов, которые сервер решил принять, чтобы сервис ответил раньше, чем у клиента истечёт таймаут). И второе число усиления повторов: «If each layer retries independently, the load on the database will increase 243x» (Если каждый слой повторяет независимо, нагрузка на базу вырастет в 243 раза), а также джиттер и ведро токенов как меры против него.https://aws.amazon.com/builders-library/using-load-shedding-to-avoid-overload/
  13. Envoy — Circuit breaking, Adaptive concurrency, Overload manager, Zone aware routing, Locality weighted LBОфициальная документация. Пять страниц, на которых стоят разделы про отброс, повторы и зоны. Различие двух защит: «This is distinct from circuit breaking which is primarily aimed at protecting upstream services» (Это отличается от размыкания цепи, которое нацелено прежде всего на защиту вышестоящих сервисов). Конфликт близости и равномерности: «send as much traffic to the local zone … while roughly maintaining the same number of requests per second across all upstream hosts» (отправить в локальную зону как можно больше трафика, сохраняя при этом примерно одинаковое число запросов в секунду по всем вышестоящим узлам). Таблица 33/67 … 0/100 и коэффициент избыточного выделения 1,4 — числа этой документации. Оттуда же порядок решения: приоритет, расположение, и только потом алгоритм.https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/zone_aware