PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
164: Building a Simple Rate Limiter with Redis
When I first started building APIs, I thought rate limiting was a simple CRUD problem. I figured I'd just create a request_logs table in MySQL, count the rows for a specific user ID in the last minute, and if the count was over 60, I'd block the request. It seems logical on paper, but in a high-traffic environment, this approach is a recipe for disaster.
Why SQL Counters Fail Under Pressure
The problem is a classic race condition. Imagine two requests from the same user hitting your server at almost the exact same millisecond. Both requests trigger a SELECT COUNT(*). Both see that the user has made 59 requests. Both conclude, "Okay, 59 is less than 60, we're good to go!" Both requests then proceed and increment the count to 60. You've just allowed 61 requests through when your limit was 60.
Beyond the race condition, hitting a disk-based database for every single incoming request—just to see if you should even process that request—is an incredible waste of resources. Your database will spend more time counting rows than actually serving data. This is why we move this logic into memory, specifically using Redis.
Using Redis Atomicity to Solve the Race
Redis is single-threaded, which is its "secret sauce" for this use case. When you use the INCR command, Redis guarantees that the operation is atomic. If ten requests hit Redis at once to increment the same key, Redis will handle them one by one, ensuring the count is perfectly accurate every time.
Let's look at how to implement a "Fixed Window" rate limiter. In this scenario, we'll imagine we are protecting an expensive SMS-sending endpoint where we only allow 5 messages per minute per user to prevent spam and cost overruns.
// Assume $redis is an instance of \Redis
$userId = 'user_123';
$limit = 5;
$window = 60; // seconds
$key = "rate_limit:sms:$userId";
// Atomic increment
$currentRequests = $redis->incr($key);
// If this is the first request in the window, set the expiration
if ($currentRequests === 1) {
$redis->expire($key, $window);
}
if ($currentRequests > $limit) {
http_response_code(429);
echo json_encode(['error' => 'Too many SMS requests. Please wait a minute.']);
exit;
}
// Proceed to send the SMS...
I like this approach because it's lean. We aren't storing timestamps or complex logs; we're just storing a single integer and a TTL (Time To Live). Once the 60 seconds are up, Redis automatically wipes the key, and the user's quota resets. It's clean, fast, and most importantly, it's thread-safe.
Handling the "Edge" of the Window
You should be aware that a "Fixed Window" has one flaw: the "burst" at the boundary. If a user sends 5 requests at 10:00:59 and another 5 requests at 10:01:01, they've technically sent 10 requests in two seconds, even though they stayed within the "per minute" limit of each individual window. For most applications, this is perfectly acceptable. If you're building something like a high-frequency trading platform, you'd want a "Sliding Window," but that requires using Redis Sorted Sets, which adds significant complexity. For 95% of the projects you'll work on, the INCR method I showed you is the right tool for the job.
📋 Practical Task
Exercise: Building a Per-API-Key Rate Limiter for a Weather Service
You are tasked with protecting a Weather API. Instead of using a User ID, the system identifies clients by an api_key passed in the request header. Your goal is to implement a rate limiter that allows 10 requests per minute per API key.
Requirements:
- Retrieve the
X-API-Keyfrom the$_SERVERsuperglobal. - If the key is missing, return a
401 Unauthorizedresponse. - Implement the Redis
INCRandEXPIRElogic to limit requests to 10 per 60 seconds. - If the limit is exceeded, return a
429 Too Many Requestsresponse with a JSON body explaining the limit. - If the request is allowed, return a
200 OKresponse with a dummy weather payload:{"temp": "22C", "condition": "Sunny"}.
Constraint: Ensure that you only call expire() when the key is first created to avoid resetting the window on every single request.
There are no comments for now.