Anti-padrão de Obtenção ExternaExtraneous Fetching antipattern

Obter mais dados do que o necessário para uma operação empresarial pode resultar em problemas de E/S desnecessários e reduzir a capacidade de resposta.Retrieving more data than needed for a business operation can result in unnecessary I/O overhead and reduce responsiveness.

Descrição do problemaProblem description

Este anti-padrão pode ocorrer se a aplicação tentar minimizar os pedidos de E/S ao recuperar todos os dados de que pode precisar.This antipattern can occur if the application tries to minimize I/O requests by retrieving all of the data that it might need. Isto costuma ser um resultado de sobrecompensação para o anti-padrão Chatty I/O.This is often a result of overcompensating for the Chatty I/O antipattern. Por exemplo, uma aplicação pode obter os detalhes para cada produto numa base de dados.For example, an application might fetch the details for every product in a database. Mas o utilizador pode precisar apenas de um subconjunto dos detalhes (alguns podem não ser relevantes para os clientes) e provavelmente não precisa de ver todos os produtos ao mesmo tempo.But the user may need just a subset of the details (some may not be relevant to customers), and probably doesn't need to see all of the products at once. Mesmo que o utilizador esteja a navegar pelo catálogo inteiro, pode fazer sentido paginar os resultados—como por exemplo mostrar 20 ao mesmo tempo.Even if the user is browsing the entire catalog, it would make sense to paginate the results—showing 20 at a time, for example.

Outra origem deste problema é seguir práticas de programação ou conceção fracas.Another source of this problem is following poor programming or design practices. Por exemplo, o seguinte código utiliza o Entity Framework para obter os detalhes completos para cada produto.For example, the following code uses Entity Framework to fetch the complete details for every product. Então filtra os resultados para devolver apenas um subconjunto dos campos, descartando o resto.Then it filters the results to return only a subset of the fields, discarding the rest. Pode encontrar o exemplo completo aqui.You can find the complete sample here.

public async Task<IHttpActionResult> GetAllFieldsAsync()
{
    using (var context = new AdventureWorksContext())
    {
        // Execute the query. This happens at the database.
        var products = await context.Products.ToListAsync();

        // Project fields from the query results. This happens in application memory.
        var result = products.Select(p => new ProductInfo { Id = p.ProductId, Name = p.Name });
        return Ok(result);
    }
}

No exemplo seguinte, a aplicação obtém dados para efetuar uma agregação que, como alternativa, pode ser feita pela base de dados.In the next example, the application retrieves data to perform an aggregation that could be done by the database instead. A aplicação calcula as vendas totais ao obter cada registo para todas as encomendas vendidas e calcular a soma desses registos.The application calculates total sales by getting every record for all orders sold, and then computing the sum over those records. Pode encontrar o exemplo completo aqui.You can find the complete sample here.

public async Task<IHttpActionResult> AggregateOnClientAsync()
{
    using (var context = new AdventureWorksContext())
    {
        // Fetch all order totals from the database.
        var orderAmounts = await context.SalesOrderHeaders.Select(soh => soh.TotalDue).ToListAsync();

        // Sum the order totals in memory.
        var total = orderAmounts.Sum();
        return Ok(total);
    }
}

O exemplo seguinte mostra um problema subtis causado pela forma como o Entity Framework utiliza o LINQ to Entities.The next example shows a subtle problem caused by the way Entity Framework uses LINQ to Entities.

var query = from p in context.Products.AsEnumerable()
            where p.SellStartDate < DateTime.Now.AddDays(-7) // AddDays cannot be mapped by LINQ to Entities
            select ...;

List<Product> products = query.ToList();

A aplicação está a tentar localizar os produtos com uma SellStartDate a mais que uma semana.The application is trying to find products with a SellStartDate more than a week old. Na maioria dos casos, o LINQ to Entities converte uma cláusula where para uma instrução de SQL que é executada pela base de dados.In most cases, LINQ to Entities would translate a where clause to a SQL statement that is executed by the database. No entanto, neste caso o LINQ to Entities não pode mapear o método AddDays para SQL.In this case, however, LINQ to Entities cannot map the AddDays method to SQL. Em vez disso, cada linha da tabela Product é devolvida e os resultados são filtrados na memória.Instead, every row from the Product table is returned, and the results are filtered in memory.

