Rate Limiting in PHP

Recently I was having a discussion about putting together an API and some ideas about caching, rate limiting or flood protection, design patterns for rendering the output, scalability, and security came up.

A while ago I came up with a rate limiting class. It wasn’t as thoroughly tested as I would hope as I left a little over a week after putting this together there, but basically we use time as a cross thread limiter for all connections. If we wanted to limit a particular user, then we’d simply have to create a unique key for the memcache entry for each user.

This logic uses time (as Unix timestamps with microseconds) as a method to “charge” each request against the server a “fee” which accrues against the max or limit. As time moves forward, it removes accrued cost from the memcache saved value.

The beauty of doing rate limiting in code is it allows the developer to manage flow and how limits are handled to protect our databases and CPU load; dishing out less expensive and properly (and expected) formed API responses.

Here’s the code:

////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// 	RATE LIMITER CLASS - rateLimiter.class.php
//
// 	@date:	2009.05.21
// 	@author 	Michael Hradek 
// 	@version 	1.0.0
// 	@license 	Apache License, Version 2.0
//
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////
// INCLUDES
////////////////////////////////////////////////////////////////////////////

require_once("initializeMemcache.class.php");

////////////////////////////////////////////////////////////////////////////
// PRIMARY CLASS
////////////////////////////////////////////////////////////////////////////

class rateLimiter
{
    private $rateLimitKey;
    private $rateLimitMax;
    private $rateLimitCost;

    private $memcache;

    function __construct($rateLimitMax, $rateLimitCost,
                         $rateLimitKey = "rate_limiter_key")
    {
        $this->rateLimitMax = (float)$rateLimitMax;
        $this->rateLimitCost = (float)$rateLimitCost;
        $this->rateLimitKey	= $rateLimitKey;

        // Initialize your memcache. This is just one way of initializing it.
        $this->memcache	= InitializeMemcache::initMemcache();
    }

    public function enforceRateLimit()
    {
        $rateLimitDataMs = $this->memcache->get($this->rateLimitKey);
        list($partMsec, $partSec) = explode(" ", microtime());
        $currentTimeMs = $partSec.$partMsec;
        if($rateLimitDataMs === false)
        {
            $this->memcache->set($this->rateLimitKey, $currentTimeMs,
                                 MEMCACHE_COMPRESSED);
            return true;
        }

        $timeDiffMs = $rateLimitDataMs - $currentTimeMs;
        if($timeDiffMs < 0) {
            $rateLimitDataMs = $currentTimeMs;
        }

        $rateLimitDataMs += $this->rateLimitCost;
        if($timeDiffMs < $this->rateLimitMax)
        {
            if(!$this->memcache->replace($this->rateLimitKey,
                                $rateLimitDataMs, MEMCACHE_COMPRESSED)) {
                $this->memcache->set($this->rateLimitKey, $rateLimitDataMs,
                                     MEMCACHE_COMPRESSED);
            }

            return true;
        }

        $rateLimitDataMs += ($this->rateLimitCost*2);
        if(!$this->memcache->replace($this->rateLimitKey, $rateLimitDataMs,
                                     MEMCACHE_COMPRESSED)) {
            $this->memcache->set($this->rateLimitKey, $rateLimitDataMs,
                                 MEMCACHE_COMPRESSED);
        }

        error_log(get_class().": Rate limit enforced.");
        return false;
    }
}

The memcache class is simply something that reads memcache server configuration arrays and loads them into a memcache handle and returns it. I think with some larger volumes we can excuse things like misses in key lookups. We’re already using a hashed configuration. Why add the trouble of retrying?

This entry was posted in Programming and tagged . Bookmark the permalink.

4 Responses to Rate Limiting in PHP