Logo

dev-resources.site

for different kinds of informations.

Redis Cache - A String story

Published at
12/28/2024
Categories
redis
string
kotlin
cache
Author
jofisaes
Categories
4 categories in total
redis
open
string
open
kotlin
open
cache
open
Author
8 person written this
jofisaes
open
Redis Cache - A String story

In one of my past projects, we were working on a project that made extensive use of Redis. This is a distributed cache system used as an alternative to Hazelcast. There are always several disputes between the usage of both among developers, but I'm not going to go there in this post. Where I am going to go is the solution to a problem I found today in less than 30 minutes, which at the time cost so much time, and we ended up not really developing the best solution.

In my current project buy-odd-yucca-concert, I'm now using Micronaut in combination with Redis in order do develop a simple pub-sub system to provide listeners for the fulfilling of several segments of a concert reservation. One of those segments is a TicketDto:

data class TicketDto(
    val name: String,
    val address: String,
    val birthDate: LocalDate,
    val concertDays: List<ConcertDayDto> = emptyList(),
    val meals: List<MealDto> = emptyList(),
    val drinks: List<DrinkDto> = emptyList(),
    val parkingReservation: ParkingReservationDto? = null,
    val createdAt: LocalDateTime? = LocalDateTime.now(),
) : Serializable
Enter fullscreen mode Exit fullscreen mode

The details of the goal of this ticket are pretty much irrelevant at this point. The only thing we are interested in known is how to put in in a Redis cache, or in my case, publish this to a Pub-Sub system.

In the past the discussion started out from a very strange assumption that Redis "does not support anything other than Strings" in the cache. One quick look at their cache info page suggest the complete opposite: https://redis.com/solutions/use-cases/caching/. And quoting what they say there:

Redis is designed around the concept of data-structures and can store your dataset across Strings, Hashes, Sorted Sets, Sets, Lists, Streams, and other data structures or Redis modules".

Unfortunately not only was this ignored, but the notion that Redis only supported Strings gained steam when going head first into the code, we saw that there was only a StringCodec, but not really an ObjectCodec or anything related. Someone then came up with the idea of using lombok's @ToString and then cache this string value and use it. I personally was never too happy with this, but hey, it worked and the code is now running! Good right? 😁... Not today in 2022 though.

Flash forward and in current days reaching this part of my project brought me back the memories of that discussion. To avoid this kind of discussions, that only waste time and money, in other developments I'm sharing with you the possibility of actually serialising whatever you want into the Redis cache, pub-sub systems or anything else in the Redis ecosystem. In my project I'm using lettuce, which is one of the most common Redis clients out there. Here is the dependency:

<dependency>
    <groupId>io.micronaut.redis</groupId>
    <artifactId>micronaut-redis-lettuce</artifactId>
    <version>5.2.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

It is a micronaut dependency, but it contains all needed lettuce dependencies.

Digging into this library, I found out that next to StringCodec there is this ByteArrayCodec, which should allow for some sort of leverage to achieve our goals. After a huge amount of time spent in discovering and re-descovering how buffering, serialising, and streaming works I finally came up with the following in Kotlin:

class TicketCodec : RedisCodec<String, TicketDto> {

    override fun decodeKey(byteBuffer: ByteBuffer): String {
        return defaultCharset().decode(byteBuffer).toString()
    }

    override fun decodeValue(byteBuffer: ByteBuffer): TicketDto =
        ObjectInputStream(
            ByteArrayInputStream(byteArrayCodec.decodeValue(byteBuffer))
        ).use { it.readObject() as TicketDto }

    override fun encodeKey(key: String): ByteBuffer {
        return defaultCharset().encode(key)
    }

    override fun encodeValue(ticketDto: TicketDto): ByteBuffer =
        ByteArrayOutputStream().use { baos ->
            ObjectOutputStream(baos).use { oos ->
                oos.writeObject(ticketDto)
                byteArrayCodec.encodeValue(baos.toByteArray())
            }
        }

    companion object {
        val byteArrayCodec = ByteArrayCodec()
    }
}
Enter fullscreen mode Exit fullscreen mode

