O Entity Framework 4 é uma boa solução para um site público com potencial de 1000 hits / segundo?
No meu entendimento, a EF é uma solução viável para sites menores ou de intranet, mas não seria escalável facilmente para algo como um site popular da comunidade (eu sei que o SO está usando LINQ to SQL, mas ... eu gostaria de mais exemplos / provas. ..)
Agora estou na encruzilhada da escolha de uma abordagem pura do ADO.NET ou do EF4. Você acha que a produtividade aprimorada do desenvolvedor com a EF vale o desempenho perdido e o acesso granular do ADO.NET (com procedimentos armazenados)? Algum problema sério que um site de alto tráfego pode enfrentar, estava usando a EF?
Agradeço antecipadamente.
entity-framework
ado.net
niaher
fonte
fonte
Respostas:
Depende um pouco da quantidade de abstração necessária . Tudo é um compromisso; por exemplo, EF e NHibernate introduzir uma grande flexibilidade para representar os dados em modelos interessantes e exóticos - mas como resultado eles fazer adicionar uma sobrecarga. Sobrecarga visível.
Se você não precisa alternar entre provedores de banco de dados e diferentes layouts de tabela por cliente, e se seus dados são lidos principalmente , e se você não precisa usar o mesmo modelo no EF, SSRS , ADO.NET Data Services, etc - então, se você quiser um desempenho absoluto como sua medida-chave, poderá fazer muito pior do que olhar para o dapper . Em nossos testes baseados no LINQ-to-SQL e no EF, descobrimos que o EF é significativamente mais lento em termos de desempenho de leitura bruta, provavelmente devido às camadas de abstração (entre o modelo de armazenamento etc.) e à materialização.
Aqui no SO, somos obsessivo-compulsivos em relação ao desempenho bruto e estamos felizes em sofrer o impacto do desenvolvimento de perder alguma abstração para ganhar velocidade. Como tal, nossa ferramenta principal para consultar o banco de dados é dapper . Isso nos permite usar nosso modelo LINQ-SQL pré-existente, mas simplesmente: é mais rápido. Nos testes de desempenho, é essencialmente exatamente o mesmo desempenho que escrever todo o código ADO.NET (parâmetros, leitores de dados, etc.) manualmente, mas sem o risco de errar o nome da coluna. É, no entanto, baseado em SQL (embora seja um prazer usar SPROCs se esse for o seu veneno escolhido). A vantagem disso é que não há processamento adicional envolvido, mas é um sistema para pessoas que gostam de SQL. O que eu considero: não é uma coisa ruim!
Uma consulta típica, por exemplo, pode ser:
que é conveniente, seguro para injeção, etc. - mas sem toneladas de gosma do leitor de dados. Observe que, embora possa lidar com partições horizontais e verticais para carregar estruturas complexas, ele não suporta carregamento lento (mas: somos grandes fãs de carregamento muito explícito - menos surpresas).
Observe nesta resposta que não estou dizendo que a EF não é adequada para trabalhos de alto volume; Simplesmente: eu sei que dapper depende disso.
fonte
A questão "qual ORM devo usar" está realmente direcionada para a ponta de um enorme iceberg quando se trata da estratégia geral de acesso a dados e da otimização de desempenho em um aplicativo de larga escala.
Todas as seguintes coisas ( aproximadamente em ordem de importância) afetarão a taxa de transferência e todas elas são tratadas (às vezes de maneiras diferentes) pela maioria das principais estruturas ORM existentes:
Design e Manutenção de Banco de Dados
Esse é, por uma ampla margem, o determinante mais importante da taxa de transferência de um aplicativo ou site orientado a dados, e muitas vezes totalmente ignorado pelos programadores.
Se você não usar técnicas adequadas de normalização, seu site estará condenado. Se você não tiver chaves primárias, quase todas as consultas serão lentas. Se você usar anti-padrões conhecidos, como o uso de tabelas para pares de valor-chave (AKA Entity-Attribute-Value) sem um bom motivo, explodirá o número de leituras e gravações físicas.
Se você não tirar proveito dos recursos que o banco de dados oferece, como compactação de página,
FILESTREAM
armazenamento (para dados binários),SPARSE
colunas,hierarchyid
hierarquias e assim por diante (todos os exemplos do SQL Server), você não verá nenhum lugar próximo ao desempenho que você poderia estar vendo.Você deve começar a se preocupar com sua estratégia de acesso a dados depois de projetar seu banco de dados e se convencer de que é o melhor possível, pelo menos por enquanto.
Carregamento ansioso x preguiçoso
A maioria dos ORMs usava uma técnica chamada carregamento lento para relacionamentos, o que significa que, por padrão, ele carrega uma entidade (linha da tabela) de cada vez e faz uma ida e volta ao banco de dados toda vez que precisar carregar um ou muitos chave) linhas.
Isso não é uma coisa boa ou ruim, mas depende do que realmente será feito com os dados e do quanto você sabe de antemão. Às vezes, o carregamento lento é absolutamente a coisa certa a fazer. O NHibernate, por exemplo, pode decidir não consultar nada e simplesmente gerar um proxy para um ID específico. Se tudo o que você precisa é o próprio ID, por que pedir mais? Por outro lado, se você estiver tentando imprimir uma árvore de cada elemento em uma hierarquia de três níveis, o carregamento lento se tornará uma operação O (N²), o que é extremamente ruim para o desempenho.
Um benefício interessante do uso de "SQL puro" (ou seja, consultas brutas do ADO.NET / procedimentos armazenados) é que basicamente obriga a pensar exatamente sobre quais dados são necessários para exibir uma determinada tela ou página. ORMs e características carregamento lento não impedir você de fazer isso, mas eles não dão-lhe a oportunidade de ser ... bem, preguiçoso , e acidentalmente explodir o número de consultas que você executar. Portanto, você precisa entender os recursos de carregamento rápido dos ORMs e estar sempre atento ao número de consultas que você está enviando ao servidor para qualquer solicitação de página.
Armazenamento em cache
Todos os principais ORMs mantêm um cache de primeiro nível, AKA "cache de identidade", o que significa que, se você solicitar a mesma entidade duas vezes por seu ID, ele não precisará de uma segunda ida e volta e também (se você projetou seu banco de dados corretamente ) oferece a capacidade de usar simultaneidade otimista.
O cache L1 é bastante opaco no L2S e EF, é preciso confiar que está funcionando. NHibernate é mais explícito sobre isso (
Get
/Load
vs.Query
/QueryOver
). Ainda assim, desde que você tente consultar por ID o máximo possível, você deve ficar bem aqui. Muitas pessoas esquecem o cache L1 e pesquisam repetidamente a mesma entidade várias vezes além de seu ID (ou seja, um campo de pesquisa). Se você precisar fazer isso, salve o ID ou até a entidade inteira para pesquisas futuras.Há também um cache de nível 2 ("cache de consulta"). O NHibernate possui esse recurso embutido. O Linq to SQL e o Entity Framework compilaram consultas , o que pode ajudar a reduzir bastante as cargas do servidor de aplicativos compilando a própria expressão de consulta, mas não armazena em cache os dados. A Microsoft parece considerar isso uma preocupação de aplicativo e não de acesso a dados, e esse é um grande ponto fraco do L2S e do EF. Escusado será dizer que também é um ponto fraco do SQL "bruto". Para obter um desempenho realmente bom com basicamente qualquer ORM que não seja o NHibernate, você precisa implementar sua própria fachada de cache.
Há também uma "extensão" de cache L2 para EF4, que é boa , mas não é realmente uma substituição por atacado de um cache no nível do aplicativo.
Número de consultas
Bancos de dados relacionais são baseados em conjuntos de dados. Eles são realmente bons em produzir grandes quantidades de dados em um curto período de tempo, mas não são tão bons em termos de latência de consultas, porque há uma certa quantidade de sobrecarga envolvida em todos os comandos. Um aplicativo bem projetado deve aproveitar os pontos fortes deste DBMS e tentar minimizar o número de consultas e maximizar a quantidade de dados em cada uma.
Agora não estou dizendo para consultar o banco de dados inteiro quando você só precisa de uma linha. O que estou dizendo é que, se você precisa de
Customer
,Address
,Phone
,CreditCard
, eOrder
linhas, tudo ao mesmo tempo, a fim de servir uma única página, então você deve perguntar para todos eles ao mesmo tempo, não execute cada consulta separadamente. Às vezes é pior do que isso, você verá o código que consulta o mesmoCustomer
registro 5 vezes seguidas, primeiro para obter oId
, então oName
, então oEmailAddress
, então ... é ridiculamente ineficiente.Mesmo se você precisar executar várias consultas que funcionem em conjuntos de dados completamente diferentes, geralmente é ainda mais eficiente enviar tudo para o banco de dados como um único "script" e retornar vários conjuntos de resultados. Você está preocupado com a sobrecarga, não com a quantidade total de dados.
Isso pode parecer senso comum, mas geralmente é muito fácil perder o controle de todas as consultas que estão sendo executadas em várias partes do aplicativo; seu provedor de associação consulta as tabelas de usuário / função, sua ação de cabeçalho consulta o carrinho de compras, sua ação de menu consulta a tabela de mapas do site, sua ação da barra lateral consulta a lista de produtos em destaque e, talvez, sua página seja dividida em algumas áreas autônomas consulte as tabelas Histórico do pedido, Exibido recentemente, Categoria e Inventário separadamente e, antes que você perceba, execute 20 consultas antes mesmo de começar a exibir a página. Isso simplesmente destrói o desempenho.
Algumas estruturas - e eu estou pensando principalmente no NHibernate aqui - são incrivelmente inteligentes sobre isso e permitem que você use algo chamado futuros que agrupam consultas inteiras e tentam executá-las todas de uma só vez, no último minuto possível. AFAIK, você está por conta própria se quiser fazer isso com qualquer uma das tecnologias da Microsoft; você precisa integrá-lo à lógica do aplicativo.
Indexação, Predicados e Projeções
Pelo menos 50% dos desenvolvedores com quem falo e até alguns DBAs parecem ter problemas com o conceito de cobertura de índices. Eles pensam: "bem, a
Customer.Name
coluna está indexada, então todas as pesquisas que faço no nome devem ser rápidas". Só que não funciona dessa maneira, a menos que oName
índice cubra a coluna específica que você está procurando. No SQL Server, isso é feitoINCLUDE
naCREATE INDEX
declaração.Se você ingenuamente usar em
SELECT *
qualquer lugar - e isso é mais ou menos o que todo ORM fará, a menos que você especifique explicitamente o contrário, usando uma projeção -, o DBMS poderá muito bem optar por ignorar completamente seus índices porque eles contêm colunas não cobertas. Uma projeção significa que, por exemplo, em vez de fazer isso:Você faz isso:
E esta vontade, para a maioria ORMs modernos, instruí-lo apenas para ir e consultar os
Id
eName
colunas que são presumivelmente abrangidos pelo índice (mas não oEmail
,LastActivityDate
ou qualquer outra colunas que aconteceu para ficar lá).Também é muito fácil eliminar completamente quaisquer benefícios de indexação usando predicados inadequados. Por exemplo:
... parece quase idêntico à nossa consulta anterior, mas na verdade resultará em uma tabela completa ou uma varredura de índice, pois é convertida em
LIKE '%Doe%'
. Da mesma forma, outra consulta que parece suspeitamente simples é:Supondo que você tenha um índice
BirthDate
, esse predicado tem uma boa chance de torná-lo completamente inútil. Nosso programador hipotético aqui obviamente tentou criar um tipo de consulta dinâmica ("apenas filtre a data de nascimento se esse parâmetro foi especificado"), mas esse não é o caminho certo para fazê-lo. Escrito assim:... agora o mecanismo de banco de dados sabe como parametrizar isso e fazer uma busca de índice. Uma pequena mudança, aparentemente insignificante, na expressão da consulta pode afetar drasticamente o desempenho.
Infelizmente, o LINQ em geral facilita muito a gravação de consultas ruins como essa, porque às vezes os provedores conseguem adivinhar o que você estava tentando fazer e otimizar a consulta, e às vezes não. Então, você acaba com resultados frustrantemente inconsistentes, que teriam sido óbvios (para um DBA experiente, de qualquer maneira) se você tivesse escrito SQL simples e antigo.
Basicamente, tudo se resume ao fato de que você realmente precisa ficar de olho no SQL gerado e nos planos de execução que eles levam, e se você não estiver obtendo os resultados esperados, não tenha medo de ignorar o Camada ORM de vez em quando e codifique manualmente o SQL. Isso vale para qualquer ORM, não apenas para a EF.
Transações e Bloqueio
Você precisa exibir dados atualizados até o milissegundo? Talvez - depende - mas provavelmente não. Infelizmente, o Entity Framework não fornece
nolock
, você pode usar apenasREAD UNCOMMITTED
no nível da transação (não no nível da tabela). De fato, nenhum dos ORMs é particularmente confiável sobre isso; se você quiser fazer leituras sujas, precisará descer para o nível SQL e escrever consultas ad-hoc ou procedimentos armazenados. Então, o que se resume, novamente, é como é fácil fazer isso dentro da estrutura.O Entity Framework percorreu um longo caminho a esse respeito - a versão 1 do EF (no .NET 3.5) foi horrível, tornou incrivelmente difícil romper a abstração de "entidades", mas agora você tem ExecuteStoreQuery e Translate , por isso é realmente não é tão ruim. Faça amizade com esses caras porque você os usará bastante.
Há também o problema de bloqueio de gravação e deadlocks e a prática geral de reter bloqueios no banco de dados pelo menor tempo possível. Nesse sentido, a maioria dos ORMs (incluindo o Entity Framework) na verdade tendem a ser melhores que o SQL bruto, porque encapsulam o padrão da unidade de trabalho , que no EF é SaveChanges . Em outras palavras, você pode "inserir" ou "atualizar" ou "excluir" entidades no conteúdo do seu coração, sempre que quiser, seguro de que nenhuma alteração será realmente enviada ao banco de dados até que você confirme a unidade de trabalho.
Observe que um UOW não é análogo a uma transação de longa execução. O UOW ainda usa os recursos de simultaneidade otimistas do ORM e rastreia todas as alterações na memória . Nenhuma única instrução DML é emitida até a confirmação final. Isso mantém os tempos de transação o mais baixo possível. Se você criar seu aplicativo usando SQL bruto, é muito difícil obter esse comportamento adiado.
O que isso significa para a EF especificamente: Torne suas unidades de trabalho o mais grosseiras possível e não as comprometa até que seja absolutamente necessário. Faça isso e você terá uma contenção de bloqueio muito menor do que usaria comandos individuais do ADO.NET em momentos aleatórios.
Em conclusão:
A EF é excelente para aplicativos de alto tráfego / alto desempenho, assim como qualquer outra estrutura é adequada para aplicativos de alto tráfego / alto desempenho. O que importa é como você o usa. Aqui está uma rápida comparação das estruturas mais populares e quais recursos eles oferecem em termos de desempenho (legenda: N = Não suportado, P = Parcial, Y = sim / suportado):
Como você pode ver, o EF4 (a versão atual) não se sai muito mal, mas provavelmente não é o melhor se o desempenho for sua principal preocupação. O NHibernate é muito mais maduro nessa área e até o Linq to SQL fornece alguns recursos de aprimoramento de desempenho que o EF ainda não fornece. O ADO.NET bruto geralmente é mais rápido em cenários de acesso a dados muito específicos , mas, quando você reúne todas as partes, ele realmente não oferece muitos benefícios importantes que você obtém das várias estruturas.
E, apenas para ter certeza absoluta de que pareço um registro quebrado, nada disso importa se você não criar suas estratégias de banco de dados, aplicativo e acesso a dados corretamente. Todos os itens no gráfico acima são para melhorar o desempenho além da linha de base; Na maioria das vezes, a linha de base é o que precisa de mais aprimoramentos.
fonte
Edit: Com base na ótima resposta da @Aaronaught, estou adicionando alguns pontos visando o desempenho com a EF. Esses novos pontos são prefixados por Edit.
A maior melhoria no desempenho em sites de alto tráfego é alcançada pelo armazenamento em cache (= primeiro evitando qualquer processamento do servidor da web ou consulta ao banco de dados), seguido de processamento assíncrono para evitar o bloqueio de encadeamentos enquanto as consultas ao banco de dados são executadas.
Não há resposta à prova de balas para sua pergunta, pois ela sempre depende dos requisitos de aplicação e da complexidade das consultas. A verdade é que a produtividade do desenvolvedor com a EF esconde a complexidade por trás da qual, em muitos casos, leva ao uso incorreto da EF e a um desempenho terrível. A idéia de que você pode expor uma interface abstrata de alto nível para acesso a dados e funcionará sem problemas em todos os casos não funciona. Mesmo com o ORM, você deve saber o que está acontecendo por trás da abstração e como usá-la corretamente.
Se você não possui experiência anterior com a EF, encontrará muitos desafios ao lidar com o desempenho. Você pode cometer muito mais erros ao trabalhar com a EF em comparação com o ADO.NET. Também há muito processamento adicional no EF; portanto, o EF sempre será significativamente mais lento que o ADO.NET nativo - isso é algo que você pode medir com uma simples prova de aplicação de conceito.
Se você deseja obter o melhor desempenho da EF, provavelmente precisará:
MergeOption.NoTracking
SqlCommand
inserção, atualização ou exclusão múltipla, mas com o EF, todos os comandos serão executados em uma ida e volta ao banco de dados.GetByKey
na API ObjectContext ouFind
na API DbContext) para consultar o cache primeiro. Se você usar o Linq-to-entity ou ESQL, ele criará uma ida e volta ao banco de dados e depois retornará a instância existente do cache.Não tenho certeza se o SO ainda está usando o L2S. Eles desenvolveram um novo ORM de código aberto chamado Dapper e acho que o principal ponto por trás desse desenvolvimento foi aumentar o desempenho.
fonte