Soms is meer minder. Wanneer het verminderen van de belasting resulteert in een toenemende latentie

Zoals in de meeste berichten, er is een probleem met een gedistribueerde service, laten we deze service Alvin noemen. Deze keer heb ik het probleem niet zelf ontdekt, de jongens van de klantzijde hebben mij geïnformeerd.

Op een dag werd ik wakker met een ontevreden e-mail vanwege lange vertragingen bij Alvin, die we in de nabije toekomst wilden lanceren. Concreet ondervond de klant een latentie van het 99e percentiel in de buurt van 50 ms, ruim boven ons latentiebudget. Dit was verrassend omdat ik de service uitgebreid heb getest, vooral op latentie, wat een veelgehoorde klacht is.

Voordat ik Alvin ging testen, heb ik veel experimenten uitgevoerd met 40 queries per seconde (QPS), die allemaal een latentie van minder dan 10 ms vertoonden. Ik was bereid te verklaren dat ik het niet eens was met hun resultaten. Maar toen ik nog eens naar de brief keek, merkte ik iets nieuws op: ik had de voorwaarden die ze noemden niet precies getest, hun QPS was veel lager dan de mijne. Ik heb getest op 40k QPS, maar zij alleen op 1k. Ik heb nog een experiment uitgevoerd, dit keer met een lagere QPS, gewoon om ze te sussen.

Omdat ik hierover blog, ben je er waarschijnlijk al achter dat hun cijfers klopten. Ik heb mijn virtuele client keer op keer getest, met hetzelfde resultaat: een laag aantal verzoeken verhoogt niet alleen de latentie, maar verhoogt ook het aantal verzoeken met een latentie van meer dan 10 ms. Met andere woorden, als bij 40k QPS ongeveer 50 verzoeken per seconde de 50 ms overschreden, dan waren er bij 1k QPS 100 verzoeken boven de 50 ms per seconde. Paradox!

Soms is meer minder. Wanneer het verminderen van de belasting resulteert in een toenemende latentie

De zoekopdracht beperken

Wanneer u wordt geconfronteerd met een latentieprobleem in een gedistribueerd systeem met veel componenten, is de eerste stap het maken van een korte lijst met verdachten. Laten we wat dieper ingaan op de architectuur van Alvin:

Soms is meer minder. Wanneer het verminderen van de belasting resulteert in een toenemende latentie

Een goed startpunt is een lijst met voltooide I/O-overgangen (netwerkoproepen/opzoeken van schijven, enz.). Laten we proberen erachter te komen waar de vertraging zit. Naast de voor de hand liggende I/O met de klant, zet Alvin nog een extra stap: hij krijgt toegang tot de datastore. Deze opslag werkt echter in hetzelfde cluster als Alvin, dus de latentie daar zou minder moeten zijn dan bij de client. Dus de lijst met verdachten:

  1. Netwerkoproep van klant naar Alvin.
  2. Netwerkoproep van Alvin naar de dataopslag.
  3. Zoek op schijf in het gegevensarchief.
  4. Netwerkoproep van het datawarehouse naar Alvin.
  5. Netwerkoproep van Alvin naar een klant.

Laten we proberen enkele punten door te strepen.

Gegevensopslag heeft er niets mee te maken

Het eerste dat ik deed was Alvin omzetten naar een ping-ping-server die geen verzoeken verwerkt. Wanneer het een verzoek ontvangt, retourneert het een leeg antwoord. Als de latency afneemt, is een bug in de Alvin- of datawarehouse-implementatie niets ongehoord. In het eerste experiment krijgen we de volgende grafiek:

Soms is meer minder. Wanneer het verminderen van de belasting resulteert in een toenemende latentie

Zoals u kunt zien, is er geen verbetering bij het gebruik van de ping-ping-server. Dit betekent dat het datawarehouse de latentie niet verhoogt en dat de lijst met verdachten wordt gehalveerd:

  1. Netwerkoproep van klant naar Alvin.
  2. Netwerkoproep van Alvin naar een klant.

