Introdução ao desempenho
O desempenho do banco de dados é um tópico vasto e complexo, abrangendo uma pilha inteira de componentes: o banco de dados, a rede, o driver de banco de dados e as camadas de acesso a dados, como o EF Core. Embora camadas de alto nível e O/RMs, como o EF Core, simplifiquem consideravelmente o desenvolvimento de aplicativos e melhorem a capacidade de manutenção, às vezes, podem ser opacas, ocultando detalhes internos críticos ao desempenho, como o SQL que está sendo executado. Esta seção tenta fornecer uma visão geral de como obter um bom desempenho com o EF Core e como evitar armadilhas comuns que podem prejudicar o desempenho do aplicativo.
Identificar gargalos e medir, medir, medir
Como sempre ocorre com o desempenho, é importante não apressar a otimização sem que os dados mostrem um problema; como o grande Donald Knuth disse certa vez: "Otimização prematura é a raiz de todo o mal". A seção diagnóstico de desempenho discute várias maneiras de entender onde seu aplicativo está gastando tempo na lógica do banco de dados e como identificar áreas problemáticas específicas. Depois que uma consulta lenta for identificada, as soluções poderão ser consideradas: o banco de dados está sem um índice? Você deve experimentar outros padrões de consulta?
Sempre use o parâmetro de comparação em seu código e possíveis alternativas por conta própria – a seção de diagnóstico de desempenho contém um parâmetro de comparação de exemplo com BenchmarkDotNet, que você pode usar como modelo para seus próprios parâmetros de comparação. Não suponha que parâmetros de comparação públicos e gerais se aplicam como estão ao seu caso de uso específico; diversos fatores, como latência de banco de dados, complexidade de consulta e quantidade de dados reais em suas tabelas, podem ter um efeito profundo sobre qual solução é a melhor. Por exemplo, muitos parâmetros de comparação públicos são realizados em condições de rede ideais, em que a latência para o banco de dados é quase zero, e com consultas extremamente leves que dificilmente exigem processamento (ou E/S de disco) no lado do banco de dados. Embora eles sejam valiosos para comparar as sobrecargas de runtime de diferentes camadas de acesso a dados, as diferenças que eles revelam geralmente se mostram insignificantes em um aplicativo do mundo real, em que o banco de dados executa um trabalho real e a latência para o banco de dados é um fator de desempenho significativo.
Aspectos do desempenho do acesso a dados
O desempenho geral do acesso a dados pode ser dividido nas seguintes categorias amplas:
- Puro desempenho do banco de dados. Com o banco de dados relacional, o EF converte as consultas LINQ do aplicativo em instruções SQL que estão sendo executadas pelo banco de dados; essas próprias instruções SQL podem ser executadas com mais ou menos eficiência. O índice correto no lugar certo pode fazer muita diferença no desempenho do SQL, ou reescrever sua consulta LINQ pode fazer com que o EF gere uma consulta SQL melhor.
- Transferência de dados de rede. Assim como acontece com qualquer sistema de rede, é importante limitar a quantidade de dados que vão e voltam na transmissão. Isso significa garantir que você envie e carregue apenas dados de que realmente precisará, mas também evite o chamado efeito de "explosão cartesiana" ao carregar entidades relacionadas.
- Idas e voltas de rede. Além da quantidade de dados que vai e volta, o roundtrip da rede, já que o tempo necessário para a execução de uma consulta no banco de dados pode ser reduzido pelo tempo que os pacotes usam para ir e voltar de seu aplicativo e seu banco de dados. A sobrecarga de ida e volta depende muito do seu ambiente; quanto mais distante for o servidor de banco de dados, maior será a latência e o custo de cada ida e volta. Com o advento da nuvem, os aplicativos estão cada vez mais longe do banco de dados, e aplicativos "tagarelas" que executam muitas viagens de ida e volta experimentam degradação do desempenho. Portanto, é importante entender exatamente quando seu aplicativo entra em contato com o banco de dados, quantas viagens de ida e volta ele executa e se esse número pode ser minimizado.
- Sobrecarga de runtime do EF. Por fim, o próprio EF adiciona alguma sobrecarga de runtime às operações de banco de dados: o EF precisa compilar suas consultas de LINQ para SQL (embora isso normalmente deva ser feito apenas uma vez), o controle de alterações adiciona alguma sobrecarga (mas pode ser desabilitado) etc. Na prática, a sobrecarga de EF para aplicativos do mundo real provavelmente será insignificante na maioria dos casos, pois o tempo de execução da consulta no banco de dados e a latência de rede dominam o tempo total; mas é importante entender quais são suas opções e como evitar algumas armadilhas.
Saiba o que está acontecendo nos bastidores
O EF permite que os desenvolvedores se concentrem na lógica de negócios gerando SQL, materializando resultados e executando outras tarefas. Como qualquer camada ou abstração, ela também tende a ocultar o que está acontecendo nos bastidores, como as consultas SQL reais que estão sendo executadas. O desempenho não é necessariamente um aspecto crítico de todos os aplicativos que existem, mas em aplicativos em que é crítico, é vital que o desenvolvedor entenda o que o EF está fazendo por eles: inspecionar as consultas SQL de saída, seguir as idas e voltas para garantir que o problema N+1 não esteja ocorrendo etc.
Cache fora do banco de dados
Por fim, a maneira mais eficiente de interagir com um banco de dados é não interagir com ele de forma alguma. Em outras palavras, se o acesso ao banco de dados aparecer como um gargalo de desempenho em seu aplicativo, talvez valha a pena armazenar em cache determinados resultados fora do banco de dados, a fim de minimizar as solicitações. Apesar de o cache adicionar complexidade, é uma parte especialmente crucial de qualquer aplicativo escalonável: embora a camada de aplicativo seja facilmente dimensionada adicionando servidores extras para lidar com o aumento da carga, dimensionar a camada de banco de dados geralmente é muito mais complicado.