Un raccourcisseur d'URL ressemble à un projet du week-end : prendre une URL longue, rendre un code court, rediriger quand quelqu'un clique. C'est pour cela qu'il revient dans tant d'entretiens de conception de systèmes. Sa petite surface cache de vraies décisions sur la génération d'ID, le cache, la sémantique HTTP et l'analytique, et chacune change l'architecture.
Cet article en conçoit un de bout en bout. Il commence par les chiffres, montre comment générer des codes courts sans collision, tranche la question 301 contre 302 et se termine par un diagramme d'architecture que vous pouvez copier et adapter.
Exigences et chiffres
Exigences fonctionnelles :
- Créer un lien court pour une URL longue, avec un alias personnalisé en option.
- Rediriger un lien court vers son URL longue.
- Compter les clics par lien.
- Faire expirer les liens sur demande.
Exigences non fonctionnelles : les redirections doivent être rapides et hautement disponibles, les codes courts ne doivent pas être devinables dans l'ordre si les liens peuvent être privés, et le système ne doit pas devenir un redirecteur gratuit pour des pages d'hameçonnage.
Un calcul d'ordre de grandeur rend la forme évidente :
| Hypothèse | Valeur | Par seconde |
|---|---|---|
| Nouveaux liens par mois | 100 millions | environ 40 écritures/s |
| Redirections par mois | 10 milliards | environ 3 900 lectures/s |
| Ratio lectures / écritures | 100:1 | |
| Taille d'un enregistrement | environ 500 octets | |
| Stockage sur 5 ans | 6 milliards de liens | environ 3 To |
Les lectures dépassent les écritures de deux ordres de grandeur : le chemin de lecture reçoit le cache, les réplicas et l'attention. Le chemin d'écriture est petit et peut rester simple.
L'API
Deux endpoints portent le produit :
POST /links
Content-Type: application/json
{"url": "https://example.com/very/long/path", "alias": "launch", "expiresAt": "2027-01-01T00:00:00Z"}
201 Created
{"code": "launch", "shortUrl": "https://sho.rt/launch"}
GET /launch
302 Found
Location: https://example.com/very/long/path
Placez-les dans des services séparés. Le service de redirection est sans état et en lecture seule : vous pouvez le faire évoluer à part et le déployer rarement.
Générer le code court
Le code court est un nombre écrit en base62 (a–z, A–Z, 0–9). Sept caractères donnent 62^7, soit environ 3 500 milliards de codes ; six en donnent environ 56,8 milliards. À 100 millions de liens par mois, sept caractères tiennent des milliers d'années.
La vraie question est de savoir d'où vient le nombre :
| Approche | Fonctionnement | Points d'attention |
|---|---|---|
| Compteur + base62 | Une séquence globale, encodée | Un point de contention unique ; codes séquentiels et devinables |
| Plages d'ID | Un allocateur donne à chaque instance d'API un bloc d'ID (par exemple 100 000) | Des trous quand une instance meurt ; toujours séquentiel dans un bloc |
| Hachage + troncature | Hacher l'URL et garder les 7 premiers caractères base62 | Collisions ; il faut vérifier et réessayer |
| ID façon Snowflake | Horodatage + ID de machine + séquence sur 64 bits | Codes plus longs (11 caractères en base62) |
Le hachage est tentant, car la même URL obtient toujours le même code. Le piège est le paradoxe des anniversaires : avec 3 500 milliards de codes possibles, la probabilité d'au moins une collision dépasse 50 % après seulement 2,2 millions de liens environ. Chaque insertion demande alors une vérification d'unicité et un nouvel essai avec un sel, ce qui transforme une écriture en une lecture plus une écriture.
Les plages d'ID sont le choix pragmatique par défaut. Chaque instance d'API réserve un bloc auprès de l'allocateur dans une petite transaction, puis distribue les codes depuis la mémoire, sans aucune coordination. Si vous ne voulez pas de codes séquentiels, faites passer l'ID par une permutation réversible (un chiffrement par bloc sur l'espace des ID, par exemple) avant de l'encoder. Snowflake, de Twitter, montre l'autre voie : un horodatage de 41 bits, un ID de machine de 10 bits et une séquence de 12 bits donnent des ID uniques de 64 bits sans allocateur central, au prix de codes plus longs.
Les alias personnalisés contournent le générateur. Stockez-les dans la même table et comptez sur une contrainte d'unicité sur la colonne du code pour rejeter les doublons.
301 ou 302 ?
La RFC 9110 définit les codes de redirection. La différence qui compte ici est le cache : une réponse 301 (Moved Permanently) peut être mise en cache de façon heuristique, donc les navigateurs peuvent la retenir et ne plus passer par votre serveur au clic suivant. Une 302 (Found) est temporaire et n'est pas mise en cache, sauf si vous le demandez.
- Utilisez 302 quand chaque clic doit être compté ou que vous voulez changer la cible plus tard.
- Utilisez 301 quand le lien ne change jamais et que vous préférez moins de requêtes à des statistiques complètes.
307 et 308 sont les versions de 302 et 301 qui conservent la méthode. Pour de simples clics dans un navigateur, qui sont des requêtes GET, le choix entre elles compte rarement.
Stockage et cache
Chaque redirection est une recherche ponctuelle par code ; tout stockage qui fait des recherches par clé rapides convient : un stockage clé-valeur comme DynamoDB ou Cassandra, ou Postgres avec le code en clé primaire. Partitionnez par code quand un nœud ne suffit plus ; un hachage du code répartit la charge uniformément, car par construction aucun code n'est plus sollicité qu'un autre. Les liens populaires font exception, et le cache les absorbe.
Placez un cache Redis devant le stockage avec une lecture cache-aside : cherchez le code dans Redis, retombez sur la base en cas d'absence, puis remplissez le cache avec un TTL. La popularité des liens suit une longue traîne : un cache qui contient une petite fraction des liens sert la plupart des clics. L'article sur les stratégies de cache détaille les TTL, les stampedes et l'invalidation. Les réplicas en lecture aident le chemin de repli ; l'article sur la réplication de base de données explique le retard qu'il faut alors tolérer.
Un CDN ou un cache en périphérie peut servir directement les redirections les plus populaires si vous envoyez un en-tête Cache-Control, mais la périphérie répond alors à des clics que vos serveurs ne voient jamais. Journalisez en périphérie ou ne mettez pas les redirections en cache.
Des statistiques de clics sans ralentir la redirection
N'écrivez jamais un compteur de clics de façon synchrone sur le chemin de redirection. Le service de redirection publie un petit événement de clic (code, horodatage, referrer, pays) dans un journal comme Kafka et répond immédiatement. Un consommateur agrège les comptes par lien et par jour et les écrit dans un stockage analytique. Si l'analytique prend du retard, les redirections continuent de fonctionner ; c'est le même découplage que dans l'article sur l'architecture événementielle.
Le diagramme d'architecture
Contenu du diagramme
- User
- Edge / CDN
- Load balancer
- Redirect service
- Shorten API
- Redis: code to URL
- Links store
- ID allocator
- URL reputation API
- Click events
- Click aggregator
- Analytics store
- GET /{code}
- POST /links
- 1. lookup
- 2. on miss
- insert code and URL
- ID range
- check the target
- click event
- consume
- daily counts
Comment le lire :
- Chemin de lecture (en haut). Les requêtes
GET /{code}traversent la périphérie et le répartiteur de charge jusqu'au service de redirection, qui interroge d'abord Redis et le stockage des liens seulement en cas d'absence. - Chemin d'écriture (en bas).
POST /linksarrive à l'API de raccourcissement. Elle vérifie la cible auprès d'un service de réputation d'URL, prend des ID dans une plage réservée auprès de l'allocateur, les encode en base62 et écrit le lien. - Analytique (à droite). Les événements de clic sont en pointillé, car ils sont asynchrones. L'agrégateur les transforme en comptes quotidiens.
- Le bloc Markdown garde les chiffres de capacité à côté de la conception, pour qu'un relecteur puisse les vérifier.
Abus, expiration et alias personnalisés
Un raccourcisseur public est un outil d'hameçonnage, sauf si vous l'en empêchez. Vérifiez les nouvelles cibles auprès d'un service de réputation comme Google Safe Browsing, limitez la création de liens par compte et par IP, et gardez un moyen de désactiver un code rapidement. Revérifiez périodiquement les anciens liens, car un domaine sain peut devenir malveillant plus tard.
Pour l'expiration, stockez expiresAt avec le lien et vérifiez-le à la redirection, puis supprimez les lignes expirées avec une tâche de fond. Renvoyez 404 ou 410 pour un code expiré au lieu de rediriger.
Ce qu'il faut mettre sur le diagramme
- Séparez les chemins de lecture et d'écriture. Ils évoluent différemment, et le diagramme doit le montrer.
- Indiquez le statut de redirection (301 ou 302) sur le service de redirection. Il décide si l'analytique est complète.
- Montrez d'où viennent les ID. « Générer un code » dans une boîte cache la décision la plus discutée de la conception.
- Dessinez les flux asynchrones en pointillé. Le lecteur doit voir d'un coup d'œil que l'analytique ne peut pas ralentir une redirection.
- Écrivez les chiffres. Une conception sans écritures par seconde ni taille de stockage ne peut pas être relue.
L'exemple de diagramme de conception de système applique les mêmes conventions à un système plus grand.
Le construire dans Archboard
- Connectez-vous avec Google ou GitHub et créez un diagramme.
- Depuis le pack Modèle C4, ajoutez une Personne pour l'utilisateur, puis depuis le pack Cloud un CDN et un Répartiteur de charge cloud.
- Ajoutez quatre nœuds Conteneur du pack Modèle C4 : le service de redirection, l'API de raccourcissement, l'allocateur d'ID et l'agrégateur de clics. Indiquez sur chacun la technologie et une description d'une ligne.
- Ajoutez Redis, Kafka et un Entrepôt de données du pack Données, une Base de données du pack Modèle C4 pour le stockage des liens et un Système externe pour l'API de réputation d'URL.
- Reliez le chemin de lecture en haut et le chemin d'écriture en bas, dessinez les événements de clic en arêtes pointillées et ajoutez un bloc Markdown du pack Base avec les chiffres de capacité.
L'exemple utilise 13 nœuds de diagramme et reste dans les limites de l'offre Free.
FAQ
Quelle longueur pour un code court ? Sept caractères base62 donnent environ 3 500 milliards de codes, assez pour des décennies à 100 millions de liens par mois. Commencez avec sept et laissez les codes passer à huit plus tard ; rien dans la conception ne dépend d'une longueur fixe.
Faut-il hacher l'URL longue pour obtenir le code ? Seulement si la déduplication compte plus que des écritures simples. Les hachages tronqués entrent en collision tôt (au-delà de 2,2 millions de liens pour 7 caractères, au moins une collision est plus probable qu'improbable), donc chaque insertion demande une vérification et un nouvel essai.
301 ou 302, lequel est le meilleur pour un raccourcisseur d'URL ? Une 302 si chaque clic doit être compté ou si la cible peut changer. Une 301 laisse les navigateurs mettre la redirection en cache, ce qui économise des requêtes mais cache les clics répétés à vos statistiques.
Quelle base de données pour stocker les liens ? N'importe quel stockage avec des recherches rapides par clé primaire. Postgres suffit longtemps à ces volumes ; un stockage clé-valeur devient intéressant quand il faut partitionner les écritures sur de nombreux nœuds.
Comment empêcher les gens de deviner les liens ? N'exposez pas d'ID séquentiels. Permutez l'ID avant de l'encoder, ou utilisez des codes aléatoires avec vérification d'unicité, et ne traitez jamais un lien court comme un secret pour du contenu privé.