View a markdown version of this page

Práticas recomendadas de desempenho para o AWS SDK para .NET - AWS SDK para .NET (V4)

A versão 4 (V4) do AWS SDK para .NET foi lançada!

Para obter informações sobre mudanças significativas e migrar seus aplicativos, consulte o tópico de migração.

Orange button with text "Click here for details".

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

Práticas recomendadas de desempenho para o AWS SDK para .NET

A forma como você cria clientes, processa respostas e configura seu aplicativo tem um grande efeito na taxa de transferência, na latência e no uso da memória. Este tópico descreve os padrões de uso e configuração que ajudam seus aplicativos a serem executados de forma eficiente e confiável. Esses padrões são mais importantes sob alta carga ou em ambientes com recursos limitados, como contêineres e funções sem servidor. Seguir essas práticas pode melhorar o desempenho e evitar problemas comuns, como respostas lentas, travamentos e alto uso de memória.

As práticas mais impactantes são as seguintes:

Antes de começar, certifique-se de ter configurado seu ambiente e seu projeto.

Reutilize um cliente de serviço único e duradouro

Clientes de serviços, como o AmazonS3Client, são seguros para uso em processos, AmazonDynamoDBClient são relativamente caros de construir e duram muito tempo. A construção de um cliente resolve as informações da região e do endpoint e estabelece a infraestrutura HTTP subjacente. Crie um cliente por serviço e reutilize-o durante toda a vida útil do seu aplicativo. Se você ligar para mais de uma região, crie um cliente separado para cada região.

Atenção

Não crie um novo cliente de serviço para cada solicitação ou dentro de um loop. A criação de clientes altera repetidamente a infraestrutura HTTP subjacente, o que adiciona latência mensurável. Ele também pode exaurir soquetes ou alças e fazer com que as solicitações falhem ou fiquem suspensas sob carga.

Em aplicativos que usam injeção de dependência, registre o cliente como um singleton. O método de AddAWSService extensão do AWSSDK.Extensions.NETCore.Setup NuGet pacote registra o cliente com uma vida útil padrão deServiceLifetime.Singleton. O cliente é criado na primeira vez que é solicitado e a mesma instância é reutilizada durante a vida útil do processo. Para obter mais informações sobre o registro de AWS serviços com injeção de dependência e as opções de leitura da configuração, consulte. AWSSDK.Extensions.NETCore.Setup e iConfiguration

Você também pode registrar um cliente como um singleton manualmente.

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

Como AddAWSService registra o cliente como um singleton por padrão, não descarte o cliente que ele fornece. Se você precisar de uma vida útil não padrão, passe um ServiceLifetime valor diferente para o lifetime parâmetro opcional deAddAWSService.

Reutilizar o cliente não significa que você deva parar de descartar respostas por operação. Reutilize o cliente durante a vida útil do aplicativo, mas continue descartando as respostas e os fluxos que as operações individuais retornam, conforme descrito em. Descarte respostas e fluxos

Descarte respostas e fluxos para liberar conexões

Alguns objetos de resposta do SDK transmitem um fluxo de rede ao vivo. O exemplo mais comum é GetObjectResponse o que implementa IDisposable e expõe o conteúdo do objeto por meio de sua ResponseStream propriedade. A resposta mantém uma conexão HTTP aberta até que o stream seja totalmente lido ou a resposta seja descartada. Se você vazar essas respostas (por exemplo, chamando GetObject um loop sem descartar cada resultado), as conexões abertas se acumularão até que o pool de conexões se esgote e a próxima chamada seja bloqueada. Essa é a causa por trás dos relatos de downloads que “param aleatoriamente” ou param no enésimo objeto.

Sempre inclua uma resposta de streaming em uma using declaração e leia ou copie a transmissão imediatamente.

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);

Descartar a resposta e descartá-la ResponseStream são equivalentes; qualquer uma delas fecha o fluxo de rede subjacente e retorna a conexão ao pool.

dica

