Zum Inhalt springen
Abstraktes Relief aus dunklem Papier: ein Papierrelief mit drei Kammern, vor dem mittleren Eingang stauen sich kleine Kugeln, dessen Rand ist orange

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.

7 min Lesezeit Performance & DevOps

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
Ein Blutdruckmessgerät mit oranger Manschette: erst zeigt die Messung, welche der fünf Phasen wirklich lang ist, dann wird über ein stärkeres Mittel entschieden.

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.

measure-url.sh bash
#!/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.

/etc/mysql/conf.d/slow-query.cnf text
[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.

Skalierung und Diagnose

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.