- PVSM.RU - https://www.pvsm.ru -
За протоколом QUIC чрезвычайно интересно наблюдать, поэтому мы любим писать о нем. Но если предыдущие публикации о QUIC носили больше исторический (краеведческий, если хотите) характер и матчасть, то сегодня мы рады опубликовать перевод другого толка – речь пойдет про реальное применение протокола в 2019 году. Причем речь не про малую инфраструктуру, базирующуюся в условном гараже, а про Uber, который работает почти по всему миру. Как инженеры компании пришли к решению использовать QUIC в продакшене, как проводили тесты и что увидели после раскатки в прод – под катом.
Картинки кликабельны. Приятного чтения!

Uber – это мировой масштаб, а именно 600 городов присутствия, в каждом из которых приложение полностью полагается на беспроводной интернет от более чем 4500 сотовых операторов. Пользователи ожидают, что приложение будет работать не просто быстро, а в реальном времени – чтобы обеспечить это, приложению Uber нужны низкие задержки и очень надежное соединение. Увы, но стек HTTP/2 [1] плохо себя чувствует в динамичных и склонных к потерям беспроводных сетях. Мы уяснили, что в данном случае низкая производительность напрямую связана с реализациями TCP в ядрах операционных систем.
Чтобы решить проблему, мы применили QUIC [2], современный протокол с мультиплексированием каналов, который дает нам больше контроля над производительностью транспортного протокола. В данный момент рабочая группа IETF [3] стандартизирует QUIC как HTTP/3 [4].
После подробных тестов, мы пришли к выводу, что внедрение QUIC в наше приложение сделает «хвостовые» задержки меньше по сравнению с TCP. Мы наблюдали снижение в диапазоне 10-30% для HTTPS-трафика на примере водительского и пассажирского приложений. Также QUIC дал нам сквозной контроль над пользовательскими пакетами.
В этой статье мы делимся опытом по оптимизации TCP для приложений Uber с помощью стека, который поддерживает QUIC.
Сегодня TCP – самый используемый транспортный протокол для доставки HTTPS-трафика в сети Интернет. TCP обеспечивает надежный поток байтов, тем самым справляясь с перегрузкой сети и потерями канального уровня. Широкое применение TCP для HTTPS-трафика объясняется вездесущностью первого (почти каждая ОС содержит TCP), доступностью на бОльшей части инфраструктуры (например, на балансировщиках нагрузки, HTTPS-прокси и CDN) и функциональностью «из коробки», которая доступна почти в большинстве платформ и сетей.
Большинство пользователей используют наше приложение на ходу, и «хвостовые» задержки TCP были далеки от требований нашего HTTPS-трафика в реальном времени. Проще говоря, с этим сталкивались пользователи по всему миру – на Рисунке 1 отражены задержки в крупных городах:

Рисунок 1. Величина «хвостовых» задержек варьируется в основных городах присутствия Uber.
Несмотря на то, что задержки в индийских и бразильских сетях были больше, чем в США и Великобритании, хвостовые задержки значительно больше чем задержки в среднем. И это так даже для США и Великобритании.
TCP был создан для проводных сетей, то есть с упором на хорошо предсказуемые ссылки. Однако у беспроводных сетей свои особенности и трудности. Во-первых, беспроводные сети чувствительны к потерям из-за помех и затухания сигнала. Например, сети Wi-Fi чувствительны к микроволнам, bluetooth и прочим радиоволнам. Сотовые сети страдают от потери сигнала (потери пути [5]) из-за отражения/поглощения сигнала предметами и строениями, а также от помех [6] от соседних сотовых вышек [7]. Это приводит к более значительным (в 4-10 раз) и разнообразным круговым задержкам (RTT) [8] и потерям пакетов по сравнению с проводным соединением.
Чтобы бороться с флуктуациями в полосе пропускания и потерях, сотовые сети обычно используют большие буферы для всплесков трафика. Это может приводить к чрезмерной очередности, что означает бОльшие задержки. Очень часто TCP трактует такую очередность как потерю из-за увеличенного таймаута, поэтому TCP склонен делать ретрансляцию и тем самым заполнять буфер. Это проблема известна как bufferbloat [9] (излишняя сетевая буферизация, распухание буфера [10]), и это очень серьезная проблема [11] современного интернета.
Наконец, производительность сотовой сети меняется в зависимости от оператора связи, региона и времени. На Рисунке 2 мы собрали медианные задержки HTTPS-трафика по сотам в диапазоне 2 километров. Данные собраны для двух крупнейших операторов сотовой связи в Дели, Индия. Как можно заметить, производительность меняется от соты к соте. Также производительность одного оператора отличается от производительности второго. На это влияют такие факторы как паттерны входа в сеть с учетом времени и локации, подвижность пользователей, а также сетевая инфраструктура с учетом плотности вышек и соотношения типов сети (LTE, 3G и т.д.).

Рисунок 2. Задержки на примере 2-километрового радиуса. Дели, Индия.
Также производительность сотовых сетей меняется во времени. На Рисунке 3 показана медианная задержка по дням недели. Мы также наблюдали разницу в более маленьком масштабе – в рамках одного дня и часа.

Рисунок 3. Хвостовые задержки могут значительно меняться в разные дни, но у того же оператора.
Все вышеупомянутое приводит к тому, что производительность TCP неэффективна в беспроводных сетях. Тем не менее, прежде чем искать альтернативы TCP, мы хотели выработать точное понимание по следующим пунктам:
Чтобы понять, как мы анализировали производительность TCP, давайте коротко вспомним, как TCP передает данные от отправителя получателю. Вначале отправитель устанавливает TCP-соединение, выполняя трехсторонний хендшейк [12]: отправитель отправляет SYN-пакет, ждет SYN-ACK-пакет от получателя, затем шлет ACK-пакет. Дополнительные второй и третий проходы уходят на создание TCP-соединения. Получатель подтверждает получение каждого пакета (ACK), чтобы обеспечить надежную доставку.
Если потерян пакет или ACK, отправитель делает ретрансмит после таймаута (RTO, retransmission timeout [13]). RTO рассчитывается динамически, на основании разных факторов, например, на ожидаемой задержке RTT между отправителем и получателем.

Рисунок 4. Обмен пакетами по TCP/TLS включает механизма ретрансмита.
Чтобы определить, как TCP работал в наших приложениях, мы отслеживали TCP-пакеты с помощью tcpdump [14] в течение недели на боевом трафике, идущем с индийских пограничных серверов. Затем мы проанализировали TCP-соединения с помощью tcptrace [15]. Дополнительно мы создали Android-приложение, которое шлет эмулированный трафик на тестовый сервер, максимально подражая реальному трафику. Смартфоны с этим приложением были розданы нескольким сотрудникам, кто собирал логи на протяжении нескольких дней.
Результаты обоих экспериментов были сообразны друг другу. Мы увидели высокие RTT-задержки; хвостовые значения были почти в 6 раз выше медианного значения; среднее арифметическое значение задержек – более 1 секунды. Многие соединения были с потерями, что заставляло TCP ретрансмитить 3,5% всех пакетов. В районах с перегрузкой, например, аэропорты и вокзалы, мы наблюдали 7%-ные потери. Такие результаты ставят под сомнение расхожее мнение, что используемые в сотовых сетях продвинутые схемы ретрансмиссии [16] значительно снижают потери на транспортном уровне. Ниже – результаты тестов из приложения-«симулянта»:
| Сетевые метрики | Значения |
|---|---|
| RTT, миллисекунды [50%,75%, 95%,99%] | [350, 425, 725, 2300] |
| RTT-расхождение, секунды | В среднем ~1,2 с |
| Потеря пакетов в неустойчивых соединениях | В среднем ~3.5% (7% в районах с перегрузкой) |
Почти в половине этих соединений была как минимум одна потеря пакетов, по большей части это были SYN и SYN-ACK-пакеты. Большинство реализаций TCP используют значение RTO в 1 секунду для SYN-пакетов, которое увеличивается экспоненциально для последующих потерь. Время загрузки приложения может увеличиться за счет того, что TCP потребуется больше времени на установку соединений.
В случае пакетов данных, высокие значения RTO эффективно снижают нагрузку на сеть при наличии временных потерь в беспроводных сетях. Мы выяснили, что среднее время ретрансмита – примерно 1 секунда с хвостовой задержкой почти в 30 секунд. Такие высокие задержки на уровне TCP вызывали HTTPS-таймауты и повторные запросы, что еще больше увеличивало задержку и неэффективность сети.
В то время как 75-й процентиль измеренных RTT был в районе 425 мс, 75-й процентиль для TCP был почти 3 секунды. Это намекает на то, что потери заставляли TCP делать 7-10 проходов чтобы успешно передать данные. Это может быть следствием неэффективного расчета RTO, невозможности TCP быстро реагировать на потерю последних пакетов [17] в окне и неэффективности алгоритма управления перегрузкой, который не различает беспроводные потери и потери из-за сетевой перегрузки. Ниже – результаты тестов потерь TCP:
| Статистика потери пакетов TCP | Значение |
|---|---|
| Процент соединений с как минимум 1 потерей пакета | 45% |
| Процент соединений с потерями во время установления соединения | 30% |
| Процент соединений с потерями во время обмена данными | 76% |
| Распределение задержек в ретрансмиссии, секунды [50%, 75%, 95%,99%] | [1, 2.8, 15, 28] |
| Распределение количества ретрансмиссий для одного пакета или TCP-сегмента | [1,3,6,7] |
Изначально спроектированный компанией Google, QUIC – это мультипоточный современный транспортный протокол, который работает поверх UDP. На данный момент QUIC в процессе стандартизации [18] (мы уже писали, что существует как бы две версии QUIC, любознательные могут пройти по ссылке [19] – прим. переводчика). Как показано на Рисунке 5, QUIC разместился под HTTP/3 (собственно, HTTP/2 поверх QUIC – это и есть HTTP/3, который сейчас усиленно стандартизируют). Он частично заменяет уровни HTTPS и TCP, используя UDP для формирования пакетов. QUIC поддерживает только безопасную передачу данных, так как TLS полностью встроен в QUIC.

Рисунок 5: QUIC работает под HTTP/3, заменяя TLS, который раньше работал под HTTP/2.
Ниже мы приводим причины, которые убедили нас использовать QUIC для усиления TCP:
Мы рассматривали альтернативные подходы к решению проблемы до того, как выбрать QUIC.
Первым делом мы попробовали развернуть TPC PoPs (Points of Presence), чтобы закрывать TCP-соединения ближе к пользователям. По сути, PoPs прерывает TCP-соединение с мобильным устройством ближе к сотовой сети и проксирует трафик до изначальной инфраструктуры. Завершая TCP ближе, мы потенциально можем уменьшить RTT и быть уверенными, что TCP будет более активно реагировать на динамичное беспроводное окружение. Однако наши эксперименты показали, что по большей части RTT и потери приходят из сотовых сетей и использование PoPs не обеспечивает значительного улучшения производительности.
Мы также смотрели в сторону тюнинга параметров TCP. Настройка TCP-стека на наших неоднородных пограничных серверах была трудной, так как TCP имеет несопоставимые реализации в разных версиях ОС. Было трудно это реализовать и проверить различные сетевые конфигурации. Настройка TCP непосредственно на мобильных устройствах была невозможна из-за отсутствия полномочий. Что еще более важно, фишки вроде соединений с 0-RTT и улучшенным предсказанием RTT критично важны для архитектуры протокола и поэтому невозможно добиться существенного преимущества, лишь настраивая TCP.
Наконец, мы оценили несколько основанных на UDP протоколов, которые устраняют неполадки в видеостриминге – мы хотели узнать, помогут ли эти протоколы в нашем случае. Увы, в них сильно не хватало многих настроек безопасности, а также им требовалось дополнительное TCP-подключение для метаданных и управляющей информации.
Наши изыскания показали, что QUIC – едва ли не единственный протокол, который может помочь с проблемой Интернет-трафика, при этом учитывая как безопасность, так и производительность.
Чтобы успешно встроить QUIC и улучшить производительность приложения в условиях плохой связи, мы заменили старый стек (HTTP/2 поверх TLS/TCP) на протокол QUIC. Мы задействовали сетевую библиотеку Cronet [28] из Chromium Projects [29], которая содержит оригинальную, гугловскую версию протокола – gQUIC. Эта реализация также постоянно совершенствуется, чтобы следовать последней спецификации IETF.
Сперва мы интегрировали Cronet в наши Android-приложения, чтобы добавить поддержку QUIC. Интеграция была осуществлена так, чтобы максимально снизить затраты на миграцию. Вместо того, чтобы полностью заменить старый сетевой стек, который использовал библиотеку OkHttp [30], мы интегрировали Cronet ПОД фреймворком OkHttp API. Выполнив интеграцию таким способом, мы избежали изменений в наших сетевых вызовах (который используют Retrofit [31]) на уровне API.
Подобно подходу к Android-устройствам, мы внедрили Cronet в приложения Uber под iOS, перехватывая HTTP-трафик из сетевых API [32], используя NSURLProtocol [33]. Эта абстракция, предоставленная iOS Foundation, обрабатывает протокол-специфичные URL-данные и гарантирует, что мы можем интегрировать Cronet в наши iOS-приложения без существенных миграционных затрат.
На стороне бэкенда прерывание QUIC обеспечено инфраструктурой Google Cloud Load balancing, которая использует alt-svc [12] заголовки в ответах, чтобы поддерживать QUIC. В общем случае, к каждому HTTP-запросу балансировщик добавляет заголовок alt-svc и уже он валидирует поддержку QUIC для домена. Когда клиент Cronet получает HTTP-ответ с таким заголовком, он использует QUIC для последующих HTTP-запросов к этому домену. Как только балансировщик прерывает QUIC, наша инфраструктура явно отправляет это действие по HTTP2/TCP в наши дата-центры.
Выдаваемая производительность – это главная причина нашего поиска лучшего протокола. Для начала мы создали стенд с эмуляцией сети [34], чтобы выяснить, как будет себя вести QUIC при разных сетевых профилях. Чтобы проверить работу QUIC в реальных сетях, мы проводили эксперименты, катаясь по Нью Дели, используя при этом эмулированный сетевой трафик, очень похожий на HTTP-вызовы в приложении пассажира.
Инвентарь для эксперимента:
[37]
[38]
Рисунок 6. Дорожный набор для тестов TCP vs QUIC состоял из Android-устройств с OkHttp и Cronet, облачных прокси для завершения соединений и сервера эмуляции.
Когда Google сделал QUIC доступным с помощью Google Cloud Load Balancing [39], мы использовали тот же инвентарь, но с одной модификацией: вместо NGINX, мы взяли гугловские балансировщики для завершения TCP и QUIC-соединений от устройств, а также для направления HTTPS-трафика в сервер эмуляции. Балансировщики распределены по всему миру, но используют ближайший к устройству PoP-сервер (спасибо геолокации).

Рисунок 7. Во втором эксперименте мы хотел сравнить задержку завершения TCP и QUIC: с помощью Google Cloud и с помощью нашего облачного прокси.
В итоге нас ждало несколько откровений:
[41]
[42]
Рисунок 8. Результаты двух экспериментов показывают, что QUIC значительно превосходит TCP.
Вдохновленные экспериментами, мы внедрили поддержку QUIC в наши Android и iOS-приложения. Мы провели A/B тестирование, чтобы определить влияние QUIC в городах присутствия Uber. В целом, мы увидели значимое снижение хвостовых задержек в разрезе как регионов, так и операторов связи и типа сети.
На графиках ниже показаны процентные улучшения хвостов (95 и 99 процентили) по макрорегионам и разным типам сети – LTE, 3G, 2G.
[43]
[44]
Рисунок 9. В боевых тестах QUIC превзошел TCP по задержкам.
Пожалуй, это только начало – выкатка QUIC в продакшн дала потрясающие возможности улучшить производительность приложений как в стабильных, так и нестабильных сетях, а именно:
Проанализировав производительность протокола на реальном трафике, мы увидели, что примерно 80% сессий успешно использовали QUIC для всех запросов, в то время как 15% сессий использовали сочетание QUIC и TCP. Мы предполагаем, что сочетание появилось из-за того, что библиотека Cronet переключается обратно на TCP по таймауту, так как она не может различать реальные UDP-сбои и плохие условия сети. Сейчас мы ищем решение этой проблемы, так как мы работаем над последующим внедрением QUIC.
Трафик из мобильных приложений чувствителен к задержкам, но не к полосе пропускания. Также наши приложения преимущественно используются в сотовых сетях. Основываясь на экспериментах, хвостовые задержки все еще велики, даже несмотря на использование прокси для завершения TCP и QUIC, который близко к пользователям. Мы активно ищем способы улучшить управление перегрузкой и повысить эффективность QUIC-алгоритмов восполнения потерь.
С этими и некоторыми другими улучшениями мы планируем улучшить пользовательский опыт вне зависимости от сети и региона, сделав удобный и бесшовный транспорт пакетов более доступным по всему миру.
Автор: nvpushkarskiy2
Источник [45]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/tcp/326786
Ссылки в тексте:
[1] HTTP/2: https://en.wikipedia.org/wiki/HTTP/2
[2] QUIC: https://en.wikipedia.org/wiki/QUIC
[3] IETF: https://www.ietf.org/
[4] HTTP/3: https://github.com/quicwg
[5] потери пути: https://en.wikipedia.org/wiki/Path_loss
[6] помех: https://en.wikipedia.org/wiki/Co-channel_interference
[7] сотовых вышек: http://www.artizanetworks.com/resources/tutorials/what_lteenb.html
[8] круговым задержкам (RTT): https://ru.wikipedia.org/wiki/%D0%9A%D1%80%D1%83%D0%B3%D0%BE%D0%B2%D0%B0%D1%8F_%D0%B7%D0%B0%D0%B4%D0%B5%D1%80%D0%B6%D0%BA%D0%B0
[9] bufferbloat: https://queue.acm.org/detail.cfm?id=2071893
[10] излишняя сетевая буферизация, распухание буфера: https://ru.wikipedia.org/wiki/%D0%98%D0%B7%D0%BB%D0%B8%D1%88%D0%BD%D1%8F%D1%8F_%D1%81%D0%B5%D1%82%D0%B5%D0%B2%D0%B0%D1%8F_%D0%B1%D1%83%D1%84%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F
[11] серьезная проблема: http://conferences.sigcomm.org/sigcomm/2012/paper/cellnet/p1.pdf
[12] хендшейк: https://www.inetdaemon.com/tutorials/internet/tcp/3-way_handshake.shtml
[13] retransmission timeout: https://tools.ietf.org/html/rfc6298
[14] tcpdump: https://www.tcpdump.org/
[15] tcptrace: http://www.tcptrace.org/
[16] продвинутые схемы ретрансмиссии: http://www.sharetechnote.com/html/BasicProcedure_LTE_HARQ.html
[17] последних пакетов: https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/41217.pdf
[18] процессе стандартизации: https://quicwg.github.io/base-drafts/draft-ietf-quic-transport.html
[19] могут пройти по ссылке: https://habr.com/ru/company/Voximplant/blog/430436/
[20] TLS1.3: https://www.ietf.org/blog/tls13/
[21] CUBIC: https://www.cs.princeton.edu/courses/archive/fall16/cos561/papers/Cubic08.pdf
[22] BBR: https://queue.acm.org/detail.cfm?id=3022184
[23] совершенствования: https://datatracker.ietf.org/meeting/102/materials/slides-102-iccrg-an-update-on-bbr-work-at-google-00
[24] tail loss probe: https://tools.ietf.org/html/draft-dukkipati-tcpm-tcp-loss-probe-01
[25] различения: https://www.extrahop.com/company/blog/2016/karns-algorithm/
[26] NACK: https://webrtcglossary.com/nack/
[27] SACK: https://tools.ietf.org/html/rfc2018
[28] Cronet: https://developer.android.com/guide/topics/connectivity/cronet
[29] Chromium Projects: https://www.chromium.org/Home
[30] OkHttp: https://square.github.io/okhttp/
[31] Retrofit: https://square.github.io/retrofit/
[32] API: https://developer.apple.com/documentation/foundation/nsurlsession
[33] NSURLProtocol: https://developer.apple.com/documentation/foundation/nsurlprotocol
[34] эмуляцией сети: https://code.fb.com/production-engineering/augmented-traffic-control-a-tool-to-simulate-network-conditions/
[35] NGINX: https://www.nginx.com/
[36] опубликовали: https://chromium.googlesource.com/chromium/src/+/4380217c0aa51adff8f624ba412e415e6493413a
[37] Image: https://habrastorage.org/webt/vp/qt/sx/vpqtsxbastzm3um6hjwcdz2giv8.png
[38] Image: https://habrastorage.org/webt/qc/ti/vz/qctivzpddsppzzfxhfqbkgzopyk.png
[39] Google Cloud Load Balancing: https://cloud.google.com/blog/products/gcp/introducing-quic-support-https-load-balancing
[40] сетевые переходы (hops): https://en.wikipedia.org/wiki/Hop_(networking)
[41] Image: https://habrastorage.org/webt/ne/co/7c/neco7cpjmlkjsp0uxdnwfez9cuk.png
[42] Image: https://habrastorage.org/webt/zd/gl/2d/zdgl2d_tftg33ztgy_t90ikfcui.png
[43] Image: https://habrastorage.org/webt/-7/p7/11/-7p711t91dffb4aoecw6f_woexw.png
[44] Image: https://habrastorage.org/webt/oo/q4/gr/ooq4grp5mldwwmiyxyy8poqyqug.png
[45] Источник: https://habr.com/ru/post/463073/?utm_campaign=463073&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.