MemCached Client em AdvPL – Parte 02

Introdução

No post anterior (MemCached Client em AdvPL – Parte 01) vimos a implementação de uma classe nativa em AdvPL para fazer o papel de API Client do MemCached. Agora, vamos ver um pouco de como usar esta classe — agora na versão 1.01, suportando todos os tipos simples do AdvPL para armazenamento e recuperação do cache, inclusive Array.

Conceito e Boas Práticas

No post anterior, vimos um fonte de testes da classe zMemCached, apenas para teste de funcionalidade. Agora, vamos ver onde poderíamos usar esta classe em algumas situações.

Antes de mais nada, vamos conceituar o objetivo de um cache: Um cache normalmente é criado para tirar o peso de um processamento usado para obter um resultado, quando o mesmo resultado será muitas vezes , por um ou mais partes de um programa, por um ou múltiplos usuários, desde que a informação seja a mesma para todos.

O MemCached foi criado para ser um cache versátil — armazena qualquer coisa — e rápido. Normalmente é usado em aplicações WEB para reduzir a quantidade de requisições ao banco de dados, quando um mesmo resultado de uma Query ou de uma página renderizada será muito requisitada e possui um baixo índice de alteração — o que evita invalida e realimentar o cache constantemente.

Por ser um cache que pode atender múltiplas conexões de múltiplos usuários de um sistema, qualquer parte comum a todos eles, e constantemente requisitada, poderia ser colocado em cache. Porém, precisamos tomar cuidado com esta afirmação:  PODER, tudo pode, mas nem tudo DEVE ser feito. Senão, dobra a memória da máquina e coloca tudo em cache. Pronto, seu cache vai comer memória com farina, você vai ter um volume estrelar de dados da memória, aqueles dados que não são usados frequentemente somente ocupam espaço e oneram as demais operações que realmente ganhariam com o uso de um cache.

Logo, a premissa numero um é: Use um cache onde é necessário e adequado. Comece com aquilo que realmente “mata” o seu banco de dados, faça um Log Profiler primeiro, não saia usando o cache por que é “legal” 😉

Implementação

O segundo mandamento é: Salvo raras exceções, como o uso de uma chave com um valor para incrementou ou decremento, use o cache para colocar um agrupamento de dados com contexto. Por exemplo, se você têm uma Query que retorna os 10 produtos mais vendidos no mês, não armazene cada linha da query em uma chave, coloque os dados em um array e guarde o array. Quase todas as consultas feitas a esta chave vão querer, via de regra, os 10 produtos.

Quer fazer uma atualização automática dos resultados a cada 60 minutos? Uma forma elegante e sob demanda é armazenar o resultado em cache com expire time de 3600 segundos (ou 1 hora). Com isso, passou uma hora, o valor é apagado do cache, o próximo processo que pedir este valor vai ver que ele não está cacheado, roda a Query, e alimenta o cache novamente com o valor mais novo.

Vale lembrar que a responsabilidade de atualizar o cache é da aplicação que o consome, Ela sempre deve buscar primeiro a informação em Cache, e caso não a encontre, ela deve gerar a informação e alimentar o cache. Por isso, mesmo que o custo de processamento de consultar e armazenar no cache seja relativamente baixo, fazer isso para uma informação com baixa incidência de busca (low hit count) é desperdiçar recurso.

Colocando uma Query em Cache

Vamos partir de uma Query qualquer, onde desejamos guardar seu resultado em Cache. Por hora, partindo de um programa já existente, primeiro ele deve ser alterado para trabalhar com um Result Set em Array. Desse modo, podemos armazenar e recuperar o Array com o resultado da Query no Cache.

// Pega a Query do banco e coloca no array 
cQuery := "SELECT CAMPO1, CAMPO2, CAMPO3 FROM TABELA WHERE CAMPO1 = '001' ORDER BY 1,2"
USE (TcGenQry(,,cQuery)) ALIAS QRY EXCLUSIVE NEW VIA "TOPCONN"
While !eof()
	AADD(aQryData , { QRY->CAMPO1 , QRY->CAMPO2 , QRY->CAMPO3 } ) 
	QRY->(DbSkip())
Enddo
USE

Agora, vamos ao uso do cache. Primeiro, criamos o objeto client e a conexão.

// Cria instancia do Cache 
oMemCache := ZMEMCACHED():New("localhost",11211)

// Conecta no Cache 
If !oMemCache:Connect()
	conout("oMemCache:Connect() FAILED")	
	conout(oClient:GetErrorStr())
	return .F.
Endif

Antes de rodar a Query, verificamos se a informação está no cache. Vamos dar um nome para a chave, um identificador desta Query para o cache.

oMemCache:Get("TEST_QRYINCACHE",@aQryData)

Se o conteudo de aQryData estiver NIL após a chamada, este array não está no cache. Neste caso, rodamos a Query, e colocamos o array em cache, usando:

oMemCache:Set("TEST_QRYINCACHE",aQryData)

Logo, o fluxo da rotina passa a ser:

  • Cria conexão com o cache
  • Verifica se o array está lá, tentando recuperar o array
  • Se o array não foi recuperado, roda a Query no banco, cria o array e coloca ele em cache
  • Segue o fluxo normal da rotina usando o array

Usando o objeto client do cache

Normalmente criamos o objeto, conectamos, pedimos algo do cache, desconectamos e matamos o objeto — FreeObj() — após desconectar. Como a conexão com o cache é algo feito muito rapidamente, não faz muito sentido mantermos uma conexão permanente em uma variável STATIC

