Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 30

Redis Data Structures for Rate Limiting

Recommended resources

Hello! Welcome to the first lesson in our module on distributed state management with Redis.

In the previous module, we built a robust system for identifying incoming requests. Your CompositeKeyResolver can now generate a unique, fine-grained key like user-123:/api/orders that precisely identifies who is making a request and to what resource.

This raises the next crucial question: once we have this key, where do we store the request count? An in-memory ConcurrentHashMap was a great start, but it has a major limitation: its state is confined to a single server instance. If we run multiple instances of our service for high availability (a standard practice in production), each will have its own separate count, and our rate limit will be effectively multiplied by the number of instances.

To solve this, we need a distributed, shared state store. For this project, we'll use Redis, an industry-standard, high-performance in-memory data store.

Your learning outcome for this lesson:

You will explain Redis data structures relevant to rate limiting (strings and hashes). Understanding these two data structures is fundamental, as they form the backbone of the distributed rate limiting algorithms we will implement later.



1. What is Redis?

At its core, Redis is a key-value store. You give it a key (like the ones we generated in the last module), and it stores a value. However, its power comes from the fact that the "value" is not just a simple piece of text. It can be one of several complex data structures.

This multi-modal nature makes Redis incredibly versatile. Let's get a high-level overview.

Redis Data Structures for Non-Redis Users

To start, let's watch a short segment from the 'Redis Data Structures for Non-Redis Users' video by the official Redis channel. This will introduce the core concept of Redis as a multi-modal key-value store.

Watch the segment from 2:15 to 4:00. Focus on how Redis differs from traditional databases by allowing the value associated with a key to be a specific data type (String, List, Hash, etc.).

As the video explains, a single key can point to different types of data structures. This is a key concept to grasp.

Redis Data Types Overview
This image illustrates the core concept of Redis: a single key can be associated with various data structures, each with its own set of specialized commands.

For our rate limiter, we are primarily interested in two of these data structures: Strings and Hashes.

Before we dive into those, let's quickly discuss keys and their lifecycle, as this is critical for managing rate limiting windows.

Redis Data Structures for Non-Redis Users

The same video provides a concise explanation of Redis keys, including naming conventions and, most importantly, the ability to set an expiration time (TTL - Time To Live).

Please watch the sections from 4:00 to 7:37. Pay close attention to: The community convention of using colons (:) in key names to create a structured, readable keyspace. This directly relates to our CompositeKeyResolver. The EXPIRE command, which allows Redis to automatically delete keys after a certain time. This is how we will manage our rate limiting windows without manual cleanup.

The ability to set a TTL on a key is fundamental to rate limiting. It allows us to define a time window (e.g., 60 seconds) and let Redis handle the state's cleanup automatically once the window has passed.


2. The STRING Data Type for Simple Counters

The simplest data type in Redis is the STRING. While it can store up to 512 MB of binary-safe data, its real power for rate limiting comes from a special set of commands that can operate on strings that represent numbers.

Redis Data Structures for Non-Redis Users

Let's revisit the 'Redis Data Structures' video to see how Redis handles strings and numeric operations.

Watch from 7:37 to 9:59. The key command to note here is INCR. This command atomically increments the number stored in a string. Understand why an attempt to increment a non-numeric string like 'sold-out' fails.

The atomicity of the INCR command is what makes it safe for concurrent use. When multiple requests for the same user hit our rate limiter simultaneously, multiple threads will try to increment the same counter in Redis. Because INCR is atomic, Redis guarantees that these operations happen sequentially, preventing race conditions where requests might get a stale count and incorrectly pass the limit.

The combination of INCR and EXPIRE is the perfect recipe for the Fixed Window Counter algorithm.

Build 5 Rate Limiters with Redis

The tutorial 'Build 5 Rate Limiters with Redis' provides a practical walkthrough. Let's see how it applies the STRING data type.

