sexta-feira, 9 de janeiro de 2009

Guia para solução de problemas relacionados ao desempenho de sistemas

SUMÁRIO

  • 1- Objetivo deste documento

  • 2- Público-alvo

  • 3- Resolução de problemas de lentidão
    • 3.1- Locks no banco
    • 3.2- Rede
    • 3.3- Nível de log verboso e uso desnecessário de appenders
    • 3.4- Modo de debug do Java
    • 3.5- Máquina fazendo swap
    • 3.6- Sobrecarga de processos na máquina
    • 3.7- Falta de índices e estatísticas do banco desatualizadas
    • 3.8- Cache de Entity Beans com tamanho inadequado
    • 3.9- Má configuração do Garbage Collector da JVM
    • 3.10 - Configuração inadequada do pool de MDBs
    • 3.11 - Cache de prepared statements não configurado

  • 4- Lista de problemas comuns e suas respectivas soluções
    • 4.1- Estouro da área da Heap
    • 4.2- Stack Overflow
    • 4.3- Estouro da área permanente da JVM
    • 4.4- Estouro de conexões de banco no pool
    • 4.5- Conflitos nos parâmetros específicos de determinada JVM
    • 4.6- Número máximo de arquivos abertos não suficiente
    • 4.7- Threads HTTP do Tomcat acima do número especificado
    • 4.8- Timeout de transação
    • 4.9- Negação de conexão à JMS
    • 4.10- Pad do XidFactory desligado
    • 4.11- idleTimeout no Oracle estar menor que no JBoss
    • 4.12- Número de processos suportados pelo Oracle
    • 4.13- Estouro do número de cursores abertos no Oracle

  • 5- Conclusões e considerações finais

  • 6- Referências

  • 7- Autores


1- Objetivo

As idéias contidas aqui não têm como objetivo dar uma receita direta para a solução de todos os problemas de desempenho encontrados, e sim servir como um guia para análise. Podem surgir novas situações relacionadas a desempenho não contidas neste documento e suas soluções podem necessitar de uma análise mais detalhada dos analistas de desempenho e/ou arquitetos.

A seção 3 descreve os principais causadores de lentidão e a seção 4 mostra outros problemas e suas respectivas soluções.



2- Público-alvo

Todos os envolvidos com testes de desempenho e pessoas responsáveis pelo suporte aos sistemas computacionais.



3- Resolução de problemas de lentidão

Quando detectado algum problema de lentidão, verificar os itens abaixo. Pode ser que a resolução não esteja nestes itens, mas é aconselhável primeiramente analisá-los:


3.1- Locks no banco

Os locks no banco ocorrem quando algum processo reserva determinada linha ou tabela do banco e não os libera, fazendo os outros processos esperarem indefinidamente pelos recursos.

Solução: Para detectar se a lentidão está ocorrendo devido à locks de banco, solicitar ao DBA responsável pelo ambiente, o monitoramento do banco de dados no momento da execução. Se for detectado algum lock, verificar com o arquiteto do sistema o motivo deste lock ter ocorrido, em busca de uma solução.


3.2- Rede

Os problemas mais comuns que encontramos relacionados à rede são:

perdas de pacotes: A rede pode perder pacotes devido à colisões e erros. Esses problemas podem ser gerados através de falhas físicas (cabeamento e conectores) e de configuração (roteador, switch e interface de rede) uso ultrapassando a banda máxima: A banda de rede necessária deve ser especificada anteriormente, mas eventualmente pode ocorrer indisponibilidade de banda se ela for mal dimensionada.

Solução: Neste caso deve-se monitorar a rede, através de ferramentas como o netstat para verificar como está a perda de pacote e as colisões. Através de ferramentas tais como o MRTG e PRTG, também é possível monitorar como está o uso da banda graficamente, através de protocolo SNMP.

O parâmetro “-i” do netstat mostra como estão as interfaces de rede, e o parâmetro “-a” mostra todas as conexões no momento. Em caso de lentidão em telas convém utilizar o parâmetro “-a” para verificar quando uma requisição chega ao servidor web e quando a requisição do servidor web chega ao banco. Isso pode mostrar claramente qual o overhead da rede na requisição. Vale lembrar que para cada sistema o netstat tem um formato diferente. Às vezes é necessário dar uma analisada melhor na versão que você está utilizando.


3.3- Nível de log verboso e uso desnecessário de appenders

Pode ser necessário a inclusão de categorias com níveis de log DEBUG para verificação de alguma funcionalidade dentro do arquivo de configuração do log4j. Estas configurações podem eventualmente serem esquecidas no arquivo. Com isso a aplicação executa mais lentamente, principalmente se o log for muito verboso. Outro fato importante a se considerar é quando se utiliza mais do que um appender desnecessáriamente, por exemplo, um appender para a saída padrão e outro para o server.log. Para logs não muito extensos isso não interfere muito, mas para aplicações em que o log é escrito frequentemente, pode ocorrer lentidão, pelo fato da velocidade de escrita em disco.

Solução: Devemos tirar todas as ocorrências do nível de DEBUG em ambientes de produção, a menos que se faça necessário por um determinado momento (testes). Se não estamos utilizando um appender, é melhor desabilitá-lo.

Devido a uma má prática de implementação pode existir uma política de log incorreta, gerando muitos dados nos arquivos, mesmo sem utilizar categorias mais verbosas. Quando isso é detectado, deve se comunicar o arquiteto para uma análise melhor de cada tipo de log e quando deve aparecer.


3.4- Modo de debug do Java

Já vivenciamos casos em que foi necessário habilitar, em algum momento, o modo de debug do Java em ambientes de testes sistêmicos e a configuração foi replicando de tal forma que acabou indo até o cliente. Isso não pode ocorrer pois torna a execução da aplicação muito mais lenta. Se surgir a necessidade de se fazer um debug, não esquecer de alterar logo após o seu término

Solução: Desabilitar o debug do Java, configurando o JAVA_OPTS sem a opção de debug. (-Xdebug e -Xrunjdwp) nos ambientes que não será feito o debug pelos desenvolvedores.


3.5- Máquina fazendo swap

O sistema operacional, quando precisa de memória, primeiro tenta encontrar recursos na memória física da máquina. Caso não encontre, ele utiliza a área de swap do sistema. A área de swap é uma área do disco que o SO utiliza como memória extra.

Como a leitura em disco é muito mais lenta do que a leitura direta na memória, se o sistema estiver utilizando muito a área de swap, a aplicação tende a ficar bem mais lenta.

Solução: Verificar qual aplicação está utilizando mais memória, inclusive o banco de dados através do comando top/prstat/ps. Se o problema for a configuração de memória para o banco ou aplicação, reduzir e testar novamente. Se o problema for a aplicação em si, comunicar os analistas de desempenho e/ou arquitetos para que possam encontrar qual o ponto do código que cria objetos demasiadamente através de profilers.

Pode ser que o problema não seja a aplicação e sim a falta de recurso da máquina para rodar a aplicação. Se isto for detectado deve-se solicitar o aumento da memória disponível.


3.6- Sobrecarga de processos na máquina

A lentidão de um sistema pode ser causada por que os processadores não estão suportando o número de processos/threads em execução.

Solução: Verificar com o comando vmstat (coluna r) os processos rodando no momento. Se este número for maior do que o número de processadores disponíveis, a máquina poderá estar sofrendo sobrecarga. É necessário então verificar se existem processos que não deveriam estar rodando e se o número de threads ou MDBs da aplicação foi configurado erroneamente. Outra coluna que mostra bem que essa sobrecarga está ocorrendo é a cs. Ela mostra o número de trocas de contexto por segundo. Se este número estiver muito alto, é evidente que os processadores estão executando muitos processos e perdendo grande parte do seu tempo com o chaveamento. É necessário fazer um tuning fino de threads da aplicação no ambiente final para que isso não ocorra.


3.7- Falta de índices e estatísticas do banco desatualizadas

Os índices correspondem a uma parte muito importante durante a análise de desempenho. Normalmente quando se coloca um índice em uma tabela grande a diferença no tempo da consulta muda dezenas ou centenas de vezes.

Com isso, antes de mais nada, quando se encontra um problema de lentidão, é recomendável a análise dos índices. As estatísticas do banco também devem ser atualizadas frequentemente, para evitar que o Oracle faça planos de execução inviáveis no caso da estratégia “choose”. A falta de índices já é verificada durante o teste de performance, mas não podemos descartar esta análise.

Solução: Monitorar as consultas ao SGBD com ferramentas tais como o P6SPY? e IronEye na qual é possível visualizar as queries problemáticas. Achada alguma query problemática, verificar se não está faltando nenhum índice na tabela relacionada.


3.8- Cache de Entity Beans com tamanho inadequado

Se o cache de entity beans estiver mal configurado com um valor abaixo do que a aplicação utiliza, podemos ter lentidão causada pelo processo de ativação, passivação e chamadas ao banco. Se este número estiver muito alto, podemos ter uso de memória demasiado.

