Sunday, August 9, 2009

O "miserável" Windows XP

Estava lendo o blog do Erick Sasse, mais especificamente o post sobre as novidades do Delphi 2010, onde ele se refere ao Windows XP como miserável :).

Eu já penso 200% diferente do Erick. Não posso me dar ao luxo de usar em minha máquina de desenvolvimento algo que reconhecidamente tem bugs, ou as "features" do Vista, que irão impactar negativamente na minha produtividade.

Imagine eu estar desenvolvendo algo com cronograma apertado, como de hábito, e de repente descobrir que no Vista não consigo debugar a aplicação como fazia no XP? Isto parece lenda, mas aconteceu na mudança do Windows 2000 para o XP e o Delphi 5: Na época, debugar aplicações web sob o IIS ou objetos COM usando a dobradinha D5/Win2000 era incrivelmente fácil. Fazer o mesmo no XP, ainda mais antes do SP1 se tornou algo bem chato que de vez em quando simplesmente teima em não funcionar, sabe-se lá porquê. Aí você pára de desenvolver, ou seja, fazer a atividade fim do seu trabalho que paga seu salário, para passar horas no google procurando COMO fazer aquilo que você já fazia há anos, e agora não pode mais por causa da definição de "evolução" de Redmond.

Um outro exemplo prático do Vista: Até o SP1 do Vista, a cópia de arquivos na rede era tão mais lenta do que no XP que todo mundo pensava que a rede estava com problemas. Este problema por si só foi suficiente impactante para me deixar longe do Vista.

É claro que tenho máquinas com Vista e Windows 7, basicamente para testes mas não as usaria em produção. O Vista porquê não me acrescenta nada e me subtrai muito. A reconhecida melhoria de segurança do Vista não é bem vinda no meu ambiente de trabalho. É impossível, ou no mínimo extremamente chato e contraproducente, desenvolver aplicativos dos mais diversos e trabalhar sem as prerrogativas de segurança de administrador local. E a mania da Microsoft em perguntar toda hora para o usuário se tem certeza que deseja fazer alguma coisa ou pedir para você confirmar que você é você mesmo chegou a níveis intoleráveis no Vista. Não é à toa que 9 entre 10 usuários "satisfeitos" com o Vista que eu conheço desligaram o UAC e rodam em níveis de segurança próximos aos que experimentavam no "miserável" XP.
Penso que o UAC é ótimo para donas de casa que vivem recebendo links no email ou no MSN do tipo "veja as fotos do seu marido com outra" e clicam nele sem pestanejar! Mas para profissionais de TI?
O Vista é tão ruim, mas tão ruim, que a maioria do pessoal que o adotou e "gostou" dele não o usa mais, porquê instalou o Windows 7 no primeiro dia de lançamento da versão beta, travando os servidores da Microsoft com milhões de downloads simultâneos... O "amor" pelo Vista é tão grande que correram atrás do seu substituto, usando-o em suas máquinas principais mesmo estando na mais crua versão beta!

Já o Windows 7, eu não o uso em produção simplesmente porquê ainda está em release candidate, ou seja, se nem a Microsoft sabe se o Windows 7 está bom o suficiente para ser usado em PRODUÇÃO, muito menos eu, pobre mortal! Certamente irei usá-lo assim que ele estiver pronto.

A única coisa realmente boa do Vista foi o seu insucesso, se tornando o maior fracasso da história da Microsoft (mesmo a MS tendo forçado o Vista OEM goela abaixo de seus parceiros HP, Dell, etc.): Sem este fracasso, o cronograma do Windows 7 não teria sido muito adiantado, me permitindo by-passar completamente um sistema operacional da Microsoft pela primeira vez desde o mais miserável de todos, o famigerado Windows 3.11!

Em minha opinião, as palavras "inferior" ou "pior" para o XP em relação ao Vista simplesmente não se aplicam. O que é realmente importante é que eu faço mais (e tudo que preciso) numa máquina XP do que eu faria se estivesse usando o reluzente Vista Ultimate.

O reconhecimento da solidez da posição do XP vem da própria Microsoft que lançou o "Modo XP" no Windows 7, sem o qual eles mesmos desconfiam que muita gente não irá migrar para o 7. Mesmo assim, a adoção do novo SO parece ainda estar comprometida pelo fiasco do Vista.

