Протокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивність

За протоколом QUIC дуже цікаво спостерігати, тому ми любимо писати про нього. Але якщо попередні публікації про QUIC носили більше історичний (краєзнавчий, якщо хочете) характер і матч, то сьогодні ми раді опублікувати переклад іншого штибу – мова піде про реальне застосування протоколу у 2019 році. Причому не про малу інфраструктуру, що базується в умовному гаражі, а про Uber, який працює майже по всьому світу. Як інженери компанії дійшли рішення використовувати QUIC у продакшені, як проводили тести і що побачили після розкочування в прод - під катом.

Картинки клікабельні. Приємного читання!

Протокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивність

Uber – це світовий масштаб, а саме 600 міст присутності, у кожному з яких програма повністю покладається на бездротовий інтернет від більш ніж 4500 стільникових операторів. Користувачі очікують, що програма буде працювати не просто швидко, а в реальному часі – щоб забезпечити це, програмі Uber потрібні низькі затримки та дуже надійне з'єднання. На жаль, але стек HTTP / 2 погано почувається в динамічних і схильних до втрат бездротових мереж. Ми зрозуміли, що в даному випадку низька продуктивність пов'язана з реалізаціями TCP в ядрах операційних систем.

Щоб вирішити проблему, ми застосували QUIC, Сучасний протокол з мультиплексування каналів, який дає нам більше контролю над продуктивністю транспортного протоколу. На даний момент робоча група IETF стандартизує QUIC як HTTP / 3.

Після докладних тестів, ми прийшли до висновку, що впровадження QUIC у наш додаток зробить "хвостові" затримки менше порівняно з TCP. Ми спостерігали зниження у діапазоні 10-30% для HTTPS-трафіку на прикладі водійського та пасажирського додатків. Також QUIC дав нам наскрізний контроль над пакетами користувача.

У цій статті ми ділимося досвідом оптимізації TCP для програм Uber за допомогою стека, який підтримує QUIC.

Останнє слово техніки: TCP

Сьогодні TCP – найпопулярніший транспортний протокол для доставки HTTPS-трафіку до мережі Інтернет. TCP забезпечує надійний потік байтів, тим самим справляючись з навантаженням мережі та втратами канального рівня. Широке застосування TCP для HTTPS-трафіку пояснюється всюдисущістю першого (майже кожна ОС містить TCP), доступністю на більшій частині інфраструктури (наприклад, на балансувальниках навантаження, HTTPS-проксі та CDN) та функціональністю «з коробки», яка доступна майже в більшості платформ і мереж.

Більшість користувачів використовують нашу програму на ходу, і «хвостові» затримки TCP були далекі від вимог нашого HTTPS-трафіку в реальному часі. Простіше кажучи, з цим стикалися користувачі по всьому світу – на Малюнку 1 відбито затримки у великих містах:

Протокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивність
Малюнок 1. Розмір «хвостових» затримок варіюється в основних містах присутності Uber.

Незважаючи на те, що затримки в індійських та бразильських мережах були більшими, ніж у США та Великій Британії, хвостові затримки значно більші за затримки в середньому. І це так навіть для США та Великобританії.

Продуктивність TCP повітрям

TCP був створений для провідних мереж, тобто з упором на посилання, що добре передбачаються. Однак у бездротових мереж свої особливості та труднощі. По-перше, бездротові мережі чутливі до втрат через перешкоди і загасання сигналу. Наприклад, мережі Wi-Fi чутливі до мікрохвиль, bluetooth та інших радіохвиль. Стільникові мережі страждають від втрати сигналу (втрати шляху) через відображення/поглинання сигналу предметами та будовами, а також від перешкод від сусідніх стільникових вишок. Це призводить до більш значних (у 4-10 разів) та різноманітних круговим затримкам (RTT) і втрат пакетів порівняно з провідним з'єднанням.

Щоб боротися з флуктуаціями в смузі пропускання та втрат, стільникові мережі зазвичай використовують великі буфери для сплесків трафіку. Це може призвести до надмірної черговості, що означає більші затримки. Дуже часто TCP трактує таку черговість як втрату через збільшений тайм, тому TCP схильний робити ретрансляцію і тим самим заповнювати буфер. Ця проблема відома як bufferbloat (зайва мережева буферизація, розпухання буфера), і це дуже серйозна проблема сучасного Інтернету.

Нарешті, продуктивність мережі змінюється залежно від оператора зв'язку, регіону та часу. На Малюнку 2 ми зібрали медіанні затримки HTTPS-трафіку сотами в діапазоні 2 кілометрів. Дані зібрані для двох найбільших операторів стільникового зв'язку в Делі, Індія. Як можна помітити, продуктивність змінюється від стільника до стільника. Також продуктивність одного оператора відрізняється від продуктивності другого. На це впливають такі фактори як патерни входу в мережу з урахуванням часу та локації, рухливість користувачів, а також мережна інфраструктура з урахуванням щільності вишок та співвідношення типів мережі (LTE, 3G тощо).

Протокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивність
Малюнок 2. Затримки з прикладу 2-кілометрового радіуса. Делі, Індія.

Також продуктивність стільникових мереж змінюється у часі. На малюнку 3 показана медіанна затримка по днях тижня. Ми також спостерігали різницю у меншому масштабі – протягом одного дня та години.

Протокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивність
Малюнок 3. Хвостові затримки можуть змінюватися у різні дні, але в того ж оператора.

Все вищезгадане призводить до того, що продуктивність TCP неефективна бездротових мережах. Тим не менш, перш ніж шукати альтернативи TCP, ми хотіли виробити точне розуміння за такими пунктами:

  • є TCP головним винуватцем хвостових затримок у наших додатках?
  • Чи мають сучасні мережі значні та різноманітні кругові затримки (RTT)?
  • Яким є вплив RTT і втрат на продуктивність TCP?

Аналіз продуктивності TCP

Щоб зрозуміти, як аналізували продуктивність TCP, давайте коротко згадаємо, як TCP передає дані від відправника одержувачу. Спочатку відправник встановлює TCP-з'єднання, виконуючи тристоронній хендшейк: відправник відправляє SYN-пакет, чекає на SYN-ACK-пакет від одержувача, потім шле ACK-пакет. Додаткові другий і третій проходи йдуть створення TCP-соединения. Отримувач підтверджує отримання кожного пакета (ACK), щоб забезпечити доставку.

Якщо втрачено пакет або ACK, відправник робить ретрансміт після таймууту (RTO, тайм-аут повторної передачі). RTO розраховується динамічно, на підставі різних факторів, наприклад, очікуваної затримки RTT між відправником та одержувачем.

Протокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивність
Малюнок 4. Обмін пакетами TCP/TLS включає механізму ретрансміту.

Щоб визначити, як TCP працював у наших програмах, ми відстежували TCP-пакети за допомогою TCPDOMP протягом тижня на бойовому трафіку, що йде з індійських прикордонних серверів. Потім ми проаналізували TCP-з'єднання за допомогою tcptrace. Додатково ми створили Android-додаток, який надсилає емульований трафік на тестовий сервер, максимально наслідуючи реальний трафік. Смартфони з цією програмою були роздані кільком співробітникам, хто збирав логи протягом кількох днів.

Результати обох експериментів відповідали один одному. Ми побачили високі RTT затримки; хвостові значення були майже в 6 разів вищі за медіанне значення; середнє арифметичне значення затримок – понад 1 секунду. Багато з'єднань були з втратами, що змушувало TCP ретрансмітувати 3,5% всіх пакетів. У районах з перевантаженням, наприклад, аеропорти та вокзали, ми спостерігали 7%-ві втрати. Такі результати ставлять під сумнів поширену думку, що використовуються в стільникових мережах просунуті схеми ретрансмісії Суттєво знижують втрати на транспортному рівні. Нижче – результати тестів із програми-симулянта:

Мережеві метрики
значення

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 швидко реагувати на втрату останніх пакетів у вікні та неефективності алгоритму управління навантаженням, який не розрізняє бездротові втрати та втрати через мережне навантаження. Нижче – результати тестів втрат TCP:

Статистика втрати пакетів TCP
значення

Відсоток з'єднань з як мінімум 1 втратою пакету
45%

Відсоток з'єднань із втратами під час встановлення з'єднання
30%

Відсоток з'єднань із втратами під час обміну даними
76%

Розподіл затримок у ретрансмісії, секунди [50%, 75%, 95%, 99%] [1, 2.8, 15, 28]

Розподіл кількості ретрансмісій для одного пакету або TCP-сегменту
[1,3,6,7]

Застосування QUIC

Спочатку спроектований компанією Google, QUIC - це мультипоточний сучасний транспортний протокол, який працює поверх UDP. На даний момент QUIC в процесі стандартизації (ми вже писали, що існує дві версії QUIC, допитливі можуть пройти за посиланням - прим. перекладача). Як показано на малюнку 5, QUIC розмістився під HTTP/3 (власне, HTTP/2 поверх QUIC – це і є HTTP/3, який зараз посилено стандартизують). Він частково замінює рівні HTTPS та TCP, використовуючи UDP для формування пакетів. QUIC підтримує лише безпечну передачу даних, оскільки TLS повністю вбудований у QUIC.

Протокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивність
Рисунок 5: QUIC працює під HTTP/3, замінюючи TLS, який раніше працював під HTTP/2.

