00

Points de contrôle de pratique

Le rythme de l’entretien reste compact, pour que la page consacre son attention aux vraies décisions de conception.

  1. 01
    Clarifier le périmètre
  2. 02
    Besoins + échelle
  3. 03
    API + modèle de données
  4. 04
    Dessiner l’architecture
  5. 05
    Deep dive
  6. 06
    Décision de compromis
01

Les besoins qui façonnent la conception

Ne vous contentez pas d’énoncer les besoins : demandez-les. Chaque carte associe la contrainte de conception à une question de clarification que vous pouvez dire à voix haute avant de dessiner l’architecture.

Besoins fonctionnels

01Qu'est-ce qui détermine qui apparaît dans ma pile — les préférences, la distance, ou les deux ?

Les utilisateurs définissent des préférences (tranche d'âge, centres d'intérêt) et une distance maximale ; la pile ne contient que des candidats satisfaisant les deux.

02Le balayage se fait-il un à la fois, et un profil peut-il jamais revenir ?

Les utilisateurs balaient oui/non un profil à la fois, et un profil balayé ne réapparaît jamais — des profils répétés se lisent comme un bug.

03Que se passe-t-il au moment d'un oui mutuel ?

Les deux utilisateurs reçoivent la notification de match immédiatement — dès l'instant où le second oui arrive, pas des minutes plus tard.

04Un utilisateur peut-il annuler un balayage à gauche (rewind) ?

Dans une courte fenêtre, oui — et le profil récupéré par le rewind peut réapparaître dans la pile.

05Que fait le désappariement ?

Le désappariement supprime le match et ferme le chat en une seule action — un match à moitié révoqué (chat vivant, match disparu) est un bug de confiance.

06Un utilisateur change ses préférences en pleine session - qu'advient-il de sa pile ?

Les nouvelles préférences prennent effet immédiatement — le tout prochain profil montré doit satisfaire les filtres mis à jour, pas les anciens.

Hors périmètreLe pipeline de téléversement de photos · La messagerie après un match · Les fonctionnalités premium (super swipes, boosts)

Besoins non fonctionnels

01Si deux personnes balaient oui l'une sur l'autre presque au même instant, exactement un match est-il garanti — jamais zéro, jamais deux ?

Exactement un match, détecté immédiatement — jamais zéro, jamais deux, quelle que soit la proximité du timing.

02Pour quel volume de balayages dimensionnons-nous ?

20M utilisateurs actifs quotidiens (DAU) × ~100 balayages ≈ 2B balayages/jour — environ 23K balayages par seconde en moyenne.

03À quelle vitesse la pile doit-elle apparaître ?

Sous 300 ms.

04À quel point la règle « ne jamais montrer deux fois » est-elle dure ?

Strict : cela doit tenir à travers les sessions, les appareils et les réinstallations — montrer un profil deux fois est pire que sauter discrètement un candidat.

05Quelles données de localisation les autres utilisateurs peuvent-ils jamais voir ?

Seulement un bucket de distance grossier (« ~5 km ») — les lat/lng bruts ne quittent jamais le backend, dans aucun payload.

Continuez à demander — l’entretien est une conversation

Les vrais entretiens sondent bien plus qu’une liste propre. Ces questions de périmètre séparent ceux qui interrogent le problème de ceux qui le récitent.

  • Combien de temps l'historique des balayages doit-il perdurer — pour toujours, ou les balayages d'il y a des années peuvent-ils expirer ?
  • Les comptes bots et les balayages de faux profils sont-ils dans le périmètre, ou un système de confiance-et-sécurité en amont les filtre-t-il ?
  • Si quelqu'un bloque ou signale un utilisateur, les deux doivent-ils disparaître immédiatement de la pile l'un de l'autre ?
  • Certains marchés imposent-ils des règles de résidence des données — les données de localisation d'un utilisateur doivent-elles rester dans la région ?
  • Les super-likes / qui-m'a-liké sont-ils dans le périmètre ? Ils changent les schémas de lecture.
02

Les chiffres qui forcent les décisions d’architecture

Traitez chaque estimation comme une pression qui justifie un composant : cache, file, partition, réplica, pool de workers ou chemin de repli.

01

Débit d'écriture des balayages

20M utilisateurs actifs quotidiens (DAU) × ~100 balayages/jour2B ÷ 86,400 s ≈ 23K swipes/s en moyenne, en soirée ×3-5 ≈ 100K/s au pic

Les balayages ont besoin d'un stockage optimisé en écriture ; la vérification mutuelle doit rester O(1) par balayage à ce débit.

02

Mémoire des profils vus

On suppose qu'un utilisateur assidu balaie ~50K profils sur toute sa vieFiltre de Bloom à 1% de faux positifs ≈ 10 bits/entrée → 50K × 10 b ≈ 62 KB par utilisateur

Toute la garde « ne jamais montrer deux fois » tient en kilo-octets par utilisateur — l'historique exact des balayages reste sur disque.