The way codecs work in redis is actually very, very simple. Everything, literally everythin is stored in a ByteBuffer! It's not even a String. You encode your values as ByteBuffers. These buffers keep your values stored in cache. Not Strings. You may be able in the code to make only String operations, but that's not what happens underwater. So essentially, and for my example this is what happens when you publish and then listen to your value:

Publish

  • the key gets serialised and the encodeKey method is called.
  • the value gets serialised and the encodeValue method is called.

Listener (Subscriber)

  • the key gets deserialised and the decodeKey method is called
  • the value gets deserialised and the decodeValue method is called

In my first attempts I kept on trying to do even more manual steps. And this is because I kept looking at the StringCodec example. Take a look at this class in your IDE to be amazed on how complex the actual String processing really is. Because I wasn't getting anywhere with pure low level methods, I decided to try the ByteArrayCodec directly. And voila. Now it works and I can serialise my objet correctly.

Finally, was able to apply this codec which make code declaration slightly different than usual. This is the bean factory modified:

@Factory
class RedisBeanFactory {
    @Singleton
    fun pubSubCommands(redisClient: RedisClient): RedisPubSubAsyncCommands<String, TicketDto> =
        redisClient.connectPubSub(TicketCodec()).async()
}
Enter fullscreen mode Exit fullscreen mode

And this is the way I'm publishing the ticket now. Again, still purely experimental code:

redisClient.connectPubSub(TicketCodec()).async().publish("ticketsChannel", ticketDto)
Enter fullscreen mode Exit fullscreen mode

It is entirely up to you, whatever you want to cache and whatever data structures you should use. Even plain old strings. And I'm also not saying with this article that using a string instead of an object representation is per se a bad thing. But in order to make good decisions, we need to know what is possible and what it's not possible and a common mistake is to assume what is not possible. A good philosophy maybe is to assume that anything is possible until proven otherwise. This always goes back to the same principle: "Knowledge is power" and "The pen is mightier than the sword". In this case, It would have made me really happy to know that the reason we used @ToString from, among so many other ideas, lombok, had been something in the lines of "it improves performance". I hope with this bite, to have broadened your horizons in your work with Redis, if you didn't know this already.


Originally posted on Buy me a coffee - Redis Cache - A String story


References

cache Article's
30 articles in total
Favicon
Caching in Node.js: Using Redis for Performance Boost
Favicon
Building the Perfect Caching System: A Comprehensive Guide
Favicon
Cache your function computation in React Server Components
Favicon
From Heartbeats to Cache Misses: Making Big Numbers Hit Home
Favicon
Redis Cache - A String story
Favicon
Boosting Backend Performance with Distributed Cache: A Comprehensive Guide
Favicon
🌟 Mastering Caching in JavaScript for Optimizing Performance πŸš€
Favicon
Cache NLogN🏎️
Favicon
System Design 02 - Caching: The Art of Keeping Users Happy Without Breaking a Sweat
Favicon
Stale cache, the holy grail of performance
Favicon
Top 5 Caching Patterns for High-Performance Applications
Favicon
How to Effectively Handle Caching in Your Application: Lazy Loading vs Write-Through
Favicon
Using Caching in React with useGetProducts: Improve Performance and UX
Favicon
The Role of Cache Memory in Enhancing Processing Speed
Favicon
Mastering Android App Visuals: A Comprehensive Guide to Effortless Image Uploading, Storage, and Sharing.
Favicon
That way to build High-Performance APIs in .NET - Part 2: Caching
Favicon
Understanding CDN Cache in NextJs
Favicon
Supercharge your applications queries with caching
Favicon
Can Postgres replace Redis as a cache?
Favicon
Difference between cache vs cookie
Favicon
Monitor Squid Proxy with Goaccess
Favicon
Speeding Up Your Website Using Cloudflare Cache
Favicon
Finally found a solution to clear the CDN cache using GitHub Actions!
Favicon
Stale-while-revalidate and it's usage with Next.js
Favicon
Why do we need NoSql Database
Favicon
Go Redis Crud quickly example
Favicon
How to build a caching layer for your Laravel API
Favicon
Davide's Code and Architecture Notes - Cache Expiration vs Cache Eviction (and Eviction Policies)
Favicon
Entendendo porque o Cache Lock Γ© sinΓ΄nimo de integridade
Favicon
Mastering Frontend Performance: Harnessing the Power of Caching

Featured ones: