limits

package
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: AGPL-3.0 Imports: 3 Imported by: 0

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

View Source
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.

View Source
const (
	// OutstandingJobsSchemaVersion identifies the only outstanding job schema.
	OutstandingJobsSchemaVersion = 1
	// OutstandingJobsPrefix is the outstanding job v1 namespace.
	OutstandingJobsPrefix = "limits:v1:outstanding_jobs:"
)
View Source
const (
	// StoredBytesSchemaVersion identifies the only stored byte schema.
	StoredBytesSchemaVersion = 1
	// StoredBytesPrefix is the stored byte v1 namespace.
	StoredBytesPrefix = "limits:v1:stored_bytes:"
)

Variables

View Source
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")
)
View Source
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")
)
View Source
var ErrSpendLimitExceeded = errors.New("spend limit exceeded")

ErrSpendLimitExceeded reports work a holder's spend budget cannot pay for.

View Source
var ErrStorageFull = errors.New("stored bytes limit exceeded")

ErrStorageFull reports a reservation that would put a holder past its stored byte bound.

View Source
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

func ContextWithAllowance(ctx context.Context, allowance Allowance) context.Context

ContextWithAllowance carries one holder's remaining spend into the request.

func ValidInterval

func ValidInterval(interval string) bool

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

func AllowanceFromContext(ctx context.Context) Allowance

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

func (a Allowance) Covers(nanoUSD int64) error

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 Budget

type Budget struct {
	Limit    int64  `json:"limit"`
	Interval string `json:"interval"`
}

Budget bounds one consumption dimension inside one fixed UTC interval.

type BudgetRule

type BudgetRule struct {
	Scope  Scope
	Budget Budget
}

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 Dimension

type Dimension string

Dimension names which consumption budget a rule meters.

const (
	// DimensionSpend meters integer nano-USD spend.
	DimensionSpend Dimension = "spend"
	// DimensionTokens meters total token consumption.
	DimensionTokens Dimension = "token"
)

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

func NewJobMeter(counter Counter) (*JobMeter, error)

NewJobMeter builds a meter over an atomic counter.

func (*JobMeter) Release

func (m *JobMeter) Release(ctx context.Context, holder string, count int64) error

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.

func (*JobMeter) Reserve

func (m *JobMeter) Reserve(ctx context.Context, holder string, count, bound int64) error

Reserve claims count job slots for the holder and reports whether the claim fits inside bound. A bound of zero or less leaves the holder unbounded.

func (*JobMeter) Total

func (m *JobMeter) Total(ctx context.Context, holder string) (int64, error)

Total reports how many jobs this holder currently has outstanding.

type LevelRule

type LevelRule struct {
	Scope Scope
	Limit int64
}

LevelRule is one level bound a call must satisfy, and the holder that set it.

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.

func (*Limits) Clone

func (l *Limits) Clone() *Limits

Clone returns a deep copy of the limits.

func (*Limits) IsZero

func (l *Limits) IsZero() bool

IsZero reports whether no limit dimension is set.

func (*Limits) Validate

func (l *Limits) Validate() error

Validate checks the limits invariants.

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

type RequestLimit struct {
	Limit         int64 `json:"limit"`
	WindowSeconds int64 `json:"window_seconds"`
}

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 (
	// ScopeAccount is an account-wide limit. It meters every key the account
	// holds, so it is the operator's cap on what the account may spend.
	ScopeAccount Scope = "account"
	// ScopeKey is one gateway API key's own limit.
	ScopeKey Scope = "key"
)
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

func (m *StorageMeter) Release(ctx context.Context, holder string, size int64) error

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.

func (*StorageMeter) Reserve

func (m *StorageMeter) Reserve(ctx context.Context, holder string, size, bound int64) error

Reserve claims size bytes for the holder and reports whether the claim fits inside bound. A bound of zero or less leaves the holder unbounded.

func (*StorageMeter) Total

func (m *StorageMeter) Total(ctx context.Context, holder string) (int64, error)

Total reports the bytes this holder currently keeps.

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

type TeamBudget struct {
	Limit    int64  `json:"limit"`
	Interval string `json:"interval"`
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL