
Advantages / Disadvantages
Advantages
- Smoother distribution of allowed requests
- Greater accuracy and fairness
- Offers a more precise count of requests within any arbitrary time window, unlike fixed window algorithm
Disadvantages
- Memory overhead
- Saving individual timestamp can be memory consuming
- Performance
- Frequent additions/removals from the timestamp queue can introduce latency
Implementation
import ratelimiter.SlidingWindowRateLimiter;
public class Main {
public static void main(String args[]) throws Exception {
SlidingWindowRateLimiter rateLimiter = new SlidingWindowRateLimiter(5, 5);
for (int i=0;i<100;i++) {
boolean canConsume = rateLimiter.tryConsume();
if (canConsume) {
System.out.println("Request " + i + " can be consumed!");
} else {
System.out.println("Request " + i + " is blocked by rate limiter");
}
Thread.sleep(500);
}
}
}
package ratelimiter;
import java.util.LinkedList;
import java.util.Queue;
public class SlidingWindowRateLimiter implements RateLimiterInterface {
private int capacity;
private int windowSizeInSeconds;
private Queue<Long> requestTimestamps;
/**
* Initialize the capacity and window size for sliding window.
* Also, initialize 'requestTimestamps' queue which is for storing timestamps inside the window
*
* @param capacity
* @param windowSizeInSeconds
*/
public SlidingWindowRateLimiter(int capacity, int windowSizeInSeconds) {
this.capacity = capacity;
this.windowSizeInSeconds = windowSizeInSeconds;
this.requestTimestamps = new LinkedList<>();
}
/**
* 1. Calculate the start of the window
* 2. Remove the timestamps saved in 'requestTimestamps' queue which is before the start of the window
* 3. Process the request if the 'requestTimestamps' queue size is smaller than capacity
*/
@Override
public boolean tryConsume() {
long currentTimeMillis = System.currentTimeMillis();
long windowStartMillis = currentTimeMillis - windowSizeInSeconds * 1_000L;
while(!requestTimestamps.isEmpty() && requestTimestamps.peek() < windowStartMillis) {
requestTimestamps.poll();
}
if (requestTimestamps.size() < capacity) {
requestTimestamps.add(currentTimeMillis);
return true;
} else {
return false;
}
}
}
Implementation – Output
Request 0 can be consumed!
Request 1 can be consumed!
Request 2 can be consumed!
Request 3 can be consumed!
Request 4 can be consumed!
Request 5 is blocked by rate limiter
Request 6 is blocked by rate limiter
Request 7 is blocked by rate limiter
Request 8 is blocked by rate limiter
Request 9 is blocked by rate limiter
Request 10 can be consumed!
Request 11 can be consumed!
Request 12 can be consumed!
Request 13 can be consumed!
Request 14 can be consumed!
Request 15 is blocked by rate limiter
FollowUp Questions
#1. How does your Sliding Window Counter implementation handle memory consumption, especially with high request rates or large window sizes? Are there optimizations to reduce memory usage?
- To optimize the memory usage, we can use
Atomic Countersinstead ofQueues
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class SlidingWindowRateLimiterOptimized {
private final int maxRequests;
private final long windowSizeInNanos;
private final AtomicInteger requestCount;
private final AtomicLong windowStart;
/**
* Initializes the rate limiter with the specified maximum number of requests
* allowed within the given window size.
*
* @param maxRequests Maximum number of allowed requests within the window.
* @param windowSizeInSeconds Size of the sliding window in seconds.
*/
public SlidingWindowRateLimiterOptimized(int maxRequests, int windowSizeInSeconds) {
if (windowSizeInSeconds <= 0) {
throw new IllegalArgumentException("Window size must be positive.");
}
this.maxRequests = maxRequests;
this.windowSizeInNanos = windowSizeInSeconds * 1_000_000_000L;
this.requestCount = new AtomicInteger(0);
this.windowStart = new AtomicLong(System.nanoTime());
}
/**
* Attempts to consume a request. Returns true if the request is allowed,
* false if the rate limit has been exceeded.
*
* @return boolean indicating whether the request is allowed.
*/
public boolean tryConsume() {
long currentTime = System.nanoTime();
long windowStartTime = windowStart.get();
long elapsedTime = currentTime - windowStartTime;
if (elapsedTime >= windowSizeInNanos) {
if (windowStart.compareAndSet(windowStartTime, currentTime)) {
requestCount.set(0);
}
}
if (requestCount.incrementAndGet() <= maxRequests) {
return true;
} else {
return false;
}
}
/**
* Returns the current number of requests within the sliding window.
*
* @return int representing the current number of requests in the window.
*/
public int getCurrentRequestCount() {
long currentTime = System.nanoTime();
long windowStartTime = windowStart.get();
if (currentTime - windowStartTime >= windowSizeInNanos) {
if (windowStart.compareAndSet(windowStartTime, currentTime)) {
return 0;
}
}
return requestCount.get();
}
}

Leave a comment