A chamada para AsEnumerable é uma sugestão que há um problema.The call to AsEnumerable is a hint that there is a problem. Este método converte os resultados para uma interface IEnumerable.This method converts the results to an IEnumerable interface. Apesar de IEnumerable suportar filtragem, a filtragem é efetuada no lado do cliente, não do lado da base de dados.Although IEnumerable supports filtering, the filtering is done on the client side, not the database. Por predefinição, o LINQ to Entities utiliza IQueryable, que passa a responsabilidade de filtragem para a origem de dados.By default, LINQ to Entities uses IQueryable, which passes the responsibility for filtering to the data source.

Como resolver o problemaHow to fix the problem

Evite a obtenção de grandes volumes de dados que podem rapidamente tornar-se desatualizados ou podem ser descartados. Obtenha apenas os dados necessários para a operação a realizar.Avoid fetching large volumes of data that may quickly become outdated or might be discarded, and only fetch the data needed for the operation being performed.

Em vez de obter cada coluna de uma tabela e então filtrá-las, selecione as colunas que precisa da base de dados.Instead of getting every column from a table and then filtering them, select the columns that you need from the database.

public async Task<IHttpActionResult> GetRequiredFieldsAsync()
{
    using (var context = new AdventureWorksContext())
    {
        // Project fields as part of the query itself
        var result = await context.Products
            .Select(p => new ProductInfo {Id = p.ProductId, Name = p.Name})
            .ToListAsync();
        return Ok(result);
    }
}

Da mesma forma, realize a agregação na base de dados e não na memória da aplicação.Similarly, perform aggregation in the database and not in application memory.

public async Task<IHttpActionResult> AggregateOnDatabaseAsync()
{
    using (var context = new AdventureWorksContext())
    {
        // Sum the order totals as part of the database query.
        var total = await context.SalesOrderHeaders.SumAsync(soh => soh.TotalDue);
        return Ok(total);
    }
}

Ao utilizar o Entity Framework, certifique-se de que as consultas de LINQ são resolvidas com a interface IQueryable e não com IEnumerable.When using Entity Framework, ensure that LINQ queries are resolved using the IQueryableinterface and not IEnumerable. Pode precisar de ajustar a consulta para apenas utilizar funções que possam ser mapeadas para a origem de dados.You may need to adjust the query to use only functions that can be mapped to the data source. O exemplo anterior pode ser refatorizado para remover o método AddDays da consulta, permitindo que a filtragem possa ser feita pela base de dados.The earlier example can be refactored to remove the AddDays method from the query, allowing filtering to be done by the database.

DateTime dateSince = DateTime.Now.AddDays(-7); // AddDays has been factored out.
var query = from p in context.Products
            where p.SellStartDate < dateSince // This criterion can be passed to the database by LINQ to Entities
            select ...;

List<Product> products = query.ToList();

