惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
T
The Blog of Author Tim Ferriss
P
Proofpoint News Feed
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cybersecurity and Infrastructure Security Agency CISA
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Martin Fowler
Martin Fowler
GbyAI
GbyAI
P
Palo Alto Networks Blog
N
Netflix TechBlog - Medium
C
Cisco Blogs
Microsoft Security Blog
Microsoft Security Blog
G
Google Developers Blog
A
About on SuperTechFans
PCI Perspectives
PCI Perspectives
Scott Helme
Scott Helme
TaoSecurity Blog
TaoSecurity Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
K
Kaspersky official blog
W
WeLiveSecurity
Y
Y Combinator Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
aimingoo的专栏
aimingoo的专栏
F
Fortinet All Blogs
有赞技术团队
有赞技术团队
人人都是产品经理
人人都是产品经理
月光博客
月光博客
N
News | PayPal Newsroom
Microsoft Azure Blog
Microsoft Azure Blog
G
GRAHAM CLULEY
爱范儿
爱范儿
The GitHub Blog
The GitHub Blog
MongoDB | Blog
MongoDB | Blog
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Know Your Adversary
Know Your Adversary
博客园 - Franky
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
F
Full Disclosure
V
Vulnerabilities – Threatpost
V
Visual Studio Blog
Forbes - Security
Forbes - Security
Attack and Defense Labs
Attack and Defense Labs
MyScale Blog
MyScale Blog
Hacker News: Ask HN
Hacker News: Ask HN
T
Tor Project blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как Яндекс Диск выдерживает сотни гигабит входящего трафика: устройство балансировки загрузок
Илья Абрамов · 2026-06-01 · via Все публикации подряд на Хабре

Как Яндекс Диск выдерживает сотни гигабит входящего трафика: устройство балансировки загрузок

13 мин

9.9K

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

Эта схема отлично работает на лёгких API‑запросах, но рассыпается, как только трафик становится тяжелее. В случае с Яндекс Диском речь идёт о массовой загрузке файлов. 

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

Типичное бэкэнд приложение

Типичное бэкэнд приложение

Проблема сетевых лимитов

В современных дата‑центрах серверы обычно оснащены сетевыми картами на 10 или 25 Гбит/с.

Если в качестве балансировщика используется программное L7-решение (например, Nginx или HAProxy), оно становится узким местом:

  • Полоса пропускания: весь входящий трафик должен пройти через балансировщик. Если пользователи суммарно льют 100 Гбит/с, вам нужно либо 10 балансировщиков на полной нагрузке, либо невероятно дорогое аппаратное решение.

  • Масштабируемость: возникает архитектурный перекос. Чтобы наращивать количество бэкендов, вам приходится пропорционально увеличивать количество балансировщиков.

Кроме того мы на ровном месте увеличили нагрузку на сеть дата‑центра, ведь данные сначала передаются от пользователя в балансер, а затем те же данные пересылаются из балансера в сервис. 

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

Типичное бэкэнд приложение на масштабах Диска

Типичное бэкэнд приложение на масштабах Диска

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

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

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

Мы разделили процесс на два типа запросов:

  1. Control‑запросы (управляющие): это легковесные HTTP‑запросы. Их задача — получить добро на загрузку и узнать адрес целевого сервера. Они проходят через стандартный балансировщик, так как почти не создают сетевой нагрузки.

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

Два типа запросов

Два типа запросов

В итоге архитектура загрузки в Диске кристаллизовалась в виде двух ключевых компонентов:

  • Балансерун (Balancer). Его работа — выбрать наиболее подходящий экземпляр сервиса для конкретного пользователя. Чтобы делать это эффективно, Балансерун в реальном времени держит в памяти актуальный список всех «живых» серверов (через Service Discovery) и мониторит их состояние. На выходе он отдает пользователю уникальную ссылку на загрузку.

  • Кладун (Uploader). Это сервис, который принимает входящий data‑запрос по прямой ссылке и занимается непосредственно сохранением файла в целевое хранилище.

Схема взаимодействия выглядит так:

  1. Пользователь спрашивает у Балансеруна: «Куда мне положить файл?»

  2. Балансерун анализирует загрузку системы и отвечает: «Вот тебе ссылка на сервер № 3».

  3. Пользователь открывает прямое соединение с этим сервером и отправляет данные в Кладун.

Схема запроса

Схема запроса

Стандартные алгоритмы балансировки

Разделение на Control и Data‑запросы было придумано ради экономии сетевых ресурсов. Сеть — самый критичный и конечный ресурс в нашей системе. Если хост перегружен, пользователи либо вообще не могут загрузить файл, либо сталкиваются с катастрофическим падением скорости.

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

Моделируем Random‑балансировку

Представим систему из трех аплоадеров и одного балансировщика. Входящей нагрузки пока нет.

Приходит первый пользователь. Балансировщик выбирает узел № 1. Пользователь начинает лить файл на скорости 500 Мбит/с.

Приходит второй пользователь. Поскольку выбор случайный, есть вероятность, что он снова попадет на узел № 1. Допустим, его скорость — 700 Мбит/с. К этому моменту первый пользователь еще не закончил сессию, и суммарная нагрузка на первый узел вырастает до 1200 Мбит/с.

Приходит третий пользователь — и «рандом» снова отправляет его на первый узел. Нагрузка достигает 1700 Мбит/с.

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

Почему так происходит? Если посмотреть на это с точки зрения теории вероятностей, мы для каждого аплоадера несколько раз выбираем значение скорости из распределения и суммируем их. Согласно центральной предельной теореме, сумма этих независимых случайных величин будет иметь распределение, близкое к нормальному.

Проблемы Random балансировки

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

Проблема в том, что у нормального распределения есть стандартное отклонение. А в масштабах Яндекс Диска это отклонение означает, что всегда будут узлы, нагруженные значительно сильнее (или слабее) среднего. 

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

Может показаться, что строгая очередность (Round‑Robin) решит проблему, ведь она распределяет количество запросов идеально поровну. Но давайте смоделируем ситуацию:

У нас есть два пустых аплоадера. Первый пользователь уходит на узел № 1 и начинает лить данные на скорости 500 Мбит/с.

Второй пользователь отправляется на узел № 2. Но он зашел с мобильного интернета где‑нибудь в дороге, и его скорость — всего 10 Мбит/с.

Приходит третий пользователь. Очередь вернулась к началу, и мы снова отправляем его на узел № 1. Допустим, он добавляет еще 1000 Мбит/с.

Итог: На первом узле — 1500 Мбит/с, на втором — жалкие 10 Мбит/с. Мы снова получили критический перекос.

Проблемы Round‑Robin и Random идентичны: они никак не учитывают реальную скорость загрузки. Мы по‑прежнему суммируем случайные величины с огромным разбросом и получаем нормальное распределение с гигантской дисперсией.

Переход к балансировке с обратной связью

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

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

  • Текущую утилизацию сети;

  • Нагрузку на дисковую подсистему;

  • Размер очереди активных загрузок;

  • Другие системные параметры.

Затем мы агрегируем эти данные в единый коэффициент, который называем суммарной загрузкой узла. Теперь задача балансировщика сводится к простому правилу: при поступлении запроса выбирать узел с минимальным значением этого коэффициента.

Ловушка «слепого» периода

Давайте посмотрим, что произойдет, если мы будем просто выбирать наименее загруженный узел (алгоритм Least Loaded), опираясь на периодические отчеты от аплоадеров.

Представим систему: два балансировщика и три аплоадера. Один узел свободен, а на два других кто‑то загружает файлы со скоростью 10 Мбит/с.

Приходит первый пользователь. Балансировщик видит, что узел № 2 абсолютно пуст, и отправляет запрос туда. Пользователь начинает лить данные на скорости 500 Мбит/с.

Но есть нюанс: мы собираем метрики раз в K секунд. В этом промежутке балансировщики остаются «слепыми» — они не знают, что на втором узле только что началась тяжелая сессия.

Приходит второй пользователь, затем третий... Балансировщики, сверяясь со старыми данными, по‑прежнему считают второй узел самым свободным и продолжают направлять трафик именно туда.

Когда время K наконец проходит и мы собираем свежие метрики, оказывается, что второй узел уже безнадежно перегружен, в то время как остальные простаивали. Из‑за задержки в обратной связи система сама создает перекосы, которые должна была устранять.

Чтобы сгладить этот эффект, мы решили скорректировать логику. Вместо того чтобы всегда выбирать один «самый лучший» узел, мы будем выбирать случайный узел из N самых свободных. Такой алгоритм назовём Top‑N Random.

Top‑N Random балансировка 

Идея в том, что небольшая доля случайности размазывает нагрузку в те самые «слепые» периоды. Если в топе окажутся три примерно одинаково свободных узла, балансировщики распределят новых пользователей между ними, а не положат какой‑то один конкретный сервер.

Моделирование Top‑N Random показало, что мы лишь снижаем вероятность катастрофы, но не исключаем её. Всегда существует шанс, что несколько балансировщиков одновременно выберут один и тот же узел из топа, и мы снова получим перегруженный «хвост» распределения. С ростом N проблема сглаживается, но мы возвращаемся к тому, с чего начали — к высокой дисперсии обычного рандома.

Стало ясно: нам была нужна глобальная переработка логики. Чтобы решить эту задачу, мы запустили полноценный НИОКР совместно с аспирантами ИТМО. Мы передали им обезличенные данные и профили нагрузок Яндекс Диска, а коллеги провели глубокий аудит наших подходов.

Анализ выявил три фундаментальные проблемы:

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

  2. Сбор статистики раз в K секунд делает систему слишком медленной для динамично меняющегося трафика.

  3. Мы не учитывали два параметра запроса — скорость пользователя и размер файла.

Результатом этой работы стал алгоритм HOBA (Hierarchical Optimized Balancing Algorithm). 

HOBA‑балансировка

Архитектура НОВА строится на трёх китах, которые закрывают слабые места предыдущих подходов: 

  1. Локальные пулы. Мы распределили всех Кладунов (аплоадеров) по непересекающимся пулам. Теперь каждый балансировщик локально обновляет состояние каждого узла в своем пуле сразу в момент назначения загрузки. Балансировщик помнит, сколько работы он уже отправил на конкретный сервер, не дожидаясь подтверждения от самого сервера.

  2. Учёт скорости. Теперь мы храним и используем данные о скорости пользователя. Это может быть скорость текущей сессии, средняя скорость последних загрузок этого клиента или медианное значение по системе, если данных о пользователе нет. Балансировщик оперирует не количеством запросов, а прогнозируемой нагрузкой на канал.

  3. Изменение роли глобальной статистики. Сбор статистики раз в K секунд остался, но его роль изменилась. Теперь эти данные используются не для принятия мгновенных решений, а для корректировки «картины мира» балансировщика. Глобальные метрики исправляют накопленную погрешность локальных расчетов. Благодаря этому интервал K удалось существенно увеличить, разгрузив тем самым шину управления.

HOBA в динамике: локальное знание и глобальные риски

Представим систему из двух балансировщиков. У каждого в ведении свой непересекающийся пул из двух Кладунов“ Когда приходит запрос, Балансерун выбирает свободный узел в своем пуле и мгновенно прибавляет к его виртуальной нагрузке ожидаемую скорость пользователя. Теперь его «картина мира» актуальна в ту же миллисекунду, без ожидания отчётов по сети.

Казалось бы, задача решена. Но в распределенных системах исправление одной проблемы неизбежно подсвечивает новые:

  1. Точка отказа: если один балансировщик падает, его пул аплоадеров мгновенно выпадает из работы. Никто другой не знает о существовании этих узлов, и мощности простаивают.

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

Нам понадобился механизм динамического распределения аплоадеров по пулам балансировщиков. К алгоритму перераспределения пулов (ребалансировке) мы предъявили два жестких требования:

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

  • Минимизация миграций: каждый Балансерун накапливает уникальную локальную информацию о Кладунах в своем пуле (текущие сессии, предсказанные скорости). Если при изменении состава системы мы начнём хаотично перекидывать аплоадеров между пулами, эта информация потеряется. Нам придётся ждать следующего сбора метрик (интервал K), и в это время система снова будет «слепой».

Почему обычный хэш не работает

Первое, что приходит на ум при необходимости распределить ресурсы — это классическое хэширование с остатком от деления. Пронумеруем всех Балансерунов (0,1,..., M−1, где М — число Балансерунов в системе). Для каждого Кладуна вычислим хэш от его ID и возьмем его по модулю количества балансировщиков:

pool_id = hash(uploader_id) % M

Но как только один Балансерун падает, число M меняется. Из‑за смены делителя остаток от деления пересчитывается для всех узлов сразу. В итоге почти каждый Кладун мигрирует в новый пул.

