Documentation
¶
Overview ¶
Package planleg caches planner calls and caps how many a user can make.
A multi-day plan is one planner call per day, each ~10 s upstream, and the planner is edited a day at a time: re-planning after a change to day 3 should cost day 3 (and later days whose start SoC moved), not every day. Cache wraps a rivian.TripPlanner so that
- a call with the same inputs inside 15 minutes is answered from Redis and is free, and
- every call that does reach the upstream planner counts against the user's 60 per hour; the 61st is refused with a QuotaError.
Every planner is wrapped, not only the live one: a replay install's synthetic plan is a Valhalla route and a charger-archive scan, which a user can also run up. Simulated plans are deterministic for their inputs, so they cache like any other. Redis being down never blocks planning, but never lifts the quota either: the cache then misses and the quota is counted in this process instead, a share of 60 an hour per user (60 divided by the number of replicas that may run at once, FallbackDivisor), so the replicas together stay inside Rivian's per-user budget. An install without Redis has the same per-process quota.
Index ¶
Constants ¶
const ( // TTL is how long a planned leg is served from cache. TTL = 15 * time.Minute // QuotaPerHour is the planner calls a user may send upstream per UTC hour. QuotaPerHour = 60 )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Cache ¶
type Cache struct {
// OnFallback, when set, is called for each quota check counted in this process
// because Redis could not serve it (or there is none).
OnFallback func()
// FallbackDivisor is the number of replicas that may run at once: while Redis cannot
// serve the quota each pod allows ceil(QuotaPerHour / FallbackDivisor) a user (at least
// one), so the replicas together stay inside the per-user budget. Zero or less: 3.
FallbackDivisor float64
// contains filtered or unexported fields
}
Cache is the Redis-backed leg cache and quota. A nil *Cache wraps nothing.
func New ¶
New returns a Cache over rdb. prefix keeps installs sharing a Redis out of each other's keys (RIVOLT_REDIS_KEY_PREFIX). With no Redis (rdb nil) there is no leg cache and the quota is counted in this process.
func (*Cache) Wrap ¶
func (c *Cache) Wrap(inner rivian.TripPlanner, uid uuid.UUID) rivian.TripPlanner
Wrap returns inner behind the cache and the quota for uid. On a nil Cache it returns inner unchanged.
type QuotaError ¶
type QuotaError struct {
// RetryAfter is the time until the quota window resets.
RetryAfter time.Duration
}
QuotaError is returned when a user is over their hourly planner quota.
func IsQuota ¶
func IsQuota(err error) (*QuotaError, bool)
IsQuota reports whether err is a QuotaError and returns it.
func (*QuotaError) Error ¶
func (e *QuotaError) Error() string