Se você precisar apenas de metadados do objeto, como tamanho, hora da última modificação, tipo de conteúdo ou ETag, ligue para GetObjectMetadata ou em vez de. GetObjectMetadataAsync GetObject A operação de metadados emite uma HEAD solicitação HTTP e não transfere o corpo do objeto, portanto, não há fluxo de conteúdo para gerenciar.

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

Tipos de resposta e stream, como GetObjectResponse não implementam um finalizador, então você não pode confiar na coleta de lixo para liberar suas conexões para você. Você deve descartar respostas e fluxos de forma determinística com using ou com uma chamada explícita. Dispose

Use async/await corretamente

No domínio.NET moderno, as operações de serviço no AWS SDK para .NET são assíncronas e retornam a. Task No .NET Framework, também existem métodos síncronos, mas as chamadas assíncronas escalam melhor e são recomendadas. Consuma operações com await e propague async por todas as camadas do seu código até o ponto de entrada. Para obter mais informações sobre programação assíncrona com o SDK, consulte. Programação assíncrona

Atenção

Não bloqueie uma chamada de SDK assíncrona com.Result,, ou. .Wait() .GetAwaiter().GetResult() Esse padrão de sincronização sobre assíncrona é uma causa comum de travamento de aplicativos:

  • Sob carga, as chamadas de bloqueio consomem threads mais rápido do que o pool de threads pode crescer, portanto, as continuações não podem ser executadas. Essa falta de pool de threads aparece como um travamento indefinido e é o modo de falha dominante no.NET moderno (onde não tem padrão). ASP.NET Core SynchronizationContext

  • Alguns contextos capturam umSynchronizationContext, como aplicativos clássicosASP.NET,, Windows Forms WPFBlazor WebAssembly, e .NET Framework. Nesses contextos, bloquear o thread de chamada enquanto uma continuação precisa desse mesmo thread produz um impasse. As operações no SDK são usadas ConfigureAwait(false) internamente, então elas não publicam suas continuações no contexto capturado. O impasse surge de um async código em outra parte da sua cadeia de chamadas que o captura. O consumo contínuo de chamadas de await SDK evita totalmente o problema.

O método a seguir bloqueia a chamada assíncrona e pode bloquear ou privar o pool de threads.

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

Em vez disso, faça o método async e await a chamada.

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

Recomendações adicionais para código assíncrono:

  • Passe CancellationToken a para cada operação para que uma chamada lenta ou paralisada possa ser cancelada em vez de suspensa. Para obter mais informações, consulte Usar o parâmetro CancellationToken para tempos limite.

  • Não engula exceções em um bloco vaziocatch. Fazer isso oculta a falha real e torna um travamento indistinguível de um erro. Capture exceções específicas e registre-as.

  • Ao relançar uma exceção capturada, use throw; em vez de throw ex; para que o rastreamento de pilha original seja preservado.

  • Se você precisar chamar de um limite síncrono, trate-o como último recurso e isole o trabalho do contexto capturado em vez de tornar as chamadas de bloqueio o padrão padrão.

Configurar a coleta de lixo do.NET para AWS Lambda e Amazon ECS

Quando um contêiner parece ter um “vazamento de memória”, o coletor de lixo do.NET (GC) pode estar retendo a memória recuperada para reutilização. Como resultado, a memória do processo pode parecer alta e estável mesmo quando a pilha gerenciada não está crescendo. Além disso, o GC não detecta automaticamente o limite de memória de um contêiner (seu limite de cgroup). Em um ambiente restrito, a pilha pode então crescer em direção à memória do host, em vez do limite do contêiner. Isso pode levar ao encerramento OutOfMemoryException ou ao encerramento do contêiner.

