Resposta ao Desafio da Semana #11 [Hang de Alta CPU em Script]


Por: Roberto Alexis Farah

Olá pessoal!

Eis a reposta do Desafio da Semana #11: https://blogs.technet.com/latam/archive/2006/09/29/459977.aspx

PROBLEMA

O fato do loop abaixo executar durante vários minutos indica que o loop está lendo vários arquivos… portanto, sabemos que está buscando um arquivo num diretório com muitas imagens! É possível se deduzir isso sem ter conhecimento real do diretório onde estão as imagens.

Nota: No cenário real pude comprovar, posteriormente, que o diretório em questão tinha 2 gb de imagens!

Set objFso = Server.CreateObject("Scripting.FileSystemObject")

Set objPasta = objFso.GetFolder(Server.MapPath("/imagens/grande/" & left(rsProduto("codigo"), 2)))

imagemGrande = false

For Each objArq in objPasta.Files

If ucase(objArq.Name) = ucase(rsProduto("codigo") & ".jpg") then

imagemGrande = true

end if

Next

Pois bem, o código acima roda perfeitamente na maioria dos cenários e provavelmente será muito rápido mesmo com centenas de arquivos, entretanto, com uma imensa quantidade de arquivos o loop vai executar continuamente por um longo tempo… (mais de 5 segundos é um longo tempo para aplicações web) como não há uma pausa (e nem deveria) a CPU será consumida por cada thread executando o loop acima.

Note que uma observada mais atenta nos revela que o loop é usado para checar a existência de um determinado arquivo, apenas isso.

Dependendo da ordem do arquivo teremos o resultado de imediato, ou então teremos uma longa demora. No pior cenário, fazer a comparação com um arquivo que não existe, faria com que o loop fosse executado “n” vezes onde “n” seria a quantidade de arquivos do diretório!

SOLUÇÃO

A solução é trivial baseando-se em dispensar o loop para checar se o arquivo existe, verificando-se de modo direto a existência do arquivo.

As alterações são pequenas e localizadas no fragmento de código ocasionando o aumento de CPU.

Set objFso = Server.CreateObject("Scripting.FileSystemObject")

‘Eis a linha que verifica se o arquivo existe.

imagemGrande = objFso.FileExists(Server.MapPath("/imagens/grande/" & left(rsProduto("codigo"), 2)) & "\" & rsProduto("codigo") & ".jpg")

<NOTA SOBRE SOLUÇÕES>

No meu modo de ver soluções de problemas, assim como diferentes arquiteturas, devem ser analisadas em termos de prós e contras, vantagens e desvantagens. Particularmente acho binário demais se julgar uma solução apenas como certa ou errada se tivermos diferentes soluções que podemos qualificar como corretas.

Para engenheiros de suporte e escalação o tipo de solução que buscamos para nossos clientes é algo que seja simples e de rápida implementação. Ou seja, uma solução direta como uma pequena cirurgia na aplicação com o específico propósito de resolver o problema, ou no pior cenário, quando uma solução simples não é possível, procuramos uma solução que alivie os sintomas, dando tempo ao cliente para estudar uma solução que resolva o problema sem a urgência de antes.

Geralmente apresentamos algumas soluções possíveis com suas respectivas vantagens e desvantagens. Pela natureza reativa da situação os clientes tendem a escolher a solução mais simples que resolva o problema de imediato, ou o paliativo que amenize os sintomas enquanto uma solução é estudada.

Eis um exemplo típico do que quero dizer com vantagens e desvantagens: Imagine um cenário onde uma aplicação C++ é constantemente modificada e apresenta memory leaks. Se lidamos com um incidente desse tipo vamos identificar as linhas de código ocasionando memory leaks e recomendar as devidas correções. Vamos orientar o cliente a checar outras partes do código que podem, também, ter os mesmos problemas e recomendar medidas pró-ativas para evitar o memory leak no futuro ou durante a fase de testes da aplicação.

Diante desse mesmo cenário consultores poderiam propor uma solução diferente, como reescrever a aplicação em C# se identificado que o problema não é tão crítico para ser solucionado de imediato. Afinal, estando a aplicação rodando em C# os riscos de memory leak deverão tender a zero, as manutenções serão mais rápidas pois C# é mais simples e será mais fácil encontrar engenheiros com skills de C# do que engenheiros com skills de C++.

O custo será maior? A curto prazo sim, mas no futuro a aplicação terá manutenção mais barata e menos sujeita a ocasionar problemas desse tipo!

Eventualmente as duas soluções poderiam ser aplicadas: Arruma-se o memory leak e, com o problema solucionado, inicia-se o desenvolvimento em C# onde a arquitetura da solução poderia ser mesmo melhorada.

Meu exemplo foi bastante simplista, deixando de lado várias outras variáveis, mas serve para ilustrar que podemos ter diferentes soluções com suas respectivas vantagens e desvantagens. E mesmo que podemos combinar diferentes soluções, obtendo-se uma solução ainda melhor.

</NOTA SOBRE SOLUÇÕES>

De volta ao nosso desafio, esse é um típico problema de escalabilidade! A solução funcionava bem e, de fato, funcionou bem por anos, mas com o aumento de carga, no caso produtos com imagens associadas, começou a ocorrer lentidão e alto consumo de CPU.

Problemas de escalabilidade podem ocorrer porque a aplicação não foi concebida para suportar um maior aumento de número de usuários, dados, etc… quando deveria ou simplesmente porque não foi concebida para um determinado tipo de uso. O uso inapropriado da aplicação seria o que denominamos problema “by design”, ou seja, não um bug mas uma consequência de uso inapropriado da aplicação uma vez que a aplicação não foi arquitetada para tal uso.

Eis uma analogia: Imagine colocar uma Ferrari para andar nas areias de um deserto. Se o carro atolar isso não significa que esteja com defeito! Simplesmente não foi projetado para esse tipo de uso. J

Um exemplo real que já me deparei algumas vezes em incidentes: Usar o banco de dados Access com IIS em aplicações comerciais.

Temos artigos públicos explicando que o Access não deveria ser usado para isso, mas vez por outra trabalhamos em incidentes de performance onde a solução comercial usa IIS com Access. Infelizmente, raramente conseguimos uma solução para o problema de performance e muitas vezes mesmo um paliativo (workarond) para amenizar os sintomas é difícil.

Problemas nesse tipo de solução seriam algo “by design”, em outras palavras, é previsto que há instabilidade utilizando-se o Access em aplicações que exigem alta disponibilidade e grande volume de usuários e isso está publicamente documentado.

Using Microsoft Jet with IIS

https://support.microsoft.com/kb/299974/en-us

When you need unlimited users, 24x7 support, and ACID transactions, we recommend that you use Microsoft SQL Server with Internet Information Server (IIS). Although Active Server Pages (ASP) works with any OLE DB and ODBC-compliant database, IIS has been extensively tested and is designed to work with Microsoft SQL Server with high transaction traffic and unlimited users that can occur in an Internet scenario.

The Microsoft Access ODBC driver (Jet ODBC driver) can have stability issues due to the version of Visual Basic for Applications that is invoked because the version is not thread safe. As a result, when multiple concurrent users make requests of a Microsoft Access database, unpredictable results may occur.

While Microsoft Jet is consciously (and continually) updated with many quality, functional, and performance improvements, it was not intended (or architected) for the high-stress performance required by 24x7 scenarios, ACID transactions, or unlimited users, that is, scenarios where there has to be absolute data integrity or very high concurrency.”

Até o próximo desafio!