Architecture systèmeArchitecte Système

Concevoir un plan de contrôle des métadonnées tolérant aux pannes et fortement cohérent pour un système de stockage d'objets distribué à l'échelle mondiale qui indexe des milliards d'objets sur des niveaux de stockage hétérogènes, gère des modifications de namespace concurrentes avec un contrôle de concurrence optimiste et garantit une cohérence linéarisable avec une latence de lecture inférieure à une milliseconde pour les chemins chauds ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse à la question

L'évolution du stockage d'objets a évolué des bases de données de métadonnées centralisées, telles que les premières implémentations Ceph et Swift, vers des architectures distribuées capables d'hyperscalabilité. Cette transition a introduit une tension fondamentale entre le besoin de sémantique de type POSIX (renommages atomiques, sérialisation stricte) et la scalabilité horizontale requise pour gérer des milliards de clés à travers des niveaux SSD, HDD et Tape. Le problème central réside dans la coordination des mutations concurrentes à travers des nœuds distribués sans encourir les pénalités de latence des protocoles traditionnels de commit en deux phases (2PC) ou de consensus global basé sur Paxos.

La solution nécessite une architecture de consensus fragmentée où chaque fragment régule une partition de namespace spécifique en utilisant le protocole Raft pour garantir une cohérence linéarisable à l'intérieur de cette limite. Une couche de routeur de métadonnées dirige les requêtes des clients en fonction des préfixes de répertoire, utilisant l'hachage cohérent pour maintenir la localité des requêtes d'intervalle tout en permettant une scalabilité horizontale. Pour la performance, les métadonnées chaudes résident dans un cache par niveaux comprenant Redis pour L1 et RocksDB local pour la persistance L2, tandis que les métadonnées froides sont compactées dans des fichiers Apache Parquet sur S3 pour réduire les coûts de stockage sans sacrifier la durabilité.

Situation vécue

Une entreprise de médias migrer de AWS S3 vers un cloud hybride privé a dû gérer 2 milliards de segments vidéo avec des métadonnées suivant les profils d'encodage, les clés DRM, et les états de cycle de vie. Leur architecture initiale utilisait MongoDB avec un sharding automatique, qui souffrait de pics de latence imprévisibles (100-500ms) lors des migrations de chunks et manquait de transactions atomiques inter-fragments, provoquant des corruptions de données pendant les déplacements de dossiers simultanés.

Solution 1 : CockroachDB (SQL Distribué)

Cette approche offrait une scalabilité horizontale native et une isolation sérialisable grâce à une architecture de type Google Spanner. L'avantage principal était l'interface SQL familière pour les requêtes analytiques complexes sur les métadonnées. Cependant, le système présentait une forte amplification des écritures (3-5x) en raison de la réplication de consensus multi-régions, et la latence dépassait systématiquement 20 ms lors des uploads de contenu viral lorsque la contention d'écriture atteignait son maximum. Les coûts de licence pour un stockage de métadonnées à l'échelle pétaoctet se sont révélés économiquement irréalistes pour l'organisation.

Solution 2 : Apache Cassandra avec Transactions Légères (LWT)

Cassandra offrait un débit d'écriture massif et une cohérence réglable, avec des LWTs basés sur Paxos offrant des opérations linéarisables. La technologie excellait à ingérer des flux de métadonnées à haute vitesse sans points de défaillance uniques. Malheureusement, la latence Paxos avait une moyenne de 15 ms et dégradait considérablement sous un accès concurrent, tandis que l'indexation secondaire pour les requêtes "lister par date de téléchargement" nécessitait des scans complets de table coûteux, la rendant inadaptée aux expériences utilisateur interactives.

Solution 3 : Shard par Dossier avec Raft

Ce design mappait chaque dossier utilisateur à un groupe de consensus Raft dédié, garantissant que les opérations au sein d'un canal (l'unité d'accès principale) étaient linéarisables et rapides grâce à l'accès au disque local. L'architecture supportait des renommages atomiques via des transactions locales à un fragment sans coordination inter-réseau. Bien que cela introduisait de la complexité dans la logique de resharding pour les répertoires viraux (points chauds) et nécessitait une bibliothèque de routage côté client sophistiquée, cela correspondait parfaitement au motif de charge de travail où le contenu vidéo se partitionnait naturellement par créateur.

Résultat : Le système a réussi à soutenir 80 000 opérations de métadonnées par seconde lors d'événements viraux avec une latence P99 inférieure à 3 ms. Des politiques de tiering automatisées ont déplacé 90 % du contenu vieillissant vers du stockage froid, réduisant les coûts d'infrastructure totaux de 60 % tout en maintenant des garanties de cohérence strictes pour le contenu actif.

Ce que les candidats manquent souvent

Comment évitez-vous les problèmes de troupeau tonnerre sur le cache de métadonnées lorsqu'un objet populaire expire ou est mis à jour ?

Les candidats suggèrent souvent une simple expiration basée sur TTL sans tenir compte de la protection contre les stampedes. L'approche correcte met en œuvre un cache basé sur des baux où les entrées de cache portent des jetons de bail à courte durée de vie, s'assurant que seul le détenteur du bail rafraîchit à partir du backend tandis que les autres attendent ou servent des données obsolètes brièvement. Combinez cela avec une expiration précoce probabiliste (ajout de fluctuations aléatoires aux TTL) et le modèle singleflight (tel qu'implémenté dans le package singleflight de Go) pour regrouper les requêtes identiques concurrentes en une seule requête backend, empêchant la surcharge de la base de données lors des manques de cache.

Quelle stratégie garantit la cohérence des métadonnées pendant une opération de division de fragment en direct (resharding) sans temps d'arrêt du cluster ?

Beaucoup proposent d'arrêter les écritures pendant la migration, ce qui viole les exigences de disponibilité. La technique appropriée utilise l'indexation d'ombre et des protocoles d'écriture double. Tout d'abord, instancier le nouveau fragment cible comme une réplique en retard du fragment source en utilisant l'expédition de journaux Raft. Une fois synchronisé, changez le chemin d'écriture vers le nouveau fragment tout en maintenant un journal de tombstone dans l'ancien fragment pendant une période de grâce pour gérer les lectures en retard. Un service de coordination comme etcd met à jour atomiquement la table de routage, tandis que des horodatages MVCC garantissent que les lectures pendant la transition voient des instantanés cohérents, rejetant les demandes qui traversent la frontière de division jusqu'à ce que la coupure atomique soit terminée.

Comment conciliez-vous l'index des métadonnées avec la couche de stockage physique lorsque la collecte des ordures asynchrone ou les migrations de tiering échouent silencieusement ?

Cela nécessite une approche orientée événements avec des modèles de Saga pour des transactions distribuées. Le service de métadonnées émet des événements de domaine (par exemple, "TieringInitiated") à un journal Apache Kafka, le consommateur de stockage confirmant le traitement réussi via des rappels idempotents. Si la couche de stockage échoue à migrer l'objet dans un délai spécifié, le service de métadonnées reçoit un événement de délai de Saga et déclenche une transaction de compensation pour ramener l'état des métadonnées à "CHAUD". De plus, implémentez un scanner de réconciliation en arrière-plan utilisant des arbres de Merkle pour identifier efficacement les divergences entre les métadonnées et les requêtes HEAD de stockage physique, réparant les incohérences sans nécessiter des scans complets de table.