Skip to content

Commit

Permalink
Fixed issue with reservoir refresh, lowered heartbeat for local datas…
Browse files Browse the repository at this point in the history
…tore
  • Loading branch information
Simon Grondin committed Sep 27, 2018
1 parent a33536a commit fc83f0f
Show file tree
Hide file tree
Showing 13 changed files with 68 additions and 36 deletions.
15 changes: 5 additions & 10 deletions bottleneck.js
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
Bottleneck.prototype.localStoreDefaults = {
Promise: Promise,
timeout: null,
heartbeatInterval: 5000
heartbeatInterval: 250
};

Bottleneck.prototype.redisStoreDefaults = {
Expand Down Expand Up @@ -1040,8 +1040,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
this.instance = instance;
this.storeOptions = storeOptions;
parser.load(storeInstanceOptions, storeInstanceOptions, this);
this._nextRequest = Date.now();
this._lastReservoirRefresh = null;
this._nextRequest = this._lastReservoirRefresh = Date.now();
this._running = 0;
this._done = 0;
this._unblockTime = 0;
Expand All @@ -1051,7 +1050,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
var now, reservoirRefreshActive;
now = Date.now();
reservoirRefreshActive = this.storeOptions.reservoirRefreshInterval != null && this.storeOptions.reservoirRefreshAmount != null;
if (reservoirRefreshActive && (this._lastReservoirRefresh == null || now >= this._lastReservoirRefresh + this.storeOptions.reservoirRefreshInterval)) {
if (reservoirRefreshActive && now >= this._lastReservoirRefresh + this.storeOptions.reservoirRefreshInterval) {
this.storeOptions.reservoir = this.storeOptions.reservoirRefreshAmount;
this._lastReservoirRefresh = now;
return this.instance._drainAll(this.computeCapacity());
Expand Down Expand Up @@ -1564,10 +1563,6 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
var args;
args = this.prepareObject(Object.assign({}, this.storeOptions, {
id: this.originalId,
nextRequest: 0, // set to now by init.lua due to retries
running: 0,
done: 0,
unblockTime: 0,
version: this.instance.version,
groupTimeout: this.timeout
}));
Expand Down Expand Up @@ -1982,8 +1977,8 @@ module.exports={
"group_check.lua": "local settings_key = KEYS[1]\n\nlocal now = tonumber(ARGV[1])\n\nreturn not (redis.call('exists', settings_key) == 1)\n",
"heartbeat.lua": "local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal now = tonumber(ARGV[1])\n\nrefresh_capacity(executing_key, running_key, settings_key, now, false)\n",
"increment_reservoir.lua": "local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal now = tonumber(ARGV[1])\nlocal incr = tonumber(ARGV[2])\n\nredis.call('hincrby', settings_key, 'reservoir', incr)\n\nlocal reservoir = refresh_capacity(executing_key, running_key, settings_key, now, true)[3]\n\nlocal groupTimeout = tonumber(redis.call('hget', settings_key, 'groupTimeout'))\nrefresh_expiration(executing_key, running_key, settings_key, 0, 0, groupTimeout)\n\nreturn reservoir\n",
"init.lua": "local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal now = tonumber(ARGV[1])\nlocal clear = tonumber(ARGV[2])\nlocal limiter_version = ARGV[3]\n\nif clear == 1 then\n redis.call('del', settings_key, running_key, executing_key)\nend\n\nif redis.call('exists', settings_key) == 0 then\n -- Create\n local args = {'hmset', settings_key}\n\n for i = 4, #ARGV do\n table.insert(args, ARGV[i])\n end\n\n redis.call(unpack(args))\n redis.call('hset', settings_key, 'nextRequest', now)\nelse\n\n -- Apply migrations\n local current_version = redis.call('hget', settings_key, 'version')\n if current_version ~= limiter_version then\n local version_digits = {}\n for k, v in string.gmatch(current_version, \"([^.]+)\") do\n table.insert(version_digits, tonumber(k))\n end\n\n -- 2.10.0\n if version_digits[2] <= 9 then\n redis.call('hsetnx', settings_key, 'reservoirRefreshInterval', '')\n redis.call('hsetnx', settings_key, 'reservoirRefreshAmount', '')\n redis.call('hsetnx', settings_key, 'lastReservoirRefresh', '')\n redis.call('hsetnx', settings_key, 'done', 0)\n redis.call('hmset', settings_key, 'version', '2.10.0')\n end\n end\n\n refresh_capacity(executing_key, running_key, settings_key, now, false)\nend\n\nlocal groupTimeout = tonumber(redis.call('hget', settings_key, 'groupTimeout'))\nrefresh_expiration(executing_key, running_key, settings_key, 0, 0, groupTimeout)\n\nreturn {}\n",
"refresh_capacity.lua": "local refresh_capacity = function (executing_key, running_key, settings_key, now, always_publish)\n\n local compute_capacity = function (maxConcurrent, running, reservoir)\n if maxConcurrent ~= nil and reservoir ~= nil then\n return math.min((maxConcurrent - running), reservoir)\n elseif maxConcurrent ~= nil then\n return maxConcurrent - running\n elseif reservoir ~= nil then\n return reservoir\n else\n return nil\n end\n end\n\n local settings = redis.call('hmget', settings_key,\n 'id',\n 'maxConcurrent',\n 'running',\n 'reservoir',\n 'reservoirRefreshInterval',\n 'reservoirRefreshAmount',\n 'lastReservoirRefresh'\n )\n local id = settings[1]\n local maxConcurrent = tonumber(settings[2])\n local running = tonumber(settings[3])\n local reservoir = tonumber(settings[4])\n local reservoirRefreshInterval = tonumber(settings[5])\n local reservoirRefreshAmount = tonumber(settings[6])\n local lastReservoirRefresh = tonumber(settings[7])\n\n local initial_capacity = compute_capacity(maxConcurrent, running, reservoir)\n\n --\n -- Compute 'running' changes\n --\n local expired = redis.call('zrangebyscore', executing_key, '-inf', '('..now)\n\n if #expired > 0 then\n redis.call('zremrangebyscore', executing_key, '-inf', '('..now)\n\n local make_batch = function ()\n return {'hmget', running_key}\n end\n\n local flush_batch = function (batch)\n local weights = redis.call(unpack(batch))\n batch[1] = 'hdel'\n local deleted = redis.call(unpack(batch))\n\n local sum = 0\n for i = 1, #weights do\n sum = sum + (tonumber(weights[i]) or 0)\n end\n return sum\n end\n\n local total = 0\n local batch_size = 1000\n\n for i = 1, #expired, batch_size do\n local batch = make_batch()\n for j = i, math.min(i + batch_size - 1, #expired) do\n table.insert(batch, expired[j])\n end\n total = total + flush_batch(batch)\n end\n\n if total > 0 then\n redis.call('hincrby', settings_key, 'done', total)\n running = tonumber(redis.call('hincrby', settings_key, 'running', -total))\n end\n end\n\n --\n -- Compute 'reservoir' changes\n --\n local reservoirRefreshActive = reservoirRefreshInterval ~= nil and reservoirRefreshAmount ~= nil\n if reservoirRefreshActive and (lastReservoirRefresh == nil or now >= lastReservoirRefresh + reservoirRefreshInterval) then\n reservoir = reservoirRefreshAmount\n redis.call('hmset', settings_key,\n 'reservoir', reservoir,\n 'lastReservoirRefresh', now\n )\n end\n\n --\n -- Broadcast capacity changes\n --\n local final_capacity = compute_capacity(maxConcurrent, running, reservoir)\n\n if always_publish or (\n -- was not unlimited, now unlimited\n initial_capacity ~= nil and final_capacity == nil\n ) or (\n -- capacity was increased\n initial_capacity ~= nil and final_capacity ~= nil and final_capacity > initial_capacity\n ) then\n redis.call('publish', 'b_'..id, 'capacity:'..final_capacity)\n end\n\n return {final_capacity, running, reservoir}\nend\n",
"init.lua": "local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal now = tonumber(ARGV[1])\nlocal clear = tonumber(ARGV[2])\nlocal limiter_version = ARGV[3]\n\nif clear == 1 then\n redis.call('del', settings_key, running_key, executing_key)\nend\n\nif redis.call('exists', settings_key) == 0 then\n -- Create\n local args = {'hmset', settings_key}\n\n for i = 4, #ARGV do\n table.insert(args, ARGV[i])\n end\n\n redis.call(unpack(args))\n redis.call('hmset', settings_key,\n 'nextRequest', now,\n 'lastReservoirRefresh', now,\n 'running', 0,\n 'done', 0,\n 'unblockTime', 0\n )\n\nelse\n -- Apply migrations\n local current_version = redis.call('hget', settings_key, 'version')\n if current_version ~= limiter_version then\n local version_digits = {}\n for k, v in string.gmatch(current_version, \"([^.]+)\") do\n table.insert(version_digits, tonumber(k))\n end\n\n -- 2.10.0\n if version_digits[2] < 10 then\n redis.call('hsetnx', settings_key, 'reservoirRefreshInterval', '')\n redis.call('hsetnx', settings_key, 'reservoirRefreshAmount', '')\n redis.call('hsetnx', settings_key, 'lastReservoirRefresh', '')\n redis.call('hsetnx', settings_key, 'done', 0)\n redis.call('hset', settings_key, 'version', '2.10.0')\n end\n\n -- 2.11.1\n if version_digits[2] < 11 and version_digits[3] < 1 then\n if redis.call('hstrlen', settings_key, 'lastReservoirRefresh') == 0 then\n redis.call('hmset', settings_key,\n 'lastReservoirRefresh', now,\n 'version', '2.11.1'\n )\n end\n end\n end\n\n refresh_capacity(executing_key, running_key, settings_key, now, false)\nend\n\nlocal groupTimeout = tonumber(redis.call('hget', settings_key, 'groupTimeout'))\nrefresh_expiration(executing_key, running_key, settings_key, 0, 0, groupTimeout)\n\nreturn {}\n",
"refresh_capacity.lua": "local refresh_capacity = function (executing_key, running_key, settings_key, now, always_publish)\n\n local compute_capacity = function (maxConcurrent, running, reservoir)\n if maxConcurrent ~= nil and reservoir ~= nil then\n return math.min((maxConcurrent - running), reservoir)\n elseif maxConcurrent ~= nil then\n return maxConcurrent - running\n elseif reservoir ~= nil then\n return reservoir\n else\n return nil\n end\n end\n\n local settings = redis.call('hmget', settings_key,\n 'id',\n 'maxConcurrent',\n 'running',\n 'reservoir',\n 'reservoirRefreshInterval',\n 'reservoirRefreshAmount',\n 'lastReservoirRefresh'\n )\n local id = settings[1]\n local maxConcurrent = tonumber(settings[2])\n local running = tonumber(settings[3])\n local reservoir = tonumber(settings[4])\n local reservoirRefreshInterval = tonumber(settings[5])\n local reservoirRefreshAmount = tonumber(settings[6])\n local lastReservoirRefresh = tonumber(settings[7])\n\n local initial_capacity = compute_capacity(maxConcurrent, running, reservoir)\n\n --\n -- Compute 'running' changes\n --\n local expired = redis.call('zrangebyscore', executing_key, '-inf', '('..now)\n\n if #expired > 0 then\n redis.call('zremrangebyscore', executing_key, '-inf', '('..now)\n\n local make_batch = function ()\n return {'hmget', running_key}\n end\n\n local flush_batch = function (batch)\n local weights = redis.call(unpack(batch))\n batch[1] = 'hdel'\n local deleted = redis.call(unpack(batch))\n\n local sum = 0\n for i = 1, #weights do\n sum = sum + (tonumber(weights[i]) or 0)\n end\n return sum\n end\n\n local total = 0\n local batch_size = 1000\n\n for i = 1, #expired, batch_size do\n local batch = make_batch()\n for j = i, math.min(i + batch_size - 1, #expired) do\n table.insert(batch, expired[j])\n end\n total = total + flush_batch(batch)\n end\n\n if total > 0 then\n redis.call('hincrby', settings_key, 'done', total)\n running = tonumber(redis.call('hincrby', settings_key, 'running', -total))\n end\n end\n\n --\n -- Compute 'reservoir' changes\n --\n local reservoirRefreshActive = reservoirRefreshInterval ~= nil and reservoirRefreshAmount ~= nil\n if reservoirRefreshActive and now >= lastReservoirRefresh + reservoirRefreshInterval then\n reservoir = reservoirRefreshAmount\n redis.call('hmset', settings_key,\n 'reservoir', reservoir,\n 'lastReservoirRefresh', now\n )\n end\n\n --\n -- Broadcast capacity changes\n --\n local final_capacity = compute_capacity(maxConcurrent, running, reservoir)\n\n if always_publish or (\n -- was not unlimited, now unlimited\n initial_capacity ~= nil and final_capacity == nil\n ) or (\n -- capacity was increased\n initial_capacity ~= nil and final_capacity ~= nil and final_capacity > initial_capacity\n ) then\n redis.call('publish', 'b_'..id, 'capacity:'..final_capacity)\n end\n\n return {final_capacity, running, reservoir}\nend\n",
"refresh_expiration.lua": "local refresh_expiration = function (executing_key, running_key, settings_key, now, nextRequest, groupTimeout)\n\n if groupTimeout ~= nil then\n local ttl = (nextRequest + groupTimeout) - now\n\n redis.call('pexpire', executing_key, ttl)\n redis.call('pexpire', running_key, ttl)\n redis.call('pexpire', settings_key, ttl)\n end\n\nend\n",
"register.lua": "local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal now = tonumber(ARGV[1])\nlocal index = ARGV[2]\nlocal weight = tonumber(ARGV[3])\nlocal expiration = tonumber(ARGV[4])\n\nlocal state = refresh_capacity(executing_key, running_key, settings_key, now, false)\nlocal capacity = state[1]\nlocal reservoir = state[3]\n\nlocal settings = redis.call('hmget', settings_key,\n 'nextRequest',\n 'minTime',\n 'groupTimeout'\n)\nlocal nextRequest = tonumber(settings[1])\nlocal minTime = tonumber(settings[2])\nlocal groupTimeout = tonumber(settings[3])\n\nif conditions_check(capacity, weight) then\n\n if expiration ~= nil then\n redis.call('zadd', executing_key, now + expiration, index)\n end\n redis.call('hset', running_key, index, weight)\n redis.call('hincrby', settings_key, 'running', weight)\n\n local wait = math.max(nextRequest - now, 0)\n local newNextRequest = now + wait + minTime\n\n if reservoir == nil then\n redis.call('hset', settings_key,\n 'nextRequest', newNextRequest\n )\n else\n reservoir = reservoir - weight\n redis.call('hmset', settings_key,\n 'reservoir', reservoir,\n 'nextRequest', newNextRequest\n )\n end\n\n refresh_expiration(executing_key, running_key, settings_key, now, newNextRequest, groupTimeout)\n\n return {true, wait, reservoir}\n\nelse\n return {false}\nend\n",
"running.lua": "local settings_key = KEYS[1]\nlocal running_key = KEYS[2]\nlocal executing_key = KEYS[3]\n\nlocal now = tonumber(ARGV[1])\n\nreturn refresh_capacity(executing_key, running_key, settings_key, now, false)[2]\n",
Expand Down
2 changes: 1 addition & 1 deletion bottleneck.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/Bottleneck.js
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
Bottleneck.prototype.localStoreDefaults = {
Promise: Promise,
timeout: null,
heartbeatInterval: 5000
heartbeatInterval: 250
};

Bottleneck.prototype.redisStoreDefaults = {
Expand Down
5 changes: 2 additions & 3 deletions lib/LocalDatastore.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
this.instance = instance;
this.storeOptions = storeOptions;
parser.load(storeInstanceOptions, storeInstanceOptions, this);
this._nextRequest = Date.now();
this._lastReservoirRefresh = null;
this._nextRequest = this._lastReservoirRefresh = Date.now();
this._running = 0;
this._done = 0;
this._unblockTime = 0;
Expand All @@ -26,7 +25,7 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
var now, reservoirRefreshActive;
now = Date.now();
reservoirRefreshActive = this.storeOptions.reservoirRefreshInterval != null && this.storeOptions.reservoirRefreshAmount != null;
if (reservoirRefreshActive && (this._lastReservoirRefresh == null || now >= this._lastReservoirRefresh + this.storeOptions.reservoirRefreshInterval)) {
if (reservoirRefreshActive && now >= this._lastReservoirRefresh + this.storeOptions.reservoirRefreshInterval) {
this.storeOptions.reservoir = this.storeOptions.reservoirRefreshAmount;
this._lastReservoirRefresh = now;
return this.instance._drainAll(this.computeCapacity());
Expand Down
4 changes: 0 additions & 4 deletions lib/RedisDatastore.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,6 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a
var args;
args = this.prepareObject(Object.assign({}, this.storeOptions, {
id: this.originalId,
nextRequest: 0, // set to now by init.lua due to retries
running: 0,
done: 0,
unblockTime: 0,
version: this.instance.version,
groupTimeout: this.timeout
}));
Expand Down
Loading

0 comments on commit fc83f0f

Please sign in to comment.