View a markdown version of this page

Meilleures pratiques en matière de performances pour AWS SDK pour .NET - AWS SDK pour .NET (VERSION 4)

La version 4 (V4) du AWS SDK pour .NET est sortie !

Pour plus d'informations sur l'interruption des modifications et la migration de vos applications, consultez la rubrique sur la migration.

Orange button with text "Click here for details".

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Meilleures pratiques en matière de performances pour AWS SDK pour .NET

La façon dont vous créez des clients, gérez les réponses et configurez votre application a un impact important sur le débit, la latence et l'utilisation de la mémoire. Cette rubrique décrit les modèles d'utilisation et de configuration qui permettent à vos applications de fonctionner de manière efficace et fiable. Ces modèles sont particulièrement importants en cas de charge élevée ou dans des environnements aux ressources limitées, tels que les conteneurs et les fonctions sans serveur. Le respect de ces pratiques peut améliorer les performances et éviter les problèmes courants tels que les réponses lentes, les blocages et l'utilisation excessive de la mémoire.

Les pratiques les plus efficaces sont les suivantes :

Avant de commencer, assurez-vous d'avoir configuré votre environnement et votre projet.

Réutilisez un seul client de service à longue durée de vie

Les clients de service tels qu'AmazonS3Client AmazonDynamoDBClient sont sûrs pour les threads, relativement coûteux à construire et durables. La création d'un client résout les informations de région et de point de terminaison et établit l'infrastructure HTTP sous-jacente. Créez un client par service et réutilisez-le pendant toute la durée de vie de votre application. Si vous appelez plus d'une région, créez un client distinct pour chaque région.

Avertissement

Ne créez pas de nouveau client de service pour chaque demande ou dans une boucle. La création de clients à plusieurs reprises entraîne une désactivation de l'infrastructure HTTP sous-jacente, ce qui ajoute une latence mesurable. Il peut également épuiser les prises ou les poignées et provoquer l'échec des demandes ou leur blocage sous charge.

Dans les applications qui utilisent l'injection de dépendances, enregistrez le client en tant que singleton. La méthode d'AddAWSServiceextension du AWSSDK.Extensions.NETCore.Setup NuGet package enregistre le client avec une durée de vie par défaut deServiceLifetime.Singleton. Le client est créé la première fois qu'il est demandé et la même instance est réutilisée pendant toute la durée du processus. Pour plus d'informations sur l'enregistrement de AWS services avec injection de dépendances et les options de lecture depuis la configuration, consultezAWSSDK.Extensions.NETCore.Setup et iConfiguration.

Vous pouvez également enregistrer un client en tant que singleton manuellement.

builder.Services.AddSingleton<IAmazonS3>(_ => new AmazonS3Client());
Note

Étant donné AddAWSService que le client est enregistré en tant que singleton par défaut, ne supprimez pas le client qu'il fournit. Si vous avez besoin d'une durée de vie autre que celle par défaut, transmettez une ServiceLifetime valeur différente au lifetime paramètre facultatif deAddAWSService.

La réutilisation du client ne signifie pas que vous devez arrêter de supprimer les réponses par opération. Réutilisez le client pendant toute la durée de vie de l'application, mais continuez à supprimer les réponses et les flux renvoyés par les différentes opérations, comme décrit dansSupprimer les réponses et les flux.

Éliminez les réponses et les flux pour libérer des connexions

Certains objets de réponse du SDK transmettent un flux réseau en direct. L'exemple le plus courant est GetObjectResponse celui qui implémente IDisposable et expose le contenu de l'objet via sa ResponseStream propriété. La réponse maintient une connexion HTTP ouverte jusqu'à ce que le flux soit entièrement lu ou que la réponse soit supprimée. Si vous divulguez ces réponses (par exemple, GetObject en appelant en boucle sans supprimer chaque résultat), les connexions ouvertes s'accumulent jusqu'à ce que le pool de connexions soit épuisé et que l'appel suivant soit bloqué. C'est la cause des informations faisant état de téléchargements « suspendus de manière aléatoire » ou bloqués sur le nième objet.

Enveloppez toujours une réponse en streaming dans une using déclaration et lisez ou copiez son flux rapidement.

using Amazon.S3; using Amazon.S3.Model; // s3Client is a reused, long-lived client. var request = new GetObjectRequest { BucketName = bucketName, Key = key }; using var response = await s3Client.GetObjectAsync(request); await response.WriteResponseStreamToFileAsync(filePath, append: false, CancellationToken.None);

L'élimination de la réponse et l'élimination de la sienne ResponseStream sont équivalentes ; l'une ou l'autre ferme le flux réseau sous-jacent et renvoie la connexion au pool.

Astuce

