Antipatrón Extraneous FetchingExtraneous Fetching antipattern

Recuperar más datos de los necesarios para una operación comercial puede dar lugar a una sobrecarga de E/S innecesarias y reducir la capacidad de respuesta.Retrieving more data than needed for a business operation can result in unnecessary I/O overhead and reduce responsiveness.

Descripción del problemaProblem description

Este antipatrón puede producirse si la aplicación intenta minimizar las solicitudes de E/S recuperando todos los datos que podría necesitar.This antipattern can occur if the application tries to minimize I/O requests by retrieving all of the data that it might need. A menudo, es el resultado de realizar una compensación excesiva para el antipatrón Chatty I/O.This is often a result of overcompensating for the Chatty I/O antipattern. Por ejemplo, una aplicación podría capturar los detalles de todos los productos de una base de datos.For example, an application might fetch the details for every product in a database. Pero puede que el usuario solo tenga un subconjunto de los detalles (algunos pueden no ser pertinentes para los clientes) y probablemente no necesite ver todos los productos a la vez.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. Incluso si el usuario está explorando el catálogo completo, tendría sentido paginar los resultados y mostrar 20 a la vez, por ejemplo.Even if the user is browsing the entire catalog, it would make sense to paginate the results—showing 20 at a time, for example.

Otro origen de este problema es seguir prácticas de programación o diseño deficientes.Another source of this problem is following poor programming or design practices. Por ejemplo, el código siguiente utiliza Entity Framework para capturar los detalles completos para todos los productos.For example, the following code uses Entity Framework to fetch the complete details for every product. A continuación, filtra los resultados para devolver únicamente un subconjunto de los campos y descarta el resto.Then it filters the results to return only a subset of the fields, discarding the rest. Puede encontrar el ejemplo completo aquí.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);
    }
}

En el ejemplo siguiente, la aplicación recupera los datos para llevar a cabo una agregación que puede realizar la base de datos en su lugar.In the next example, the application retrieves data to perform an aggregation that could be done by the database instead. La aplicación calcula el total de ventas obteniendo todos los registros de todos los pedidos vendidos y, a continuación, calculando la suma de esos registros.The application calculates total sales by getting every record for all orders sold, and then computing the sum over those records. Puede encontrar el ejemplo completo aquí.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);
    }
}

En el ejemplo siguiente se muestra un pequeño problema debido a la forma en que Entity Framework usa 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();

La aplicación está intentando buscar productos con un valor de SellStartDate de más de una semana.The application is trying to find products with a SellStartDate more than a week old. En la mayoría de los casos, LINQ to Entities traduciría una cláusula where en una instrucción SQL que la base de datos ejecuta.In most cases, LINQ to Entities would translate a where clause to a SQL statement that is executed by the database. No obstante, en este caso, LINQ to Entities no puede asignar el método AddDays a SQL.In this case, however, LINQ to Entities cannot map the AddDays method to SQL. En su lugar, se devuelve cada fila de la tabla Product y los resultados se filtran en la memoria.Instead, every row from the Product table is returned, and the results are filtered in memory.

La llamada a AsEnumerable es una sugerencia de que hay un problema.The call to AsEnumerable is a hint that there is a problem. Este método convierte los resultados a una interfaz IEnumerable.This method converts the results to an IEnumerable interface. Aunque IEnumerable admite el filtrado, este se realiza en el cliente, no en la base de datos.Although IEnumerable supports filtering, the filtering is done on the client side, not the database. De forma predeterminada, LINQ to Entities usa IQueryable, que pasa la responsabilidad del filtrado al origen de datos.By default, LINQ to Entities uses IQueryable, which passes the responsibility for filtering to the data source.

Procedimiento para corregir el problemaHow to fix the problem

Evite capturar grandes volúmenes de datos que puedan quedarse obsoletos rápidamente o podrían descartarse, y capture solo los necesarios para realizar la operación.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.

En lugar de obtener todas las columnas de una tabla y, a continuación, filtrarlas después, seleccione las que necesite de la base de datos.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);
    }
}

De igual forma, realice las agregaciones en la base de datos y no en memoria de la aplicación.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);
    }
}

