Documentation
¶
Overview ¶
Package flags owns the DB-backed operational flag table (migration 0003_flags.sql). It exposes one real flag today — rivian_upstream_paused, the kill switch wired into every Rivian-facing code path — but the shape generalises: add a new row, add a new accessor.
Design ¶
The hot path must never block on Postgres. A Store holds an in-process snapshot of the flag row and refreshes it on a ticker (default 10s). Every caller reads the snapshot, which is a plain atomic load — no DB round-trip, no mutex for readers.
Operator writes (HTTP admin endpoint, future CLI) go straight to Postgres and immediately refresh the snapshot so the writer sees their own effect. Other replicas see the change on their next poll (p99 ≤ poll interval). That gap is intentional: a kill switch is a "within ~10 seconds the storm stops" contract, not a "within 10ms" one — the architecture doc sizes it that way on purpose.
Why not LISTEN/NOTIFY ¶
LISTEN/NOTIFY would close the cross-replica propagation gap to sub-second. It also doubles our Postgres connection assumptions (every pod needs a dedicated listen connection) and our failure surface (reconnect logic, missed notifies). 10s polling at 3-8 pods is one indexed SELECT per pod per 10s — cheap enough that the simplicity wins. Revisit when a pod count gets into the dozens.
Index ¶
- Constants
- type KillSwitchState
- type Store
- func (s *Store) KillSwitch() KillSwitchState
- func (s *Store) SetKillSwitch(ctx context.Context, paused bool, reason, actor string) error
- func (s *Store) SetTripPlanner(ctx context.Context, enabled bool, actor string) error
- func (s *Store) Start(ctx context.Context)
- func (s *Store) TripPlanner() TripPlannerState
- type TripPlannerState
Constants ¶
const DefaultPollInterval = 10 * time.Second
DefaultPollInterval is how often the Store refreshes its in-memory snapshot from Postgres. Chosen so a flipped flag takes effect in roughly the time a human can notice — on the same order as a Prometheus scrape interval.
const KillSwitchName = "rivian_upstream_paused"
KillSwitchName is the flags.name value for the Rivian upstream kill switch. Exported so tests and admin handlers can reference the same string the poller does.
const TripPlannerEnabledName = "trip_planner_enabled"
TripPlannerEnabledName is the flags.name value gating the trip planner UI + endpoints. Default-off: when the row is missing or the JSON decodes to {enabled:false}, the planner stays hidden.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type KillSwitchState ¶
type KillSwitchState struct {
Paused bool `json:"paused"`
Reason string `json:"reason,omitempty"`
Actor string `json:"actor,omitempty"`
}
KillSwitchState is the JSONB payload stored in flags.value for rivian_upstream_paused. The extra fields let us track who flipped it and why without a schema migration per axis.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the runtime flag cache. Start() kicks off the background refresh; callers read via KillSwitch(). The type is safe for concurrent use: all readers hit atomic.Value, only the refresh goroutine mutates.
func OpenStore ¶
OpenStore builds a Store against the shared Postgres pool and primes the snapshot with a blocking read so the first KillSwitch() call after New never returns a zero value. A DB outage at startup is non-fatal — the snapshot defaults to "not paused" and the background loop keeps trying.
func (*Store) KillSwitch ¶
func (s *Store) KillSwitch() KillSwitchState
KillSwitch returns the current cached kill-switch state. Cheap — one atomic load. Safe to call on every outbound request. Returns the zero value (Paused: false) when the Store was opened against a DB where the migration hasn't been applied.
func (*Store) SetKillSwitch ¶
SetKillSwitch persists a new kill-switch state and immediately refreshes the local snapshot so the caller observes their own write. `actor` is stored on the row for audit trails; pass the username of whoever flipped the switch (or "system:reason" for automated trips).
func (*Store) SetTripPlanner ¶
SetTripPlanner persists a new trip-planner flag state.
func (*Store) Start ¶
Start launches the refresh loop. Safe to call multiple times; only the first invocation takes effect. The loop exits when ctx is cancelled.
func (*Store) TripPlanner ¶
func (s *Store) TripPlanner() TripPlannerState
TripPlanner returns the current cached trip-planner flag state. Same lock-free read shape as KillSwitch.
type TripPlannerState ¶
type TripPlannerState struct {
Enabled bool `json:"enabled"`
Actor string `json:"actor,omitempty"`
}
TripPlannerState is the JSONB payload for the trip-planner feature flag. Default zero value (Enabled=false) hides the planner UI and 404s its endpoints — chosen so missing rows or fresh installs stay locked down.