Ein Redirect blockiert den Besucher. Er klickt auf einen Kurzlink, der Browser wartet, und nichts anderes lädt, bis der 302 zurückkommt. Deshalb hat services/edge-redirect ein Latenzbudget statt eines Gefühls von "schnell genug": p50 5 ms und p95 15 ms bei einem Cache-Treffer.
Lesen Sie das als Ziel, nicht als Ergebnis. Das Budget steht im README des Dienstes und im Bereitschafts-Runbook, und unsere Abnahmeschwelle im Lasttest baut darauf auf. Wir haben keinen eingecheckten Benchmark und keine Lasttest-Ergebnisdatei, die ein Perzentil pro Region belegt, deshalb druckt dieser Beitrag keines. Er beschreibt anhand des Codes, was der Hot Path tut und, ebenso nützlich, was er verweigert.
TL;DR
- Die Edge ist ein Go-Dienst auf einer allokationsarmen Go-HTTP-Bibliothek, der heute auf einem einzelnen Produktions-Host hinter einem Reverse Proxy läuft, mit einem geplanten Multi-Region-Edge.
- Der Lookup läuft durch einen prozessinternen LRU (100.000 Einträge, 60 Sekunden TTL), dann einen gemeinsamen In-Memory-Cache (5 Minuten TTL), dann api-core über gRPC mit einem Timeout von 1,5 Sekunden.
- Das Klick-Ereignis wird aus einer separaten Goroutine erzeugt. Der Besucher wartet nie darauf.
- Budget: p50 5 ms, p95 15 ms bei einem Cache-Treffer. Das sind Ziele, gemessene Perzentile werden hier nicht veröffentlicht.
Warum es das Budget gibt
Die Redirect-Zeit addiert sich zu allem, was danach kommt. Schickt ein Kampagnenlink Traffic auf eine Landingpage, ist der Redirect Teil von deren Time to First Byte. Ein Budget erzwingt eine Entscheidung, sobald jemand diesem Pfad Arbeit hinzufügen will: Sie passt, oder sie wandert in einen Worker.
Die Zahlen aus services/edge-redirect/README.md:
| Perzentil | Cache-Treffer | Cache-Miss |
|---|---|---|
| p50 | 5 ms | 40 ms |
| p95 | 15 ms | 100 ms |
| p99 | 30 ms | 200 ms |
Sie sind ein Designziel. Brauchen Sie gemessene Zahlen für eine Anbieterprüfung, führen Sie das am Ende dieses Beitrags beschriebene k6-Szenario gegen Ihre eigenen Links aus und bewahren Sie die Ausgabe auf.
Der Request-Pfad, Schritt für Schritt
internal/handler/redirect.go dokumentiert den Ablauf am Anfang der Datei. In der Reihenfolge:
- Den Host in Kleinbuchstaben umwandeln und den Slug aus dem Pfad nehmen.
resolver.Resolveläuft durch L1, dann L2, dann den Origin.- Kurzschluss nach Zustand: ein pausierter Workspace, ein abgelaufener Link, ein passwortgeschützter Link (der Besucher geht zur Entsperrseite und es wird kein Klick erzeugt), ein vom url-scanner als
blocked(451) markierter Link und ein Ziel-Host auf der Admin-Sperrliste. - Das Klick-Ereignis abfeuern, ohne darauf zu warten.
- Mit
302antworten, es sei denn, der Link sagt301oder307.
Alles in Schritt 3 ist ein Feld im gecachten Link-Datensatz oder ein Lookup in einer In-Memory-Menge. Der Sperrlisten-Matcher wird zum Beispiel im Hintergrund von api-core synchronisiert, seine Prüfung kostet also ein paar Map-Lookups, keinen Datenbankaufruf.
Link-Vorschauen (ein Slug mit + am Ende), KI-Agenten-Anfragen, die JSON verlangen, und Ende-zu-Ende-verschlüsselte Links nehmen vor dem Redirect andere Zweige. Keiner davon berührt Postgres.
Die drei Lookup-Stufen
Jede Stufe ist absichtlich klein, und jede arbeitet nach bestem Bemühen, außer der letzten.
L1, prozessinterner LRU. internal/cache/lru.go umhüllt hashicorp/golang-lru/v2/expirable. Die Größe ist L1Size: 100_000 Einträge und die TTL L1TTL: 60s, beides in internal/config/config.go gesetzt. Die TTL existiert, damit ein deaktivierter oder gelöschter Link nicht ewig von einem Knoten ausgeliefert werden kann. Der Schlüssel ist host + "/" + slug, derselbe Slug auf zwei Domains kollidiert also nicht.
L2, gemeinsamer Cache. internal/cache speichert den Link als JSON, geschlüsselt nach Host und Slug, mit L2TTL: 5m. Er wird von den Edge-Instanzen gemeinsam genutzt. Ein nil-Client ist zulässig: Die Cache-Methoden liefern einen Miss, und die Edge arbeitet weiter.
Origin, gRPC zu api-core. internal/origin/origin.go unterstützt zwei Transporte. REST (GET /v1/lookup) ist der Standard, wenn nichts anderes konfiguriert ist. gRPC (elido.v1.LookupService.Resolve) wird genutzt, sobald GRPC_ORIGIN_ADDR gesetzt ist, und ist die Produktionswahl. Der Aufruf ist durch OriginTimeout begrenzt, 1,5 Sekunden.
Der Resolver ist kurz genug, um den wichtigen Teil zu zitieren:
if v, ok := r.l1.Get(key); ok {
return v, nil
}
if v, ok := r.l2.Get(ctx, key); ok {
r.l1.Set(key, v)
return v, nil
}
link, err := r.origin.Resolve(ctx, host, slug)
if err != nil {
return nil, err
}
r.l1.Set(key, link)
r.l2.Set(ctx, key, link)
return link, nil
Ein Treffer in L2 füllt L1 wieder auf. Ein Miss in beiden geht an api-core und füllt beide wieder auf. Nur der Origin darf "not found" sagen; tut er das, versucht der Resolver es einmal mit einem Wildcard-Parent-Host erneut (abc.example.com wird zu *.example.com) für Wildcard-Custom-Domains.
Was dieses Design nicht hat
Eine frühere Version dieses Beitrags beschrieb Dinge, die der Code nicht tut. Zur Klarstellung:
- Es gibt keine
singleflight-Gruppe. Zwei gleichzeitige Misses für denselben kalten Slug erreichen beide den Origin. - Es gibt kein Pub/Sub-Abonnement auf der Edge. Ändert sich ein Link, löscht api-core den L2-Schlüssel (der Purger in
services/api-core/internal/linkmod/adapters.go). In L1 wird nichts gepusht, ein bearbeiteter Link kann also bis zu 60 Sekunden aus dem Speicher eines Knotens ausgeliefert werden. - L1 ist ein TTL-begrenzter LRU, kein zugangskontrollierter Cache, und seine Größe richtet sich nach der Eintragszahl, nicht nach Bytes.
- Es gibt keinen
cache-status-Endpunkt, keinen Response-Header pro Stufe und kein von der Edge exportiertes Latenz-Histogramm.
Das 60-Sekunden-Fenster ist der Grund, warum ein bearbeitbarer Link einen temporären Redirect nutzen sollte und warum 301 vs. 302 für die Browserseite desselben Problems zählt. Das Cache-Design ist ausführlicher in Cache-Strategie für URL-Redirects behandelt.
Die eine Prometheus-Metrik, auf die wir im Edge-Code zeigen können, ist elido_edge_redirect_total mit den Labels fleet und outcome. Das Runbook erwähnt andere Metriknamen (edge_cache_*, edge_origin_grpc_duration), die wir im aktuellen Code nicht registriert finden konnten, deshalb dokumentieren wir sie nicht.
Was vom Hot Path fernbleibt
Klickspeicherung. click.Producer.Emit serialisiert das Ereignis und übergibt es an einen franz-go-Producer, und der Handler ruft es als go h.clicks.Emit(...) auf. Der Produce-Callback loggt nur einen Fehler. Ist der Event-Bus langsam, merkt der Besucher nichts; der Preis ist, dass ein Klick verloren gehen kann, der übliche Kompromiss von Fire-and-forget. Das Schreiben der Klicks in den Analytics-Speicher ist die Aufgabe von click-ingester, der die Klick-Analytics-API speist.
Bot-Challenges. Es gibt keine JavaScript-Challenge auf dem Redirect. Die Bot-Erkennung ist eine User-Agent-Teilstringliste, die entscheidet, ob ein Klick gezählt wird, sodass Crawler und Link-Vorschau-Abrufe ihren Redirect weiterhin bekommen, aber die Analytics nicht aufblähen. Es ist ein grober Filter, mit Absicht.
Scanning. url-scanner veröffentlicht ein Urteil pro Link bei der Erstellung und nach Zeitplan. Die Edge liest nur das gespeicherte Urteil (clean, suspect, blocked), und nur blocked stoppt den Redirect.
Regelauswertung. Smart Links reisen mit dem Link-Datensatz, Regeln für Land, Gerät und Ähnliches laufen also gegen die gecachte Kopie, wobei der GeoIP-Lookup prozessintern aus einer lokalen Datenbank erfolgt. Ein Cache-Treffer mit Regeln braucht keinen Netzwerkaufruf an api-core.
Die Servereinstellungen sind bescheiden. cmd/server/main.go konfiguriert den Server mit einem Lese- und Schreib-Timeout von 2 Sekunden, einem Idle-Timeout von 30 Sekunden und einem Request-Body-Limit von 64 KB. Wir haben ihn wegen geringerer Allokation pro Request gegenüber net/http gewählt, wie das README des Dienstes festhält. Einen direkten Benchmark-Vergleich für unseren Handler haben wir nicht veröffentlicht, das ist also ein Designgrund, kein gemessenes Ergebnis.
So messen Sie es selbst
Das Repo hat ein Lasttest-Runbook (docs/runbooks/19-load-testing.md), das auf k6 aufbaut. Seine Abnahmeschwelle vor dem Start für den Hot Path ist 1,5k Req/s für 5 Minuten bei 15 ms p95 ohne 5xx und 5k Req/s für 1 Minute bei 30 ms p95 mit unter 0,1 % 5xx. Das sind Schwellen, keine Ergebnisse.
Ein Test von einem nahen Lastgenerator misst den Dienst. Ein Test von einem weit entfernten Host misst Ihr Netzwerk. Für die Latenz aus Besuchersicht prüfen Sie von außen mit einem synthetischen Monitor, wie in Redirects mit Sentry und Datadog überwachen.
Ehrlich gesagt würde ich 15 ms einem Kunden noch nicht als Messwert nennen. Lücken, die wir schließen würden, bevor wir eine Tabelle pro Region drucken: ein Latenz-Histogramm auf der Edge, Singleflight bei kalten Misses, Push-Invalidierung von L1 und ein eingecheckter Benchmark hinter der 15-ms-Zahl.
Marius Voß ist DevRel und Edge-Infrastruktur bei Elido. Quellen: services/edge-redirect, services/api-core/internal/linkmod/adapters.go und docs/runbooks/19-load-testing.md; es werden keine externen Herstelleraussagen gemacht.
Weiterlesen im Blog
- Cache-Strategie für URL-Redirects - das L1- und L2-Design ausführlicher.
- 301 vs. 302 Redirects - warum bearbeitbare Links einen temporären Redirect nutzen.
- Smart Links erklärt - Regeln, die auf dem gecachten Datensatz laufen.
- Link-Redirects mit Sentry und Datadog überwachen - Messen von außen.
- Link-Klick-Analytics-API - wo die Klick-Ereignisse landen.
Häufig gestellte Fragen
Ist 15 ms p95 eine gemessene Zahl?
Nein. 15 ms p95 (mit p50 bei 5 ms) bei einem Cache-Treffer ist das Latenzbudget des Dienstes edge-redirect, und gegen diese Zahl sind unsere Alerts und die Abnahmeschwelle im Lasttest geschrieben. Dieser Beitrag veröffentlicht keine gemessenen Perzentile pro Region, weil wir keinen eingecheckten Benchmark und keine Ergebnisdatei haben, die wir dafür zitieren könnten.
Wie löst ein Elido-Redirect einen Slug auf?
Die Edge schaut zuerst in einen prozessinternen LRU, dann in einen gemeinsamen In-Memory-Cache und fragt erst dann api-core über gRPC. Jede Cache-Schicht arbeitet nach bestem Bemühen, nur der Origin kann daher ein verbindliches Not-found liefern. Ein Treffer in einer unteren Schicht wird in die darüberliegenden Schichten kopiert.
Was bleibt vom Redirect-Pfad fern?
Klickspeicherung, Analytics und die Prüfungen des url-scanner. Die Edge sendet das Klick-Ereignis in einer separaten Goroutine an den Event-Bus und antwortet dem Besucher, ohne darauf zu warten. Bot-Filterung ist eine User-Agent-Prüfung, die entscheidet, ob ein Klick gezählt wird, keine Challenge, die der Besucher bestehen muss.
Warum nutzt Elido standardmäßig 302?
Ein 302 sagt Browsern, das Ziel nicht als dauerhaft zu behandeln, sodass ein bearbeiteter Link beim nächsten Besuch wirkt. Ein Link kann einzeln auf 301 oder 307 gesetzt werden. Der Kompromiss ist in unserem Beitrag zu 301 vs. 302 behandelt.
Wo stehen die Edge-POPs?
Produktiv läuft heute ein einzelner Host, auf dem der Edge-Dienst hinter einem Reverse Proxy läuft. Ein Multi-Region-Edge (Europa, Nordamerika und Südostasien, jeweils mit derselben Edge-Binary) ist geplant, nicht live. Die Hauptdatenbanken und der Event-Bus bleiben in der EU, und in diesem Design würden nur Redirect-Cache-Daten neben den Edge-Knoten liegen, wie ADR-0014 beschreibt.
Kann ein bearbeiteter Link veraltet ausgeliefert werden?
Kurzzeitig. Wenn sich ein Link ändert, löscht api-core seinen Eintrag im gemeinsamen Cache, aber die prozessinterne Kopie eines Edge-Knotens lebt, bis ihre 60-Sekunden-TTL abläuft. Eine Ziel-Änderung kann also bis zu einer Minute brauchen, um jeden Knoten zu erreichen, ein Grund, warum temporäre Redirects zu bearbeitbaren Links passen.
Elido testen
URL einfügen, kurzer Link in Sekunden
Kein Konto nötig. Link bleibt 30 Tage aktiv. Konto erstellen, um ihn dauerhaft zu behalten.
Keine Anmeldung nötig. Kostenlos registrieren für 50 Links im Monat.
