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

redis Article's
30 articles in total
Favicon
Protect Your APIs from Abuse with FastAPI and Redis
Favicon
Token Bucket Rate Limiter (Redis & Java)
Favicon
A Simple Guide to Connect to Amazon ElastiCache Redis from Outside of Amazon
Favicon
Caching in Node.js: Using Redis for Performance Boost
Favicon
How to Install and Run Redis Directly on macOS (Without Homebrew)
Favicon
Install Redis Locally in Windows
Favicon
Redis: Powering Real-Time Applications with Unmatched Performance
Favicon
Why does clustering with redis suck?
Favicon
Fixed Window Counter Rate Limiter (Redis & Java)
Favicon
Redis Data Structures
Favicon
Pub-sub Redis in Micronaut
Favicon
Real-Time Data Indexing: Powering Instant Insights and Scalable Querying
Favicon
How to Implement Caching in PHP and Which Caching Techniques Are Best for Performance?
Favicon
tnfy.link - One more shortener?
Favicon
Lettuce - A Java Redis Client
Favicon
Introducing keyv-upstash: Seamless Key-Value Storage for Serverless Redis
Favicon
Sherlock Holmes: The Case Of Redis Overload During a DDoS Attack
Favicon
Real-Time Location Tracking with Laravel and Pulsetracker's Redis Pub/Sub
Favicon
Redis Cache - A String story
Favicon
Working on Redis streams? Don't forget these commands.
Favicon
Caching with Redis for Backend in Apache Superset
Favicon
Redis Queue and Cron in Go
Favicon
Infinite redis request
Favicon
Rate limiting with Redis: An essential guide
Favicon
What do 200 electrocuted monks have to do with Redis 8, the fastest Redis ever?
Favicon
FLAIV-KING Weekly (Flink AI Vectors Kafka) for 18 Nov 2024
Favicon
Building a Real-Time Flask and Next.js Application with Redis, Socket.IO, and Docker Compose
Favicon
Spring Boot + Redis: A Beginner-Friendly Guide to Supercharging Your App’s Performance
Favicon
Boosting Speed and Performance with Advanced Caching in NestJS: How to Use AVL Trees and Redis
Favicon
I used GitHub as a CMS

Featured ones: