Einleitung: Ohne Metriken sieht das Problem aus wie „bei uns ist alles langsam“

Eine bekannte Szene. Im Arbeits-Chat taucht eine Nachricht auf: „Die Proxys sind lahm.“ Fünf Minuten später die zweite: „Und viele Fehler.“ Danach beginnt das, was Ingenieure untereinander Archäologie nennen. Jemand öffnet die Konsole und jagt manuell curl durch drei zufällige Adressen. Jemand schaut auf einen CPU-Auslastungsgraphen, der mit den Proxys nichts zu tun hat. Jemand schreibt dem Provider-Support eine Mail im Stil von „bei euch stimmt was nicht“, ohne eine einzige Zahl in der Hand. Eine Stunde vergeht. Dann stellt sich heraus, dass nur ein Segment des Pools degradiert ist, nur in einem Land, und dass es nicht „jetzt“ begonnen hat, sondern vor zwei Tagen – es ist nur niemandem aufgefallen.

Der Unterschied zwischen einem Team, das für so eine Geschichte eine Stunde braucht, und einem Team, das eine Minute braucht, liegt nicht an Erfahrung und nicht an Talent. Der Unterschied ist, dass das zweite Team vorher entschieden hat, was es messen, was es loggen und worauf es Alarme setzen will. Wenn du einen Graphen der Erfolgsquote nach Land und Betreiber hast, verwandelt sich „alles ist langsam“ in eine konkrete Aussage: „Seit 14:32 ist p95 der Zeit bis zum ersten Byte über mobile Proxys von Betreiber A in Region B von 1,8 auf 6,4 Sekunden gestiegen, die Fehlerquote blieb unverändert, die Retry-Quote hat sich verdreifacht.“ Mit so einer Aussage kann man arbeiten: Last umverteilen, ein Ticket beim Provider mit Daten statt Emotionen aufmachen oder erkennen, dass das Problem gar nicht bei den Proxys liegt, sondern bei der Zielressource.

In diesem Leitfaden betrachten wir die Observability rund um den Proxy-Pool von Anfang bis Ende. Du erfährst, welche vier Signale fast die gesamte nützliche Information liefern, welche Felder in jeder Log-Zeile stehen müssen – und welche niemals, warum die durchschnittliche Antwortzeit lügt und wie man Perzentile korrekt berechnet, wie man Daten nach Dimensionen segmentiert, um die Degradation eines einzelnen Segments zu sehen, wie man Alarme baut, die selten, aber treffsicher auslösen, und wie man auf dem Dashboard in einer Minute drei typische Problembilder unterscheidet. Am Ende betrachten wir den minimalen Technologie-Stack, reale Fälle mit Zahlen und beantworten häufige Fragen.

Grenzen sofort klarstellen: Die Auswahl einer konkreten IP aus dem Pool, Health-Checks von Proxys und die Quarantäne defekter Adressen besprechen wir hier nicht im Detail – dem ist ein eigener Artikel gewidmet. Hier geht es nur um zwei Dinge: Messung und Alarmierung. Alles, was unten beschrieben wird, gilt für legale Engineering-Szenarien: Preis-Monitoring auf öffentlichen Plattformen, Prüfung der Anzeige von Werbung in verschiedenen Regionen, Testen geoabhängiger Inhalte eigener Dienste, Sammlung offener Daten unter Einhaltung der Regeln der Ressourcen.

Ein bisschen Grundlagen: Was heißt Observability bezogen auf Proxys?

Die klassische Definition sagt, dass Observability (Beobachtbarkeit) die Eigenschaft eines Systems ist, die es erlaubt, seinen inneren Zustand aus äußeren Ausgaben zu verstehen. Ins Ingenieur-Deutsch übersetzt: Du kannst dem System eine neue Frage stellen, die nicht im Voraus vorgesehen war, und bekommst eine Antwort ohne neuen Code-Deploy. Monitoring beantwortet bekannte Fragen („lebt der Service?“), Observability unbekannte („warum werden Anfragen an diese Domain über mobile Adressen gerade dienstags langsamer?“).

Die drei Säulen der Observability sind bekannt: Metriken (numerische Zeitreihen, günstig zu speichern, einfach zu aggregieren), Logs (Ereignisse mit Kontext, teuer zu speichern, aber detailliert) und Traces (der Weg einer Anfrage durch Komponenten). Für einen Proxy-Pool ist das Bild etwas speziell. Zwischen deinem Code und der Zielseite steht eine externe Infrastruktur, die du nicht kontrollierst: Ausgangsadresse, Netzbetreiber, Route zum Ziel. Du kannst keinen Agenten auf den Mobilfunkmast setzen. Deshalb ist der einzige Punkt, an dem du alles messen kannst, dein eigener Client, der die Anfrage durch den Proxy schickt. Jede Anfrage durch den Pool muss zwei Spuren hinterlassen: eine Erhöhung der Metriken und eine Zeile strukturiertes Log. Traces sind nützlich, aber für die meisten Teams übertrieben, und wir kommen nur in der FAQ darauf zurück.

Warum ist das gerade für Proxys wichtig? Weil die Degradation von Proxys fast nie wie ein Ausfall aussieht. Der Server antwortet nicht – das ist einfach. Der Proxy ist dagegen „etwas langsamer“, „gibt etwas häufiger Verbindungsfehler zurück“, „bricht manchmal den Download in der Mitte ab“. Jedes dieser Symptome liegt für sich unterhalb der menschlichen Aufmerksamkeitsschwelle. In Summe verdoppeln sie die Kosten der Datenerfassung und sprengen Deadlines. Eine langsame Degradation erkennt man nur durch Messung.

Vier Signale für Proxys: Erfolgsquote, Latenz, Retries, Traffic

In der Zuverlässigkeitspraxis gibt es das Konzept der „goldenen Signale“: Latenz, Traffic, Fehler und Sättigung. Für einen Proxy-Pool adaptieren wir diese Idee und erhalten vier Signale, die für 90 % der Situationen ausreichen. Warum genau vier und nicht zwanzig? Weil jeder zusätzliche Graph auf dem Dashboard die Chance verringert, dass der Bereitschaftsdienst den richtigen ansieht. Vier Signale passen in eine Bildschirmzeile und sind in zehn Sekunden lesbar.

Signal 1: Erfolgsquote

Die Erfolgsquote (success rate) ist das Verhältnis von Anfragen mit nützlichem Ergebnis zur Gesamtzahl der Anfragen im Zeitfenster. Das Schlüsselwort ist „nützlich“. Erfolg für einen Proxy ist nicht nur HTTP 200. Erfolg ist, wenn die Zielressource mit dem geantwortet hat, was du erwartet hast, und die Antwort vollständig ankam. Eine Verbindung, die mitten im Body abbricht, ist kein Erfolg, selbst wenn die Header den Code 200 hatten. Ein Code 403 von der Zielseite ist ebenfalls kein Erfolg – aber das ist wahrscheinlich nicht die Schuld des Proxys, und wir unterscheiden das durch die Klassifizierung des Ergebnisses.

Praktische Regel: Klassifiziere das Ergebnis jeder Anfrage in eine kleine Zahl von Kategorien. Zum Beispiel: ok (erwartete Antwort erhalten), proxy_connect_error (keine Verbindung zum Proxy), proxy_auth_error (Proxy hat die Authentifizierung abgelehnt), upstream_timeout (Proxy hat geantwortet, aber die Zielressource nicht rechtzeitig), upstream_4xx, upstream_5xx, truncated (Verbindung vor Ende des Bodys getrennt). Diese Klassifizierung ist die Grundlage für die gesamte weitere Analyse. Die Erfolgsquote zählt ok gegenüber allem, aber separat schaust du auf die Anteile jeder Fehlerkategorie, weil sie auf unterschiedliche Verursacher hinweisen.

Signal 2: Latenz nach Perzentilen

Die Latenz für Proxys sollte an drei Punkten gemessen werden: Zeit des Verbindungsaufbaus zum Proxy, Zeit bis zum ersten Byte der Zielressource (TTFB, time to first byte) und die Gesamtzeit der Anfrage. Das erste sagt etwas über die Verfügbarkeit des Proxy-Knotens aus. Das zweite über die Qualität der Route vom Proxy zum Ziel und die Geschwindigkeit der Zielressource. Das dritte über die Bandbreite des Kanals. Wenn nur das dritte steigt und die ersten beiden stabil sind, liegt das Problem an der Bandbreite, nicht an der Verfügbarkeit. Wenn das erste steigt, liegt das Problem zwischen dir und dem Proxy. Latenz muss in Perzentilen berechnet werden, nicht als Mittelwert – dazu unten ein eigener Abschnitt.

Signal 3: Retry-Quote

Die Retry-Quote (retry rate) ist das am meisten unterschätzte Signal. Die meisten Clients sind so geschrieben, dass sie bei einem Fehler die Anfrage automatisch über eine andere Adresse wiederholen. Das ist aus Sicht der Zuverlässigkeit richtig und aus Sicht der Observability furchtbar: Die Erfolgsquote bleibt bei 99 %, aber unter der Haube wird jede Anfrage dreimal ausgeführt. Du zahlst den dreifachen Traffic, die Zielressource bekommt die dreifache Last, und auf dem Dashboard ist alles grün. Die Retry-Quote deckt versteckte Degradation auf, bevor sie sichtbare Degradation wird. Sie wird als Verhältnis von Versuchen zu logischen Anfragen berechnet: der Wert 1,0 bedeutet keine Retries, 1,5 bedeutet, dass jede zweite Anfrage wiederholt wurde.

Signal 4: Traffic-Verbrauch

Der Traffic-Verbrauch ist ein Signal, das direkt mit Geld zusammenhängt, weil viele Tarife – einschließlich der Tarife von Proxeon – das übertragene Datenvolumen berücksichtigen. Aber es hat auch eine diagnostische Rolle. Ein steiler Anstieg der Bytes pro erfolgreicher Anfrage bedeutet, dass die Zielressource andere Seiten liefert: schwere Platzhalterseiten, Weiterleitungen auf andere Bereiche, geändertes Layout. Ein steiler Abfall bedeutet, dass du statt vollständiger Seiten kurze Fehlerantworten bekommst. Zähle den Traffic in beide Richtungen und normalisiere ihn unbedingt auf die Anzahl erfolgreicher Anfragen: „Kilobyte pro erfolgreicher Anfrage“ ist eine viel aussagekräftigere Metrik als „Gigabyte pro Stunde“.

Wie das im Code aussieht

Unten ein minimales Metrik-Set für Prometheus in Python. Achte auf die Labels: Pool, Land, Betreiber, Proxy-Typ. Die Adresse des konkreten Proxys fehlt absichtlich in den Labels – wir erklären das im Abschnitt über Dimensionen.

from prometheus_client import Counter, Histogram

LABELS = ["pool", "country", "operator", "proxy_type"]

REQUESTS = Counter("proxy_requests_total", "Logische Anfragen durch den Pool", LABELS + ["outcome"])
ATTEMPTS = Counter("proxy_attempts_total", "Versuche inkl. Retries", LABELS + ["attempt_kind"])
TTFB = Histogram("proxy_ttfb_seconds", "Zeit bis zum ersten Byte", LABELS, buckets=(0.1, 0.25, 0.5, 1, 2, 4, 8, 16, 30))
CONNECT = Histogram("proxy_connect_seconds", "Verbindungsaufbau zum Proxy", LABELS, buckets=(0.05, 0.1, 0.25, 0.5, 1, 2, 5))
BYTES = Counter("proxy_bytes_total", "Bytes durch den Pool", LABELS + ["direction"])

def observe(ctx, outcome, ttfb, connect, bytes_in, bytes_out, attempt):
base = dict(pool=ctx.pool, country=ctx.country, operator=ctx.operator, proxy_type=ctx.proxy_type)
kind = "first" if attempt == 1 else "retry"
ATTEMPTS.labels(**base, attempt_kind=kind).inc()
if outcome in ("ok", "upstream_4xx", "upstream_5xx", "truncated", "upstream_timeout"):
TTFB.labels(**base).observe(ttfb)
CONNECT.labels(**base).observe(connect)
BYTES.labels(**base, direction="in").inc(bytes_in)
BYTES.labels(**base, direction="out").inc(bytes_out)
if outcome == "ok" or attempt == ctx.max_attempts:
REQUESTS.labels(**base, outcome=outcome).inc()

Beachte das Detail: Der Zähler der logischen Anfragen wird nur bei Erfolg oder bei Erschöpfung der Versuche erhöht. So spiegelt die Erfolgsquote das Ergebnis für den Nutzer deines Codes wider, während der Versuche-Zähler die tatsächliche Arbeit des Pools zeigt. Die Differenz zwischen beiden ist die Retry-Quote.

Tabelle: Metrik, was ein Anstieg bedeutet, erste Maßnahme

MetrikWas ein Anstieg bedeutetErste Maßnahme
Anteil proxy_connect_errorProxy-Knoten nicht erreichbar oder überlastet; Problem zwischen dir und dem ProviderEigenen ausgehenden Kanal und DNS prüfen, dann nach Segmenten vergleichen: wenn ganzer Pool, Ursache bei dir suchen; wenn ein Segment, Provider mit Daten benachrichtigen
Anteil proxy_auth_errorZugang abgelaufen, Credentials geändert, Verbindungslimit überschrittenAktualität der Zugangsdaten und Limits im Provider-Panel prüfen, solche Anfragen nicht wiederholen
Anteil upstream_timeoutZielressource antwortet über diese Route langsam oder Proxy kommt nicht hinterherTTFB nach Zielhosts vergleichen: wenn ein Host, Problem des Ziels; wenn alle, Route des Proxys
Anteil upstream_4xx (außer 404)Zielressource begrenzt Intensität oder hat Zugriffsregeln geändertFrequenz der Anfragen an die Ressource senken, Regeln prüfen, Retries nicht erhöhen
Anteil upstream_5xxProblem auf Seiten der ZielressourceAbwarten, Last senken; Proxys sind wahrscheinlich unschuldig
Anteil truncatedVerbindungen brechen in der Mitte ab: instabiler Kanal, zu kurze Read-TimeoutsRead-Timeouts und Antwortgrößen prüfen; nach Betreibern vergleichen
p95 Verbindungszeit zum ProxyDegradation der Verfügbarkeit der Proxy-Knoten oder deines Netzwerk-StacksAnzahl gleichzeitiger Verbindungen und Port-Erschöpfung bei dir prüfen
p95 TTFB bei stabiler VerbindungszeitRoute vom Proxy zum Ziel langsamer oder Ziel verlangsamtNach Zielhosts und Pool-Segmenten aufschlüsseln
p99 Gesamtzeit bei stabiler TTFBBandbreite des Proxy-Kanals gesunken oder Antworten größerBytes pro Anfrage prüfen; wenn Größe unverändert, Problem der Bandbreite
Retry-QuoteVersteckte Degradation: Erfolg hält nur durch Wiederholungen, Kosten steigenHerausfinden, welche Fehlerkategorie die Wiederholungen verursacht, und sie bearbeiten
Bytes pro erfolgreicher AnfrageZielressource liefert anderen Inhalt: Platzhalter, Weiterleitungen, geänderte StrukturInhalt der Antworten an einer Stichprobe prüfen, Parser und Validierung aktualisieren
Gesamttraffic bei stabiler AnfragezahlWachstum von Retries oder Antwortgröße; direkter Schlag aufs BudgetAuf zwei Faktoren aufteilen: Anzahl Versuche und Bytes pro Versuch

Was in das Log pro Anfrage gehört – und was nicht

Metriken beantworten die Frage „was passiert“, Logs die Frage „womit genau“. Wenn das Dashboard einen Anstieg der Fehler im Segment „Land X, Betreiber Y“ zeigt, musst du verstehen, ob alle Adressen dieses Segments schuld sind oder zwei von dreißig. Metriken mit Adress-Label sprengen das Storage, Logs nicht – denn Logs aggregierst du nicht dauerhaft, sondern filterst sie auf Anfrage.

Pflichtfelder einer Log-Zeile

Jeder Versuch einer Anfrage durch den Pool soll einen strukturierten Eintrag erzeugen, idealerweise im JSON-Format. Hier die Felder, die wir als minimal ausreichend betrachten:

  • ts – Zeitstempel in UTC mit Millisekunden. Keine lokale Zeit. Niemals.
  • request_id – Kennung der logischen Anfrage, gemeinsam für alle ihre Versuche. Erlaubt, die gesamte Geschichte einer Anfrage mit einem Befehl zusammenzuziehen.
  • attempt und max_attempts – Versuchsnummer und Limit. Ohne Versuchsnummer kannst du den ersten Fehler nicht vom dritten unterscheiden.
  • proxy_id – interne Kennung des Proxys. Keine Adresse mit Port und keine Verbindungszeichenfolge. Eine interne Kennung, die du über eine separate geschützte Tabelle mit der echten Adresse abgleichen kannst.
  • proxy_type, country, operator, pool – dieselben Dimensionen wie in den Metriken. Das erlaubt den Sprung zwischen Dashboard und Logs ohne Neuberechnung.
  • target_host – Domainname des Ziels. Keine vollständige URL: in Query-Strings gibt es manchmal Tokens und personenbezogene Daten.
  • method – HTTP-Methode.
  • status – HTTP-Code der Antwort, falls erhalten, sonst null.
  • outcome – genau die Klassifizierung des Ergebnisses, von der oben die Rede war.
  • error_class – Klasse der Exception oder kurzer Code des Transportfehlers, wenn das Ergebnis nicht ok ist.
  • connect_ms, ttfb_ms, total_ms – die drei Latenzen in Millisekunden.
  • bytes_in, bytes_out – Größe des Antwort-Bodys und Größe der Anfrage.
  • session_id – wenn du sticky Sessions nutzt, die Kennung der Session; so siehst du, dass sich Fehler in langen Sessions konzentrieren.
  • retry_reason – für Versuche ab dem zweiten: das Ergebnis des vorherigen Versuchs.

Beispielzeile:

{"ts":"2026-03-14T11:32:07.412Z","request_id":"7f3a9c1e","attempt":2,"max_attempts":3,"retry_reason":"upstream_timeout","proxy_id":"px-2841","proxy_type":"mobile","country":"RU","operator":"op_a","pool":"pricing","target_host":"shop.example","method":"GET","status":200,"outcome":"ok","error_class":null,"connect_ms":184,"ttfb_ms":1420,"total_ms":2310,"bytes_in":48211,"bytes_out":612,"session_id":"s-19b"}

Eine solche Zeile wiegt etwa 400 Bytes. Bei 50 Anfragen pro Sekunde sind das ungefähr 1,7 Gigabyte pro Tag ohne Kompression und 200–300 Megabyte mit Kompression. Das ist selbst für ein bescheidenes Budget machbar.

Was in Logs nicht vorkommen darf

Hier wechseln wir von Engineering zu Verantwortung. Die Liste ist kurz, aber jeder Punkt ist wichtig:

  1. Zugangsdaten zum Proxy. Das häufigste Leck ist die Verbindungszeichenfolge der Form http://login:passwort@host:port, die komplett ins Log gerät, weil der Entwickler die Konfiguration „zum Debuggen“ geloggt hat. Logs werden in Tickets kopiert, an Kollegen weitergeleitet, jahrelang gespeichert. Zugangsdaten darin sind Zugangsdaten, die jeder sieht, der das Log je gesehen hat.
  2. Header Proxy-Authorization und alle anderen Autorisierungs-Header, einschließlich Header an die Zielressource.
  3. Antwort-Body. Er ist riesig, er kann personenbezogene Daten Dritter enthalten, und für die Proxy-Diagnose ist er fast nie nötig. Wenn du verstehen musst, was die Zielressource geliefert hat, speichere einen Hash des Bodys und seine Länge; die Antwort selbst in einen separaten Speicher mit kurzer Lebensdauer, nur stichprobenartig.
  4. Cookies in beide Richtungen.
  5. Vollständige URL mit Query-Parametern. Host reicht, bei Bedarf der Pfad ohne Parameter.
  6. Echte Proxy-Adresse im Klartext, wenn das Log in einen gemeinsamen Speicher geht. Eine interne Kennung reicht.

Die Praxis, die wir empfehlen: Implementiere das Redigieren auf Ebene einer einzigen Funktion, durch die jeder proxybezogene Eintrag läuft. Verlass dich nicht auf die Disziplin der Entwickler.

import re

CRED_IN_URL = re.compile(r"(https?|socks5h?)://[^/@:]+:[^/@]+@")
SENSITIVE_HEADERS = {"proxy-authorization", "authorization", "cookie", "set-cookie"}

def redact(record: dict) -> dict:
for key, value in list(record.items()):
if key.lower() in SENSITIVE_HEADERS:
record[key] = "[redacted]"
elif isinstance(value, str):
record[key] = CRED_IN_URL.sub(lambda m: m.group(1) + "://[redacted]@", value)
record.pop("body", None)
return record

Sampling und Speicherung

Muss jeder Versuch geloggt werden? Fehler – ja, alle. Erfolgreiche Versuche können gesampelt werden: bei mehr als hundert Anfragen pro Sekunde reichen 5–10 % erfolgreicher Zeilen, um Latenzen zu schätzen, weil die Metriken das vollständige Bild enthalten. Wichtig ist, dass die Sampling-Entscheidung pro request_id getroffen wird, nicht zufällig pro Versuch: sonst bekommst du den zweiten Versuch ohne den ersten. Aufbewahrungsdauer detaillierter Logs – üblicherweise 7–14 Tage; Aggregate und Metriken leben länger.

Perzentile statt Mittelwert: Warum der Mittelwert Probleme verdeckt

Stell dir einen Pool aus hundert Adressen vor. Neunzig antworten in 0,8 Sekunden. Zehn in 9 Sekunden, weil sie auf eine überlastete Basisstation getroffen sind. Die durchschnittliche Antwortzeit beträgt 1,62 Sekunden. Vor einer Woche, als alles gut war, lag der Durchschnitt bei 1,4. Ein Anstieg um 15 %. Wer reagiert darauf? Niemand. Dabei wartet jede zehnte deiner Anfragen neun Sekunden, und wenn du ein Timeout von acht hast, fällt jede zehnte Anfrage mit einem Fehler aus.

Das arithmetische Mittel beschreibt Verteilungen, die einer Glocke ähneln, gut. Latenzen über Proxys ähneln nie einer Glocke. Es ist eine Verteilung mit langem rechtem Schweif: die meisten Anfragen sind schnell, aber einige sehr langsam, und genau sie bestimmen Nutzererlebnis und Kosten. Mehr noch, Latenzen über einen Pool sind oft bimodal: es gibt einen „normalen“ Buckel und einen Buckel problematischer Adressen. Das Mittel liegt zwischen ihnen, an einem Punkt, an dem fast keine realen Anfragen liegen.

Was p50, p95, p99 bedeuten

Perzentil ist der Wert, unterhalb dessen ein gegebener Anteil der Beobachtungen liegt. p50 (Median) – die Hälfte der Anfragen ist schneller als dieser Wert. p95 – 95 % schneller, 5 % langsamer. p99 – nur ein Prozent langsamer. Für einen Proxy-Pool empfehlen wir, drei Perzentile mit unterschiedlichen Zielen anzusehen:

  • p50 zeigt die „typische“ Anfrage. Wenn p50 steigt, ist die Mehrheit degradiert. Das ist ein seltenes und ernstes Signal.
  • p95 – Arbeitsmetrik für Alarme. Sie ist empfindlich gegenüber Problemsegmenten (im obigen Beispiel zeigt p95 sofort 9 Sekunden), aber robust gegen einzelne Ausreißer.
  • p99 – Metrik für Timeouts und Planung. Dein Timeout sollte etwas höher liegen als ein „gesundes“ p99, sonst brichst du normale Anfragen ab und verwandelst sie in Retries.

Wie man exakt rechnet

Wenn es wenige Daten gibt (Tausende Werte im Fenster), rechne exakt: sortieren und das Element nach Index nehmen.

def percentile(values, p):
if not values:
return None
ordered = sorted(values)
k = (len(ordered) - 1) * p / 100.0
lo = int(k)
hi = min(lo + 1, len(ordered) - 1)
return ordered[lo] + (ordered[hi] - ordered[lo]) * (k - lo)

ttfb_window = [0.8, 0.9, 0.7, 9.1, 0.85, 8.7, 0.78, 0.92, 0.81, 0.79]
print(percentile(ttfb_window, 50), percentile(ttfb_window, 95))

Exakte Berechnung skaliert nicht: Um p95 über eine Woche über Millionen von Anfragen zu rechnen, müsste man alle Werte speichern. Deshalb nutzen industrielle Systeme approximative Strukturen.

Wie man in großem Maßstab rechnet: Histogramme

Prometheus und kompatible Systeme speichern ein Histogramm als Satz von Zählern über Buckets: wie viele Beobachtungen unter 0,1 s, unter 0,25 s, unter 0,5 s usw. Das Perzentil wird durch lineare Interpolation innerhalb des Buckets rekonstruiert. Die Genauigkeit hängt davon ab, wie geschickt die Bucket-Grenzen gewählt sind. Für Latenzen über Proxys funktioniert eine logarithmische Skala von 100 Millisekunden bis 30 Sekunden gut: 0,1; 0,25; 0,5; 1; 2; 4; 8; 16; 30. Wenn dein gesundes p95 bei etwa 2 Sekunden liegt und das Timeout bei 15, liefert so ein Raster eine Abweichung im Bereich von 20–30 % am Schweif – genug für Alarme und Diagnose.

histogram_quantile(0.95,
sum by (le, country, operator) (
rate(proxy_ttfb_seconds_bucket{pool="pricing"}[5m])
)
)

Achte auf sum by (le, ...) innen. Das ist ein kritisch wichtiges Detail: Histogramme verschiedener Instanzen deines Clients müssen zuerst summiert und dann der Quantil berechnet werden. Die umgekehrte Operation – p95 auf jeder Instanz berechnen und mitteln – ist mathematisch sinnlos. Das Mittel von Perzentilen ist kein Perzentil. Das ist einer der häufigsten Fehler, und er kann ein Problem genauso zuverlässig verbergen wie ein gewöhnlicher Mittelwert.

Wenn du hohe Genauigkeit am Schweif brauchst (zum Beispiel, weil du SLA auf Latenz an interne Kunden verkaufst), nutze Strukturen wie t-digest oder HDR-Histogramme, die bei festem Speicherverbrauch Genauigkeit im Bereich von Bruchteilen eines Prozents liefern. Für die meisten Teams reichen Standard-Histogramme.

Segmentierung nach Dimensionen: Land, Betreiber, Proxy-Typ

Hier ein Szenario, das illustriert, wofür Dimensionen nötig sind. Du hast einen Pool aus 400 Adressen, 5 % davon sind mobile Adressen eines Betreibers in einer Region. Dieses Segment ist vollständig degradiert: die Erfolgsquote dort ist auf 10 % gefallen. Was zeigt der allgemeine Erfolgsquoten-Graph über den Pool? Einen Rückgang von 99 % auf 94,5 %. Auffällig, aber nicht kritisch. Der Bereitschaftsdienst denkt „das Netz spinnt“ und geht weiter. Und der Datenkunde in dieser Region bekommt nichts.

Die Segmentierung nach Dimensionen (in der Prometheus-Terminologie – Labels) löst genau diese Aufgabe: Sie erlaubt, das Aggregat in Segmente zu zerlegen und zu sehen, dass das Problem lokalisiert ist. Aber Segmentierung hat ihren Preis, und den muss man verstehen.

Welche Dimensionen man wählen sollte

Eine gute Dimension hat zwei Eigenschaften: eine kleine, begrenzte Zahl von Werten und eine Aufteilung, die eine echte Diagnosefrage beantwortet. Für einen Proxy-Pool empfehlen wir vier Dimensionen in den Metriken:

  • proxy_type – mobil, residentiell, Rechenzentrum. Verschiedene Typen degradieren unterschiedlich und haben unterschiedliche Kosten.
  • country – Land der Ausgangsadresse. Meist 1 bis 30 Werte.
  • operator – Netzbetreiber oder autonomes System, über das die Adresse austritt. Probleme sind oft genau hier lokalisiert: eine überlastete Basisstation oder ein schlechtes Peering eines Betreibers.
  • pool – logischer Zweck: welches Produkt oder welche Aufgabe dieses Segment nutzt. Erlaubt, Vorfälle nach Geschäftswirkung zu trennen.

Eine fünfte Dimension, über die man nachdenken sollte, ist target_host, der Zielhost. Sie wird gebraucht, um Probleme des Proxys von Problemen des Ziels zu unterscheiden. Aber wenn du Tausende verschiedene Domains ansprichst, explodiert die Kardinalität. Kompromiss: Behalte im Label nur die Top-20-Hosts nach Volumen, der Rest wird zu einem Wert other zusammengeführt.

Arithmetik der Kardinalität

Jede eindeutige Kombination von Label-Werten ist eine eigene Zeitreihe. Rechnen wir: 3 Typen × 10 Länder × 15 Betreiber × 4 Pools × 7 Ergebnisse = 12.600 Reihen allein für den Anfragen-Zähler. Ein Histogramm mit 9 Buckets multipliziert seine Reihenzahl mit 10. Insgesamt etwa 50.000–60.000 Reihen. Für Prometheus ist das komfortabel. Jetzt stell dir vor, du fügst ein Label mit der Proxy-Adresse hinzu (400 Werte). Die Zahl der Reihen wächst um den Faktor 400 – auf 20 Millionen. Das ist nicht mehr „komfortabel“, das ist der Tod des Speichers.

Daher die Regel: Die konkrete Proxy-Adresse lebt in den Logs, nicht in den Metriken. Metriken beantworten die Frage „welches Segment ist degradiert“, Logs die Frage „welche Adressen innerhalb des Segments“. Diese Arbeitsteilung liefert Geschwindigkeit und Detailtiefe ohne explosionsartiges Wachstum der Kosten.

Abfragen, die das Segment finden

Die zentrale Diagnosefrage: „Wo ist es am schlimmsten?“ Sie beantwortet eine Sortierung der Segmente nach Erfolgsquote:

bottomk(5,
sum by (country, operator, proxy_type) (rate(proxy_requests_total{outcome="ok"}[10m]))
/
sum by (country, operator, proxy_type) (rate(proxy_requests_total[10m]))
)

Die zweite Frage: „Ist das ein neues Problem oder war das immer so?“ Darauf antwortet der Vergleich mit der Vergangenheit:

(
sum by (operator) (rate(proxy_requests_total{outcome="ok"}[10m])) / sum by (operator) (rate(proxy_requests_total[10m]))
)
-
(
sum by (operator) (rate(proxy_requests_total{outcome="ok"}[10m] offset 1d)) / sum by (operator) (rate(proxy_requests_total[10m] offset 1d))
)

Ein negativer Wert zeigt, wie viel schlechter das Segment im Vergleich zu vor 24 Stunden geworden ist. So ein Graph auf dem Dashboard verwandelt „irgendwo stimmt was nicht“ in „Betreiber B in Land C minus 12 Punkte gegenüber gestern“.

Woher die Werte der Dimensionen kommen