03

Coût de précalcul de la pile

On suppose une pile de ~200 candidats par utilisateur actif, rafraîchie quand elle est basse20M users × 200 IDs × 8 B ≈ 32 GB

Les piles précalculées pour chaque utilisateur actif tiennent dans une seule couche de cache — c'est ce qui rend le <300 ms faisable.

04

Pourquoi ne pas interroger en direct

Une ville dense peut contenir environ 1 % de 20M de DAU dans un même cercle de distance maximale — à peu près 200K utilisateurs. Chacun a encore besoin d'un filtrage géo, âge, préférence et non-vu.200 K candidats × ~1-2 µs chacun pour intersecter les filtres et vérifier la liste des vus ≈ 200-400 ms par requête — tout le budget de 300 ms épuisé avant même que le classement ne commence

Le budget du feed exclut de lancer la requête en direct sur le chemin de la requête. À la place, précalculez le deck et regarnissez-le en arrière-plan.

05

Coût de la vérification de match

Chaque balayage-oui vérifie le sens inverse1 read-modify-write atomique par balayage ≈ sous-ms en mémoire

Co-localiser l'état de balayage de la paire (une seule clé) est ce qui maintient la vérification en une seule opération.

Exemple de décision

Les chiffres

Cent mille swipes par seconde au pic. Le seul moment qui ne peut pas mal tourner, c'est deux personnes se disant oui l'une à l'autre en même temps.

Mon choix

Je stockerais l'état de swipe de chaque paire sous une clé unique — les deux IDs d'utilisateur, le plus petit d'abord. Puis j'enregistre le swipe et je vérifie le swipe inverse en une seule opération atomique. Un script Lua dans Redis fait ce read-modify-write en une seule étape, et la copie durable est écrite dans le store de swipes derrière. Les decks sont précalculés par utilisateur et regarnis par une requête géo de fond. Les profils déjà vus de chaque utilisateur vivent dans un Bloom filter, si bien que « ne jamais montrer deux fois » coûte des kilo-octets, pas un balayage d'historique.

À éviter

Ce que je NE ferais PAS : enregistrer le swipe et vérifier l'inverse en deux étapes séparées. Deux oui simultanés se manquent l'un l'autre, et le match ne se déclenche jamais. Ce trou entre la vérification et l'action est la même course qui fait vendre deux fois des sièges de concert — la vérification et l'écriture doivent être une seule opération. Je ne construirais pas non plus le feed comme une requête géo en direct par requête, car ça brûle tout le budget de 300 ms en recherches d'index.

Changer si

Si Redis devient le goulet d'étranglement, ou si le basculement de cluster devient trop complexe, je déplacerais l'atomicité dans la couche de stockage. Une clé de partition composée (smaller_id:larger_id) fait atterrir les deux swipes dans une même partition. Là, une transaction légère couvre la paire — le compare-and-set propre à la base, plus lent que Redis mais intégré. Ça coûte plus par swipe, mais économise un système mobile.

03

Chemin d’architecture

D’abord une image complète, puis chaque chemin comme son propre schéma — le chemin d’écriture et le chemin de lecture portent un trafic différent et justifient des composants différents.

Image complète

Vue d’ensemble — chaque composant

ClientAPI GatewaySwipe ServicePair State(Redis, atomic)Swipe Store(durable)MatchNotificationsoui mutuel → les deux utilisateursDeck Service +Cachefeed précalculéBloom Filters(seen)ne jamais montrer deux fois

Le chemin de balayage est la colonne vertébrale critique pour la cohérence ; le chemin du feed est optimisé en lecture et précalculé. En pointillés = maintenu de façon asynchrone à partir des balayages durables, reconstructible après une perte de cache.

Chemin 1

Chemin de balayage — enregistrer et apparier atomiquement

ClientSwipe ServicePair Key (Lua,one op)Swipe StoreMatchNotification

Une seule opération atomique enregistre le swipe et vérifie l'inverse. L'écriture durable suit. Si un nœud Redis est perdu, il rejoue depuis le store durable et perd au plus les derniers swipes non déversés — un compromis accepté pour la vitesse du chemin de swipe.

Chemin 2

Chemin du feed — pile précalculée, recomplétée

ClientDeck CacheDeck ServiceGeo + PreferenceQueryBloom Filter(exclude seen)

Le chemin de la requête ne lit que la pile. Quand elle s'épuise, la requête en arrière-plan la recomplète, en excluant les profils vus via le filtre de Bloom — les faux positifs sautent un candidat, n'en répètent jamais un.

04

API et modèle de données

Avant d’optimiser, rendez le contrat inspectable : endpoints, entités, propriété, retries et état.

POST/profile

req{ age_min, age_max, distance_km, interested_in }

rés200 profile

Identité issue du token de session. Les changements de préférence invalident la pile précalculée.

GET/feed

