Redis, known for its blazing-fast in-memory data structures, doesn’t natively support secondary indexes in the way a relational database does. This means you can’t directly query, for example, all users with city = 'London' if your primary key is user_id. However, a common and effective pattern to achieve this "query by any field" capability involves using Redis Sets.

Let’s see this in action. Imagine we’re storing user data and want to find users by their email, username, or city.

First, we store the main user data, keyed by their unique user_id:

HMSET user:1001 name "Alice Smith" email "alice.smith@example.com" city "New York"
HMSET user:1002 name "Bob Johnson" email "bob.j@example.com" city "London"
HMSET user:1003 name "Charlie Brown" email "charlie.b@example.com" city "New York"

Now, to enable querying by email and city, we create Redis Sets where the members of the set are the user_ids, and the key of the set is a composite of the field name and its value.

For email:

SADD email:alice.smith@example.com 1001
SADD email:bob.j@example.com 1002
SADD email:charlie.b@example.com 1003

And for city:

SADD city:New York 1001
SADD city:New York 1003
SADD city:London 1002

To find all users in "New York", we simply query the set city:New York:

SMEMBERS city:New York

This would return:

1) "1001"
2) "1003"

Then, for each of these user_ids, we can fetch the full user details using HGETALL user:<user_id>:

HGETALL user:1001
# Returns: name "Alice Smith" email "alice.smith@example.com" city "New York"
HGETALL user:1003
# Returns: name "Charlie Brown" email "charlie.b@example.com" city "New York"

This pattern effectively builds secondary indexes by leveraging Redis Sets. Each unique value for a searchable field becomes the suffix of a Redis key, and the value associated with that key is a set containing the primary keys (in our case, user_ids) of all records that match that field’s value. When you need to query, you first look up the set corresponding to your criteria, and then use the resulting primary keys to retrieve the actual data from your primary hash stores.

The core problem this solves is the lack of direct secondary index support in Redis. By creating these "index sets," we transform Redis from a simple key-value store into a more versatile data structure capable of performing lookups based on arbitrary attributes. The SADD command is crucial here; it adds members to a set, and importantly, it’s idempotent – adding an existing member has no effect, ensuring data integrity. The SMEMBERS command then efficiently retrieves all matching primary keys.

When updating a record, say changing Alice’s city from "New York" to "San Francisco", you need to perform a few operations atomically (ideally within a Redis transaction using MULTI/EXEC):

  1. Remove Alice’s user_id (1001) from the old index set (city:New York).
  2. Add Alice’s user_id (1001) to the new index set (city:San Francisco).
  3. Update the user’s hash itself (HMSET user:1001 ... city "San Francisco").

This approach allows for flexible querying, but it introduces complexity in data management, especially during updates and deletions. The trade-off is flexibility for speed; querying the index sets is extremely fast, but maintaining them requires careful application logic.

The most surprising aspect is how effectively simple Redis Sets, with their O(1) average-case time complexity for adding and checking membership, can be used to mimic the behavior of traditional database indexes. The "magic" is in carefully constructing your keys to represent the relationship between an attribute’s value and the primary keys it points to. This isn’t just about storing data; it’s about structuring your keyspace to enable specific query patterns. You might think a sorted set (ZSET) would be useful here for range queries, but for simple equality checks, a regular set is more efficient and simpler to manage.

The next challenge you’ll face is handling composite queries, like finding users who live in "New York" AND have the email "alice.smith@example.com".

Want structured learning?

Take the full Redis course →