.NET 9 bringt zahlreiche Optimierungen hinsichtlich Performance und Ressourcenmanagement. Eine der wichtigsten Neuerungen ist DATAS (Dynamic adaptation to application sizes), welches nun standardmäßig aktiviert ist. DATAS verbessert die Speicherverwaltung, indem es die Heap-Größe dynamisch an die Arbeitslast und verfügbaren Ressourcen anpasst.
Was ist DATAS?
DATAS ist eine Optimierung des Garbage Collectors, die in .NET 8 als optionales Feature eingeführt und in .NET 9 standardmäßig aktiviert wurde.
Im Gegensatz zum klassischen Server GC, der auf maximalen Durchsatz optimiert ist, passt DATAS die Heap-Größe und GC-Strategien dynamisch an die tatsächlichen Anforderungen der Anwendung an. Dies ist besonders vorteilhaft bei schwankender Last und in Container-Umgebungen, da der Speicherverbrauch effizienter gesteuert werden kann.
Weitere Details zur Funktionsweise von DATAS finden Sie in der offiziellen Dokumentation.
Test Setup
Um die Verbesserungen durch DATAS zu demonstrieren, haben wir ein Test-Setup mit folgenden Komponenten erstellt:
- Minikube als lokaler Kubernetes Cluster
- Ein ASP.NET Service, der große Speichermengen allokiert
- OpenTelemetry für das Monitoring
- Prometheus als Zeitreihendatenbank
- Grafana für die Visualisierung
- k6 für automatischen Lasttests
Der Service speichert große Strings für jedes Businessobjekt im Speicher und löscht diese nach einiger Zeit wieder aus dem Speicher.
⚠️ Hinweis: Der Service speichert Personendaten. Das tut er lediglich aus dem Grund, dass dem Autor kein besseres Beispiel eingefallen ist und es als Erinnerung dienen soll, dass man so etwas NIE LEICHTFERTIG TUN SOLLTE 😉.
Den kompletten Source Code finden Sie hier: GitHub
Installation und Setup
# Minikube starten
minikube start --extra-config=kubelet.authentication-token-webhook=false --extra-config=kubelet.authorization-mode=AlwaysAllow
# Monitoring Stack deployen
kubectl apply -f kubectl/observability/
# Service deployen und testen
make serve_dotnet8 # für .NET 8
# URL und Port kopieren
k6 run k6/test.js -e MAX_USERS=50 -e BASE_URL=<URL:PORT>
# Test abwarten
make serve_dotnet9 # für .NET 9
# URL und Port kopieren
k6 run k6/test.js -e MAX_USERS=50 -e BASE_URL=<URL:PORT>
Beobachtungen
In unseren Tests haben wir das Verhalten unter Last mit k6 verglichen und mit einem Grafana Dashboard visualisiert.

Dabei zeigen sich deutliche Unterschiede zwischen .NET 8 und .NET 9:
Speicher Allokation

Der Graph zeigt, dass .NET 9 mit DATAS ein kontrollierteres Wachstum der Allokationen aufweist. Die blaue Linie (.NET Allocations) steigt gradueller an verglichen mit den steilen Stufen in .NET 8.
Heap Größe

Besonders interessant ist das Verhalten des Large Object Heap (LOH - orange Linie):
- .NET 8 zeigt größere, stufenweise Anstiege
- .NET 9 weist ein graduelleres Wachstum auf
- Die Anpassung an die Last erfolgt dynamischer
Garbage Collection Laufzeit

.NET 9 zeigt:
- Häufigere aber kürzere GC Spikes
- Regelmäßigere Gen0 Collections (grüne Linie)
- Insgesamt bessere Verteilung der GC-Pausen
Anzahl gespeicherter Objekte

Die Anzahl der gespeicherten Objekte wächst in beiden Versionen ähnlich, was die Vergleichbarkeit der Tests bestätigt.
Fazit
Die Ergebnisse zeigen deutlich die Vorteile von DATAS in .NET 9:
- Effizientere Speichernutzung während Lastspitzen
- Bessere Anpassung an sich ändernde Workloads
- Kontrolliertes Wachstum des Large Object Heap
Diese Verbesserungen sind besonders relevant für containerisierte Microservices, da sie zu einer besseren Ressourcennutzung, stabilerer Performance und durchschnittlich geringeren Betriebskosten führen.
Habt ihr eure Kubernetes Workloas bereits auf .NET 9 umgestellt und seht Verbesserungen durch aktiviertes DATAS bzgl. der Speicherauslastung? Schreibt uns! Wir freuen uns auf den Austausch mit euch.