Como criar um cache de dados com Redis em Node.js

Quando estudamos arquitetura de computadores, seja na faculdade ou no técnico, aprendemos que existem recursos computacionais mais lentos e mais rápidos. Por exemplo, ler a memória RAM é extremamente rápido, enquanto escrever em um disco é extremamente lento.

No entanto, quanto mais rápido o recurso, mais caro ele é e portanto mesmo recursos lentos como discos rígidos ainda são de grande valia dado o seu baixo custo por GB de armazenamento. Além disso, existem técnicas para diminuir a “lentidão” do acesso à disco.

A técnica mais comum para isso, a nível de aplicação, é caching.

Atenção: este é um tutorial intermediário de Node.js, ele pressupõe que você já saiba criar um CRUD com ele antes de avançar. Se você não está nesse nível ainda, sugiro buscar algum tutorial mais iniciante ou meus livros.

Se preferir assistir a um vídeo, segue uma mini-aula minha, que apresentei em um de meus cursos.

O que é caching?

Caching consiste em armazenar em memória (rápida) conteúdos acessados com frequência que estejam originalmente no disco (lento). Assim, ao invés de ficar indo o tempo todo no disco para pegar a mesma informação, primeiro verificamos se ela não está em cache e, se estiver, pega de lá mesmo. Se não estiver, vamos no disco e, antes de devolver o dado solicitado, deixamos uma cópia na cache, para que na chamada posterior, ela seja pega de lá.

É exatamente isso que faz o processador com a sua memória cache interna. Ele prioriza ela ao invés da RAM, por sua maior velocidade.

De maneira bem simplista a técnica é essa e, via de regra, você pode implementar isso usando variáveis e objetos em memória mesmo, armazenando seus dados recorrentes no próprio processo da aplicação.

No entanto, fazer a gestão profissional de recursos em cache pode implicar em ter de lidar com conceitos como busca, invalidação, desduplicação, entre outros. Nestes casos, o ideal é partir para soluções criadas para este fim, como o Redis.

O que é Redis?

Redis é um “banco de dados” (não confundir com SGBD) in-memory e open-source, usado geralmente como cache ou como message broker (tipo RabbitMQ e AWS SQS). Ele fornece estruturas de dados como strings, hashes, listas, conjuntos, conjuntos ordenados, bitmaps, índices geoespaciais, streams, entre outros.

Apesar de ser muito associado como “banco em memória”, Redis tem diferentes níveis de persistência, transações, replicação e permite fazer clusterização. E apesar também de ser muito associado a estruturas chave-valor, ele permite uma grande gama de operações diferentes do que apenas consultar e armazenar baseado em chaves.

Considerando estas características citadas, Redis é extremamente popular para criação de cache de dados, ficando à frente de algum SGBD como MongoDB, MySQL, PostgreSQL ou MS SQL Server, entre outros. Eu também já usei ele para criar Índices Invertidos, uma estrutura de dados popular em mecanismos de busca.

Assim, sempre que você precisar de um dado que seja acessado com frequência (por exemplo o menu dinâmico de um site que precisa ser carregado em todas páginas ou as permissões de um usuário autenticado), você primeiro pede ao Redis, se ele não tiver em cache, você vai no banco e deixa uma cópia nele, para que na próxima requisição, já esteja por lá.

Mas indo ao que interessa, baixe a versão stable no site oficial e rode o comando ‘make’ para compilar ele na sua máquina e depois um ‘make test’ para ver se está tudo ok. Isso te deixará apto a rodar um servidor de Redis localmente usando o seguinte comando a partir da pasta do Redis.

Isso vai subir uma instância do Redis com as configurações padrões, que são o suficiente para este tutorial.

Curso FullStack

Como usar com Node.js?

Para uso com Node.js você irá precisar de uma aplicação Node.js básica que você pode criar rapidamente aí na sua máquina e de uma biblioteca de uso do Redis para Node, como a Node Redis. No entanto, a Node Redis não possui suporte a Promises ainda, então vou recomendar que você instale outra lib, a Promise Redis:

Esse pacote é um wrapper do pacote Node Redis que apenas lhe dá os poderes das promises. A Node Redis por sua vez, faz uma transcrição literal em JavaScript de TODOS os comandos possíveis de serem executados em um servidor Redis, mas aqui vamos fazer um uso bem simples dele.

Assim, no fim das contas, estaremos usando Node Redis, mas com suporte a Promises, o que é algo bem importante para estarmos escrevendo JavaScript mais moderno. Na verdade, meu interesse maior é de usar Async/Await, que depende da existência de Promises sendo retornadas pelas funções.

Abaixo, um exemplo simples que se conecta em um Redis padrão (localhost sem usuário e senha), salva um valor “value” com a chave “key” e depois busca este valor pela sua chave.

Coloquei dentro de um IIFE async para que o await funcione, mas talvez isso não seja necessário no seu caso, dependendo de onde for usar.

Aquele objeto client expõe todos os comandos existentes no Redis, amplamente documentados e que recomendo que você dê uma olhada caso queira ir além do SET e GET que usei ali, que são o básico do básico do Redis. Por exemplo, falando de cache, você não vai querer que ele fique armazenado para sempre, certo?

Se você quiser que ele expire sozinho, você pode passar mais alguns parâmetros na função set ou então chamar a função expire.

Mas será que vale a pena mesmo usar Redis como cache?

Curso Node.js e MongoDB

Redis como cache de MySQL

Vamos fazer um teste aqui de uso do Redis como cache na frente de um banco MySQL. Este mesmo teste é válido se quiser usar o Redis na frente de outro banco SQL como PostgreSQL e MS SQL Server, apenas adapte o código de algum dos meus tutoriais para o seu banco favorito.

Instalarei a dependência do mysql2 na aplicação e vou usar ele para criar uma tabela em um banco já existente (se não tem MySQL na sua máquina, sugiro criar na Umbler) e populá-la com 1000 registros falsos, só para teste.

Note que você vai ter de ajustar a connection string à sua realidade, e caso nunca tenha usado MySQL com Node.js antes, recomendo fazer este tutorial.

Agora que temos o banco, com a tabela clientes e com 1000 registros de exemplo, vamos fazer a nossa lógica que vai usar o Redis como cache do MySQL. Tenhamos como base a consulta abaixo, que vai no MySQL para consultar um cliente por id e imprimir seu nome no console.

Aqui na minha máquina, esse processo acima leva em torno de 5.845ms a cada chamada e quando eu fiz 1.000 consultas iguais a essa, sempre para o mesmo id, o resultado foi um tempo de 360.033ms (fiz um for simples). Esses números foram obtidos usando aquelas funções console.time ali.

Agora, vamos fazer diferente, adicionando o Redis antes dessa chamada e considerando que o id do cliente vai vir de um parâmetro de função ou variável externa:

Assim, dado um cliente com id x, quando ele for solicitado da primeira vez, ele não vai existir no Redis (usei o id como chave do registro) e terá de ser solicitado ao banco de dados. No retorno, aproveitamos para guardar uma cópia do registro em formato de JSON serializado (string). Em requisições subsequentes do cliente com mesmo id, ele já estará em cache e portanto não precisaremos ir até o banco.

Assim, a primeira execução levou 8.392ms, mais lento que indo no MySQL direto, mas a partir da segunda já temos um tempo de 3.706ms, 30% mais rápido por causa do cache. E se fizermos 1.000 chamadas em sequência para o mesmo registro? 163.588ms, ou seja, mais de 50% de aumento de performance!

Resumindo (sem as casas decimais):

  • Primeiro acesso: 5ms contra 8ms, MySQL venceu
  • Segundo acesso: 5ms contra 3ms, Redis venceu (+30%)
  • Mil acessos: 360ms contra 163ms, Redis venceu (+50%)

Ou seja, enquanto que existe um ganho bacana mesmo com poucos acessos, a complexidade adicional no código e na infraestrutura se paga mesmo quando fazemos cache de dados que são acessados com muita frequência e/ou que cada acesso é muito lento, como valores computados ou que são a junção de diversas fontes.

Além disso, ali eu armazenei o objeto inteiro como JSON, o que acaba perdendo algum tempo entre serialização e as várias deserializações. Caso você precise de apenas uma informação, é muito melhor e mais rápido guardar apenas ela.

Espero que tenha gostado do tutorial.

Um abraço e até a próxima!

Para mais lições como essa, conheça meus livros e meus cursos, tenho certeza que vai gostar!