quinta-feira, 28 de julho de 2016

Entity Framework e lazy loading

A cada dia que passa fica mais comum encontramos soluções com camadas de mapeamento objeto-relacional (ORM), simplificando o acesso a dados para os desenvolvedores.
Ressalto que não sou contra o Entity Framework, hibernate ou outros frameworks de mapeamento objeto relacional, só quero que as pessoas que o utilizam tomem cuidados básicos no acesso a dados, pois não existe mágica…

Banco de dados


Utilize o script abaixo para criar um banco de dados de exemplo com três tabelas, Pessoa, Endereço e Telefone, inserindo também alguns registros.
Ok, eu sei que você modelaria diferente, pois uma pessoa pode ter mais de um endereço ou telefone, mas para este exemplo a regra de negócio da Lutilândia não permite.

CREATE DATABASE EF
GO
USE EF
GO
IF (OBJECT_ID('dbo.Pessoa') IS NOT NULL)
DROP TABLE dbo.Pessoa
go
IF (OBJECT_ID('dbo.Endereco') IS NOT NULL)
DROP TABLE dbo.Endereco
GO
IF (OBJECT_ID('dbo.Telefone') IS NOT NULL)
DROP TABLE dbo.Telefone
go

CREATE TABLE dbo.Endereco
(ID INT IDENTITY NOT NULL PRIMARY KEY,
 Descricao VARCHAR(500) NOT NULL)
GO

CREATE TABLE dbo.Telefone
(ID INT IDENTITY NOT NULL PRIMARY KEY,
 Descricao VARCHAR(500) NOT NULL)
GO

CREATE TABLE dbo.Pessoa
(Codigo INT IDENTITY NOT NULL PRIMARY KEY,
 Nome VARCHAR(100) NOT NULL,
 IdEndereco INT NULL,
 IdTelefone INT NULL
 )
GO

ALTER TABLE dbo.Pessoa
ADD CONSTRAINT fk_Pessoa_Endereco_ID 
FOREIGN KEY (IdEndereco)
REFERENCES dbo.Endereco (ID)
GO

ALTER TABLE dbo.Pessoa
ADD CONSTRAINT fk_Pessoa_Telefone_ID 
FOREIGN KEY (IdTelefone)
REFERENCES dbo.Telefone (ID)
GO

INSERT INTO dbo.Endereco (Descricao)
SELECT TOP 100000 O.description
FROM sys.dm_xe_map_values AS M
CROSS JOIN sys.dm_xe_objects AS O
GO

INSERT INTO dbo.Telefone (Descricao)
SELECT TOP 100000 O.description
FROM sys.dm_xe_map_values AS M
CROSS JOIN sys.dm_xe_objects AS O
GO

INSERT INTO dbo.Pessoa (Nome)
SELECT TOP 1000000 M.name
FROM sys.dm_xe_map_values AS M
CROSS JOIN sys.dm_xe_objects AS O
GO

UPDATE dbo.Pessoa
SET IdEndereco = (Codigo % 100000) + 1
, IdTelefone = (Codigo % 100000) + 1
GO

Lazy loading

Utilizando o Entity Framework 6.0, o comportamento padrão do EF é que o contexto trabalhe com lazy loading. O que isso significa?

Para quem está seguindo o script, crie um projeto C# Console no Visual Studio e…

    1. Adicione um ADO.NET Entity Data Model (chame do que quiser)
    2. Escolha EF Designer from database
    3. Aponte para seu banco de dados EF (pode manter o nome EFEntities)
    4. Selecione as tabelas Pessoa, Endereco e Telefone
    5. Clique em Finish

Depois da geração do modelo você vai ver algo similar a imagem abaixo:



Edite seu método Main e coloque o código abaixo. O objetivo do código é buscar quase 10000 pessoas e depois fazer algo simples, acessando o endereço e telefone do indivíduo (note que não me interessa gerar saída alguma, só o acesso ao banco de dados).

String s = "";
DateTime dt = DateTime.Now;

EFEntities contexto = new EFEntities();
var r = contexto.Pessoas.Where(x => x.Codigo < 10000);

foreach (var p in r.ToList())
{
    s = "Nome: " + p.Nome + " - Endereço: " + p.Endereco.Descricao + " - Telefone: " + p.Telefone.Descricao;
}
Console.WriteLine(DateTime.Now.Subtract(dt).Seconds.ToString());

A execução dessa aplicação na minha máquina levou algo em torno de 12 segundos, nada demais para o desenvolvedor que está executando este código, então poucos fazem uma análise mais detalhada, como faremos a seguir.

Utilizando o profiler para monitorar os eventos SQL:BatchCompleted e RPC:Completed, toda vez que o Entity Framework acessa a coluna Descrição da entidade Endereco ou Telefone, uma chamada é feita para o SQL Server (note o @EntityKeyValue1 variando), o que neste exemplo gerou 39998 entradas no profiler.

