Une redirection bloque le visiteur. Il clique sur un lien court, le navigateur attend, et rien d'autre ne se charge tant que le 302 n'est pas revenu. C'est pourquoi services/edge-redirect a un budget de latence plutôt qu'une impression de "assez rapide" : p50 5 ms et p95 15 ms en cache hit.
Lisez cela comme une cible, pas comme un résultat. Le budget est écrit dans le README du service et dans le runbook d'astreinte, et notre seuil d'acceptation des tests de charge repose dessus. Nous n'avons ni benchmark commité ni fichier de résultats de test de charge qui prouve un percentile par région : cet article n'en imprime donc pas. Il décrit, à partir du code, ce que fait le chemin chaud et, tout aussi utile, ce qu'il refuse de faire.
En bref
- Le edge est un service Go sur une bibliothèque HTTP Go à faible allocation, exécuté aujourd'hui sur un hôte de production unique derrière un reverse proxy, avec un edge multi-région prévu.
- La recherche parcourt un LRU en processus (100 000 entrées, TTL de 60 secondes), puis un cache mémoire partagé (TTL de 5 minutes), puis api-core via gRPC avec un timeout de 1,5 seconde.
- L'événement de clic est produit depuis une goroutine séparée. Le visiteur ne l'attend jamais.
- Budget : p50 5 ms, p95 15 ms en cache hit. Ce sont des cibles, et les percentiles mesurés ne sont pas publiés ici.
Pourquoi le budget existe
Le temps de redirection s'ajoute à tout ce qui suit. Si un lien de campagne envoie le trafic vers une page d'atterrissage, la redirection fait partie du time to first byte de cette page. Un budget force une décision chaque fois que quelqu'un veut ajouter du travail à ce chemin : cela tient, ou cela part dans un worker.
Les chiffres de services/edge-redirect/README.md :
| Percentile | Cache hit | Cache miss |
|---|---|---|
| p50 | 5 ms | 40 ms |
| p95 | 15 ms | 100 ms |
| p99 | 30 ms | 200 ms |
C'est une cible de conception. Si vous avez besoin de chiffres mesurés pour une revue de fournisseur, lancez le scénario k6 décrit à la fin de cet article sur vos propres liens et gardez la sortie.
Le chemin de la requête, étape par étape
internal/handler/redirect.go documente le flux en haut du fichier. Dans l'ordre :
- Mettre l'hôte en minuscules et prendre le slug dans le chemin.
resolver.Resolveparcourt L1, puis L2, puis l'origine.- Court-circuiter selon l'état : un espace de travail en pause, un lien expiré, un lien protégé par mot de passe (le visiteur va vers la page de déverrouillage et aucun clic n'est émis), un lien que l'url-scanner a marqué
blocked(451), et un hôte de destination présent sur la liste de blocage admin. - Émettre l'événement de clic sans l'attendre.
- Répondre avec
302, sauf si le lien indique301ou307.
Tout ce qui figure à l'étape 3 est un champ de l'enregistrement de lien en cache ou une recherche dans un ensemble en mémoire. Le matcher de la liste de blocage, par exemple, est synchronisé depuis api-core en arrière-plan : le vérifier coûte donc quelques recherches de map, pas un appel à la base de données.
Les aperçus de lien (un slug se terminant par +), les requêtes d'agents IA qui demandent du JSON et les liens chiffrés de bout en bout prennent d'autres branches avant la redirection. Aucun ne touche Postgres.
Les trois niveaux de recherche
Chaque niveau est petit volontairement, et chacun fonctionne au mieux sauf le dernier.
L1, LRU en processus. internal/cache/lru.go enveloppe hashicorp/golang-lru/v2/expirable. La taille est L1Size: 100_000 entrées et le TTL est L1TTL: 60s, tous deux définis dans internal/config/config.go. Le TTL existe pour qu'un lien désactivé ou supprimé ne puisse pas être servi indéfiniment par un nœud. La clé est host + "/" + slug, de sorte que le même slug sur deux domaines n'entre pas en collision.
L2, cache partagé. internal/cache stocke le lien en JSON, indexé par hôte et slug, avec L2TTL: 5m. Il est partagé par les instances edge. Un client nul est légal : les méthodes de cache renvoient un miss et le edge continue de fonctionner.
Origine, gRPC vers api-core. internal/origin/origin.go prend en charge deux transports. REST (GET /v1/lookup) est la valeur par défaut quand rien d'autre n'est configuré. gRPC (elido.v1.LookupService.Resolve) est utilisé dès que GRPC_ORIGIN_ADDR est défini, et c'est le choix de production. L'appel est borné par OriginTimeout, 1,5 seconde.
Le resolver est assez court pour citer l'essentiel :
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 dans L2 remplit L1. Un miss dans les deux va vers api-core et remplit les deux. Seule l'origine peut dire "not found" ; si elle le fait, le resolver réessaie une fois avec un hôte parent générique (abc.example.com devient *.example.com) pour les domaines personnalisés génériques.
Ce que ce design n'a pas
Une version antérieure de cet article décrivait des choses que le code ne fait pas. Pour être explicite :
- Il n'y a pas de groupe
singleflight. Deux miss simultanés pour le même slug froid atteignent tous deux l'origine. - Il n'y a pas d'abonnement pub/sub sur le edge. Quand un lien change, api-core supprime la clé L2 (le purger dans
services/api-core/internal/linkmod/adapters.go). L1 ne reçoit aucune poussée : un lien modifié peut donc être servi depuis la mémoire d'un nœud jusqu'à 60 secondes. - L1 est un LRU borné par TTL, pas un cache à contrôle d'admission, et il est dimensionné en nombre d'entrées, pas en octets.
- Il n'y a pas d'endpoint
cache-status, pas d'en-tête de réponse par niveau et pas d'histogramme de latence exporté par le edge.
La fenêtre de 60 secondes explique pourquoi un lien modifiable doit utiliser une redirection temporaire, et pourquoi 301 vs 302 compte pour le côté navigateur du même problème. Le design du cache est traité plus en profondeur dans stratégie de cache pour les redirections d'URL.
La seule métrique Prometheus que nous pouvons montrer dans le code du edge est elido_edge_redirect_total avec les labels fleet et outcome. Le runbook mentionne d'autres noms de métriques (edge_cache_*, edge_origin_grpc_duration) que nous n'avons pas trouvés enregistrés dans le code actuel : nous ne les documentons donc pas.
Ce qui reste hors du chemin chaud
Stockage des clics. click.Producer.Emit sérialise l'événement et le confie à un producteur franz-go, et le handler l'appelle sous la forme go h.clicks.Emit(...). Le callback de production ne fait que journaliser une erreur. Si le bus d'événements est lent, le visiteur ne le remarque pas ; le coût est qu'un clic peut être perdu, le compromis habituel du fire-and-forget. Écrire les clics dans le stockage analytique est le travail de click-ingester, qui alimente l'API d'analytique des clics.
Défis anti-bots. Il n'y a pas de défi JavaScript sur la redirection. La détection des bots est une liste de sous-chaînes d'user-agent qui décide si un clic est compté : les crawlers et les récupérateurs d'aperçus de lien reçoivent donc quand même leur redirection mais ne gonflent pas l'analytique. C'est un filtre grossier par conception.
Analyse. url-scanner publie un verdict par lien à la création et selon un calendrier. Le edge ne lit que le verdict stocké (clean, suspect, blocked), et seul blocked arrête la redirection.
Évaluation des règles. Les smart links voyagent avec l'enregistrement de lien : les règles de pays, d'appareil et similaires s'exécutent donc sur la copie en cache, la recherche GeoIP étant faite en processus à partir d'une base locale. Un cache hit avec règles n'a pas besoin d'appel réseau vers api-core.
Les réglages du serveur sont modestes. cmd/server/main.go configure le serveur avec un timeout de lecture et d'écriture de 2 secondes, un timeout d'inactivité de 30 secondes et une limite de corps de requête de 64 Ko. Nous l'avons préféré à net/http pour une allocation par requête plus faible, comme le note le README du service. Nous n'avons pas publié de benchmark comparatif pour notre handler : c'est donc une raison de conception, pas un résultat mesuré.
Comment le mesurer vous-même
Le dépôt a un runbook de test de charge (docs/runbooks/19-load-testing.md) construit sur k6. Son seuil d'acceptation avant lancement pour le chemin chaud est 1,5k req/s pendant 5 minutes à 15 ms de p95 avec zéro 5xx, et 5k req/s pendant 1 minute à 30 ms de p95 avec moins de 0,1 % de 5xx. Ce sont des seuils, pas des résultats.
Un test depuis un générateur de charge proche mesure le service. Un test depuis un hôte lointain mesure votre réseau. Pour la latence côté visiteur, sondez depuis l'extérieur avec un moniteur synthétique, comme dans surveiller les redirections avec Sentry et Datadog.
Honnêtement, je ne citerais pas encore 15 ms à un client comme une mesure. Les écarts que nous comblerions avant d'imprimer un tableau par région : un histogramme de latence sur le edge, singleflight sur les miss à froid, l'invalidation par poussée de L1, et un benchmark commité derrière le chiffre de 15 ms.
Marius Voß est DevRel et edge infra chez Elido. Sources : services/edge-redirect, services/api-core/internal/linkmod/adapters.go et docs/runbooks/19-load-testing.md ; aucune affirmation de fournisseur externe n'est avancée.
À lire aussi sur le blog
- Stratégie de cache pour les redirections d'URL - le design L1 et L2 plus en profondeur.
- Redirections 301 vs 302 - pourquoi les liens modifiables utilisent une redirection temporaire.
- Les smart links expliqués - des règles exécutées sur l'enregistrement en cache.
- Surveiller les redirections de liens avec Sentry et Datadog - mesurer depuis l'extérieur.
- API d'analytique des clics de liens - où atterrissent les événements de clic.
Questions fréquentes
Le p95 de 15 ms est-il un chiffre mesuré ?
Non. Un p95 de 15 ms (avec un p50 à 5 ms) en cache hit est le budget de latence du service edge-redirect, et c'est sur lui que sont écrits nos alertes et notre seuil d'acceptation des tests de charge. Cet article ne publie pas de percentiles mesurés par région, car nous n'avons aucun benchmark ni fichier de résultats commité à citer pour eux.
Comment une redirection Elido résout-elle un slug ?
Le edge regarde d'abord dans un LRU en processus, puis dans un cache mémoire partagé, et ne demande qu'ensuite à api-core via gRPC. Chaque couche de cache fonctionne au mieux, donc seule l'origine peut renvoyer un not-found faisant autorité. Un hit dans une couche inférieure est recopié vers les couches du dessus.
Qu'est-ce qui reste hors du chemin de redirection ?
Le stockage des clics, l'analytique et les vérifications d'url-scanner. Le edge émet l'événement de clic vers le bus d'événements dans une goroutine séparée et répond au visiteur sans l'attendre. Le filtrage des bots est une vérification d'user-agent qui décide si un clic est compté, pas un défi que le visiteur doit réussir.
Pourquoi Elido utilise-t-il 302 par défaut ?
Un 302 indique aux navigateurs de ne pas considérer la destination comme permanente : un lien modifié prend donc effet à la visite suivante. Un lien peut être réglé individuellement en 301 ou 307. Le compromis est traité dans notre article sur 301 vs 302.
Où sont les POP edge ?
Aujourd'hui, la production est un hôte unique exécutant le service edge derrière un reverse proxy. Un edge multi-région (Europe, Amérique du Nord et Asie du Sud-Est, chacune exécutant le même binaire edge) est prévu, pas en production. Les bases de données principales et le bus d'événements restent dans l'UE, et dans cette conception seules les données du cache de redirection se trouveraient près des nœuds edge, comme le décrit l'ADR-0014.
Un lien modifié peut-il être servi périmé ?
Brièvement. Quand un lien change, api-core supprime son entrée du cache partagé, mais la copie en processus d'un nœud edge vit jusqu'à l'expiration de son TTL de 60 secondes. Une modification de destination peut donc mettre jusqu'à une minute à atteindre tous les nœuds, ce qui est une raison pour laquelle les redirections temporaires conviennent aux liens modifiables.
Essayer Elido
Collez une URL, obtenez un lien court
Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.
Sans inscription. Créez un compte gratuit pour 50 liens par mois.