Read the section on the Fixed Window Counter. Notice how it explicitly uses the STRING data type with the INCR command to bump the count and EXPIRE to auto-clean the key. This is a direct application of the concepts we just discussed.

This pattern is simple, fast, and memory-efficient, making it ideal for basic rate limiting. As the same resource mentions in its section on the "Sliding window counter", a more advanced version of this algorithm can also be built using two STRING keys and a weighted formula.


3. The HASH Data Type for Structured State

While strings are great for simple counters, some algorithms require us to store multiple pieces of related information under a single key. For example, the Token Bucket algorithm needs to track both the number of available tokens and the last time the bucket was refilled.

Storing these as two separate string keys (e.g., ratelimit:user123:tokens and ratelimit:user123:last_refill) is inefficient and difficult to manage atomically. This is where the HASH data type shines. A Redis Hash is essentially a map of field-value pairs stored at a single key—a "key-value store within a key."

Redis Data Structures for Non-Redis Users

Let's return to our introductory video one last time to understand Hashes.

Watch from 16:45 to 19:19. Notice how a hash allows you to store and update individual fields (like 'capacity' or 'venue') without rewriting the whole object. The video explicitly calls out rate limiting as a typical use case for hashes.

Using a hash, we can store our token bucket's state in a single Redis key, with fields like tokens and last_refill. We can then use commands like HINCRBY to atomically modify just the token count.

Build 5 Rate Limiters with Redis

The 'Build 5 Rate Limiters with Redis' tutorial explains exactly how a hash is used to implement the Token Bucket algorithm.

Read the section on the Token Bucket algorithm, focusing on the 'Redis data structure' part. It describes using a HASH with two fields: tokens and last_refill. Also, note the 'Why Lua?' section—it's a preview of a crucial concept (atomicity for complex operations) that we'll cover in detail later.

Hashes are also instrumental for more sophisticated implementations of the Sliding Window algorithm. Instead of just tracking one or two counters, a hash can be used to store counts for many smaller sub-windows, providing higher accuracy. The video "Sliding Window Counter Rate Limiter (Redis & Java)" (resource LINK) demonstrates this technique, using commands like HINCRBY to update sub-window counts and HEXPIRE to expire individual fields within the hash.


4. Strings vs. Hashes: A Quick Comparison

You now know the two primary Redis data structures for rate limiting. So, when should you use one over the other?

Redis Strings vs Hashes - Which Data Structure Is Best for Your Application

This article gives a clear, concise comparison of Strings and Hashes, helping you decide which to use.

Read the first three paragraphs of the article. It effectively summarizes the core trade-off: use Strings for simple, standalone values (like a single counter), and use Hashes for storing multiple related fields that belong to a single logical entity (like a token bucket's state).

Here's a summary table from the "Build 5 Rate Limiters with Redis" tutorial (resource LINK), which neatly ties each algorithm to its underlying Redis data structure:

Algorithm Redis type Use Case in Our Project
Fixed window STRING A simple counter for a single time window.
Sliding window counter STRING x2 Two counters for previous and current windows.
Token bucket HASH + Lua A single key holding tokens and last_refill fields.


Conclusion

In this lesson, you've taken the first step into the world of distributed state management with Redis. You now understand the fundamental building blocks we'll use for our rate limiter.

Key Takeaways:

  • Redis is a fast, in-memory, multi-modal key-value store, perfect for managing shared state across multiple service instances.
  • Keys can be given a Time-To-Live (TTL), which is essential for automatically cleaning up expired rate-limiting windows.
  • The STRING data type, combined with the atomic INCR command, is ideal for implementing simple counter-based algorithms like the Fixed Window.
  • The HASH data type allows you to store structured data (field-value pairs) under a single key. It is perfect for more complex algorithms like the Token Bucket, which needs to track multiple state variables atomically.

Next Steps

You've learned what data structures to use. In the next lesson, we will focus on the how. You will learn to connect your Spring Boot application to a Redis instance using Spring Data Redis, setting the stage for implementing our first distributed rate limiting algorithm.