AdvPL

AdvPL: Integrando com o Redis

Fala pessoal,

No artigo de hoje irei mostrar a vocês como realizar uma integração em AdvPL com o Redis.

O que é o Redis?

O Redis é um armazenador de dados no formato chave e valor disponibilizado pela AWS. Seu diferencial está relacionado ao fato de armazenar essas informações na memória RAM da máquina servidora, tornando a busca de seus dados muito mais ágil que um banco de dados convencional. E ao contrário do que muitos pensam, apesar de estarmos falando de dados volátil, é possível criar snapshots com o Redis para salvar o estado das informações salvas na memória até um determinado momento.

Por que eu devo utilizar o Redis?

Imaginem que você é mantenedor de um blog que possui mais de 100.000 acessos simultâneos por dia, e queira disponibilizar essas informações de uma maneira mais ágil para seus usuários. Com o Redis você pode armazenar o conteúdo do blog em um tipo de cache, e a partir desse momento ao invés dos usuários terem que aguardar o retorno de sua API para retornar os dados, apenas a primeira requisição deverá esperar a conclusão do processo. Os demais usuários terão os dados retornados a partir do cacheamento feito com o Redis. Vejam abaixo uma ilustração:

Notem que nesse modelo é seguido o fluxo convencional, onde a requisição feita pelo client vai até a rota da API, e essa por sua vez fica responsável por conversar com o banco de dados para retornar o que lhe foi solicitado. No processo de devolução dos dados temos uma etapa a mais, onde o conteúdo encontrado pela API é armazenado no Redis no momento em que é devolvido a informação para o primeiro requisitante. Mas o que está sendo armazenado no Redis? Nada mais nada menos que JSON, afinal como dissemos anteriormente trata-se de um armazenador de chave e valor.

Vejam como nesse segundo exemplo o mesmo processo fica mais enxuto. Isso porque, não é mais necessário ir até a API buscar as informações, uma vez que todos os dados necessários para o usuário estão cacheados no Redis.

Vantagens e Desvantagens

Uma das vantagens de usar o Redis é poder listar informações para o usuário num tempo muito mais ágil que buscando de um banco de dados através de uma API. Entretanto, como tudo na vida temos que olhar para alguns pontos:
A ideia da utilização do Redis nesse artigo é utilizar o Redis para armazenar informações que não mudem com uma periodicidade alta. No caso do blog não teremos postagens sendo criadas a todo minuto, então podemos armazenar suas informações num espaço de tempo bem maior.
Agora imaginem uma aplicação responsável por devolver dados relacionados a bolsa de valores que estão em constante mudança a cada segundo. A ideia de cacheamento para esse cenário não seria aproveitada como se deve, pois a informação devolvida pelo cache já não seria mais tão atual para o solicitante, podendo gerar grande transtorno (leia-se perda de dinheiro) por parte de seus clientes.
Procurem sempre avaliar se o cenário em que se encontra cabe a aplicação do Redis.

Uso no AdvPL

Abaixo deixo para vocês um exemplo que criei de uma API que realiza a listagem de filmes para um usuário. Uma vez que, para a primeira requisição feita os dados serão devolvidas pelo banco e as subsequentes pelo Redis.

#INCLUDE "TOTVS.CH"
#INCLUDE "RESTFUL.CH"

/*/{Protheus.doc} IBGENA01

Restful desenvolvido para realizar listagem de filmes com Redis

@type class
@author Wallace
@since 14/02/2022

/*/
WSRESTFUL IBGENA01 DESCRIPTION "Listar Filmes utilizando Redis para cacheamento"

	WSMETHOD GET DESCRIPTION "Realiza listagem de Filmes com Redis" WSSYNTAX "/"

END WSRESTFUL

/*/{Protheus.doc} IBGENA01

Método GET responsável por listar os filmes, ora vindo do banco, ora vindo do Redis

@type method
@author Wallace
@since 14/02/2022

/*/
WSMETHOD GET WSSERVICE IBGENA01
    Local lRedisConnected   := .F.
    Local cMoviesJson       := ""
    Local cKey              := ""
    Private oRedisClient    := Nil
    Private cAlias          := getNextAlias()

    lRedisConnected := connectRedis()

    If lRedisConnected
        cKey        := "movies"
        cMoviesJson := getRedis( cKey )

        If Empty( cMoviesJson )
            listMoviesDatabase()
            setMoviesInJson( @cMoviesJson )
            setExRedis( cKey, cMoviesJson, 20 )
        EndIf

        closeConnections()
    EndIf

    ::setResponse( cMoviesJson )

Return .T.

