Некоторое время назад мне пришла задача спроектировать высокопроизводительный балансировщик нагрузки для протокола Diameter. Через 3 месяца задача исчезла, так как как оказалось слишком долго и дорого, но в итоге балансировщик был сделан и имеется его MVP которое готово к установке как на реальное железо так и в облаке. В итоге продукт получился неплохим, и он с лихвой выигрывал имееющееся решение в той компании, по производительности был выигрыш раз в 6 по ресурсам раз в 100. Но суть не в этом, а в том что балансировщик был кластерным и умел хранить сессии в Redis. Решение было сделано, оно показало работоспособность, все хорошо, но меня не устраивала производительность. В тот момент я столкнулся с очень интересной проблемой - по каким то причинам я не мог пробить барьер в 5-7К Diameter Transactions per second. В принципе 5-7К TPS было неплохо, но проблемным участком как показал анализ был Redis. Проведя немного времени я смог добиться приемлемого результать и производительность поднялась до 20К TPS но там были другие проблемы. Читать далее
Некоторое время назад передо мной встала задача спроектировать высокопроизводительный балансировщик нагрузки для протокола Diameter. Через три месяца проект на стороне заказчика свернули — решение показалось слишком долгим и дорогим в разработке. Тем не менее, я довел дело до конца и собрал MVP, готовый к развертыванию как на «голом железе», так и в облачной инфраструктуре.
Продукт получился отличным: он с лихвой выигрывал у имевшегося в компании решения, превосходя его по производительности примерно в 6 раз, а по эффективности использования ресурсов — почти в 100 раз.
Но речь сейчас не о самом балансировщике, а об одной из его ключевых частей. Балансировщик был кластерным и хранил сессии в Redis. Система работала, функциональность была подтверждена, но меня категорически не устраивала производительность. Я столкнулся с удручающим барьером: по непонятным на первый взгляд причинам система упиралась в 5 000–7 000 Diameter Transactions Per Second (TPS).
Профилирование показало, что узким местом был именно Redis. Потратив некоторое время на оптимизацию взаимодействия, я сумел поднять производительность балансировщика до 20 000 TPS, но это решение заставило пойти на компромиссы и принесло новые проблемы.
Причины создания HurriCacheПроведя детальный анализ, я выяснил: без пайплайнинга Redis физически не способен отдавать данные быстрее 5 000–6 000 RPS на одно соединение/поток. Мой балансировщик аккуратно подтвердил эту цифру, упершись ровно в данный потолок.
Чтобы преодолеть ограничение, я перепробовал разные подходы: от нативного пайплайнинга до группировки запросов в батчи. Желанные 20 000 TPS для балансировщика в итоге были достигнуты, и формальная цель проекта была выполнена. Однако меня зацепил исследовательский вопрос: какова реальная (а не маркетинговая) пропускная способность Redis на одной ноде?
Серия синтетических тестов дала следующие результаты:
Тип операции | Без пайплайна (Single Request) | С пайплайном (Pipelined) |
Чтение | 5К RPS | 60К RPS |
Запись | 3К RPS | 20К RPS |
Эта таблица наглядно объясняет, почему пробить потолок в 20 000 TPS на реальной бизнес-логике балансировщика оказалось настолько сложно.
В этот момент и родилась идея: а что если спроектировать собственное in-memory хранилище — похожее на Redis по возможностям, но избавленное от его фундаментальных сетевых и архитектурных узких мест?
Так началась разработка HurriCache.
Архитектурный фундамент HurriCacheОдна из главных проблем Redis — невозможность переиспользовать сетевое соединение до тех пор, пока из него не будут полностью вычитаны данные предыдущего ответа (отсутствие честного мультиплексирования). Из-за этого использовать Redis без пулов соединений (Connection Pools) в высоконагруженных системах практически бессмысленно. Пулы частично решают проблему, но создают оверхед на менеджмент соединений и контекст-свичи.
Я решил сразу строить транспортный слой на протоколе с нативной поддержкой мультиплексирования.
В качестве фундамента был выбран gRPC + Protocol Buffers. Это дало возможность гонять тысячи параллельных запросов через одно TCP-соединение без блокировок и оверхеда на парсинг текстового протокола RESP.
Далее я спроектировал Protobuf-интерфейс, который закрывает большинство привычных примитивов Redis и добавляет то, чего в нем исторически не хватало:
Basic Key-Value (строки, бинарные данные);
Lists (массивы и связные списки);
Queues (очереди с гарантией порядка);
HashSet / HashMap;
OrderedSet / OrderedMap;
Atomics (полный набор атомарных операций, включая Compare-And-Swap / CAS);
Locks (поддержка READ/WRITE/GLOBAL locks в зависимости от клиента)
Нативная кластеризация «из коробки».
В процессе разработки выяснилось множество интересных деталей о поведении стандартных C++ контейнеров под экстремальной нагрузкой.
Например, популярные решения std::unordered_map и absl::flat_hashmap на моих синтетических профилях вызовов показывали практически одинаковую производительность, упираясь в промахи по кэшу процессора.
После долгой борьбы с L1/L2/L3 cache misses и тонкой настройки процессорных инструкций префетчинга (prefetch), мне пришлось написать собственную реализацию flat_hashmap, оптимизированную под специфику аллокаций HurriCache.
(Детальный разбор реализации этой хэш-таблицы, борьбы с cache miss и работы с memory arenas я планирую вынести в отдельную техническую статью, иначе этот материал получится бесконечным).
Что получилось в итогеНа текущий момент HurriCache уверенно работает в кластерном режиме. Вот реальные показатели производительности на одну ноду без ухищрений с пайплайнингом:
Тип операции | HurriCache (Без пайплайнинга) | Redis (Без пайплайнинга) | Redis (С пайплайном) |
Чтение (Read) | 85 000+ RPS | ~5 000 RPS | ~60 000 RPS |
Запись (Write) | 77 000+ RPS | ~3 000 RPS | ~20 000 RPS |
ЗаключениеПримечание: Концепция классического пайплайнинга в HurriCache просто не требуется — мультиплексирование gRPC позволяет насыщать канал асинхронными запросами без искусственной задержки на сбор батча.
Как первое приближение и MVP, HurriCache показал себя крайне интересным и жизнеспособным решением. Нам удалось не просто догнать Redis с пайплайнингом, а превзойти его показатели (особенно на операциях записи: 77K RPS против 20K RPS), сохранив при этом прозрачность синхронного кода без необходимости собирать ручные батчи на стороне клиента.
Сейчас проект активно развивается: впереди создание клиентов на разных языках программирования. На данный момент уже реализован клиент на Java. Благодаря выбору стек-технологий gRPC + Protocol Buffers, поддержка которых есть практически везде, реализация клиентов под другие языки не должна вызывать особых сложностей.
Буду рад ответить на вопросы по архитектуре в комментариях!
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Как Яндекс Диск выдерживает сотни гигабит входящего трафика: устройство балансировки загрузок | 1 | 8.04 | 01-06-2026 |
| 2 | От полной выгрузки к S3 и PostgreSQL: как мы доставляем гигабайты данных в память подов | 5 | 7 | 06-07-2026 |
| 3 | Valkey и Redis: два года спустя — за кем будущее? | 0 | 7.32 | 22-06-2026 |
| 4 | Чат на сайте стоит дороже, чем кажется: как я замерил его цену и переписал виджет под результат | 0 | 9.58 | 28-07-2026 |
| 5 | Я делал «nginx для телеграм‑ботов», а получился конструктор шлюзов на Rust | 0 | 9.39 | 06-07-2026 |
| 6 | Считаем пакеты на Rust | 0 | 9.87 | 04-07-2026 |
| 7 | librats: Выпуск версии 2.0.x (библиотека для распределённых P2P-приложений). Так же релиз rats-search 2.1.7 и rasync | 0 | 12.02 | 03-08-2026 |
| 8 | Читаю на Интерфаксе статью академика РАН Арбатова, руководителя Центра международной ... | 0 | 9.49 | 30-07-2026 |
| 9 | После установки патча 2-50 для моего движка у «умного ИИ» ... | 0 | 7 | 30-07-2026 |
| 10 | Нулевой баланс больше не страшен: что даст россиянам проект "Доступный интернет" | 0 | 0 | 08-04-2020 |