Concevoir une stratégie de partitionnement scalable pour le stockage Table Azure

Cet article décrit le partitionnement d’une table dans le stockage Table Azure et les stratégies que vous pouvez utiliser pour garantir une scalabilité efficace.

Azure fournit un stockage cloud hautement disponible et hautement évolutif. Le système de stockage sous-jacent pour Azure est fourni par le biais d’un ensemble de services, notamment stockage Blob Azure, Stockage Table Azure, Stockage File d’attente Azure et Azure Files.

Le stockage Table Azure est conçu pour stocker des données structurées. Le service Stockage Azure prend en charge un nombre illimité de tables. Chaque table peut être mise à l’échelle à des niveaux massifs et fournir des téraoctets de stockage physique. Pour tirer le meilleur parti des tables, vous devez partitionner vos données de manière optimale. Cet article explore les stratégies que vous pouvez utiliser pour partitionner efficacement des données pour le stockage Table Azure.

Entités de table

Les entités de table représentent les unités de données stockées dans une table. Les entités de table sont similaires aux lignes d’une table de base de données relationnelle classique. Chaque entité définit une collection de propriétés. Chaque propriété est définie en tant que paire clé/valeur par son nom, sa valeur et le type de données de la valeur. Les entités doivent définir les trois propriétés système suivantes dans le cadre de la collection de propriétés :

  • PartitionKey : la propriété PartitionKey stocke des valeurs de chaîne qui identifient la partition à laquelle appartient une entité. Les partitions, comme nous l’aborderons plus loin, font partie intégrante de la scalabilité de la table. Les entités qui ont la même valeur PartitionKey sont stockées dans la même partition.

  • RowKey : la propriété RowKey stocke des valeurs de chaîne qui identifient de manière unique les entités au sein de chaque partition. PartitionKey et RowKey forment ensemble la clé primaire de l’entité.

  • Timestamp : la propriété Timestamp fournit la traçabilité pour une entité. Un horodatage est une valeur de date/heure qui vous indique l’heure de la dernière modification de l’entité. Un horodatage est parfois appelé version de l’entité. Les modifications apportées aux horodatages sont ignorées, car le service de table conserve la valeur de cette propriété pendant toutes les opérations d’insertion et de mise à jour.

Clé primaire de table

La clé primaire d’une entité Azure se compose des propriétés PartitionKey et RowKey combinées . Les deux propriétés forment un index cluster unique dans la table. Les propriétés PartitionKey et RowKey peuvent stocker jusqu’à 1 Kio de valeurs de chaîne. Les chaînes vides sont également autorisées ; Toutefois, les valeurs Null ne sont pas autorisées.

L’index cluster trie par partitionKey dans l’ordre croissant, puis par RowKey dans l’ordre croissant. L'ordre de tri est appliqué dans les réponses aux requêtes. Des comparaisons lexicales sont utilisées lors de l'opération de tri. Une valeur de chaîne de « 111 » s’affiche avant la valeur de chaîne « 2 ». Dans certains cas, vous pouvez souhaiter que l’ordre de tri soit numérique. Pour effectuer un tri dans un ordre numérique et croissant, vous devez utiliser des chaînes de longueur fixe et sans rembourrage. Dans l’exemple précédent, « 002 » apparaît avant « 111 ».

Partitions de table

Les partitions représentent une collection d’entités avec les mêmes valeurs PartitionKey . Les partitions sont toujours servies à partir d’un serveur de partition. Chaque serveur de partition peut servir une ou plusieurs partitions. Un serveur de partitions a une limite quant au nombre d'entités qu'il peut traiter pour une partition pendant une période donnée. Plus précisément, une partition a une cible d’extensibilité de 2 000 entités par seconde. Ce débit peut être plus élevé pendant une charge minimale sur le nœud de stockage, mais il est limité lorsque le nœud devient actif ou actif.