A exceção são os casos onde uma ou mais sub-rotinas durante o processamento também vão fazer requisições ao cache. Neste caso, podemos encapsular o objeto de cache usando uma classe de controle, armazenando o objeto client do cache em uma variável STATIC, e controlando o acesso a este objeto usando métodos GetCache() e ReleaseCache().

Neste caso, as rotinas e sub-rotinas que usam o cache poderiam buscar o objeto usando o GetCache(), que incrementaria um contador interno de uso, e ao terminar o processamento, chamar a ReleaseCache(), que decrementa a referência, anula a variável em uso, e caso a referência esta chegue em 0 (zero), desconecta e limpa o objeto armazenado, enquanto quem consome o cache apenas atribui NIL para a variável usada para armazenar o objeto de cache daquela rotina. Inclusive, podemos nos aproveitar de algumas características interessantes das classes em AdvPL. Vejamos:

#include "protheus.ch"

/* ==============================================================================
Classe      ZMEMCACHEDPOOL
Autor       Julio Wittwer
Data        01/2019
Descrição   Encapsula objeto client do MemCache, usando contador de referencias. 
Os programas que consomem o cache devem obter a instância usando :
   ZMEMCACHEDPOOL():GetCache( @oMemCache , @cError )
E após o uso, soltar a instancia usando : 
   ZMEMCACHEDPOOL():ReleaseCache( @oMemCache )

==============================================================================*/

STATIC _oMemCache        // Objeto do Cache em  "Cache"
STATIC _nRefCount := 0   // Contador de referencias 

CLASS ZMEMCACHEDPOOL FROM LONGNAMECLASS

   METHOD GetCache()
   METHOD ReleaseCache()
   METHOD RefCount() 
   
ENDCLASS

// ----------------------------------------------------
// Obtem por referencia uma instancia do cache, e em caso de
// falha, obtem o erro também por referência 

METHOD GetCache( oCache , cError ) CLASS ZMEMCACHEDPOOL

// Inicializa parâmetros passados por referência
oCache := NIL
cError := ""

IF _oMemCache != NIL
	// Já tenho um objeto de conexao 
	// Verifico se a conexão está OK
	IF _oMemCache:IsConnected()
		// Conexão OK, incremento contador de referencias
		// e retorno 
		_nRefCount++
		oCache := _oMemCache
	Else
		// A conexão não está OK
		// Limpa o objeto e passa para a próxima parte 
		FreeObj(_oMemCache)
		_oMemCache := NIL
	Endif
Endif

IF _oMemCache == NIL
	// Nao tenho o objeto de conexão
	// Crio o objeto e tento conectar 
	_oMemCache := ZMEMCACHED():New("localhost",11211)
	IF _oMemCache:Connect()
		// Conexão OK,incrementa contador e atribui o objeto 
		_nRefCount++
		oCache := _oMemCache
	Else
		// Nao conectou, recupera o erro, alimenta cError 
		// e mata este objeto
		cError := _oMemCache:GetErrorStr()
		FreeObj(_oMemCache)
		_oMemCache := NIL
	Endif
Endif
Return 

// ----------------------------------------------------
// Solta a referência do cache em uso, anula a variável 
// recebida por referencia, e caso o contador 
// seja menor que um, limpa o objeto da memória 
METHOD ReleaseCache( oCache ) CLASS ZMEMCACHEDPOOL

IF oCache != NIL 
	oCache := NIL
	_nRefCount--
	IF _nRefCount < 1 
		_oMemCache:Disconnect()
		FreeObj(_oMemCache)
		_oMemCache := NIL
	Endif
Endif

Return 

// ----------------------------------------------------
// Retorna o contador de referencias de uso 
// do objeto do Cache 

METHOD RefCount() CLASS ZMEMCACHEDPOOL
Return _nRefCount

Dessa forma, você centraliza a conexão com o MemCached, e no seu processo obtém uma instância única em uso. Basta tomar cuidado para não fazer mais Release()  do que Get(), senão você acaba matando a instância e todas as referências que ainda podem fazer uso dele.  Da mesma forma, não esqueça de fazer um Release() após usar, senão você larga o objeto na memória e deixa a conexão dele lá até que o processo termine. Vamos ver como ficariam os fontes que consomem o cache:

// --- Fonte Original ----

// **** Cria o objeto e conecta com o Cache ****
oMemCache := ZMEMCACHED():New("localhost",11211)
If !oMemCache:Connect()
	conout("oMemCache:Connect() FAILED")	
	conout(oClient:GetErrorStr())
	return .F.
Endif

// **** USA O CACHE ****

// **** Desconecta e limpa o objeto ****
oMemCache:Disconnect()
FreeObj(oMemCache)
oMemCache := NIL

// FONTE NOVO -- Usando o ZMEMCACHEDPOOL 

// Obtém o objeto do Cache 
oMemCache := NIL
cError := ''
ZMEMCACHEDPOOL():GetCache( @oMemCache , @cError )
IF oMemCache == NIL 
	conout("ZMEMCACHEDPOOL:GetCache() FAILED")	
	conout(cError)
	return .F.
Endif

// (...) 
// **** USA O CACHE ****
// (...) 
// **** Faz Release do objeto ****
ZMEMCACHEDPOOL():ReleaseCache( @oMemCache )

Conclusão

Valendo-se destas premissas, e usando elegantemente os recursos de cache, vários processos podem obter benefícios diretos e indiretos desta implementação.

Todos os fontes atualizados da ZLIB estão no GitHub https://github.com/siga0984/zLIB ! É baixar, compilar  e usar. Os exemplos de uso e teste do cache estão no https://github.com/siga0984/Blog

Novamente agradeço a audiência e desejo a todos TERABYTES DE SUCESSO !!! 

 

 

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s