Back-End

23 out, 2014

Tunning de solução Java Web

Publicidade

Segue abaixo um relato resumido de uma das minhas consultorias para uma empresa que tinha uma solução web feita em Java que estava apresentando travamento e tempo de resposta ruim. O objetivo com esse artigo é apresentar os problemas encontrados na solução e as medidas adotadas de otimização, de forma com que outros profissionais possam aprender com os fatos e ou reutilizar as estratégias encontradas.

1. Otimização de acesso ao SGDB

Fizemos uma triagem da quantidade de acessos que o sistema faz ao sgdb quando um usuário acessa os principais processos. O resultado foi 117 round-trips. Após estudos e otimizações, reduzimos para 41 nos mesmos processos. Foi uma redução de 65%! Dado que o número de acessos diário dessa solução é de +- 7 mil – isso que dizer que reduzimos por dia (117 – 41 = 76 * 7.000) 532 mil acesso ao sgdb. Essa otimização, além de aumentar o tempo de resposta, reduziu drasticamente o número de conexões do DataSource e a quantidade de parse de framework ORM, o que resultou em menos gastos com HEAP.

2. Otimização de auditoria

O sistema usa uma tabela de logg que registra toda a auditoria de seus processos no qual tem uma coluna auto incrementável manual com uma chave composta, que por sua vez resulta em algumas consequências negativas:

  1. Para evitar inconsistências de concorrência nos MAXID + 1 para inserir registro, o sistema sincroniza a concorrência em todos os processos, deixando a execução da solução em modo “MONO” usuários.
  2. Para cada novo registro de logg, o sistema é obrigado a fazer + 1 acesso ao sgdb para pegar o próximo MAXID + 1 a ser gravado.

Após estudos e otimizações, resolvemos ambos os problemas criando um nova tabela de logg contendo a mesma estrutura, só que utilizando o conceito de UUID como chave primária. O resultado foi que para cada novo registro de auditoria não é mais necessário acessar o sgdb para buscar o próximo MAXID + 1, uma vez que o UUID é gerado pela própria solução. Usando a mesma média de acesso diário já indicada acima, reduzimos + 7 mil acessos ao sgdb por dia. A partir dessa medida, foi possível retirar todas as sincronizações das operações de auditoria, fazendo com que eles pudessem ser executados de forma agora 100% concorrentes.

3. Auditoria assíncrona

O sistema faz registros de loggs com características somente de acesso, não sendo processos idempotentes. Do ponto de vista arquitetural, não faz sentido o usuário final esperar estes registros de loggs acontecer enquanto navega na solução. Com isso,  foi elaborada uma nova arquitetura de logg assíncrona específica para estes processos, no qual o sistema faz uso de threads separadas que vão registrando os loggs, sem fazer o usuário pagar o tempo de espera na resposta da sua requisição HTTP.

4. Índices de acesso

Depois de averiguações, foi constatado que as tabelas do sistema estão sem nenhum índice, fazendo com que o sgdb busque os registros originários das consultas do sistema (WHERE) 100% sequencial. Com base em todas as consultas existentes, foi criado uma API de índices que promova melhor performance nas consultas das tabelas, conforme descrito no livro oficial do provedor do sgdb. Foram criados exatamente 63 índices.

5. Evitando bloqueios pessimistas

O sistema faz acesso constante a um banco de dados de uma outra solução legada da década de 90, que por sua vez, utiliza-se do conceito de bloqueio pessimista para assegurar a consistência nas concorrências de seus processos. Em virtude disso, esse sistema sofre intensivo bloqueio, não conseguindo acessar as tabelas dessa outra solução e ocasionando diversas paradas diárias sentidas constantemente pelos usuários finais. Para contornar isso, aplicamos o nível de isolamento das consultas do sistema para READ_UNCOMMITED que faz leituras das tabela independente dos bloqueios existentes. Os únicos lugares que tivemos que manter o nível READ_COMMITED é nas operações de processos de negócios. Como hoje o sistema é praticamente 70% de consultas e 30% de operações de negócios transacionais, o usuário final com essa nova otimização não ficará mais  impedido de acessar e consultar informações quando existir bloqueios pessimistas. Isso reduzirá drasticamente a maioria das reclamações.

6. Otimização de filtro NO-CACHE

Depois de averiguações, foi constatado que a solução faz uso de um filtro servlet para decorar recursos web como “no-cache” HTTP. Nesse controle, encontramos o uso de operação de substring() e contains() da classejava.lang.String. Para otimizar, foi utilizado o recurso de expressão regular, juntamente com a classe java.lang.Match que funciona bem mais rápido na procura de padrões de texto. Com base na mesma média de acesso diário já indicada acima e que em cada requisição HTTP o filtro é executado +- 6 vezes, essa otimização deixou a tempo de resposta bem melhor.

7. Mensurando as otimizações

Para podermos quantificar as melhorias das otimizações, foi feita uma amostra de teste usando nas seguintes configurações SEVIDOR LINUX, JAVA7, TOMCAT 6 com 1GB HEAD, ferramenta de testes JMETER e usando um sgdb remoto de homologação, executando os principais cenários de processos utilizados pelos usuários. Segue um resumo das estatísticas do resultado:

Versão do sistema atual, rodamos os testes usando a versão atual da sistema:

  • 1 usuário demorou 6 segundos para executar todo o script de testes.
  • 500 usuários simultâneos demoraram 6:10 minutos para executar todo o script de testes.

Versão do sistema otimizada, rodamos o mesmos testes e usando uma versão com todas a otimizações:

  • 1 usuário demorou 4 segundos para executar todo o script de testes –33% MAIS RÁPIDO!
  • 500 usuários simultâneos demoraram 2 :38 minutos para executar todo o script de testes – 66% MAIS RÁPIDO!

Depois de dois meses de muito trabalho, conclui-se que o resultado final foi positivo e muito expressivo. Vale destacar aqui que as otimizações só foram possíveis de serem aplicadas uma vez que a solução estava devidamente arquiteturada em camadas, seguindo corretamente a filosofia do DDD, e porque utilizada gerenciamento de transações automáticas via AOP com o framework Spring Transaction. Caso contrário, ficaríamos impedidos de fazer qualquer tipo de alterações, levando a empresa contratante ao caminho tortuoso da reescrita total da solução.

Para os interessados nesse tipo de assunto, veja minha regrinha de bolo de otimização que qualquer um pode reusar como ponto inicial para esse tipo de atividade.

Até a próxima!