¿Es Entity Framework 4 una buena solución para un sitio web público con potencialmente 1000 visitas / segundo?
Según tengo entendido, EF es una solución viable para sitios web más pequeños o de intranet, pero no se puede escalar fácilmente para algo como un sitio web popular de la comunidad (sé que SO está usando LINQ to SQL, pero ... Me gustaría obtener más ejemplos / pruebas. ..)
Ahora estoy en la encrucijada de elegir un enfoque ADO.NET puro o EF4. ¿Cree que la mejora de la productividad del desarrollador con EF vale la pena el rendimiento perdido y el acceso granular de ADO.NET (con procedimientos almacenados)? Cualquier problema serio que un sitio web de alto tráfico podría enfrentar, ¿estaba usando EF?
Gracias de antemano.
entity-framework
ado.net
niaher
fuente
fuente
Respuestas:
Depende un poco de la cantidad de abstracción que necesites . Todo es un compromiso; por ejemplo, EF y NHibernate introducen una gran flexibilidad para la representación de los datos en modelos interesantes y exóticos - pero como resultado que hacen agregar una sobrecarga. Sobrecarga notable.
Si no necesita poder cambiar entre proveedores de bases de datos y diferentes diseños de tabla por cliente, y si sus datos se leen principalmente , y si no necesita poder usar el mismo modelo en EF, SSRS , ADO.NET Data Services, etc., entonces si desea un rendimiento absoluto como su medida clave, podría hacer mucho peor que mirar a Dapper . En nuestras pruebas basadas en LINQ-to-SQL y EF, encontramos que EF es significativamente más lento en términos de rendimiento de lectura sin procesar, presumiblemente debido a las capas de abstracción (entre el modelo de almacenamiento, etc.) y la materialización.
Aquí en SO, somos obsesivos-compulsivos con respecto al rendimiento en bruto, y estamos contentos de recibir el golpe de desarrollo de perder algo de abstracción para ganar velocidad. Como tal, nuestra herramienta principal para consultar la base de datos es apta . Esto incluso nos permite usar nuestro modelo LINQ-to-SQL preexistente, pero simplemente: es mucho más rápido. En las pruebas de rendimiento, es esencialmente exactamente el mismo rendimiento que escribir todo el código ADO.NET (parámetros, lectores de datos, etc.) manualmente, pero sin el riesgo de que el nombre de una columna sea incorrecto. Sin embargo, está basado en SQL (aunque es feliz usar SPROC si ese es su veneno elegido). La ventaja de esto es que no hay un procesamiento adicional involucrado, pero es un sistema para personas a las que les gusta SQL. Lo que considero: ¡no es malo!
Una consulta típica, por ejemplo, podría ser:
lo cual es conveniente, seguro para inyección, etc., pero sin toneladas de pegajoso lector de datos. Tenga en cuenta que si bien puede manejar particiones horizontales y verticales para cargar estructuras complejas, no admitirá la carga diferida (pero: somos grandes admiradores de la carga muy explícita, menos sorpresas).
Tenga en cuenta en esta respuesta que no estoy diciendo que EF no es adecuado para trabajos de gran volumen; simplemente: sé que apuesto está a la altura.
fuente
La pregunta "qué ORM debería usar" está realmente dirigida a la punta de un gran iceberg cuando se trata de la estrategia general de acceso a datos y la optimización del rendimiento en una aplicación a gran escala.
Todas las siguientes cosas ( aproximadamente en orden de importancia) afectarán el rendimiento, y todas ellas son manejadas (a veces de diferentes maneras) por la mayoría de los principales marcos de ORM:
Diseño de bases de datos y mantenimiento
Este es, por un amplio margen, el determinante más importante del rendimiento de una aplicación o sitio web basado en datos y, a menudo, totalmente ignorado por los programadores.
Si no utiliza las técnicas de normalización adecuadas, su sitio está condenado. Si no tiene claves primarias, casi todas las consultas serán lentas. Si utiliza antipatrones bien conocidos, como el uso de tablas para pares de valores clave (AKA Entity-Attribute-Value) sin una buena razón, explotará la cantidad de lecturas y escrituras físicas.
Si no aprovecha las características que le brinda la base de datos, como la compresión de páginas, el
FILESTREAM
almacenamiento (para datos binarios), lasSPARSE
columnas, lashierarchyid
jerarquías, etc. (todos los ejemplos de SQL Server), no verá nada cerca del rendimiento que podrías estar viendo.Debería comenzar a preocuparse por su estrategia de acceso a datos después de haber diseñado su base de datos y convencido de que es tan buena como sea posible, al menos por el momento.
Carga ansiosa vs. perezosa
La mayoría de los ORM usaban una técnica llamada carga diferida para las relaciones, lo que significa que, de manera predeterminada, cargará una entidad (fila de tabla) a la vez y realizará un viaje de ida y vuelta a la base de datos cada vez que necesite cargar uno o varios relacionados (externos clave) filas.
Esto no es algo bueno o malo, sino que depende de lo que realmente se va a hacer con los datos y de cuánto sabes por adelantado. A veces, la carga diferida es absolutamente lo que hay que hacer. NHibernate, por ejemplo, puede decidir no consultar nada y simplemente generar un proxy para una ID en particular. Si todo lo que necesita es la identificación, ¿por qué debería pedir más? Por otro lado, si está intentando imprimir un árbol de cada elemento individual en una jerarquía de 3 niveles, la carga diferida se convierte en una operación O (N²), lo que es extremadamente malo para el rendimiento.
Un beneficio interesante de usar "SQL puro" (es decir, consultas ADO.NET sin procesar / procedimientos almacenados) es que básicamente te obliga a pensar exactamente qué datos son necesarios para mostrar cualquier pantalla o página. ORM y características de carga perezosa no impiden que se haga esto, pero ellos no le dan la oportunidad de ser ... bueno, perezoso , y sin querer explotar el número de consultas que vaya haciendo. Por lo tanto, debe comprender las funciones de carga entusiasta de sus ORM y estar siempre atento a la cantidad de consultas que envía al servidor para cualquier solicitud de página determinada.
Almacenamiento en caché
Todos los ORM principales mantienen un caché de primer nivel, también conocido como "caché de identidad", lo que significa que si solicita la misma entidad dos veces por su ID, no requiere un segundo viaje de ida y vuelta (y si diseñó su base de datos correctamente) ) le ofrece la posibilidad de utilizar la simultaneidad optimista.
El caché L1 es bastante opaco en L2S y EF, tienes que confiar en que está funcionando. NHibernate es más explícito al respecto (
Get
/Load
vs.Query
/QueryOver
). Aún así, siempre que intente consultar por ID tanto como sea posible, debería estar bien aquí. Mucha gente se olvida del caché L1 y busca repetidamente la misma entidad una y otra vez por algo que no sea su ID (es decir, un campo de búsqueda). Si necesita hacer esto, debe guardar la ID o incluso la entidad completa para futuras búsquedas.También hay un caché de nivel 2 ("caché de consultas"). NHibernate tiene esto incorporado. Linq to SQL y Entity Framework han compilado consultas , lo que puede ayudar a reducir bastante las cargas del servidor de aplicaciones compilando la expresión de consulta en sí, pero no almacena en caché los datos. Microsoft parece considerar esto una preocupación de la aplicación en lugar de una preocupación de acceso a datos, y este es un punto débil importante tanto de L2S como de EF. No hace falta decir que también es un punto débil de SQL "en bruto". Para obtener un rendimiento realmente bueno con básicamente cualquier ORM que no sea NHibernate, debe implementar su propia fachada de almacenamiento en caché.
También hay una "extensión" de caché L2 para EF4 que está bien , pero no es realmente un reemplazo total para un caché de nivel de aplicación.
Numero de consultas
Las bases de datos relacionales se basan en conjuntos de datos. Son muy buenos para producir grandes cantidades de datos en un corto período de tiempo, pero no son tan buenos en términos de latencia de consulta porque hay una cierta cantidad de sobrecarga involucrada en cada comando. Una aplicación bien diseñada debe aprovechar los puntos fuertes de este DBMS e intentar minimizar la cantidad de consultas y maximizar la cantidad de datos en cada una.
Ahora no estoy diciendo que consulte toda la base de datos cuando solo necesita una fila. Lo que estoy diciendo es, si usted necesita las
Customer
,Address
,Phone
,CreditCard
, yOrder
filas, todo al mismo tiempo con el fin de servir a una sola página, a continuación, usted debe preguntar por todos ellos al mismo tiempo, no ejecutar cada consulta por separado. A veces es peor que eso, verá un código que consulta el mismoCustomer
registro 5 veces seguidas, primero para obtener elId
, luego elName
, luego elEmailAddress
, luego ... es ridículamente ineficiente.Incluso si necesita ejecutar varias consultas que operan en conjuntos de datos completamente diferentes, generalmente es aún más eficiente enviarlo todo a la base de datos como un "script" único y hacer que devuelva múltiples conjuntos de resultados. Lo que le preocupa es la sobrecarga, no la cantidad total de datos.
Esto puede sonar a sentido común, pero a menudo es muy fácil perder el rastro de todas las consultas que se ejecutan en varias partes de la aplicación; su proveedor de membresía consulta las tablas de usuario / rol, su acción de encabezado consulta el carrito de compras, su acción de menú consulta la tabla de mapa del sitio, su acción de barra lateral consulta la lista de productos destacados y luego su página se divide en algunas áreas autónomas separadas que consulta las tablas Historial de pedidos, Recientemente visto, Categoría e Inventario por separado, y antes de que te des cuenta, estás ejecutando 20 consultas antes de que puedas comenzar a servir la página. Simplemente destruye completamente el rendimiento.
Algunos marcos, y estoy pensando principalmente en NHibernate aquí, son increíblemente inteligentes al respecto y le permiten usar algo llamado futuros que agrupa consultas completas e intenta ejecutarlas todas a la vez, en el último minuto posible. AFAIK, estás solo si quieres hacer esto con cualquiera de las tecnologías de Microsoft; tienes que construirlo en la lógica de tu aplicación.
Indización, predicados y proyecciones
Al menos el 50% de los desarrolladores con los que hablo e incluso algunos DBA parecen tener problemas con el concepto de índices de cobertura. Piensan: "bueno, la
Customer.Name
columna está indexada, por lo que cada búsqueda que haga del nombre debe ser rápida". Excepto que no funciona de esa manera a menos que elName
índice cubra la columna específica que está buscando. En SQL Server, eso se haceINCLUDE
en laCREATE INDEX
declaración.Si ingenuamente lo usa en
SELECT *
todas partes, y eso es más o menos lo que hará cada ORM a menos que especifique explícitamente lo contrario utilizando una proyección, entonces el DBMS puede elegir ignorar por completo sus índices porque contienen columnas no cubiertas. Una proyección significa que, por ejemplo, en lugar de hacer esto:Haces esto en su lugar:
Y esta voluntad, para la mayoría de ORM modernas, instruir sólo para ir y consultar las
Id
yName
columnas que son presumiblemente cubiertos por el índice (pero no elEmail
,LastActivityDate
o cualquier otra columnas que le pasó a pegarse allí).También es muy fácil eliminar por completo cualquier beneficio de indexación mediante el uso de predicados inapropiados. Por ejemplo:
... parece casi idéntico a nuestra consulta anterior, pero de hecho dará como resultado una tabla completa o exploración de índice porque se traduce en
LIKE '%Doe%'
. Del mismo modo, otra consulta que parece sospechosamente simple es:Suponiendo que tiene un índice
BirthDate
, este predicado tiene una buena oportunidad de volverlo completamente inútil. Nuestro programador hipotético aquí obviamente ha intentado crear una especie de consulta dinámica ("solo filtra la fecha de nacimiento si se especificó ese parámetro"), pero esta no es la forma correcta de hacerlo. Escrito así en su lugar:... ahora el motor de base de datos sabe cómo parametrizar esto y hacer una búsqueda de índice. Un cambio menor, aparentemente insignificante, en la expresión de la consulta puede afectar drásticamente el rendimiento.
Desafortunadamente, LINQ en general hace que sea demasiado fácil escribir consultas malas como esta porque a veces los proveedores pueden adivinar lo que estaba tratando de hacer y optimizar la consulta, y a veces no lo son. Por lo tanto, terminas con resultados frustrantemente inconsistentes que habrían sido cegadoramente obvios (para un DBA experimentado, de todos modos) si hubieras escrito un simple SQL antiguo.
Básicamente, todo se reduce al hecho de que realmente tienes que vigilar de cerca tanto el SQL generado como los planes de ejecución a los que conducen, y si no estás obteniendo los resultados que esperas, no temas pasar por alto Capa ORM de vez en cuando y codifique manualmente el SQL. Esto se aplica a cualquier ORM, no solo a EF.
Transacciones y Bloqueo
¿Necesita mostrar datos que estén actualizados hasta el milisegundo? Tal vez, depende, pero probablemente no. Lamentablemente, Entity Framework no te da
nolock
, solo puedes usarloREAD UNCOMMITTED
a nivel de transacción (no a nivel de tabla). De hecho, ninguno de los ORM es particularmente confiable al respecto; si desea hacer lecturas sucias, debe desplegarse al nivel SQL y escribir consultas ad-hoc o procedimientos almacenados. Entonces, de nuevo, todo se reduce a lo fácil que es hacerlo dentro del marco.Entity Framework ha recorrido un largo camino en este sentido: la versión 1 de EF (en .NET 3.5) fue horrible, hizo increíblemente difícil romper la abstracción de "entidades", pero ahora tiene ExecuteStoreQuery y Translate , por lo que es realmente No está mal. Haz amigos con estos chicos porque los usarás mucho.
También está el problema del bloqueo de escritura y los puntos muertos y la práctica general de mantener los bloqueos en la base de datos durante el menor tiempo posible. A este respecto, la mayoría de los ORM (incluido Entity Framework) en realidad tienden a ser mejores que SQL sin formato porque encapsulan la unidad del patrón de trabajo , que en EF es SaveChanges . En otras palabras, puede "insertar" o "actualizar" o "eliminar" entidades al contenido de su corazón, siempre que lo desee, con la certeza de que no se introducirán cambios en la base de datos hasta que confirme la unidad de trabajo.
Tenga en cuenta que un UOW no es análogo a una transacción de larga duración. El UOW aún utiliza las características optimistas de concurrencia del ORM y rastrea todos los cambios en la memoria . No se emite una sola declaración DML hasta la confirmación final. Esto mantiene los tiempos de transacción lo más bajo posible. Si crea su aplicación utilizando SQL sin formato, es bastante difícil lograr este comportamiento diferido.
Lo que esto significa específicamente para EF: haga que sus unidades de trabajo sean lo más gruesas posible y no las comprometa hasta que sea absolutamente necesario. Haga esto y terminará con una contención de bloqueo mucho menor que la que usaría los comandos individuales de ADO.NET en momentos aleatorios.
En conclusión:
EF está completamente bien para aplicaciones de alto tráfico / alto rendimiento, al igual que cualquier otro marco está bien para aplicaciones de alto tráfico / alto rendimiento. Lo que importa es cómo lo usas. Aquí hay una comparación rápida de los marcos más populares y las características que ofrecen en términos de rendimiento (leyenda: N = No compatible, P = Parcial, Y = sí / compatible):
Como puede ver, a EF4 (la versión actual) no le va demasiado mal, pero probablemente no sea el mejor si el rendimiento es su principal preocupación. NHibernate es mucho más maduro en esta área e incluso Linq to SQL proporciona algunas características que mejoran el rendimiento que EF todavía no ofrece. Raw ADO.NET a menudo va a ser más rápido para escenarios de acceso a datos muy específicos , pero, cuando reúne todas las piezas, realmente no ofrece muchos beneficios importantes que obtiene de los diversos marcos.
Y, solo para asegurarme por completo de que sueno como un disco rayado , nada de esto importa en lo más mínimo si no diseñas tus bases de datos, aplicaciones y estrategias de acceso a datos correctamente. Todos los elementos en el cuadro anterior son para mejorar el rendimiento más allá de la línea de base; la mayoría de las veces, la línea de base en sí misma es lo que necesita más mejoras.
fuente
Editar: según la excelente respuesta de @Aaronaught, estoy agregando algunos puntos al rendimiento de orientación con EF. Esos nuevos puntos tienen el prefijo Edit.
La mayor mejora en el rendimiento en sitios web de alto tráfico se logra mediante el almacenamiento en caché (= en primer lugar, evitar el procesamiento del servidor web o las consultas de la base de datos) seguido del procesamiento asincrónico para evitar el bloqueo de subprocesos mientras se realizan las consultas de la base de datos.
No hay una respuesta a prueba de balas a su pregunta porque siempre depende de los requisitos de aplicación y de la complejidad de las consultas. La verdad es que la productividad del desarrollador con EF oculta la complejidad que, en muchos casos, conduce a un uso incorrecto de EF y un rendimiento terrible. La idea de que puede exponer una interfaz abstracta de alto nivel para el acceso a datos y que funcione sin problemas en todos los casos no funciona. Incluso con ORM debe saber qué está sucediendo detrás de la abstracción y cómo usarla correctamente.
Si no tiene experiencia previa con EF, enfrentará muchos desafíos al tratar con el rendimiento. Puede cometer muchos más errores al trabajar con EF en comparación con ADO.NET. También hay una gran cantidad de procesamiento adicional realizado en EF, por lo que EF siempre será significativamente más lento que ADO.NET nativo; eso es algo que puede medir con una simple aplicación de prueba de concepto.
Si desea obtener el mejor rendimiento de EF, lo más probable es que tenga que:
MergeOption.NoTracking
SqlCommand
contiene múltiples inserciones, actualizaciones o eliminaciones, pero con EF cada comando se ejecutará en un viaje de ida y vuelta por separado a la base de datos.GetByKey
en ObjectContext API oFind
en DbContext API) para consultar primero el caché. Si usa Linq-to-persons o ESQL, creará un viaje de ida y vuelta a la base de datos y luego devolverá la instancia existente del caché.No estoy seguro si SO todavía está usando L2S. Desarrollaron un nuevo ORM de código abierto llamado Dapper y creo que el punto principal detrás de este desarrollo fue aumentar el rendimiento.
fuente