Pour mieux illustrer le concept de partitionnement, la figure suivante montre un tableau qui contient un petit sous-ensemble de données pour les inscriptions aux événements de course au pied. La figure présente une vue conceptuelle du partitionnement où partitionKey contient trois valeurs différentes : le nom de l’événement combiné à trois distances (marathon complet, semi-marathon et 10 km). Cet exemple utilise deux serveurs de partition. Le serveur A contient les inscriptions pour les distances de demi-marathon et de 10 km. Le serveur B contient uniquement les distances complètes du marathon. Les valeurs RowKey sont affichées pour fournir un contexte, mais les valeurs ne sont pas significatives pour cet exemple.

Diagramme montrant une table qui a trois partitions
Une table avec trois partitions

Extensibilité

Comme une partition est toujours traitée depuis un seul serveur de partitions et que chaque serveur de partitions peut traiter une ou plusieurs partitions, l'efficacité du traitement des entités est liée à l'intégrité du serveur. Les serveurs qui rencontrent un trafic élevé pour leurs partitions peuvent ne pas être en mesure de supporter un débit élevé. Par exemple, dans la figure précédente, si de nombreuses demandes pour « 2011 New York City Marathon__Half » sont reçues, le serveur A peut devenir trop chaud. Pour augmenter le débit du serveur, le système de stockage équilibre la charge des partitions avec d'autres serveurs. Le résultat est que trafic est distribué entre de nombreux autres serveurs. Pour optimiser l’équilibrage de charge du trafic, vous devez utiliser davantage de partitions afin que le stockage Table Azure puisse distribuer les partitions à d’autres serveurs de partition.

Transactions de groupe d’entités

Une transaction de groupe d’entités est un ensemble d’opérations de stockage implémentées atomiquement sur des entités qui ont la même valeur PartitionKey . Si une opération de stockage dans le groupe d’entités échoue, toutes les opérations de stockage dans l’entité sont restaurées. Une transaction de groupe d’entités ne comprend pas plus de 100 opérations de stockage et peut avoir une taille maximale de 4 Mio. Les transactions de groupe d’entités fournissent au stockage Table Azure une forme limitée de la sémantique ACID (atomicité, cohérence, isolation et durabilité) fournie par les bases de données relationnelles.

Les transactions de groupe d’entités améliorent le débit, car elles réduisent le nombre d’opérations de stockage individuelles qui doivent être soumises au stockage Table Azure. Les transactions de groupe d’entités offrent également un avantage économique. Une transaction de groupe d’entités est facturée en tant qu’opération de stockage unique, quel que soit le nombre d’opérations de stockage qu’elle contient. Étant donné que toutes les opérations de stockage dans une transaction de groupe d’entités affectent les entités qui ont la même valeur PartitionKey , la nécessité d’utiliser des transactions de groupe d’entités peut entraîner la sélection de la valeur PartitionKey .

Partitions de plage

Si vous utilisez des valeurs PartitionKey uniques pour vos entités, chaque entité appartient à sa propre partition. Si les valeurs uniques que vous utilisez augmentent ou diminuent en valeur, il est possible qu’Azure crée des partitions de plage. Les partitions de plage regroupent des entités qui ont des valeurs PartitionKey uniques et séquentielles pour améliorer les performances des requêtes de plage. Sans partitions de plage, une requête de plage doit franchir les limites de partition ou les limites du serveur, ce qui peut réduire les performances des requêtes. Prenons l’exemple d’une application qui utilise le tableau suivant, qui a une valeur de séquence croissante pour PartitionKey :

PartitionKey RowKey
"0001" -
"0002" -
"0003" -
"0004" -
"0005" -
"0006" -

Azure peut regrouper les trois premières entités dans une partition de plage. Si vous appliquez une requête de plage à la table qui utilise partitionKey comme critères et demande des entités comprises entre « 0001 » et « 0003 », la requête peut s’exécuter efficacement, car les entités sont servies à partir d’un serveur de partition unique. Il n’y a aucune garantie quand et comment une partition de plage sera créée.

L’existence de partitions de plage pour votre table peut affecter les performances de vos opérations d’insertion si vous insérez des entités qui ont des valeurs PartitionKey croissantes ou décroissantes. L’insertion d’entités qui ont des valeurs PartitionKey croissantes est appelée modèle d’ajout uniquement. L’insertion d’entités qui ont des valeurs décroissantes est appelée modèle prépend-only. N’utilisez pas ces types de modèles, car le débit global de vos demandes d’insertion est limité par un serveur à partition unique. Cela est dû au fait que, si des partitions de plage existent, les première et dernière partitions (plage) contiennent respectivement les valeurs PartitionKey les moins élevées et les plus élevées. Par conséquent, l’insertion d’une nouvelle entité, dont la valeur PartitionKey est séquentiellement inférieure ou supérieure, cible l’une des partitions de fin. La figure suivante montre un ensemble possible de partitions de plage basées sur l’exemple précédent. Si un ensemble d’entités « 0007 », « 0008 » et « 0009 » était inséré, elles seraient affectées à la dernière partition (orange).

Diagramme montrant un ensemble de partitions de plage
Ensemble de partitions de plage

Il est important de noter qu’il n’y a aucun effet négatif sur les performances si les opérations d’insertion utilisent des valeurs PartitionKey qui sont plus dispersées.

Analyser des données

Contrairement à une table d’une base de données relationnelle que vous pouvez utiliser pour gérer les index, les tables du stockage Table Azure ne peuvent avoir qu’un seul index. Un index dans le stockage Table Azure se compose toujours des propriétés PartitionKey et RowKey .

Dans une table Azure, vous n’avez pas le luxe d’optimiser les performances de votre table en ajoutant d’autres index ou en modifiant une table existante après son déploiement. Vous devez analyser les données lors de la conception de votre table. Les aspects les plus importants à prendre en compte pour une scalabilité optimale et pour l’efficacité des requêtes et des insertions sont les valeurs PartitionKey et RowKey . Cet article met l’accent sur le choix de partitionKey , car il est directement lié à la façon dont les tables sont partitionnés.

Dimensionnement des partitions

Le dimensionnement des partitions est relatif au nombre d'entités que contient une partition. Comme nous l’avons vu dans Scalabilité, le fait d’avoir plus de partitions vous permet d’obtenir un meilleur équilibrage de charge. La granularité de la valeur PartitionKey affecte la taille des partitions. Au niveau le plus grosseur, si une seule valeur est utilisée comme PartitionKey, toutes les entités se trouvent dans une seule partition qui est très grande. Au niveau le plus élevé de granularité, partitionKey peut contenir des valeurs uniques pour chaque entité. Le résultat est qu’il existe une partition pour chaque entité. Le tableau suivant présente les avantages et les inconvénients de la plage de granularités :

Granularité de PartitionKey Taille de partition Avantages Inconvénients
Valeur unique Petit nombre d'entités Les transactions par lots sont possibles avec n’importe quelle entité.

Toutes les entités sont locales et servies à partir du même nœud de stockage.
Valeur unique Grand nombre d'entités Les transactions de groupe d’entités peuvent être possibles avec n’importe quelle entité. Pour plus d’informations sur les limites des transactions de groupe d’entités, consultez Exécution de transactions de groupe d’entités. L'évolutivité est limitée.

Le débit est limité par la performance d'un seul serveur.
Valeurs multiples Partitions multiples

Les tailles de partition dépendent de la distribution des entités.
Les transactions par lots sont possibles sur certaines entités.

Le partitionnement dynamique est possible.

Les requêtes à requête unique sont possibles (aucun jeton de continuation).

L’équilibrage de charge sur d’autres serveurs de partition est possible.
Une distribution très inégale des entités entre les partitions peut limiter les performances des partitions plus grandes et plus actives.
Valeurs uniques De nombreuses petites partitions La table est hautement évolutive.

Les partitions de plage peuvent améliorer les performances des requêtes de plage entre partitions.
Les requêtes qui impliquent des plages peuvent nécessiter des visites sur plusieurs serveurs.

Les transactions par lots ne sont pas possibles.

Les modèles d’ajout uniquement ou d’ajout uniquement peuvent affecter le débit d’insertion.

Le tableau montre comment la mise à l’échelle est affectée par les valeurs PartitionKey . Il est recommandé de privilégier les partitions plus petites, car elles offrent un meilleur équilibrage de charge. Les partitions plus volumineuses peuvent être appropriées dans certains scénarios, et elles ne sont pas nécessairement désavantageuses. Par exemple, si votre application ne nécessite pas de scalabilité, une seule grande partition peut être appropriée.

Déterminer les requêtes

Les requêtes récupèrent des données dans les tables. Lorsque vous analysez les données d’une table dans le stockage Table Azure, il est important de prendre en compte les requêtes que l’application utilisera. Si une application a plusieurs requêtes, vous devrez peut-être les hiérarchiser, bien que vos décisions puissent être subjectives. Dans de nombreux cas, les requêtes dominantes sont discernables des autres requêtes. En termes de performance, les requêtes peuvent être classées en différentes catégories. Étant donné qu’une table n’a qu’un seul index, les performances des requêtes sont généralement liées aux propriétés PartitionKey et RowKey . Le tableau suivant présente les différents types de requêtes et leurs performances :

Type de requête Correspondance partitionkey Correspondance rowKey Évaluation des performances
Analyse des plages de valeurs des lignes Exact Partial Mieux avec des partitions de plus petite taille.

Mauvais avec des partitions qui sont très volumineuses.
Analyse des plages de valeurs des partitions Partiel Partiel Bon avec un petit nombre de serveurs de partition touchés.

Pire avec plus de serveurs touchés.
Analyse complète de la table Partielle, aucune Partielle, aucune Pire avec un sous-ensemble de partitions en cours d’analyse.

Pire avec toutes les partitions analysées.

Notes

Le tableau définit les évaluations de la performance les unes par rapport aux autres. Le nombre et la taille des partitions peuvent finalement déterminer le fonctionnement de la requête. Par exemple, une analyse de plage de partitions pour une table qui comporte de nombreuses partitions volumineuses peut être médiocre par rapport à une analyse de table complète pour une table qui a quelques petites partitions.

Les types de requêtes répertoriés dans le tableau précédent montrent une progression des meilleurs types de requêtes à utiliser vers les pires types, en fonction de leurs évaluations de performances. Les requêtes univoques sont les meilleurs types de requêtes à utiliser, car elles utilisent entièrement l'index cluster de la table. La requête point suivante utilise les données de la table d’inscription des courses de pieds :

http://<account>.windows.core.net/registrations(PartitionKey=”2011 New York City Marathon__Full”,RowKey=”1234__John__M__55”)  
  

Si l'application utilise plusieurs requêtes, elles ne peuvent pas toutes être des requêtes univoques. En termes de performance, les requêtes de plages de données viennent après les requêtes univoques. Il existe deux types de requêtes de plage : l’analyse de plage de lignes et l’analyse de plage de partitions. L'analyse de plage de valeurs de lignes spécifie une seule partition. Étant donné que l’opération se produit sur un serveur à partition unique, les analyses de plages de lignes sont généralement plus efficaces que les analyses de plage de partitions. Toutefois, un facteur clé dans les performances des analyses de plage de lignes est la façon dont une requête est sélective. La sélectivité de la requête détermine combien de lignes doivent être parcourues pour trouver les lignes en correspondance. Des requêtes plus sélectives sont plus efficaces lors des analyses de plage de valeurs de lignes.

Pour évaluer les priorités de vos requêtes, tenez compte des exigences de fréquence et de temps de réponse pour chaque requête. Les requêtes fréquemment exécutées peuvent être hiérarchisées plus haut. Toutefois, une requête importante mais rarement utilisée peut avoir des exigences de latence faible qui pourraient la classer plus haut dans la liste des priorités.

Choisir la valeur PartitionKey

Le cœur de la conception d’une table est sa scalabilité, les requêtes utilisées pour y accéder et les exigences en matière d’opérations de stockage. Les valeurs PartitionKey que vous choisissez déterminent la façon dont une table est partitionnée et le type de requêtes que vous pouvez utiliser. Les opérations de stockage, et en particulier les insertions, peuvent également affecter votre choix de valeurs PartitionKey . Les valeurs PartitionKey peuvent aller de valeurs uniques à valeurs uniques. Ils peuvent également être créés à l’aide de plusieurs valeurs. Vous pouvez utiliser les propriétés d’entité pour former la valeur PartitionKey . Ou l’application peut calculer la valeur. Les sections suivantes traitent des considérations importantes.

