Arquivo da categoria ‘escalabilidade’
Oracle Coherence: Além do Put e do Get
Segundo a querida Wikipedia, “Cache é uma coleção de dados duplicando valores existentes em algum lugar ou previamente calculados, …” [Tradução minha]. Aplicações frequentemente utilizam cache para poupar recursos com custo alto de acesso tais como Bases de Dados Relacionais, arquivos, comunicações remotas. A intenção é manter o mais próximo da aplicação, de maneira rápida e barata o acesso a dados necessários para alguma computação, ou seja: Caches são em sua maioria um conjunto de dados em memória.
Alguma vez na vida já utilizamos Cache no desenvolvimento de uma aplicação. E muito frequentemente este Cache está fortemente integrado a um framework ORM como Hibernate ou TopLink. O lado bom é que abstrai-se para o desenvolvedor os meios necessários para obtenção de dados a partir do cache, ou a partir da fonte de dados real (Banco de Dados nesse caso). Mas essa é de longe a estratégia mais simples e, de fato, mais pobre quando se trata de tirar o máximo da sua ferramenta de Caching ou data grid.
Outro uso comum de Cache são os Caches locais, os que residem na memória da aplicação. É aí que o uso dos cache.get(key) e cache.put(key, value) aparecem, mas o simples uso destes dois métodos traz uma série de questões:
- Toda vez que uma entrada no cache é atualizada, como e quando devo aplicar tais mudanças para a fonte de dados original (ex.: Base de Dados)?
- E se eu obtiver uma entrada no cache, iniciar um processamento e, antes de concluir este processamento a entrada for alterada?
- Eu tenho servidores geograficamente distribuídos, como compartilhar um mesmo cache para um recurso extremamente custoso entre estes servidores?
- Utilizo Cache para evitar acesso constante à Base Relacional, mas minha aplicação é “Clusterizada” e uma próxima requisição frequentemente é destinada a um servidor cuja entrada ainda não foi inserida no Cache. Como evitar acessos repetidos para os mesmos dados processados em servidores diferentes?
- Acessar uma entrada pela chave não é suficiente, isso me força a criar chaves modificadas – como “SP-Republica”, uma junção entre dados de dois campos distintos de uma mesma entidade (supondo Endereco(UF, Bairro)) – ou mesmo ignorar o cache e efetuar consultas constantemente na Base de Dados.
- Preciso realizar uma operação em milhares de entradas no cache, mas cada get() executado traz os dados para a memória local o que pode causar OutOfMemory, como contornar isso?
Poderíamos ter um Post inteiro só com questionamentos do gênero. Pelo menos um destas dúvidas você já teve, certo? Resolvi apresentar o Oracle Coherence desta maneira, respondendo estas. O Coherence é uma suite poderosa para Data Grid e Cache provida pela Oracle e permite a replicação (particionada) de dados em cluster.
Não é minha intenção (nem seria possível) cobrir todos os aspectos da ferramenta, mas sim de oferecer um Overview, um ponto de vista e até mesmo dicas para o uso da ferramenta baseado na minha experiência.
Neste Post trago uma resposta ao menos à primeira pergunta:
Toda vez que uma entrada no cache é atualizada, como e quando devo aplicar tais mudanças para a fonte de dados original (ex.: Base de Dados)?
A técnica comumente utilizada para escrita simultânea no cache e na fonte de dados é conhecida por Write-Through (veja artigo na Wikipedia). É possível utilizar um DAO conhecedor do Cache que faz um put e um insert em uma tabela, por exemplo. Dessa maneira o Write-Through é feito com o Cache-aside, ou seja o cache participa passivamente neste processo.
O Oracle Coherence utiliza o conceito de CacheStore e oferece a interface com.tangosol.net.cache.CacheStore para onde a operação de persistência é delegada:
public class YourCacheStore implements com.tangosol.net.cache.CacheStore {
...
@Override
public void store(Object key, Object value) {
... // code to actually persist the value.
}
@Override
public void storeAll(Map cached) {
for (Object key : cached.keySet()) {
... //persist it!
}
}
}
O CacheStore é uma classe qualquer que conhece como obter e salvar definitivamente as informações para uma entrada no Cache. Logo, não é relevante para o Coherence como o dado é acessado, seja via JDBC, JPA, WebService, RMI, etc. O Coherece delagará a execução para a classe configurada em um cache do tipo read-write-backing-map-scheme:
<read-write-backing-map-scheme>
<scheme-name>DB</scheme-name>
<internal-cache-scheme>
<local-scheme/>
</internal-cache-scheme>
<cachestore-scheme>
<class-scheme>
<class-name>com.codemountain.grid.store.YourCacheStore</class-name>
</class-scheme>
</cachestore-scheme>
<write-delay>5s</write-delay>
<write-batch-factor>0.3</write-batch-factor>
</read-write-backing-map-scheme>
Note o parâmetro write-delay:5s, isso significa que o Coherence vai iniciar a escrita após o tempo especificado de 5 segundos. Assim passamos a fazer o uso da técnica de Write-Behind, ou seja os dados se mantém consistentes e disponíveis para a aplicação, enquanto num momento futuro o Coherence iniciará uma escrita Assíncrona para o repositório de dados.
Até aqui temos um mecanismo integrado à ferramenta de Cache, garantindo consistência dos dados em cache e em Base de Dados. O Coherence executa o que ele chama de coalescing , ou seja, sucessivas escritas a uma mesma entrada não gerarão diversos updates/inserts para a entrada.
Write-Behind é uma técnica particularmente interessante e pode reduzir drasticamente o acesso a uma base de dados enviando em lote uma quantidade maior de registros alterados ou inseridos. É possível configurar um fator (write-bacth-factor:0.3), tal que, toda entrada cujo tempo de espera para persistência tenha atingido uma percentagem do tempo total de delay, será enviada em lote para escrita. A formula para o wirte-batch-factor é : (1 – 0.3) * 5 = 3.5s.
Sem esta possibilidade de definir uma janela de tempo, as escritas só aconteceriam num momento diferente do insert da entrada no cache, mantendo a alta carga carga no banco de dados. Um CacheStore implementado corretamente deve tirar proveito disso e utilizar apenas uma conexão/transação para a escrita do lote através do método storeAll(). Ah! Fique atento as necessidades de negócio e até mesmo acesso de outras aplicações a esta base dedados, estes são pontos determinantes para a adoção desta técnica.
Com o Write-Behind você pode cortar pela metade a quantidade de acesso à base de dados. Numa aplicação onde eu e minha equipe aplicamos o Oracle Coherence, eliminamos um INSERT + SELECT + UPDATE por apenas um INSERT. Ou seja, o dado era criado em memória no cluster, obtido em um momento posterior e então atualizado. Com o Coherence, e as operações ocorrendo em memória, todas as manipulações se traduzem para um único INSERT no final do procedimento. Multiplique 3 acesso à base de dados por 2 milhoes de transações e note que uma operação é muito menos custosa para todo o seu ambiente.
A geração de IDs para registros na base de dados pode ser feita em cluster de forma segura (leia-se consistente) evitando até mesmo um MY_SEQUENCE.nextval.
É importante saber que há também aqueles que não acreditam em Caches como a melhor solução. Como disse Greg Linden: “Camadas de Cache adicionam complexidade ao design, latência para entradas não encontradas no cache, e uso infeficiente de recursos ‘clusterizados’” (Tradução livre minha). Greg ainda advoga a favor da remoção das camadas de cache e transferência de tais máquinas para a camada de banco de dados, que por sua vez deve apostar alto no particionamento e em seus próprios mecanismos de cache.
Ao tomar decisões de design para aplicações que demandam alta disponibilidade, baixar latência e alta performance, esteja ciente das vantagens e desvantagens das diversas topologias e abordagens disponíveis, inclusive o caching.