diff --git a/lib/base.js b/lib/base.js index 7e60fff5..00861b75 100644 --- a/lib/base.js +++ b/lib/base.js @@ -18,7 +18,7 @@ var Base = Class.extend({ }, runAction: function(method, path, queryParams, bodyData, callback) { - runAction(this, method, path, queryParams, bodyData, callback); + runAction(this, method, path, queryParams, bodyData, callback, 0); }, _checkStatusForError: function(statusCode, body) { diff --git a/lib/internal_config.json b/lib/internal_config.json index 79c18a7f..93d3bca0 100644 --- a/lib/internal_config.json +++ b/lib/internal_config.json @@ -1,3 +1,4 @@ { - "RETRY_DELAY_IF_RATE_LIMITED": 5000 + "INITIAL_RETRY_DELAY_IF_RATE_LIMITED": 5000, + "MAX_RETRY_DELAY_IF_RATE_LIMITED": 600000 } diff --git a/lib/run_action.js b/lib/run_action.js index e431a057..f349ac03 100644 --- a/lib/run_action.js +++ b/lib/run_action.js @@ -6,7 +6,15 @@ var objectToQueryParamString = require('./object_to_query_param_string'); // This will become require('xhr') in the browser. var request = require('request'); -function runAction(base, method, path, queryParams, bodyData, callback) { +// "Full Jitter" algorithm taken from https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ +function exponentialBackoffWithJitter(numberOfRetries, initialBackoffTimeMs, maxBackoffTimeMs) { + var rawBackoffTimeMs = initialBackoffTimeMs * Math.pow(2, numberOfRetries); + var clippedBackoffTimeMs = Math.min(maxBackoffTimeMs, rawBackoffTimeMs); + var jitteredBackoffTimeMs = Math.random() * clippedBackoffTimeMs; + return jitteredBackoffTimeMs; +} + +function runAction(base, method, path, queryParams, bodyData, callback, numAttempts) { var url = base._airtable._endpointUrl + '/v' + base._airtable._apiVersionMajor + '/' + base._id + path + '?' + objectToQueryParamString(queryParams); var headers = { @@ -49,9 +57,10 @@ function runAction(base, method, path, queryParams, bodyData, callback) { } if (resp.statusCode === 429 && !base._airtable._noRetryIfRateLimited) { + var backoffDelayMs = exponentialBackoffWithJitter(numAttempts, internalConfig.INITIAL_RETRY_DELAY_IF_RATE_LIMITED, internalConfig.MAX_RETRY_DELAY_IF_RATE_LIMITED); setTimeout(function() { - runAction(base, method, path, queryParams, bodyData, callback); - }, internalConfig.RETRY_DELAY_IF_RATE_LIMITED); + runAction(base, method, path, queryParams, bodyData, callback, numAttempts + 1); + }, backoffDelayMs); return; }