# Hand-rolled token bucket on Upstash Redis with Lua

The SDKs token bucket may not fit: custom refill curves, per-tenant
burst budgets, or cost attribution per consume. A Lua script gives you
atomic check-and-consume in one round trip.

## The script

Store tokens, capacity, refill rate, and last timestamp in a hash per
identifier. The script computes elapsed time, refills, and consumes
atomically:

local key [your value]
local now = tonumber(ARGV[1])
local requested = tonumber(ARGV[2])
local data = redis.call('HMGET', key, 'tokens', 'ts')
local tokens = tonumber(data[1]) or CAPACITY
local ts = tonumber(data[2]) or now
local refill = math.floor((now - ts) / 1000 * REFILL_RATE)
tokens = math.min(CAPACITY, tokens + refill)
if tokens >= requested then
  redis.call('HSET', key, 'tokens', tokens - requested, 'ts', now)
  redis.call('PEXPIRE', key, 60000)
  return 1
else
  redis.call('HSET', key, 'tokens', tokens, 'ts', now)
  redis.call('PEXPIRE', key, 60000)
  return 0
end

Bake your CAPACITY and REFILL_RATE into the script or pass them as
ARGV. Keep PEXPIRE so idle buckets disappear instead of leaking
memory.

## Run it

Over REST, send EVAL with the script and keys. Over the SDK, use
eval with the same arguments. One network round trip, atomic
consume, no race between check and decrement.

## Cost

One EVAL plus the commands inside it, per check. That matches the
SDKs token-bucket cost profile (EVAL, HMGET, HSET, PEXPIRE) without
the SDKs opinion about refill math.

## Verify

Burst-test: fire 2x capacity in a tight loop and confirm exactly
capacity requests return 1. Wait one refill interval and confirm
tokens reappear.