Jmeter! Et sprudlende værktøj
Forestil dig hvis du kunne få alle mennesker i et gigantisk kinesisk supermarked til at load-teste alle kasseapparater, alle ekspedienter, ringe til alle virksomhedens telefoner, stå i kø til lokummerne, åbne og lukke alle køleelementer, bevæge sig ned af de samme gange og på den måde teste hylderne stabilitet. Det kan du faktisk! Ihvertfald hvis det gigantiske supermarked består af en masse microservices. Det er Jmeter, det er power!
Jeg satte en test op med 4000 fiktive brugere til at teste registreringsendpointet på UserService. Efter en masse konfigureringsfejl i JMeter GUI'en fik jeg det til at virke... sådan da. Den oprettede 45 brugere og crashede. Total users var sat til 4000. Ramp up time på 2 minutter. Loop 2, fordi jeg også gerne vil udsætte servicen for et load som resulterer i en database query der giver et DuplicateUserName tilbage. Test ressoursen sat /register via min gateway. Gatewayen kører fint og spytter forwarding-request ud i min docker-container.
UserServicen.... død
.Så inden jeg går videre vil jeg altså lige sætte noget ratelimiting op på min Gateway og se om det kunne gøre noget! Jeg kunne også skrue på ramp-up timen, men det er alligevel på tide at der bliver sat noget ratelimiting op på min gateway. Jeg vender stærkt tilbage til denne post... forhåbentlig.
Jeg er tilbage, og det er min UserService også. For at se mit indlæg om ratelimiting kan du gå til den her Jeg satte noget RateLimiting op vha. NuGet-pakken System.Thread.RateLimiting på min Gateway.
Det skal indrømmes! Jeg var for naiv: 4000 requests på endpointet '/register' komplet bombede min UserService.
CPUen var faktisk oppe at ramme 780% inden den crashede, jeg kunne bare ikke nå at fange billedet.
Jeg måtte prøve noget andet...
Jeg satte en ratelimit policy på max 100 requests i minuttet. JMeter: 100 users, ramp up time 20 sekunder. UJserService døde.
Okay!
Vi starter blødt ud, og hæver gradvist stressen, tænkte jeg.
50 users, ramp up time 20 sekunder.
UserService døde.
Okay... det tænkte jeg alligevel var godt satans. Det skal min service altså kunne klare. Var der noget elementært jeg kunne ændre på... optimere.... koden var clean, simpel. UserController bliver aktiveret, login aktionen invoked, ModelState valideres, hvis rigtig, IdentityUser laves, UserManager laver brugeren, og rapporterer fejl/duplicateUserName tilbage. Det var det. Hvad ellers....... hvad kan optimeres på noget så smukt og lækkert og simpelt....
Selvfølgelig. Det er fandemer noget af en proces, når man lige tænker over det. ModelState skal valideres, klart. Men der er slet ingen grund til at IdentityUser laves og at hele igangsættelsen af kreering af bruger hvis den allerede findes i databasen.
Løsning! Vi tjekker først og fremmest om usernamet allerede findes i databasen, og på baggrund af denne query, finder returnerer vi en boolean. Hvis brugeren findes; return BadRequest med en meddelse om at brugeren allerede eksisterer.
Hvis brugeren ikke findes! Så kan vi igangsætte den ellers smukke polerede og i første øjekast (fra mit POV) optimerede kode.
50 users, ramp up time på 20 sekunder, no issue, ingen dikkedarer, snildt, CPU load på max 30%.
100 users, ramp up time på 20 sekunder... død. Okay, hvad kan det skyldes....
100 users, ramp up time på 30 sekunder... død. Hm....
100 users, ramp up time på 40 sekunder... fint! Max load på 70%. Hvad kan dog skyldes at den crasher på en ramp up time på 30, men ikke på 40. Det er for mange requests på engang... men hverken CPUen eller memoryen ser ud til at kunne være grunden....
Løsning!: "For sjov skyld" prøvede jeg at give UserService adgang til ubegrænset CPU-kraft fra min maskine vha. Docker Compose: