Crafting Keys: Best Naming Strategies and Sorting Techniques in Redis
In large-scale systems, effective grouping and indexing choices often define whether queries return in milliseconds or get stuck in multi‑second blocking scans.
Join the DZone community and get the full member experience.
Join For FreeIn Redis, everything begins with a key. A key is more than just a string that points to a value. It defines how your data is organized, retrieved, and queried. A well-designed key structure makes it easier to group related records, filter by attributes, and scale queries as datasets grow. On the other hand, poor key design can lead to inefficient lookups, expensive scans, and unnecessary complexity in your application logic.
In this guide, we’ll explore different strategies for designing Redis keys using a hotel dataset. You’ll see how simple naming conventions, sets, and sorted sets can shape how data is grouped and retrieved, and when it makes sense to move toward Redis modules like RediSearch for more complex querying.
Sample data:
{
"Hotels": [
{
"Id": 101,
"Name": "Holiday Inn",
"Rooms": 15,
"Occupied": 10,
"Vacant": 5,
"Rating" : 3,
"Address" : "7,Garden Road, MistWood, MW4567",
"RoomsByType" : {
"Exec" : 3,
"Premium" : 5,
"Double" : 7
}
},
{
"Id": 102,
"Name": "Seaside View Villa",
"Rooms": 10,
"Occupied": 9,
"Vacant": 1,
"Rating" : 4,
"Address" : "6/A,Wander Road, MistPark, MP7342",
"RoomsByType" : {
"Exec" : 3,
"Premium" : 3,
"Double" : 4
}
},
{
"Id": 103,
"Name": "Greenwood Inn",
"Rooms": 20,
"Occupied": 9,
"Vacant": 11,
"Rating" : 2,
"Address" : "10,Garden Road, MistWood, MW4567",
"RoomsByType" : {
"Exec" : 5,
"Premium" : 5,
"Double" : 10
}
},
{
"Id": 104,
"Name": "Beach House",
"Rooms": 20,
"Occupied": 16,
"Vacant": 4,
"Rating" : 4,
"Address" : "11 ,Wander Road, MistPark, MP7342",
"RoomsByType" : {
"Exec" : 5,
"Premium" : 5,
"Double" : 10
}
]
}
Suppose we want to group the hotels by their location. There are multiple ways to achieve this. One approach is to design keys to create namespaces that group related data. Generally, colons(:) are used to logically separate segments in keys. For example, the Key-Value pairs could be:
Key : "MistWood:101"
Value: {
"Id": 101,
"Name": "Holiday Inn",
"Rooms": 15,
"Occupied": 10,
"Vacant": 5,
"Rating" : 3,
"Address" : "7,Garden Road, MistWood, MW4567",
"RoomsByType" : {
"Exec" : 3,
"Premium" : 5,
"Double" : 7
}
}
--------------------------------
Key: "MistWood:103"
Value: {
"Id": 103,
"Name": "Greenwood Inn",
"Rooms": 20,
"Occupied": 9,
"Vacant": 11,
"Rating" : 2,
"Address" : "10,Garden Road, MistWood, MW4567",
"RoomsByType" : {
"Exec" : 5,
"Premium" : 5,
"Double" : 10
}
}
-----------------------------------
Key : "MistPark:104"
Value: {
"Id": 104,
"Name": "Beach House",
"Rooms": 20,
"Occupied": 16,
"Vacant": 4,
"Rating" : 4,
"Address" : "11 ,Wander Road, MistPark, MP7342",
"RoomsByType" : {
"Exec" : 5,
"Villa" : 5,
"Double" : 10
}
To query all the hotels in "MistWood" area:
SCAN 0 MATCH *MistWood* COUNT 100
This approach is especially useful when grouping items by "users", "tenants" or any other logical category.
When to Choose this Approach
- When the dataset is relatively small to medium in size.
- When queries are primarily based on a predictable key prefix.
- Avoid for very large datasets where even non‑blocking
SCANoperations may become costly.
Another way to achieve this is to store the main data separately and achieve logical grouping using sets or sorted sets.
For example, store the main hotel data as key-value pairs:
Key : 101
Value : {
"Id": 101,
"Name": "Holiday Inn",
"Rooms": 15,
"Occupied": 10,
"Vacant": 5,
"Rating" : 3,
"Address" : "7,Garden Road, MistWood, MW4567",
"RoomsByType" : {
"Exec" : 3,
"Premium" : 5,
"Double" : 7
}
}
-----------------------------------------
Key : 103
Value : {
"Id": 103,
"Name": "Greenwood Inn",
"Rooms": 20,
"Occupied": 9,
"Vacant": 11,
"Rating" : 2,
"Address" : "10,Garden Road, MistWood, MW4567",
"RoomsByType" : {
"Exec" : 5,
"Premium" : 5,
"Double" : 10
}
}
---------------------------------------------
Key : 104
Value: {
"Id": 104,
"Name": "Beach House",
"Rooms": 20,
"Occupied": 16,
"Vacant": 4,
"Rating" : 4,
"Address" : "11 ,Wander Road, MistPark, MP7342",
"RoomsByType" : {
"Exec" : 5,
"Villa" : 5,
"Double" : 10
}
Then create sets or sorted sets (if we want data to be sorted) and store just the hotel IDs (keys of main data).
Lets create two sets, one for MistWood and one for MistPark to group the hotels.
SADD location:MistWood 101 103
SADD location:MistPark 104
To look up all hotels at MistWood:
SMEMBERS location:MistWood
# → ["101", "103"]
To get the hotel details, issue a second query:
MGET 101 103
Now lets say we want to group the hotels by location and also sort them by the number of Exec rooms
Create one sorted set for each location, where the score is the number of Exec rooms and value is the hotel ID:
ZADD location:MistWood:byExec 3 101 5 103
ZADD location:MistPark:byExec 5 104
Redis sorted sets are always ordered by score
To get the list of hotel IDs sorted by the number of Exec rooms in ascending order
ZRANGE location:MistWood:byExec 0 -1 WITHSCORES
# → ["101", 3, "103", 5]
Use ZREVRANGE to get the hotels in descending order. Once we have the list of IDs, get the complete hotel data using those IDs.
If we want all the hotels in both MistWood and MistPark with at least 5 Exec rooms, create a temporary sorted set that combines the hotels from both locations, using the number of Exec rooms as the score:
ZUNIONSTORE temp:allExec 2 location:MistWood:byExec location:MistPark:byExec
Now fetch all hotels with at least 5 Exec rooms:
ZRANGEBYSCORE temp:allExec 5 +inf
# Returns: [103, 104]
-> Always cleanup the temporary sets to avoid unnecessary memory usage.
-> Always check if the commands are blocking or non-blocking.
When to choose this approach:
- Use sets when you need O(1) lookups for group retrieval, or intersection/union operations without scanning the whole database.
- Use sorted sets when you also need ordering by a numeric value (e.g., vacancies, ratings, exec room count).
- This is the recommended pattern in enterprise or large datasets where scanning would otherwise be too slow.
These types of union and intersection queries are very powerful for filtering and sorting data.
However, when queries become complex and the filtering conditions (fields, logic, sorting criteria) are dynamic or frequently change at runtime, RediSearch is the recommended solution for Redis.
Assume the hotels are stored as RedisJson, create an index as follows:
FT.CREATE hotelsIdx ON JSON PREFIX 1 hotel: SCHEMA \
$.Name AS Name TEXT \
$.Location AS Location TAG \
$.RoomsByType.Exec AS Exec NUMERIC SORTABLE \
$.Vacant AS Vacant NUMERIC \
$.Rating AS Rating NUMERIC
The Query to get all the hotels in both MistWood and MistPark with at least 5 exec rooms would be:
FT.SEARCH hotelsIdx '(@Location:{MistWood|MistPark}) @Exec:[5 +inf]'
To summarize, the performance of Redis depends not only on available resources, but also on:
1. Understanding the query patterns (which data is queried, the filters and sorting orders, the complexity involved)
2. Designing keys to logically group data making sorting and filtering easier
3. Creating additional supportive data structures to enable complex queries
4. Using powerful Redis modules like RedisJSON and RediSearch
Remember, designing and refining your data structures may take multiple iterations to achieve optimal results.
Happy Coding!
Opinions expressed by DZone contributors are their own.
Comments