Wednesday, 7 September 2016

Building a faceted search using Redis and MVC.net - part 1: The key is key

Faceted searches on web pages come in many forms, both technically and UX-wise. This one is built using Redis and MVC.net. The data is stored and modelled in Redis to suit this particular need. The web site uses plain javascript to build the calls to the MVC app, which serves the resulting list as an HTML-blob.


Basic concept

A demo can be found on http://hotelweb.azurewebsites.net/. The basic concept is: 2000 hotels in a list, possible to filter by different qualities and sort by column. The filtering facets are updated with the number of hits each option has. All calls for filtering and sorting are server calls going to the Redis DB. The demo is hosted on Azure and the Redis DB on RedisLabs, which makes the demo not quite as fast as it could be. Sorry about that. And it's not responsive. At all.

Data in Redis

Redis is a key-value store that gives you the possibility to use a number of different data types as the value. In this case, we make use of:
  • Strings, a plain string value: color = "blue".
  • Sets, an unordered collection of unique strings: allcolors = "blue"; "red"; "green".
  • Sorted sets, a set where each element has a score for sorting them: bestcolors = "blue", 1; "green", 2; "red", 3.

Being able to use sets and stored sets is great, but you still have to wrap your head around how to think about data in Redis. It's a bit different compared to working with the relational or document database you might be used to. Let's look at the first example: how to store and connect countries and hotels.

Compounding keys

In Redis, it's all about working with the data, creating all the keys necessary for what you want to do. There is a great pattern for doing this. First, let's look at what we want to be able to do with the countries in this scenario.
  • We want to fetch all the countries available. This sounds like a set.
  • We want to fetch all hotels for a country. This also sounds like a set.

So if we create the set "Countries", holding the values "Germany", "Sweden", "Denmark", that would solve our first task. But how will we solve the second one? Should we have a set called "Germany" that holds the hotel ids? Then, how would we know that the set "Germany" is a country? We won't, and maybe that wouldn't be a problem in a small data model, but it will definitely quickly get messy and hard to understand the data structure.

The solution to this are compound keys (yay!). Let the key reflect the data structure and let the values be other keys leading down to more data.
Countries = ["Countries:1", "Countries:2", "Countries:3"]
Countries:1 = "Germany"
Countries:2 = "Sweden"
Countries:3 = "Denmark"
Countries:1:Hotels = ["Hotels:1", "Hotels:33", "Hotels:194"]
Hotels:1 = '{\"Name\":\"Hotel 1\",\"Stars\":3,\"PricePerNight\":1000}'
Hotels:33 = '{\"Name\":\"Hotel 33\",\"Stars\":5,\"PricePerNight\":2700}'
Hotels:194 = '{\"Name\":\"Hotel 194\",\"Stars\":1,\"PricePerNight\":235}'

Using this pattern, the data structure is fairly simple to grasp just by looking at the key. The set Countries holds the keys Countries:1, Countries:2, Countries:3. These keys lead to the name of the country. By adding another segment to the key you can connect the countries to other sets. Countries:1:Hotels holds the keys to all hotels in that particular country. That hotel key in turn uses the string data type to store a json blob containing the hotel information.

Compound keys can of course be used in any direction. Hotels:1:Beaches can hold a set of all the beaches close to a certain hotel, in the same way as Beaches:1:Hotels can hold a set of all hotels close to a particular beach. Slicing and dicing the data in advance is key (no pun intended) when working with Redis.

Next step

Now we have a start to getting the hotels we want when clicking countries and stars (you guessed it, Stars:1:Hotels) in our faceted search. Next up we'll see how we can combine and sort sets.

No comments:

Post a comment