Нижче ми наводимо причини, які переконали нас використовувати QUIC для посилення TCP:

  • 0-RTT встановлення з'єднання. QUIC дозволяє повторне використання авторизацій з попередніх з'єднань, знижуючи кількість хендшейків безпеки. В майбутньому TLS1.3 підтримуватиме 0-RTT, проте тристоронній TCP-хендшейк все ще буде обов'язковим.
  • подолання HoL-блокування. HTTP/2 використовує одне TCP-з'єднання для кожного клієнта, щоб покращити продуктивність, але це може призвести до HoL (head-of-line) блокування. QUIC спрощує мультиплексування та доставляє запити в програму незалежно один від одного.
  • керування перевантаженням. QUIC знаходиться на рівні додатків, дозволяючи простіше оновлювати головний алгоритм транспорту, який керує відправкою, ґрунтуючись на параметрах мережі (кількість втрат або RTT). Більшість TCP-реалізацій використовують алгоритм КУБІЧНИЙ, який не є оптимальним для трафіку, чутливого до затримок. Нещодавно розроблені алгоритми на кшталт BBR, більш точно моделюють мережу та оптимізують затримки. QUIC дозволяє використовувати BBR та оновлювати цей алгоритм у міру його вдосконалення.
  • поповнення втрат. QUIC викликає два TLP (tail loss probe) до того, як спрацює RTO – навіть коли втрати дуже відчутні. Це відрізняється від реалізації TCP. TLP ретрансмітить головним чином останній пакет (або новий, якщо є такий), щоб запустити швидке поповнення. Обробка хвостових затримок особливо корисна для того, як Uber працює з мережею, а саме для коротких, епізодичних та чутливих до затримок даних.
  • оптимізований ACK. Так як кожен пакет має унікальний послідовний номер, не виникає проблема розрізнення пакетів при їхньому ретрансміті. ACK-пакети також містять час для обробки пакета та генерації ACK на стороні клієнта. Ці особливості гарантують, що QUIC точніше розраховує RTT. ACK у QUIC підтримує до 256 діапазонів НЕК, допомагаючи відправнику бути більш стійким до перестановки пакетів та використовувати менше байтів у процесі. Вибірковий ACK (МІШОК) у TCP не вирішує цю проблему у всіх випадках.
  • міграція з'єднання. З'єднання QUIC ідентифікуються за допомогою 64-бітного ID, тому якщо клієнт змінює IP-адреси, можна далі використовувати ID старого з'єднання на новій IP-адресі, без переривань. Це дуже часта практика для мобільних програм, коли користувач перемикається між Wi-Fi і стільниковими з'єднаннями.

Альтернативи QUIC

Ми розглядали альтернативні підходи до вирішення проблеми, перш ніж вибрати 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 у платформу

Щоб успішно вбудувати QUIC та покращити продуктивність програми в умовах поганого зв'язку, ми замінили старий стек (HTTP/2 поверх TLS/TCP) на протокол QUIC. Ми задіяли мережеву бібліотеку Cronet з Chromium Projects, Що містить оригінальну, гуглівську версію протоколу - gQUIC. Ця реалізація також постійно вдосконалюється, щоб слідувати останній специфікації IETF.

Спочатку ми інтегрували Cronet у наші Android-програми, щоб додати підтримку QUIC. Інтеграцію було здійснено так, щоб максимально знизити витрати на міграцію. Замість того, щоб повністю замінити старий мережевий стек, який використовував бібліотеку OkHttp, ми інтегрували Cronet ПІД фреймворком OkHttp API. Виконавши інтеграцію у такий спосіб, ми уникли змін у наших мережевих викликах (який використовують Модифікувати) на рівні API.

Подібно до підходу до Android-пристроїв, ми впровадили Cronet у додатки Uber під iOS, перехоплюючи HTTP-трафік з мережевих API, використовуючи Протокол NSURL. Ця абстракція, надана iOS Foundation, обробляє протокол-специфічні URL-дані та гарантує, що ми можемо інтегрувати Cronet у наші iOS-додатки без істотних міграційних витрат.

Завершення QUIC на балансувальниках Google Cloud

На боці бекенда завершення QUIC забезпечено інфраструктурою Google Cloud Load balancing, яка використовує alt-svc заголовки у відповідях, щоб підтримувати QUIC. У загальному випадку, до кожного HTTP-запиту балансувальник додає заголовок alt-svc і він валідує підтримку QUIC для домену. Коли клієнт Cronet отримує HTTP-відповідь з таким заголовком, він використовує QUIC для подальших запитів HTTP до цього домену. Як тільки балансувальник завершує QUIC, наша інфраструктура явно відправляє цю дію HTTP2/TCP в наші дата-центри.

Продуктивність: результати

