Skip to content

0xsequence/quotacontrol

Repository files navigation

Quota Control

Go Workflow Go Reference

This package implements a Quota Control - Rate Limiting service. It allows to limit the amount of compute units spent by projects.

Entities

  • AccessKey: a specific key used for Quota Control, it belongs to a project.
  • Limit: attribute of project, a series of limitations like quota and number of keys.
  • AccessUsage: the usage of certain project or key. It has 3 values:
    • valid: requests done using the "Free Limit" limit
    • over: requests over "Free Limit" and below "Hard Limit"
    • limited: request over "Hard Limit" which result in a failure (HTTP 429)
  • Cycle:a range of time where limits should be enforced (e.g. monthly, weekly)

Actors

There are two actors at play:

  • server: the main responsabilities are taking care of persisting records and preparing cache entries
  • client: it communicates with cache and server to retrieve record. It does it using a series of middlewares.
    • SetAccessKey: looks into a specific header (X-Access-Key) to set the access key.
    • VerifyAccessKey: fetches the data related to the access key (timeframe, limits, access key metadata) and verifies that the provided key is valid.
    • SpendUsage: tries to increment the usage counter in the cache and compares it to the existing limits.

The actors share information through the cache, and server is the one populating it. The only execption is the usage spending, which is done by the clients using an increment operation on the cache.

Configuration

The configuration for the service can be found here:

quotacontrol/config.go

Lines 19 to 35 in 062a68e

type Config struct {
Enabled bool `toml:"enabled"`
URL string `toml:"url"`
AccessKey string `toml:"access_key"`
UpdateFreq Duration `toml:"update_freq"`
RateLimiter RateLimiterConfig `toml:"rate_limiter"`
Redis redis.Config `toml:"redis"`
LRUSize int `toml:"lru_size"`
}
type RateLimiterConfig struct {
Enabled bool `toml:"enabled"`
PublicRequestsPerMinute int `toml:"public_requests_per_minute"`
UserRequestsPerMinute int `toml:"user_requests_per_minute"`
ServiceRequestsPerMinute int `toml:"service_requests_per_minute"`
ErrorMessage string `toml:"error_message"`
}

Service

The QuotaControlService server requires two storages: a cache and a permanent store.

type Cache struct {
QuotaCache
UsageCache
PermissionCache
}
type Store struct {
LimitStore
AccessKeyStore
UsageStore
CycleStore
PermissionStore
}

Each aspect of the cache and the storage can be re-implemented and customised to the project needs.

The package offers a Redis implementation for the cache, and a Memory version of the permanent store useful for testing.

The methods that are used to save/load in a permanent storage the 3 entities are not implemented. The requests are measure in compute units, if a compute unit is not specified it is assumed that the value it's 1. A client can specify the amount of compute units by manipulating the request context using the WithCost function.

Increment operation

The client method SpendCost takes care of doing an increment operation in the cache. And works as follows:

  • It tries to fetch the usage record from the cache
  • On a hit it executes the INCR operation.
  • On a miss it sets it to -1 and ask the server to populate it.
  • If it finds the value -1 it waits and retries
  • After a few retries it fails with a timeout.