Land und Proxy-Typ sind meist aus der Pool-Konfiguration bekannt. Den Betreiber kann man aus den Metadaten des Anbieters beziehen: Zum Beispiel sind in der Panel und API von Proxeon die Adressen mit Informationen zu Geografie und Betreiber versehen, die du bei der Initialisierung des Pools lädst und neben der internen Proxy-Kennung cachest. Den Betreiber im Moment der Anfrage über externe Datenbanken zu bestimmen, lohnt sich nicht: Das erhöht die Latenz und schafft eine zusätzliche Abhängigkeit.

Alarme, die nicht rauschen: Schwellen, Fenster, Hysterese

Das Schlimmste, was einem Alarmsystem passieren kann, ist nicht ein verpasster Vorfall. Das Schlimmste ist, wenn der Bereitschaftsdienst 40 Benachrichtigungen am Tag bekommt, von denen 39 nichts bedeuten, und aufhört, die vierzigste zu lesen. Ein Alarm, der ignoriert wird, ist schlimmer als kein Alarm, weil er die Illusion von Kontrolle erzeugt.

Prinzip 1: Alarm auf das Symptom, nicht auf die Ursache

Setze keinen Alarm auf „Proxy px-2841 hat einen Fehler zurückgegeben“. Eine einzelne Adresse darf kränkeln; dafür ist der Quarantäne-Mechanismus im Pool zuständig, den wir hier nicht besprechen. Setze den Alarm auf das, was der Konsument deiner Daten spürt: Erfolgsquote des Pools unter Ziel, p95 über Ziel, Retries über Ziel. Die Ursache findest du über das Dashboard in einer Minute, wenn du die Segmentierung richtig gemacht hast. Die Aufgabe des Alarms ist, zu wecken, nicht zu erklären.

Prinzip 2: minimales Traffic-Volumen

Eine Erfolgsquote von 50 % bei zwei Anfragen in fünf Minuten ist eine misslungene Anfrage, kein Vorfall. Jede Regel auf eine Quote muss eine Bedingung auf eine Mindestanzahl von Anfragen im Fenster enthalten. Sonst ertrinkst du nachts, wenn der Traffic fällt, in Fehlalarmen.

Prinzip 3: Fenster und Budget-Verbrauchsrate

Wenn du ein Ziel-Zuverlässigkeitsniveau definiert hast (zum Beispiel 98 % erfolgreiche Anfragen pro Monat), hast du ein Fehlerbudget: 2 % der Anfragen. Ein guter Alarm reagiert auf die Geschwindigkeit des Budget-Verbrauchs, nicht auf den Momentanwert. Die Praxis, die in der Zuverlässigkeitsbranche Standard geworden ist, sind Multi-Window-Alarme: ein kurzes Fenster (5 Minuten) fängt steile Abstürze, ein langes (1 Stunde) langsame Degradation, und beide müssen gleichzeitig überschritten sein, um kurze Ausreißer auszuschließen.

Prinzip 4: Hysterese

Hysterese sind unterschiedliche Schwellen für Auslösen und Aufheben eines Alarms. Wenn der Alarm bei einer Erfolgsquote unter 95 % auslöst und bei Rückkehr über 95 % aufgehoben wird, erzeugt eine um 95 % schwankende Metrik eine Serie von Benachrichtigungen „ausgelöst – aufgehoben – ausgelöst“ alle paar Minuten. Lösung: auslösen bei 95 %, aufheben bei 97 %. Oder, einfacher, verlangen, dass die Bedingung kontinuierlich eine bestimmte Zeit hält (in Prometheus das Schlüsselwort for), und zusätzlich eine Verzögerung für das Aufheben setzen.

Prinzip 5: Schweregrade und Routing

Nicht alles muss per Anruf zugestellt werden. Teile die Benachrichtigungen in zwei bis drei Stufen: kritisch (Wirkung auf Datenkonsumenten jetzt – Anruf oder Push an den Bereitschaftsdienst), Warnung (Degradation eines Segments, Anstieg der Retries, Traffic-Anomalie – Nachricht in den Kanal, Bearbeitung zu Arbeitszeiten), informativ (fehlende Daten einer Instanz, Annäherung an ein Traffic-Limit – tägliche Zusammenfassung).

Beispiel-Regelsatz

groups:
- name: proxy-pool
rules:
- record: pool:success_ratio:5m
expr: sum by (pool) (rate(proxy_requests_total{outcome="ok"}[5m])) / sum by (pool) (rate(proxy_requests_total[5m]))
- record: pool:success_ratio:1h
expr: sum by (pool) (rate(proxy_requests_total{outcome="ok"}[1h])) / sum by (pool) (rate(proxy_requests_total[1h]))
- record: pool:request_rate:5m
expr: sum by (pool) (rate(proxy_requests_total[5m]))

- alert: ProxyPoolSuccessLow
expr: pool:success_ratio:5m < 0.95 and pool:success_ratio:1h < 0.97 and pool:request_rate:5m > 0.5
for: 5m
labels: { severity: critical }
annotations:
summary: "Erfolgsquote im Pool {{ $labels.pool }} unter Ziel"

- alert: ProxyPoolLatencyHigh
expr: histogram_quantile(0.95, sum by (le, pool) (rate(proxy_ttfb_seconds_bucket[10m]))) > 4 and pool:request_rate:5m > 0.5
for: 10m
labels: { severity: warning }

- alert: ProxyPoolRetryRateHigh
expr: sum by (pool) (rate(proxy_attempts_total[15m])) / sum by (pool) (rate(proxy_requests_total[15m])) > 1.4
for: 15m
labels: { severity: warning }

- alert: ProxyPoolSliceDegraded
expr: (sum by (pool, country, operator) (rate(proxy_requests_total{outcome="ok"}[15m])) / sum by (pool, country, operator) (rate(proxy_requests_total[15m]))) < 0.85 and sum by (pool, country, operator) (rate(proxy_requests_total[15m])) > 0.2
for: 15m
labels: { severity: warning }

- alert: ProxyPoolNoData
expr: absent(proxy_requests_total) or pool:request_rate:5m == 0
for: 10m
labels: { severity: warning }

Achte auf die letzte Regel. Fehlende Daten sind der tückischste Ausfall: Der Client ist abgestürzt, es gibt keine Metriken, die Graphen sind flach und grün, alle sind ruhig. Ein Alarm auf die Stille ist Pflicht.

Checkliste für jeden neuen Alarm

  1. Steht in der Annotation, was der Empfänger tun soll? Wenn nein, ist die Regel nicht fertig.
  2. Gibt es eine Bedingung auf minimalen Traffic?
  3. Gibt es eine Verzögerung for oder eine Hysterese?
  4. Wurde die Regel an der Historie des letzten Monats geprüft: wie oft hätte sie ausgelöst und wie viele davon waren echte Vorfälle?
  5. Ist klar, zu welchem Schweregrad sie gehört und wohin sie geroutet wird?
  6. Gibt es einen Rückkopplungsprozess: Wenn der Alarm ausgelöst hat und falsch war, wer und wann überprüft ihn?

Schnelldiagnose über das Dashboard: drei typische Bilder

Ein Dashboard ist keine Galerie von Graphen, sondern ein Entscheidungswerkzeug. Ein gutes Dashboard für einen Proxy-Pool ist wie ein Trichter aufgebaut. Obere Zeile: vier Signale über den gesamten Pool für die letzten 6 Stunden mit der gestrigen Linie dünn überlagert. Zweite Zeile: dieselben Signale aufgeschlüsselt nach Land, Betreiber und Typ – als Tabelle oder Heatmap, sortiert nach dem schlechtesten Wert. Dritte Zeile: Erfolgsquote und TTFB nach Top-10-Zielhosts. Vierte: Verhältnis von Verbindungszeit, TTFB und Gesamtzeit. Dazu vertikale Marker für Deploys und Änderungen der Pool-Konfiguration.