Geweldig! De lijst wordt snel kleiner. Ik dacht dat ik de reden bijna had ontdekt.

gRPC

Dit is het moment om je voor te stellen aan een nieuwe speler: gRPC. Dit is een open source bibliotheek van Google voor communicatie tijdens het proces RPC. hoewel gRPC goed geoptimaliseerd en veel gebruikt, dit was de eerste keer dat ik het gebruikte op een systeem van deze omvang en ik verwachtte dat mijn implementatie op zijn zachtst gezegd suboptimaal zou zijn.

beschikbaarheid gRPC in de stapel gaf aanleiding tot een nieuwe vraag: misschien is het mijn implementatie of mijzelf gRPC waardoor latentieprobleem? Een nieuwe verdachte aan de lijst toevoegen:

  1. De klant belt de bibliotheek gRPC
  2. bibliotheek gRPC maakt een netwerkoproep naar de bibliotheek op de client gRPC op server
  3. bibliotheek gRPC neemt contact op met Alvin (geen werking bij pingpongserver)

Om je een idee te geven van hoe de code eruit ziet: mijn client/Alvin-implementatie verschilt niet veel van de client-server-implementaties asynchroon voorbeelden.

Opmerking: de bovenstaande lijst is een beetje vereenvoudigd omdat gRPC maakt het mogelijk om uw eigen (template?) threading-model te gebruiken, waarbij de uitvoeringsstack met elkaar verweven is gRPC en gebruikersimplementatie. Omwille van de eenvoud houden we ons aan dit model.

Profilering zal alles oplossen

Nadat ik de datastores had doorgestreept, dacht ik dat ik bijna klaar was: “Nu is het gemakkelijk! Laten we het profiel toepassen en uitzoeken waar de vertraging optreedt.” I grote fan van precisieprofilering, omdat CPU's erg snel zijn en meestal niet het knelpunt vormen. De meeste vertragingen treden op wanneer de processor de verwerking moet stoppen om iets anders te doen. Nauwkeurige CPU-profilering doet precies dat: het registreert nauwkeurig alles contextschakelaars en maakt duidelijk waar vertragingen optreden.

Ik heb vier profielen genomen: met hoge QPS (low latency) en met een pingpongserver met lage QPS (high latency), zowel aan de clientzijde als aan de serverzijde. En voor het geval dat, heb ik ook een voorbeeldprocessorprofiel genomen. Bij het vergelijken van profielen zoek ik meestal naar een afwijkende call-stack. Aan de slechte kant met hoge latentie zijn er bijvoorbeeld veel meer contextwisselingen (10 keer of meer). Maar in mijn geval was het aantal contextwisselingen vrijwel hetzelfde. Tot mijn grote schrik was er niets belangrijks aan de hand.

Extra foutopsporing

Ik was wanhopig. Ik wist niet welke andere hulpmiddelen ik kon gebruiken, en mijn volgende plan was in wezen om de experimenten met verschillende variaties te herhalen in plaats van een duidelijke diagnose van het probleem te stellen.

Wat als

Vanaf het allereerste begin maakte ik me zorgen over de specifieke latentie van 50 ms. Dit is een hele grote tijd. Ik besloot dat ik stukjes uit de code zou knippen totdat ik precies kon achterhalen welk onderdeel deze fout veroorzaakte. Toen kwam er een experiment dat werkte.

Zoals gewoonlijk lijkt achteraf gezien alles vanzelfsprekend te zijn. Ik plaatste de client op dezelfde machine als Alvin - en stuurde een verzoek naar localhost. En de toename in latentie is verdwenen!

Soms is meer minder. Wanneer het verminderen van de belasting resulteert in een toenemende latentie

Er was iets mis met het netwerk.

Netwerkingenieurvaardigheden leren

Ik moet toegeven: mijn kennis van netwerktechnologieën is verschrikkelijk, vooral gezien het feit dat ik er elke dag mee werk. Maar het netwerk was de hoofdverdachte en ik moest leren hoe ik daar fouten in kon maken.

