C# — Utilizando MessagePack com Redis
Antes de iniciarmos quero especificar aqui o que estou utilizando para o desenvolvimento desse artigo.
Como cache distribuído, estou utilizando redis, sendo executado em container com a imagem oficial, conforme comando abaixo:
Como Redis client, gosto de utilizar o “Another Redis Desktop Manager”, https://github.com/qishibo/AnotherRedisDesktopManager/
A implementação foi realizada com dotnet core 6, utilizando os pacotes listados aqui:
- MessagePack — 2.4.35
- StackExchange.Redis — 2.6.48
Para teste de carga utilizei a ferramenta K6 https://k6.io/
Agora vamos ao que importa!
Grande parte das implementações de cache distribuído que já tive contato faz uso da estratégia de serializar em JSON o objeto que se deseja inserir no cache, e a string resultante da serialização é enviada para o cache distribuído, conforme exemplo abaixo, mais especificamente nas linhas 25 e 30.
Essa estratégia funciona bem, mas o que podemos fazer quando precisamos reduzir o consumo de espaço no cache? Ou quando a quantidade de dados trafegados entre a aplicação e o cache é um problema? Por exemplo, o objeto User descrito no código acima ocupa 182 bytes no cache:
E o espaço total utilizado pelo item (estimado), incluindo dados para seu gerenciamento, é de 248 bytes, conforme exibido pelo comando “MEMORY USAGE”, .
Quais seriam as possibilidades para tentar reduzir o espaço utilizado no cache e não ter grandes alterações na aplicação?
Para essa redução de espaço podemos pensar em algumas possibilidades:
- reduzir as informações salvas, por exemplo, salvando objetos menores, sendo necessário realizar uma análise de quais informações são realmente necessárias de estarem no cache;
- utilização de outros tipos de dados do redis, como Hash ou List, verificando se esses “data types” são mais adequados para salvar o objeto em questão e se isso tem vantagens no seu contexto;
- alterar a forma de serialização do objeto para algum mecanismo que gere os dados de forma mais compacta;
Neste artigo vamos abordar a estratégia de utilizarmos outro mecanismo de serialização, no caso o MessagePack.
MessagePack
É um formato de serialização binária, mais compacto que JSON. Por exemplo, o json abaixo tem 233 bytes, porém no formato MessagePack possui 158 bytes, 32% menor.
Contudo, nem tudo são flores, pois o formato MessagePack possui algumas limitações, como por exemplo o tamanho de inteiros, que deve ser entre -(2⁶³) até (2⁶⁴)-1. Portanto é importante verificarmos outras informações relevantes sobre o formato nos endereços a seguir:
Comparando tamanho do cache
Agora vamos utilizar o formato MessagePack para armazenarmos os dados no cache distribuído e verificarmos o ganho que temos. Como informado no início do artigo, vamos utilizar o pacote nuget MessagePack, https://github.com/neuecc/MessagePack-CSharp
Para isso, primeiramente precisamos alterar a classe User inserindo alguns atributos utilizados no processo de serialização.
Necessitamos inserir o atributo “MessagePackObject” na definição da classe, e o atributo “Key” nas propriedades que devem ser serializadas. A utilização desses atributos pode ser minimizada, para isso verifique a documentação em https://github.com/neuecc/MessagePack-CSharp#object-serialization
Após as alterações, a classe User ficará como abaixo.
A implementação da API fica bem semelhante à implementação utilizando o JsonSerializer, apenas alterando o processo de serialização e desserialização que podemos observar nas linhas 25 e 30 do código abaixo.
Com essa alteração, o objeto User salvo no Redis passa de 182 bytes para 91 bytes, conforme imagem abaixo:
E o espaço total utilizado pelo item (estimado), sai de 248 bytes para 152 bytes.
Economizamos aproximadamente 60% de espaço no Redis.
Performance
Utilizei o K6 para realizar teste de carga e verificar se existe alguma diferença de desempenho entre as implementações.
Observei uma pequena vantagem para a implementação com o MessagePack, mas em alguns cenários a implementação com JSON foi mais rápida. Contudo esses testes não foram conclusivos, pois, como estamos reduzindo a quantidade de dados trafegados entre a aplicação e o cache e como o teste foi realizado num ambiente com todos os componentes rodando localmente, isso interfere nos resultados obtidos. Sendo assim é necessários a realização de mais testes num ambiente mais controlado.
Para os testes eu executei os scripts abaixo no K6:
Dois exemplos dos resultados obtidos:
Podemos observar que com 10 VUs (virtual users), foram realizadas 99.111 requisições em 30 segundos com a serialização com MessagePack, contra 87.348 utilizando a serialização com JSON. Mas repito, é necessário mais validações com um ambiente mais controlado, não em uma máquina com vários processos em execução que também podem impactar no resultado, como a minha.
Esses códigos estarão disponíveis no Github abaixo:
https://github.com/ElvisFDias/RedisMsgPackApi
Concluindo, MessagePack não é um tópico novo, mas acredito que vale a pena dar uma estudada e verificar em quais casos pode ser útil.
Espero ter contribuído um pouco, até mais!