Mit so einem Dashboard reduziert sich die Diagnose auf die Beantwortung von drei Fragen: „Ist der ganze Pool degradiert oder ein Segment?“, „Sind alle Ziele degradiert oder eines?“, „Welche der drei Latenzen ist gestiegen?“. Die Kombinationen der Antworten ergeben drei typische Bilder.

Bild 1: Alle Segmente gleichzeitig degradiert

Alle Länder, alle Betreiber, alle Proxy-Typen zeigen synchron einen Anstieg von Fehlern oder Latenzen, auf die Minute genau. Externe Provider und Betreiber brechen nicht so ein: Sie haben keinen gemeinsamen Ausfallpunkt auf deiner Seite. Also liegt das Problem bei dir: dein ausgehender Kanal, dein DNS, Erschöpfung von Dateideskriptoren oder lokalen Ports, ein kürzlicher Deploy, der Timeouts oder die Größe des Verbindungspools geändert hat, Konkurrenz um die CPU mit einem anderen Prozess auf derselben Maschine. Erste Maßnahme: auf den Deploy-Marker und die Verbindungszeit zum Proxy schauen. Wenn die Verbindungszeit gestiegen ist und die TTFB (abzüglich Verbindung) stabil bleibt – das Problem liegt zwischen dir und den Proxy-Knoten, und fast sicher ist es deine Seite oder dein Netzbetreiber.

Bild 2: Ein Segment ist degradiert

Ein Betreiber in einem Land oder ein Proxy-Typ zeigt einen Einbruch, während die anderen Segmente stabil sind. Das ist das häufigste Bild. Die Ursache liegt beim Netzbetreiber (Überlastung eines Netzsegments, planmäßige Arbeiten) oder im Infrastruktursegment des Proxy-Anbieters. Erste Maßnahme: das Gewicht dieses Segments in der Lastverteilung vorübergehend senken (der Mechanismus liegt außerhalb dieses Artikels) und dem Provider-Support eine Nachricht mit konkreten Daten schicken: Segment, Startzeit, Metriken vorher und nachher, Beispiele von request_id. Der Support von Proxeon reagiert auf solche Anfragen schneller als auf „bei euch ist alles schlecht“, einfach weil er eine fertige Lokalisierung bekommt. Zweite Maßnahme: Logs des Segments prüfen, um zu verstehen, ob alle Adressen innerhalb des Segments degradiert sind oder nur einige.

Bild 3: Erfolgsquote für einen Zielhost gefallen, Latenzen normal

Alle Pool-Segmente erreichen weiterhin alle Hosts gut, außer einem. Für diesen Host steigt der Anteil upstream_4xx oder das Volumen der Bytes pro Anfrage hat sich geändert. Die Proxys sind hier überhaupt nicht schuld. Die Zielressource hat ihr Verhalten geändert: Ratenbegrenzung eingeführt, Seitenstruktur geändert, liefert anderen Inhalt für einen Teil der Anfragen. Erste Maßnahme: Intensität der Anfragen an diese Ressource senken und prüfen, ob deine Sammlung ihren Nutzungsregeln entspricht. Zweite: Inhalt an einer Stichprobe prüfen und die Antwortvalidierung aktualisieren. Auf keinen Fall sollte man in dieser Situation die Zahl der Retries erhöhen oder die Adressen häufiger wechseln – das erhöht nur die Last auf der Ressource und deine Traffic-Kosten.

Bild 4 (Bonus): Retries steigen, Erfolg bleibt

Erfolgsquote 99 %, Latenzen erträglich, aber die Retry-Quote ist von 1,1 auf 1,8 gestiegen. Das ist versteckte Degradation. Sie kostet dich schon jetzt Geld und wird sichtbare Degradation, sobald der Vorrat an Versuchen erschöpft ist. Erste Maßnahme: schauen, welches Ergebnis die Wiederholungen verursacht (Feld retry_reason in den Logs), und dann einem der ersten drei Bilder folgen.

Checkliste Diagnose in 60 Sekunden

  1. Sekunden 0–10: obere Zeile des Dashboards. Welches der vier Signale hat sich von der gestrigen Linie entfernt? Wann genau?
  2. Sekunden 10–25: Tabelle nach Segmenten. Ist ein Segment rot oder alle? Wenn alle – Bild 1, zu den Deploy-Markern und deiner Infrastruktur gehen.
  3. Sekunden 25–40: Tabelle nach Zielhosts. Ein Host oder alle? Wenn einer – Bild 3, Proxys sind unschuldig.
  4. Sekunden 40–50: Verhältnis der Latenzen. Ist die Verbindung, TTFB oder die Gesamtzeit gestiegen?
  5. Sekunden 50–60: einen Satz formulieren: „Seit [Zeit] ist im Segment [Segment] für [Ziel] [Metrik] von [vorher] auf [nachher] gestiegen, vermutlich wegen [Hypothese].“ Dieser Satz geht in den Incident-Kanal und ins Provider-Ticket.

Typische Fehler, die den Nutzen der Observability zunichtemachen

Wir haben Dutzende Einführungen von Monitoring rund um Proxys gesehen. Die Fehler wiederholen sich mit erstaunlicher Konstanz.

  • Durchschnittliche Antwortzeit als Hauptmetrik. Schon besprochen: Der Mittelwert versteckt den Schweif, und der Schweif ist das eigentliche Problem.
  • Perzentile zwischen Instanzen mitteln. Histogramme summieren, nicht fertige p95.
  • Proxy-Adresse als Metrik-Label. Kardinalitätsexplosion und Speicherausfall nach einem Monat.
  • Zugangsdaten in Logs. Eine Zeile „for debug“ – und das Passwort liegt ein Jahr im gemeinsamen Log-Speicher.
  • Retries werden als Erfolg gezählt. Wenn nur das Endergebnis zählt, siehst du nie, dass der Pool dreimal intensiver arbeitet als nötig.
  • Codes 429 und 403 von der Zielressource werden als Proxy-Fehler erfasst. Ein Klassifizierungsfehler, der die Untersuchung in die falsche Richtung lenkt und die Beziehung zum Provider beschädigt.
  • Eine Latenz statt drei. Ohne getrennte Messung von Verbindung, TTFB und Gesamtzeit unterscheidest du Verfügbarkeitsproblem nicht von Bandbreitenproblem.
  • Alarm ohne minimalen Traffic. Nächtlicher Lärm und Vertrauensverlust in die Benachrichtigungen.
  • Alarm auf jede einzelne Adresse. Das ist Aufgabe der Quarantäne, nicht der Benachrichtigung eines Menschen.
  • Kein Alarm auf fehlende Daten. Client abgestürzt – Graphen grün.
  • Lokale Zeit in Logs. Der Abgleich mit Provider-Daten und Kollegen in anderen Zeitzonen wird zur Qual.
  • Dashboard ohne Deploy-Marker. Die Hälfte der Vorfälle fällt mit einer Code- oder Konfigurationsänderung zusammen. Ohne Marker suchst du die Ursache draußen, obwohl sie innen liegt.
  • Detaillierte Logs ewig speichern. Teuer und sinnlos: Nach zwei Wochen braucht man die Details nicht mehr, und Aggregate liegen schon in den Metriken.

Minimaler Stack ohne Überkomplizierung

Observability lässt sich leicht in ein Projekt um des Projekts willen verwandeln. Ein Team aus drei Leuten rollt eine verteilte Message-Queue, einen Cluster einer Spalten-Datenbank und ein Trace-System aus – für einen Client, der 20 Anfragen pro Sekunde macht. Mach das nicht. Der Stack muss zur Größe passen.

Stufe 1: ein Service, bis 50 Anfragen pro Sekunde

Es reichen strukturierte JSON-Logs in eine Datei oder stdout und ein Skript, das einmal pro Minute aggregiert und die vier Signale in eine einfache Datenbank oder sogar ins selbe Log schreibt. Dashboard – irgendein Tool, das aus einer Tabelle einen Graphen zeichnen kann. Alarme – ein Cron-Skript, das Aggregate mit Schwellen vergleicht und eine Nachricht in den Messenger schickt. So ein Stack ist in einem Tag aufgesetzt und deckt 80 % des Bedarfs.