Solução: É necessário fazer vários testes tentando chegar em seu número ideal para o processamento no ambiente alvo.


3.9- Má configuração do Garbage Collector da JVM

A configuração do Garbage Collector não é tão simples e não há solução pré-determinada. Isto depende da máquina que estamos utilizando e de nossa aplicação. Se este estiver mal configurado, pode causar transtornos ao desempenho dos sistema. Para evitar problemas do tipo, no manual de instalação especificamos uma configuração básica inicial. É importante ressaltar que aqueles parâmetros ali especificados representam somente um dado inicial mínimo para o funcionamento. É necessário fazer um trabalho detalhado de tuning de GC para garantir a performance ideal do sistema para a plataforma definida.

Solução: Verificar as configurações iniciais contidas no documento de instalação da suite. Se as configurações estiverem fora do especificado, alterá-las conforme o manual. Se estiverem dentro do especificado vale a pena verificar através do verbose:gc, jstat, visualGC ou JConsole o comportamento do Garbage Collector e fazer as alterações necessárias em relação ao tamanho da heap, se necessário.


3.10- Configuração inadequada do pool de MDBs

A configuração correta dos MDBs é essencial para uma performance adequada. Se seu número é abaixo do ideal, os recursos do ambiente computacional vão ser subutilizados. Se ultrapassarmos o limite ideal, podemos causar sobrecarga de máquina, tornando o processo lento.

Solução: Verificar o uso de CPU. Se estiver com pouca utilização, aumentar o número de MDBs e verificar o que mudou. Observar que às vezes o uso de CPU é baixo, não porque temos poucos MDBs e sim porque o gargalo do sistema é outro. Por isso é interessante fazer uma verificação de como está a memória, resposta dos discos, rede, etc.

Se a CPU estiver com média de uso maior que 80%, isso pode indicar que está havendo uma sobrecarga do sistema. Então é necessário diminuir o número de MDBs até chegar em uma média boa de utilização de CPU.

A configuração dos MDBs deve levar em conta também a janela de execução de cada módulo da solução e prioridades entre eles através de configurações específicas.


3.11- Cache de prepared statements não configurado

O cache dos prepared statements deve ser utilizado, para evitar o trabalho de parse e compilação desnecessária pelo SGBD em toda execução. Notamos ganhos significantes em alguns módulos quando utilizamos este parâmetro. Atualmente estamos utilizando o seu valor como 100. Esse parâmetro é o inicial definido pelos arquitetos. Pode ser necessário fazer um tuning específico para determinado ambiente.

Solução: Verificar como este parâmetro foi configurado. Se seu ambiente não estiver igual ao especificado no manual alterá-lo.



4- Lista de problemas comuns e suas respectivas soluções

4.1- Estouro da área da Heap

A heap é o espaço em memória da JVM onde são armazenados os objetos em Java. Se esse espaço ultrapassa seus limites estabelecidos inicialmente, ocorre um estouro. A JVM normalmente acaba abortando o processo.

Problema: No log é mostrada a string “OutOfMemoryError”. Quando foi a área permanente que estourou é mostrado PermGen space seguido deste erro. Neste caso a resolução é o item 4.3.

Solução: Aumentar a área da heap, conforme a necessidade, alterando o parâmetro Xmx para um valor maior do que o atual. Nesses casos é aconselhável fazer um monitoramento da máquina para fazer um dimensionamento ideal da área da heap. Esse monitoramento pode ser feito através das ferramentas jvmstat, visualGC e JConsole (JVM 1.5). As JVMs já vêm com a opção leve e util de monitoramento de coletas de lixo “-verbose:gc”. Com ela é possível monitorar cada GC, dando a informação em quanto estava a heap no momento da limpeza:

Exemplo:

[GC 325407K->83000K(776768K), 0.2300771 secs]

Neste caso, a heap que estava com 325407K de espaço usado, sofreu uma coleta, e possui, após a coleta, 83000K. A heap total neste caso é de 776768K.


4.2- Stack Overflow

A pilha de chamadas é uma área de memória especifica para cada thread. Ela é dividida em frames para cada método, onde a informação não é compartilhada.

Se o tamanho da pilha estiver menor do que o necessário pode ocorrer o estouro de pilha. O estouro pode ter outros motivos como chamadas recursivas exageradas. Não podemos exagerar na configuração do tamanho da pilha pois isso pode ter um efeito colateral. Quando aumentamos muito a área que cada thread ocupa, se tivermos muitas threads, podemos ter o estouro da heap (item 3.1). Normalmente 128k é suficiente para a maioria dos programas Java, mas isso pode ser insuficiente em alguns casos. O valor padrão depende da plataforma e versão de JVM, mas está normalmente nos valores 256k ou 512k. Este limite de 128k não se demonstrou suficiente para alguns ambientes de integração, por isso é aconselhável seguir o manual de instalação.