Transactions de groupe d’entités

Les développeurs doivent d’abord déterminer si l’application utilisera des transactions de groupe d’entités (mises à jour par lots). Les transactions de groupe d’entités nécessitent que les entités aient la même valeur PartitionKey . En outre, étant donné que les mises à jour par lots concernent l’ensemble d’un groupe, les choix de valeurs PartitionKey peuvent être limités. Par exemple, une application bancaire qui gère des transactions en espèces doit les insérer dans la table selon le principe d'atomicité. Les transactions en espèces représentent à la fois le côté débit et le côté crédit et doivent être nettes à zéro. Cette exigence signifie que le numéro de compte ne peut pas être utilisé comme partie de la valeur PartitionKey , car chaque côté de la transaction utilise des numéros de compte différents. Au lieu de cela, un ID de transaction peut être un meilleur choix.

Partitions

Les nombres et tailles de partition affectent la scalabilité d’une table en cours de chargement. Elles sont également contrôlées par la précision des valeurs PartitionKey . Il peut être difficile de déterminer partitionKey en fonction de la taille de la partition, en particulier si la distribution des valeurs est difficile à prédire. Une bonne règle est d'utiliser plusieurs partitions de plus petite taille. De nombreuses partitions de table permettent au stockage Table Azure de gérer plus facilement les nœuds de stockage à partir duquel les partitions sont traitées.

Le choix de valeurs uniques ou plus fines pour PartitionKey entraîne des partitions plus petites, mais plus volumineuses. Cela est généralement favorable, car le système peut équilibrer la charge des nombreuses partitions pour répartir la charge sur de nombreuses partitions. Vous devez cependant considérer l'effet de ces nombreuses partitions sur les requêtes de plage de données interpartitions. Ces types de requêtes doivent visiter plusieurs partitions pour satisfaire une requête. Il est possible que les partitions soient distribuées sur de nombreux serveurs de partition. Si une requête dépasse la limite d'un serveur, des jetons de continuation doivent être retournés. Les jetons de continuation spécifient les valeurs PartitionKey ou RowKey suivantes pour récupérer le jeu de données suivant pour la requête. En d’autres termes, les jetons de continuation représentent au moins une demande supplémentaire adressée au service, ce qui peut dégrader les performances globales de la requête.

La sélectivité d'une requête est un autre facteur qui peut affecter sa performance. La sélectivité d'une requête est une mesure du nombre de lignes qui doivent être parcourues pour chaque partition. Plus une requête est sélective, plus la requête est efficace pour renvoyer les lignes souhaitées. Les performances globales des requêtes de plage peuvent dépendre du nombre de serveurs de partition à toucher ou de la sélection de la requête. Vous devez également éviter d’utiliser les modèles append-only ou prepend-only lorsque vous insérez des données dans votre table. Si vous utilisez ces modèles, malgré la création de partitions petites et nombreuses, vous pouvez limiter le débit de vos opérations d’insertion. Les modèles append-only et prepend-only sont abordés dans Partitions de plage.

Requêtes

Connaître les requêtes que vous allez utiliser peut vous aider à déterminer quelles propriétés sont importantes à prendre en compte pour la valeur PartitionKey . Les propriétés que vous utilisez dans les requêtes sont des candidats pour la valeur PartitionKey . Le tableau suivant fournit des instructions générales sur la façon de déterminer la valeur PartitionKey :

Si l'entité… Action
A une seule propriété clé Utilisez-la comme PartitionKey.
A deux propriétés clés Utilisez l’une comme PartitionKey et l’autre comme RowKey.
A plus de deux propriétés clés Utilisez une clé composite de valeurs concaténées.

S’il existe plusieurs requêtes tout aussi dominantes, vous pouvez insérer les informations plusieurs fois à l’aide des différentes valeurs RowKey dont vous avez besoin. Votre application gère les lignes secondaires (ou tertiaires, etc.). Vous pouvez utiliser ce type de modèle pour répondre aux exigences de performances de vos requêtes. L’exemple suivant utilise les données de l’exemple d’inscription à la course de pied. Il a deux requêtes dominantes :

  • Requête par numéro de dossard
  • Requête par âge