Продуктивність, що видається, – це головна причина нашого пошуку кращого протоколу. Для початку ми створили стенд з емуляцією мережі, щоб з'ясувати, як поводитиметься QUIC при різних мережних профілях. Щоб перевірити роботу QUIC в реальних мережах, ми проводили експерименти, катаючись Нью-Делі, використовуючи при цьому емульований мережевий трафік, дуже схожий на HTTP-дзвінки в додатку пасажира.

експеримент 1

Інвентар для експерименту:

  • тестові пристрої на Android зі стеками OkHttp та Cronet, щоб переконатися, що ми пускаємо HTTPS-трафік по TCP та QUIC відповідно;
  • сервер емуляції на базі Java, який надсилає однотипні HTTPS-заголовки у відповідях і навантажує клієнтські пристрої, щоб отримувати від них запити;
  • хмарні проксі, які фізично розташовані близько до Індії, щоб завершувати TCP та QUIC-з'єднання. У той час як для завершення TCP ми використовували зворотний проксі на NGINX, було важко знайти опенсорсний зворотний проксі для QUIC. Ми зібрали зворотний проксі для QUIC самі, використовуючи базовий стек QUIC з Chromium та опублікували його у хроміум як опенсорсний.

Протокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивністьПротокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивність
Малюнок 6. Дорожній набір для тестів TCP vs QUIC складався з Android-пристроїв з OkHttp та Cronet, хмарних проксі для завершення з'єднань та сервера емуляції.

експеримент 2

Коли Google зробив QUIC доступним за допомогою Балансування хмарного навантаження Google, ми використовували той самий інвентар, але з однією модифікацією: замість NGINX, ми взяли гуглівські балансувальники для завершення TCP та QUIC-з'єднань від пристроїв, а також для направлення HTTPS-трафіку на сервер емуляції. Балансувальники розподілені по всьому світу, але використовують найближчий до пристрою PoP-сервер (спасибі геолокації).

Протокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивність
Рисунок 7. У другому експерименті ми хотіли порівняти затримку завершення TCP та QUIC: за допомогою Google Cloud та за допомогою нашого хмарного проксі.

У результаті нас чекало кілька одкровень:

  • завершення через PoP покращило продуктивність TCP. Так як балансувальники завершують TCP-з'єднання ближче до користувачів і добре оптимізовані, це дає менші RTT, що покращує продуктивність TCP. І хоча на QUIC це позначилося менше, він все одно обійшов TCP щодо зниження хвостових затримок (на 10-30 відсотків).
  • на хвости впливають мережеві переходи (hops). Хоча наш QUIC-проксі був далі від пристроїв (затримка приблизно на 50 мс вище), ніж гуглівські балансувальники, він видавав схожу продуктивність - 15% зниження затримок проти 20% зниження в 99 відсотках у TCP. Це свідчить, що перехід на останній милі – це вузьке місце (bottleneck) у роботі мережі.

Протокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивністьПротокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивність
Рисунок 8. Результати двох експериментів показують, що QUIC значно перевищує TCP.

Бойовий трафік

Натхненні експериментами, ми впровадили підтримку QUIC у наші Android та iOS-додатки. Ми провели A/B тестування, щоб визначити вплив QUIC у містах присутності Uber. Загалом ми побачили значне зниження хвостових затримок у розрізі як регіонів, так і операторів зв'язку та типу мережі.

На графіках нижче показані відсоткові поліпшення хвостів (95 і 99 процентили) за макрорегіонами та різними типами мережі – LTE, 3G, 2G.
Протокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивністьПротокол QUIC у справі: як його впроваджував Uber, щоб оптимізувати продуктивність
Малюнок 9. У бойових тестах QUIC перевершив TCP затримки.

Тільки вперед

Мабуть, це лише початок – викочування QUIC у продакшн дала чудові можливості покращити продуктивність додатків як у стабільних, так і нестабільних мережах, а саме:

Збільшення покриття

Проаналізувавши продуктивність протоколу на реальному трафіку, ми побачили, що приблизно 80% сесій успішно використали QUIC для всіх запитів, у той час як 15% сесій використали поєднання QUIC та TCP. Ми припускаємо, що поєднання з'явилося через те, що бібліотека Cronet перемикається назад на TCP по тайму, оскільки вона не може розрізняти реальні UDP-збої та погані умови мережі. Зараз ми шукаємо вирішення цієї проблеми, оскільки ми працюємо над подальшим впровадженням QUIC.

Оптимізація QUIC

Трафік з мобільних програм чутливий до затримок, але не до смуги пропускання. Також наші програми переважно використовуються в стільникових мережах. Грунтуючись на експериментах, хвостові затримки ще великі, навіть незважаючи на використання проксі для завершення TCP і QUIC близько до користувачів. Ми активно шукаємо способи покращити управління навантаженням та підвищити ефективність QUIC-алгоритмів поповнення втрат.

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

Джерело: habr.com

Додати коментар або відгук