Quando a pilha de chamadas da thread excede seu limite a aplicação aborta sua execução.

Problema: No log é mostrada a string “StackOverflowError”

Solução: aumentar a área da pilha (stack size) com o parâmetro -Xss= e fazer um reteste.


4.3- Estouro da área permanente da JVM

A área permanente é uma área de memória alocada por processos não relacionados à criação de objetos, entre eles: Carga de classes (Classloader) Área de métodos (código compilado) Classes geradas dinamicamente (JSPs) Objetos nativos (JNI)

Essa área de memória dificilmente passa por uma limpeza. É uma área que não faz parte da parametrização de tamanho total de heap. Existem dois parâmetros específicos para configuração dessa área: -XX:PermSize e -XX:MaxPermSize (em algumas JVMs). O primeiro específica a área mínima e o segundo a máxima.

Problema: no log é mostrado a string “java.lang.OutOfMemoryError: PermGen space”

S*olução:* aumentar os parâmetros -XX:PermSize e -XX:MaxPermSize para um valor identificado como ideal através das ferramentas visualGC ou JConsole (JVM 1.5)


4.4- Estouro de conexões de banco no pool

As conexões com o banco dados em nossas aplicações é gerenciado por pools que são configuráveis nos arquivos com final “ds.xml” dentro do diretório de deploy do JBoss. A configuração do número máximo depende da quantidade de threads (nosso caso MDBs) que está acessando o banco simultaneamente. Para saber quantas conexões estão ativas e qual o máximo utilizado pelo processo podemos verificar os atributos InUseConnections e MaxInUseConnections respectivamente do mbean JCA do pool do datasource correspondente através da interface jmx-console ou web-console do JBoss.

Problema: no log é mostrado a mensagem “No managed connections”

Solução: aumentar o número máximo de conexões do pool afetado, ajustando para um valor cerca de 20% acima do alcançado em um teste com uma carga real.

Dicas: No log da exceção, o JBoss não mostra qual datasource estourou e sim qual era o blocking timeout dele. Para saber qual deles estourou rapidamente podemos ajustar os blocking timeouts de cada datasource diferenciando 1 milisegundo de cada. Desta forma já pelo log é possível detectar qual datasource teve o problema.

Nunca deixe o blocking timeout com um valor muito alto pois ele pode te enganar, fazendo o processo esperar muito tempo por uma conexão sem reclamar.

Para evitar problemas em um ambiente de produção, é aconselhável colocar um valor bem alto no número máximo de conexões do pool, evitando erros de falta de conexão.


4.5- Conflitos nos parâmetros específicos de determinada JVM

Quando for configurar alguns parâmetros da JVM, é importante observar que existem parâmetros específicos para cada JVM.

Problema: Quando o parâmetro não existe na JVM, ela nem chega a rodar, dando um erro de parâmetro não reconhecido.

Solução: Tirar o parâmetro que está com conflito e tentar achar um parâmetro equivalente na atual JVM através de seu manual.


4.6- Número máximo de arquivos abertos não suficiente

Cada arquivo possui seu descritor no Linux. Um socket de conexão é associado também à um arquivo no sistema operacional. Com isso, para aplicações onde temos muitas conexões HTTP, como um call center, pode haver estouro do número máximo de descritores de arquivos.

Problema: O número máximo de arquivos abertos pode estourar, o que acarreta em negação de abertura de novos arquivos pelo processo, ou mesmo pelo sistema operacional. Isso pode gerar em negação de abertura de socket com a máquina, já que cada socket abre um arquivo no sistema operacional.

Solução: Verifique o número atual de arquivos abertos pelo processo e verifique se esse número não ultrapassou o máximo permitido. Também verifique o número total de arquivos abertos por todos os processos e seu respectivo limite.

Para verificar o uso de arquivos por processo utilize o comando lsof e para ver o numero total permitido utilize o comando ulimit -n.

Para verificar o uso de arquivos total de todos os processos utilize o comando lsof e para visualizar o tamanho total permitido o comando “cat /proc/sys/fs/file-max”

Verificar se o número não está fora dos limites aceitáveis. Se estiver, comunicar o arquiteto do sistema e o analista de desempenho para uma análise mais detalhada. Se o número realmente estiver baixo, aumentar os parâmetros do SO:


4.7- Threads HTTP do Tomcat acima do número especificado

Problema: A aplicação nega a conexão web para um determinado usuário. Com isso o usuário recebe uma página de acesso negado na tela do browser e aparace no log uma mensagem de connection refused.

Solução: A solução para esse problema é aumentar o número de conexões no pool de conexões HTTP do Tomcat. É aconselhável alterar esse limite para o número esperado de usuários somando 20% para picos inesperados.


4.8- Timeout de transação

Algumas aplicações, como o nosso caso, exigem um transação muito longa, às vezes tornando necessário o aumento do limíte default de 300 segundos do JBoss para um numero mais alto.

Problema: É mostrado no log a exceção de Transaction Timeout

Solução: Verificar o padrão para a suite especificado no documento de instalação e se estiver correto, acionar o arquiteto para definição de outro valor ou averiguação do sistema.


4.9- Negação de conexão à JMS

A negação de conexão à JMS ocorre caso haja uma quantidade de MDBs configurada insuficiente ou devido à um problema de rede com a máquina onde estão as filas JMS.

Problema: No log aparece: org.jboss.mq.SpyJMSException: Cannot process a transaction; - nested throwable: (java.io.IOException: Client is not connected)

Solução: Verificar primeiramente se o número de MDBs está correto. Se estiver correto, aumentar o número de conexões no datasource JMS e monitorar o uso dessas conexões a fim de se chegar no número ideal.


4.10- Pad do XidFactory desligado

O serviço de transação usa o XidFactory para gerar os identificadores de transações XA. Para o Oracle o tamanho deste identificador pode extrapolar o limite default. Para evitar esse problema, é necessário configurar o atributo “pad” para true no mbean org.jboss.tm.XidFactory contido no arquivo jboss-service.xml.

Problema: Ocorre uma XAException no log dizendo que o Xid da transação é inválido.

Solução: Alterar o atributo pad para true (basta tirar o comentário padrão)


4.11- idleTimeout no Oracle estar menor que no JBoss

Quando o idleTimeout do Oracle estiver com um valor menor que o idleTimeout do datasource no JBoss corremos risco do Oracle derrubar uma conexão antes do JBoss, fazendo o servidor de aplicações contar com uma conexão que já não existe. Este problema é solucionado deixando esse parâmetro como ilimitado no Oracle, deixando somente o JBoss invalidar as conexões.

Problema: Ocorre uma exceção ao solicitar uma conexão que não existe mais.

Solução: Solicitar ao DBA a alteração do parâmetro de idleTimeout do Oracle para infinito.


4.12- Número de processos suportados pelo Oracle

O parâmetro PROCESS do Oracle indica qual o número máximo de processos que ele pode abrir. Esse número é dependente da máquina e necessidade de cada cliente

Problema: É mostrado um erro do Oracle no log do servidor de aplicações dizendo que este excedeu o número máximo de processos.

Solução: Solicitar ao DBA uma análise do caso específico e um tuning para um número ideal.


4.13- Estouro do número de cursores abertos no Oracle

Para cada execução de determinada query o Oracle abre um cursor. Esse número pode ultrapassar o limite máximo por algum motivo específico. Com isso o Oracle pára de responder e nega conexões com a aplicação.

Problema: no log é mostrado: ORA-01000 maximum open cursors exceeded

Solução: Aumentar o valor do parâmetro de MAX_CURSORS do Oracle para um limite maior do que o atual. Para isso solicite ao suporte à análise do valor ideal e alteração do parâmetro.



5- Conclusões e considerações finais

Os problemas/soluções listados neste documento servirão para uma agilidade maior na resolução de problemas relacionados ao desempenho. Com isso, quando houver algum problema de desempenho, é recomendável a leitura deste documento para analise. Este documento será atualizado sempre que houver algum outro problema relevante encontrado.



6- Referências

Red Hat Enterprise Linux (2006). System administration guide http://www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/sysadmin-guide/

Sun Microsystems (2005b). Monitoring and Management for the Java Platform. http://java.sun.com/j2se/1.5.0/docs/guide/management/

JBoss Tuning Tutorial http://tutorials.freeskills.com/ps/jboss_tuning_tutorial.htm

Java Performance Tuning http://www.javaperformancetuning.com/

JBoss - Using other Databases http://docs.jboss.org/jbossas/getting_started/v4/html/db.html

Shirazi, J. Java Performance Tuning. O’Reilly & Associates, 2000



7- Autores

Angelo Ferecini / Domingos Creado / Rogério Colferai / Douglas Gomes