Cache – Ultrapassando os Limites com Guava

Como descrito em post anterior, JMeter – Determinando os Limites, podem ser usadas ferramentas de teste de carga para determinar os limites de uma aplicação, mas quando se depara com situações em que este “limite” encontrado não satisfaz as necessidades de um projeto então temos que buscar alternativas. Pode-se aperfeiçoar:

◆ Os SQLs;

◆ A infraestrutura de servidores;

◆ Ou, a aplicação.

Uma vez que os dois primeiros itens citados acima já estão no seu limite e não pode-se extrair nenhum ganho de performance trabalhando neles, resta como opção otimizar a aplicação.

Para efetuar melhorias em aplicações web podemos usar várias técnicas, tanto com foco em back-end como em front-end. Neste artigo o foco será falar um pouco sobre uma experiência positiva em relação ao back-end.

Em um determinado projeto, nos deparamos com um problema em que mesmo com uma infraestrutura muito poderosa de servidores de aplicação, ainda assim tínhamos problemas quando determinado volume de acessos simultâneos era superado. O banco de dados da aplicação atingia seu limite máximo de requisições por segundo e aplicação ficava indisponível como um todo.

A saída foi adotar uma estratégia para reduzir a quantidade de chamadas que chegam ao banco de dados, para isso decidiu-se aplicar cache em diversos pontos da aplicação. A ferramenta escolhida para isso foi o Guava. Como seus pontos fortes podemos citar:
◆ Simplicidade para integração;
◆ Facilidade para programação;
◆ Robustez.

Com o uso do Guava, conseguiu-se multiplicar o número de acessos simultâneos em mais de 10x. Pode-se exemplificar o ganho de uma maneira simples:
◆ Uma página mostra 30 itens;
◆ Para carregar esses itens é chamado o mesmo serviço 30 vezes(“buscaItem(itemId)”);
◆ Este serviço executa uma Query no banco de dados e carrega os dados do Item.

Num cenário de 1000 acessos por segundo, teremos 1.800.000 consulta executadas em 1 minuto.

Exemplo de código para a aplicação de cache:

private static final int CACHE_SIZE = 2000;// Quantidade de Objetos no Cache
private static final int CACHE_DURATION = 2;// Tempo para atualização do cache
private static final TimeUnit CACHE_UNIT = TimeUnit.MINUTES;// Unidade de tempo

private static final Cache ITEM_CACHE = CacheBuilder.newBuilder()
.maximumSize(CACHE_SIZE).expireAfterWrite(CACHE_DURATION, CACHE_UNIT).build();private final Item getItemFromCache(Long itemId) {final ItemLoader loader = new ItemLoader(itemId);
try {
return ITEM_CACHE.get(itemId, loader);
catch (ExecutionException e) {
throw e;
}return null;
}

private static final class ItemLoader implements Callable {

private final Long itemId;

private ItemLoader(Long itemId) {
super();
this.itemId = itemId;
}

@Override
public Item call() throws Exception {
try { //Neste ponto devemos chamar o método que consulta os dados diretamente no Banco de Dados
            BuscaItemService service =new BuscaItemService();
return service.buscaItem(itemId);
catch (KKException e) {
throw e;
}
}
}

No trecho de código acima temos um exemplo completo da estrutura de cache. O método getItemFromCache(Long itemId) deve ser chamado em todos os pontos onde anteriormente era chamado o método buscaItem(itemId); desta maneira antes de buscar um Item diretamente na base de dados, o cache será consultado. Caso esteja em cache, evitamos o acesso desnecessário ao banco de dados. A classe ItemLoader é responsável por efetuar a busca de objetos Item que não estejam em cache; ela deve implementar a Interface Callable e a chamada ao método responsável por carregar os dados deve ficar dentro do método Call().

Com base no exemplo acima, teremos o seguinte resultado para o mesmo cenário: para 1000 acessos por segundo, em 1 minuto, reduziremos de 1.8 milhão de requisições para apenas 30 requisições, sendo que em 1 hora teriamos 60 x 1.8 milhões de requisições contra apenas 30 x 30 consultas após a aplicação da estrutura de cache.

Podemos notar assim que uma otimização simples no nosso serviço de carregamento de itens pode gerar uma redução expressiva no tempo de carregamento com a redução da latência; além de uma economia de recursos financeiros, caso o servidor de banco de dados seja faturado com base na quantidade de dados trafegados, bem como uma diminuição no tempo de carregamento das páginas, o que segundo diversos artigos gera aumento de receitas para o site. É importante avaliar a fundo a estrutura da aplicação bem como seu uso antes de aplicar Cache. Em aplicações em que as informações variam pouco, ou onde o  atraso na propagação das mudanças é aceitável, é mais seguro aplicar Cache. Em aplicações muito dinâmicas é necessário tomar mais cuidados no momento de definir onde aplicar, qual tamanho do cache e também o tempo para atualização dos dados contidos no cache para evitar danos colaterais.

Por LUIZ COUTO

Postado em: 22 de maio de 2013

Confira outros artigos do nosso blog

[Webinar] Profile de aplicações Java com Oracle Mission Control e Flight Recorder

24 de julho de 2017

Danival Calegari

Criando Mocks de serviços REST com SoapUI

27 de junho de 2017

Monise Costa

Three laws that enable agile software development

09 de março de 2017

Celso Gonçalves Junior

Medindo performance de uma API REST

21 de fevereiro de 2017

Monise Costa

Deixe seu comentário