gRPC — это современный фреймворк для межсервисного взаимодействия, основанный на HTTP/2, который широко применяется для построения распределённых приложений и систем с высокой нагрузкой. Он обеспечивает удобный и продуктивный способ организации удалённых вызовов процедур (RPC), поддерживает разные языки программирования и отлично подходит для масштабируемых решений. Однако даже при его высокой производительности в условиях реального использования можно столкнуться с непривычными ограничениями. Недавние исследования, проведённые специалистами YDB, открыли любопытный и неожиданный узкий участок в производительности gRPC со стороны клиента, проявляющийся именно в сетях с очень низкой задержкой. Несмотря на то, что обычно проблемы возникают на серверной стороне или в сети, оказалось, что сжатие инфраструктуры кластера и уменьшение количества узлов приводят не к ускорению, а к ухудшению показателей.
В частности, при уменьшении числа узлов наблюдается увеличение латентности на стороне клиента, а также рост простаивающих ресурсов на стороне сервера, что ставит под вопрос саму эффективность нагрузки системы. Суть нового открытия в том, что по умолчанию gRPC создаёт ограниченное число TCP-соединений между клиентом и сервером, причём все каналы gRPC могут использовать общий TCP-сокет посредством мультиплексирования HTTP/2. Вследствие этого возникает ограничение на количество одновременных потоков, так как HTTP/2 вводит лимит по числу конкурентных потоков на соединение, который в большинстве реализаций составляет по умолчанию около 100. Когда количество активных запросов выходит за пределы этого лимита, лишние запросы помещаются в очередь и не отправляются, пока все текущие не будут завершены. Такой подход приводит к тому, что рост параллелизма на стороне клиента не даёт ожидаемого прироста пропускной способности, а задержка ощутимо возрастает.
Детальный разбор проблемы был проведён с использованием специального микро-бенчмарка, реализованного на C++. Тесты проводились на двух мощных серверах с процессорами Intel Xeon Gold, соединённых высокоскоростной сетью с пропускной способностью 50 Гбит/с и минимальной задержкой порядка нескольких микросекунд. В рамках экспериментов изучалась зависимость пропускной способности и латентности от количества одновременных запросов (in-flight) и количества используемых каналов gRPC с различными параметрами конфигурации. Стандартный сценарий, когда клиенты используют единственный канал и соответствующее TCP-соединение, показал существенное снижение эффективности с ростом числа одновременных запросов. При увеличении количества рабочих клиентов в 10 раз производительность выросла всего в 3,7 раза.
Более того, задержка на стороне клиента линейно увеличивалась с увеличением параллелизма, достигая значений, в несколько раз превышающих собственную минимальную задержку сети. Анализы сетевого трафика показали, что проблема не связана с ограничениями сети или TCP-стеком — отсутствовали признаки потери пакетов, задержек в подтверждениях и иных сетевых проблем. Основной «узкий горлышко» скрывался именно внутри gRPC на стороне клиента. Дальнейшие эксперименты с разделением каналов и использованием пула каналов, каждый из которых создавал отдельное TCP-соединение, продемонстрировали значительное улучшение. Эксперименты с включением специального параметра GRPC_ARG_USE_LOCAL_SUBCHANNEL_POOL, который заставляет создавать группы локальных субканалов, позволили добиться оптимального баланса между количеством каналов и эффективным распределением запросов.
Результаты показали ускорение примерно в 6 раз по пропускной способности для обычных RPC и около 4,5 раза — для стриминговых вызовов. Задержка при этом росла значительно медленнее, позволяя системы достигать высокой масштабируемости без потерь качества отклика. Интересно отметить, что в условиях сетей с более высокой задержкой (около 5 миллисекунд) подобный узкий профиль не наблюдается. В таких случаях производительность зависит не от количества TCP-соединений и внутренней мультиплексии, а ограничивается самой производительностью сети и временем отклика на стороне сервера. Это объясняет, почему для многих распределённых систем с более высокой латентностью явных ограничений на стороне клиента gRPC нет, и они могут успешно работать с одним соединением.
С практической точки зрения для разработчиков и архитекторов систем, использующих gRPC в условиях низких задержек, подобное открытие крайне важно. Оно позволяет переосмыслить стратегию работы с каналами и их конфигурацию. Традиционные рекомендации, предлагающие использовать либо отдельный канал на отдельный участок высокой нагрузки, либо пул каналов, в данном случае оказываются частями единого решения. Создание отдельного канала с уникальными параметрами для каждого рабочего потока и активация локального пула субканалов позволяет снизить накладные задержки и значительно повысить общую эффективность обмена данными. Это особенно актуально для высокопроизводительных баз данных и распределённых хранилищ данных, таких как YDB, где низкая задержка и высокая пропускная способность критичны для пользовательского опыта и масштабируемости.
Переход на оптимизированную модель клиентских подключений gRPC помогает увеличить нагрузку на имеющиеся узлы кластера, снизить простои ресурсов и добиться более равномерного распределения запросов. В дополнение к указанным оптимизациям, важно также помнить о необходимости тщательной настройки NUMA-афинити и распределения потоков внутри серверных машин, так как это влияет на стабильность и производительность. Также рекомендовано использовать современные версии gRPC и следить за обновлениями, поскольку сообщество и разработчики постоянно работают над улучшениями в области производительности. В конечном итоге выявление и устранение клиентского узкого места в gRPC стало еще одним подтверждением классической мысли о том, что улучшать нужно именно те участки, где кроется настоящая проблема. Повышение пропускной способности за счёт перераспределения каналов и создания отдельного соединения для каждого рабочего потока доказывает необходимость комплексного подхода и глубокого анализа подсистем в высокопроизводительных распределённых сервисах.
Для инженеров и специалистов, использующих gRPC, полезно включить описанную стратегию в процессы мониторинга и оптимизации, а также рассмотреть возможность участия в развитии соответствующих open-source проектов. Специальный микро-бенчмарк, разработанный командой YDB, доступен в открытом доступе и может быть использован для самостоятельной проверки и воспроизведения выявленного узкого места на собственных системах. Таким образом, понимание внутренностей gRPC и правильная конфигурация каналов позволят достичь максимальной производительности и минимальной задержки, что является ключевым условием успеха современных распределённых приложений, работающих в реальном времени в условиях высокоскоростных локальных сетей.