Si vous n'avez besoin que des métadonnées de l'objet, telles que la taille, l'heure de dernière modification, le type de contenu ou l'ETag, appelez GetObjectMetadata ou GetObjectMetadataAsync à la place de. GetObject L'opération de métadonnées émet une HEAD requête HTTP et ne transfère aucun corps d'objet. Il n'y a donc aucun flux de contenu à gérer.

var metadata = await s3Client.GetObjectMetadataAsync(bucketName, key); Console.WriteLine($"Size: {metadata.ContentLength} bytes");
Avertissement

Les types de réponse et de flux tels que GetObjectResponse n'implémentent pas de finaliseur, vous ne pouvez donc pas compter sur le ramasse-miettes pour libérer leurs connexions à votre place. Vous devez disposer les réponses et les flux de manière déterministe à l'aide using d'un appel expliciteDispose.

Utiliser async/await correctement

Sur le .NET moderne, les opérations de service dans le AWS SDK pour .NET sont asynchrones et renvoient un. Task Sur .NET Framework, des méthodes synchrones existent également, mais les appels asynchrones sont plus évolutifs et sont recommandés. Effectuez des opérations avec await et async propagez chaque couche de votre code jusqu'au point d'entrée. Pour plus d'informations sur la programmation asynchrone avec le SDK, consultez. Programmation asynchrone

Avertissement

Ne bloquez pas un appel au SDK asynchrone avec.Result, .Wait() ou. .GetAwaiter().GetResult() Ce modèle de synchronisation sur asynchrone est une cause fréquente de blocage des applications :

  • En cas de charge, le blocage des appels consomme les threads plus rapidement que ne peut augmenter le pool de threads, de sorte que les continuations ne peuvent pas s'exécuter. Ce blocage du pool de threads apparaît comme un blocage indéfini et constitue le mode de défaillance dominant sur le .NET moderne (où il n'y en ASP.NET Core a pas par défaut). SynchronizationContext

  • Certains contextes capturent unSynchronizationContext, comme les applications classiques ASP.NETWindows Forms,WPF,Blazor WebAssembly, et .NET Framework. Dans ces contextes, le blocage du thread appelant alors qu'une continuation a besoin de ce même thread entraîne un blocage. Les opérations du SDK sont utilisées ConfigureAwait(false) en interne, elles ne publient donc pas leurs suites dans le contexte capturé. Le blocage provient d'un async code situé ailleurs dans votre chaîne d'appels qui le capture. La consommation d'appels SDK avec await throughout permet d'éviter complètement le problème.

La méthode suivante bloque l'appel asynchrone et peut bloquer ou priver le pool de threads.

// Anti-pattern: do not do this. public GetObjectResponse Get(GetObjectRequest request) { return s3Client.GetObjectAsync(request).Result; }

Définissez plutôt la méthode async et await l'appel.

public async Task<GetObjectResponse> GetAsync(GetObjectRequest request) { return await s3Client.GetObjectAsync(request); }

Recommandations supplémentaires pour le code asynchrone :

  • Passez un CancellationToken à chaque opération afin qu'un appel lent ou bloqué puisse être annulé au lieu d'être suspendu. Pour de plus amples informations, veuillez consulter Utilisation du CancellationToken paramètre pour les délais.

  • N'avalez pas les exceptions dans un catch bloc vide. Cela masque le véritable échec et rend un blocage impossible à distinguer d'une erreur. Détectez des exceptions spécifiques et enregistrez-les.

  • Lorsque vous renvoyez une exception interceptée, utilisez throw; plutôt que throw ex; pour que la trace de pile d'origine soit préservée.

  • Si vous devez appeler depuis une frontière synchrone, considérez cela comme un dernier recours et isolez le travail du contexte capturé plutôt que de faire du blocage des appels le modèle par défaut.

Configurer le ramasse-miettes .NET pour AWS Lambda et Amazon ECS

Lorsqu'un conteneur semble présenter une « fuite de mémoire », le ramasse-miettes .NET (GC) peut conserver la mémoire récupérée pour la réutiliser. Par conséquent, la mémoire de processus peut sembler élevée et stable même lorsque le tas géré n'augmente pas. De plus, le GC ne détecte pas automatiquement la limite de mémoire d'un conteneur (sa limite de groupe de contrôle). Dans un environnement restreint, le tas peut alors croître vers la mémoire de l'hôte plutôt que vers la limite du conteneur. Cela peut entraîner la OutOfMemoryException fermeture du conteneur ou sa fermeture.