Essas chamadas são feitas porque o EF vai "preguiçosamente" (lazy) pedindo para o SQL Server os elementos de outras entidades, varrendo o resultado retornado após o acesso ao elemento Pessoa (primeira consulta exibida na imagem abaixo).


Eager loading

Caso você queira que o Entity Framework não trabalhe com esse comportamento de lazy loading, você pode alterar no seu código a utilização do contexto, adicionando o método "Include", que por debaixo dos planos já busca todos os elementos.

var r = contexto.Pessoas.Include("Endereco").Include("Telefone").Where(x => x.Codigo < 10000);

Analisando a execução deste novo código, temos APENAS DUAS linhas na saída do profiler, onde notamos que o EF enviou para o SQL Server uma única consulta fazendo o join entre as três tabelas, já retornando o endereço e telefone. Dessa forma evitamos N round-trips entre o cliente e o servidor, fazendo com que em minha máquina o tempo total de execução ficasse em torno de 3 segundos.


Desempenho da aplicação


A diferença entre 3 e 12 segundos é onde mora o perigo... Talvez testando a solução em sua máquina, os 9 segundos não sejam significativos para os desenvolvedores, e um build sem a análise de desempenho ou do número de chamadas acabe chegando em produção.

Se a solução é para controlar o nome dos envolvidos no próximo bolão do campeonato lá na sua empresa, tudo tranquilo, porém caso a solução tenha milhares de requisições pesquisando por pessoas específicas, o número de chamadas ao banco de dados será muito grande. E dependendo da forma como a aplicação está gerenciando e utilizando os seus recursos (ou consumindo os registros), ela mesmo pode ser tornar o gargalo, que não respondendo adequadamente vai deixar o SQL Server cheio de requisições com espera do tipo ASYNC_NETWORK_IO (e NÃO É PROBLEMA DE REDE, ok?!!).

É meio óbvio que para escrever este artigo eu já encontrei diversos problemas de desempenho em aplicações por conta do Lazy Loading. O que em um primeiro momento sempre foi apontado como problema no banco de dados, conseguimos rastrear até apontar o comportamento indevido da aplicação. Então peço para que façam uma análise mais criteriosa de como o seu framework de mapeamento OR acessa o banco de dados, sempre entendendo o perfil das requisições e como pode utilizar da melhor forma o framework adotado.

Então estou falando para nunca usar lazy loading ou até configurar o contexto (this.Configuration.LazyLoadingEnabled = false) para impedir que um desenvolvedor desavisado faça uso do lazy load?
Claro que não, quero que você entenda que o maior custo de IO e CPU visto em uma única consulta com eager loading pode ser muito inferior quando comparado ao tempo total de execução do round-trip de milhares de chamadas para o SQL Server.

No fim tudo depende, só precisamos fazer as perguntas certas e achar as respostas adequadas, então espero que você guarde mais essa informação no seu repertório de soluções.

Abraços

Luciano Caixeta Moreira - {Luti}
luciano.moreira@srnimbus.com.br
www.twitter.com/luticm
www.srnimbus.com.br

quarta-feira, 6 de julho de 2016

SQL Saturday #570 São Paulo - eu vou!

Depois de passar o primeiro semestre sem palestrar em eventos da comunidade técnica, no segundo semestre estou planejando participar e apresentar algumas sessões (se forem aprovadas, claro!).

Portanto acabei de submeter uma sessão para o SQL Saturday #570 (http://www.sqlsaturday.com/570/EventHome.aspx), que acontecerá em São Paulo no dia 08 de Outubro.

Nem preciso falar que quem trabalha com SQL Server e mora em SP, a participação é obrigatória!

Os detalhes da sessão que submeti estão abaixo.

