tutorial net mvc framework first español asp c# asp.net performance entity-framework entity-framework-6

c# - net - entity framework tutorial español



La creación de una instancia de Entity Framework Context se ralentiza bajo carga (1)

Aquí es donde empezaría a resolver el problema, sin pasar a una solución más amigable para las empresas.

Our context is a fairly large code-first context with around 300 entities

Si bien EF ha mejorado mucho con el tiempo, aún así empezaría a considerar seriamente cortar las cosas una vez que llegues a 100 entidades (en realidad, empezaría mucho antes de eso, pero eso parece ser un número mágico que muchas personas han declarado: ¿consenso?). ¿Piensa que es un diseño para "contextos", pero usa la palabra "dominio" en su lugar? ¿De esa manera puede vender a sus ejecutivos que está aplicando "diseño impulsado por dominio" para arreglar la aplicación? Tal vez esté diseñando para futuros "microservicios" y luego use dos palabras de moda en un solo párrafo. ;-)

No soy un gran fan de EF en el espacio de Enterprise, por lo que tiendo a evitarlo para aplicaciones de gran escala o alto rendimiento. Su experiencia puede ser diferente. Para SMB, es probable que esté perfectamente bien. Sin embargo, me encuentro con clientes que lo utilizan.

No estoy seguro de que las siguientes ideas estén completamente actualizadas, pero son otras cosas que consideraría, basadas en la experiencia.

  • Pre-gen sus puntos de vista. Son la parte más cara de la consulta. Esto ayudará aún más con los modelos grandes.
  • Mueva su modelo a un conjunto separado. No es tanto una cosa mejor que una de mis mascotas en la organización de códigos.
  • Examine su aplicación, modelo, para las posibilidades de almacenamiento en caché. El almacenamiento en caché del plan de consultas a menudo puede restar bastante tiempo.
  • Utilice CompileQuery.
  • Cuando sea posible, utilice NoTracking. Este es un gran ahorro, si no necesita la función.

Parece que ya está ejecutando algún tipo de generador de perfiles en la aplicación, por lo que voy a suponer que también examinó sus consultas de SQL y las posibles mejoras de rendimiento. Sí, sé que ese no es el problema que está buscando resolver, pero es algo que puede contribuir a la totalidad del problema desde la perspectiva del usuario.

En respuesta al comentario de @ WiktorZichia acerca de no responder la pregunta sobre el problema de rendimiento, la mejor manera de deshacerse de este tipo de problemas en un Sistema Empresarial es deshacerse de Entity Framework. Hay concesiones en cada decisión. EF es una gran abstracción y acelera el desarrollo. Pero viene con una sobrecarga innecesaria que puede dañar los sistemas a escala. Ahora, técnicamente, todavía no respondí a la pregunta "¿Cómo resuelvo este problema en la forma en que lo estoy tratando de resolver?", Por lo que esto todavía podría verse como un fracaso.

Notamos que algunas llamadas de servicio web muy pequeñas demoraban más de lo que esperábamos. Hicimos un poco de investigación y pusimos algunos temporizadores en su lugar y lo redujimos a la creación de una instancia de nuestro Entity Framework 6 DbContext. No la consulta en sí, solo la creación del contexto. Desde entonces, puse un poco de registro para ver en promedio cuánto tiempo lleva en realidad crear una instancia de nuestro DbContext y parece que fue alrededor de 50 ms.

Después de que se calienta la aplicación, la creación de contexto no es lenta. Después de un reciclaje de la aplicación, comienza a 2-4 ms (que es lo que vemos en nuestros entornos de desarrollo). La creación de contexto parece disminuir con el tiempo. Durante las próximas dos horas, se arrastrará hasta el rango de 50-80 ms y se estabilizará.

Nuestro contexto es un primer contexto de código bastante grande con alrededor de 300 entidades, incluidas algunas relaciones bastante complejas entre algunas de las entidades. Estamos ejecutando EF 6.1.3. Estamos haciendo un "un contexto por solicitud", pero para la mayoría de nuestras llamadas a la API web solo se realizan una o dos consultas. Crear un contexto tomando 60 + ms, y luego ejecutar una consulta de 1 ms es un poco insatisfactorio. Tenemos alrededor de 10 mil solicitudes por minuto, por lo que no somos un sitio ligeramente usado.

Aquí hay una instantánea de lo que vemos. Los tiempos están en MS, la gran caída es un despliegue que recicló el dominio de la aplicación. Cada línea es uno de los 4 servidores web diferentes. Note que tampoco es siempre el mismo servidor.

Tomé un volcado de memoria para probar y desarrollar lo que está pasando y aquí están las estadísticas del montón:

