Понекогаш повеќе е помалку. Кога се намалува оптоварувањето резултира со зголемување на латентноста

Како во повеќето објави, има проблем со дистрибуирана услуга, да ја наречеме услугава Алвин. Овој пат сам не го открив проблемот, ме известија момците од клиентската страна.

Еден ден се разбудив со незадоволен е-пошта поради долги одложувања со Алвин, кој планиравме да го лансираме во блиска иднина. Поточно, клиентот доживеа доцнење од 99-ти перцентил во регионот од 50 ms, многу над нашиот буџет за латентност. Ова беше изненадувачки бидејќи ја тестирав услугата опширно, особено на латентност, што е вообичаена поплака.

Пред да го ставам Алвин на тестирање, извршив многу експерименти со 40 илјади прашања во секунда (QPS), сите покажуваа латентност помала од 10 ms. Бев подготвен да се изјаснам дека не се согласувам со нивните резултати. Но, гледајќи го уште еднаш писмото, забележав нешто ново: не ги тестирав точно условите што тие ги спомнаа, нивниот QPS беше многу понизок од мојот. Јас тестирав на 40k QPS, но тие само на 1k. Направив уште еден експеримент, овој пат со понизок QPS, само за да ги смирам.

Бидејќи блогирам за ова, веројатно веќе сте сфатиле дека нивните бројки се точни. Го тестирав мојот виртуелен клиент одново и одново, со истиот резултат: малиот број на барања не само што ја зголемува латентноста, туку го зголемува бројот на барања со латентност од повеќе од 10 ms. Со други зборови, ако при 40k QPS околу 50 барања во секунда надминувале 50 ms, тогаш при 1k QPS имало 100 барања над 50 ms секоја секунда. Парадокс!

Понекогаш повеќе е помалку. Кога се намалува оптоварувањето резултира со зголемување на латентноста

Стеснување на пребарувањето

Кога се соочуваме со проблем со латентност во дистрибуиран систем со многу компоненти, првиот чекор е да се создаде кратка листа на осомничени. Ајде да копаме малку подлабоко во архитектурата на Алвин:

Понекогаш повеќе е помалку. Кога се намалува оптоварувањето резултира со зголемување на латентноста

Добра почетна точка е списокот на завршени I/O транзиции (мрежни повици/пребарување на диск, итн.). Ајде да се обидеме да откриеме каде е доцнењето. Покрај очигледниот влез/излез со клиентот, Алвин презема уште еден чекор: пристапува до продавницата за податоци. Сепак, ова складирање работи во истиот кластер како и Alvin, така што латентноста таму треба да биде помала отколку кај клиентот. Значи, списокот на осомничени:

  1. Мрежен повик од клиент до Алвин.
  2. Мрежен повик од Алвин до продавницата за податоци.
  3. Пребарувајте на дискот во продавницата за податоци.
  4. Мрежен повик од складиштето на податоци до Алвин.
  5. Мрежен повик од Алвин до клиент.

Ајде да се обидеме да прецртаме некои точки.

Складирањето податоци нема никаква врска со тоа

Првото нешто што го направив беше да го конвертирам Алвин во пинг-пинг сервер кој не обработува барања. Кога ќе прими барање, враќа празен одговор. Ако латентноста се намали, тогаш грешката во имплементацијата на Alvin или складиштето на податоци не е ништо нечуено. Во првиот експеримент го добиваме следниот график:

Понекогаш повеќе е помалку. Кога се намалува оптоварувањето резултира со зголемување на латентноста

Како што можете да видите, нема подобрување при користење на серверот за пинг-пинг. Ова значи дека складиштето на податоци не ја зголемува латентноста, а листата на осомничени е преполовена:

  1. Мрежен повик од клиент до Алвин.
  2. Мрежен повик од Алвин до клиент.

Одлично! Списокот брзо се намалува. Мислев дека речиси ја сфатив причината.

gRPC

Сега е време да ве запознаам со нов играч: gRPC. Ова е библиотека со отворен код од Google за комуникација во процесот Rpc... Иако gRPC добро оптимизиран и широко користен, ова беше мојот прв пат да го користам на систем од оваа големина и очекував мојата имплементација да биде неоптимална - во најмала рака.

достапност gRPC во магацинот доведе до ново прашање: можеби тоа е мојата имплементација или јас gRPC предизвикува проблем со латентност? Додавање нов осомничен на списокот:

  1. Клиентот ја повикува библиотеката gRPC
  2. Библиотека gRPC прави мрежен повик до библиотеката на клиентот gRPC на серверот
  3. Библиотека gRPC контакти Алвин (без операција во случај на пинг-понг сервер)

За да ви дадам идеја за тоа како изгледа кодот, имплементацијата на мојот клиент/Алвин не се разликува многу од оние клиент-сервер асинхрони примери.

Забелешка: Горенаведената листа е малку поедноставена затоа што gRPC овозможува користење на ваш сопствен (шаблон?) модел на нишки, во кој е испреплетен оџакот за извршување gRPC и корисничка имплементација. Заради едноставност, ќе се задржиме на овој модел.

Профилирањето ќе поправи се

Откако ги пречкртав продавниците за податоци, помислив дека речиси сум готов: „Сега е лесно! Ајде да го примениме профилот и да дознаеме каде се случува доцнењето“. Јас голем љубител на прецизното профилирање, бидејќи процесорите се многу брзи и најчесто не се тесно грло. Повеќето одложувања се случуваат кога процесорот мора да престане со обработка за да направи нешто друго. Прецизното профилирање на процесорот го прави токму тоа: прецизно снима сè контекстуални прекинувачи и јасно дава до знаење каде се случуваат одложувања.

Зедов четири профили: со висок QPS (ниска латентност) и со пинг-понг сервер со низок QPS (висока латентност), и на страната на клиентот и на страната на серверот. И за секој случај, земав и примерок од профилот на процесорот. Кога ги споредувам профилите, обично барам аномален куп на повици. На пример, на лошата страна со висока латентност има многу повеќе контекстуални прекинувачи (10 пати или повеќе). Но, во мојот случај, бројот на прекинувачи на контекст беше речиси ист. За мое ужас, таму немаше ништо значајно.

Дополнително дебагирање

Бев очајна. Не знаев кои други алатки можам да ги користам, а мојот следен план беше во суштина да ги повторам експериментите со различни варијации наместо јасно да го дијагностицирам проблемот.

Што ако

Од самиот почеток, бев загрижен за специфичната латентност од 50 ms. Ова е многу големо време. Решив дека ќе исечам делови од кодот додека не можам да сфатам точно кој дел ја предизвикува оваа грешка. Потоа дојде експеримент кој успеа.

Како и обично, во ретроспектива се чини дека сè беше очигледно. Го ставив клиентот на истата машина како и Алвин - и испратив барање до localhost. И зголемувањето на латентноста го нема!

Понекогаш повеќе е помалку. Кога се намалува оптоварувањето резултира со зголемување на латентноста

Нешто не беше во ред со мрежата.

Учење мрежни инженерски вештини

Морам да признаам: моето знаење за мрежните технологии е страшно, особено ако се земе предвид фактот дека со нив работам секој ден. Но, мрежата беше главниот осомничен и требаше да научам како да ја дебагирам.

За среќа, Интернетот ги сака оние кои сакаат да учат. Комбинацијата на пинг и трасерт се чинеше како доволно добар почеток за дебагирање на проблемите со транспортот на мрежата.

Прво, лансирав Psping до TCP портата на Алвин. Ги користев стандардните поставки - ништо посебно. Од повеќе од илјада пингови, ниту еден не надмина 10 ms, со исклучок на првиот за загревање. Ова е спротивно на забележаното зголемување на латентноста од 50 ms на 99-от перцентил: таму, на секои 100 барања, требаше да видиме околу едно барање со латентност од 50 ms.

Потоа се обидов трагач: Може да има проблем на еден од јазлите долж маршрутата помеѓу Алвин и клиентот. Но и трагачот се вратил со празни раце.

Значи, не беше мојот код, имплементацијата на gRPC или мрежата што го предизвикуваше доцнењето. Почнав да се грижам дека никогаш нема да го разберам ова.

Сега на кој оперативен систем сме

gRPC широко користен на Linux, но егзотичен на Windows. Решив да пробам експеримент, кој успеа: создадов виртуелна машина Линукс, го составив Алвин за Линукс и го распоредив.

Понекогаш повеќе е помалку. Кога се намалува оптоварувањето резултира со зголемување на латентноста

И еве што се случи: пинг-понг серверот Линукс ги немаше истите доцнења како сличен Windows хост, иако изворот на податоци не се разликуваше. Излегува дека проблемот е во имплементацијата на gRPC за Windows.

Нагловиот алгоритам

Цело ова време мислев дека ми фали знаме gRPC. Сега разбирам што всушност е тоа gRPC Знамето на Windows недостасува. Најдов внатрешна RPC библиотека за која бев убеден дека ќе работи добро за сите поставени знамиња Winsock. Потоа ги додадов сите овие знамиња на gRPC и го распоредив Алвин на Windows, во закрпен Windows пинг-понг сервер!

Понекогаш повеќе е помалку. Кога се намалува оптоварувањето резултира со зголемување на латентноста

Речиси Готово: Почнав да ги отстранувам додадените знаменца едно по едно додека не се врати регресијата за да можам точно да ја одредам причината. Тоа беше неславно TCP_NODELAY, Nagle-овиот алгоритамски прекинувач.

Нагловиот алгоритам се обидува да го намали бројот на пакети испратени преку мрежа со одложување на преносот на пораки додека големината на пакетот не надмине одреден број бајти. Иако ова може да биде убаво за просечниот корисник, тоа е деструктивно за серверите во реално време бидејќи оперативниот систем ќе одложи некои пораки, предизвикувајќи заостанување на низок QPS. У gRPC ова знаме беше поставено во имплементацијата на Linux за TCP приклучоците, но не и во Windows. Јас сум ова поправена.

Заклучок

Поголемата доцнење при низок QPS беше предизвикана од оптимизацијата на ОС. Во ретроспектива, профилирањето не откри латентност бидејќи беше направено во режим на јадро наместо во кориснички режим. Не знам дали алгоритмот на Nagle може да се набљудува преку ETW фаќања, но би било интересно.

Што се однесува до експериментот на локалниот хост, тој веројатно не го допрел вистинскиот мрежен код и алгоритмот на Нагл не работи, така што проблемите со латентноста исчезнаа кога клиентот стигна до Алвин преку локалниот хост.

Следниот пат кога ќе видите зголемување на латентноста бидејќи бројот на барања во секунда се намалува, алгоритмот на Nagle треба да биде на вашата листа на осомничени!

Извор: www.habr.com

Додадете коментар