Pour aider le GC à bien fonctionner dans des environnements restreints :

  • Définissez une limite de mémoire explicite sur le conteneur afin que le GC respecte la limite du groupe de contrôle, and/or définissez la DOTNET_GCHeapHardLimit (une valeur d'octet absolue, en hexadécimal) ou la variable d'DOTNET_GCHeapHardLimitPercentenvironnement pour plafonner le tas géré. Dans plusieurs cas de manque de mémoire signalés sur Amazon ECS, la définition d'une limite de mémoire dure a permis de résoudre les pannes.

  • Sur les hôtes de petite taille AWS Lambda et Amazon ECS, pensez à désactiver la collecte des déchets simultanée (en arrière-plan) afin que le collecteur ne réserve pas de mémoire supplémentaire ; par exemple, définissez la variable d'DOTNET_gcConcurrentenvironnement sur ou définissez-la <ConcurrentGarbageCollection>false</ConcurrentGarbageCollection> dans le fichier de projet. 0

  • Limitez votre concurrence. Le lancement de nombreuses opérations à la fois, comme l'appel d'une grande collection, augmente la mémoire de processus et peut Task.WhenAll priver le pool de connexions. Limitez plutôt le degré de parallélisme. Par exemple, évitez ce schéma illimité :

    // Anti-pattern: starts one task per item with no limit. await Task.WhenAll(keys.Select(key => s3Client.GetObjectMetadataAsync(bucket, key)));

    Au lieu de cela, limitez la simultanéité avec Parallel.ForEachAsync :

    var options = new ParallelOptions { MaxDegreeOfParallelism = 10 }; await Parallel.ForEachAsync(keys, options, async (key, token) => { await s3Client.GetObjectMetadataAsync(bucket, key, token); });

Pour plus d'informations sur ces paramètres, consultez la section Options de configuration d'exécution pour la collecte des déchets sur learn.microsoft.com. Pour obtenir des conseils spécifiques au AWS calcul, consultez l'article de blog AWS consacré aux outils de développement sur la configuration de .NET Garbage Collection pour Amazon ECS et AWS Lambda.

Gérer les connexions HTTP et les limites de connexion

À haut débit, deux problèmes liés à la connexion sont courants. Le premier est l'ouverture d'un trop grand nombre de connexions éphémères. Cela épuise les ports éphémères, laisse les sockets en TIME_WAIT place et augmente la latence de prise de contact TCP et TLS. Le second est le manque de connexions disponibles, ce qui entrave le parallélisme. La réutilisation d'un seul client à longue durée de vie (voirRéutiliser les clients du service) est la base d'un regroupement de connexions sain, car le regroupement dépend de la réutilisation du client.

Pour régler le nombre de connexions simultanées par point de terminaison, définissez la MaxConnectionsPerServer propriété dans la configuration du client. Lorsque cette propriété est null (valeur par défaut), la valeur HttpClientHandler par défaut sous-jacente s'applique, ce qui est effectivement illimité sur le .NET moderne. Ne l'augmentez que lorsque de nombreuses demandes simultanées adressées au même point de terminaison sont bloquées au niveau des connexions. Un bon point de départ est le nombre maximal de demandes simultanées que vous attendez par point de terminaison. Le régler à un niveau bien supérieur à vos besoins en termes de charge de travail gaspille des sockets sans améliorer le débit.

using Amazon.S3; var config = new AmazonS3Config { MaxConnectionsPerServer = 50 }; var s3Client = new AmazonS3Client(config);

Si vous utilisez déjà l'injection de dépendances, configurez vos clients viaAWSSDK.Extensions.NETCore.Setup. Il s'agit de l'approche recommandée lorsque vous utilisez DI ou que vous enregistrez plusieurs clients de service. Il centralise la configuration et facilite l'injection et le test des clients. Vous pouvez définir les valeurs de configuration à partir de la configuration de votre application plutôt que dans le code. Pour de plus amples informations, veuillez consulter AWSSDK.Extensions.NETCore.Setup et iConfiguration.

Enfin, limitez votre propre parallélisme afin de ne pas démarrer plus d'opérations simultanées que ne le permet votre limite de connexion. Par exemple, passez des appels dont la SemaphoreSlim taille correspond à votre limite de connexion :

var throttle = new SemaphoreSlim(50); // match MaxConnectionsPerServer await throttle.WaitAsync(token); try { await s3Client.GetObjectAsync(request, token); } finally { throttle.Release(); }

Configurer les délais d'attente et les nouvelles tentatives

Les temps morts et les nouvelles tentatives ont un effet direct sur les performances perçues. Une Timeout valeur trop élevée entraîne le blocage d'une requête pendant une longue période. Lorsqu'un service renvoie déjà des erreurs de limitation, une politique de nouvelle tentative agressive ajoute d'autres demandes et peut aggraver la situation. Choisissez une politique de nouvelle tentative adaptée à la tolérance de votre application en matière de latence par rapport à l'échec, et laissez les véritables exceptions se propager au lieu de réessayer de manière à masquer un blocage.

Note

La Timeout propriété n'affecte pas les appels asynchrones. Si vous utilisez des appels asynchrones, consultez Utilisation du CancellationToken paramètre pour les délais plutôt.

Pour plus d'informations sur les modes de nouvelle tentative et la Timeout propriété (ReadWriteTimeouts'applique uniquement à .NET Framework), ainsi que des exemples sur la façon de les définir, consultezRéessais et délais d'attente. MaxErrorRetry