Pour traiter ces deux requêtes principales, insérez deux lignes comme transaction de groupe d'entités. Le tableau suivant montre les propriétés PartitionKey et RowKey pour ce scénario. Les valeurs RowKey fournissent un préfixe pour le dossard et l’âge afin que l’application puisse faire la distinction entre les deux valeurs.

PartitionKey RowKey
Marathon de New York 2011__Complet BIB:01234__John__M__55
Marathon de New York 2011__Complet AGE:055__1234__John__M

Dans cet exemple, une transaction de groupe d’entités est possible, car les valeurs PartitionKey sont identiques. La transaction de groupe fournit l’atomicité de l’opération d’insertion. Bien qu’il soit possible d’utiliser ce modèle avec des valeurs PartitionKey différentes, nous vous recommandons d’utiliser les mêmes valeurs pour obtenir cet avantage. Sinon, vous devrez peut-être écrire une logique supplémentaire pour garantir les transactions atomiques qui utilisent des valeurs PartitionKey différentes.

Opérations de stockage

Les tables du stockage Table Azure peuvent être chargées non seulement à partir de requêtes. Ils peuvent également être chargés à partir d’opérations de stockage telles que les insertions, les mises à jour et les suppressions. Déterminez le type d’opérations de stockage que vous allez effectuer sur la table et à quelle vitesse. Si vous effectuez ces opérations rarement, vous n’avez peut-être pas besoin de vous en soucier. Toutefois, pour les opérations fréquentes telles que l’exécution de nombreuses insertions dans un court laps de temps, vous devez tenir compte de la façon dont ces opérations sont traitées à la suite des valeurs PartitionKey que vous choisissez. Les modèles append-only et prepend-only sont des exemples importants. Les modèles append-only et prepend-only sont abordés dans Partitions de plage.

Lorsque vous utilisez un modèle d’ajout uniquement ou de prépendation uniquement, vous utilisez des valeurs ascendantes ou décroissantes uniques pour partitionKey lors des insertions suivantes. Si vous combinez ce modèle avec des opérations d’insertion fréquentes, votre table ne sera pas en mesure de traiter les opérations d’insertion avec une grande scalabilité. La scalabilité de votre table est affectée, car Azure ne peut pas équilibrer la charge des demandes d’opération vers d’autres serveurs de partition. Dans ce cas, vous pouvez envisager d’utiliser des valeurs aléatoires, telles que des valeurs GUID. Ensuite, vos tailles de partition peuvent rester petites et maintenir l’équilibrage de charge pendant les opérations de stockage.

Test de contrainte de partition de table

Lorsque la valeur PartitionKey est complexe ou nécessite des comparaisons avec d’autres mappages PartitionKey , vous devrez peut-être tester les performances de la table. Le test doit examiner le fonctionnement d'une partition sous des charges intenses.

Pour effectuer un test de contrainte

  1. Créez une table de test.
  2. Chargez la table de test avec des données afin qu’elle contienne des entités qui ont la valeur PartitionKey que vous allez cibler.
  3. Utilisez l’application pour simuler le pic de charge dans la table. Ciblez une partition unique à l’aide de la valeur PartitionKey de l’étape 2. Cette étape est différente pour chaque application, mais la simulation doit inclure toutes les requêtes et opérations de stockage requises. Vous devrez peut-être ajuster l’application afin qu’elle cible une seule partition.
  4. Examinez le débit des opérations GET ou PUT sur la table.

Pour examiner le débit, comparez les valeurs réelles à la limite spécifiée d'une seule partition sur un seul serveur. Les partitions sont limitées à 2 000 entités par seconde. Si le débit dépasse 2 000 entités par seconde pour une partition, le serveur peut s’exécuter trop à chaud dans un paramètre de production. Dans ce cas, les valeurs PartitionKey peuvent être trop grossières, de sorte qu’il n’y a pas assez de partitions ou que les partitions sont trop volumineuses. Vous devrez peut-être modifier la valeur PartitionKey afin que les partitions soient distribuées entre d’autres serveurs.