PS: Hoje estava passeando pelo forum da Embarcadero e olha a pérola que eu encontrei:

"I intend to slowly incinerate the Vista box on my Weber barbecue
as soon as Windows 7 comes out, enjoying a good glass of
Prosecco whilst watching it burn. Vengeance is mine!"

https://forums.codegear.com/thread.jspa?threadID=22604&tstart=0

Este é mais um que "não compreende os benefícios do Vista" ;-)

Sunday, August 2, 2009

Make your ADO + DataSnap application FLY!

Note: These tests are valid if you are using DataSnap in conjunction with ADO for DB access.

I've been using DataSnap very successfully for years now. Lately I've been optimizing it for the best possible performance, specially when dealing with a large number of records.

Andreas Hausladen did a great job with his Midas SpeedFix, but there is more.

I have a simple table named "streets" containing some fields: ID (Integer), NAME (varchar[50]) and a few other fields (it is a large DataSet contaning all street names of all cities of my state). Well, I'm using ADO (dbGo) to access it. The table has 50,000 records. I have an ADOQuery with this SQL statement:

SELECT * FROM streets

Connected to this ADOQuery I have a DataSetProvider and a ClientDataSet. When I open the ADOQuery it took exactly 1.5 seconds, but when I open the ClientDataSet it took 59 seconds!!! 59 seconds to open a query is completely out of question in a production environment.

Some people will say: "ADO didn't fetch all the records, so the difference". That's not true. You can open the ADOQuery and go to the LAST record (fetching all the 50,000 records), and the time is the same. 1.5 seconds to fetch 50,000 records! Nice number.

Other people will say: "You can't have a 50,000 records ClientDataSet!". Can't I? Why not? 15 years ago they told me to "fetch only a few records, not thousands!". Nowadays we have 8 Gb RAM application servers, 10 Mpbs internet and the same limits remain? ClientDataSets can be used as a cache mechanism, freeing the database server from returning the same resultsets over and over again, but I need to put more than a few hundred records in it! Besides that, there are briefcase model applications. How one can create a briefcase application using DataSnap if the DataSnap framework imposes such a low limit?

So I decided to find out why ADO takes only 1.5 seconds and DataSnap took 40 times more. Profiling Provider.pas I found out that most of that time is spent inside TDataSetProvider.InternalGetRecords method. TDataSetProvider.InternalGetRecords calls inherited TBaseProvider.InternalGetRecords, and it calls TDataSetProvider.CreateDataPacket. There is a really long chain of method calls, but in the end I discovered that the bottleneck is TDataPacketWriter.WriteDataSet. Looking at this method we may have a clue:

while (not DataSet.EOF) and (Result <>
As we can see, there is a while loop going through all records of the DataSet. Well, we know that this kind of construction can be very slow because each DataSet.Next call fires a long chain of events if DataSet.ControlsDisabled = False, that is, if we didn't call DataSet.DisableControls before entering the loop.

So, just for testing purposes, I did a little change in the code, like that:

ADOQuery1.DisableControls;
try
ClientDataSet.Open;
finally
ADOQuery1.EnableControls;
end;

The results were incredible: From 59 seconds, the time spent drop to 3.3 seconds, 95% better than before! The difference is bigger, the bigger is the number of rows in the DataSet.

There are two practical problems with this approach:
1) Call DataSet.DisableControls programatically in every point of the code where there is a ClientDataSet connected to a DataSet provider is totally out of the question;
2) DisableControls cannot be used when the DataSet is acting as a Master, in a Master/Detail relationship.

Another interesting think that I've found is that DBExpress doesn't suffer the same problem, that is, DisableControls makes little difference (I will benchmark it too).

I will write about the solution I've created in a new post.

Faça sua aplicação ADO + DataSnap VOAR!

Nota: Estes testes são válidos somente para aplicações que utilizam o mecanismo de acesso a dados ADO (ou dbGo).

Tenho usado o DataSnap de forma bem sucedida há muitos anos. Ultimamente venho otimizando os meus sistemas que usam DataSnap para a melhor performance possível, especialmente quando estou lidando com DataSets com muitos registros.

