Codemountain, Paulo Suzart's Blog

Archive for the ‘oracle’ Category

Clojure + Coherence = Cloherence

leave a comment »

Que combinação! Datagrid Coherence e Clojure! O último post deu uma prévia da abstração simples em cima do Coherence que venho escrevendo. Na verdade não há nada demais nela, a não ser permitir o acesso aos caches e ao processamento de entradas no datagrid de forma mais natural em Clojure. O Cloherence permite duas semânticas, uma que é uma pequena DSL para operações de put, get, remove put de sequencias e processamento no cluster. Vamos a elas:

    (def a1 {:name "Nacho" :breed "Bulldog"})
    (def a2 {:name "Sushi" :breed "Shire"})
    (with-cache "dogs"
        (put-val 1 a1)
        (println (get-val 1)))
;; put a1 into a named cache "dogs" and print the value get from 1
    (with-cache "dogs"
        (put-seq :name (list a1 a2)))
;; put a1 and a2 using :name as key

A função put-seq é interessante pra casos onde alguma função retorna uma lista, e esta pode ser “cacheada” de uma vez. Acontece é que NamedCaches no Coherence são herdeiros de java.util.Map. E isso me deu a ideia de usar a mesma semântica de maps em clojure para tratar os caches Coherence. E ficamos assim:

    (cache-map cats)
    (assoc cats 1 {:name "chico" :breed :unknown})
    (get cats 1) ;; {:name "chico" :breed :unknown}
    (inplace-update cats 1
        (fn [e]
            {:name (.toUpperCase (:name e)) :breed "none"}))
;;put {:name "CHICO" :breed "none"} in the cache with key 1

inplace-update não existe em clojure. A lib oferece uma função update para se assemelhar à função update-in, com a diferença que não permite chaves encadeadas.

A função inplace-update emite um Entry Processor para o grid, e no nó do DataGrid que reside a entrada, a função f é invocada com a entrada passada como argumento. A vantagem é não trazer para a JVM que emite o processador, a entrada encontrada. Escalando isso, livramos a JVM da aplicação de receber possíveis milhares de entradas do cache para efetuar a alteração nas entradas.

Vamos examinar o par EntryProcessor mais a função inplace-update:


;;PProcess é do pacote .core

(deftype CljProcessor
    [f args] java.io.Serializable PProcessor
        (process [this entry]
	(apply f (.getValue entry) args)))

(defn inplace-update
    [cache e-key f & args]
        (with-cache cache
        (process e-key (cloherence.maps.CljProcessor. f args))))

Aqui conto com a função process, com a própria DSL da lib with-cache e com o protocol PProcessor. f de inplace-update é executada assim que a entrada é encontrada, recebendo o valor como primeiro argumento e &args como restante dos argumentos.

Bom, além de divertido, a lib cloherence se propõe a dar uma melhor aderência dos caches Coherence com Clojure. Como próximos passos (não sei se vou fazer, mas pretendo), pretendo implementar inplace-update para várias chaves, iplementar locks e utilizar primitivas de concorrência clojure para algumas operações no cache.

É isso, boa diversão!

@paulosuzart

P.S. Gostaria de um feedback pra saber que nível seria mais adequado abordar Clojure aqui no blog. As vezes sinto que estou escrevendo coisas sem ter feito uma base como fiz com os posts sobre Scala. Quem quiser pode dar um feed. 😉

Anúncios

Written by paulosuzart

abril 3, 2011 at 8:00 am

Publicado em clojure, coherence

OO Funcional: Clojure, Coherence e JavaScript

with 2 comments

Nossa! Estou escrevendo cada vez menos. Isso me deixa um pouco triste. Mas é consequencia de algumas mudanças, até agora positivas.

Durante o carnaval decidi implementar uma ideia que havia comentado com @danielamadei. É uma idea simples: Criar uma fina camada REST em cima do Coherence, expandindo assim sua aplicabilidade para além do Java, .Net e C. Bom, o objetivo mesmo é estudar e aplicar o que tenho aprendido em algum caso que eventualmente pode se tornar útil. A mistura de Coherence com Clojure e Javascript motivou a elaboração do post e o título dele.

O resultado foi: Clojure, Ring, Rhino e Moustache. As libs contrib usadas foram: Duck-streams, json e string.

Mas, antes tentei fazer uma coisa mais louca com Aleph e Lamina. Aleph é um framework de comunicação  assíncrono escrito sobre lamina e JBoss Netty criado pelo mesmo autor do Lamina. É interessante, mas o Aleph acabou se mostrando com um nível de maturidade insuficiente para o que queria fazer, embora fosse simples.

