10 min de lectureIngénierie

Comment créer un raccourcisseur d'URL : architecture et code

Comment créer un raccourcisseur d'URL qui résiste à la production : génération de code court, chemin de redirection, mise en cache, suivi des clics, défense contre les abus, et ce qu'il faut maintenir.

Marius Voß
DevRel · edge infra
Diagramme d'architecture d'un raccourcisseur d'URL montrant le chemin d'écriture qui encode un code court et le chemin de lecture qui résout une redirection depuis le cache

Pour créer un raccourcisseur d'URL, vous avez besoin de quatre choses : un endroit pour stocker la correspondance entre un code court et une URL de destination, un moyen de générer un code unique pour chaque nouveau lien, un gestionnaire de redirection qui recherche le code et retourne une redirection HTTP, et un cache devant la recherche car les lectures dépassent les écritures dans une proportion considérable. C'est tout le coeur du système, et vous pouvez le mettre en place en un après-midi.

Le piège est de croire que la version de l'après-midi est le produit. Une redirection qui fonctionne sur votre ordinateur portable et un service de raccourcissement d'URL qui résiste à des inconnus qui le pointent vers des malwares, le bombardent de trafic, et s'attendent à quatre neuf de disponibilité sont deux problèmes d'ingénierie différents. Le premier est un algorithme. Le second est un engagement opérationnel.

Ce guide construit honnêtement le coeur du système, puis consacre la majeure partie de son temps à la partie que les tutoriels de conception de systèmes ignorent : ce que vous devez encore construire après que la redirection fonctionne. Si vous souhaitez d'abord le guide conceptuel, comment fonctionnent les raccourcisseurs d'URL couvre les mécanismes sans le code.

Deux chemins dans un raccourcisseur d'URL : le chemin d'écriture encode un identifiant unique en code court et le stocke, le chemin de lecture résout un clic via le cache vers une redirection

La version courte : ce que fait réellement un raccourcisseur d'URL#

Un raccourcisseur d'URL est une recherche clé-valeur habillée d'une redirection HTTP. La clé est le code court, la valeur est la longue URL, et l'entièreté du travail consiste à transformer example.com/aB3x9 en un 302 pointant vers l'adresse d'origine.

Le modèle de données est une seule table :

CREATE TABLE links (
    id          BIGSERIAL PRIMARY KEY,
    short_code  TEXT NOT NULL UNIQUE,
    long_url    TEXT NOT NULL,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE UNIQUE INDEX idx_links_short_code ON links (short_code);

Il y a deux chemins à travers celui-ci. Le chemin d'écriture prend une longue URL, génère un code court, et insère la ligne. Le chemin de lecture prend un code court, recherche la ligne, et retourne une redirection. Les lectures dominent dans un ratio couramment autour de 1000 pour 1, donc presque toute votre attention d'ingénierie doit être consacrée à rendre la recherche rapide et peu coûteuse. L'index unique sur short_code est ce qui fait de cette recherche une recherche par index plutôt qu'un scan. C'est tout le coeur du système.

Génération du code court : base62, aléatoire ou hachage#

Le code court est là où se trouve la décision intéressante. Vous avez trois stratégies réalistes, et elles impliquent des compromis entre longueur, prévisibilité, et la difficulté de gérer les collisions.

Le base62 d'un identifiant unique est le classique. Prenez l'identifiant de ligne auto-incrémenté et encodez-le en base62, les 62 caractères a-z, A-Z, et 0-9. Les codes sont courts, ils ne se heurtent jamais car chaque identifiant est unique, et ils gagnent un caractère de longueur approximativement tous les 62x en volume. L'inconvénient est qu'ils sont séquentiels et devinables, donc n'importe qui peut parcourir votre espace de nommage.

const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

// encode turns a positive integer ID into a base62 short code.
func encode(id uint64) string {
	if id == 0 {
		return string(alphabet[0])
	}
	var b []byte
	for id > 0 {
		b = append(b, alphabet[id%62])
		id /= 62
	}
	// reverse, since we built the digits least-significant first
	for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
		b[i], b[j] = b[j], b[i]
	}
	return string(b)
}

