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.


Comments (8)

  1. Show Catae, eu venho encontrado esses casos diariamente, mas eu já imaginava que o desenvolvedor não sabe nada do que esta acontecendo por trás dos panos, a dificuldade é em resolver esses problemas, porém vejo que como DBA ficamos muito limitados na resolução, sendo um trabalho mais no código da aplicação e na maioria dos casos lidamos com sistemas de terceiros que dificulta ainda mais o acesso ao código.

    Muito legal esses pontos de vista que esta expondo sobre o EF x DBA…

    Atenciosamente
    Reginaldo Silva

    1. Oi Reginaldo, obrigado por sentir as mesmas dificuldades que eu sinto como DBA. Esse é um assunto que demorei bastante para conseguir elaborar da forma mais neutra possível. Errei um pouco em alguns pontos, mas consegui passar a mensagem. Abraços, Fabricio

  2. Mais um excelente artigo, Catae! Realmente, o Entity ajuda MUITO o lado dos Desenvolvedores no que se refere a produtividade e abstração de código SQL, mas muitas vezes, acabando gerando consultas sem qualquer boa prática de query tuning, prejudicando a performance dessa consulta. O DBA, coitado, fica de mãos atadas sem ter muito o que fazer e o desenvolvedor não sabe nem qual a consulta que o sistema dele está enviando para o banco.

    1. Obrigado Dirceu! Eu penso exatamente isso! Já passei por várias situações que nem o DBA nem o desenvolvedor sabiam o que fazer.
      Estou preparando a continuação dessa série e estou buscando novas ideias. Se tiver alguma compartilhe!
      Abraços, Fabricio

  3. Paulo Daniel Nobre disse:

    Excelente artigo Catae, como sempre! Cara, você teve algum problema de Lazy Loading, ocasionando waits do tipo ASYNC_NETWORK_IO? Pode falar um pouco mais dele no próximo artigo?

    []’s

    1. Obrigado Paulo! Conheço o NETWORK_IO e isso normalmente não impacta no desempenho. Porém, não sei que relação teria com o Lazy Loading. Veja se a aplicação está usando o Multiple Active Result Sets (MARS) – basta verificar se uma sessão (session_id) dispara múltiplos requests (request_id). Nesse caso, é possível que o NETWORK_IO seja apenas uma consequencia do comportamento do MARS. Não é exatamente algo errado, mas certamente poderia melhorar.
      Abraços, Fabricio

  4. Dobereiner Miller disse:

    Excelente artigo e algo que tenho lutado bastante para tentar ensinar e explicar cada vez mais.
    O uso do EF e do LINQ, traz muita comodidade e são inúmeros os casos onde percebia que uma cobertura por certo índice era inútil, através de alguma aplicação fazendo uso desta tecnologia. É certo claro.. já que estamos falando de uma linguagem mapeada mais focada em objetos.. (retorno de objetos tipados, anônimos e etc.). Mas sempre aprendo mais quando vejo que mesmo usando EF ainda é possível ver uma diversidade de técnica aplicada… e quase que uma sutil assinatura nas queries LINQ de cada programador que a escreve.
    No mais eu adicionaria como pontos positivos de se usar o ADO.Net EF os seguintes pontos:
    Fica fácil detectar a necessidade de rever uma query LINQ quando temos pouco uso de cobertura de índices. Geralmente aponto a duas causas:
    1) A query gerou um select *, ou, não enviou parâmetros no formato correto, a dificuldade em escrever queries mais complexas que acabam gerando CONVERT_IMPLICIT por exemplo, com o uso incorreto de algum método, e;
    2) Necessidade de rever a modelagem da tabela usada (neste caso, geralmente quando para cada query, tenho a informação que precisaria de um novo índice para ter cobertura).
    Excelentes pontos apresentados.
    Att,

    1. Valeu!!! É isso mesmo. Obrigado Dobereiner.

Skip to main content