Título: Acesso a dados com ADO.NET e Entity Framework
Descrição: Esta sessão tem por objetivo analisar características do ADO.NET e Entity Framework ao acessar o SQL Server, sugerindo melhores práticas no uso destes. Iremos discutir aspectos como pool de conexões, transações locais e distribuídas, parametrização de comandos, paginação, lazy loading, cuidados com projeção, consultas genéricas, entre outros aspectos. Uma sessão de nível intermediário que pode ajudar o desenvolvedor e o DBA a se entenderem melhor…
(http://www.sqlsaturday.com/570/Sessions/Details.aspx?sid=52900)

Eu também ia submeter uma sessão intitulada In-memory OLTP Internals, porém já havia uma com o mesmo nome do nosso amigo Frederico Santos. Quem sabe o Fred não têm duas sessões aprovadas e eu consigo apresentar alguma coisa de Hekaton junto com ele! kkkkkk

Abraços

Luciano Caixeta Moreira - {Luti}
luciano.moreira@srnimbus.com.br
www.twitter.com/luticm
www.srnimbus.com.br

terça-feira, 5 de julho de 2016

[SQLServerDF] Encontro XXXIV - Analizando o Always Encrypted

Na próxima semana temos mais uma apresentação do SQLServerDF, começando 18:30h.

NÃO é necessário confirmar participação através do SQLServerDF. De qualquer forma, incentivo a participação na nossa lista de discussão, então para aqueles que não estão no grupo, basta ir até http://groups.google.com/group/sqlserverdf, fazer sua inscrição e aguardar minha moderação.

Data e horário: 12/07/2016, das 18:30h às 20:30h
Local: Xperts Trainning Center
Palestrantes: Gustavo Moura Fé Maia

Título: Analizando o Always Encrypted

Descrição: Nesta sessão, iremos conhecer uma das novas features do SQL Server 2016, o Always Encrypted. Entenderemos como funciona este recurso que promete proteger os dados de acessos indevidos numa topologia cliente/servidor e implantaremos essa funcionalidade simulando um ambiente real. Depois, iremos um pouco mais fundo e analisaremos as informações protegidas pelo Always Encrypted enquanto trafegam pela rede e enquanto estão em memória.

Mini-cv do palestrante: Gustavo [Guzz] Moura Fé Maia é um SQL Geek viciado em automatização e performance tuning. Trabalha com SQL Server desde 2012 e se tornou DBA em 2013. É um dos autores do blog Comunidade SQL Server onde gosta de escrever sobre programação em T-SQL, replicação e segurança. Quando não está com uma caneca de café, lendo e escrevendo sobre SQL, está com uma caneca de café fazendo outra coisa.

Abraços

Luciano Caixeta Moreira - {Luti}
luciano.moreira@srnimbus.com.br
www.twitter.com/luticm
www.srnimbus.com.br

domingo, 3 de julho de 2016

SQL22 + Em busca de recolocação

Primeiro uma pequena história…

Alguns meses atrás recebi de um amigo um e-mail pedindo para encaminhar o CV de um conhecido DBA, que estava desempregado havia alguns meses. Ao analisar o currículo do indivíduo, minha resposta foi: peça para o Astrogildo** vir conversar comigo.

Meu conselho para o Astrogildo foi de que ele precisava se qualificar mais, pois o CV dele estava fraco e, eu como recrutador, dificilmente chamaria ela para uma entrevista de emprego. Sugeri que ele participasse do curso SQL23 - Mastering the database engine, onde poderia aprender muito e teríamos tempo de conversar. De quebra ele aproveitaria que o treinamento estava com um preço promocional, e para ajudar eu tentaria facilitar o pagamento.

Sexta-feira anterior ao início do treinamento e nada da matrícula do Astrogildo, uma pena. Só que no sábado o Astrogildo me procurou, conversamos um pouco e, dada a complicada situação em que ele se encontrava, eu o convidei para participar da turma. Depois de 48 horas de um treinamento bem puxado, peguei o voo para Brasília, com o sentimento de que o treinamento tinha sido importante para o Astrogildo.

Menos de dois meses depois, em uma segunda-feira de madrugada, recebo um e-mail do Astrogildo com a seguinte frase "Trenzão lotado, 5 da Matina em pé na maior friaca!....primeiro dia de um novo desafio! Feliz pra caramba!".

Pense em uma semana que começou muito bem! Levantei da cama com um baita sorriso…

** Nome fictício para facilitar a narrativa

==============

E qual o motivo dessa historinha que acabei de contar? Dia 17 de Julho eu vou ministrar mais um treinamento da Nimbus, o SQL22 - Administração e Monitoramento (http://www.srnimbus.com.br/calendario/sql22_spjulho2016/), em São Paulo.

E estou reservando uma vaga na sala para alguém que está DESEMPREGADO, que fará o treinamento SEM FAZER QUALQUER INVESTIMENTO.

Quer trabalhar como DBA SQL Server e está em busca de recolocação profissional? Seu CV não está chamando a atenção?

Mande um e-mail para luciano.moreira@srnimbus.com.br (até o dia 07/07) e me fale um pouco sobre você, o que anda fazendo para se capacitar e encontrar um bom lugar no mercado. Vou analisar os e-mails dos interessados e escolherei UM para participar do treinamento.

Quem sabe daqui a alguns meses minha semana não começa com uma boa notícia, novamente…

Abraços

Luciano Caixeta Moreira - {Luti}
luciano.moreira@srnimbus.com.br
www.twitter.com/luticm
www.srnimbus.com.br

quarta-feira, 29 de junho de 2016

Vídeo #08 - Recovery Bulk-logged e restore point-in-time (DEMO 02)

Confome prometido no último post, publiquei o vídeo com a segunda demonstração sobre recovery model bulk-logged, operações minimamente logadas e restore do banco point-in-time.

Sugiro assistir os vídeos na sequência, então caso você ainda não tenha visto o primeiro, veja post anterior: http://luticm.blogspot.com.br/2016/06/video-07-recovery-bulk-logged-e-restore.html

Neste vídeo é feito uma operação de bulk insert minimamente logada, que faz com que o bulk changed map (BCM) seja alterado, que pode ser visto com entradas "ML_MAP" no transaction log.



Abraços

Luciano Caixeta Moreira - {Luti}
luciano.moreira@srnimbus.com.br
www.twitter.com/luticm
www.srnimbus.com.br