/*
    Conecta ao Redis
*/
Static Function connectRedis()
    Local cHost         := "localhost"
    Local nPort         := 6379
    Local lIsConnected  := .F.

    oRedisClient := tRedisClient():New()
    oRedisClient:connect( cHost, nPort )
  
    If oRedisClient:lConnected
        lIsConnected := .T.
    EndIf
    
Return lIsConnected

/*
    Lista os filmes vindos do banco de dados
*/
Static Function listMoviesDatabase()
    Local cSQL      := ""

    cSQL := "SELECT                             " + CRLF
    cSQL += "   ZZ4_CODIGO                      " + CRLF
    cSQL += "   , ZZ4_NOME                      " + CRLF
    cSQL += "   , ZZ4_GENERO                    " + CRLF
    cSQL += "FROM                               " + CRLF
    cSQL += "   " + RetSQLName("ZZ4") + " ZZ4   " + CRLF
    cSQL += "WHERE                              " + CRLF
    cSQL += "   1 = 1                           " + CRLF
    cSQL += "   AND D_E_L_E_T_ = ' '            " + CRLF

    plsQuery( cSQL, cAlias )

Return

/*
    Armazena os filmes em uma estrutura JSON
*/
Static Function setMoviesInJson( cMoviesJson )
    Local oMoviesJson   := JsonObject():new()
    Local aListMovies   := {}
    Local nIndex        := 1
    Default cMoviesJson := ""

    While !( cAlias )->( EoF() )
        aAdd( aListMovies, JsonObject():new() )

        nIndex := Len( aListMovies )

        aListMovies[nIndex]['code']     := allTrim( ( cAlias )->ZZ4_CODIGO )
        aListMovies[nIndex]['name']     := allTrim( ( cAlias )->ZZ4_NOME )
        aListMovies[nIndex]['gender']   := allTrim( ( cAlias )->ZZ4_GENERO )

        ( cAlias )->( dbSkip() )
    EndDo

    oMoviesJson:set( aListMovies )
    cMoviesJson := oMoviesJson:toJSON()

Return

/*
    Busca o conteúdo de uma chave no Redis
*/
Static Function getRedis( cKey )
    Local cCommand      := ""
    Local cValue        := ""
    Local xValueReturn  := Nil
    Default cKey        := ""
    
    cCommand := "GET " + cKey
    oRedisClient:exec( cCommand, @xValueReturn )

    If oRedisClient:lOk
        cValue := xValueReturn

        If valType( xValueReturn ) != 'C'
            cValue := cValToChar( xValueReturn )
        EndIf
    EndIf

Return cValue

/*
    Armazena o conteúdo em uma chave dentro do Redis
*/
Static Function setExRedis( cKey, cValue, nExpiresInSeconds )
    Local xValueReturn  := Nil
    Default cKey        := ""
    Default cValue      := ""
    Default nExpiresIn  := 0

    cCommand := "SETEX " + cKey + " " + cValToChar( nExpiresInSeconds ) + " '" + cValue + "' "
    oRedisClient:exec( cCommand, @xValueReturn )

    If !oRedisClient:lOk
        conOut("Falha ao salvar o JSON na key do Redis")
    EndIf

Return 

/*
    Encerrando as conexões em aberto
*/
Static Function closeConnections()
    oRedisClient:disconnect()

    If Select( cAlias ) > 0
        ( cAlias )->( dbCloseArea() )
    EndIf
    
Return


No exemplo acima, temos uma API que lista todos os filmes de uma tabela. Notem que na linha 39, após feita a conexão com o Redis utilizamos uma função para verificar se tem algum conteúdo dentro da chave “movies“. Caso o retorno seja vazio significa que estamos fazendo a primeira requisição, e com isso devemos armazenar o JSON retornado dentro do Redis. Para todo conteúdo salvo no Redis passamos o tempo que seu conteúdo ficará disponível antes de expirar (vide função setRedisEx).

Abaixo temos ilustrado a diferença de performance entre uma requisição retornada pelo banco e outra pelo Redis:

Requisição feita para o BD
Requisição feita para o Redis

Percebam a diferença entre cada requisição: na primeira imagem o conteúdo todo foi retornado pelo BD após 891ms, enquanto na requisição feita pelo Redis o mesmo conteúdo foi retornado após 95ms. Essa é apenas uma dentre várias utilidades que o Redis pode nos proporcionar.

Caso queiram se aprofundar no assunto seguem links utilizados como referência na montagem desse post:
https://redis.io/commands
https://tdn.totvs.com/display/tec/Classe+tRedisClient

Para utilização do Redis junto ao Advpl será necessário a instalação da dll rdwincli.dll, disponível para download no link do TDN informado acima, na seção de Observações.

O objetivo desse post foi trazer alternativas para melhorar a performance de listagem de dados utilizando o conceito de cacheamento.

Espero que tenham gostado.

Até a proxima!

Deixe um comentário