Gelukkig houdt internet van degenen die willen leren. De combinatie van ping en tracert leek een goed begin voor het oplossen van netwerktransportproblemen.

Ten eerste lanceerde ik PsPing naar de TCP-poort van Alvin. Ik heb de standaardinstellingen gebruikt - niets bijzonders. Van de meer dan duizend pings overschreed geen enkele de 10 ms, met uitzondering van de eerste voor het opwarmen. Dit is in strijd met de waargenomen toename van de latentie van 50 ms op het 99e percentiel: daar hadden we voor elke 100 verzoeken ongeveer één verzoek moeten zien met een latentie van 50 ms.

Toen probeerde ik het tracert: Er is mogelijk een probleem op een van de knooppunten langs de route tussen Alvin en de klant. Maar ook de tracer keerde met lege handen terug.

Het was dus niet mijn code, de gRPC-implementatie of het netwerk dat de vertraging veroorzaakte. Ik begon me zorgen te maken dat ik dit nooit zou begrijpen.

Welk besturingssysteem gebruiken we nu?

gRPC veel gebruikt op Linux, maar exotisch op Windows. Ik besloot een experiment uit te proberen, dat werkte: ik creëerde een virtuele Linux-machine, compileerde Alvin voor Linux en implementeerde deze.

Soms is meer minder. Wanneer het verminderen van de belasting resulteert in een toenemende latentie

En dit is wat er gebeurde: de Linux-pingpongserver had niet dezelfde vertragingen als een vergelijkbare Windows-host, hoewel de gegevensbron niet anders was. Het blijkt dat het probleem zit in de gRPC-implementatie voor Windows.

Het algoritme van Nagle

Al die tijd dacht ik dat ik een vlag miste gRPC. Nu begrijp ik wat het werkelijk is gRPC Windows-vlag ontbreekt. Ik vond een interne RPC-bibliotheek waarvan ik zeker wist dat deze goed zou werken voor alle ingestelde vlaggen Winsock. Vervolgens heb ik al deze vlaggen aan gRPC toegevoegd en Alvin op Windows geïmplementeerd, op een gepatchte Windows-pingpongserver!

Soms is meer minder. Wanneer het verminderen van de belasting resulteert in een toenemende latentie

bijna Klaar: ik begon de toegevoegde vlaggen één voor één te verwijderen totdat de regressie terugkeerde, zodat ik de oorzaak kon achterhalen. Het was berucht TCP_NODELAY, Nagle's algoritmeschakelaar.

Het algoritme van Nagle probeert het aantal pakketten dat via een netwerk wordt verzonden te verminderen door de verzending van berichten te vertragen totdat de pakketgrootte een bepaald aantal bytes overschrijdt. Hoewel dit leuk kan zijn voor de gemiddelde gebruiker, is het destructief voor realtime servers, omdat het besturingssysteem sommige berichten zal vertragen, wat vertragingen veroorzaakt bij lage QPS. U gRPC deze vlag is ingesteld in de Linux-implementatie voor TCP-sockets, maar niet in Windows. ik ben dit gecorrigeerd.

Conclusie

De hogere latentie bij lage QPS werd veroorzaakt door OS-optimalisatie. Achteraf gezien heeft profilering geen latentie gedetecteerd omdat het in de kernelmodus werd uitgevoerd in plaats van in gebruikersmodus. Ik weet niet of het algoritme van Nagle kan worden waargenomen via ETW-opnamen, maar het zou interessant zijn.

Wat het localhost-experiment betreft, het raakte waarschijnlijk de daadwerkelijke netwerkcode niet en het algoritme van Nagle werkte niet, dus de latentieproblemen verdwenen toen de client Alvin bereikte via localhost.

De volgende keer dat u een toename van de latentie ziet naarmate het aantal verzoeken per seconde afneemt, zou het algoritme van Nagle op uw lijst met verdachten moeten staan!

Bron: www.habr.com

Voeg een reactie