Documentation
¶
Overview ¶
Package fleetbucket paces the calls a pod makes to Rivian when it takes vehicles over (zero-impact deploys, G7). A roll moves every group within seconds; the 50 ms stagger spaces one process's first connects, but N pods and N vehicles together still arrive as a burst. Wait gives each call a slot: first from a per-process token bucket, then from a fleet-wide per-second budget in Redis, with active vehicles (driving or charging) kept ahead of idle ones. A wait is bounded, so pacing never becomes an outage: after maxWait the call goes ahead and is counted as starved. A Redis that cannot answer means only the local bucket applies.
The fleet budget is a counter, not pub/sub: the app's Valkey user has no channel rights, and scripts plus TIME are allowed. Its seconds are Redis's (TIME, read inside the admission script), not any pod's: pods whose clocks disagree still count each call in the same second, and no pod's clock names a key.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Bucket ¶
type Bucket struct {
// contains filtered or unexported fields
}
Bucket is safe for concurrent use.
type Config ¶
type Config struct {
// LocalRPS and FleetRPS are the calls per second one process and the whole fleet may start. Defaults 10 and 20 (no Rivian 429 data yet: raise
// them only from observed 429s and the takeover p95). FleetRPS is the TOTAL for active and idle calls together; idle calls may use at most
// half of it, so active ones always find the other half.
LocalRPS int
FleetRPS int
// ActiveMaxWait and IdleMaxWait bound one Wait. Active vehicles must not wait long; idle ones may queue.
ActiveMaxWait time.Duration
IdleMaxWait time.Duration
ShareTTL time.Duration
// RedisBackoff: after a Redis error the fleet budget is skipped (local only) for this long, so a Redis that does not answer costs one slow call, not every one.
// Default 10 s.
RedisBackoff time.Duration
// Redis and Prefix: the fleet budget. Nil Redis: local only.
Redis *redis.Client
Prefix string
// Now is the clock and Sleep the wait for the next second of the fleet budget (tests drive both, so nothing depends on how loaded the
// machine is). Sleep returns when d has passed or ctx is done. Now paces the local bucket, the share window and the Redis backoff; the fleet
// budget's seconds are Redis's own.
Now func() time.Time
Sleep func(ctx context.Context, d time.Duration)
// Observe hears the outcome of every Wait (class and priority as the caller gave them): how long it waited, and whether it was shared,
// starved or local-only. It is called once per Wait, on the caller's goroutine, and must not block.
Observe func(class string, active bool, r Result)
}
Config sizes a Bucket. Zero values fall back to the defaults below.
type Result ¶
type Result struct {
Waited time.Duration
Starved bool // the bound passed and the call went ahead without a slot
Local bool // Redis did not answer: the fleet budget was skipped
// FleetSecond is the Unix second, by Redis's clock, whose fleet budget granted the call (0: no fleet slot was granted).
FleetSecond int64
}
Result says what a Wait did.