ConsideraçõesConsiderations

  • Em alguns casos, pode melhorar o desempenho ao particionar os dados horizontalmente.In some cases, you can improve performance by partitioning data horizontally. Se diferentes operações acederem a atributos diferentes dos dados, a partição horizontal pode reduzir a contenção.If different operations access different attributes of the data, horizontal partitioning may reduce contention. Muitas vezes, a maior parte das operações são executadas em relação a um pequeno subconjunto de dados, pelo que propagar esta carga pode melhorar o desempenho.Often, most operations are run against a small subset of the data, so spreading this load may improve performance. Consulte Partição de dados.See Data partitioning.

  • Para operações que têm de suportar consultas sem limites, implemente a paginação e obtenha apenas um número limitado de entidades de cada vez.For operations that have to support unbounded queries, implement pagination and only fetch a limited number of entities at a time. Por exemplo, se um cliente estiver a navegar num catálogo de produtos, pode msotrar uma página de resultados de cada vez.For example, if a customer is browsing a product catalog, you can show one page of results at a time.

  • Quando possível, tire partido das funcionalidades integradas no arquivo de dados.When possible, take advantage of features built into the data store. Por exemplo, as bases de dados SQL costumam fornecer funções de agregação.For example, SQL databases typically provide aggregate functions.

  • Se estiver a utilizar um arquivo de dados que não suporta uma função específica, como a agregação, pode armazenar o resultado calculado noutro local ao atualizar o valor como registos adicionados ou atualizados, pelo que a aplicação não tem que recalcular o valor sempre que é preciso.If you're using a data store that doesn't support a particular function, such as aggregation, you could store the calculated result elsewhere, updating the value as records are added or updated, so the application doesn't have to recalculate the value each time it's needed.

  • Se vir que os pedidos estão a devolver um número grande de campos, examine o código-fonte para determinar se todos esses campos são necessários.If you see that requests are retrieving a large number of fields, examine the source code to determine whether all of these fields are necessary. Por vezes esses pedidos resultam de uma consulta de SELECT * mal concebida.Sometimes these requests are the result of poorly designed SELECT * query.

  • Da mesma forma, os pedidos que obtêm um grande número de entidades podem ser um sinal que a aplicação não está a filtrar corretamente os dados.Similarly, requests that retrieve a large number of entities may be sign that the application is not filtering data correctly. Certifique-se de que todas essas entidades são necessárias.Verify that all of these entities are needed. Utilize a filtragem do lado da base de dados se possível, por exemplo ao utilizar cláusulas WHERE em SQL.Use database-side filtering if possible, for example, by using WHERE clauses in SQL.

  • O processamento de descarga para a base de dados nem sempre é a melhor opção.Offloading processing to the database is not always the best option. Utilize esta estratégia apenas quando a base de dados está concebida ou otimizada para tal.Only use this strategy when the database is designed or optimized to do so. A maior parte dos sistemas de bases de dados estão otimizados para determinadas funções, mas não estão concebidas para funcionar como motores de aplicações para efeitos gerais.Most database systems are highly optimized for certain functions, but are not designed to act as general-purpose application engines. Para mais informações, consulte Anti-padrão de Base de Dados Ocupada.For more information, see the Busy Database antipattern.

Como detetar o problemaHow to detect the problem

Os sintomas de obtenção externa incluem elevada latência e baixo débito.Symptoms of extraneous fetching include high latency and low throughput. Se os dados forem obtidos de um arquivo de dados, é possível que haja um aumento na contenção.If the data is retrieved from a data store, increased contention is also probable. É provável que os utilizadores finais reportem tempos de resposta prolongados ou falhas causadas por tempo limite excedido. Estas falhas podem devolver erros de HTTP 500 (Servidor Interno) ou erros de HTTP 503 (Serviço Indisponível).End users are likely to report extended response times or failures caused by services timing out. These failures could return HTTP 500 (Internal Server) errors or HTTP 503 (Service Unavailable) errors. Analise os registos de eventos do servidor Web, os quais contêm provavelmente informações mais detalhadas sobre as causas e as circunstâncias dos erros.Examine the event logs for the web server, which likely contain more detailed information about the causes and circumstances of the errors.

Os sintomas deste anti-padrão e alguns da telemetria obtida podem ser muito semelhantes aos do Anti-padrão de Persistência Monolítica.The symptoms of this antipattern and some of the telemetry obtained might be very similar to those of the Monolithic Persistence antipattern.