Les chaînes aléatoires corrigent le problème de devinabilité. Générez un code aléatoire court, par exemple avec une bibliothèque comme nanoid, et vérifiez-le par rapport à l'index unique avant de l'enregistrer. À sept caractères de base62 vous avez des milliards de milliards de possibilités, donc les collisions sont rares, mais vous devez quand même gérer le rare cas d'insertion qui échoue à la contrainte d'unicité en réessayant avec un nouveau code.

Le hachage de l'URL est la troisième option et généralement la pire. Un hachage de la longue URL est déterministe, ce qui semble pratique, mais vous devez quand même le tronquer, vous obtenez quand même des collisions, et des URL identiques correspondent à des codes identiques, ce qui divulgue des informations. La plupart des services en production choisissent le base62 pour les identifiants internes ou les codes aléatoires pour les codes publics. Les slugs personnalisés ou de marque, les codes qu'un utilisateur saisit à la main, sont validés par rapport au même index unique avant d'être acceptés.

Le chemin de redirection : 301 vs 302 et pourquoi cela détermine vos analyses#

Le code de statut de redirection n'est pas un choix cosmétique. Il détermine si vous verrez jamais un deuxième clic.

Un 301 Moved Permanently indique aux navigateurs et aux proxies que le déplacement est permanent, ils le mettent donc en cache. Après la première visite, le navigateur peut envoyer les futurs clics directement vers la destination sans toucher votre serveur. Excellent pour la vitesse brute, fatal pour les analyses, car les clics que vous souhaitez le plus comptabiliser sont ceux qui ne vous parviennent jamais. La sémantique HTTP est explicitée dans RFC 9110, qui définit à la fois les redirections permanentes et temporaires.

Un 302 Found ou 307 Temporary Redirect est redemandé à chaque fois. Le navigateur interroge votre serveur à chaque clic, ce qui signifie que vous pouvez compter chaque visite et modifier la destination ultérieurement sans lutter contre les caches périmés. Pour un raccourcisseur de liens dont toute la valeur réside dans des liens modifiables et des données de clics, c'est la valeur par défaut correcte. Le coût est un aller-retour réseau par clic, que un cache hit rend négligeable.

La règle générale : optez pour 302 sauf si vous avez une raison spécifique de vouloir que le lien soit gelé et mis en cache pour toujours. L'article redirections 301 vs 302 examine le compromis en détail, et les types de redirections couvre le reste de la famille 3xx, notamment quand 307 et 308 sont pertinents.

Stockage et mise en cache : concevoir pour un ratio lecture/écriture de 1000:1#

Parce que les lectures écrasent les écritures, la base de données n'est pas votre goulot d'étranglement, c'est votre stratégie de cache. Le schéma est un cache à lecture anticipée : lors d'un clic, vérifiez d'abord un cache en mémoire, et ne revenez à la base de données qu'en cas de miss, en réécrivant le résultat dans le cache pour la prochaine fois.

func resolve(ctx context.Context, code string) (string, error) {
	if url, ok := cache.Get(code); ok {
		return url, nil // hot path: served from memory
	}
	url, err := db.LookupLongURL(ctx, code)
	if err != nil {
		return "", err
	}
	cache.Set(code, url) // populate for the next click
	return url, nil
}

En production, cela devient généralement deux niveaux : un petit cache en cours de processus pour les liens les plus actifs, soutenu par un store en mémoire partagé tel que Redis afin que chaque instance de serveur bénéficie d'une recherche que l'une d'elles a déjà effectuée. La base de données, la source de vérité, n'est touchée que lors d'un vrai cold miss. Maîtrisez cette couche et un seul serveur modeste gère un volume de clics énorme. L'article stratégie de cache pour les redirections d'URL approfondit les décisions d'éviction et de dimensionnement, et le guide de référence sur atteindre p95 sous 15ms couvre ce qu'un chemin de redirection optimisé ressemble sous charge.

Si vous préférez ne pas gérer tout cela, l'API d'Elido vous offre le niveau de redirection, le cache, et la livraison EU en région avec p95 sous 15ms sur un cache hit, derrière un seul appel. Commencez gratuitement et évitez les opérations.

Comptabiliser les clics sans ralentir la redirection#

L'erreur qui tue la latence de redirection est d'écrire le clic dans la base de données à l'intérieur du gestionnaire de redirection. Faites cela et chaque visiteur attend votre écriture d'analyse avant d'obtenir sa redirection.