Para ajudar o GC a funcionar bem em ambientes restritos:

  • Defina um limite de memória explícito no contêiner para que o GC honre o limite do cgroup, and/or defina a variável de ambiente DOTNET_GCHeapHardLimit (um valor absoluto de byte, em hexadecimal) ou DOTNET_GCHeapHardLimitPercent de ambiente para limitar a pilha gerenciada. Em vários casos relatados de falta de memória do Amazon ECS, definir um limite de memória rígida resolveu as falhas.

  • Em hosts pequenos AWS Lambda e no Amazon ECS, considere desativar a coleta de lixo simultânea (em segundo plano) para que o coletor não reserve memória adicional; por exemplo, defina a variável de DOTNET_gcConcurrent ambiente como ou <ConcurrentGarbageCollection>false</ConcurrentGarbageCollection> defina-a no 0 arquivo do projeto.

  • Limite sua concorrência. Iniciar várias operações ao mesmo tempo, como chamar Task.WhenAll uma grande coleção, infla a memória do processo e pode prejudicar o pool de conexões. Em vez disso, limite o grau de paralelismo. Por exemplo, evite esse padrão ilimitado:

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

    Em vez disso, limite a simultaneidade comParallel.ForEachAsync:

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

Para obter mais informações sobre essas configurações, consulte Opções de configuração de tempo de execução para coleta de lixo em learn.microsoft.com. Para obter orientações específicas sobre AWS computação, consulte a postagem do blog AWS Developer Tools Configurando a coleta de lixo do.NET para Amazon ECS e. AWS Lambda

Gerenciar conexões HTTP e limites de conexão

Em alta taxa de transferência, dois problemas relacionados à conexão são comuns. A primeira é abrir muitas conexões de curta duração. Isso esgota as portas efêmeras, deixa os soquetes dentro e adiciona latência de handshake TIME_WAIT TCP e TLS. A segunda é ter poucas conexões disponíveis, o que prejudica o paralelismo. A reutilização de um cliente único e duradouro (consulteReutilize clientes de serviços) é a base para um pool de conexões saudável, porque o agrupamento depende da reutilização do cliente.

Para ajustar o número de conexões simultâneas por endpoint, defina a MaxConnectionsPerServer propriedade na configuração do cliente. Quando essa propriedade é null (o padrão), o HttpClientHandler padrão subjacente se aplica, o que é efetivamente ilimitado no.NET moderno. Aumente-o somente quando muitas solicitações simultâneas para o mesmo endpoint estiverem bloqueadas nas conexões. Um bom ponto de partida é o número máximo de solicitações simultâneas que você espera por endpoint. Defini-la muito acima das necessidades de sua carga de trabalho desperdiça soquetes sem melhorar a produtividade.

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

Se você já usa injeção de dependência, configure seus clientes por meio AWSSDK.Extensions.NETCore.Setup de. Essa é a abordagem recomendada quando você usa DI ou registra vários clientes de serviço. Ele centraliza a configuração e facilita a injeção e o teste dos clientes. Você pode definir valores de configuração a partir da configuração do seu aplicativo em vez de no código. Para obter mais informações, consulte AWSSDK.Extensions.NETCore.Setup e iConfiguration.

Finalmente, limite seu próprio paralelismo para que você não inicie mais operações simultâneas do que seu limite de conexão permite. Por exemplo, chamadas de portão com um SemaphoreSlim tamanho igual ao seu limite de conexão:

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

Configurar tempos limite e novas tentativas

Os tempos limite e as novas tentativas têm um efeito direto no desempenho percebido. Um Timeout valor muito alto permite que uma solicitação paralisada seja bloqueada por muito tempo. Quando um serviço já está retornando erros de limitação, uma política agressiva de repetição adiciona mais solicitações e pode piorar a limitação. Escolha uma política de repetição que atenda à tolerância de latência versus falha do seu aplicativo e permita que exceções genuínas se propaguem em vez de tentar novamente de uma forma que mascare um travamento.

nota

A Timeout propriedade não afeta as chamadas assíncronas. Se você estiver usando chamadas assíncronas, consulte em vez disso. Usar o parâmetro CancellationToken para tempos limite

Para obter mais informações sobre os modos de repetição e a Timeout propriedade (ReadWriteTimeoutaplicável somente ao.NET Framework), além de exemplos de como configurá-los, consulteNovas tentativas e tempos limite. MaxErrorRetry

Otimize o streaming e as transferências de objetos grandes (Amazon S3)