Pode realizar os passos seguintes para ajudar a identificar a causa:You can perform the following steps to help identify the cause:

  1. Identifique as cargas de trabalho ou transações lentas ao realizar um teste de carga, monitorização de processos ou outros métodos de captura de dados de instrumentação.Identify slow workloads or transactions by performing load-testing, process monitoring, or other methods of capturing instrumentation data.
  2. Observe quaisquer padrões comportamentais apresentados pelo sistema.Observe any behavioral patterns exhibited by the system. Existem limites particulares em termos de transações por segundo ou volume de utilizadores?Are there particular limits in terms of transactions per second or volume of users?
  3. Faça a correlação das instâncias de cargas de trabalho lentas com os padrões comportamentais.Correlate the instances of slow workloads with behavioral patterns.
  4. Identifique os arquivos de dados a utilizar.Identify the data stores being used. Para cada origem de dados, execute uma telemetria de nível inferior para observar o comportamento das operações.For each data source, run lower-level telemetry to observe the behavior of operations.
  5. Identifique quaisquer consultas de execução lenta que referenciem estas origens de dados.Identify any slow-running queries that reference these data sources.
  6. Realize uma análise específica em recursos das consultas lentas e determine como os dados são utilizados e consumidos.Perform a resource-specific analysis of the slow-running queries and ascertain how the data is used and consumed.

Procure por qualquer um destes sintomas:Look for any of these symptoms:

  • São feitos pedidos grandes e frequentes de E/S ao mesmo recurso ou arquivo de dados.Frequent, large I/O requests made to the same resource or data store.
  • Contenção num recurso partilhado ou arquivo de dados.Contention in a shared resource or data store.
  • Uma operação que recebe frequentemente grandes volumes de dados através da rede.An operation that frequently receives large volumes of data over the network.
  • As aplicações e serviços que passem um tempo significativo à espera de E/S para concluir.Applications and services spending significant time waiting for I/O to complete.

Diagnóstico de exemploExample diagnosis

As seguintes secções aplicam estes passos aos exemplos anteriores.The following sections apply these steps to the previous examples.

Identificar cargas de trabalho lentasIdentify slow workloads

Este gráfico mostra os resultados de desempenho de um teste de carga com simulação de até 400 utilizadores em simultâneo com o método GetAllFieldsAsync apresentado anteriormente.This graph shows performance results from a load test that simulated up to 400 concurrent users running the GetAllFieldsAsync method shown earlier. O débito diminui lentamente conforme a carga aumenta.Throughput diminishes slowly as the load increases. O tempo de resposta médio sobe à medida que a carga de trabalho aumenta.Average response time goes up as the workload increases.

Resultados do teste de carga para o método GetAllFieldsAsync

Um teste de carga para a operação AggregateOnClientAsync mostra um padrão semelhante.A load test for the AggregateOnClientAsync operation shows a similar pattern. O volume de pedidos está razoavelmente estável.The volume of requests is reasonably stable. O tempo médio de resposta aumenta com a carga de trabalho, embora mais lentamente que no gráfico anterior.The average response time increases with the workload, although more slowly than the previous graph.

Resultados do teste de carga para o método AggregateOnClientAsync

Faça a correlação das cargas de trabalho lentas com os padrões comportamentaisCorrelate slow workloads with behavioral patterns

Qualquer correlação entre os períodos de elevada utilização e um desempenho mais lento pode indicar áreas de preocupação.Any correlation between regular periods of high usage and slowing performance can indicate areas of concern. Examine rigorosamente o perfil de desempenho da funcionalidade que se suspeita estar em execução lenta, para poder determinar se corresponde ao teste de carga realizado antes.Closely examine the performance profile of functionality that is suspected to be slow running, to determine whether it matches the load testing performed earlier.

Faça o teste de carga à mesma funcionalidade com carregamentos de utilizadores com base em passos, para descobrir o ponto em que o desempenho cai significativamente ou falha completamente.Load test the same functionality using step-based user loads, to find the point where performance drops significantly or fails completely. Se esse ponto calhar dentro dos limites da sua utilização de mundo real esperada, examine como está implementada a funcionalidade.If that point falls within the bounds of your expected real-world usage, examine how the functionality is implemented.