00007ffadddd1d60 70821 2266272 System.Reflection.Emit.GenericFieldInfo 00007ffae02e88a8 29885 2390800 System.Linq.Enumerable+WhereSelectListIterator`2[[NewRelic.Agent.Core.WireModels.MetricDataWireModel, NewRelic.Agent.Core],[System.Single, mscorlib]] 00007ffadda7c1a0 1462 2654992 System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.Object, mscorlib],[System.Object, mscorlib]][] 00007ffadd4eccf8 83298 2715168 System.RuntimeType[] 00007ffadd4e37c8 24667 2762704 System.Reflection.Emit.DynamicMethod 00007ffadd573180 30013 3121352 System.Web.Caching.CacheEntry 00007ffadd2dc5b8 35089 3348512 System.String[] 00007ffadd6734b8 35233 3382368 System.RuntimeMethodInfoStub 00007ffadddbf0a0 24667 3749384 System.Reflection.Emit.DynamicILGenerator 00007ffae04491d8 67611 4327104 System.Data.Entity.Core.Metadata.Edm.MetadataProperty 00007ffadd4edaf0 57264 4581120 System.Signature 00007ffadd4dfa18 204161 4899864 System.RuntimeMethodHandle 00007ffadd4ee2c0 41900 5028000 System.Reflection.RuntimeParameterInfo 00007ffae0c9e990 21560 5346880 System.Data.SqlClient._SqlMetaData 00007ffae0442398 79504 5724288 System.Data.Entity.Core.Metadata.Edm.TypeUsage 00007ffadd432898 88807 8685476 System.Int32[] 00007ffadd433868 9985 9560880 System.Collections.Hashtable+bucket[] 00007ffadd4e3160 92105 10315760 System.Reflection.RuntimeMethodInfo 00007ffadd266668 493622 11846928 System.Object 00007ffadd2dc770 33965 16336068 System.Char[] 00007ffadd26bff8 121618 17335832 System.Object[] 00007ffadd2df8c0 168529 68677312 System.Byte[] 00007ffadd2d4d08 581057 127721734 System.String 0000019cf59e37d0 166894 143731666 Free Total 5529765 objects Fragmented blocks larger than 0.5 MB: Addr Size Followed by 0000019ef63f2140 2.9MB 0000019ef66cfb40 Free 0000019f36614dc8 2.8MB 0000019f368d6670 System.Data.Entity.Core.Query.InternalTrees.SimpleColumnMap[] 0000019f764817f8 0.8MB 0000019f76550768 Free 0000019fb63a9ca8 0.6MB 0000019fb644eb38 System.Data.Entity.Core.Common.Utils.Set`1[[System.Data.Entity.Core.Metadata.Edm.EntitySet, EntityFramework]] 000001a0f6449328 0.7MB 000001a0f64f9b48 System.String 000001a0f65e35e8 0.5MB 000001a0f666e2a0 System.Collections.Hashtable+bucket[] 000001a1764e8ae0 0.7MB 000001a17659d050 System.RuntimeMethodHandle 000001a3b6430fd8 0.8MB 000001a3b6501aa0 Free 000001a4f62c05c8 0.7MB 000001a4f636e8a8 Free 000001a6762e2300 0.6MB 000001a676372c38 System.String 000001a7761b5650 0.6MB 000001a776259598 System.String 000001a8763c4bc0 2.3MB 000001a8766083a8 System.String 000001a876686f48 1.4MB 000001a8767f9178 System.String 000001a9f62adc90 0.7MB 000001a9f63653c0 System.String 000001aa362b8220 0.6MB 000001aa36358798 Free

Eso parece un poco de metadatos y tipografía.

Cosas que hemos intentado:

  1. Creación de un arnés de prueba simple para replicar. Falló, mi conjetura es porque no estábamos variando el tráfico, o variando el tipo de consultas ejecutadas. Solo cargar el contexto y ejecutar un par de consultas una y otra vez no resultó en un aumento de tiempo.
  2. Hemos reducido el contexto de manera significativa, eran 500 entidades, ahora 300. No hubo una diferencia en la velocidad. Mi conjetura es porque no estábamos usando esas 200 entidades en absoluto.
  3. (Editar) Usamos SimpleInjector para crear nuestro "contexto por solicitud". Para validarlo, no es SimpleInjector. He ejecutado una instancia del Contexto solo por renovar. Los mismos tiempos de creación lenta.
  4. (Editar) Tenemos ngen''d EF. No hizo ningún impacto.

¿Qué podemos investigar más a fondo? Entiendo que el caché usado por EF es extenso para acelerar las cosas. ¿Hay más cosas en la memoria caché que retrasan la creación del contexto? ¿Hay una manera de ver exactamente qué hay en ese caché para hacer realidad algunas cosas extrañas allí? ¿Alguien sabe qué podemos hacer específicamente para acelerar la creación de contexto?

Actualización - 5/30/17

Tomé la fuente EF6 y compilé nuestra propia versión para mantener algunos tiempos. Ejecutamos un sitio bastante popular, por lo que recopilar una gran cantidad de información de tiempo es complicado y no llegué a lo que quería, pero básicamente descubrimos que toda la desaceleración proviene de este método.

public void ForceOSpaceLoadingForKnownEntityTypes() { if (!_oSpaceLoadingForced) { // Attempting to get o-space data for types that are not mapped is expensive so // only try to do it once. _oSpaceLoadingForced = true; Initialize(); foreach (var set in _genericSets.Values.Union(_nonGenericSets.Values)) { set.InternalSet.TryInitialize(); } } }

Cada iteración de esos hits foreach para cada una de las entidades definidas por un DBSet en nuestro contexto. Cada iteración es relativamente corta .1-.3 ms, pero cuando se agregan las 254 entidades, se agregó. Todavía no hemos descubierto por qué es rápido al principio y se ralentiza.