Documentation
¶
Overview ¶
Package limits owns the request-rate and consumption vocabulary that bounds what a holder may spend. Both a gateway API key and an account hold limits, so the vocabulary lives here rather than inside either owner.
Index ¶
- Constants
- Variables
- func ContextWithAllowance(ctx context.Context, allowance Allowance) context.Context
- func ValidInterval(interval string) bool
- type Allowance
- type Budget
- type BudgetRule
- type Counter
- type Dimension
- type JobMeter
- type LevelRule
- type Limits
- type OutstandingJobsRule
- type RequestLimit
- type RequestRule
- type Scope
- type StorageMeter
- type StoredBytesRule
- type TeamBudget
Constants ¶
const ( // IntervalDay selects the fixed UTC day window. IntervalDay = "day" // IntervalWeek selects the fixed UTC ISO week window. IntervalWeek = "week" // IntervalMonth selects the fixed UTC month window. IntervalMonth = "month" )
Budget interval names. The values match the fixed UTC aggregation intervals that the usage seam maintains, so a stored budget interval reads one exact aggregate window.
const ( // OutstandingJobsSchemaVersion identifies the only outstanding job schema. OutstandingJobsSchemaVersion = 1 // OutstandingJobsPrefix is the outstanding job v1 namespace. OutstandingJobsPrefix = "limits:v1:outstanding_jobs:" )
const ( // StoredBytesSchemaVersion identifies the only stored byte schema. StoredBytesSchemaVersion = 1 // StoredBytesPrefix is the stored byte v1 namespace. StoredBytesPrefix = "limits:v1:stored_bytes:" )
Variables ¶
var ( // ErrCounterRequired reports a meter built without an atomic counter. ErrCounterRequired = errors.New("level counter is required") // ErrInvalidHolder reports an empty holder identity. ErrInvalidHolder = errors.New("level holder is required") )
var ( // ErrInvalidRequestLimit reports a non-positive request limit. ErrInvalidRequestLimit = errors.New("request limit must be positive") // ErrInvalidRequestWindow reports a non-positive request window. ErrInvalidRequestWindow = errors.New("request window seconds must be positive") // ErrInvalidBudgetLimit reports a non-positive budget limit. ErrInvalidBudgetLimit = errors.New("budget limit must be positive") // ErrInvalidBudgetInterval reports an unknown budget interval. ErrInvalidBudgetInterval = errors.New("budget interval must be day, week, or month") // ErrInvalidStoredBytes reports a non-positive stored byte bound. ErrInvalidStoredBytes = errors.New("stored bytes limit must be positive") // ErrInvalidOutstandingJobs reports a non-positive outstanding job bound. ErrInvalidOutstandingJobs = errors.New("outstanding jobs limit must be positive") )
var ErrSpendLimitExceeded = errors.New("spend limit exceeded")
ErrSpendLimitExceeded reports work a holder's spend budget cannot pay for.
var ErrStorageFull = errors.New("stored bytes limit exceeded")
ErrStorageFull reports a reservation that would put a holder past its stored byte bound.
var ErrTooManyOutstandingJobs = errors.New("outstanding job limit exceeded")
ErrTooManyOutstandingJobs reports a submission that would put a holder past its outstanding job bound.
Functions ¶
func ContextWithAllowance ¶
ContextWithAllowance carries one holder's remaining spend into the request.
func ValidInterval ¶
ValidInterval reports whether interval names a supported budget window.
Types ¶
type Allowance ¶
type Allowance struct {
// NanoUSD is what remains in the window. It is meaningful only when
// Bounded is true.
NanoUSD int64
// Bounded reports that a spend budget applies to this holder.
Bounded bool
}
Allowance is what one holder may still spend in its current budget window, in integer nano-USD.
An unbounded allowance is the normal case: most deployments set no spend budget at all, and a holder without one is not metered against anything. Bounded says which of the two a zero means, because a holder with nothing left and a holder with no budget are opposite answers.
func AllowanceFromContext ¶
AllowanceFromContext reads the remaining spend the budget gate recorded.
A request that never passed the gate reads as unbounded. That is the honest answer rather than a permissive default: no budget was found, so no budget refuses anything.
func (Allowance) Covers ¶
Covers reports whether the allowance pays for work priced at nanoUSD.
It refuses the work that crosses the bound rather than the work after it. A caller at the door is refused on a window already spent, which lets the crossing request through; here the price is known before the money is spent, so refusing first is both possible and cheaper for the account.
type BudgetRule ¶
BudgetRule is one consumption meter a request must satisfy.
func BudgetRules ¶
func BudgetRules(accountLimits, keyLimits *Limits, dimension Dimension) []BudgetRule
BudgetRules returns every consumption meter one request must satisfy for one dimension, account before key.
func TeamBudgetRule ¶ added in v1.2.0
func TeamBudgetRule(budget *TeamBudget) (BudgetRule, bool)
TeamBudgetRule projects a stored team budget into the consumption rule the enforcement path runs beside the account and key rules. It reports false when the team sets no budget.
type Counter ¶
type Counter interface {
Increment(ctx context.Context, key string, delta int64) (int64, error)
Decrement(ctx context.Context, key string, delta int64) (int64, error)
}
Counter is the atomic counter a level meter reserves against.
The limits package names the primitive it needs rather than importing a store, because the limit vocabulary stays a leaf. Any counter whose increment and decrement are atomic against concurrent callers satisfies it, and the durable key-value store already does.
type JobMeter ¶
type JobMeter struct {
// contains filtered or unexported fields
}
JobMeter bounds how many jobs one holder may have running at a time.
An outstanding job is a spend commitment this gateway has already made to a provider and cannot read yet. Every other limit meters something that is already over: a request that returned, or bytes that are already stored. This one meters work in flight, which is the only bound that can refuse a caller before the provider bills for it.
It repeats the shape of StorageMeter deliberately. Both wrap the same level meter, and merging them into one type would make a byte budget and a job budget interchangeable at every call site that takes either. They already satisfy the same structural interface elsewhere, so a single type would let a deployment count videos against its stored byte bound and compile.
func NewJobMeter ¶
NewJobMeter builds a meter over an atomic counter.
func (*JobMeter) Release ¶
Release gives count job slots back to the holder. One job frees its slot when it reaches a terminal state, and a submission that never reached a provider frees the slot it claimed.
type Limits ¶
type Limits struct {
// Requests overrides the global request window for this holder.
Requests *RequestLimit `json:"requests,omitempty"`
// Spend bounds integer nano-USD spend inside one fixed UTC interval.
Spend *Budget `json:"spend,omitempty"`
// Tokens bounds total token consumption inside one fixed UTC interval.
Tokens *Budget `json:"tokens,omitempty"`
// StoredBytes bounds how many bytes this holder keeps in file storage at
// one time. It is a level rather than a rate, so no interval resets it: a
// write raises the total and a delete lowers it.
StoredBytes *int64 `json:"stored_bytes,omitempty"`
// OutstandingJobs bounds how many jobs this holder may have running at
// one time. Like StoredBytes it is a level rather than a rate, and unlike
// every other limit here it meters work that has not finished: a submitted
// job is a spend commitment this gateway cannot read yet.
OutstandingJobs *int64 `json:"outstanding_jobs,omitempty"`
}
Limits carries the request-rate override and the consumption budgets of one holder. A nil field leaves that dimension unlimited by this holder.
type OutstandingJobsRule ¶
type OutstandingJobsRule = LevelRule
OutstandingJobsRule is one outstanding job bound a submission must satisfy.
func TightestOutstandingJobs ¶
func TightestOutstandingJobs(accountLimits, keyLimits *Limits) (OutstandingJobsRule, bool)
TightestOutstandingJobs reports the outstanding job bound a submission must satisfy, and whether one applies at all.
It resolves the same way stored bytes do, and for the same reason. A job belongs to an account, so both bounds read one counter and the smaller of them satisfies the larger. A key bound is an operator asking that this key not fill the account's queue on its own.
type RequestLimit ¶
RequestLimit is one request-rate override.
type RequestRule ¶
type RequestRule struct {
Scope Scope
Limit RequestLimit
}
RequestRule is one request-rate meter a request must satisfy.
func RequestRules ¶
func RequestRules(accountLimits, keyLimits *Limits, deploymentDefault *RequestLimit) []RequestRule
RequestRules returns every request-rate meter one request must satisfy, account before key. deploymentDefault is the gateway's global window; it applies at key scope only when the key sets no request limit of its own, because an explicit key limit is admin intent about that key. Pass nil when the deployment has no global window.
type Scope ¶
type Scope string
Scope names the holder that set a limit.
const ScopeTeam Scope = "team"
ScopeTeam is a team-wide limit. It meters every key attributed to the team.
type StorageMeter ¶
type StorageMeter struct {
// contains filtered or unexported fields
}
StorageMeter bounds how many bytes one holder keeps at a time.
Stored bytes are a level, not a rate. Nothing resets the total at an interval boundary, so the meter tracks a standing amount that a write raises and a delete lowers.
func NewStorageMeter ¶
func NewStorageMeter(counter Counter) (*StorageMeter, error)
NewStorageMeter builds a meter over an atomic counter.
func (*StorageMeter) Release ¶
Release gives size bytes back to the holder. The caller uses it for a write that failed and for a delete that removed bytes the meter had counted.
type StoredBytesRule ¶
type StoredBytesRule = LevelRule
StoredBytesRule is one stored byte bound an upload must satisfy.
func TightestStoredBytes ¶
func TightestStoredBytes(accountLimits, keyLimits *Limits) (StoredBytesRule, bool)
TightestStoredBytes reports the stored byte bound an upload must satisfy, and whether one applies at all.
Stored bytes resolve to the smaller of the two, which is the opposite of what RequestRules does above, and for a reason the shapes of the two meters give. A request rate meters two different populations, so both meters have to run. Stored bytes meter one: a file belongs to an account, and a key holds no bytes of its own. Both bounds therefore read the same counter, and running the smaller one satisfies the larger by arithmetic.
A key bound is an operator asking that this key not push the account past a tighter number than the account's own. The returned scope names which holder set the bound, so a refusal can name the owner an operator has to talk to.
type TeamBudget ¶ added in v1.2.0
TeamBudget bounds a team's consumption of one dimension inside one fixed UTC interval. It is a distinct named type rather than a bare Budget so the identity seam can carry it without importing the meaning of every other limit dimension a Limits carrier holds.
func (*TeamBudget) Clone ¶ added in v1.2.0
func (b *TeamBudget) Clone() *TeamBudget
Clone returns a deep copy of the team budget.
func (*TeamBudget) Validate ¶ added in v1.2.0
func (b *TeamBudget) Validate() error
Validate checks the team budget invariants.