Uma operação lenta não é necessariamente um problema, se não estiver a ser realizada quando o sistema está em esforço, sem ser crítico em termos de tempo e não afetar negativamente o desempenho de outras operações importantes.A slow operation is not necessarily a problem, if it is not being performed when the system is under stress, is not time critical, and does not negatively affect the performance of other important operations. Por exemplo, gerar estatísticas operacionais mensais pode ser uma operação a longo prazo, mas pode ser realizada como um processo em lote e baixa prioridade.For example, generating monthly operational statistics might be a long-running operation, but it can probably be performed as a batch process and run as a low-priority job. Por outro lado, os clientes a consultar o catálogo de produtos é uma operação crítica para o negócio.On the other hand, customers querying the product catalog is a critical business operation. Foque-se na telemetria gerada por essas operações críticas para ver como o desempenho varia durante os períodos de utilização elevada.Focus on the telemetry generated by these critical operations to see how the performance varies during periods of high usage.

Identifique as origens de dados em cargas de trabalho lentasIdentify data sources in slow workloads

Se suspeitar que um serviço está a ser executado de forma insuficiente devido à forma como obtém dados, investigue como é que a aplicação interage com os repositórios que utiliza.If you suspect that a service is performing poorly because of the way it retrieves data, investigate how the application interacts with the repositories it uses. Monitorize o sistema ao vivo para ver que origens são acendidas durante os períodos de fraco desempenho.Monitor the live system to see which sources are accessed during periods of poor performance.

Para cada origem de dados, instrumente o sistema para capturar o seguinte:For each data source, instrument the system to capture the following:

  • A frequência com que cada arquivo de dados é acedido.The frequency that each data store is accessed.
  • O volume de dados a entrar e sair do arquivo de dados.The volume of data entering and exiting the data store.
  • A temporização destas operações, em particular, a latência dos pedidos.The timing of these operations, especially the latency of requests.
  • A natureza e a taxa de quaisquer erros que ocorram ao aceder cada arquivo de dados sob uma carga típica.The nature and rate of any errors that occur while accessing each data store under typical load.

Compare estas informações com o volume de dados devolvidos pela aplicação para o cliente.Compare this information against the volume of data being returned by the application to the client. Controle a proporção entre o volume de dados devolvidos pelo arquivo de dados com o volume de dados devolvidos para o cliente.Track the ratio of the volume of data returned by the data store against the volume of data returned to the client. Se não houver uma grande disparidade, investigue para determinar se a aplicação está a obter dados de que não precisa.If there is any large disparity, investigate to determine whether the application is fetching data that it doesn't need.

Pode capturar estes dados ao observar o sistema ao vivo e rastrear o ciclo de vida de cada utilizador, ou pode modelar uma série de cargas de trabalho sintéticas e executá-las num sistema de teste.You may be able to capture this data by observing the live system and tracing the lifecycle of each user request, or you can model a series of synthetic workloads and run them against a test system.

Os gráficos seguintes mostram a telemetria capturada com o New Relic APM durante um teste de carga do método GetAllFieldsAsync.The following graphs show telemetry captured using New Relic APM during a load test of the GetAllFieldsAsync method. Tenha em atenção a diferença entre os volumes de dados recebidos da base de dados e as respostas HTTP correspondentes.Note the difference between the volumes of data received from the database and the corresponding HTTP responses.

Telemetria para o método "GetAllFieldsAsync"

Para cada pedido, a base de dados devolveu 80.503 bytes, mas a resposta para o cliente apenas continha 19.855 bytes, ou seja, cerca de 25% do tamanho da resposta da base de dados.For each request, the database returned 80,503 bytes, but the response to the client only contained 19,855 bytes, about 25% of the size of the database response. O tamanho dos dados devolvidos ao cliente podem variar dependendo do formato.The size of the data returned to the client can vary depending on the format. Para este teste de carga, o cliente pediu dados JSON.For this load test, the client requested JSON data. Os testes em separado com XML (não mostrado) tiveram um tamanho de resposta de 35.655 bytes, ou seja, 44% do tamanho da resposta da base de dados.Separate testing using XML (not shown) had a response size of 35,655 bytes, or 44% of the size of the database response.

O teste de carga para o método AggregateOnClientAsync mostra resultados mais extremos.The load test for the AggregateOnClientAsync method shows more extreme results. Neste caso, cada teste realizou uma consulta que desenvolveu mais de 280 Kb de dados da base de dados, mas a resposta JSON uns meros 14 bytes.In this case, each test performed a query that retrieved over 280 Kb of data from the database, but the JSON response was a mere 14 bytes. A grande disparidade deve-se ao método calcular um resultado agregado a partir de um grande volume de dados.The wide disparity is because the method calculates an aggregated result from a large volume of data.

Telemetria para o método "AggregateOnClientAsync"

Identificar e analisar consultas lentasIdentify and analyze slow queries

Procure por consultas de base de dados que consumam mais recursos e tire o maior tempo para executar.Look for database queries that consume the most resources and take the most time to execute. Pode adicionar a instrumentação para descobrir as horas de início e conclusão de várias operações da base de dados.You can add instrumentation to find the start and completion times for many database operations. Vários arquivos de dados também fornecem informação aprofundada sobre como as consultas são otimizadas e desempenhadas.Many data stores also provide in-depth information on how queries are performed and optimized. Por exemplo, o painel Desempenho de Consulta no portal de gestão da Base de Dados SQL do Azure permite-lhe selecionar uma consultar e ver informações de desempenho de runtime detalhadas.For example, the Query Performance pane in the Azure SQL Database management portal lets you select a query and view detailed runtime performance information. Aqui está a consulta gerada pela operação GetAllFieldsAsync:Here is the query generated by the GetAllFieldsAsync operation:

O painel Detalhes de Consulta no portal de gestão da Base de Dados SQL do Microsoft Azure

Implementar a solução e verificar o resultadoImplement the solution and verify the result

Em seguida a alterar o método GetRequiredFieldsAsync para utilizar a instrução SELECT no lado da base de dados, o teste de carga mostrou os seguintes resultados.After changing the GetRequiredFieldsAsync method to use a SELECT statement on the database side, load testing showed the following results.

Resultados do teste de carga para o método GetRequiredFieldsAsync

Este teste de carga utilizou a mesma implementação e a mesma carga de trabalho simulada com os 400 utilizadores em simultâneo, como antes.This load test used the same deployment and the same simulated workload of 400 concurrent users as before. O gráfico mostra uma latência muito menor.The graph shows much lower latency. O tempo de resposta sobe com a carga para aproximadamente 1,3 segundos, em comparação com os 4 segundos no caso anterior.Response time rises with load to approximately 1.3 seconds, compared to 4 seconds in the previous case. O débito também é superior, em 350 pedidos por segundo em comparação com os 100 antes.The throughput is also higher at 350 requests per second compared to 100 earlier. O volume de dados obtidos da base de dados agora aproxima-se do tamanho das mensagens de resposta HTTP.The volume of data retrieved from the database now closely matches the size of the HTTP response messages.

Telemetria para o método "GetRequiredFieldsAsync"

O teste de carga com o método AggregateOnDatabaseAsync gera os seguintes resultados:Load testing using the AggregateOnDatabaseAsync method generates the following results:

Resultados do teste de carga para o método AggregateOnDatabaseAsync

O tempo médio de resposta é mínimo, neste momento.The average response time is now minimal. Isto é uma melhoria considerável no desempenho, causada sobretudo pela grande redução na E/S da base de dados.This is an order of magnitude improvement in performance, caused primarily by the large reduction in I/O from the database.

Esta é a telemetria correspondente para o método AggregateOnDatabaseAsync.Here is the corresponding telemetry for the AggregateOnDatabaseAsync method. A quantidade de dados obtidos da base de dados foi bastante reduzida, de mais de 280 Kb por transação para 53 bytes.The amount of data retrieved from the database was vastly reduced, from over 280 Kb per transaction to 53 bytes. Como resultado, o número máximo constante de pedidos por minuto subiu de cerca de 2.000 para mais de 25.000.As a result, the maximum sustained number of requests per minute was raised from around 2,000 to over 25,000.

Telemetria para o método "AggregateOnDatabaseAsync"