Rate Limiting in R Programming Language

Here’s the translation of the Go rate limiting example to R, formatted in Markdown for Hugo:

Rate limiting is an important mechanism for controlling resource utilization and maintaining quality of service. R supports rate limiting through various mechanisms, including the use of timers and queues.

library(R6)
library(lubridate)

# First we'll look at basic rate limiting. Suppose
# we want to limit our handling of incoming requests.
# We'll serve these requests off a queue of the same name.

requests <- c(1, 2, 3, 4, 5)

# This limiter function will return TRUE every 200 milliseconds.
# This is the regulator in our rate limiting scheme.
create_limiter <- function(interval) {
  last_check <- Sys.time()
  function() {
    now <- Sys.time()
    if (as.numeric(now - last_check, units = "secs") >= interval) {
      last_check <<- now
      return(TRUE)
    }
    return(FALSE)
  }
}

limiter <- create_limiter(0.2)  # 200 milliseconds

# By checking the limiter before processing each request,
# we limit ourselves to 1 request every 200 milliseconds.
for (req in requests) {
  while (!limiter()) {
    Sys.sleep(0.01)  # Small sleep to avoid busy waiting
  }
  cat("request", req, as.character(Sys.time()), "\n")
}

# We may want to allow short bursts of requests in
# our rate limiting scheme while preserving the
# overall rate limit. We can accomplish this by
# using a token bucket algorithm.

TokenBucket <- R6Class("TokenBucket",
  public = list(
    capacity = NULL,
    tokens = NULL,
    fill_rate = NULL,
    last_fill = NULL,
    
    initialize = function(capacity, fill_rate) {
      self$capacity <- capacity
      self$tokens <- capacity
      self$fill_rate <- fill_rate
      self$last_fill <- Sys.time()
    },
    
    consume = function(tokens = 1) {
      self$refill()
      if (self$tokens >= tokens) {
        self$tokens <- self$tokens - tokens
        return(TRUE)
      }
      return(FALSE)
    },
    
    refill = function() {
      now <- Sys.time()
      delta <- as.numeric(now - self$last_fill, units = "secs")
      self$tokens <- min(self$capacity, self$tokens + delta * self$fill_rate)
      self$last_fill <- now
    }
  )
)

# This bursty_limiter will allow bursts of up to 3 events.
bursty_limiter <- TokenBucket$new(capacity = 3, fill_rate = 5)  # 5 tokens per second

# Now simulate 5 more incoming requests. The first
# 3 of these will benefit from the burst capability
# of bursty_limiter.
bursty_requests <- c(1, 2, 3, 4, 5)

for (req in bursty_requests) {
  while (!bursty_limiter$consume()) {
    Sys.sleep(0.01)  # Small sleep to avoid busy waiting
  }
  cat("request", req, as.character(Sys.time()), "\n")
}

Running our program, we see the first batch of requests handled once every ~200 milliseconds as desired.

request 1 2023-06-01 12:00:00.000
request 2 2023-06-01 12:00:00.200
request 3 2023-06-01 12:00:00.400
request 4 2023-06-01 12:00:00.600
request 5 2023-06-01 12:00:00.800

For the second batch of requests, we serve the first 3 immediately because of the burstable rate limiting, then serve the remaining 2 with delays.

request 1 2023-06-01 12:00:01.000
request 2 2023-06-01 12:00:01.000
request 3 2023-06-01 12:00:01.000
request 4 2023-06-01 12:00:01.200
request 5 2023-06-01 12:00:01.400

This example demonstrates how to implement basic and bursty rate limiting in R. The basic rate limiting uses a simple timer approach, while the bursty rate limiting uses a token bucket algorithm. These techniques can be useful for controlling the rate of operations in various scenarios, such as API calls or resource-intensive computations.