Effortless Rate Limiting with Google Guava: Real-World Java Examples
Learn from my experience & how to implement a rate limiter in your Java Spring application! You get so much with 3 lines of code 🙌
In many high-performance systems, controlling the rate at which operations are executed is crucial for maintaining stability.
One common use case is API rate limiting—ensuring that external or internal APIs are not overwhelmed by excessive requests.
However, rate limiting has applications far beyond APIs. For instance, it can be a lifesaver when regulating interactions between different parts of an application, such as the service and database layer.
Recently, I encountered this problem, where the Google Guava RateLimiter played a critical role.
In a Java Spring Scheduler, we needed to process transactions while ensuring the database wasn't overloaded. Allowing unrestricted transaction flow could lead to excessive IO operations and potential database performance issues on production.
That’s why I needed something to help me restrict the scheduler to process only X transactions per second. As we also had the AWS limit of 3k IO operations per second at max!
👇 Let’s dive into how this problem was solved and explore other use cases for RateLimiter.
Rate Limiting Basics with Google Guava
Google Guava’s RateLimiter is based on a token-bucket algorithm.
It allows you to control the rate of events or operations, expressed in terms of permits per second. Each operation acquires a permit, and the RateLimiter ensures only the specified number of permits are granted per second. Excess operations wait until permits become available.
Example:
// Allows for 10 permits per second:
RateLimiter rateLimiter = RateLimiter.create(10.0);
// Blocks until a permit is available:
rateLimiter.acquire();
This ensures that only 10 operations are performed per second.
And it’s implementation is as simple as that. Of course you would need to create a Bean of the RateLimiter for Spring to catch it & use it in the application.
Real-World Example: Managing Database Transactions
The Problem:
In my Java Spring Scheduler, a task periodically fetched and processed batches of transactions. Without rate limiting, all transactions would be executed as quickly as possible.
While efficient on the surface, this approach risked overwhelming the database with too many concurrent IO operations, leading to potential performance degradation or crashes.
The Solution:
Using Google Guava’s RateLimiter, I was able to regulate the flow of transactions:
just a mock example of how we enabled the rate limiter in the code itself:
@Service
public class TransactionProcessor {
private final RateLimiter rateLimiter = RateLimiter.create(100);
public void processTransactions(List<Transaction> transactions) {
for (Transaction transaction : transactions) {
rateLimiter.acquire();
process(transaction);
}
}
private void process(Transaction transaction) {
// .. Transaction processing logic
}
}
In this implementation:
Each transaction acquires a permit before being processed.
The rateLimiter ensures that only 100 transactions are processed per second, preventing excessive strain on the database.
Comparing Use Cases: API Rate Limiting vs. Internal Rate Limiting
API Rate Limiting:
🛠️ Purpose: Prevent external or internal APIs from being overwhelmed by excessive requests.
🔬 Example:
// Allow for 50 API calls per second at max
RateLimiter apiRateLimiter = RateLimiter.create(50);
public void callExternalApi() {
apiRateLimiter.acquire();
// Some API call logic / FeignClient invocation..
}
✅ Benefits:
Protects APIs from abuse or unintended overload.
Ensures fair usage among clients.
Internal Rate Limiting (e.g., Database Transactions):
Purpose: Regulate internal system interactions to maintain resource stability.
Example: Processing transactions, as shown above.
✅ Benefits:
Prevents database crashes or IO bottlenecks.
Ensures smooth and predictable system performance.
Spreads resource usage evenly over time.
Other Real-World Applications of RateLimiter
Background Job Throttling:
For batch jobs that interact with third-party services or shared resources, RateLimiter ensures the workload is spread evenly over time, avoiding spikes.
// Limiting to 20 jobs per second at max:
RateLimiter jobRateLimiter = RateLimiter.create(20);
public void executeBatchJobs(List<Job> jobs) {
for (Job job : jobs) {
jobRateLimiter.acquire();
execute(job);
}
}
(📌) Streaming Data Processing:
When consuming messages from a queue or stream, you may want to limit the rate at which messages are processed to ensure downstream systems are not overwhelmed.
This can be a very powerful tool here!
Testing and Simulation:
In performance testing, RateLimiter can simulate real-world traffic patterns by controlling the rate of simulated requests.
Another amazing use-case for it! I even didn’t thought of it, before researching for this article 😅
Why Choose Google Guava RateLimiter? 🚀
Ease of Use: With minimal configuration, you can regulate throughput effectively.
Lightweight: It’s a simple, lightweight solution that integrates seamlessly into Java applications.
Thread Safety: RateLimiter is thread-safe, making it ideal for multi-threaded environments.
Predictable Behavior: By smoothing bursts, it ensures consistent system performance under load.
🎬 Conclusion
The Google Guava RateLimiter is a great tool that goes beyond the typical use case of API rate limiting.
Whether you’re managing API requests, processing database transactions, or handling background jobs, RateLimiter provides a reliable way to control throughput and protect system resources.
In my case, integrating it into a Java Spring Scheduler helped prevent database overload and ensured smooth transaction processing.
Have you used RateLimiter or similar tools in your projects?
I’d love to hear your experiences and insights! 🚀