Al utilizar Entity Framework, asegúrese de que las consultas LINQ se resuelven mediante la interfaz IQueryable y no IEnumerable.When using Entity Framework, ensure that LINQ queries are resolved using the IQueryableinterface and not IEnumerable. Puede que tenga que ajustar la consulta para utilizar solo las funciones que se puedan asignar al origen de datos.You may need to adjust the query to use only functions that can be mapped to the data source. El ejemplo anterior se puede refactorizar para quitar el método AddDays de la consulta, lo que permite que la base de datos realice el filtrado.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();

ConsideracionesConsiderations

  • En algunos casos, puede mejorar el rendimiento mediante la partición horizontal de los datos.In some cases, you can improve performance by partitioning data horizontally. Si diferentes operaciones acceden a atributos distintos de los datos, crear particiones horizontales puede reducir la contención.If different operations access different attributes of the data, horizontal partitioning may reduce contention. A menudo, la mayoría de las operaciones se ejecutan en un pequeño subconjunto de los datos, de modo que diseminar esta carga puede mejorar el rendimiento.Often, most operations are run against a small subset of the data, so spreading this load may improve performance. Consulte Creación de particiones de datos.See Data partitioning.

  • Para las operaciones que tienen que admitir consultas sin limitar, implemente la paginación y capture solo un número limitado de entidades a la vez.For operations that have to support unbounded queries, implement pagination and only fetch a limited number of entities at a time. Por ejemplo, si un cliente está explorando un catálogo de productos, puede mostrar una página de resultados al mismo tiempo.For example, if a customer is browsing a product catalog, you can show one page of results at a time.

  • Cuando sea posible, aproveche las características integradas en el almacén de datos.When possible, take advantage of features built into the data store. Por ejemplo, las bases de datos SQL suelen proporcionar funciones de agregado.For example, SQL databases typically provide aggregate functions.

  • Si utiliza un almacén de datos que no es compatible con una función determinada, como la agregación, podría almacenar el resultado calculado en otra parte, actualizar el valor a medida que los registros se agregan o se actualizan, de forma que la aplicación no tenga que volver a calcular el valor cada vez que sea necesario.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.

  • Si ve que las solicitudes recuperan un gran número de campos, examine el código fuente para determinar si todos son necesarios.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. A veces, estas solicitudes son el resultado de una consulta SELECT * con un diseño inadecuado.Sometimes these requests are the result of poorly designed SELECT * query.

  • De forma similar, las solicitudes que recuperan un gran número de entidades pueden ser un indicio de que la aplicación no filtra los datos correctamente.Similarly, requests that retrieve a large number of entities may be sign that the application is not filtering data correctly. Compruebe que todas estas entidades sean necesarias.Verify that all of these entities are needed. Use el filtrado de la base de datos si es posible, por ejemplo, mediante cláusulas WHERE de SQL.Use database-side filtering if possible, for example, by using WHERE clauses in SQL.

  • Descargar el procesamiento en la base de datos no siempre es la mejor opción.Offloading processing to the database is not always the best option. Solo puede usar esta estrategia cuando la base de datos se haya diseñado u optimizado para realizar esta acción.Only use this strategy when the database is designed or optimized to do so. La mayor parte de sistemas de base de datos están muy optimizados para determinadas funciones, pero no están diseñados para actuar como motores de aplicaciones de uso general.Most database systems are highly optimized for certain functions, but are not designed to act as general-purpose application engines. Para más información, consulte el antipatrón Busy Database.For more information, see the Busy Database antipattern.

Procedimiento para detectar el problemaHow to detect the problem

Algunos síntomas del antipatrón Extraneous Fetching son una latencia elevada y un rendimiento bajo.Symptoms of extraneous fetching include high latency and low throughput. Si los datos se recuperan de un almacén de datos, también es probable que se dé una mayor contención.If the data is retrieved from a data store, increased contention is also probable. Es probable que los usuarios finales informen de tiempos de respuesta prolongados o errores provocados por el agotamiento del tiempo de espera de los servicios. Estos errores podrían devolver errores HTTP 500 (servidor interno) o errores HTTP 503 (servicio no disponible).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. Examine los registros de eventos del servidor web, que probablemente contengan información más detallada sobre las causas y las circunstancias de los errores.Examine the event logs for the web server, which likely contain more detailed information about the causes and circumstances of the errors.

