We’ve installed Redis for use on Obsidian Portal in some specific caching instances. We already use MySQL and MongoDB, so in the spirit of further fracturing out underlying data model, I thought I’d throw Redis into the mix.
I installed Redis using Chef and the Opscode Redis cookbook, and it was fairly painless. Even with my lack of Chef knowledge, I had it up and running in a half hour.
Likewise, connecting to it from Rails was a breeze. The redis-rb gem found my Redis install across the network and connected seamlessly. Along with Mongo, this no-hassle connection is a very nice departure from the MySQL (or worse, PostgreSQL) user permissions model. I don’t disagree with the old-school permissions model, I just don’t need it for my purposes, and it’s nice to be able to bypass it and get right down to business.
We generate user activity streams detailing a user’s most recent actions on the site. Although the MySQL table is relatively small and indexed as best I can imagine, the query speed to generate the different streams is agonizingly slow. I really must be doing something wrong, but I couldn’t figure it out after much tweaking and playing with indices.
My solution was to leave the data storage in MySQL, but move the stream construction into Redis. Instead of querying MySQL and doing the joins to make the stream, I just create a linear stream for each user and append new IDs to it every time an action occurs. Then, the MySQL query is a dead-simple “SELECT WHERE IN” query using the IDs pulled from the Redis list. This takes a 30+ second MySQL query down to a few milliseconds.
As always, the main issue is synchronization. I have to make sure that the Redis activity stream matches the activities stored in MySQL. For the most part, it’s easy, but it’s never as easy as it seems. For example, we have issues when deleting activities from the stream. This can happen when someone decides to make something private. I didn’t think to make this deletion cascade to the Redis list, so sometimes the Redis list is referencing a MySQL record that doesn’t exist anymore. Luckily it’s not a huge problem, it just means that for a short time someone’s activity stream is slightly shorter than before. No biggie.
A great fit, but not for every job
Overall, I’ve been very impressed with Redis. It’s a very barebones approach to data persistence, and I can imagine a lot of cases where it would be useful. Foremost in my mind are caching scenarios like the one I just described. Use MySQL to store the actual data, but use Redis to pre-cache the most likely access patterns. This fits well with my definition of NoSQL as “Not Only SQL”
All that being said, I don’t see Redis as a full persistence backend for a real system. I like to see some relation between my application objects and the persistence layer, and Redis is so simple that I can’t see it. An RDBMS row or Mongo document fits my definition of “object” much better than the very simple data types that Redis supports. Of course, it could just be that I’m not creative enough to break out of my Object Oriented RDBMS prison.
New tool in the box
I’m very happy to add Redis to my toolbox, and I hope that I can find some new places to use it in the future. In fact, right now I’m trying to avoid applying my shiny new toy to every problem I see. I’d like to coin a corollary to the old saying “If your only tool is a hammer, everything looks like a nail.” As I’m seeing now, “If your only tool was a hammer, then you find a screwdriver, it’s very tempting to yank all the nails and replace them with screws, only to realize that the nails did perfectly fine to begin with.”