Help us shape the future of Go! Take the 2026 Go Developer Survey

sendrate

package
v1.8.4 Latest Latest
Warning

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

Go to latest
Published: Aug 29, 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