Andreas Hausladen fez um trabalho excelente em seu Midas SpeedFix, mas tem mais.

Eu tenho uma tabela simples "RUAS" contendo alguns campos: ID (Integer), NOME (varchar[50]) e mais alguns poucos campos. Bem, estou usando ADO para o acesso aos dados. A tabela tem 50.000 registros. Eu tenho um ADOQuery com este comando SQL:
SELECT * FROM ruas

Conectado a este ADOQuery eu tenho um DataSetProvider e a ele um ClientDataSet. Quando eu abro o ADOQuery leva 1,5 segundos, mas quando eu abro o ClientDataSet leva exatamente 59 segundos!!! 59 segundos para abrir uma query num ambiente de produção está completamente fora de questão.

Alguns vão dizer: "O ADO não fez o fetch de todos os registros, daí a diferença". Isto não é verdade. Você pode abrir o ADOQuery e ir para o último registro (efetivamente fazendo um fetch all) e o tempo será o mesmo. 1,5 segundoso para recuperar 50.000 registros! Um bom número.

Outros irão dizer: "Você não pode ter um ClientDataSet com 50.000 registros!". Não posso? Porquê não? 15 anos atrás me disseram "traga apenas alguns registros, não milhares". Atualmente temos servidores de aplicação com 8 Gb de RAM pelo menos, internet banda larga com 10 Mbps e os mesmos limites de 15 anos atrás se aplicam?

O ClientDataSet pode ser usado como mecanismo de cache, liberando o servidor de banco de dados de retornar o mesmo resultset repetidamente, mas eu preciso colocar mais do que umas poucas centenas de registros nele! Além disso, existem aplicações que seguem o modelo "Briefcase". Como alguém pode desenvolver uma aplicação com este modelo se o framework DataSnap impõe um limite tão baixo?

Então eu decidi descobrir porquê o ADO leva 1,5 segundos e o DataSnap leva 40 vezes mais. Fiz um profile da unit Provider.pas e descobri que a maior parte do tempo é gasta dentro do método TDataSetProvider.InternalGetRecords. O método TDataSetProvider.InternalGetRecords chama o TBaseProvider.InternalGetRecords herdado, que por sua vez chama TDataSetProvider.CreateDataPacket. Existe uma longa cadeia de chamadas de métodos, mas no final eu descobri que o gargalo é o método TDataPacketWriter.WriteDataSet. Analisando este método talvez tenhamos uma dica:

while (not DataSet.EOF) and (Result < RecsOut) do
begin
FIDSWriter.PutField(fldIsChanged, 1, @B);
for i := 0 to High(Info) do
Info[i].PutProc(@Info[i]);
Inc(Result);
if Result < RecsOut then
DataSet.Next;
end;

Como podemos ver, existe um loop while percorrendo todos os registros do DataSet. Bem, nós sabemos que este tipo de cenário pode ser bem lento pois cada chamada a DataSet.Next dispara uma longa sequência de eventos se DataSet.ControlsDisabled = False, ou seja, se não chamarmos o método DataSet.DisableControls antes do loop.

Então, só para testar o efeito de DisableControls na velocidade da obtenção dos dados pelo DataSetProvider, eu fiz uma pequena alteração no código onde eu o ClientDataSet era aberto, da seguinte forma:

ADOQuery1.DisableControls;
try
ClientDataSet.Open;
finally
ADOQuery1.EnableControls;
end;

O resultado que eu obtive foi incrível: Dos 59 segundos originais, o tempo caiu para 3,3 segundos, uma melhora de quase 95% no tempo de abertura do ClientDataSet!
Eu pude verificar que a diferença de performance é tanto maior quanto mais registros existem no ClientDataSet.

Existem dois problemas de ordem prática nesta abordagem:
1) Usar DisableControls programaticamente em todos os pontos do sistema onde se tem um ClientDataSet ligado a um DataSetProvider é totalmente inviável;
2) DisableControls não pode ser utilizado quando a tabela é master em uma relação Master/Detail.

Outra coisa interessante que eu descobri é que o DBExpress não sofre do mesmo problema, isto é, DisbleControls faz pouca diferença. De qualquer forma, irei medir o desempenho do DBExpress, com e sem DisableControls para comparar os resultados.

A solução que eu criei terá abordada num próximo post.