Vous avez besoin de partitionner une collection de 2 To et de distribuer les données sur toutes vos partitions en moins de 24 heures ? Tirez parti du repartitionnement pour accélérer la migration de vos données !
Avis de non-responsabilité : je suis un employé de MongoDB, mais toutes les opinions et opinions exprimées sont les miennes
Dans cet article, nous aborderons une technique appelée "reshard-to-shard" qui utilise le repartitionnement pour répartir rapidement les données sur les fragments de votre cluster.
Nous passerons en revue :
Si vous débutez dans le sharding ou si vous souhaitez un rappel sur la façon dont MongoDB offre une évolutivité horizontale, veuillez consulter le
Lorsqu'une collection est initialement partitionnée sur un cluster à plusieurs partitions, l'équilibreur commence à migrer les données de la partition qui contient la collection récemment partitionnée vers les autres partitions du cluster afin de répartir équitablement la collection sur les partitions. Lorsque l'équilibreur migre des données, une partition ne peut participer qu'à une seule migration à la fois, quel que soit le nombre de collections à migrer. Cela signifie que dans un cluster à 3 partitions, seules deux partitions à la fois peuvent migrer des données entre elles. Le repartitionnement ne partage pas les mêmes limitations en raison des différences d'exécution internes.
Étant donné que le repartitionnement réécrit toutes les données, il est capable d'écrire des données en parallèle sur tous les fragments du cluster, ce qui augmente le débit et réduit considérablement le temps de migration des données entre les fragments par rapport à ce que l'équilibreur peut accomplir. Le repartitionnement crée une nouvelle collection avec une nouvelle clé de partition en arrière-plan sur chacune des partitions tout en gardant votre collection existante disponible pour que votre application puisse l'utiliser. Une fois que tous les documents sont clonés dans la nouvelle collection, le basculement se produit. La collection existante avec l'ancienne clé de partition est abandonnée au profit de la nouvelle collection construite par l'opération de repartitionnement.
Tout d'abord, c'est beaucoup plus rapide ! En tirant parti du resharding, un client a pu partitionner et distribuer sa collection de 3,5 To sur 4 partitions en 22,5 heures. Le même processus aurait pris 30 jours s'il avait été laissé à la méthode par défaut de l'équilibreur de migrations de blocs.
Deuxièmement, cela a un impact minimal sur votre charge de travail ! Une fois que l'équilibreur a migré les données, il doit effectuer une opération de nettoyage appelée
Troisièmement, vous récupérez automatiquement votre espace disque ! En supprimant l'ancienne collection, cela libère de l'espace de stockage à utiliser par n'importe quelle collection sans avoir à exécuter une opération telle que
Par exemple, un client consommait près de 2,8 To sur shard0 avant que l'opération de repartitionnement de sa plus grande collection ne soit terminée.
Une fois le repartitionnement terminé, 1,9 To d'espace de stockage a été immédiatement restitué ! Ils sont passés de la consommation de 2,7 To de stockage à 873 Go de stockage.
Réponse : Lorsque vous partagez initialement une collection de n'importe quelle taille sur un nombre quelconque de fragments.
Il existe certains scénarios où l'équilibrage peut être plus rapide (par exemple, moins de 100 Go), mais vous devez toujours prendre en compte la suppression de la plage et la récupération du stockage via la synchronisation compacte ou initiale. Par conséquent, si vous en avez la capacité, nous vous recommandons de reshard-to-shard, quelle que soit la taille de la collection que vous souhaitez partitionner.
Vous ne devez pas utiliser la tactique reshard-to-shard lorsque :
La durée des écritures pouvant être bloquées pour la collection en cours de repartitionnement est par défaut de deux secondes, il existe un paramètre configurable qui permet de modifier la durée de blocage.
Pour les scénarios répertoriés ci-dessus, utilisez la méthode traditionnelle de partitionnement d'une collection et de laisser l'équilibreur migrer les données.
Pour exécuter avec succès une opération de repartitionnement, votre cluster doit avoir :
Les clients utilisant Atlas dont le cluster ne répond pas aux exigences de stockage, d'E/S et de CPU pour exécuter le repartitionnement peuvent facilement
Il existe deux étapes très simples pour exécuter une opération de reshard-to-shard :
Pourquoi est-ce que je ferais d'abord un fragment dans une clé de fragment temporaire, et cela ne nuira-t-il pas à mon application ?
Expliquons-nous !
Actuellement, le repartitionnement ne prend pas en charge le repartitionnement dans la même clé de partition (cela réussira en tant que "no-op" car vous êtes déjà dans l'état souhaité). Pour contourner cette limitation, la technique reshard-to-shard nécessite un partitionnement intentionnel dans une clé de partition temporaire différente de la clé de partition souhaitée. En raison de la prise en charge par MongoDB du partage de plage et du partage de hachage, la clé de partition temporaire peut être très légèrement modifiée par rapport à la clé de partition souhaitée que vous avez sélectionnée pour votre collection.
La clé de partition temporaire doit sélectionner une stratégie de partitionnement différente pour un seul de vos champs de clé de partition. En raison des limitations de certaines requêtes telles que updateOne(), updateMany(), deleteOne(), etc. qui nécessitent que la requête inclue la clé de partition, vous utiliserez une stratégie de partitionnement différente. MongoDB utilise des stratégies de partitionnement uniquement pour déterminer comment répartir vos données sur les fragments de votre cluster et ne modifie pas les valeurs du document. Cela signifie que votre application peut utiliser une requête updateOne
ou une autre requête nécessitant la clé de partition avec les deux stratégies de partitionnement.
Par exemple, si la clé de partition souhaitée que vous avez sélectionnée pour votre collection est :
{"_id": "hashed"}
La clé de partition temporaire à utiliser initialement pour votre collection doit être :
{"_id": 1}
Pour les clés de partition composées, un seul de vos champs de clé de partition doit utiliser une stratégie de partitionnement différente. Par exemple, si la clé de partition souhaitée que vous avez sélectionnée pour votre collection est :
{ launch_vehicle: 1, payload: 1}
Votre clé de partition temporaire doit être :
{ launch_vehicle: 1, payload: "hashed"}
La tactique de reshard-to-shard appelle à un repartitionnement immédiat dans la clé de partition que vous utiliserez à long terme après la fin du partitionnement initial de la collection avec la clé de partition temporaire. Cela permet de conserver plus de 99 % des données sur une seule partition pendant l'exécution de l'opération de repartitionnement, ce qui réduit considérablement l'impact des requêtes de diffusion.
Si vous voulez vous assurer que 100 % de vos données se trouvent sur une partition, vous pouvez désactiver l'équilibreur avant de commencer par partitionner la collection avec la clé de partition temporaire. La désactivation de l'équilibreur garantit qu'aucune migration ne se produit avant le début du repartitionnement. Nous verrons comment vous pouvez désactiver l'équilibreur dans l'exemple ci-dessous.
Étant donné que vous aurez créé les deux index pour votre clé de partition temporaire et souhaitée, pendant que l'opération de repartitionnement est en cours, les requêtes utilisant votre clé de partition souhaitée seront performantes car elles peuvent exploiter l'index pour la clé de partition souhaitée pendant que votre collection est temporairement partitionnée. par votre clé de partition temporaire.
La deuxième étape consiste à exécuter une opération de repartitionnement normale, sauf que vous tirez parti d'un effet secondaire du fonctionnement du repartitionnement à votre avantage.
Le repartitionnement comporte quatre phases principales :
Initialisation - la collection en cours de repartitionnement est échantillonnée et une nouvelle distribution de données basée sur la nouvelle clé de partitionnement est déterminée.
Index - l'opération de repartitionnement crée une nouvelle collection partitionnée temporaire vide sur toutes les partitions en fonction de la nouvelle clé de partition et construit les index, y compris les index de clé non partition prenant en charge la collection existante.
Cloner, rattraper et appliquer - les documents sont clonés sur les partitions en fonction de la nouvelle clé de partition, et toutes les modifications apportées aux documents pendant l'exécution de l'opération de repartitionnement sont appliquées.
Commit - la collection temporaire est renommée et prend la place de la collection repartitionnée et la collection désormais ancienne est supprimée.
Après avoir passé en revue les phases ci-dessus, vous pouvez voir comment vous bénéficierez d'un déplacement rapide des données, d'une collection partitionnée équitablement répartie sur vos partitions une fois l'opération terminée et de l'espace de stockage libéré en une seule fois.
Une fois l'opération de repartitionnement terminée, vous pouvez effectuer des opérations de nettoyage telles que la suppression de l'index de clé de partitionnement temporaire et la réduction de votre cluster et/ou de votre stockage pour répondre à vos besoins en régime permanent.
Supposons que vous travaillez sur une application qui suivra les avions commerciaux afin que les clients puissent être informés s'il est probable que leur vol sera retardé. Vous avez étudié les modèles de requête de votre application et passé en revue les attributs qui contribuent à une bonne clé de partition.
La clé de fragment que vous avez sélectionnée pour votre collection est :
{ airline: 1, flight_number: 1, date: "hashed" }
Une fois la clé de partition déterminée, vous pouvez commencer à vérifier les prérequis pour exécuter une opération reshard-to-shard. Tout d'abord, vous générez votre clé de partition temporaire. Comme indiqué précédemment, vous souhaitez que votre clé de partition temporaire soit une version très légèrement modifiée de la clé de partition souhaitée.
Ainsi, la clé de partition temporaire que vous avez sélectionnée est :
{ airline: 1, flight_number: 1, date: 1}
Ensuite, vous créez les index pour prendre en charge les clés de partition temporaires et finales.
Les index peuvent être créés à l'aide du shell Mongo via la commande db.collection.createIndexes()
:
db.flight_tracker.createIndexes([ {"airline": 1, "flight_number": 1, date: "hashed"}, {"airline": 1, "flight_number": 1, date: 1} ])
Les constructions d'index peuvent être surveillées à l'aide de mongo shell via la commande suivante :
db.adminCommand({ currentOp: true, $or: [ { op: "command", "command.createIndexes": { $exists: true } }, { op: "none", "msg" : /^Index Build/ } ] })
Si vous souhaitez vous assurer qu'aucune migration ne se produit entre le partitionnement de votre collection et l'appel du repartitionnement, vous pouvez désactiver l'équilibreur en exécutant la commande suivante :
sh.stopBalancer()
Si votre cluster MongoDB est déployé sur Atlas, vous pouvez utiliser l'interface utilisateur d'Atlas pour consulter facilement les métriques disponibles qui vous informent que votre cluster dispose de suffisamment d'espace de stockage disponible, plus le CPU et la marge d'E/S pour effectuer une opération de repartitionnement.
S'il n'y a pas assez d'espace de stockage disponible ou de marge d'E/S, vous pouvez faire évoluer le stockage. En cas de manque de marge CPU, vous pouvez faire évoluer le cluster. La mise à l'échelle du stockage et du cluster se fait facilement via l'interface utilisateur Atlas.
L'accès à la configuration du cluster est simple et peut être effectué à partir de l'écran de présentation de votre groupe qui affiche tous les clusters déployés pour le groupe.
Une fois toutes les conditions préalables remplies, vous pouvez démarrer la première partie du processus de reshard-to-shard - partitionner la collection avec la clé de partition temporaire.
Vous pouvez ensuite utiliser le shell Mongo et la commande sh.shardCollection() pour partitionner la collection :
sh.shardCollection("main.flight_tracker", { airline: 1, flight_number: 1, date: 1} )
sh.shardCollection() renverra un document une fois terminé, le champ ok aura une valeur de 1 si l'opération a réussi.
{ collectionsharded: 'main.flight_tracker', ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1684160896, i: 25 }), signature: { hash: Binary(Buffer.from("7cb424a56cacd56e47bf155bc036e4a4da4ad6b6", "hex"), 0), keyId: Long("7233411438331559942") } }, operationTime: Timestamp({ t: 1684160896, i: 21 }) }
Une fois la collection partitionnée, vous pouvez immédiatement passer à la repartitionnement de la collection !
Pour reshard votre collection dans la clé de partition souhaitée, utilisez sh.reshardCollection() dans mongo shell :
sh.reshardCollection("main.flight_tracker", { airline: 1, flight_number: 1, date: "hashed" })
Si vous exécutez la commande sh.status() dans mongo shell, vous verrez une nouvelle collection dans la sortie avec le format du nom de la nouvelle collection étant <db_name>.system.resharding.<UUID>
. Il s'agit de la collection que le repartitionnement construit et distribue les données en fonction de la clé de partitionnement souhaitée
Pour surveiller l'état de l'opération de repartitionnement pour la collection flight_tracker, vous pouvez utiliser la commande suivante
db.getSiblingDB("admin").aggregate([ { $currentOp: { allUsers: true, localOps: false } }, { $match: { type: "op", "originatingCommand.reshardCollection": "main.flight_tracker" } } ])
La sortie de la commande vous informera de l'étape à laquelle l'opération de repartitionnement est en cours d'exécution et du temps estimé jusqu'à son achèvement via le champ restantOperationTimeEstimatedSecs. Veuillez noter que le temps estimé de repartage jusqu'à l'achèvement est pessimiste et que les opérations de repartitionnement prennent beaucoup moins de temps que prévu !
L'opération de repartitionnement devrait être presque terminée lorsque la taille des données de chaque partition a augmenté de la taille de la collection repartitionnée divisée par le nombre de partitions dans le cluster. Par exemple, une collection de 1 To étant repartitionnée sur 4 partitions, l'opération de repartitionnement doit être terminée lorsque chaque partition a écrit 250 Go (sans tenir compte des autres données insérées, mises à jour ou supprimées sur la partition).
Si votre cluster est déployé sur Atlas, vous pouvez également surveiller la progression de l'opération de repartitionnement via l'interface utilisateur Atlas à l'aide de l'onglet Métriques du cluster.
Pour les clusters Atlas exécutant MongoDB 6.0 et versions ultérieures, vous pouvez utiliser l'option d'affichage de la taille des données de partition, puis sélectionner une collection avec une syntaxe de <db_name>.system.resharding.<UUID>
. Cette vue isole la collection temporaire et affiche uniquement la croissance de la taille des données de la nouvelle collection.
Pour les clusters Atlas exécutant MongoDB 5.0, vous pouvez utiliser l'option d'affichage de la taille des données logiques db. Cette vue ne permet pas l'isolation au niveau de la collection.
Pendant l'exécution du repartitionnement, les trois métriques du cluster que vous devez principalement surveiller sont :
Si vous craignez que le repartitionnement n'affecte négativement votre cluster, vous pouvez interrompre instantanément l'opération de repartitionnement avant qu'elle n'atteigne la partie validation du processus via la commande suivante :
sh.abortReshardCollection("main.flight_tracker")
Si vous avez désactivé l'équilibreur pour vous assurer qu'aucune migration ne se produit entre le partitionnement et le repartitionnement de la collection, réactivez l'équilibreur maintenant à l'aide de la commande suivante :
sh.startBalancer()
L'opération de repartitionnement lorsqu'elle se termine reviendra si l'opération a réussi ou non au client appelant.
Étant donné que le repartitionnement est une opération de longue durée et que vous avez peut-être fermé cette session de shell Mongo, vous pouvez vérifier si l'opération de partitionnement est toujours en cours d'exécution en utilisant l'agrégation de surveillance du repartitionnement si vous souhaitez des détails ou
Vous pouvez utiliser db.collection.getShardDistribution
pour vérifier si l'opération a réussi :
db.flight_tracker.getShardDistribution()
Si le repartitionnement a réussi, vous devriez voir une sortie où la distribution est égale entre les fragments.
Pour MongoDB 6.0 et supérieur, la régularité est déterminée par la taille des données par partition, vous devriez donc voir une quantité presque égale de données sur chacune des partitions dans la sortie de db.collection.getShardDistribution.
Pour MongoDB 5.0, la régularité est déterminée par le nombre de morceaux par fragment, vous devriez donc voir un nombre égal de morceaux sur chacun des fragments dans la sortie de db.collection.getShardDistribution.
Si votre cluster est déployé sur Atlas, vous pouvez utiliser l'interface utilisateur d'Atlas via l'onglet Métriques pour déterminer si l'opération de repartitionnement a réussi.
Pour les clusters Atlas exécutant MongoDB 6.0 et versions ultérieures, vous pouvez utiliser l'option d'affichage de la taille des données de partition, puis sélectionner votre collection qui a subi un repartitionnement. Vous devriez voir une quantité égale de données par partition affichée.
Pour les clusters Atlas exécutant MongoDB 5.0, vous pouvez utiliser l'option d'affichage des fragments, puis sélectionner votre collection qui a subi un repartitionnement. Vous devriez voir un nombre presque égal de fragments affichés sur tous les fragments de votre cluster.
Pour la taille des données de partition et le nombre de morceaux, l'interface utilisateur d'Atlas affichera une forte augmentation de la métrique pertinente en raison du repartitionnement à l'aide d'un format de nom de collection de <db_name>.system.resharding.<UUID>
temporairement avant de le renommer et de supprimer l'ancien collection avec l'ancienne clé de fragment.
Si le repartitionnement est abandonné, la sortie de db.collection.getShardDistribution
affichera probablement la plupart des données sur le fragment sur lequel la collection a été initialement fragmentée. Les abandons avec repartitionnement sont rares et sont probablement dus au fait que le repartitionnement n'a pas pu effectuer la transition de collecte en 2 secondes ou moins.
Si tel est le cas, nous vous recommandons de programmer le début du repartitionnement afin qu'il tente de s'engager pendant une période de faible trafic pour votre cluster. Alternativement, si votre application peut tolérer un blocage des écritures pendant plus de 2 secondes, vous pouvez modifier le temps de validation en augmentant la durée avec le paramètre reshardingCriticalSectionTimeoutMillis au-delà de la valeur par défaut de 2000 ms.
Une fois l'opération de repartitionnement terminée, vous pouvez effectuer des opérations de nettoyage telles que la suppression de l'index de clé de partition temporaire et la réduction de votre cluster et/ou de votre stockage pour répondre à vos besoins en régime permanent.
Vous pouvez maintenant supprimer l'index temporaire avec la commande db.collection.dropIndex()
dans mongo shell !
db.flight_tracker.dropIndex( {"airline": 1, "flight_number": 1, "date": 1} )
Combien de temps prend le reshard-to-shard ?
Cela dépend de la taille de votre collection, du nombre et de la taille des index de votre collection et du nombre de partitions dans votre cluster, mais nous sommes convaincus que vous pouvez reshard-to-shard une collection de 4 To avec 10 index sur 4 partitions en 48 heures ou moins. Laisser l'équilibreur gérer les migrations prendrait 30 jours ou plus.
Pourquoi le repartitionnement est-il plus rapide que de laisser l'équilibreur migrer les données ?
Les éléments internes de la façon dont l'équilibreur et le repartitionnement migrent les données sont différents. Le repartitionnement lit les documents dans un ordre différent de celui des migrations de blocs et, puisque le repartitionnement se termine par une suppression de l'ancienne collection, il n'y a pas d'attente pour la suppression de la plage pour libérer l'espace disque.
Je souhaite utiliser reshard-to-shard sur une collection qui a une contrainte d'unicité et les index de hachage ne prennent pas en charge l'application de l'unicité.
Si votre collection a une contrainte d'unicité, vous pouvez utiliser reshard-to-shard, mais vous devrez adopter une approche différente. En ajoutant un champ supplémentaire à votre clé de partition temporaire au lieu de modifier la stratégie de partitionnement, vous débloquez la possibilité de repartitionner dans la clé de partition souhaitée. Par exemple, si la clé de partition souhaitée est :
{ launch_vehicle: 1, payload: 1}
Votre clé de partition temporaire serait :
{ launch_vehicle: 1, payload: 1, launch_pad: 1}
Veuillez être conscient des limitations pour les requêtes (ex : updateOne(), updateMany(), deleteOne()) qui nécessitent que la requête inclue la clé de partition. Votre application doit inclure la clé de partition temporaire dans tous les scénarios où une requête requiert l'exécution réussie de la clé de partition jusqu'à ce que l'opération de repartitionnement soit terminée.
Comment surveiller une opération de repartitionnement en cours ?
Exécutez la commande suivante :
db.getSiblingDB("admin").aggregate([ { $currentOp: { allUsers: true, localOps: false } }, { $match: { type: "op", "originatingCommand.reshardCollection": "<database>.<collection>" } } ])
Comment arrêter une opération de repartitionnement en cours ?
Exécutez la commande suivante, qui annule instantanément l'opération de repartitionnement :
sh.abortReshardCollection("<database>.<collection>")
Je crains que le repartitionnement n'affecte les performances de mon cluster.
Si vous répondez aux exigences de repartitionnement décrites précédemment, l'opération ne devrait pas affecter les performances de votre cluster. Toutefois, si votre cluster est déployé sur Atlas, vous pouvez augmenter temporairement votre cluster lors de l'exécution d'une opération de reshard-to-shard et réduire à nouveau le cluster une fois l'opération terminée.
Quelles métriques de mon cluster dois-je surveiller pendant l'exécution de l'opération de repartitionnement ?
Espace de stockage disponible - si l'un de vos fragments dispose de moins de 100 Go de stockage disponible, vous devez abandonner le repartitionnement
Utilisation du processeur - si votre cluster consomme toutes les ressources de calcul disponibles, vous pouvez provoquer un conflit de ressources et devez abandonner le repartitionnement
Consommation d'E/S - si votre cluster consomme toutes les IOPS disponibles, vous pouvez provoquer un conflit de ressources et devez abandonner le repartitionnement.
La collection temporaire est apparemment répartie uniformément sur tous mes fragments, pourquoi le repartitionnement n'est-il pas terminé ?
Avant que le repartitionnement puisse passer à la collection avec la clé de partitionnement souhaitée, il doit
Mon application ne supporte pas que les écritures soient bloquées pendant plus d'une seconde, puis-je modifier la durée pendant laquelle les écritures seront bloquées sur la collection repartitionnée ?
Oui, mais nous ne nous attendons pas à ce que cette option soit souvent utilisée car l'opération de repartitionnement par défaut tentera de basculer vers la nouvelle collection lorsqu'elle estime que le basculement peut être effectué en moins d'une seconde. Si votre application ne peut pas supporter que les écritures soient bloquées dans la collection repartitionnée pendant 2 secondes, vous pouvez modifier le paramètre reshardingCriticalSectionTimeoutMillis pour bloquer les écritures pendant seulement 1 seconde.
Exécutez la commande suivante :
db.adminCommand({ setParameter: 1, reshardingCriticalSectionTimeoutMillis: 1000 })
Photo de titre par panumas nikhomkhai trouvée sur Pexels.