Un redirect blocca il visitatore. Clicca un link corto, il browser aspetta, e nient'altro si carica finché non torna il 302. Per questo services/edge-redirect ha un budget di latenza anziché una sensazione di "abbastanza veloce": p50 5ms e p95 15ms su cache hit.
Leggilo come un target, non come un risultato. Il budget è scritto nel README del servizio e nel runbook di reperibilità, e la nostra soglia di accettazione dei load test è costruita su di esso. Non abbiamo un benchmark committato né un file di risultati di load test che dimostri un percentile per regione, quindi questo articolo non ne stampa uno. Descrive, dal codice, cosa fa l'hot path e, altrettanto utile, cosa si rifiuta di fare.
In sintesi
- L'edge è un servizio Go su una libreria HTTP Go a bassa allocazione, in esecuzione oggi su un singolo host di produzione dietro un reverse proxy, con un edge multi-regione pianificato.
- La ricerca attraversa una LRU in-process (100.000 voci, TTL di 60 secondi), poi una cache condivisa in memoria (TTL di 5 minuti), poi api-core via gRPC con un timeout di 1,5 secondi.
- L'evento del clic viene prodotto da una goroutine separata. Il visitatore non lo attende mai.
- Budget: p50 5ms, p95 15ms su cache hit. Sono target, e i percentili misurati qui non sono pubblicati.
Perché esiste il budget
Il tempo del redirect si somma a tutto ciò che viene dopo. Se un link di campagna manda il traffico a una landing page, il redirect fa parte del time to first byte di quella pagina. Un budget impone una decisione ogni volta che qualcuno vuole aggiungere lavoro a questo percorso: ci sta, oppure passa a un worker.
I numeri in services/edge-redirect/README.md:
| Percentile | Cache hit | Cache miss |
|---|---|---|
| p50 | 5ms | 40ms |
| p95 | 15ms | 100ms |
| p99 | 30ms | 200ms |
Sono un target di progetto. Se ti servono numeri misurati per una revisione del fornitore, esegui lo scenario k6 descritto alla fine di questo articolo sui tuoi link e conserva l'output.
Il percorso della richiesta, passo per passo
internal/handler/redirect.go documenta il flusso in cima al file. In ordine:
- Converti l'host in minuscolo e prendi lo slug dal percorso.
resolver.Resolveattraversa L1, poi L2, poi l'origin.- Cortocircuito sullo stato: un workspace in pausa, un link scaduto, un link protetto da password (il visitatore va alla pagina di sblocco e non viene emesso alcun clic), un link che l'url-scanner ha marcato
blocked(451) e un host di destinazione nella blocklist dell'admin. - Emetti l'evento del clic senza attenderlo.
- Rispondi con
302a meno che il link non indichi301o307.
Tutto nel passo 3 è un campo del record del link in cache o una ricerca in un insieme in memoria. Il matcher della blocklist, per esempio, viene sincronizzato da api-core in background, quindi controllarlo costa un paio di ricerche in una mappa, non una chiamata al database.
Le anteprime dei link (uno slug che termina con +), le richieste di agenti AI che chiedono JSON e i link con cifratura end-to-end prendono rami diversi prima del redirect. Nessuno di essi tocca Postgres.
I tre livelli di ricerca
Ogni livello è piccolo di proposito, e ciascuno è best-effort tranne l'ultimo.
L1, LRU in-process. internal/cache/lru.go incapsula hashicorp/golang-lru/v2/expirable. La dimensione è L1Size: 100_000 voci e il TTL è L1TTL: 60s, entrambi impostati in internal/config/config.go. Il TTL esiste perché un link disabilitato o eliminato non possa essere servito per sempre da un nodo. La chiave è host + "/" + slug, così lo stesso slug su due domini non collide.
L2, cache condivisa. internal/cache conserva il link come JSON, con chiave host e slug, con L2TTL: 5m. È condivisa dalle istanze edge. Un client nil è legale: i metodi della cache restituiscono un miss e l'edge continua a funzionare.
Origin, gRPC verso api-core. internal/origin/origin.go supporta due trasporti. REST (GET /v1/lookup) è il predefinito quando non è configurato altro. gRPC (elido.v1.LookupService.Resolve) è usato una volta impostato GRPC_ORIGIN_ADDR, ed è la scelta di produzione. La chiamata è limitata da OriginTimeout, 1,5 secondi.
Il resolver è abbastanza breve da citarne la parte importante:
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
Un hit in L2 riempie di nuovo L1. Un miss in entrambi va ad api-core e riempie entrambi. Solo l'origin può dire "non trovato"; se lo fa, il resolver riprova una volta con un host genitore wildcard (abc.example.com diventa *.example.com) per i domini personalizzati wildcard.
Cosa questo design non ha
Una versione precedente di questo articolo descriveva cose che il codice non fa. Per essere espliciti:
- Non esiste un gruppo
singleflight. Due miss simultanei per lo stesso slug freddo raggiungono entrambi l'origin. - Non c'è alcuna sottoscrizione pub/sub sull'edge. Quando un link cambia, api-core elimina la chiave L2 (il purger in
services/api-core/internal/linkmod/adapters.go). L1 non riceve push, quindi un link modificato può essere servito dalla memoria di un nodo per fino a 60 secondi. - L1 è una LRU limitata dal TTL, non una cache con controllo di ammissione, ed è dimensionata per numero di voci, non per byte.
- Non esiste un endpoint
cache-status, nessun header di risposta per livello e nessun istogramma di latenza esportato dall'edge.
La finestra di 60 secondi è il motivo per cui un link modificabile dovrebbe usare un redirect temporaneo, e per cui 301 vs 302 conta per il lato browser dello stesso problema. Il design della cache è trattato più a fondo in strategia di cache per i redirect di URL.
L'unica metrica Prometheus che possiamo indicare nel codice dell'edge è elido_edge_redirect_total con le label fleet e outcome. Il runbook cita altri nomi di metriche (edge_cache_*, edge_origin_grpc_duration) che non siamo riusciti a trovare registrati nel codice attuale, quindi non le documentiamo.
Cosa resta fuori dall'hot path
Archiviazione dei clic. click.Producer.Emit serializza l'evento e lo passa a un producer franz-go, e il gestore lo chiama come go h.clicks.Emit(...). Il callback di produzione registra solo un errore. Se il bus di eventi è lento, il visitatore non se ne accorge; il costo è che un clic può andare perso, che è il normale compromesso del fire-and-forget. Scrivere i clic nell'archivio analytics è compito di click-ingester, che alimenta l'API analytics dei clic.
Sfide ai bot. Non c'è alcuna sfida JavaScript sul redirect. Il rilevamento dei bot è un elenco di sottostringhe di user-agent che decide se un clic viene contato, quindi crawler e fetcher di anteprime dei link ricevono comunque il loro redirect ma non gonfiano gli analytics. È un filtro grossolano per progetto.
Scansione. url-scanner pubblica un verdetto per link al momento della creazione e a intervalli pianificati. L'edge legge soltanto il verdetto memorizzato (clean, suspect, blocked), e solo blocked ferma il redirect.
Valutazione delle regole. Gli smart link viaggiano con il record del link, quindi le regole per paese, dispositivo e simili girano sulla copia in cache, con la ricerca GeoIP eseguita in-process da un database locale. Un cache hit con regole non richiede una chiamata di rete ad api-core.
Le impostazioni del server sono modeste. cmd/server/main.go configura il server con un timeout di lettura e scrittura di 2 secondi, un timeout di inattività di 30 secondi e un limite di 64 KB sul corpo della richiesta. L'abbiamo scelto al posto di net/http per una minore allocazione per richiesta, come nota il README del servizio. Non abbiamo pubblicato un benchmark diretto per il nostro handler, quindi è una ragione di progetto, non un risultato misurato.
Come misurarlo tu stesso
Il repo ha un runbook di load testing (docs/runbooks/19-load-testing.md) basato su k6. La sua soglia di accettazione pre-lancio per l'hot path è 1,5k req/s per 5 minuti a 15ms p95 con zero 5xx, e 5k req/s per 1 minuto a 30ms p95 con meno dello 0,1% di 5xx. Sono soglie, non risultati.
Un test da un generatore di carico vicino misura il servizio. Un test da un host lontano misura la tua rete. Per la latenza lato visitatore, sonda dall'esterno con un monitor sintetico, come in monitorare i redirect con Sentry e Datadog.
Onestamente, non citerei ancora 15ms a un cliente come misurazione. Lacune che chiuderemmo prima di stampare una tabella per regione: un istogramma di latenza sull'edge, singleflight sui miss a freddo, invalidazione push di L1 e un benchmark committato dietro la cifra dei 15ms.
Marius Voß è DevRel e edge infra in Elido. Fonti: services/edge-redirect, services/api-core/internal/linkmod/adapters.go e docs/runbooks/19-load-testing.md; non si fanno affermazioni di fornitori esterni.
Correlati sul blog
- Strategia di cache per i redirect di URL - il design di L1 e L2 più a fondo.
- Redirect 301 vs 302 - perché i link modificabili usano un redirect temporaneo.
- Smart link spiegati - regole che girano sul record in cache.
- Monitorare i redirect dei link con Sentry e Datadog - misurare dall'esterno.
- API analytics dei clic dei link - dove finiscono gli eventi dei clic.
Domande frequenti
15ms p95 è un numero misurato?
No. 15ms p95 (con p50 a 5ms) su una cache hit è il budget di latenza del servizio edge-redirect, ed è il parametro su cui sono scritti i nostri alert e la soglia di accettazione dei load test. Questo articolo non pubblica percentili misurati per regione perché non abbiamo un benchmark o un file di risultati committato da citare.
Come risolve uno slug un redirect di Elido?
L'edge guarda prima in una LRU in-process, poi in una cache condivisa in memoria, e solo dopo interroga api-core via gRPC. Ogni livello di cache è best-effort, quindi solo l'origin può restituire un not-found autorevole. Un hit in un livello inferiore viene copiato nei livelli superiori.
Cosa resta fuori dal percorso del redirect?
Archiviazione dei clic, analytics e i controlli dell'url-scanner. L'edge emette l'evento del clic sul bus di eventi in una goroutine separata e risponde al visitatore senza attenderlo. Il filtro dei bot è un controllo sull'user-agent che decide se un clic viene contato, non una sfida che il visitatore deve superare.
Perché Elido usa 302 per impostazione predefinita?
Un 302 dice ai browser di non trattare la destinazione come permanente, quindi un link modificato ha effetto alla visita successiva. Un link può essere impostato singolarmente su 301 o 307. Il compromesso è trattato nel nostro articolo su 301 vs 302.
Dove si trovano i POP edge?
Oggi la produzione è un singolo host che esegue il servizio edge dietro un reverse proxy. Un edge multi-regione (Europa, Nord America e Sud-Est asiatico, ciascuno con lo stesso binario edge) è pianificato, non attivo. I database principali e il bus di eventi restano nell'UE, e in quel design solo i dati della cache dei redirect starebbero accanto ai nodi edge, come descrive l'ADR-0014.
Un link modificato può essere servito in versione obsoleta?
Brevemente. Quando un link cambia, api-core elimina la sua voce nella cache condivisa, ma la copia in-process di un nodo edge vive finché non scade il suo TTL di 60 secondi. Quindi una modifica della destinazione può impiegare fino a un minuto per raggiungere ogni nodo, ed è uno dei motivi per cui i redirect temporanei si adattano ai link modificabili.
Prova Elido
Incolla un URL, ottieni un link breve
Senza registrazione. Il link vive 30 giorni. Iscriviti per conservarlo.
Senza registrazione. Crea un account gratuito per 50 link al mese.
