Понякога повече е по-малко. Когато намаляването на натоварването води до увеличаване на латентността

Както в повечето публикации, има проблем с разпределена услуга, нека наречем тази услуга Alvin. Този път не открих проблема сам, информираха ме момчетата от страна на клиента.

Един ден се събудих с недоволен имейл поради големи закъснения с Алвин, който планирахме да стартираме в близко бъдеще. По-конкретно, клиентът изпита 99-ти процентил на латентност в района на 50 ms, доста над нашия бюджет за латентност. Това беше изненадващо, тъй като тествах широко услугата, особено по отношение на латентността, което е често срещано оплакване.

Преди да подложа Alvin на тестване, проведох много експерименти с 40 10 заявки в секунда (QPS), всички показващи латентност от по-малко от 40 ms. Бях готов да заявя, че не съм съгласен с техните резултати. Но като погледнах отново писмото, забелязах нещо ново: не бях тествал точно условията, които споменаха, техните QPS бяха много по-ниски от моите. Тествах на 1k QPS, но те само на XNUMXk. Проведох друг експеримент, този път с по-нисък QPS, само за да ги успокоя.

Тъй като пиша в блог за това, вероятно вече сте разбрали, че техните числа са правилни. Тествах моя виртуален клиент отново и отново със същия резултат: малък брой заявки не само увеличава латентността, но увеличава броя на заявките с латентност над 10 ms. С други думи, ако при 40k QPS около 50 заявки в секунда надвишават 50 ms, тогава при 1k QPS има 100 заявки над 50 ms всяка секунда. Парадокс!

Понякога повече е по-малко. Когато намаляването на натоварването води до увеличаване на латентността

Стесняване на търсенето

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

Понякога повече е по-малко. Когато намаляването на натоварването води до увеличаване на латентността

Добра отправна точка е списък със завършени I/O преходи (мрежови повиквания/търсене на диск и т.н.). Нека се опитаме да разберем къде е забавянето. Освен очевидния I/O с клиента, Алвин предприема допълнителна стъпка: той осъществява достъп до хранилището на данни. Това хранилище обаче работи в същия клъстер като Alvin, така че забавянето там трябва да е по-малко, отколкото при клиента. И така, списъкът на заподозрените:

  1. Мрежово обаждане от клиент до Алвин.
  2. Мрежово обаждане от Alvin към хранилището на данни.
  3. Търсене на диск в хранилището на данни.
  4. Мрежово обаждане от хранилището на данни до Алвин.
  5. Мрежово обаждане от Алвин до клиент.

Нека се опитаме да зачеркнем някои точки.

Съхранението на данни няма нищо общо с това

Първото нещо, което направих, беше да конвертирам Alvin в ping-ping сървър, който не обработва заявки. Когато получи заявка, тя връща празен отговор. Ако забавянето намалее, тогава грешка в изпълнението на Alvin или хранилище за данни не е нещо нечувано. В първия експеримент получаваме следната графика:

Понякога повече е по-малко. Когато намаляването на натоварването води до увеличаване на латентността

Както можете да видите, няма подобрение при използване на ping-ping сървъра. Това означава, че хранилището на данни не увеличава латентността и списъкът на заподозрените е намален наполовина:

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

Страхотен! Списъкът бързо намалява. Мислех, че почти разбрах причината.

gRPC

Сега е моментът да ви представим нов играч: gRPC. Това е библиотека с отворен код от Google за комуникация в процеса RPC, въпреки че gRPC добре оптимизиран и широко използван, това беше първият ми път, когато го използвах на система с такъв размер и очаквах внедряването ми да бъде неоптимално - най-малкото.

наличност gRPC в стека породи нов въпрос: може би това е моята реализация или аз gRPC причинява проблем със закъснението? Добавяне на нов заподозрян към списъка:

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

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

Забележка: Горният списък е малко опростен, защото gRPC прави възможно използването на ваш собствен (шаблон?) нишков модел, в който стекът за изпълнение е преплетен gRPC и потребителско внедряване. За по-голяма простота ще се спрем на този модел.

Профилирането ще оправи всичко

След като зачеркнах хранилищата за данни, си помислих, че почти съм готов: „Сега е лесно! Нека приложим профила и да разберем къде възниква забавянето.“ аз голям фен на прецизното профилиране, тъй като процесорите са много бързи и най-често не са тясното място. Повечето забавяния възникват, когато процесорът трябва да спре обработката, за да направи нещо друго. Accurate CPU Profiling прави точно това: то точно записва всичко контекстни превключватели и изяснява къде възникват закъснения.

