sendrate

package
v1.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 1, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package sendrate enforces the per-agent fire-time submission budget — the durable, Postgres-backed counterpart of the acceptance-time in-memory send limit (internal/httpapi checkSendLimit, 60 submissions/min/agent). The outbound send worker (internal/outboundsend) reserves one slot immediately before each provider submission, so scheduled sends that accumulated as River jobs — and multi-replica deployments the in-memory limiter cannot coordinate — cannot burst past the advertised per-agent rate at the upstream provider.

Storage is one row per agent (migration 094) holding a sliding-window log of recent reservation timestamps. Reserve serializes on the row lock (SELECT ... FOR UPDATE), so the limit holds across replicas and concurrent workers without SKIP LOCKED or multi-statement races. All timestamps come from the DB server's clock (clock_timestamp() at statement execution — never the app's, and never the transaction-start time, which would stamp events early under lock contention) — app-clock skew never enters the window math.

Crash semantics: a slot is consumed at Reserve, BEFORE submission. A crash between Reserve and Deliver burns one slot without a submission — the provider sees FEWER sends than allowed (the fail-safe direction) — and River re-drives the job, which reserves again once the window has capacity. A crash between Deliver and MarkSent consumes no second slot: the provider_accepted_at evidence settles the row as sent on re-drive without re-submission. Rows self-prune on every Reserve (entries older than the window are dropped), so the limiter adds no janitor or cleanup obligation; a row is at most `limit` timestamps.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Decision

type Decision struct {
	Allowed bool
	RetryAt time.Time
}

Decision is the outcome of one reservation attempt. When Allowed is false, RetryAt is the earliest instant the agent's window frees a slot (the oldest kept event aging out).

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store reserves fire-time submission slots against agent_send_rate_windows. window and limit are constructor-fixed (production wires 1 minute / 60 in cmd/e2a/main.go; tests use short windows).

func NewStore

func NewStore(pool *pgxpool.Pool, window time.Duration, limit int) *Store

func (*Store) Reserve

func (s *Store) Reserve(ctx context.Context, agentID string) (Decision, error)

Reserve consumes one slot in agentID's sliding window, or reports when the window next frees capacity. The whole read-modify-write runs in one transaction guarded by the agent's row lock: ensure-row, lock, prune expired entries, then append or defer.

func (*Store) Window

func (s *Store) Window() time.Duration

Window returns the store's sliding window — exposed so callers (the send worker's snooze clamp) cannot diverge from the limiter's real window.

Jump to

Keyboard shortcuts

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