Een API die soepel door de staging-omgeving glijdt, kan instorten op het moment dat het echte verkeer binnenkomt. De fout is zelden spectaculair. Het is een dood door duizend kleine sneetjes. Een geblokkeerde thread hier. Een overbodige query daar. Onder lichte belasting blijven deze kleine inefficiënties verborgen. Onder productiedruk stapelen ze zich op tot thread pool-uitputting, verstikking van de database en responstijden die het vertrouwen van de gebruiker vernietigen. Performance tuning gaat niet over het vinden van één magische oplossing. Het gaat over het bouwen van een systeem waarin elke laag de schaarste aan threads, geheugen en databaseverbindingen respecteert. Wanneer je deze middelen als eindig beschouwt, stop je met het reageren op uitval en begin je deze te voorkomen.

Elimineer sync-over-async voordat het jou fataal wordt

Het meest destructieve patroon in applicaties met veel verkeer in ASP.NET Core is sync-over-async. Je ziet het wanneer iemand .Result of .Wait() aanroept op een asynchrone methode omdat ze de waarde direct nodig hebben en de call stack niet willen refactoren. Die beslissing blokkeert de aanroepende thread. De thread zit inactief te wachten op werk dat elders al plaatsvindt, maar de runtime kan deze niet hergebruiken voor een ander verzoek.

Wanneer voldoende verzoeken dit doen, raakt de thread pool uitgeput. Je CPU-grafiek ziet er gezond uit omdat de processors niet druk zijn, maar je latentie explodeert. Verzoeken hopen zich op in de wachtrij, wachtend op threads die nooit vrij zullen komen. De oplossing is mechanisch maar vereist discipline: gebruik await door de gehele call stack heen. Als een methode een async API aanroept, moet deze zelf ook async zijn. Er zijn hier geen shortcuts. Elke synchronisatie die je wegneemt, creëert weer ruimte voor echt verkeer.

Stop met het verspillen van werk aan ontkoppelde clients

Clients verbreken de verbinding. Browsers sluiten tabbladen. Mobiele apps verliezen signaal. Als je server niet weet dat de client weg is, blijft hij databasequeries uitvoeren, JSON parsen en threads verspillen aan een response die niemand zal ontvangen. Geef een CancellationToken mee aan elke async operatie die dit ondersteunt. Dat betekent Entity Framework-queries, HTTP-aanroepen met HttpClient en al het langlopende achtergrondwerk.

Wanneer de verbinding wegvalt, activeert de token de annulering en stopt het werk onmiddellijk. Dit bespaart CPU-cycli van de database en geeft threads sneller terug aan de pool. Het is een kleine wijziging in de methodesignaturen die zich onder belasting volledig terugbetaalt.

Cache wat niet verandert

Je productcatalogus verandert waarschijnlijk niet bij elk verzoek. Je configuratievlaggen zeker niet. Toch raadplegen veel API's herhaaldelijk de database voor dezelfde statische gegevens. Output caching in ASP.NET Core stelt je in staat om gerenderde responses op te slaan en ze direct vanuit het geheugen te serveren zonder je controllers of database opnieuw aan te raken.

Gebruik tag-gebaseerde invalidatie zorgvuldig. Wanneer je een product bijwerkt, invalideer dan alleen de tag die bij die categorie of dat item hoort. Je hoeft niet de gehele cache te legen. Dit houdt je cache hit ratio hoog en het aantal databasequeries laag.

Los eerst je data-toegang op

Database-aanroepen verbruiken in de meeste API's het grootste deel van de verwerkingstijd van een verzoek. Voordat je iets anders optimaliseert, kijk je hiernaar.

Als je gegevens alleen opvraagt om ze te tonen en nooit van plan bent ze bij te werken, voeg dan AsNoTracking() toe aan je Entity Framework-queries. EF Core slaat dan change tracking en het maken van snapshots over.