Throttling Lookups

Throttle the number of lookups the `doLookup` method can make

A common concern when developing integrations is throttling the number of lookups an integration makes in a given time interval. We can add this functionality directly to an integration. The following guide walks through how to add basic throttling to your integration. To start you will need to define two constants to your integration.js file (or your main integration module if you are not using the default naming conventions).

At the top of your integration file add the following global constants:

integration.js
 // throttle interval in milliseconds
const THROTTLE_INTERVAL = 5000;
// number of lookups allowed within the THROTTLE_INTERVAL
const THROTTLE_MAX_REQUESTS = 2;

The THROTTLE_MAX_REQUESTS constant is the maximum number of times you want your lookup or function to excecute within the given THROTTLE_INTERVAL. Keep in mind that the THROTTLE_INTERVAL is specified in milliseconds. As an example, if you want your lookup routine to be called no more than once a second you could set the following values:

integration.js
const THROTTLE_INTERVAL = 1000;
const THROTTLE_MAX_REQUESTS = 1;

In addition to the two global constants we will need two more global variables that will track our throttling state. You can add the following right below the throttle constants we defined above:

integration.js
// Tracks the number of lookups we have made within the throttle window
let numLookupsInThrottleWindow = 0;
// Tracks the last time we started a new throttle window
let lastThrottleWindowStartTime = Date.now();

The variables numLookupsInThrottleWindow and lastThrottleWindowStartTime are used to track the current state of our throttling. These are not values you should configure or modify.

Next we will add our throttle function into our integration.js file.

integration.js
/**
 * Executes the `execFunc` if we have not reached our throttling limit, 
 * otherwise executes the `throttledCb` callback
 * 
 * @param execFunc
 * @param throttledCb
 */
function throttle(execFunc, throttledCb) {
    // If we are past our throttle interval then we can reset the throttle window start time
    // as well as the number of lookups we have made during the window.
    if(Date.now() - lastThrottleWindowStartTime > THROTTLE_INTERVAL){
        numLookupsInThrottleWindow = 0;
        lastThrottleWindowStartTime = Date.now();
    }

    // As long as the number of lookups we have made during the window is less
    // than the maximum allowed execute the `execFunc`.
    if(numLookupsInThrottleWindow < THROTTLE_MAX_REQUESTS){
        numLookupsInThrottleWindow++;
        execFunc();
    }else{
        // Reached our throttle limit so just call the throttled callback
        throttledCb(null);
    }
}

The above function takes two parameters. The first parameter is the function we want to throttle. The second parameter is a callback that is executed in the event that we have reached our throttle maximum. As an example, if we wanted to throttle a function called _lookupIP we could set it up like this:

integration.js
throttle(function(){
    _lookupIP(entity);
}, function(){
    // this callback is executed if the lookup was throttled
})

The more typical case is that our lookup function is executed inside an async.each loop. In that case your code might look like this:

integration.js
function doLookup(entities, options, cb) {
    let lookupResults = [];

    async.each(entities, function (entityObj, next) {
        if (entityObj.isIPv4) {
            throttle(function(){
                // execute _lookupIP in a throttled manner
                _lookupIP(entityObj, options, function (err, result) {
                    if (err) {
                        next(err);
                    } else {
                        lookupResults.push(result);
                        next(null);
                    }
                });
            }, function(){
                // called if the _lookupIP method gets throttled
                // calling next(null) effectively skips this entity
                next(null);
            })
        } else {
            // This is not an IPv4 so we skip it
            next(null);
        }
    }, function (err) {
        // Return our results 
        cb(err, lookupResults);
    });
}

In the above example we throttle the _lookupIP method so that it only executes at most THROTTLE_MAX_REQUESTS within the time inteval THROTTLE_INTERVAL. If we reach the throttle limit then we simply ignore the entity to be looked up by making the call to next(null) thereby skipping over it within our async.each loop. You could also return an error message if you wanted to notify the user that their lookups are now being throttled.

integration.js
throttle(function(){
// execute _lookupIP in a throttled manner
    _lookupIP(entityObj, options, function (err, result) {
        // handle results
    }, function(){
        // called if the _lookupIP method gets throttled
        // In this example we are passing an error message back to the notification window
        next(`Your lookup for [${entityObj.value}] was throttled.`);
    })
});

Last updated