Stufe 2: mehrere Services, 50–1000 Anfragen pro Sekunde

Der Standard-Set: Prometheus für Metriken, Grafana für Dashboards, Alertmanager für Routing der Benachrichtigungen, Loki oder ein vergleichbarer Log-Speicher mit Index über Labels. Clients exportieren Metriken über einen eingebauten HTTP-Endpunkt und schreiben JSON-Logs nach stdout; ein Agent sammelt sie ein. Recording Rules berechnen Erfolgsquoten vor, damit Dashboards sofort öffnen. Das ist die Konfiguration aus den Beispielen oben, und sie deckt die überwiegende Mehrheit der Teams ab.

Stufe 3: Dutzende Services, Tausende Anfragen pro Sekunde

Hier ergibt OpenTelemetry als einheitliche Sammelschicht Sinn, ein Spalten-Speicher für Logs mit beliebigen analytischen Abfragen über Milliarden Zeilen und möglicherweise Traces für komplexe Ketten. Aber selbst auf dieser Stufe ändern sich die vier Signale und die Log-Struktur nicht. Es ändert sich nur Transport und Speicherung.

Provider-Daten als Ergänzung

Der Proxy-Anbieter sieht seine Seite: Traffic-Verbrauch pro Port, Anzahl aktiver Verbindungen, Zeit der letzten Rotation. Panel und API von Proxeon liefern diese Statistik, und es ist nützlich, sie neben den eigenen Metriken zu ziehen – zumindest für den Abgleich des Traffic-Verbrauchs: Wenn dein Byte-Zähler und der Zähler des Providers um mehr als 10–15 % auseinandergehen, hast du ein Messproblem (meist werden Header, TLS-Handshakes oder abgebrochene Antworten nicht berücksichtigt). Aber die Provider-Daten ersetzen deine nicht: Nur du weißt, welche Anfrage aus Sicht der Aufgabe erfolgreich war.

Client-Wrapper als einheitlicher Messpunkt

Unabhängig von der Stack-Stufe müssen alle Messungen durch einen Wrapper über den HTTP-Client laufen. Kein „hier messen wir, hier vergessen wir es“. Schematisch:

import time, json, sys, requests

def fetch_via_proxy(ctx, url, timeout=(5, 20)):
t0 = time.perf_counter()
outcome, status, err = "ok", None, None
ttfb = connect = total = 0.0
bytes_in = 0
try:
session = ctx.session # уже настроена на прокси по внутреннему proxy_id
resp = session.get(url, timeout=timeout, stream=True)
ttfb = time.perf_counter() - t0
status = resp.status_code
body = resp.content
bytes_in = len(body)
total = time.perf_counter() - t0
if status >= 500: outcome = "upstream_5xx"
elif status >= 400: outcome = "upstream_4xx"
elif not ctx.validate(body): outcome = "invalid_content"
except requests.exceptions.ProxyError as e:
outcome, err = "proxy_connect_error", type(e).__name__
except requests.exceptions.ReadTimeout as e:
outcome, err = "upstream_timeout", type(e).__name__
except requests.exceptions.ChunkedEncodingError as e:
outcome, err = "truncated", type(e).__name__
finally:
total = total or (time.perf_counter() - t0)
observe(ctx, outcome, ttfb, connect, bytes_in, len(url), ctx.attempt)
record = redact({"ts": ctx.now_utc(), "request_id": ctx.request_id, "attempt": ctx.attempt,
"proxy_id": ctx.proxy_id, "proxy_type": ctx.proxy_type, "country": ctx.country,
"operator": ctx.operator, "pool": ctx.pool, "target_host": ctx.host(url),
"status": status, "outcome": outcome, "error_class": err,
"ttfb_ms": int(ttfb * 1000), "total_ms": int(total * 1000), "bytes_in": bytes_in})
sys.stdout.write(json.dumps(record, ensure_ascii=False) + chr(10))
return outcome, status

Achte auf das outcome invalid_content: eine Antwort mit Code 200, die deine Validierung nicht besteht (zum Beispiel eine Seite ohne den erwarteten Datenblock), ist kein Erfolg. Genau solche Antworten maskieren meist die geänderte Verhalten der Zielressource.

Cases: Wie Observability in der Praxis aussieht

Case 1: Ein Betreiber, ein Land, minus zwei Stunden Ausfall pro Woche

Ein Team für Preis-Monitoring auf öffentlichen Handelsplattformen arbeitete über einen Pool von etwa 300 mobilen und residentiellen Adressen bei einer Intensität von rund 40 Anfragen pro Sekunde. Vor der Einführung der Segmentierung nach Betreibern sahen sie eines: Das allgemeine p95 TTFB sprang zeitweise von 1,6 auf 3–4 Sekunden für einige Stunden, die Erfolgsquote sank um 3–5 Punkte. Die Untersuchung jedes solchen Episods dauerte ein bis zwei Stunden und endete meist mit den Worten „es ist von selbst weggegangen“. Nach dem Hinzufügen der Labels country und operator wurde der erste Episod in zwei Minuten lokalisiert: mobile Adressen eines Betreibers in einer Region, p95 dort bei 11 Sekunden gegenüber einer Norm von 2. Die restlichen 85 % des Pools waren völlig in Ordnung. Das Team senkte das Gewicht des Segments in der Verteilung, schickte dem Provider einen Bericht mit Zeitrahmen und Metriken und bekam einige Stunden später die Bestätigung planmäßiger Arbeiten beim Betreiber. Ergebnis im Quartal: Die Diagnosezeit sank von 90 auf 5 Minuten, die Zahl der Episoden blieb gleich, aber ihr Einfluss auf das gesamte p95 wurde ununterscheidbar.

Case 2: Versteckte Retries und +38 % auf der Traffic-Rechnung

Ein anderes Team entdeckte einen Anstieg des Traffic-Verbrauchs um 38 % über einen Monat bei unveränderter Anzahl von Aufgaben und stabiler Erfolgsquote von 98,5 %. Die erste Hypothese – die Zielseiten sind schwerer geworden. Die Metrik Bytes pro erfolgreicher Anfrage widerlegte das: 46 Kilobyte, wie zuvor. Dafür war die Retry-Quote, die bis dahin niemand angeschaut hatte, von 1,08 auf 1,52 gestiegen. Die Analyse des Feldes retry_reason in den Logs zeigte: Fast alle Wiederholungen wurden durch das Ergebnis upstream_timeout für einen Zielhost verursacht, der langsamer antwortete, aber beim zweiten Versuch innerhalb des Timeouts blieb. Die Proxys waren überhaupt nicht die Ursache. Das Team erhöhte das Read-Timeout für diesen Host von 8 auf 15 Sekunden (das gesunde p99 lag bei 11), senkte die Intensität der Anfragen an ihn und brachte den Traffic-Verbrauch innerhalb von drei Tagen auf das vorherige Niveau zurück. Moral: Die Retry-Quote ist eine Metrik über Geld.

Case 3: Von 200 Benachrichtigungen pro Woche auf sechs

Ein drittes Team erbte ein Alarmsystem mit der Regel „Erfolgsquote irgendeines Segments über 1 Minute unter 90 %“ ohne Traffic-Begrenzung. Nachts, wenn durch einige Segmente zwei bis drei Anfragen pro Minute gingen, löste die Regel Dutzende Male aus. Die Bereitschaftsdienste schalteten den Ton aus. Ein echter Vorfall – ein vierstündiger Ausfall der Verfügbarkeit eines ganzen Segments – wurde morgens anhand von Beschwerden bemerkt. Die Überarbeitung der Regeln dauerte einen Tag: eine Bedingung auf minimalen Traffic hinzugefügt, zwei Fenster eingeführt (5 Minuten und 1 Stunde), eine Verzögerung for von 10 Minuten ergänzt, Segmente auf die Stufe warning verschoben, auf Pool-Ebene ein einziges critical belassen. Ergebnis: etwa sechs Benachrichtigungen pro Woche, von denen vier bis fünf eine echte Aktion erforderten. Die Bereitschaftsdienste schalteten den Ton wieder ein.

FAQ: Häufige Fragen zur Proxy-Pool-Observability

Braucht man Traces, wenn es Metriken und Logs gibt?

Für die meisten Teams – nein. Eine Anfrage durch einen Proxy läuft nicht durch zehn Microservices; sie hat einen Übergang. Die Rolle des Traces übernimmt hier request_id in den Logs: damit ziehst du alle Versuche einer Anfrage zusammen. Traces werden nützlich, wenn der Proxy-Client nur ein Teil einer langen Kette ist (Task-Queue, Parser, Validator, Loader in die Datenbank) und du verstehen musst, wo genau die Zeit verloren geht.

Wie misst man TTFB, wenn der HTTP-Client es nicht direkt liefert?

Die meisten Clients geben die Kontrolle nach Empfang der Header zurück, wenn man Streaming-Lesen anfordert (stream=True oder analog). Der Moment der Rückkehr ist die Annäherung an TTFB. Die Verbindungszeit wird genauer auf Transportebene gemessen: Viele Bibliotheken haben Hooks auf Verbindungsereignisse, einige liefern fertige Informationen über die Phasen der Anfrage. Wenn es keine Hooks gibt, kann man die Zeit bis zum Senden des ersten Bytes der Anfrage separat mit einer Low-Level-Messung erfassen, aber für praktische Zwecke reicht die Annäherung über Streaming-Lesen.

Wie unterscheidet man einen Proxy-Fehler von einem Fehler der Zielressource?

Am Ort des Auftretens. Wenn die Verbindung zum Proxy nicht aufgebaut wurde oder der Proxy einen eigenen Code zurückgegeben hat (zum Beispiel 407 bei Autorisierungsproblemen oder eine spezifische Gateway-Antwort), ist das ein Proxy-Fehler. Wenn der Proxy die Anfrage angenommen hat und du danach einen Code vom Zielserver oder einen Timeout beim Warten auf die Antwort bekommst, ist das ein Fehler zwischen Proxy und Ziel oder der Zielressource selbst. Klassifiziere an dieser Grenze und vergleiche dann nach Segmenten: Fehler des Ziels sind für alle Segmente gleichzeitig sichtbar, Proxy-Fehler für konkrete Segmente.

Welche Retry-Quote ist gesund?

Hängt von den Zielen und dem Proxy-Typ ab. Für mobile Adressen kann 1,05–1,15 als normal gelten: Ein Funknetz ist von Natur aus weniger vorhersehbar. Für Rechenzentrumsadressen – 1,01–1,05. Wichtiger als der absolute Wert ist seine Stabilität: ein Anstieg um 30 % gegenüber dem Wochen-Basisniveau ist ein Anlass, nachzusehen, auch wenn der absolute Wert noch bescheiden aussieht.

Wie wählt man Alarm-Schwellen, wenn es keine Historie gibt?

Beginne mit Beobachtung ohne Alarme: Sammle zwei Wochen Daten, schau dir die Verteilung der Erfolgsquote und p95 über Tage und Stunden an. Setze die Schwelle für critical so, dass sie in diesen zwei Wochen höchstens ein- oder zweimal ausgelöst hätte, und beide Male würdest du zustimmen, dass es ein Vorfall war. Die Schwelle für warning – so, dass sie einige Male pro Woche auslöst. Überprüfe dann monatlich: Ein Alarm, der nie zu einer Aktion geführt hat, wird entweder verschärft oder entfernt.

Sollte man den Antwort-Body zumindest für Fehler speichern?

Für Transportfehler gibt es keinen Body. Für Antworten mit unerwartetem Inhalt (invalid_content) ist es nützlich, die ersten paar hundert Bytes oder einen Hash und die Länge zu speichern, in einem separaten Speicher mit einer Lebensdauer von ein bis zwei Tagen, mit obligatorischer Bereinigung von personenbezogenen Daten. Vollständige Bodies müssen nicht gespeichert werden: Sie sind schwer, können Daten Dritter enthalten, und für die Proxy-Diagnose reicht fast immer zu verstehen, welcher Seitentyp angekommen ist.

Wie gleicht man die eigene Traffic-Zählung mit den Daten des Providers ab?

Zähle Bytes in beide Richtungen, einschließlich Header, und vergleiche Tagessummen pro Port oder Session mit dem, was das Provider-Panel zeigt. Eine Abweichung von 5–10 % ist normal: Bei dir werden Overhead von TLS und abgebrochene Verbindungen nicht berücksichtigt. Eine Abweichung über 20 % ist ein Anlass zu prüfen, ob du nur den Antwort-Body zählst und Versuche verlierst, die vor dem Lesen des Bodys mit einer Exception enden.

Wie lange dauert die Einführung des beschriebenen Ansatzes von Grund auf?

Für einen Client mit vorhandenem HTTP-Wrapper: ein Tag für Metriken und Log-Struktur, ein Tag für das Dashboard, ein Tag für Alarm-Regeln und weitere zwei Wochen Beobachtung vor dem Aktivieren von critical-Alarmen. Wenn du mehrere Clients in verschiedenen Sprachen hast, geht die Hauptzeit in die Abstimmung eines einheitlichen Satzes von Ergebnissen und Labels – hier ist es wichtig, nicht zu hetzen, denn das Umbenennen einer Metrik nach einem Jahr gesammelter Historie tut sehr weh.

Was tun, wenn der Provider für einen Teil der Adressen keine Betreiberdaten liefert?

Führe den Wert unknown ein und beobachte seinen Anteil. Wenn unknown mehr als 10–15 % des Pools ausmacht, verlieren deine Segmente nach Betreiber ihren Sinn – fordere vom Provider vollständige Metadaten an oder markiere die Adressen einmalig manuell beim Hinzufügen zum Pool.

Fazit: Eine Minute statt einer Stunde

Zurück zur Szene vom Anfang. „Die Proxys sind lahm“ – ein Satz, der keine Information enthält. Alles, was in diesem Leitfaden beschrieben ist, dient einem Ziel: ihn in einen Satz mit Zahlen, Segment, Zeit und Hypothese zu verwandeln. Vier Signale – Erfolgsquote, Latenz nach Perzentilen, Retry-Quote, Traffic-Verbrauch – liefern das Bild des Pool-Zustands. Ein strukturiertes Log mit Request-ID, Proxy-ID, Versuchsnummer, Code, drei Latenzen und Größe – die Details für die Untersuchung. Perzentile statt Mittelwert zeigen den Schweif, in dem die realen Probleme leben. Die Segmentierung nach Land, Betreiber und Typ lokalisiert die Degradation bis zum Segment. Alarme mit minimalem Traffic, zwei Fenstern und Hysterese lösen selten und treffsicher aus. Das Trichter-Dashboard beantwortet drei Fragen in einer Minute.

Womit morgen anfangen? Erster Schritt – ein Wrapper über den Client mit einheitlicher Ergebniskategorisierung und einer einzigen Schreibfunktion. Zweiter – ein TTFB-Histogramm und ein Versuche-Zähler mit Labels nach Land, Betreiber und Typ. Dritter – eine Redaktionsfunktion für Logs, durch die alles ohne Ausnahme läuft. Vierter – ein Dashboard aus vier Graphen und einer Tabelle nach Segmenten. Fünfter – zwei Wochen Beobachtung und erst dann Alarme. Nach einem Monat wirst du auf den Graphen der Retry-Quote schauen und dich vielleicht wundern: Der Pool arbeitete deutlich intensiver, als du dachtest. Und nach einem Quartal wird der Satz „bei uns ist alles langsam“ aus deinem Chat verschwinden – nicht weil es keine Probleme mehr gäbe, sondern weil du jedes von ihnen beim Namen nennen wirst.