Взех четири профила: с висок QPS (ниска латентност) и с пинг-понг сървър с нисък QPS (висока латентност), както от страна на клиента, така и от страна на сървъра. И за всеки случай взех и примерен профил на процесора. Когато сравнявам профили, обикновено търся аномален стек от повиквания. Например, лошата страна с висока латентност има много повече контекстни превключвания (10 пъти или повече). Но в моя случай броят на контекстните превключватели беше почти същият. За мой ужас там нямаше нищо съществено.

Допълнително отстраняване на грешки

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

Какво ако

От самото начало бях загрижен за специфичната латентност от 50 ms. Това е много голямо време. Реших, че ще изрежа части от кода, докато мога да разбера коя точно част причинява тази грешка. Тогава дойде експеримент, който проработи.

Както обикновено, погледнато назад изглежда, че всичко е очевидно. Поставих клиента на същата машина като Алвин - и изпратих заявка до localhost. И увеличението на латентността изчезна!

Понякога повече е по-малко. Когато намаляването на натоварването води до увеличаване на латентността

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

Учене на умения за мрежов инженер

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

За щастие, Интернет обича онези, които искат да учат. Комбинацията от ping и tracert изглеждаше като достатъчно добро начало за отстраняване на грешки при проблеми с мрежовия транспорт.

Първо, стартирах PsPing към TCP порта на Алвин. Използвах настройките по подразбиране - нищо особено. От повече от хиляда пинга нито един не надхвърли 10 ms, с изключение на първия за загряване. Това е в противоречие с наблюдаваното увеличение на латентността от 50 ms при 99-ия персентил: там за всеки 100 заявки трябва да сме видели около една заявка с латентност от 50 ms.

Тогава опитах Tracert: Може да има проблем в един от възлите по маршрута между Алвин и клиента. Но трайсерът също се върна с празни ръце.

Така че не моят код, внедряването на gRPC или мрежата са причинили забавянето. Започвах да се притеснявам, че никога няма да разбера това.

Сега на каква ОС сме

gRPC широко използван в Linux, но екзотичен в Windows. Реших да опитам експеримент, който проработи: създадох виртуална машина на Linux, компилирах Alvin за Linux и я внедрих.

Понякога повече е по-малко. Когато намаляването на натоварването води до увеличаване на латентността

И ето какво се случи: сървърът за пинг-понг на Linux нямаше същите забавяния като подобен хост на Windows, въпреки че източникът на данни не беше по-различен. Оказва се, че проблемът е в имплементацията на gRPC за Windows.

Алгоритъмът на Nagle

През цялото това време си мислех, че ми липсва знаме gRPC. Сега разбирам какво е всъщност gRPC Windows флаг липсва. Намерих вътрешна RPC библиотека, за която бях уверен, че ще работи добре за всички зададени флагове Привлекателен. След това добавих всички тези флагове към gRPC и внедрих Alvin в Windows, в закърпен сървър за пинг-понг на Windows!

Понякога повече е по-малко. Когато намаляването на натоварването води до увеличаване на латентността

почти Готово: Започнах да премахвам добавените флагове един по един, докато регресията се върна, за да мога да определя причината. Беше позорно TCP_NODELAY, превключвател на алгоритъма на Nagle.

Алгоритъмът на Nagle се опитва да намали броя на пакетите, изпратени по мрежа, като забавя предаването на съобщения, докато размерът на пакета надвиши определен брой байтове. Въпреки че това може да е приятно за обикновения потребител, то е разрушително за сървърите в реално време, тъй като операционната система ще забави някои съобщения, причинявайки забавяне при ниски QPS. U gRPC този флаг е зададен в изпълнението на Linux за TCP сокети, но не и в Windows. аз съм това поправено.

Заключение

По-високата латентност при ниски QPS е причинена от оптимизация на ОС. В ретроспекция профилирането не откри латентност, защото беше направено в режим на ядрото, а не в потребителски режим. Не знам дали алгоритъмът на Nagle може да се наблюдава чрез ETW улавяния, но би било интересно.

Що се отнася до експеримента с localhost, той вероятно не е докоснал действителния мрежов код и алгоритъмът на Nagle не е работил, така че проблемите със закъснението са изчезнали, когато клиентът достигне до Alvin чрез localhost.

Следващият път, когато видите увеличаване на латентността, тъй като броят на заявките в секунда намалява, алгоритъмът на Nagle трябва да бъде в списъка ви със заподозрени!

Източник: www.habr.com

Добавяне на нов коментар