Documentation
¶
Index ¶
- Variables
- func RunAdapter(ctx context.Context, adapter FrameworkAdapter, opts RuntimeOptions) error
- type AccountQueue
- type AdmissionController
- type AdmissionDecision
- type AdmissionLimits
- type AdmissionRejectedError
- type Aquifer
- func (a *Aquifer) AdmissionSnapshot() map[string]any
- func (a *Aquifer) Enqueue(req JobRequest) (EnqueueResult, error)
- func (a *Aquifer) GetJob(id string) (*Job, error)
- func (a *Aquifer) HandleL8Challenge(req L8ChallengeReq) (*L8ChallengeResp, error)
- func (a *Aquifer) Health() map[string]any
- func (a *Aquifer) L8Metadata(host string) L8Meta
- func (a *Aquifer) MaxBodyBytes() int64
- func (a *Aquifer) RegisterPoolMember(poolID, memberID, address string, declaredRPS float64, ...) error
- func (a *Aquifer) RetryAfterSeconds() int
- func (a *Aquifer) SubscribeJob(id string) (*Job, <-chan SSEEvent, func(), error)
- type Broker
- type Config
- type DrainConfig
- type DrainState
- type EnqueueResult
- type FrameworkAdapter
- type HTTPAdapter
- type Job
- type JobRequest
- type JobStore
- type L8ChallengeReq
- type L8ChallengeResp
- type L8Meta
- type L8Registry
- type LedgerEntry
- type MCPStdioAdapter
- type MetricsAdapter
- type NoopMetricsAdapter
- func (NoopMetricsAdapter) DrainFlushFailed(instanceKey string, ledgerSize int)
- func (NoopMetricsAdapter) DrainFlushSucceeded(instanceKey string, ledgerSize int)
- func (NoopMetricsAdapter) FlowRate(upstream string, rps float64)
- func (NoopMetricsAdapter) JobCompleted(userID, upstream string, durationMs int64)
- func (NoopMetricsAdapter) JobDispatched(userID, upstream string)
- func (NoopMetricsAdapter) JobFailed(userID, upstream string, reason string)
- func (NoopMetricsAdapter) JobQueued(userID, upstream string)
- func (NoopMetricsAdapter) QueueDepth(upstream string, depth int)
- func (NoopMetricsAdapter) WebhookDelivered(url string, attempt int)
- func (NoopMetricsAdapter) WebhookFailed(url string, attempts int)
- type PebbleStore
- func (s *PebbleStore) CheckOrInsert(job *Job) (string, bool)
- func (s *PebbleStore) ClearIdempotentKeys()
- func (s *PebbleStore) Close() error
- func (s *PebbleStore) Counts() StoreCounts
- func (s *PebbleStore) DeleteJob(jobID string)
- func (s *PebbleStore) GetJob(jobID string) *Job
- func (s *PebbleStore) GetQueuedJobs() []*Job
- func (s *PebbleStore) ListIdempotentKeys() []LedgerEntry
- func (s *PebbleStore) MarkInFlight(jobID string)
- func (s *PebbleStore) Path() string
- func (s *PebbleStore) RecoverInFlight(queueKey string) []*Job
- func (s *PebbleStore) SetQueueKey(jobID, queueKey string)
- func (s *PebbleStore) UpdateStatus(jobID string, status Status)
- type Pool
- func (p *Pool) Pick() *PoolMember
- func (p *Pool) RecordFailure(id string) bool
- func (p *Pool) RecordSuccess(id string)
- func (p *Pool) Register(id, address string, declaredRPS float64, heartbeatInterval time.Duration)
- func (p *Pool) Size() int
- func (p *Pool) Snapshot() []map[string]any
- func (p *Pool) TotalCapacity() float64
- type PoolMember
- type PoolRegistry
- type RateConfig
- type Registry
- func (r *Registry) ConfigureDrain(cfg DrainConfig)
- func (r *Registry) DrainSnapshot() map[string]any
- func (r *Registry) DrainState() DrainState
- func (r *Registry) Enqueue(job *Job, accountQueueHeader string)
- func (r *Registry) EnqueueWebhook(originalJobID, userID, webhookURL string, payload map[string]any)
- func (r *Registry) JobDispatched()
- func (r *Registry) JobDone()
- type Runtime
- type RuntimeOptions
- type SSEEvent
- type Server
- type Status
- type Store
- func (s *Store) CheckOrInsert(job *Job) (string, bool)
- func (s *Store) ClearIdempotentKeys()
- func (s *Store) Close() error
- func (s *Store) Counts() StoreCounts
- func (s *Store) DeleteJob(jobID string)
- func (s *Store) GetJob(jobID string) *Job
- func (s *Store) GetQueuedJobs() []*Job
- func (s *Store) ListIdempotentKeys() []LedgerEntry
- func (s *Store) MarkInFlight(jobID string)
- func (s *Store) Path() string
- func (s *Store) RecoverInFlight(queueKey string) []*Job
- func (s *Store) SetQueueKey(jobID, queueKey string)
- func (s *Store) UpdateStatus(jobID string, status Status)
- type StoreCounts
- type URLWorker
Constants ¶
This section is empty.
Variables ¶
var ErrJobNotFound = errors.New("job not found")
Functions ¶
func RunAdapter ¶
func RunAdapter(ctx context.Context, adapter FrameworkAdapter, opts RuntimeOptions) error
Types ¶
type AccountQueue ¶
type AccountQueue struct {
// contains filtered or unexported fields
}
func NewAccountQueue ¶
func NewAccountQueue(key, upstream string, rps float64, maxConc int, pool *Pool, store JobStore, broker *Broker, l8 *L8Registry, metrics MetricsAdapter, enqueueWebhook webhookEnqueuer, onIdle func(string)) *AccountQueue
func (*AccountQueue) Enqueue ¶
func (q *AccountQueue) Enqueue(job *Job)
func (*AccountQueue) RPS ¶
func (q *AccountQueue) RPS() float64
func (*AccountQueue) Throttle ¶ added in v0.4.0
func (q *AccountQueue) Throttle(rps float64)
Throttle pushes an external rate adjustment into the queue's dispatch loop, reusing the same jobDoneMsg channel that header-driven pacing already uses — the loop doesn't need to know whether a lower rate came from the upstream's own response header or from the URLWorker capping this queue's share of a shared aggregate budget, it's the same signal either way.
type AdmissionController ¶ added in v0.3.0
type AdmissionController struct {
// contains filtered or unexported fields
}
AdmissionController evaluates memory and DB size limits at request time. Body size is enforced separately at the transport layer via http.MaxBytesReader, since that has to happen before the body is even read.
func NewAdmissionController ¶ added in v0.3.0
func NewAdmissionController(limits AdmissionLimits, dbPath string) *AdmissionController
func (*AdmissionController) AnyLimitConfigured ¶ added in v0.3.0
func (c *AdmissionController) AnyLimitConfigured() bool
AnyLimitConfigured reports whether at least one admission limit is actually active, as opposed to merely whether a controller instance exists (the runtime always constructs one, even with everything at zero).
func (*AdmissionController) Check ¶ added in v0.3.0
func (c *AdmissionController) Check() AdmissionDecision
func (*AdmissionController) MaxBodyBytes ¶ added in v0.3.0
func (c *AdmissionController) MaxBodyBytes() int64
func (*AdmissionController) RetryAfterSeconds ¶ added in v0.3.0
func (c *AdmissionController) RetryAfterSeconds() int
RetryAfterSeconds returns the configured base value on the first rejection, then doubles for each additional consecutive rejection (capped at maxRetryAfterSeconds), resetting the moment a request is allowed again.
func (*AdmissionController) Snapshot ¶ added in v0.3.0
func (c *AdmissionController) Snapshot() map[string]any
Snapshot reports current admission pressure for /health, independent of whether any request is currently being rejected.
type AdmissionDecision ¶ added in v0.3.0
type AdmissionDecision struct {
Allowed bool
Reason string // "memory" or "db_size"
Limit int64
Current int64
}
AdmissionDecision is the result of an admission check on a new (non-duplicate) job.
type AdmissionLimits ¶ added in v0.3.0
type AdmissionLimits struct {
MemoryLimitMB int64 // AQUIFER_MEMORY_LIMIT_MB
MaxBodyBytes int64 // AQUIFER_MAX_BODY_BYTES
DBMaxBytes int64 // AQUIFER_DB_MAX_BYTES
RetryAfterSeconds int // AQUIFER_RETRY_AFTER_SECONDS
}
AdmissionLimits are operator-configured ceilings that protect Aquifer itself from the traffic it's meant to be absorbing. A zero value disables that particular check. Memory is opt-in (LoadAdmissionLimits defaults it to disabled); body size and DB size default on — see LoadAdmissionLimits.
func LoadAdmissionLimits ¶ added in v0.3.0
func LoadAdmissionLimits() AdmissionLimits
LoadAdmissionLimits reads the AQUIFER_* admission env vars. Body size and DB size fall back to sane, non-zero defaults when unset (see the default* constants above) rather than being disabled — Aquifer's whole purpose is protecting the process from the traffic it absorbs, so it protects itself by default rather than requiring that to be opted into. An explicit "0" still disables a given check. Memory has no safe one-size-fits-all default (it depends on the deployment's own memory budget, not Aquifer's benchmarked disk usage), so it stays disabled unless set explicitly — LoadAdmissionLimits logs a warning when it is.
type AdmissionRejectedError ¶ added in v0.3.0
type AdmissionRejectedError struct {
Decision AdmissionDecision
}
AdmissionRejectedError is returned by Aquifer.Enqueue when a genuinely new job is rejected due to memory or DB size pressure. Callers (the HTTP server) type-assert on this to build a 429 with Retry-After.
func (*AdmissionRejectedError) Error ¶ added in v0.3.0
func (e *AdmissionRejectedError) Error() string
type Aquifer ¶
type Aquifer struct {
// contains filtered or unexported fields
}
func NewAquifer ¶
func NewAquifer(store JobStore, registry *Registry, broker *Broker, l8 *L8Registry, admission *AdmissionController, pools *PoolRegistry) *Aquifer
func (*Aquifer) AdmissionSnapshot ¶ added in v0.3.0
AdmissionSnapshot reports current admission pressure for /health. Returns enabled:false if admission control isn't configured.
func (*Aquifer) Enqueue ¶
func (a *Aquifer) Enqueue(req JobRequest) (EnqueueResult, error)
func (*Aquifer) HandleL8Challenge ¶
func (a *Aquifer) HandleL8Challenge(req L8ChallengeReq) (*L8ChallengeResp, error)
func (*Aquifer) L8Metadata ¶
func (*Aquifer) MaxBodyBytes ¶ added in v0.3.0
MaxBodyBytes returns the configured request body ceiling, or 0 if unconfigured (unlimited).
func (*Aquifer) RegisterPoolMember ¶ added in v0.4.0
func (a *Aquifer) RegisterPoolMember(poolID, memberID, address string, declaredRPS float64, heartbeatIntervalSeconds int) error
RegisterPoolMember adds or refreshes (heartbeats) a member of a pool. The same call serves both roles — re-registering resets the member's liveness TTL and updates its declared capacity.
func (*Aquifer) RetryAfterSeconds ¶ added in v0.3.0
RetryAfterSeconds returns the configured Retry-After value for 429 responses, defaulting to 5 seconds if admission control isn't configured.
type Broker ¶
type Broker struct {
// contains filtered or unexported fields
}
Broker is the pub/sub layer for SSE streams. Each job gets its own set of subscriber channels.
type Config ¶
type Config struct {
Defaults RateConfig `yaml:"defaults"`
Upstreams map[string]RateConfig `yaml:"upstreams"`
}
func LoadConfig ¶
func (*Config) ForURL ¶
func (c *Config) ForURL(rawURL string) RateConfig
type DrainConfig ¶ added in v0.6.0
type DrainConfig struct {
Enabled bool // AQUIFER_DRAIN_ENABLED
TimerSeconds int64 // AQUIFER_DRAIN_TIMER_SECONDS
WebhookURL string
}
func LoadDrainConfig ¶ added in v0.6.0
func LoadDrainConfig() DrainConfig
LoadDrainConfig reads the AQUIFER_DRAIN_* env vars. Enabled defaults to false. If enabled but no webhook URL is configured, that's treated as disabled (with a warning) rather than a flush attempt with nowhere to send it -- fail-safe toward "do nothing," never toward "clear the ledger anyway."
type DrainState ¶ added in v0.6.0
type DrainState string
DrainState is the instance's explicit position in drain mode's lifecycle, visible via GET /health (see Registry.DrainSnapshot). Only meaningful when drain mode is enabled.
const ( // DrainStateActive: at least one worker has live work. The normal // state for any instance, drain mode enabled or not. DrainStateActive DrainState = "active" // DrainStateDraining: every worker has gone idle, but either the drain // timer hasn't elapsed yet, or a flush attempt is in flight/being // retried. Not yet safe to hand off. DrainStateDraining DrainState = "draining" // DrainStateUnassigned: the ledger was successfully flushed (or there // was nothing to flush) and local state is clear. Safe to hand off to // a different tenant. Reverts to Active the instant new work arrives. DrainStateUnassigned DrainState = "unassigned" )
type EnqueueResult ¶
type FrameworkAdapter ¶
type HTTPAdapter ¶
type HTTPAdapter struct {
// contains filtered or unexported fields
}
func NewHTTPAdapter ¶
func NewHTTPAdapter(addr string) *HTTPAdapter
func (*HTTPAdapter) Name ¶
func (a *HTTPAdapter) Name() string
type Job ¶
type Job struct {
ID string `json:"id"`
UserID string `json:"user_id"`
IdempotentKey string `json:"idempotent_key"`
URL string `json:"url,omitempty"`
PoolID string `json:"pool_id,omitempty"`
Method string `json:"method"`
Headers map[string]string `json:"headers,omitempty"`
Body string `json:"body,omitempty"`
WebhookURL string `json:"webhook_url"`
Status Status `json:"status"`
CreatedAt int64 `json:"created_at"`
}
func NewJob ¶
func NewJob(r *JobRequest) *Job
type JobRequest ¶
type JobRequest struct {
UserID string `json:"user_id"`
IdempotentKey string `json:"idempotent_key"`
URL string `json:"url,omitempty"`
PoolID string `json:"pool_id,omitempty"`
Method string `json:"method"`
Headers map[string]string `json:"headers,omitempty"`
Body string `json:"body,omitempty"`
WebhookURL string `json:"webhook_url"`
// AccountQueueMode is never read from the request body — it's set by the
// HTTP adapter from the X-Aqueduct-Account-Queue / X-Aquifer-Account-Queue
// request header, the only source of truth for this setting. Empty means
// "no opinion, leave the upstream's current mode unchanged."
AccountQueueMode string `json:"-"`
}
func (*JobRequest) Validate ¶
func (r *JobRequest) Validate() string
type JobStore ¶ added in v0.4.0
type JobStore interface {
Path() string
Close() error
CheckOrInsert(job *Job) (string, bool)
SetQueueKey(jobID, queueKey string)
DeleteJob(jobID string)
MarkInFlight(jobID string)
RecoverInFlight(queueKey string) []*Job
UpdateStatus(jobID string, status Status)
Counts() StoreCounts
GetJob(jobID string) *Job
GetQueuedJobs() []*Job
// ListIdempotentKeys and ClearIdempotentKeys back drain mode (see
// drain.go) -- an opt-in feature, off by default, so calling these on
// a deployment that never enables it is never reached.
ListIdempotentKeys() []LedgerEntry
ClearIdempotentKeys()
}
JobStore is the storage backend behind everything that persists a job: idempotency, status transitions, crash recovery, and admission control's db-size check. *Store (SQLite/WAL) is the default and only implementation until now; *PebbleStore is an opt-in alternative for benchmarking whether a memory-first LSM store changes the throughput ceiling the way it did for the Elixir/Mnesia sibling of this project. Selected via AQUIFER_STORE_BACKEND ("sqlite", the default, or "pebble") — existing deployments that don't set it see no change at all.
func NewJobStore ¶ added in v0.4.0
NewJobStore constructs whichever backend AQUIFER_STORE_BACKEND names. path is the same value that used to go straight to NewStore — for SQLite it's a file path, for Pebble it's a directory.
type L8ChallengeReq ¶
type L8ChallengeResp ¶
type L8Meta ¶
type L8Meta struct {
ProtocolVersion string `json:"protocol_version"`
ServiceName string `json:"service_name"`
PublicKey string `json:"public_key"`
ChallengeEndpoint string `json:"challenge_endpoint"`
SupportedAlgos []string `json:"supported_algorithms"`
Capabilities []string `json:"capabilities"`
SpecURL string `json:"spec_url"`
}
type L8Registry ¶
type L8Registry struct {
PubB64 string // exported so server can embed in responses
// contains filtered or unexported fields
}
func NewL8Registry ¶
func NewL8Registry(keyPath, trustDir string) *L8Registry
func (*L8Registry) EnsureTrust ¶
func (r *L8Registry) EnsureTrust(webhookURL string)
EnsureTrust runs the L8 handshake with the webhook domain if not already trusted. Silently does nothing if the receiver doesn't support L8 — delivery still proceeds unsigned.
func (*L8Registry) HandleChallenge ¶
func (r *L8Registry) HandleChallenge(req L8ChallengeReq) (*L8ChallengeResp, error)
HandleChallenge verifies the sender's ownership proof and returns Aquifer's signed response.
func (*L8Registry) IsTrusted ¶
func (r *L8Registry) IsTrusted(webhookURL string) bool
IsTrusted returns true if the L8 handshake has been completed for this webhook domain.
func (*L8Registry) Meta ¶
func (r *L8Registry) Meta(host string) L8Meta
func (*L8Registry) SignHeaders ¶
func (r *L8Registry) SignHeaders(body []byte) map[string]string
SignHeaders returns X-L8-* headers to attach to an outgoing webhook delivery.
type LedgerEntry ¶ added in v0.6.0
type LedgerEntry struct {
HashKey string `json:"idempotent_key_hash"`
JobID string `json:"job_id"`
Status Status `json:"status"`
}
LedgerEntry is one row of the drain-mode idempotency ledger -- hash-only, never the plaintext idempotent key. See drain.go.
type MCPStdioAdapter ¶
type MCPStdioAdapter struct {
// contains filtered or unexported fields
}
func NewMCPStdioAdapter ¶
func NewMCPStdioAdapter(in io.Reader, out io.Writer) *MCPStdioAdapter
func (*MCPStdioAdapter) Name ¶
func (a *MCPStdioAdapter) Name() string
type MetricsAdapter ¶
type MetricsAdapter interface {
JobQueued(userID, upstream string)
JobDispatched(userID, upstream string)
JobCompleted(userID, upstream string, durationMs int64)
JobFailed(userID, upstream string, reason string)
WebhookDelivered(url string, attempt int)
WebhookFailed(url string, attempts int)
QueueDepth(upstream string, depth int)
FlowRate(upstream string, rps float64)
// DrainFlushSucceeded/DrainFlushFailed only ever fire when drain mode
// is enabled (see drain.go) -- unreached on a deployment that never
// turns it on.
DrainFlushSucceeded(instanceKey string, ledgerSize int)
DrainFlushFailed(instanceKey string, ledgerSize int)
}
type NoopMetricsAdapter ¶
type NoopMetricsAdapter struct{}
func (NoopMetricsAdapter) DrainFlushFailed ¶ added in v0.6.0
func (NoopMetricsAdapter) DrainFlushFailed(instanceKey string, ledgerSize int)
func (NoopMetricsAdapter) DrainFlushSucceeded ¶ added in v0.6.0
func (NoopMetricsAdapter) DrainFlushSucceeded(instanceKey string, ledgerSize int)
func (NoopMetricsAdapter) FlowRate ¶
func (NoopMetricsAdapter) FlowRate(upstream string, rps float64)
func (NoopMetricsAdapter) JobCompleted ¶
func (NoopMetricsAdapter) JobCompleted(userID, upstream string, durationMs int64)
func (NoopMetricsAdapter) JobDispatched ¶
func (NoopMetricsAdapter) JobDispatched(userID, upstream string)
func (NoopMetricsAdapter) JobFailed ¶
func (NoopMetricsAdapter) JobFailed(userID, upstream string, reason string)
func (NoopMetricsAdapter) JobQueued ¶
func (NoopMetricsAdapter) JobQueued(userID, upstream string)
func (NoopMetricsAdapter) QueueDepth ¶
func (NoopMetricsAdapter) QueueDepth(upstream string, depth int)
func (NoopMetricsAdapter) WebhookDelivered ¶
func (NoopMetricsAdapter) WebhookDelivered(url string, attempt int)
func (NoopMetricsAdapter) WebhookFailed ¶
func (NoopMetricsAdapter) WebhookFailed(url string, attempts int)
type PebbleStore ¶ added in v0.4.0
type PebbleStore struct {
// contains filtered or unexported fields
}
func NewPebbleStore ¶ added in v0.4.0
func NewPebbleStore(path string) *PebbleStore
func (*PebbleStore) CheckOrInsert ¶ added in v0.4.0
func (s *PebbleStore) CheckOrInsert(job *Job) (string, bool)
CheckOrInsert mirrors Store.CheckOrInsert's contract exactly: :ok for a fresh job, or the existing job ID if the (user_id, idempotent_key) pair was already accepted. The shard lock is what makes this atomic instead of a racy Get-then-Set — see the package doc comment above.
func (*PebbleStore) ClearIdempotentKeys ¶ added in v0.6.0
func (s *PebbleStore) ClearIdempotentKeys()
ClearIdempotentKeys wipes both the job: and idem: prefixes -- only ever called by drain mode's watchdog after a successful ledger-flush webhook delivery, never on a normal (non-drain-mode) deployment. Takes every shard lock before wiping so a concurrent CheckOrInsert (which only holds one shard's lock) can't race a mid-wipe read/write and reintroduce a row.
func (*PebbleStore) Close ¶ added in v0.4.0
func (s *PebbleStore) Close() error
func (*PebbleStore) Counts ¶ added in v0.4.0
func (s *PebbleStore) Counts() StoreCounts
func (*PebbleStore) DeleteJob ¶ added in v0.4.0
func (s *PebbleStore) DeleteJob(jobID string)
DeleteJob removes both the job row and its idempotency index entry — dropping only the job: key would leave a dangling idem: entry pointing at a job that no longer exists, the same ghost-row risk DeleteJob exists to prevent on the SQLite side.
func (*PebbleStore) GetJob ¶ added in v0.4.0
func (s *PebbleStore) GetJob(jobID string) *Job
func (*PebbleStore) GetQueuedJobs ¶ added in v0.4.0
func (s *PebbleStore) GetQueuedJobs() []*Job
func (*PebbleStore) ListIdempotentKeys ¶ added in v0.6.0
func (s *PebbleStore) ListIdempotentKeys() []LedgerEntry
ListIdempotentKeys backs drain mode's ledger export. Unlike SQLite, pebbleRecord.Job retains the plaintext IdempotentKey (for unrelated reasons -- see the package doc comment), but this must never surface it: the hash is recomputed the same way CheckOrInsert derives it, and only the hash/job_id/status ever go into the returned LedgerEntry. Excludes webhook-delivery jobs (WebhookURL == "", see Job.isWebhookDeliveryJob) -- those are internal delivery bookkeeping, not real user-submitted work, and have no business appearing in a ledger meant for tenant-handoff dedup.
func (*PebbleStore) MarkInFlight ¶ added in v0.4.0
func (s *PebbleStore) MarkInFlight(jobID string)
func (*PebbleStore) Path ¶ added in v0.4.0
func (s *PebbleStore) Path() string
func (*PebbleStore) RecoverInFlight ¶ added in v0.4.0
func (s *PebbleStore) RecoverInFlight(queueKey string) []*Job
func (*PebbleStore) SetQueueKey ¶ added in v0.4.0
func (s *PebbleStore) SetQueueKey(jobID, queueKey string)
func (*PebbleStore) UpdateStatus ¶ added in v0.4.0
func (s *PebbleStore) UpdateStatus(jobID string, status Status)
type Pool ¶ added in v0.4.0
type Pool struct {
// contains filtered or unexported fields
}
Pool is one named group of registered members. Selection uses virtual-time weighted round robin: each pick advances the chosen member's virtual position by 1/weight (a higher-weight member advances less per pick, so it comes back to the front of the heap sooner and gets picked proportionally more often), giving O(log n) per dispatch — the naive nginx-style smooth-weighted-round-robin algorithm (recompute every member's running weight on every pick) is O(n) per pick instead; this avoids that by only ever touching the single member actually chosen.
func (*Pool) Pick ¶ added in v0.4.0
func (p *Pool) Pick() *PoolMember
Pick selects a member proportional to declared_rate x reputation and advances its virtual position. Returns nil if the pool has no members.
func (*Pool) RecordFailure ¶ added in v0.4.0
RecordFailure halves a member's reputation — the same shape as the existing Retry-After backoff (doubles per consecutive event), just applied as a share reduction instead of a delay increase. Once reputation has been at or below the floor continuously for defaultFloorEvictionWindow, the member is evicted. Returns whether this call caused an eviction.
func (*Pool) RecordSuccess ¶ added in v0.4.0
RecordSuccess nudges a member's reputation back toward full trust and unconditionally clears any in-progress floor-eviction timer — the eviction criterion is sustained badness with zero successes in between, not a numeric reputation threshold held for a duration, so any success resets the clock even if the reputation number itself hasn't recovered above the floor yet.
func (*Pool) Register ¶ added in v0.4.0
Register adds a new member or refreshes an existing one. The same call serves as both initial registration and heartbeat — re-calling it resets LastHeartbeat and updates declared capacity, so a member that wants to lower or raise its reported rate just re-registers.
func (*Pool) TotalCapacity ¶ added in v0.4.0
TotalCapacity is the live sum of every member's current effective weight — the pool's aggregate ceiling grows and shrinks automatically as members register, degrade, or drop out, rather than being a static number an operator configures once.
type PoolMember ¶ added in v0.4.0
type PoolMember struct {
ID string
Address string
DeclaredRPS float64
HeartbeatInterval time.Duration
LastHeartbeat time.Time
// contains filtered or unexported fields
}
PoolMember is one registered target within a pool — a service instance that pinged in with its own address and declared capacity.
func (*PoolMember) Reputation ¶ added in v0.4.0
func (m *PoolMember) Reputation() float64
type PoolRegistry ¶ added in v0.4.0
type PoolRegistry struct {
// contains filtered or unexported fields
}
PoolRegistry owns every named pool. One registry per Aquifer instance.
func NewPoolRegistry ¶ added in v0.4.0
func NewPoolRegistry() *PoolRegistry
func (*PoolRegistry) Get ¶ added in v0.4.0
func (pr *PoolRegistry) Get(poolID string) *Pool
Get returns the named pool, creating an empty one if nobody has registered to it yet. A job dispatched to a pool that exists but has zero members is a different, correctly-handled state (fails cleanly with "no pool members registered") from a job that isn't pool-backed at all — returning nil here would collapse that distinction and let a pool-backed job silently fall through to a non-pool dispatch path with an empty URL instead.
func (*PoolRegistry) Register ¶ added in v0.4.0
func (pr *PoolRegistry) Register(poolID, memberID, address string, declaredRPS float64, heartbeatInterval time.Duration)
func (*PoolRegistry) Snapshot ¶ added in v0.4.0
func (pr *PoolRegistry) Snapshot() map[string]any
func (*PoolRegistry) Stop ¶ added in v0.4.0
func (pr *PoolRegistry) Stop()
type RateConfig ¶
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
func NewRegistry ¶
func NewRegistry(store JobStore, cfg *Config, broker *Broker, l8 *L8Registry, metrics MetricsAdapter, pools *PoolRegistry) *Registry
NewRegistry reads drain mode's config from AQUIFER_DRAIN_* env vars (LoadDrainConfig) — disabled unless AQUIFER_DRAIN_ENABLED is explicitly set, matching NewPebbleStore's existing precedent of reading its own opt-in env vars internally. Callers wanting a programmatic override (RuntimeOptions.DrainConfig) call ConfigureDrain after construction.
func (*Registry) ConfigureDrain ¶ added in v0.6.0
func (r *Registry) ConfigureDrain(cfg DrainConfig)
ConfigureDrain overrides drain mode's config after construction (used by RuntimeOptions.DrainConfig) and starts the watchdog if the override enables it and the constructor-time env-based config hadn't already started one. There is no supported path to stop an already-running watchdog at runtime — disabling drain mode requires a restart, same as every other env-var-driven config in this codebase.
func (*Registry) DrainSnapshot ¶ added in v0.6.0
DrainSnapshot reports drain mode's current state for GET /health, or nil when drain mode isn't enabled — an instance that never turned this on shouldn't see a new key appear in its health output.
func (*Registry) DrainState ¶ added in v0.6.0
func (r *Registry) DrainState() DrainState
DrainState is the instance's current position in drain mode's lifecycle (active/draining/unassigned) — meaningful only when drain mode is enabled, but always safe to call (returns DrainStateActive otherwise, since the watchdog that would ever move it elsewhere never runs).
func (*Registry) Enqueue ¶
Enqueue queues a job on the URLWorker for its upstream domain, or for its target pool if the job carries a PoolID instead of a URL. accountQueueHeader is the raw X-Aqueduct-Account-Queue/X-Aquifer-Account-Queue value from the originating HTTP request, or "" if this job has no live request behind it (e.g. recovered from disk at startup). An empty value leaves the worker's current account-queue mode unchanged rather than forcing it off — the mode is shared per upstream domain, so one request that doesn't care about it shouldn't be able to flip it off for every other concurrent tenant relying on it being on.
func (*Registry) EnqueueWebhook ¶ added in v0.7.0
EnqueueWebhook queues a webhook delivery through the same domain-keyed account-queue pacing and backpressure machinery as forward dispatch (RPS/concurrency limits, X-Aqueduct-* response-header throttling) instead of firing immediately with a fixed retry schedule — a slow or rate-limited webhook receiver can now shed load exactly the way an upstream API already can, and delivery is durable across a restart the same way a real job is (the underlying webhook-delivery Job is persisted via CheckOrInsert, not just an in-memory retry loop).
originalJobID scopes the idempotent key (see Job.isWebhookDeliveryJob) so a given job's webhook is enqueued at most once even if this were somehow called twice for it.
func (*Registry) JobDispatched ¶
func (r *Registry) JobDispatched()
type Runtime ¶
type Runtime struct {
Aquifer *Aquifer
Store JobStore
Broker *Broker
Registry *Registry
L8 *L8Registry
Config *Config
Admission *AdmissionController
Pools *PoolRegistry
}
func NewRuntime ¶
func NewRuntime(opts RuntimeOptions) *Runtime
func (*Runtime) RecoverQueuedJobs ¶
type RuntimeOptions ¶
type RuntimeOptions struct {
DBPath string
ConfigPath string
Config *Config
L8KeyPath string
L8TrustDir string
Metrics MetricsAdapter
AdmissionLimits *AdmissionLimits
// Store overrides the storage backend entirely. If nil, NewRuntime falls
// back to NewJobStore(DBPath), selecting sqlite/pebble via
// AQUIFER_STORE_BACKEND as before. Set this to plug in a custom JobStore
// implementation (e.g. Postgres, rqlite) without needing to bypass
// NewRuntime and wire the lower-level constructors by hand.
Store JobStore
// DrainConfig overrides drain mode's config (see drain.go). If nil,
// the Registry reads AQUIFER_DRAIN_* env vars itself — disabled unless
// AQUIFER_DRAIN_ENABLED is explicitly set to true.
DrainConfig *DrainConfig
}
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
func (*Store) CheckOrInsert ¶
CheckOrInsert inserts job unless its (user_id, idempotent_key) pair already exists, in which case it reports the existing job's ID as a duplicate. The duplicate/fresh decision is read directly off the INSERT's own RowsAffected, not a follow-up SELECT — a SELECT-after-INSERT here would race under real concurrency (multiple open connections): if the INSERT is still in flight relative to another goroutine's read, or a transient busy-retry delays it, the SELECT can find nothing and this would misreport a brand-new job as an empty-ID "duplicate," silently losing it. RowsAffected==1 is authoritative and atomic: it's exactly the row this call just wrote, no read-your-own-write race possible.
func (*Store) ClearIdempotentKeys ¶ added in v0.6.0
func (s *Store) ClearIdempotentKeys()
ClearIdempotentKeys wipes the whole table -- only ever called by drain mode's watchdog after a successful ledger-flush webhook delivery, never on a normal (non-drain-mode) deployment.
func (*Store) Close ¶ added in v0.4.0
Close releases the underlying connection pool. In WAL mode, SQLite keeps -wal/-shm files alongside the main database file for as long as any connection is open; callers that manage a Store's lifetime explicitly (tests especially, cleaning up a t.TempDir()) should call this before their directory is removed.
func (*Store) Counts ¶
func (s *Store) Counts() StoreCounts
func (*Store) DeleteJob ¶ added in v0.3.0
DeleteJob removes a job row outright. Used when a freshly-inserted, non-duplicate job is rejected by admission control — CheckOrInsert already wrote the row before duplicate status was known, so a rejected job must be deleted here or it would sit as a ghost "queued" row that never dispatches.
func (*Store) GetQueuedJobs ¶
func (*Store) ListIdempotentKeys ¶ added in v0.6.0
func (s *Store) ListIdempotentKeys() []LedgerEntry
ListIdempotentKeys backs drain mode's ledger export -- hash-only, matches what this table has always stored (the plaintext idempotent key was never persisted here, only its hash, see hashKey/CheckOrInsert). Excludes webhook-delivery jobs (webhook_url = ”, see Job.isWebhookDeliveryJob) -- those are internal delivery bookkeeping, not real user-submitted work, and have no business appearing in a ledger meant for tenant-handoff dedup.
func (*Store) MarkInFlight ¶
func (*Store) RecoverInFlight ¶
func (*Store) SetQueueKey ¶
func (*Store) UpdateStatus ¶
type StoreCounts ¶
type URLWorker ¶
type URLWorker struct {
// contains filtered or unexported fields
}
func NewURLWorker ¶
func NewURLWorker(domain string, rps float64, maxConc int, pool *Pool, store JobStore, broker *Broker, l8 *L8Registry, metrics MetricsAdapter, enqueueWebhook webhookEnqueuer, onIdle func(string)) *URLWorker
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package a2aadapter exposes Aquifer as an A2A (Agent2Agent protocol, v1.0) agent over JSON-RPC/HTTPS.
|
Package a2aadapter exposes Aquifer as an A2A (Agent2Agent protocol, v1.0) agent over JSON-RPC/HTTPS. |
|
cmd
|
|
|
aquifer
command
|
|
|
aquifer-scenario
command
|
|
|
examples
|
|
|
custom_adapter
command
|