Réponse A: Anthropic Claude Sonnet 5
Conception d'un système de notification en temps réel pour une application de médias sociaux
1. Récapitulatif des exigences et estimation sommaire
Estimation du trafic :
- 10 millions d'utilisateurs actifs par jour × 20 notifications/jour = 200 millions de notifications/jour
- Taux moyen = 200 millions / 86 400 s ≈ 2 315 notifications/sec
- Taux de pointe (5x) ≈ 11 500 notifications/sec
- Pour 100 millions d'utilisateurs actifs par jour (futur) : moyenne ~23 000/s, pointe ~115 000/s
Estimation du stockage :
- Chaque enregistrement de notification ~500 octets (métadonnées + charge utile)
- Stocka...
Afficher la réponse complète ▼
Conception d'un système de notification en temps réel pour une application de médias sociaux
1. Récapitulatif des exigences et estimation sommaire
Estimation du trafic :
- 10 millions d'utilisateurs actifs par jour × 20 notifications/jour = 200 millions de notifications/jour
- Taux moyen = 200 millions / 86 400 s ≈ 2 315 notifications/sec
- Taux de pointe (5x) ≈ 11 500 notifications/sec
- Pour 100 millions d'utilisateurs actifs par jour (futur) : moyenne ~23 000/s, pointe ~115 000/s
Estimation du stockage :
- Chaque enregistrement de notification ~500 octets (métadonnées + charge utile)
- Stockage quotidien : 200 millions × 500 octets = 100 Go/jour → ~36 To/an (avant compression/archivage)
- L'exigence d'historique (100 dernières notifications par utilisateur) correspond à un modèle d'accès « en lecture intensive » à chaud, favorisant une conception où les notifications récentes sont peu coûteuses à récupérer (par exemple, une liste limitée par utilisateur) tandis que l'historique complet est stocké à froid.
Cela confirme que nous avons affaire à un système lourd en écriture, lourd en diffusion (fan-out), sensible à la latence qui doit découpler l'ingestion de la livraison.
2. Architecture générale
[Producteurs d'événements] → [Bus d'événements / Kafka] → [Service de notification (Consommateurs)]
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
[Service de préférences] [Rendu/Mise en forme] [Limiteur de débit/Dédoublonnage]
│ │ │
└───────────────┬────────────┴───────────────────────────┘
▼
[Répartiteur de livraison / Routeur]
┌─────────────┬─────────────┬─────────────┐
▼ ▼ ▼ ▼
[Service Push] [Passerelle WebSocket/ [Service E-mail] [Écriture dans le magasin
(FCM/APNs)] SSE] (SES/SendGrid) en application - DynamoDB/Cassandra]
│ │
[Appareils Mobiles][Clients Connectés]
Flux :
- Un système source (par exemple, Service de J'aime, Service de Commentaires, Service de Suivi, Service de Messagerie) émet un événement (par exemple,
user_liked_post) vers un bus de messages durable (Kafka). - L'Orchestrateur de notifications consomme ces événements, vérifie les préférences de l'utilisateur, applique la logique de déduplication/limitation de débit/regroupement (par exemple, « John et 5 autres personnes ont aimé votre publication »), et génère un objet de notification.
- L'orchestrateur persiste la notification (pour l'historique) et la pousse vers un Répartiteur de livraison, qui diffuse vers les canaux appropriés en fonction des préférences de l'utilisateur et de l'état de l'appareil (en ligne ou hors ligne).
- Les workers de livraison gèrent la transmission réelle : fournisseurs de notifications push (FCM pour Android, APNs pour iOS), WebSocket/SSE pour les mises à jour de badges en temps réel dans l'application, et e-mail via un fournisseur d'e-mails transactionnels.
3. Composants clés
3.1 Couche d'ingestion d'événements — Apache Kafka
- Tous les services sources publient des événements vers des sujets Kafka (partitionnés par
user_idpour préserver l'ordre par utilisateur). - Kafka fournit la durabilité (facteur de réplication 3), un débit élevé et un tampon naturel pendant les pics de trafic — essentiel car le trafic de pointe est 5 fois supérieur à la moyenne.
- Sujets :
notification.likes,notification.comments,notification.followers,notification.messages(ou un sujet unique avec un champ de type d'événement, selon les besoins d'évolution du schéma).
Pourquoi Kafka plutôt que SQS/RabbitMQ ? Kafka gère un débit très élevé avec une faible surcharge par message et prend en charge la relecture (utile pour retraiter des lots échoués ou pour le remplissage de données). SQS est plus simple sur le plan opérationnel mais plus difficile à faire évoluer au-delà de 100 000 msg/s de manière rentable et ne prend pas en charge les sémantiques de relecture de groupe de consommateurs de manière aussi claire.
3.2 Orchestrateur de notifications
- Groupe de consommateurs sans état lisant depuis Kafka.
- Responsabilités :
- Vérification des préférences : interroger un magasin clé-valeur rapide (Redis ou DynamoDB) pour les paramètres de notification de l'utilisateur avant de poursuivre le traitement. Cela évite un travail inutile de génération de notifications auxquelles un utilisateur s'est désabonné.
- Dédoublonnage/Regroupement : utiliser une fenêtre d'agrégation de courte durée (par exemple, des ensembles triés Redis avec TTL) pour regrouper des événements similaires (par exemple, plusieurs « j'aime » sur la même publication en 60 secondes deviennent une seule notification).
- Diffusion pour les abonnés : pour les événements tels que « nouvelle publication de quelqu'un que vous suivez », cela peut nécessiter une diffusion à des millions d'abonnés (problème de la célébrité). Utiliser un modèle de diffusion hybride :
- Diffusion à l'écriture pour les utilisateurs réguliers (pousser immédiatement vers le fil de notification de chaque abonné).
- Diffusion à la lecture pour les célébrités/comptes à fort nombre d'abonnés (calculer au moment de la lecture pour éviter une tempête d'écritures).
- Évolutif horizontalement — adapter le nombre d'instances de consommateurs en fonction du nombre de partitions Kafka et du décalage.
3.3 Magasin de notifications (Couche de persistance)
- Magasin principal : une base de données NoSQL à colonnes larges comme Apache Cassandra ou DynamoDB, partitionnée par
user_id, groupée/triée partimestamp(décroissant).- Ce modèle est idéal car le modèle d'accès dominant est « obtenir les 100 dernières notifications pour l'utilisateur X », ce qui correspond à une simple requête de plage sur une partition — aucune jointure nécessaire.
- Cassandra offre une cohérence réglable et une évolutivité horizontale bien au-delà de 100 millions d'utilisateurs ; DynamoDB offre une alternative entièrement gérée avec moins de surcharge opérationnelle (compromis : coût plus élevé à très grande échelle, et risque de partition chaude pour les utilisateurs extrêmement actifs, sauf si les clés de partition sont salées).
- TTL/Archivage : conserver uniquement les notifications récentes (par exemple, 30 à 90 jours) dans le magasin chaud ; les données plus anciennes sont archivées dans un stockage moins cher (S3 + Glacier) pour la conformité/l'audit, le plafond des « 100 dernières » étant appliqué à l'écriture (une liste limitée par utilisateur, ou tronquée par une compaction périodique).
3.4 Répartiteur de livraison
- Lit l'objet de notification finalisé et détermine les canaux à utiliser en fonction de :
- Préférences de canaux de l'utilisateur (push/e-mail/les deux/aucun)
- État de connexion de l'utilisateur (suivi via un service de présence basé sur Redis, mis à jour par WebSocket/battement de cœur)
- Route vers :
- Service de notifications push : s'intègre avec FCM (Android) et APNs (iOS). Encapsuler dans une couche d'abstraction interne pour normaliser les nouvelles tentatives, les formats de charge utile et la gestion des jetons d'appareil (jetons stockés dans une table
user_devices, rafraîchis au lancement de l'application). - Livraison en temps réel dans l'application : pour les utilisateurs activement connectés via WebSocket/SSE, pousser directement vers une passerelle de connexion (par exemple, une flotte de serveurs WebSocket derrière un équilibreur de charge, utilisant quelque chose comme Socket.IO ou un service géré comme AWS API Gateway WebSockets). La correspondance connexion-serveur est suivie dans Redis afin que tout nœud répartiteur puisse trouver quelle instance de passerelle détient la connexion d'un utilisateur.
- Service d'e-mail : pour les notifications moins sensibles au temps (par exemple, résumé hebdomadaire) ou comme solution de repli pour les utilisateurs hors ligne sur certains types de notifications, s'intégrer avec un fournisseur comme Amazon SES ou SendGrid, en utilisant une file d'attente distincte à priorité plus basse car les SLA de messagerie sont plus souples (quelques secondes à quelques minutes suffisent).
- Service de notifications push : s'intègre avec FCM (Android) et APNs (iOS). Encapsuler dans une couche d'abstraction interne pour normaliser les nouvelles tentatives, les formats de charge utile et la gestion des jetons d'appareil (jetons stockés dans une table
3.5 Service de préférences
- Service simple basé sur une base de données relationnelle (Postgres) ou DynamoDB, mis en cache de manière agressive dans Redis (les préférences changent rarement, lectures extrêmement fréquentes — candidat idéal pour la mise en cache).
- Schéma :
user_id, notification_type, channel, enabled.
4. Modèle de données
Table des notifications (Cassandra/DynamoDB)
Clé de partition : user_id
Clé de clustering : notification_id (UUID basé sur le temps, trié par ordre décroissant)
Attributs :
- type (like, comment, follow, message)
- actor_id (qui l'a déclenché)
- actor_ids (tableau, pour les notifications groupées)
- target_object_id (post_id, comment_id, etc.)
- message_preview
- created_at
- read_status (booléen)
- delivered_channels (tableau : push, email, in-app)
Table des préférences utilisateur
Clé de partition : user_id
Attributs : { likes: {push: true, email: false}, comments: {...}, follows: {...}, messages: {...} }
Table des jetons d'appareil
Clé de partition : user_id
Clé de clustering : device_id
Attributs : platform (ios/android), token, last_active
5. Assurer la fiabilité (« Aucune notification perdue »)
- Messagerie durable : Kafka avec un facteur de réplication ≥3 et
acks=allsur les producteurs garantit que les événements ne sont pas perdus avant le traitement. - Traitement au moins une fois avec idempotence : les consommateurs peuvent retraiter en cas d'échec/redémarrage, donc les ID de notification sont générés de manière déterministe (par exemple, hachage de l'ID d'événement source + type) pour permettre des écritures idempotentes — évite les notifications dupliquées lors de la nouvelle tentative.
- Files d'attente de lettres mortes (DLQ) : les livraisons échouées (par exemple, délai d'attente du fournisseur push) sont envoyées vers un sujet DLQ avec une nouvelle tentative par exponentielle décroissante (par exemple, 3 nouvelles tentatives avec jitter), puis vers une revue manuelle/alerte si l'échec persiste.
- Écrire la notification dans le magasin AVANT de tenter la livraison : cela découple « la notification existe » (durabilité/historique) de « la notification a été livrée » (temps réel au mieux).
- Modèle de boîte d'envoi (outbox) dans les services sources : pour éviter les problèmes de double écriture (écriture en base de données + publication d'événement), utiliser le modèle de boîte d'envoi transactionnelle afin que lorsqu'un « j'aime » est enregistré dans la base de données du service source, l'événement soit garanti d'être également publié dans Kafka via un outil de capture de données modifiées (CDC) comme Debezium.
6. Stratégie d'évolutivité (10 millions → 100 millions d'utilisateurs actifs par jour)
- Kafka : augmenter le nombre de partitions (partitionnées par hachage de
user_id) — évolue linéairement avec plus de brokers/consommateurs. - Orchestrateur et Répartiteur de notifications : sans état, évolutifs horizontalement derrière des groupes de consommateurs ; adapter via HPA Kubernetes en fonction du décalage des consommateurs Kafka.
- Cassandra : ajouter des nœuds au cluster ; le hachage cohérent distribue la charge automatiquement. Surveiller les partitions chaudes dues au contenu viral/comptes de célébrités — atténuer via le partitionnement (par exemple, diviser la diffusion d'une célébrité sur plusieurs clés de partitionnement).
- Passerelle WebSocket : adapter horizontalement ; utiliser des sessions collantes via un registre de connexions dans Redis afin que les répartiteurs sachent quel nœud de passerelle possède quelle connexion, quel que soit le nombre de nœuds de passerelle existants.
- Fournisseurs Push/E-mail : ce sont des services tiers gérés (FCM, APNs, SES) qui évoluent indépendamment ; notre responsabilité est le regroupement et la limitation de débit pour rester dans les quotas des fournisseurs.
- Mise en cache : la mise en cache agressive dans Redis des préférences et des données de présence réduit la charge de la base de données à mesure que le nombre d'utilisateurs est multiplié par 10.
7. Compromis clés
| Décision | Compromis |
|---|---|
| Kafka vs. file d'attente plus simple (SQS) | Kafka ajoute une complexité opérationnelle (nécessite une expertise dédiée en exploitation, gestion de ZooKeeper/KRaft) mais offre un débit et une capacité de relecture bien supérieurs, nécessaires à cette échelle. |
| Cassandra vs. DynamoDB | Cassandra offre plus de contrôle et potentiellement un coût inférieur à très grande échelle mais nécessite une exploitation interne ; DynamoDB est entièrement géré (plus rapide à construire, moins de charge opérationnelle) mais peut devenir coûteux à une échelle extrême et présente des contraintes plus strictes en matière de taille d'élément/partitionnement de débit. |
| Diffusion à l'écriture vs. diffusion à la lecture | La diffusion à l'écriture offre une latence de lecture plus faible (idéal pour le SLA de 2 secondes) mais risque une tempête d'écritures « troupeau » pour les célébrités ; la diffusion à la lecture évite cela mais ajoute de la latence et du calcul au moment de la lecture. L'approche hybride équilibre les deux mais ajoute de la complexité de conception/code (deux chemins de code). |
| Stockage de l'historique complet vs. 100 notifications limitées à chaud + archive | Réduit les coûts de stockage à chaud et maintient des requêtes rapides, mais nécessite un chemin d'archivage/récupération pour la conformité ou les fonctionnalités « charger plus », ajoutant de la complexité. |
| Livraison au moins une fois + idempotence vs. exactement une fois | Les sémantiques « exactement une fois » dans les systèmes distribués sont coûteuses/complexes (nécessite des consommateurs transactionnels) ; la livraison « au moins une fois » + écritures idempotentes atteint la même garantie pratique (aucune notification visible dupliquée) à un coût opérationnel bien inférieur. |
| Push en temps réel pour tout vs. regroupement/agrégation | L'agrégation de notifications similaires (par exemple, « 10 personnes ont aimé votre photo ») réduit la fatigue des notifications et le volume de livraison, améliorant à la fois l'expérience utilisateur et le coût, au détriment d'une logique d'orchestration légèrement plus complexe et d'un léger délai de mise en mémoire tampon (toujours bien dans le SLA de 2 secondes si la fenêtre est courte, par exemple, 5 à 10 secondes maximum avant vidage). |
8. Surveillance et alertes
- Suivre le décalage des consommateurs Kafka, le taux de succès/échec de livraison par canal, la latence de bout en bout P50/P95/P99 (ingestion d'événements → livraison à l'appareil), et la profondeur des DLQ.
- Les tableaux de bord SLA alertent l'astreinte si la latence P95 dépasse 2 secondes ou si la croissance des DLQ indique une défaillance systémique de livraison (par exemple, panne du fournisseur FCM), déclenchant une logique de basculement automatique (par exemple, mise en file d'attente et nouvelle tentative avec exponentielle décroissante plutôt que de rejeter).
Résumé
La conception découple la génération des notifications (pilotée par les événements, basée sur Kafka, idempotente) de la livraison (répartiteur multi-canal avec nouvelle tentative/exponentielle décroissante par canal), soutenue par un magasin à colonnes larges optimisé pour le modèle d'accès « historique récent par utilisateur ». Cette séparation permet au système de respecter le SLA de latence de 2 secondes tout en garantissant l'absence de perte de données, et il évolue horizontalement à chaque niveau (ingestion, traitement, stockage, livraison) pour supporter une croissance 10 fois supérieure avec une surcharge opérationnelle et de coût gérable.
Résultat
Votes gagnants
0 / 3
Score moyen
Score total
Commentaire global
La réponse A est une proposition de conception système solide avec des estimations de capacité précises, une architecture événementielle logique, une utilisation appropriée de Kafka, Cassandra/DynamoDB, Redis, WebSockets, APNs/FCM et des fournisseurs d'e-mails, ainsi qu'une bonne couverture de la persistance, des préférences, des nouvelles tentatives, des DLQ, de l'idempotence, de la surveillance et des compromis. Ses principales faiblesses résident dans le fait que certains domaines sont moins précis qu'ils pourraient l'être, tels que la limite de latence exacte, la stratégie HA/DR, les détails de l'API/du chemin de lecture, la priorisation en cas de surcharge et les sémantiques de livraison nuancées autour des fournisseurs externes.
Afficher le détail de l’évaluation ▼
Qualité de l’architecture
Poids 30%La réponse A présente une architecture cohérente avec des producteurs d'événements, Kafka, l'orchestration des notifications, la vérification des préférences, le stockage, les répartiteurs, la livraison WebSocket, les fournisseurs de poussée et les travailleurs d'e-mails. Le flux est logique et complet, bien que certains détails de séparation du chemin de lecture/API et des commandes de canal soient moins explicites.
Complétude
Poids 20%La réponse A couvre les exigences principales : types de notifications, livraison quasi en temps réel, canaux push/e-mail/en application, historique des 100 derniers, préférences, évolutivité, fiabilité, coût, surveillance et modèles de données. Elle est un peu plus légère sur les détails de l'API, la sécurité/confidentialité, la reprise après sinistre et le comportement opérationnel exact en cas de surcharge ou de pannes de fournisseur.
Analyse des compromis
Poids 20%La réponse A inclut un tableau de compromis utile couvrant Kafka vs SQS, Cassandra vs DynamoDB, fan-out-on-write vs fan-out-on-read, stockage à chaud plafonné vs archive, au moins une fois vs exactement une fois, et livraison par lots vs en temps réel. Le raisonnement est solide, bien que certains compromis soient résumés plutôt que profondément liés aux conséquences opérationnelles.
Scalabilité et fiabilité
Poids 20%La réponse A fournit de solides mécanismes d'évolutivité et de fiabilité : réplication et relecture de Kafka, écritures idempotentes, DLQ, nouvelles tentatives, modèle outbox, mise à l'échelle horizontale, partitionnement Cassandra/DynamoDB, mise en cache Redis et mise à l'échelle WebSocket. Elle est moins détaillée sur la récupération multi-régions, la priorisation en cas de surcharge, la discipline des offsets du consommateur, les limites de livraison du fournisseur et les sémantiques exactes des SLO.
Clarté
Poids 10%La réponse A est clairement structurée, facile à suivre et utilise efficacement des diagrammes, des puces, des schémas et un tableau de compromis concis. Elle communique la conception de manière efficace avec une ambiguïté minimale.
Score total
Commentaire global
La réponse A est une proposition de conception soignée et bien structurée qui serait bien reçue lors d'un entretien. Elle maîtrise les calculs de capacité, fournit un diagramme d'architecture lisible, des modèles de données concrets, un tableau des compromis clair et aborde le modèle outbox, l'idempotence, les DLQ et la mise à l'échelle horizontale à tous les niveaux. Ses faiblesses résident dans la profondeur et l'étendue de certains domaines : pas de conception d'API/chemin de lecture, pas de discussion sur la sécurité ou la confidentialité, pas de topologie HA ou de reprise après sinistre, pas d'isolation des priorités entre les classes de notifications, et elle accepte sans critique le SLA de 2 secondes de bout en bout sans noter que les fournisseurs tiers le rendent inapplicable. La suggestion de fan-out-on-read est également un peu mal adaptée à une boîte de réception de notifications.
Afficher le détail de l’évaluation ▼
Qualité de l’architecture
Poids 30%Présente une architecture en couches claire avec un diagramme ASCII, un bus d'événements (Kafka), un orchestrateur, un service de préférences, un répartiteur, une passerelle WebSocket, des workers push/email et un magasin wide-column. Le flux est facile à suivre et les composants sont bien délimités. Légères faiblesses : la discussion sur le fan-out-on-read est quelque peu mal appliquée aux notifications (c'est un concept de flux), et la couche API/chemin de lecture ainsi que la couche API gateway sont à peine abordées malgré leur mention dans la politique de jugement.
Complétude
Poids 20%Couvre l'estimation, les quatre types de notifications, les canaux push/email/in-app, l'historique via une liste limitée plus l'archivage, les préférences avec schéma, les jetons de périphérique, la fiabilité, la scalabilité et la surveillance. Manquant ou faible : conception d'API/chemin de lecture, sécurité et confidentialité, reprise après sinistre et stratégie multi-régions, comptes non lus et topologie HA explicite (répartition AZ).
Analyse des compromis
Poids 20%Un tableau de compromis dédié couvre Kafka vs SQS, Cassandra vs DynamoDB, fan-out à l'écriture vs à la lecture, stockage à chaud vs archive, au moins une fois vs exactement une fois, et batching vs immédiateté. Chaque entrée nomme à la fois l'avantage et le coût, ce qui est clair et lisible. Cependant, les compromis sont principalement conventionnels et énoncés brièvement, avec moins de profondeur sur les limites de cohérence, les garanties d'ordre ou les nuances de définition des SLO.
Scalabilité et fiabilité
Poids 20%Solide : partitionnement et réplication Kafka, acks=all, au moins une fois avec des identifiants idempotents déterministes, DLQ avec backoff exponentiel et jitter, écriture avant livraison, outbox transactionnel avec Debezium, HPA sur le décalage du consommateur, expansion de l'anneau Cassandra, salage des partitions chaudes, et registre de connexions Redis. Manque de HA multi-AZ/multi-régions explicite, de procédures de DR/basculement, de backpressure ou d'isolation des priorités entre les classes de notifications, et de sémantique de commit de décalage.
Clarté
Poids 10%Excellente lisibilité : sections numérotées, un diagramme d'architecture ASCII, des blocs de code pour le modèle de données, un tableau des compromis et un résumé concis. Facile à parcourir et rapide à saisir la conception en un coup d'œil.
Score total
Commentaire global
La réponse A fournit une conception de système très solide et bien structurée. Ses principaux atouts sont sa clarté et son organisation, utilisant un diagramme et un tableau pour rendre les concepts complexes faciles à comprendre. Elle couvre toutes les exigences fondamentales de la requête, proposant une architecture logique avec des choix technologiques appropriés et des stratégies saines pour la scalabilité et la fiabilité. Cependant, elle manque de la profondeur et de l'étendue de la réponse B, en particulier dans des domaines tels que la conception d'API, la sécurité et la planification détaillée de la reprise après sinistre.
Afficher le détail de l’évaluation ▼
Qualité de l’architecture
Poids 30%L'architecture proposée est logique, complète et bien adaptée à la tâche. Elle identifie clairement tous les composants majeurs et leurs interactions, et l'inclusion d'un diagramme facilite grandement la compréhension. Le flux des producteurs d'événements vers les canaux de diffusion est bien défini.
Complétude
Poids 20%La réponse aborde toutes les exigences fonctionnelles et non fonctionnelles spécifiées dans la requête. Elle couvre les aspects principaux de la conception, y compris les estimations, les composants, les modèles de données et les stratégies de mise à l'échelle et de fiabilité.
Analyse des compromis
Poids 20%La réponse discute clairement des compromis clés dans un tableau dédié, ce qui est très efficace. Elle fournit des justifications solides pour des choix tels que Kafka plutôt que SQS et Cassandra plutôt que DynamoDB, démontrant une bonne compréhension des principes impliqués.
Scalabilité et fiabilité
Poids 20%La conception aborde efficacement la scalabilité et la fiabilité. Elle propose des techniques standard et efficaces telles que la mise à l'échelle horizontale pour les services, le partitionnement dans Kafka et Cassandra, et l'utilisation de DLQ et d'idempotence pour la fiabilité. Les stratégies sont saines et bien expliquées.
Clarté
Poids 10%La réponse est exceptionnellement claire et bien organisée. L'utilisation de titres, d'un diagramme de flux et d'un tableau pour les compromis rend la conception complexe facile à suivre et à assimiler. L'écriture est directe et va droit au but.