Rendimiento de consultas con LINQ y Entity Framework

entityGran parte de los problemas de rendimiento, al menos los que más llamativos, suelen estar ocasionados por consultas sobre la base datos en las que en un principio no se ha estudiado una estrategia de consumo de dichos datos. Con la llegada de los ORM como Entity Framework este tipo de consultas pasan aún más inadvertidas. Vamos a verlo a través de un ejemplo.

Imaginemos un modelo compuesto por clientes, facturas y artículos. Un cliente puede tener varias facturas y cada factura varios artículos:

rendimiento1

Imaginemos la siguiente consulta que trata de visualizar, para cada artículo existente, los siguientes datos:

  • Nombre del cliente
  • Nombre del artículo
  • Precio del artículo
  • Identificador de la factura

Si creamos un test que nos permita realizar esta consulta y escribirla en la consola sería algo así:

rendimiento2

Para poder medir el rendimiento de la consulta, vamos a crear el siguiente contenido en la base de datos:

  • 10 clientes
  • Por cada cliente 100 facturas
  • Por cada factura 100 artículos

El total del conjunto de datos es de (100 x 100 x 10) = 100.000 líneas. Si ejecutamos el test sobre este conjunto de datos obtenemos que el test tarda en ejecutarse 4 segundos, que es un tiempo bastante considerable.

rendimiento3

En este momento deberíamos parar y plantearnos cuál es el motivo de tanto retraso. Si usamos una herramienta de “profiling” para ver qué está ocurriendo, en mi caso será SQL Server Profiler, podemos ver en nuestra consulta lo siguiente:

rendimiento4

El resultado es que se han realizado 1011 peticiones al servidor de base de datos para recuperar la consulta completa. Esto equivale a 100 * 10 + 10 + 1. Es decir, la primera consulta pidiendo el listado de clientes  y las sucesivas, en las que para cada cliente se han pedido en un bloque sus facturas y para cada factura el conjunto de artículos:

rendimiento-consultas-5

Si depuramos y analizamos la instancia de la clase cliente veremos que es un Proxy de Entity Framework, ya que aparece con el nombre de:

System.Data.Entity.DynamicProxies.Customer_{código Hash}.

rendimiento-consultas-6

Esto significa que Entity Framework sustituye las instancias por proxies, para monitorizar aquellas propiedades que están enganchadas a la base de datos, en nuestro caso facturas y artículos. Si no usamos estas propiedades en nuestro código no se volverá a llamar a la base de datos. Si por el contrario usamos la propiedad facturas, Entity Framework recibirá una notificación de que debe cargar las facturas de dicho cliente y realizará una consulta a base de datos para ello. Puesto que en el siguiente bucle for consultamos de nuevo los artículos para cada factura Entity Framework volverá a recibir otra petición vía proxy para traer los datos de la propiedad Items de la clase Invoice.

Esto se traduce en sucesivas peticiones a la base de datos, sobrecargando el motor de base de datos y obligando a nuestro programa a esperar la latencia de cada petición que hacemos de datos.

Una posible optimización es intentar pedir los datos concretos a Entity Framework para que componga la consulta optimizada y evitemos realizar múltiples peticiones al servidor:

rendimiento-consultas-7

Si ejecutamos de nuevo ambos test podemos comprobar que la diferencia es de 4 segundos a 1 segundo, obteniendo exactamente los mismos datos.

rendimiento-consultas-8

Si volvemos a la herramienta de Profiler, podemos ver que esta vez sólo se ha ejecutado una consulta anidada pidiendo la composición de los datos según lo hemos especificado en la consulta Select de LINQ.

Estos son los datos mostrados en la ventana:

rendimiento-consultas-9

Y la consulta resultante es la siguiente:

rendimiento-consultas-10

Con un sencillo cambio hemos obtenido una mejora de un 75%. Si queréis calcular porcentajes de mejora es simple:

tiempo anteriortiempo nuevo  / tiempo anterior x 100

En nuestro caso: 4-1/4 = 3/4 = 0,75 x 100 = 75%

Como recomendación, es bueno antes de dar por cerrada una consulta de datos ver que está ocurriendo en ella a través de las herramientas de profiling y hacer pruebas de carga que simulen la cantidad real de datos que vamos a manejar para no llevarnos sorpresas. Otras veces no nos queda otra opción que plantearnos la consulta o consultas de otra manera, para que el usuario tenga una buena experiencia en cuanto a usabilidad de la aplicación.

David RuizDavid Ruiz es Licenciado en Ingeniería informática, con más de 10 años de experiencia orientados al diseño, análisis y desarrollo de soluciones empresariales basadas en tecnologías Microsoft y a la supervisión de equipos de desarrollo, aplicando patrones, estándares y métricas de calidad. Desde 2012 ejerce como arquitecto de software dentro de la Unidad de Soluciones Microsoft de Sogeti España.

Si quieres saber más sobre los servicios de desarrollo de proyectos de TI, puedes consultar la web de Sogeti.

4 thoughts

  1. Gracias, muy aclarador el artículo. Por fín entiendo mejor qué son los proxies y qué peligro tiene usar un tipo de carga u otra.
    No entiendo como Gabi dice que esto “no es un problema”!

    Me gusta

    1. Claramente este no es un problema “real”. Los problemas reales son bastante más complejos y enrevesados como para explicarlos de forma clara y sencilla y perderse en detalles técnicos que esconderían el trasfondo real del artículo que es, sencillamente, que detrás de cada consulta a base de datos hay que saber que ocurre en pro de la experiencia de usuario y la calidad del software.
      Añadir que en aplicaciones con miles de usuarios, estos temas no solo son un problema, sino un problema crítico al igual que en cualquier aplicación empresarial.
      Más allá de esta crítica poco constructiva, estaría bien que explicases porque no es un problema que, a mi entender, lleva dándome trabajo muchos años.
      Respecto al test unitario, yo también me alegro de haberlo escrito .

      Me gusta

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s