Tanto los síntomas de este antipatrón como algunos de los datos telemetría obtenidos podrían ser muy similares a los del antipatrón Monolithic Persistence.The symptoms of this antipattern and some of the telemetry obtained might be very similar to those of the Monolithic Persistence antipattern.

Puede realizar los pasos siguientes para ayudar a identificar la causa:You can perform the following steps to help identify the cause:

  1. Identifique las cargas de trabajo o transacciones lentas realizando pruebas de carga, supervisando los procesos o con otros métodos de captura de datos de instrumentación.Identify slow workloads or transactions by performing load-testing, process monitoring, or other methods of capturing instrumentation data.
  2. Observe los patrones de comportamiento mostrados por el sistema.Observe any behavioral patterns exhibited by the system. ¿Hay límites determinados en cuanto a transacciones por segundo o en el volumen de los usuarios?Are there particular limits in terms of transactions per second or volume of users?
  3. Ponga en correlación las instancias de las cargas de trabajo de baja velocidad con los patrones de comportamiento.Correlate the instances of slow workloads with behavioral patterns.
  4. Identifique los almacenes de datos que se van a usar.Identify the data stores being used. Para cada origen de datos, ejecute una telemetría de nivel inferior para observar el comportamiento de las operaciones.For each data source, run lower-level telemetry to observe the behavior of operations.
  5. Identifique las consultas de ejecución lenta que hacen referencia a estos orígenes de datos.Identify any slow-running queries that reference these data sources.
  6. Realice un análisis específico de los recursos de las consultas de ejecución lenta y determine cómo se usan y consumen los datos.Perform a resource-specific analysis of the slow-running queries and ascertain how the data is used and consumed.

Busque alguno de estos síntomas:Look for any of these symptoms:

  • Solicitudes de E/S frecuentes y grandes realizadas en el mismo almacén de datos o de recursos.Frequent, large I/O requests made to the same resource or data store.
  • Contención en un almacén de datos o un recurso compartido.Contention in a shared resource or data store.
  • Una operación que reciba con frecuencia grandes volúmenes de datos a través de la red.An operation that frequently receives large volumes of data over the network.
  • Aplicaciones y servicios que empleen mucho tiempo esperando a que las operaciones de E/S se completen.Applications and services spending significant time waiting for I/O to complete.

Diagnóstico de ejemploExample diagnosis

En las secciones siguientes se aplican estos pasos a los ejemplos anteriores.The following sections apply these steps to the previous examples.

Identificación de las cargas de trabajo de baja velocidadIdentify slow workloads

Este gráfico muestra los resultados de rendimiento de una prueba de carga que simula hasta 400 usuarios que ejecutan simultáneamente el método GetAllFieldsAsync mostrado anteriormente.This graph shows performance results from a load test that simulated up to 400 concurrent users running the GetAllFieldsAsync method shown earlier. El rendimiento disminuye lentamente a medida que aumenta la carga.Throughput diminishes slowly as the load increases. El tiempo promedio de respuesta aumenta cuando aumenta la carga de trabajo.Average response time goes up as the workload increases.

Resultados de la prueba de carga para el método GetAllFieldsAsync

Una prueba de carga para la operación AggregateOnClientAsync muestra un patrón similar.A load test for the AggregateOnClientAsync operation shows a similar pattern. El volumen de solicitudes es razonablemente estable.The volume of requests is reasonably stable. El tiempo promedio de respuesta aumenta la carga de trabajo, aunque más lentamente que en el gráfico anterior.The average response time increases with the workload, although more slowly than the previous graph.

Resultados de la prueba de carga para el método AggregateOnClientAsync

Ponga en correlación de las cargas de trabajo de baja velocidad con los patrones de comportamientoCorrelate slow workloads with behavioral patterns

La correlación entre períodos regulares de uso elevado y una disminución del rendimiento pueden indicar áreas problemáticas.Any correlation between regular periods of high usage and slowing performance can indicate areas of concern. Examine detenidamente el perfil de rendimiento de la funcionalidad que parece ejecutarse con lentitud, para determinar si coincide con las pruebas de carga realizadas anteriormente.Closely examine the performance profile of functionality that is suspected to be slow running, to determine whether it matches the load testing performed earlier.