rés200 User[] (next slice of the deck)

Sert la pile précalculée ; quand elle s'épuise, une nouvelle requête géo+préférence la recomplète. La localisation vient du contexte de session, pas des paramètres de requête.

POST/swipe/{target_user_id}

req{ decision: yes | no }

rés200 { matched: boolean } — matched:true fires both notifications

L'étape atomique : enregistrer le balayage ET vérifier le balayage inverse en une seule opération.

Entités principales

User

user_id (PK) · profile · preferences · geo_cell (coarse)

La localisation est stockée comme une cellule géo grossière pour l'appariement ; les coordonnées précises ne sont jamais servies aux autres clients.

Swipe

swiping_user · target_user · decision: yes/no · swiped_at

Écrit durablement dans le swipe store ; également replié dans le filtre de Bloom des profils vus du balayeur.

Match

match_id (PK) · user_a · user_b · matched_at

05

Directions de deep dive

Choisissez une voie pour le dernier tiers de l’entretien. Chaque voie vous donne le sujet, la question de l’examinateur à laquelle répondre, et le mode d’échec à éviter.

Focus

Le oui simultané

Question

Deux utilisateurs balaient oui l'un sur l'autre dans les mêmes 10 ms. Déroulez les opérations exactes qui produisent un match, pas zéro.

Réponse

Faites-en une seule opération atomique. Stockez l'état de balayage des deux utilisateurs sous une clé unique, les deux ID triés du plus petit d'abord. Enregistrez le balayage et vérifiez le balayage inverse dans un seul script Lua Redis. Le deuxième balayage voit toujours le premier, donc exactement un match se déclenche.

À éviter

Read-then-write en deux étapes — les deux balayages se manquent et le match ne se produit silencieusement jamais.

Focus

Ne jamais montrer deux fois

Question

Comment excluez-vous 50 000 profils déjà balayés de chaque recomplétion de pile sans scanner l'historique ?

Réponse

Utilisez, par utilisateur, un filtre de Bloom de chaque profil qu'il a balayé. À 50 K entrées, ça fait environ 62 Ko. Filtrez chaque candidat de la pile contre lui ; un faux positif se contente de sauter un candidat et n'en répète jamais un. Un filtre de Bloom ne peut pas désapprendre, alors gardez les derniers balayages dans un petit buffer exact — c'est ce qui fait marcher le rewind.

À éviter

Traiter les faux positifs de Bloom comme un bug — sauter un candidat est le coût prévu ; en répéter un est l'échec.

Focus

La pile se vide

Question

Un utilisateur actif balaie toute sa pile précalculée. Qu'est-ce qui la recomplète, à quel point est-elle fraîche, et quelle latence voit-il ?

Réponse

Répondez à chaque requête depuis le cache de pile précalculée. Les recharges se déclenchent de façon asynchrone à un seuil bas, si bien qu'une requête géo+préférences en arrière-plan reconstruit la pile de 200 candidats avant que l'utilisateur n'atteigne le fond. Ce précalcul est ce qui garde une requête multi-filtres en direct hors du chemin des 300 ms. Un changement de préférences abandonne la pile et la recharge de la même façon.

À éviter

Recompléter de façon synchrone sur la requête de pile vide — le budget de 300 ms est parti avant même que la requête ne démarre.

Focus

La localisation sans la divulguer

Question

L'appariement a besoin de la distance, les utilisateurs ne doivent jamais voir les coordonnées. Où la précision est-elle abandonnée, et que renvoie réellement l'API ?

Réponse

Gardez les coordonnées précises côté serveur uniquement, pour la sélection des candidats. Le backend calcule la distance et l'arrondit dans un compartiment grossier comme « à ~5 km » avant qu'elle n'entre dans une réponse. Aucune charge utile ne porte jamais de lat/lng dans un quelconque champ. Arrondir côté client est du théâtre — c'est la charge utile elle-même qui est la fuite.

À éviter

Envoyer les lat/lng bruts aux clients et arrondir dans l'UI — le payload est la fuite.

Focus

Tout le monde balaie la même personne

Question

Un profil très populaire apparaît dans des millions de piles. Qu'est-ce qui devient un point chaud en premier, et comment gardez-vous l'exposition équilibrée ?

Réponse

Les clés de paire et les partitions de balayage du profil deviennent le point chaud en premier, alors shardez ou répliquez cet état. Ensuite, donnez au profil un budget d'exposition : un plafond sur le nombre de piles en direct qu'il peut occuper à la fois, réapprovisionné à mesure que les balayages le vident. Ainsi un profil populaire ne peut pas remplir chaque pile à proximité et priver tous les autres de vues.

À éviter

Ignorer le biais d'exposition — sans plafonds, les profils populaires dominent chaque pile et l'engagement s'effondre.

Prêt à vous entraîner ?

Expliquez Tinder à voix haute et obtenez une évaluation IA de votre explication.

S’entraîner avec l’IA →