В современном мире распределённых систем и микросервисов gRPC является одним из наиболее популярных инструментов для реализации высокопроизводительных и надежных коммуникаций между сервисами. Благодаря использованию HTTP/2 и поддержке эффективного двунаправленного обмена сообщениями, gRPC предлагает значительные преимущества в сравнении с REST и другими протоколами. Однако даже при самых лучших технических характеристиках протокола и сетей с низкой задержкой поведение клиента gRPC может неожиданно превратиться в узкое место, ограничивающее общую производительность системы. Само по себе это явление стало открытием для команды разработчиков YDB — открытой высоконадежной распределённой SQL-базы данных, использующей gRPC для взаимодействия с клиентскими приложениями. Исследования выявили, что при уменьшении количества узлов кластера клиенты испытывают возрастание латентности и наблюдается простой на стороне сервера, несмотря на увеличенные задержки на клиентской стороне.
Такое противоречивое поведение послужило поводом для глубокого анализа причин и разработки оптимизаций. По своей природе gRPC клиент строится вокруг понятий каналов и потоков. Каждый gRPC канал обычно устанавливает собственное TCP-соединение с сервером, управляя несколькими одновременными RPC-запросами, реализованными в виде потоков HTTP/2. При этом существует ограничение на максимальное число активных потоков в рамках одного TCP-соединения. Если это ограничение достигается, новые запросы ставятся в очередь, что приводит к дополнительным задержкам.
Официальные рекомендации gRPC по оптимизации производительности советуют либо создавать отдельные каналы для областей с высокой нагрузкой, либо использовать пул каналов с уникальными аргументами конфигурации для равномерного распределения загрузки между TCP-соединениями. В случае YDB применялся первый подход — отдельный канал на каждый рабочий поток. Но неожиданно на практике это не приводило к значительному улучшению. Анализ сетевых трасс показал, что независимо от числа каналов и рабочих потоков, клиент всё равно использует лишь одно TCP-соединение, а RPC-запросы оказываются упакованными и передаваемыми пакетами, что создаёт дополнительные паузы в 150-200 микросекунд между отправками пакетов. Именно эти задержки и формировали «бутылочное горлышко» в производительности.
Для исследования проблемы была разработана простая микробенчмарковая программа на C++ с использованием асинхронного и синхронного gRPC API. Тестирование проходило на двух физических машинах с процессорами Intel Xeon Gold и сетевым каналом 50 Гбит/с, что позволяло обеспечить практически идеальные условия передачи и исключить задержки, связанные с сетью. Анализ данных показал, что наивысшие показатели пропускной способности и минимальные задержки достигаются при условии, что у каждого рабочего потока есть свой собственный gRPC канал, при этом каналы конфигурируются с уникальными параметрами, предотвращающими совместное использование подканалов внутри gRPC. В частности, включение опции GRPC_ARG_USE_LOCAL_SUBCHANNEL_POOL позволило добиться масштабирования производительности более чем в шесть раз, а рост задержек при увеличении числа параллельных запросов стал минимальным. Применение мультиканального подхода — когда множество TCP-соединений активны одновременно — нивелировало проблемы, связанные с ограничением числа параллельных потоков в HTTP/2.
Особенно выраженным эффект был в сетях с низкой задержкой, где клиентское программное обеспечение не позволяло достичь идеальной производительности именно из-за внутренней структурной организации gRPC. Интересно, что при увеличении искусственной сетевой задержки до 5 миллисекунд разрыв между производительностью одноканального и мультиканального режимов существенно снижается. Это свидетельствует о том, что в условиях реальных «дальних» сетей с высокой латентностью ограничивающим фактором становится сама сеть, а не внутренняя архитектура клиента. Тем не менее, в современных дата-центрах и облачных платформах задачи требуют максимальной производительности при минимальных задержках на грани физических возможностей, поэтому правильная настройка клиентской стороны gRPC становится ключевым аспектом оптимизации. Результаты исследования имеют практическое значение не только для разработчиков YDB, но и для широкого сообщества, использующего gRPC для построения распределённых систем и микросервисных архитектур.
Преподнесённый опыт демонстрирует, что даже широко используемые инструменты могут иметь скрытые «узкие места», обнаружение и устранение которых обеспечивает значительный рост производительности. Важно отметить, что оптимизация gRPC клиента до сих пор остаётся частью более широкой области исследований в сфере сетевого взаимодействия и программных протоколов. Помимо мультиканального подхода, возможны и другие направления для улучшения, включая тонкую настройку параметров потоков, оптимизацию обработки очередей, полное использование преимуществ асинхронной модели и аппаратное ускорение. Команда YDB приглашает к сотрудничеству и обмену опытом всех заинтересованных специалистов, поскольку вопросы масштабирования высокопроизводительных систем требуют объединённых усилий и постоянного обмена знаниями. В заключение стоит отметить, что на пути к созданию действительно эффективных распределённых систем важно не только выбирать современные протоколы и технологии, но и внимательно исследовать все слои стека, выявлять неожиданные узкие места и применять комплексные решения.
Опыт, полученный в ходе анализа граничных сценариев работы gRPC, является ярким примером, как глубокий технический разбор способен раскрыть точки роста в, казалось бы, уже зрелых технологиях и открыть новые горизонты для оптимизации систем с низкой латентностью и высокой пропускной способностью.