Realice la prueba de carga de la misma funcionalidad con cargas de usuario paso a paso, para buscar el punto donde el rendimiento disminuye de forma significativa o se colapsa por completo.Load test the same functionality using step-based user loads, to find the point where performance drops significantly or fails completely. Si ese punto cae dentro de los límites de su uso real esperado, examine cómo se implementa la funcionalidad.If that point falls within the bounds of your expected real-world usage, examine how the functionality is implemented.

Un funcionamiento lento no es necesariamente un problema, si no ocurre cuando el sistema está bajo presión, en un momento crítico y no afecta negativamente al rendimiento de otras operaciones 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 ejemplo, generar estadísticas de funcionamiento mensuales podría ser una operación de ejecución prolongada, pero probablemente se pueda realizar como un proceso por lotes y ejecutarse como un trabajo de prioridad baja.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 otro lado, que los clientes consulten el catálogo de productos es una operación esencial para el negocio.On the other hand, customers querying the product catalog is a critical business operation. Céntrese en la telemetría generada por estas operaciones críticas para ver cómo varía el rendimiento durante los períodos de uso elevado.Focus on the telemetry generated by these critical operations to see how the performance varies during periods of high usage.

Identificación de los orígenes de datos en las cargas de trabajo de baja velocidadIdentify data sources in slow workloads

Si sospecha que un servicio está teniendo un rendimiento bajo debido al modo en que recupera los datos, investigue cómo interactúa la aplicación con los repositorios 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. Supervise el sistema real para ver a qué orígenes se tiene acceso durante los períodos de bajo rendimiento.Monitor the live system to see which sources are accessed during periods of poor performance.

Para cada origen de datos, instrumente el sistema para capturar lo siguiente:For each data source, instrument the system to capture the following:

  • La frecuencia con que se tiene acceso a cada almacén de datos.The frequency that each data store is accessed.
  • El volumen de datos que entran y salen en el almacén de datos.The volume of data entering and exiting the data store.
  • La temporalización de estas operaciones, especialmente la latencia de las solicitudes.The timing of these operations, especially the latency of requests.
  • La naturaleza y la frecuencia de los errores que se producen al tener acceso a cada almacén de datos bajo una carga típica.The nature and rate of any errors that occur while accessing each data store under typical load.

Compare esta información con el volumen de datos devueltos por la aplicación al cliente.Compare this information against the volume of data being returned by the application to the client. Realice un seguimiento de la relación entre el volumen de datos devueltos por el almacén de datos y el volumen devuelto al cliente.Track the ratio of the volume of data returned by the data store against the volume of data returned to the client. Si hay grandes diferencias, investigue para determinar si la aplicación está recuperando datos que no sean necesarios.If there is any large disparity, investigate to determine whether the application is fetching data that it doesn't need.

Es posible que pueda capturar estos datos observando el sistema real y siguiendo el ciclo de vida de cada solicitud de usuario, o que pueda modelar una serie de cargas de trabajo sintéticas y ejecutarlas en un sistema de prueba.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.

Los gráficos siguientes muestran la telemetría capturada mediante APM de New Relic durante una prueba de carga del método GetAllFieldsAsync.The following graphs show telemetry captured using New Relic APM during a load test of the GetAllFieldsAsync method. Tenga en cuenta la diferencia entre los volúmenes de los datos recibidos de la base de datos y las respuestas HTTP correspondientes.Note the difference between the volumes of data received from the database and the corresponding HTTP responses.

Telemetría para el método “GetAllFieldsAsync“

Para cada solicitud, la base de datos devolvió 80 503 bytes, pero la respuesta al cliente solo contenía 19 855 bytes, aproximadamente un 25 % del tamaño de la respuesta de la base de datos.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. El tamaño de los datos devueltos al cliente puede variar según el formato.The size of the data returned to the client can vary depending on the format. Para esta prueba de carga, el cliente solicitó datos JSON.For this load test, the client requested JSON data. La prueba independiente con XML (que no se muestra) tenía un tamaño de respuesta de 35 655 bytes o el 44 % del tamaño de la respuesta de la base de datos.Separate testing using XML (not shown) had a response size of 35,655 bytes, or 44% of the size of the database response.

La prueba de carga para el método AggregateOnClientAsync muestra resultados más extremos.The load test for the AggregateOnClientAsync method shows more extreme results. En este caso, cada prueba realizó una consulta que recuperó más de 280 Kb de datos de la base de datos, pero la respuesta JSON fue solo de 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. La amplia disparidad se debe a que el método calcula un resultado total a partir de un gran volumen de datos.The wide disparity is because the method calculates an aggregated result from a large volume of data.

Telemetría para el método “AggregateOnClientAsync“

Identificación y análisis de las consultas de baja velocidadIdentify and analyze slow queries

Busque las consultas de base de datos que consuman la mayoría de los recursos y tarden más tiempo en ejecutarse.Look for database queries that consume the most resources and take the most time to execute. Puede agregar instrumentación para buscar las horas de inicio y finalización de muchas operaciones de base de datos.You can add instrumentation to find the start and completion times for many database operations. Muchos de los almacenes de datos también proporcionan información detallada sobre cómo se realizan y optimizan las consultas.Many data stores also provide in-depth information on how queries are performed and optimized. Por ejemplo, el panel Rendimiento de las consultas en el portal de administración de Azure SQL Database permite seleccionar una consulta y ver la información detallada del rendimiento en tiempo de ejecución.For example, the Query Performance pane in the Azure SQL Database management portal lets you select a query and view detailed runtime performance information. Esta es la consulta generada por la operación GetAllFieldsAsync:Here is the query generated by the GetAllFieldsAsync operation:

El panel Detalles de la consulta del portal de administración de Windows Azure SQL Database

Implementación de la solución y comprobación del resultadoImplement the solution and verify the result

Después de cambiar el método GetRequiredFieldsAsync para utilizar una instrucción SELECT en la base de datos, las pruebas de carga mostraban los resultados siguientes.After changing the GetRequiredFieldsAsync method to use a SELECT statement on the database side, load testing showed the following results.

Resultados de las pruebas de carga para el método GetRequiredFieldsAsyn

Esta prueba de carga usaba la misma implementación y la misma carga de trabajo simulada de 400 usuarios simultáneos anterior.This load test used the same deployment and the same simulated workload of 400 concurrent users as before. El gráfico muestra una latencia mucho menor.The graph shows much lower latency. El tiempo de respuesta aumenta con la carga a aproximadamente 1,3 segundos, en comparación con los 4 segundos del caso anterior.Response time rises with load to approximately 1.3 seconds, compared to 4 seconds in the previous case. El rendimiento también es superior a 350 solicitudes por segundo, en comparación con los de 100 de antes.The throughput is also higher at 350 requests per second compared to 100 earlier. El volumen de datos recuperados de la base de datos ahora coincide más con el tamaño de los mensajes de respuesta HTTP.The volume of data retrieved from the database now closely matches the size of the HTTP response messages.

Telemetría para el método “GetRequiredFieldsAsync“

Las pruebas de carga mediante el método AggregateOnDatabaseAsync generan los siguientes resultados:Load testing using the AggregateOnDatabaseAsync method generates the following results:

Resultados de la prueba de carga para el método AggregateOnDatabaseAsync

El tiempo de respuesta promedio ahora es mínimo.The average response time is now minimal. Se trata de una mejora del orden de magnitud en el rendimiento, provocada principalmente por la gran reducción de las operaciones de E/S desde la base de datos.This is an order of magnitude improvement in performance, caused primarily by the large reduction in I/O from the database.

Aquí está la telemetría correspondiente para el método AggregateOnDatabaseAsync.Here is the corresponding telemetry for the AggregateOnDatabaseAsync method. La cantidad de datos recuperados de la base de datos se ha reducido considerablemente desde más de 280 Kb por transacción a 53 bytes.The amount of data retrieved from the database was vastly reduced, from over 280 Kb per transaction to 53 bytes. Como resultado, la cantidad máxima sostenida de solicitudes por minuto se ha ampliado de alrededor de 2 000 a más de 25 000.As a result, the maximum sustained number of requests per minute was raised from around 2,000 to over 25,000.

Telemetría para el método “AggregateOnDatabaseAsync“