Eine API, die in der Staging-Umgebung mühelos funktioniert, kann in dem Moment zusammenbrechen, in dem echter Traffic auftrifft. Das Scheitern ist selten dramatisch. Es ist ein schleichender Tod durch tausend kleine Schnitte. Ein blockierter Thread hier. Eine redundante Abfrage dort. Unter geringer Last verbergen sich diese kleinen Ineffizienzen. Unter Produktionsdruck summieren sie sich zu Thread-Pool-Starvation, einer Überlastung der Datenbank und Antwortzeiten, die das Vertrauen der Nutzer zerstören. Performance-Tuning bedeutet nicht, die eine magische Lösung zu finden. Es geht darum, ein System aufzubauen, in dem jede Ebene die Knappheit von Threads, Arbeitsspeicher und Datenbankverbindungen respektiert. Wenn Sie diese Ressourcen als endlich behandeln, hören Sie auf, auf Ausfälle zu reagieren, und beginnen, sie zu verhindern.

Eliminieren Sie Sync-over-Async, bevor es Sie ruiniert

Das mit Abstand destruktivste Muster in hochfrequentierten ASP.NET Core Anwendungen ist Sync-over-Async. Man sieht es, wenn jemand .Result oder .Wait() auf einer asynchronen Methode aufruft, weil der Wert sofort benötigt wird und der Call-Stack nicht refactored werden soll. Diese Entscheidung blockiert den aufrufenden Thread. Der Thread verharrt im Leerlauf und wartet auf eine Arbeit, die bereits an anderer Stelle ausgeführt wird, aber die Laufzeitumgebung kann ihn nicht für eine andere Anfrage wiederverwenden.

Wenn genügend Anfragen so vorgehen, verhungert der Thread-Pool. Ihr CPU-Graph sieht gesund aus, weil die Prozessoren nicht ausgelastet sind, aber Ihre Latenz explodiert. Anfragen stauen sich in der Warteschlange und warten auf Threads, die niemals frei werden. Die Lösung ist mechanisch, erfordert aber Disziplin: Verwenden Sie await über den gesamten Call-Stack hinweg. Wenn eine Methode eine asynchrone API aufruft, muss sie selbst asynchron sein. Es gibt hier keine Abkürzungen. Jede synchronisierte Blockierung, die Sie entfernen, verschafft Ihnen mehr Spielraum für echten Traffic.

Hören Sie auf, Arbeit für getrennte Clients zu verschwenden

Clients trennen die Verbindung. Browser schließen Tabs. Mobile Apps verlieren das Signal. Wenn Ihr Server nicht weiß, dass der Client weg ist, führt er weiterhin Datenbankabfragen aus, parst JSON und verbraucht Threads für eine Antwort, die niemand mehr empfangen wird. Übergeben Sie einen CancellationToken an jede asynchrone Operation, die dies unterstützt. Das bedeutet Entity Framework-Abfragen, HTTP-Aufrufe mit HttpClient und jede andere lang laufende Hintergrundarbeit.

Sobald die Verbindung abbricht, löst der Token die Abbrechung aus und die Arbeit stoppt sofort. Dies schont die CPU-Zyklen der Datenbank und gibt Threads schneller an den Pool zurück. Es ist eine kleine Änderung an den Methodensignaturen, die sich unter Last massiv auszahlt.

Cachen Sie, was sich nicht ändert

Ihr Produktkatalog ändert sich wahrscheinlich nicht zwischen jeder einzelnen Anfrage. Ihre Konfigurationsflags erst recht nicht. Dennoch greifen viele APIs wiederholt auf die Datenbank zu, um dieselben statischen Daten abzurufen. Output Caching in ASP.NET Core ermöglicht es Ihnen, gerenderte Antworten zu speichern und diese direkt aus dem Arbeitsspeicher auszuliefern, ohne Ihre Controller oder die Datenbank erneut zu belasten.

Nutzen Sie die Tag-basierte Invalidierung mit Bedacht. Wenn Sie ein Produkt aktualisieren, invalidieren Sie nur das Tag, das mit dieser Kategorie oder diesem Artikel verknüpft ist. Sie müssen nicht den gesamten Cache leeren. Dies hält Ihre Cache-Hit-Ratio hoch und die Anzahl der Datenbankabfragen niedrig.

Optimieren Sie zuerst Ihren Datenzugriff

Datenbankaufrufe verbrauchen in den meisten APIs den Großteil der Anfragedauer. Bevor Sie etwas anderes optimieren, schauen Sie hier nach.

Wenn Sie Daten nur abfragen, um sie anzuzeigen, und nicht planen, sie jemals zu aktualisieren, hängen Sie AsNoTracking() an Ihre Entity Framework-Abfragen an. EF Core überspringt dadurch das Change Tracking und die Erstellung von Snapshots.