Simple Rate Limits Try it

Two things need to happen to use a rate limit. First, you need to define the LimitDefinition. You can do this in the Prefab.cloud UI or via the API.

The first parameter to create a limit is the definition "group". This is the lookup key for the limit. The second parameters are the limit itself and the policy name which is essentially the time period. Let's see what a basic hourly rate limit would look like:

UI for creating a new limit

Now to use this only_do_once_per_minute limit we simply create a rate limit client and call the pass? method.

@@client = Prefab::Client.new(shared_cache: Rails.cache)
@@ratelimit_client = @@client.ratelimit_client(timeout: 30)

if @@ratelimit_client.pass? "only_do_once_per_minute"
  puts "only will happen once a minute"
end
net.spy.memcached.MemcachedClient memcachedClient = new net.spy.memcached.MemcachedClient(
        new InetSocketAddress("localhost", 11211));

PrefabCloudClient.Builder builder = new PrefabCloudClient.Builder()
    .setDistributedCache(new MemcachedCache(memcachedClient))

final PrefabCloudClient prefabCloudClient = new PrefabCloudClient(builder);
final RateLimitClient rateLimitClient = prefabCloudClient.rateLimitClient();

rateLimitClient.upsert(Prefab.LimitDefinition.newBuilder()
    .setGroup("hourlytask")
    .setLimit(1)
    .setPolicyName(Prefab.LimitResponse.LimitPolicyNames.HOURLY_ROLLING)
    .build());

boolean result = rateLimitClient.isPass(Prefab.LimitRequest.newBuilder()
          .setAcquireAmount(1)
          .addGroups("hourlytask")
          .build());

if(result){
    System.out.println("do hourly thing");
}
var client = new PrefabCloudClient();

client.rateLimit.pass("hundred").then(function(passed){
    console.log("only will happen once a minute");
});

Use the same limit definition multiple times Try it

Here is where things get interesting. In the first example we were sharing a single rate limit for everyone. What if we want to have a different limiter for each user? Let's see how to re-use a limit definition.

Lets say we are tracking page landings, but we are trying to limit how many events we send. To do that, we'll want a separate limiter per per user per page. This will let us only track one visit for a user/page even if Bob reloads page A twenty times.

The LimitDefinition group is a colon delimited string but when you lookup a limitdefinition you need not specify the whole thing. We'll find the closest matching limit definition. So for a definition like events:hourly you can call Prefab for a limit on events:hourly:thing1. That will get the definition of events:hourly, but you'll get a different token bucket for thing1 than you will if you do it again for events:hourly:thing2.

For the next example, create a limit with group: pageload, limit:HOURLY_ROLLING and amount:1.

client = Prefab::Client.new
ratelimit_client = client.ratelimit_client

if ratelimit_client.pass?("pageload:/my_account/login/:user12312")
   # this will fire
   TrackingService.trackPage(user12312, "/my_account/login/")
end

if ratelimit_client.pass?("pageload:/my_account/login/:user456")
   # this will fire
   TrackingService.trackPage(user456, "/my_account/login/")
end

if ratelimit_client.pass?("pageload:/my_account/login/:user456")
   # this won't fire because we're over user456's limit this hour
   TrackingService.trackPage(user456, "/my_account/login/")
end

Override Limit Definitions Try it

When we lookup a limit we find the closest match. That means you can do handy things to override specific use cases. Let's say you want to limit firing usage events to once per event type per user per hour. That's great, but say there is a specific event that you only want to "debounce" and you'd like to fire no matter how many times it happens in an hour. Let's see how to accomplish that:

First create a limit with group: pageload, HOURLY_ROLLING and amount:1.

Then create a limit with group: pageload:/order_placed, SECONDLY_ROLLING and amount:1.

client = Prefab::Client.new
ratelimit_client = client.ratelimit_client
ratelimit_client.upsert("pageload", :HOURLY_ROLLING, 1, 1)
ratelimit_client.upsert("pageload:/order_placed", :SECONDLY_ROLLING, 1, 1)

if ratelimit_client.pass?("pageload:/my_account/login/:user12312")
   # this will fire 1 / hour
   TrackingService.trackPage(user12312, "/my_account/login/")
end

if ratelimit_client.pass?("pageload:/order_placed:user456")
   # this will match the more specific limit definition and fire 1/sec
   TrackingService.trackPage(user456, "/order_placed")
end

As you can see in this example "closeness" is defined by the number of matching group sections, where : is the section delimitter.

Token Buckets & 'Burst' Try it

Rate limits are built on a "token bucket" system. For each key you send us we keep a "bucket" full of tokens. When you ask for a token we give you one if there is one available. We "refill" the bucket by amount tokens every policy_name. "Burst" is equivalent to how big the bucket is.

client = Prefab::Client.new
ratelimit_client = client.ratelimit_client
ratelimit_client.upsert("one_per_hour", :HOURLY_ROLLING, 1)
ratelimit_client.upsert("one_per_hour_but_start_with_10", :HOURLY_ROLLING, 1, burst: 10)