Entity Framework: A vida do DBA

Vamos falar sobre o Entity Framework do ponto de vista do DBA.

Veja os demais artigos:

Iniciei o SQL Profiler no projeto do ARDA (https://github.com/DXBrazil/Arda) para coletar as queries que estão rodando no banco de dados. Rapidamente encontrei algumas coisas interessantes.

Select TOP 2

"Quem escreve query assim?!?"

Não é errado usar a sintaxe TOP 2, mas é pouco comum especificar exatamente 2 registros a serem retornados.

image

Procurando no código, não há nenhuma referência de TOP 2. Após analisar um pouco as consultas envolvidas, descobri que as extensões LINQ Single ou SingleOrDefault adicionam esse limite. Esse é um efeito colateral causado pelo Entity Framework que, embora seja inofensivo, não é um comportamento normal.

Entretanto, nem sempre é assim. Veja a discussão anterior no artigo do Broken LINQ.

Stored Procedures

Não tem! "Como posso otimizar o código?"

Scan de tabela

Enquanto estava analisando o desempenho do servidor, encontrei um SCAN de tabela.

image

A query é simples.

image

Rapidamente identificamos a falta de índice adequado na tabela. A pergunta é “por que o desenvolvedor não criou índice?” .

Ao mesmo tempo, já deduzi que o desenvolvedor usou code-first e sequer passou pela cabeça dele a necessidade de índice. Sabe como sei disso? No caso, eu era o desenvolvedor dessa aplicação. Só fui enxergar o problema claramente quando saí da frente do Visual Studio e entrei no SQL Management Studio.

Maior carga no banco de dados

Coletando as consultas mais pesadas no banco de dados, identifiquei uma query que realizava operações de SORT e SCAN.

image

A mesma query apresentava planos de execução ligeiramente parecidos, mas diferentes:

image

Ao ver qual a consulta problemática, descobrimos outra coisa interessante: a tabela FiscalYear faz JOIN com ela mesma!

image

Uma das dificuldades do Entity Framework é encontrar o código com as queries LINQ correspondentes às consultas SQL. Quem faz join de FiscalYear com FiscalYear?

  • Não há comentários.
  • Não está encapsulado em uma procedure.
  • Não tenho ideia.

"Por qual motivo o desenvolvedor faz isso?" , pergunta o DBA.

Resolvendo o problema

A resolução é simples (mas não trivial).

O primeiro passo é encontrar qual o trecho que gera esse join duplicado da tabela de FiscalYear com ela mesma.

image

Nesse caso, eu como desenvolvedor, fui procurando por todas as classes C# que acessavam informações da tabela Metrics e FiscalYear. O projeto é pequeno, então não foi difícil descobrir que o problema estava na classe MetricRepository.

O trecho de código com problema possui um JOIN entre as tabelas Metrics e FiscalYear.

image

Analisando a query LINQ, (aparentemente) não tem referência da tabela FiscalYear com ela mesma.

Demorou um tempo para enxergar que sim, existe! FiscalYear está duplicada. Veja o código novamente até encontrar.

...

(Se você ainda não achou, veja que a consulta faz join entre 3 tabelas: _context.Metrics, _context.FiscalYear e m.FiscalYear).

Está muito claro! O código está forçando o JOIN do FiscalYear com FiscalYear. Como essa relação é expressa por um campo já existente na tabela Metrics, então não precisamos fazer esse JOIN. Podemos reescrever assim:

image

Aplicando o novo código, temos a query correta:

image

Os detalhes do problema estão registrados no GitHub Issue que acabei de abrir.

[GitHub Issue #100] Improve SQL query performance for Metrics
https://github.com/DXBrazil/Arda/issues/100

 

Conclusão

No final do dia, os pensamentos do DBA são:

  • Quem escreve query assim?  (ex: SELECT TOP2 e afins)
  • Como posso otimizar o código? (ex: stored Procedures não tem!)
  • Por que o desenvolvedor não criou índice? (ex: scan de tabela)
  • Por qual motivo o desenvolvedor faz isso? (ex: maior carga no banco de dados)

Na maior parte dos casos, o DBA se sente incapaz de resolver problemas de desempenho causado pelo EF. Embora a resolução seja simples, ela não é trivial.