github.com/rudderlabs/rudder-go-kit@v0.30.0/throttling/lua/gcra.lua (about) 1 --[[ 2 To debug this script you can add these entries here in the script and then check the Redis server output: 3 redis.log(redis.LOG_NOTICE, "some label", some_variable) 4 5 For more information please refer to: https://redis.io/docs/manual/programmability/lua-debugging/ 6 --]] 7 8 local rate_limit_key = KEYS[1] 9 local burst = ARGV[1] 10 local rate = ARGV[2] 11 local period = ARGV[3] * 1000 * 1000 -- converting to microseconds 12 local cost = tonumber(ARGV[4]) 13 local emission_interval = period / rate 14 local increment = emission_interval * cost 15 local burst_offset = emission_interval * burst 16 17 -- Redis returns time as an array containing two integers: seconds of the epoch 18 -- time (10 digits) and microseconds (~5-6 digits). for convenience we need to 19 -- convert them to a floating point number. The resulting number is 16 digits, 20 -- bordering on the limits of a 64-bit double-precision floating point number. 21 -- Adjust the epoch to be relative to Jan 1, 2017 00:00:00 GMT to avoid floating 22 -- point problems. This approach is good until "now" is 2,483,228,799 (Wed, 09 23 -- Sep 2048 01:46:39 GMT), when the adjusted value is 16 digits. 24 local jan_1_2017 = 1483228800 * 1000 * 1000 -- in microseconds precision 25 local current_time = redis.call("TIME") 26 local microseconds = current_time[2] 27 while string.len(microseconds) < 6 do 28 -- in case the microseconds part (i.e. current_time[2]) is less than 6 digits 29 microseconds = "0" .. microseconds 30 end 31 32 local current_time_micro = tonumber(current_time[1] .. microseconds) 33 current_time = current_time_micro - jan_1_2017 34 35 local tat = redis.call("GET", rate_limit_key) 36 if not tat then 37 tat = current_time 38 else 39 tat = tonumber(tat) 40 end 41 tat = math.max(tat, current_time) 42 43 local new_tat = tat + increment 44 local allow_at = new_tat - burst_offset 45 local diff = current_time - allow_at 46 local remaining = diff / emission_interval 47 if remaining < 0 then 48 local reset_after = tat - current_time 49 local retry_after = diff * -1 50 return { 51 current_time_micro, 52 0, -- allowed 53 0, -- remaining 54 tonumber(retry_after), 55 tonumber(reset_after), 56 } 57 end 58 59 local reset_after = new_tat - current_time 60 if reset_after > 0 then 61 redis.call("SET", rate_limit_key, new_tat, "EX", math.ceil(reset_after)) 62 end 63 64 local retry_after = -1 65 return { current_time_micro, cost, remaining, tonumber(retry_after), tonumber(reset_after) }