Hi,
I want to use Aerospike to cache ratelimit counters for my golang server app:
- request comes in
- increment counter, and only set expiration when the counter is created (not when it is updated)
- check result and abort when counter exceeds limit
- use different TTL’s for different keys, so the default namespace TTL is not used
I am able to do this in golang, but only with 2 roundtrips to the server (that pose an issue). Is there another way to do this with one roundtrip?
This is my working example code
func rateLimit(gin *Gin.Context, client *Aerospike.Client, limit uint32, seconds uint32) (bool, error) {
const setName = "limit"
const binName = "count"
// policies
policyDoNotTouchExpiration := Aerospike.NewWritePolicy(0, math.MaxUint32-1)
policyUpdateExpiration := Aerospike.NewWritePolicy(0, seconds)
// create key
keyname := fmt.Sprint(gin.ClientIP(), ":", gin.Request.Method, ":", gin.Request.URL.Path)
key, err := Aerospike.NewKey("default", setName, keyname)
if err != nil {
return false, err
}
record, err := client.Operate(policyDoNotTouchExpiration, key,
Aerospike.AddOp(Aerospike.NewBin(binName, 1)),
Aerospike.GetOp(),
)
if err != nil {
return false, err
}
count := uint32(record.Bins[binName].(int))
// only if record is created, adjust expiration
// TODO can this be done in one roundtrip?
if count == 1 {
if err := client.Touch(policyUpdateExpiration, key); err != nil {
return false, err
}
}
return count <= limit, nil
}
The problem with this approach is that when the Touch fails, the rate limiter would not work.
It would be great if the WritePolicy would contain some kind of ExpirationPolicy where I can set behaviour to set expiration only on record creation (and possibly some other edge cases).
Or, there is a much simpler solution for my problem that I am not aware of …
Thanks, Cheers,
Gerd