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

planleg

package
v0.40.22 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 16 Imported by: 0

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

View Source
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

func New(rdb *redis.Client, prefix string, log *slog.Logger) *Cache

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

Jump to

Keyboard shortcuts

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