Optimisez le streaming et les transferts d'objets volumineux (Amazon S3)

Pour charger et télécharger des objets volumineux ou de nombreux objets, utilisez la TransferUtility classe de l'Amazon.S3.Transferespace de noms. Il télécharge et télécharge en parallèle à l'aide de transferts partitionnés et gère les flux, les parties et les connexions pour vous. C'est plus rapide qu'un transfert à flux unique et c'est la méthode recommandée pour déplacer des objets volumineux.

  • Téléchargement en plusieurs parties en parallèle. Les versions précédentes du SDK téléchargeaient un objet sous la forme d'un flux unique, au lieu de télécharger des parties en parallèle. À partir de AWSSDK.S3 la version 4.0.17, TransferUtility propose un téléchargement en plusieurs parties (parallèle) via les méthodes DownloadWithResponseAsyncOpenStreamWithResponseAsync, et. DownloadDirectoryWithResponseAsync Lors de l'utilisationOpenStreamWithResponseAsync, les parties de l'objet sont mises en mémoire tampon pendant que vous consommez le flux renvoyé. Contrôlez le nombre de pièces mises en mémoire tampon avec la MaxInMemoryParts propriété de TransferUtilityOpenStreamRequest. Pour la plupart des transferts, préférezTransferUtility. Téléchargez vous-même des plages d'octets avec la ByteRange propriété de GetObjectRequest uniquement lorsque vous avez besoin d'une plage spécifique ou d'un schéma de parallélisme personnalisé. Pour plus d'informations, consultez la section Présentation de la prise en charge du téléchargement en plusieurs parties pour le AWS SDK pour .NET Transfer Manager sur le blog AWS Developer Tools.

  • Longueur du contenu pour les téléchargements. Un Amazon S3 PUT nécessite une longueur de contenu connue et, par défaut, le SDK calcule une somme de contrôle sur le corps de la requête. Lorsque la longueur est connue et que le flux est consultable, le SDK peut le faire sans mettre l'objet entier en mémoire tampon. Il TransferUtility gère pour vous les flux non consultables en les mettant en mémoire tampon si nécessaire. Si vous appelez PutObjectAsync directement avec un flux non consultable (tel qu'un corps de requête brutASP.NET Core), la demande peut échouer. Fournissez un flux consultable, définissez explicitement la longueur du contenu ou configurez la façon dont le SDK calcule les sommes de contrôle. Pour plus d'informations, consultez la section Protection de l'intégrité des données dans le Guide de référence AWS des kits de développement logiciel et des outils.

  • Dimensionnement des pièces Amazon S3 autorise un maximum de 10 000 parties par téléchargement en plusieurs parties. Lorsque la longueur totale est connue, le SDK calcule automatiquement une taille de pièce qui reste dans cette limite. Vous devez principalement PartSize vous fixer pour les streams dont la durée n'est pas connue à l'avance. Dans le cas contraire, les petites parties par défaut peuvent dépasser la limite d'un téléchargement très volumineux. Une taille de pièce plus importante réduit également les frais généraux par pièce, au prix d'une plus grande quantité de mémoire par pièce.

Diagnostiquer les problèmes de performances

Lorsque vous étudiez un ralentissement, un blocage ou une fuite apparente, les signaux suivants vous aident à en trouver rapidement la cause :

  • Un nombre croissant de sockets à l'TIME_WAITétat CLOSE_WAIT ou (visible avecnetstat) est l'empreinte de réponses indisposées ou de clients créés et supprimés par demande. Consultez Supprimer les réponses et les flux et Réutiliser les clients du service.

  • Activez les mesures de demande et l'enregistrement des réponses pour confirmer que les demandes sont effectivement envoyées et pour mesurer la latence. Définissez les propriétés de l'LoggingConfigobjet sur AWSConfigs avant de créer vos clients de service. Un client capture les paramètres, par exemple au LogMetrics moment de sa création.

    using Amazon; AWSConfigs.LoggingConfig.LogMetrics = true; AWSConfigs.LoggingConfig.LogResponses = ResponseLoggingOption.OnError;
  • Lorsque vous étudiez la mémoire, distinguez la mémoire du processus complet du tas géré. Une mémoire de processus élevée mais stable est souvent le GC contenant la mémoire récupérée plutôt qu'une fuite. Consultez Configuration de la collecte des déchets.