Para carregar e baixar objetos grandes, ou muitos objetos, use a TransferUtility classe no Amazon.S3.Transfer namespace. Ele carrega e baixa em paralelo usando transferências de várias partes e gerencia fluxos, partes e conexões para você. Isso é mais rápido do que uma transferência de fluxo único e é a maneira recomendada de mover objetos grandes.

  • Download paralelo de várias partes. As versões anteriores do SDK baixavam um objeto como um único stream, em vez de baixar partes em paralelo. A partir da AWSSDK.S3 versão 4.0.17, TransferUtility fornece download de várias partes (paralelo) por meio dos métodos DownloadWithResponseAsyncOpenStreamWithResponseAsync, e. DownloadDirectoryWithResponseAsync Quando você usaOpenStreamWithResponseAsync, as partes do objeto são armazenadas em buffer na memória enquanto você consome o fluxo retornado. Controle quantas peças são armazenadas em buffer com a MaxInMemoryParts propriedade de TransferUtilityOpenStreamRequest. Para a maioria das transferências, prefiraTransferUtility. Baixe você mesmo os intervalos de bytes com a ByteRange propriedade de GetObjectRequest somente quando precisar de um intervalo específico ou de um esquema de paralelismo personalizado. Para obter mais informações, consulte Introdução ao suporte de download de várias partes para o AWS SDK para .NET Transfer Manager no blog de ferramentas para AWS desenvolvedores.

  • Tamanho do conteúdo para uploads. Um Amazon S3 PUT exige um tamanho de conteúdo conhecido e, por padrão, o SDK calcula uma soma de verificação sobre o corpo da solicitação. Quando o comprimento é conhecido e o fluxo é pesquisável, o SDK pode fazer isso sem armazenar o objeto inteiro na memória. Ele TransferUtility lida com fluxos não pesquisáveis para você, armazenando em buffer conforme necessário. Se, em vez disso, você ligar PutObjectAsync diretamente com um fluxo não pesquisável (como um corpo de solicitação brutoASP.NET Core), a solicitação poderá falhar. Forneça um stream pesquisável, defina o tamanho do conteúdo explicitamente ou configure como o SDK calcula as somas de verificação. Para obter mais informações, consulte Proteções de integridade de dados no Guia de referência de AWS SDKs e ferramentas.

  • Dimensionamento da peça. O Amazon S3 permite um máximo de 10.000 peças por upload de várias partes. Quando o tamanho total é conhecido, o SDK calcula automaticamente um tamanho de peça que permanece dentro desse limite. Você precisa PartSize se configurar principalmente para streams cuja duração não é conhecida com antecedência. Caso contrário, pequenas partes padrão podem exceder o limite de um upload muito grande. Um tamanho de peça maior também reduz a sobrecarga por peça, ao custo de mais memória por peça.

Diagnosticar problemas de desempenho

Quando você investiga uma lentidão, um travamento ou um aparente vazamento, os seguintes sinais ajudam você a encontrar a causa rapidamente:

  • Um número crescente de soquetes no TIME_WAIT estado CLOSE_WAIT or (visível comnetstat) é a impressão digital de respostas não descartadas ou de clientes sendo criados e descartados por solicitação. Consulte Descarte respostas e fluxos e Reutilize clientes de serviços.

  • Ative as métricas de solicitação e o registro de respostas para confirmar se as solicitações estão realmente sendo enviadas e para medir a latência. Defina as propriedades do LoggingConfig objeto em AWSConfigs antes de criar seus clientes de serviço. Um cliente captura configurações, como LogMetrics quando ele é construído.

    using Amazon; AWSConfigs.LoggingConfig.LogMetrics = true; AWSConfigs.LoggingConfig.LogResponses = ResponseLoggingOption.OnError;
  • Ao investigar a memória, diferencie a memória de todo o processo da pilha gerenciada. A memória de processo alta, mas estável, geralmente é o GC que contém a memória recuperada em vez de um vazamento. Consulte Configurar a coleta de lixo.