Découpler les deux. Le gestionnaire émet la redirection immédiatement, puis envoie l'événement de clic dans un journal durable ou une file de messages en mode fire-and-forget. Un consommateur séparé lit ce flux et écrit les événements dans un store d'analyse selon son propre calendrier. Le visiteur n'attend jamais, et une requête de reporting qui parcourt des millions de lignes de clics ne concurrence jamais le chemin de redirection pour les ressources. Une base de données d'analyse en colonnes gère ces requêtes agrégées bien mieux qu'un store en lignes, c'est pourquoi les événements de clics atterrissent généralement ailleurs que la table des liens. L'article ingestion de clics en fire-and-forget détaille le côté file de messages, et pourquoi un store en colonnes bat Postgres pour les analyses de clics explique le choix de stockage. Les analyses d'Elido suivent cette architecture pour que les clics soient interrogeables en secondes sans ajouter de millisecondes à la redirection.

Le backlog de production au-delà d'une redirection fonctionnelle : analyse des abus, limitation du débit, TLS pour domaine personnalisé, données de clics conformes au GDPR, et haute disponibilité

Ce que vous devez encore construire : les 80 pour cent difficiles#

Voici la partie que les guides de conception de systèmes omettent. Une redirection fonctionnelle représente peut-être un cinquième d'un vrai service de raccourcissement d'URL. Le reste est tout ce qui transforme une démo en quelque chose que vous pouvez mettre sur l'internet public.

  • Analyse des abus et sécurité. Un raccourcisseur public est un aimant à phishing en quelques heures après le lancement. Vous devez vérifier les destinations par rapport à un flux de menaces tel que Google Safe Browsing et re-scanner, car une URL propre à la création peut devenir malveillante plus tard. La liste de contrôle de sécurité pour les raccourcisseurs d'URL est la liste complète.
  • Limitation du débit et idempotence. Un point d'accès de création ouvert est scripté instantanément. Vous avez besoin de limites par clé et d'idempotence pour qu'une requête réessayée ne crée pas de liens en double. Les mécanismes sont dans les limites de débit de l'API et l'idempotence.
  • Domaines personnalisés avec TLS. Les liens de marque impliquent d'émettre des certificats pour des domaines que vous ne possédez pas, à la demande, sans étapes manuelles.
  • Données de clics conformes au GDPR. Dès que vous journalisez des clics, vous traitez des données personnelles. Tronquer les adresses IP et documenter la rétention n'est pas facultatif dans l'UE, comme GDPR pour les raccourcisseurs d'URL l'explique.
  • Haute disponibilité. Votre redirection est maintenant sur le chemin critique de chaque lien que quelqu'un a jamais partagé. Les pannes cassent le contenu d'autres personnes, donc la barre de disponibilité est plus haute que pour la plupart des applications.

Rien de tout cela n'est exotique. Ce sont juste beaucoup de travail soutenu, et cela ne finit jamais, ce qui est la raison honnête pour laquelle la plupart des équipes s'arrêtent au MVP et se tournent vers quelque chose de maintenu.

Construire, acheter ou auto-héberger#

Construire le sien est la meilleure façon de comprendre les redirections, l'encodage et la mise en cache, et pour un outil interne fermé le MVP peut être tout ce dont vous aurez jamais besoin. Construisez-le. Vous apprendrez plus en un week-end que n'importe quelle préparation aux entretiens.

Pour tout ce qui est public ou orienté entreprise, évaluez honnêtement la maintenance. La redirection est gratuite ; la gestion des abus, le TLS pour les domaines personnalisés, le pipeline d'analyse, et l'astreinte ne le sont pas. Si vous voulez le contrôle sans tout écrire de zéro, vous pouvez auto-héberger un service existant, et Elido propose un chemin d'auto-hébergement exactement pour cela, avec l'article sur les options open-source qui les présente côte à côte. Si vous préférez déléguer entièrement, la solution pour les développeurs et le démarrage rapide API et SDK vous donnent un niveau de redirection en production sans le backlog ci-dessus.

Sur le blog#

Essayer Elido

Collez une URL, obtenez un lien court

Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.

Gratuit, sans inscription · 2 par jour

Essayer Elido

Raccourcisseur d'URL hébergé en UE : domaines personnalisés, analyses approfondies et API ouverte. Forfait gratuit - sans carte bancaire.

Tags
build a url shortener
url shortener system design
short code generation
base62 encoding
url redirect
url shortener architecture
link shortener api

Lire la suite