Для нас это катастрофа: мы мгновенно теряем всю накопленную локальную статистику на всех балансировщиках. Система забывает, кто и с какой скоростью сейчас грузит файлы, и на интервал K (до следующего сбора метрик) мы остаемся совершенно беззащитными перед перегрузками.

Rendezvous Hashing

Чтобы решить проблему массовых миграций, мы внедрили алгоритм Rendezvous Hashing (также известный как Highest Random Weight Hashing).

Его логика в корне отличается от деления по модулю:

  1. Для каждой пары «Аплоадер + Балансировщик» вычисляется определённая весовая функция.

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

Если один балансировщик падает, пересчёт происходит только для тех Кладунов, которые принадлежали именно ему. Все остальные пары «Аплоадер + Балансировщик» сохраняют свои веса относительно друг друга, а значит, остаются в своих прежних пулах.

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

Борьба с перекосами пулов

Rendezvous‑хэширование отлично справляется с миграциями: если Балансерун падает, свои пулы меняют только Кладуны, которые были к нему привязаны. 

При добавлении нового узла система так же плавно перераспределяется. Благодаря равномерности хэш‑функции, в среднем мы получаем равные по размеру пулы.

Но «в среднем» — опасная ловушка для Highload‑систем. Случайность есть случайность, и в реальности мы можем столкнуться с перекосами:

  • один балансировщик может получить в свой пул три аплоадера, а другой — ни одного.

  • если часть Кладунов в одном пуле выйдет из строя, балансировщик останется с недостаточным ресурсом, в то время как соседние узлы будут простаивать.

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

Мы определили «идеальный» размер пула как:

max\_pool\_size=\lceil\frac{uploaders\_num}{balancers\_num}\rceil

Теперь процесс назначения Кладуна в пул выглядит чуть сложнее, чем просто поиск максимума:

  1. Мы вычисляем веса для всех пар «Аплоадер + Балансировщик».

  2. Проверяем балансировщик с самым высоким весом. Если его пул еще не заполнен — задача решена.

  3. Если же пул лидера уже достиг своего лимита (max_pool_size), мы просто берем следующий по величине результат весовой функции.

Пример: допустим, Кладун с ID 4 по всем расчетам должен попасть в пул к Балансеруну № 1. Но этот балансировщик уже укомплектован под завязку. Тогда алгоритм смотрит, кто выдал второй лучший результат веса — например, Балансерун № 0 — и отправляет аплоадера к нему.

Вообще, если ограничивать размер пулов только сверху, то при небольшом (но отличном от нуля) остатке от деления uploaders_num на balancers_num, можно получить ситуацию, когда пул одного балансера заполнен ровно на этот остаток. 

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

Введем число

min\_pool\_size=\lfloor\frac{uploaders\_num}{balancers\_num}\rfloor

Для начала будем назначать аплоадеры только на те балансеры, размер пулов которых меньше min_pool_size (заметим, что в конце концов каждый балансер будет иметь пул размера min_pool_size). Затем для оставшихся аплоадеров выберем балансер, дающий максимум весовой функции — тогда разница между наиболее и наименее заполненными пулами не будет превышать единицы.

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

Несмотря на все преимущества HOBA, мы не строим иллюзий — это не «серебряная пуля». Над нашими Балансерунами по‑прежнему стоит внешний L7-балансировщик. Теоретически сохраняется риск, что он отправит всех «быстрых» пользователей на один узел, а «медленных» — на другой. Вероятность такого сценария крайне мала, но она существует, и на этом уровне абстракции мы принимаем это как неизбежную погрешность.

Результаты: что нам дал новый алгоритм

Если сравнить распределение загрузки сети до и после внедрения HOBA, разница очевидна:

На графике распределения «холм» нового алгоритма (синий) стал уже и выше, что соответствует снижению стандартного отклонения на 22%. Это означает, что мы снизили количество тех самых опасных «хвостов», что привело к паданию загруженности сети в нашей системе. 

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

Выводы

К каким урокам мы пришли в процессе этого пути: 

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

  2. Типовые инструменты вроде Round Robin решают большинство задач. Начинать строить свой НИОКР стоит только тогда, когда нагрузки становятся действительно аномальными.

  3. Ищите вдохновение в смежных областях. Хорошие идеи часто лежат на поверхности, но в соседнем отделе. Rendezvous‑хэширование — классика шардирования баз данных, но, как выяснилось, оно идеально подходит и для динамического распределения пулов балансировки.