Équilibrage de la charge

L’équilibrage de charge au niveau de la couche de partition se produit lorsqu’une partition devient trop chaude. Lorsqu’une partition est trop chaude, la partition, en particulier le serveur de partition, fonctionne au-delà de sa scalabilité cible. Pour le stockage Azure, chaque partition a une cible de scalabilité de 2 000 entités par seconde. L’équilibrage de charge se produit également au niveau de la couche Système de fichiers distribué (DFS).

L’équilibrage de charge au niveau de la couche DFS traite de la charge d’E/S et n’entre pas dans le cadre de cet article. L’équilibrage de charge au niveau de la couche de partition ne se produit pas immédiatement après le dépassement de la cible de scalabilité. Au lieu de cela, le système attend quelques minutes avant de commencer le processus d’équilibrage de charge. Ceci permet de garantir qu'une partition est réellement soumise à une charge intense. Il n’est pas nécessaire d’amorcer des partitions avec une charge générée qui déclenche l’équilibrage de charge, car le système effectue automatiquement la tâche.

Si une table a été amorcée avec une certaine charge, le système peut être en mesure d’équilibrer les partitions en fonction de la charge réelle, ce qui entraîne une distribution sensiblement différente des partitions. Au lieu d’amorcer des partitions, envisagez d’écrire du code qui gère les erreurs de délai d’attente et d’occupation du serveur. Les erreurs sont retournées lorsque le système est en cours d’équilibrage de charge. En gérant ces erreurs à l’aide d’une stratégie de nouvelle tentative, votre application peut mieux gérer les pics de charge. Les stratégies de nouvelle tentative sont présentées plus en détails dans la section suivante.

Lorsque l’équilibrage de charge se produit, la partition devient hors connexion pendant quelques secondes. Pendant la période hors connexion, le système réaffecte la partition à un autre serveur de partition. Il est important de noter que vos données ne sont pas stockées par les serveurs de partition. Au lieu de cela, les serveurs de partitions traitent des entités de la couche DFS. Étant donné que vos données ne sont pas stockées au niveau de la couche de partition, le déplacement de partitions vers différents serveurs est un processus rapide. Cette flexibilité limite considérablement le temps d’arrêt, le cas échéant, que votre application peut rencontrer.

Stratégie de nouvelle tentative

Il est important que votre application gère les échecs d’opération de stockage pour vous assurer que vous ne perdez aucune mise à jour de données. Certaines défaillances ne nécessitent pas de stratégie de nouvelle tentative. Par exemple, les mises à jour qui retournent une erreur non autorisée 401 ne bénéficient pas de la nouvelle tentative de l’opération, car il est probable que l’état de l’application ne change pas entre les nouvelles tentatives qui résolvent l’erreur 401. Toutefois, les erreurs telles que Server Busy ou Timeout sont liées aux fonctionnalités d’équilibrage de charge d’Azure qui fournissent une scalabilité de table. Lorsque les nœuds de stockage qui servent vos entités deviennent chauds, Azure équilibre la charge en déplaçant les partitions vers d’autres nœuds. Pendant ce temps, la partition peut être inaccessible, ce qui entraîne des erreurs d’occupation du serveur ou de délai d’attente. Finalement, la partition est réactivée et les mises à jour reprennent.

Une stratégie de nouvelle tentative est appropriée pour les erreurs d’occupation du serveur ou de délai d’attente. Dans la plupart des cas, vous pouvez exclure des erreurs de niveau 400 et quelques erreurs de niveau 500 de la logique de nouvelle tentative. Les erreurs qui peuvent être exclues incluent la version 501 Non implémentée et la version HTTP 505 non prise en charge. Ensuite, vous pouvez implémenter une stratégie de nouvelle tentative pour les erreurs de niveau 500 maximum, telles que Server Busy (503) et Timeout (504).

