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
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.
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:
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.
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.
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.
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
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.
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=
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.
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.
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.
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
Angelo Ferecini / Domingos Creado / Rogério Colferai / Douglas Gomes