Na lib que dei o nome de CoheREST, fiz a parte de inserção e busca pela chave. A parte mais interessante foi usar o Rhino pra executar funções Javascript submetidas via http para o servidor Ring. Bom, a abstração para acesso ao Coherence ficou assim:

    (with-cache "My-Cache"
        (put-val 1 {"name" "Paulo" "age" 20}))

É isso mesmo, basta esse código para iniciar, criar e inserir um Map com a chave 1 no cache de nome “My-Cache”. Analogamente, pare obter o valor, basta usar uma função chamada get-val. Mas isso foi só pra ilustrar como ficou a simples abstração.

Ok! O Coherence tem uma feature muito interessante. Ao invés de pesquisar no cache uma certa quantidade de entradas, efetuar algum tipo de operação e depois submeter as mudanças ao cache, ele permite que o processamento seja emitido por todo o cache (EntryProcessor). Isto faz com que o processamento ocorra localmente – no nó do grid em que a entrada reside – sem onerar o nó emissor do processamento. Isto é obtido emitindo para o cache uma implementação de AbstractProcessor.

Para emitir um Entry Processor, é possível fazer como se faz para por ou resgatar uma entrada no cache:

    (with-cache "My-Cache"
        (process 1 (coherest.processor.JSProcessor. some-js)))

Só uma observação, nesta versão implementei a emissão de um EntryProcessor para uma única entrada, aqui com a chave 1. E agora começa a parte interessante. coherest.processor.JSProcessor é executado por um EntryProcessor (apresentado mais abaixo), e é ele quem executa javascript no cluster, desde que o javascript tenha o seguinte formato:

    function(e) {//e é a entrada no cache
         e.age = e.age + 1;
         return e;
     }

Isto é, caso a entrada seja encontrada, ela será passada no formato JSON para a função que pode alterar a entrada e retornar o resultado do processamento. Aqui o exemplo é um  simples incremento da propriedade age da entrada. A função é a string some-js passada na construção do JSProcessor. E é aqui que o OO funcional começa.

JSProcessor é um type que implementa um Protocol (leia-se interface em clojure) que define apenas uma função: process. O Protocol e o type ficam assim:

    (defprotocol PProcessor
        (process [this entry]))

    (deftype JSProcessor
		[^String script] java.io.Serializable PProcessor
		(process [this entry]
                       ;; Implementation went here))

Simples, o Coherence vai invocar apenas o método process(entry) na Implementação de AbstractProcessor. Como o processor é espalhado pelo cluster, ele também precisa implementar Serializable. Mas no início do post falei que um EntryProcessor deve implementar AbstractProcessor, mas esta classe não apareceu em lugar nenhum aqui até agora. Acontece que por questões de design, meu Protocol PProcessor é na verdade invocado por uma implementação anonima de AbstractProcess. Esta implementação anonima é que contem a implementação do método process, que por sua vez se encarrega em a função process do JSProcessor, passando como argumento a entrada encontrada no cache. Veja como ficou:

(defn make-processor
	"Create a valid Coherence EntryProcessor from the PProcessor passed
	 as agurment"
	[processor]
	(proxy [AbstractProcessor] []
	    (process [entry]
		(process processor entry))))

Ufa! make-processor é uma função que recebe a instância de JSProcessor, cria um proxy de AbstractProcessor, onde sua implementação é justamente a invocação da função process nele passando a entrada encontrada. A implementação de AbstractProcessor não passa de uma porta de entrada para a invocação das funções definidas pelo PProcessor. E usar um Protocol para o carro chefe na execução de funções no cluster permite que criemos instâncias deste protocol que façam o que desejarmos. O JSProcessor no caso, usa o Rhino pra executar funões no grid.

O CoheREST foi feito sem nenhuma intenção comercial, tão pouco para uso em ambiente corporativo. Isto por que ainda preciso verificar algumas questões de licença. Aí posso liberar o fonte no meu github. O post já foi bastante longo e não pude entrar em detalhes sobre o CoheREST ou sobre o Ring e o Rhino sendo usado direto em Clojure. Mas fica uma deixa pra próximos posts.

Bom, e aqui tivemos herança, programação com interfaces, etc, tudo em clojure, uma linguagem não orientada a objetos, mas que não nos limita em nada a usar conceitos OO. Obrigado e lembre-se: @paulosuzart.

Written by paulosuzart

março 14, 2011 at 1:07 pm

Publicado em clojure, coherence

Tagged with ,

Oracle Coherence: Além do Put e do Get

with 4 comments

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.

Todas as informações sobre o produto podem ser encontradas na documentação do próprio produto Oracle Coherence de propriedade da Oracle. Este blog não expressa direta ou indiretamente quaisquer opinião da Oracle.

Written by paulosuzart

novembro 3, 2009 at 12:59 am

Publicado em coherence, escalabilidade, Java, oracle

Tagged with , ,