Woran Sie merken, dass Ihre Webanwendung an Grenzen stößt
Wird eine Webanwendung langsam, zeigt sich die Grenze an Antwortzeiten, die zu Spitzenzeiten steigen, an Fehlern unter Last und an Aufgaben, die liegen bleiben. Ob Server, Datenbank oder Code der Engpass ist, klärt eine Messung, bevor jemand einen größeren Server bestellt.
Montag, neun Uhr: Der Export der Wochenrechnungen braucht 40 Sekunden statt drei, und zwei Mitarbeiter melden eine Fehlerseite. Am Dienstag läuft wieder alles normal. So sieht eine Anwendung aus, die an einer Stelle an ihre Grenze kommt, aber noch nicht überall.
Lädt eine Website im Browser langsam, kann die Ursache in Bildern, Schriften und Skripten liegen; diese Seite des Problems behandelt Langsame Website: zehn Ursachen und wie Sie sie beheben. Die Warnzeichen unten betreffen die Serverseite einer Anwendung mit Anmeldung, Datenbank und Hintergrundaufgaben. Wie Sie die Grenze vor einer Kampagne gezielt mit künstlicher Last suchen, steht unter Lasttest vor der Kampagne. Ob danach ein größerer Server oder mehrere Server helfen, entscheidet erst die Messung.
Anzeichen
Warnzeichen im Alltag
Die ersten Hinweise kommen aus dem Alltag der Nutzer und aus den Logdateien. Achten Sie auf diese Anzeichen:
Seiten werden zu bestimmten Uhrzeiten oder an bestimmten Tagen langsam, in der übrigen Zeit nicht.
Unter Last erscheinen Fehlerseiten mit 502 oder 504, also Zeitüberschreitungen zwischen Webserver und Anwendung.
Berichte und Exporte brauchen mit jedem Monat länger, obwohl sich am Code nichts geändert hat.
E-Mails, PDFs oder Importe kommen verzögert an, weil sich Hintergrundaufgaben stauen.
Das Log meldet „too many connections“ von der Datenbank oder die Warnung, dass PHP-FPM seine Höchstzahl an Prozessen erreicht hat.
Jedes dieser Zeichen sagt, dass etwas knapp wird. Welche Ressource es ist, sagt keines davon allein.
Diagnose
Engpass finden: Server, Datenbank oder Code
Die Tabelle ordnet Symptome der Stelle zu, an der wir zuerst suchen würden. Sie ersetzt keine Messung, sie legt nur fest, welche zuerst kommt.
| Wahrscheinlicher Engpass | Erste Messung | |
|---|---|---|
| Alle Seiten werden gleichmäßig langsam, die CPU liegt dauerhaft nahe 100 Prozent | Server: zu wenig Rechenleistung | vmstat 5 auf dem Server: Spalten us und sy hoch, id nahe null |
| Langsam trotz niedriger CPU-Last der Anwendung, auf einem Cloud-Server | Server: Rechenzeit fehlt auf dem Host | vmstat 5: Spalte st (gestohlene Zeit) dauerhaft über null |
| Spitzen mit 502 oder 504, PHP-FPM meldet erreichte Prozessgrenze | Server oder Code: zu wenige Prozesse oder zu lange Anfragen | FPM-Statusseite: listen queue und max children reached |
| Einzelne Seiten mit Listen oder Berichten sind langsam, der Rest nicht | Datenbank: fehlender Index oder Abfragen auf zu viele Zeilen | Slow Query Log mit long_query_time = 1 |
| Eine Seite feuert Hunderte gleichartiger Abfragen | Code: N+1-Abfragen | Abfragezähler je Anfrage in der Entwicklung, Lazy Loading sperren |
| Exporte und Berichte bremsen die übrigen Nutzer | Datenbank: Lese- und Schreiblast auf derselben Instanz | Slow Query Log zur Exportzeit, Sperren und Laufzeiten |
| Speicher voll, Server lagert aus | Server: zu wenig Arbeitsspeicher oder ein Speicherleck | vmstat 5: Spalten si und so über null |
| Zeit vergeht vor der ersten Antwort, im Browser steht lange „Waiting“ | Anwendung oder Datenbank | curl mit Zeitaufteilung, Phase server |
Messen
Erst messen, dann vergrößern
Eine Antwortzeit setzt sich aus Phasen zusammen: Namensauflösung, Verbindungsaufbau, TLS-Aushandlung, Warten auf die erste Antwort des Servers und Herunterladen. Nur die vierte Phase gehört der Anwendung. Ist sie klein und der Rest groß, hilft kein größerer Server. Das Skript unten ruft eine Adresse mehrmals auf und gibt für jede Phase den Median in Millisekunden aus, damit ein einzelner Ausreißer das Bild nicht verfälscht.
Aufruf etwa mit ./measure-url.sh https://app.example.com/login 20; fehlgeschlagene Anfragen zählen nicht mit, ohne eine erfolgreiche endet das Skript mit Exit-Code 2.
#!/usr/bin/env bash
# Splits the response time of a URL into phases and prints the median of n runs.
# Usage: measure-url.sh <url> [runs, default 10]
set -euo pipefail
export LC_ALL=C # decimal point for awk, also in a German locale
url="${1:?usage: measure-url.sh <url> [runs]}"
runs="${2:-10}"
case "$runs" in ''|*[!0-9]*|0) echo "runs must be a whole number above 0" >&2; exit 64 ;; esac
tmp="$(mktemp)"
trap 'rm -f "$tmp" "$tmp.ms"' EXIT
fmt='%{time_namelookup} %{time_connect} %{time_appconnect} %{time_pretransfer} %{time_starttransfer} %{time_total} %{http_code}\n'
for _ in $(seq "$runs"); do
if line="$(curl -sS -o /dev/null -m 30 -w "$fmt" "$url")"; then
echo "$line" >> "$tmp"
else
echo "request failed, not counted" >&2
fi
done
[ -s "$tmp" ] || { echo "no successful request" >&2; exit 2; }
# phases in milliseconds: dns, tcp, tls, server (waiting for first byte), download
awk '{ printf "%.0f %.0f %.0f %.0f %.0f %s\n",
$1*1000, ($2-$1)*1000, ($3>0 ? ($3-$2)*1000 : 0), ($5-$4)*1000, ($6-$5)*1000, $7 }' "$tmp" > "$tmp.ms"
mid=$(( ($(wc -l < "$tmp.ms") + 1) / 2 ))
printf '%-9s %s\n' phase median_ms
col=1
for phase in dns tcp tls server download; do
printf '%-9s %s\n' "$phase" "$(cut -d' ' -f"$col" "$tmp.ms" | sort -n | sed -n "${mid}p")"
col=$((col + 1))
done
echo "status codes: $(cut -d' ' -f6 "$tmp.ms" | sort | uniq -c | awk '{ printf "%s%s x%s", sep, $2, $1; sep = ", " }')"
Datenbank und Code messen
Die Zeile mit LC_ALL=C hat einen Grund aus dem Test: In einer Shell mit deutscher Spracheinstellung las awk unter macOS den Punkt in „0.084“ nicht als Dezimaltrennzeichen, und alle Phasen standen auf null. Messen Sie zweimal: einmal zur ruhigen Zeit, einmal in der Spitze. Steigt nur die Phase server, sitzt der Engpass in Anwendung oder Datenbank.
Für die Datenbank hat MySQL das Slow Query Log. Es ist standardmäßig aus und erfasst mit der Voreinstellung erst Abfragen ab zehn Sekunden. Mit einer Schwelle von einer Sekunde und der Auswertung durch mysqldumpslow sehen Sie, welche Abfragen die Zeit kosten. In Laravel meldet DB::whenQueryingForLongerThan, wenn eine einzelne Anfrage insgesamt zu lange in der Datenbank verbringt, und Model::preventLazyLoading macht N+1-Abfragen in der Entwicklung zu einem Fehler. Wie Sie solche Abfragen systematisch finden, zeigt Eloquent N+1 systematisch finden und beheben.
Nach einem Neustart von MySQL aktiv; mysqldumpslow -s t /var/log/mysql/slow.log sortiert die Einträge nach Gesamtzeit.
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
Messdaten aus dem Betrieb
Eine einzelne Messung erwischt die Spitze vom Montagmorgen nur, wenn sie zufällig dann läuft; Daten aus dem laufenden Betrieb enthalten sie. Beim Taxifahrer-Portal der Taxi-München eG haben wir Laravel Nightwatch deshalb von Anfang an verkabelt: Die Performance-Daten aus dem produktiven Betrieb liegen ab dem ersten Tag vor, und Engpässe werden sichtbar, bevor sie zu Beschwerden werden. Wer die Prüfungen von außen dazunimmt, hat mit einem Uptime-Monitoring auch Ausfälle und liegengebliebene Cronjobs im Blick.
Größer
Wann ein größerer Server reicht
Ein größerer Server hilft, wenn die Messung eine knappe Ressource zeigt und der Code mit ihr sorgsam umgeht: CPU dauerhaft ausgelastet, Arbeitsspeicher voll, gestohlene Rechenzeit auf dem Host. Bei einem Cloud-Server ist das ein Wechsel der Servergröße mit kurzer Unterbrechung. Messen Sie danach mit demselben Skript noch einmal. Fällt die Phase server deutlich, war es die richtige Stelle.
Ein größerer Server hilft nicht, wenn eine einzige Abfrage ohne Index zehn Millionen Zeilen liest oder eine Seite 400 Abfragen absetzt. Dann verschiebt mehr Leistung die Grenze nur ein Stück, und die Rechnung steigt dauerhaft. Welche Ausbaustufen wir für Anwendungen planen, von Caches bis zu Read-Replicas, steht auf der Seite zur Skalierung von Webanwendungen.
Umbauen
Wann die Architektur geändert werden muss
Umbauen müssen Sie, wenn eine Anfrage mehr Arbeit erledigt, als ein Nutzer abwarten sollte. Ein PDF-Export über 20.000 Datensätze gehört in eine Warteschlange, die im Hintergrund arbeitet und den Nutzer benachrichtigt, wenn die Datei bereitsteht. Berichte, die die Datenbank für alle anderen ausbremsen, laufen besser gegen eine Lesekopie oder gegen vorab verdichtete Tabellen. Hängt alles an einem einzigen Server, ist er gleichzeitig Engpass und Ausfallrisiko.
Solche Umbauten brauchen Tage statt Stunden und gehören in die Planung, nicht in die Nacht nach dem Ausfall. Mit den Messungen von vorher wissen Sie, welche Seiten oder Aufgaben den größten Teil der Last erzeugen, und bauen nur diese um.
Wo sitzt der Engpass in Ihrer Anwendung?
Schicken Sie uns die Seiten, die zu Spitzenzeiten langsam werden, und wann es passiert. Wir messen, ordnen den Engpass Server, Datenbank oder Code zu und sagen, ob ein größerer Server reicht.
Kapazität und Messung
Häufige Fragen
-
Nein. Mehr Arbeitsspeicher hilft, wenn der Server auslagert, also vmstat in den Spalten si und so Werte über null zeigt, oder wenn die Datenbank ihre häufig genutzten Daten nicht mehr im Speicher halten kann. Ist dagegen die CPU ausgelastet oder läuft eine Abfrage ohne Index, ändert zusätzlicher Speicher an der Antwortzeit wenig.
-
Im Browser zeigen die Entwicklerwerkzeuge im Netzwerk-Tab für jede Anfrage die Phase „Waiting (TTFB)“, die Wartezeit auf die erste Antwort des Servers. Auf dem Server kann nginx mit der Variable $request_time die Bearbeitungszeit jeder Anfrage ins Zugriffsprotokoll schreiben, auch für die Spitzenzeiten. Beides kostet nichts außer der Einrichtung.
Nachweise
Quellen und Stand
Stand: 06.10.2026. Das Messskript haben wir unter macOS mit bash 3.2 und curl 8.7 gegen öffentliche Websites, gegen einen lokalen Testserver ohne TLS, gegen eine nicht erreichbare Adresse und mit ungültiger Anzahl ausgeführt, auch mit deutscher Spracheinstellung der Shell. Die MySQL-Einstellungen sind gegen das MySQL-8.4-Handbuch geprüft und nicht auf einem Datenbankserver ausgeführt.
Artikel teilen