Vous pouvez choisir parmi trois stratégies de nouvelle tentative courantes pour votre application :

  • Aucune nouvelle tentative : aucune nouvelle tentative n’est effectuée.
  • Backoff fixe : l’opération est retentée N fois avec une valeur de backoff constante.
  • Backoff exponentiel : l’opération est retentée N fois avec une valeur de backoff exponentielle.

La stratégie Pas de nouvelle tentative est un moyen simple (et de l'ordre de l'évitement) de gérer les échecs des opérations de mise à jour. Toutefois, une stratégie Sans nouvelle tentative n’est pas très utile. Le fait de n'imposer aucune nouvelle tentative fait prendre des risques évidents, avec des données qui ne sont pas stockées correctement après l'échec de certaines opérations. Une meilleure stratégie consiste à utiliser la stratégie Backoff fixe. qui offre la possibilité de réessayer des opérations avec la même durée d’interruption.

Toutefois, la stratégie n’est pas optimisée pour la gestion des tables hautement évolutives. Si de nombreux threads ou processus attendent la même durée, des collisions peuvent se produire. Une stratégie de nouvelle tentative recommandée est une stratégie qui utilise un backoff exponentiel où chaque nouvelle tentative est plus longue que la dernière tentative. Il est similaire à l’algorithme d’évitement de collision utilisé dans les réseaux informatiques, tels qu’Ethernet. L'interruption exponentielle utilise un facteur aléatoire pour fournir une variation supplémentaire à l'intervalle résultant. La valeur de l'interruption est ensuite contrainte à une limite minimale et à une limite maximale. La formule suivante peut être utilisée pour calculer la valeur de l'interruption suivante, à l'aide d'un algorithme exponentiel :

y = Rand(0,8z , 1,2z)(2x-1

y = Min(zmin + y, zmax

Où :

z = interruption par défaut en millisecondes

zmin = interruption minimale par défaut en millisecondes

zmax = interruption maximale par défaut en millisecondes

x = le nombre de nouvelles tentatives

y = la valeur de l'interruption en millisecondes

Les multiplicateurs 0,8 et 1,2 utilisés dans la fonction Rand (aléatoire) produisent une variance aléatoire de l’inverse par défaut dans ±20 % de la valeur d’origine. La plage ±20 % est acceptable pour la plupart des stratégies de nouvelles tentatives, et elle empêche d’autres collisions. La formule peut être implémentée à l’aide du code suivant :

int retries = 1;  
  
// Initialize variables with default values  
var defaultBackoff = TimeSpan.FromSeconds(30);  
var backoffMin = TimeSpan.FromSeconds(3);  
var backoffMax = TimeSpan.FromSeconds(90);  
  
var random = new Random();  
  
double backoff = random.Next(  
    (int)(0.8D * defaultBackoff.TotalMilliseconds),   
    (int)(1.2D * defaultBackoff.TotalMilliseconds));  
backoff *= (Math.Pow(2, retries) - 1);  
backoff = Math.Min(  
    backoffMin.TotalMilliseconds + backoff,   
    backoffMax.TotalMilliseconds);  
  

Résumé

Une application dans stockage Table Azure peut stocker une quantité massive de données, car le stockage Table gère et réattribue les partitions sur de nombreux nœuds de stockage. Vous pouvez utiliser le partitionnement des données pour contrôler l'évolutivité des tables. Planifiez à l’avance lorsque vous définissez un schéma de table pour vous assurer que vous implémentez des stratégies de partitionnement efficaces. Plus précisément, analysez les exigences, les données et les requêtes de l’application avant de sélectionner PartitionKey Values . Chaque partition peut être réaffectée à différents nœuds de stockage à mesure que le système répond au trafic. Utilisez un test de contrainte de partition pour vous assurer que la table a les valeurs PartitionKey correctes . Ce test vous aide à déterminer quand les partitions sont trop chaudes et vous aide à effectuer les ajustements de partition nécessaires.

Pour vous assurer que votre application gère les erreurs intermittentes et que vos données sont conservées, utilisez une stratégie de nouvelle tentative avec backoff. La stratégie de nouvelles tentatives par défaut utilisée par la bibliothèque cliente stockage Azure a un backoff exponentiel qui évite les collisions et optimise le débit de votre application.