autoscale

package
v0.10.17 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package autoscale provides a provider-agnostic replica autoscale controller. It evaluates each opted-in app's session saturation on a fixed interval and drives the incremental scale primitives to converge the replica count on a target average sessions-per-replica, within the app's configured bounds and the runtime ceiling. It never scales worker hosts and never touches apps that have not opted in.

Index

Constants

View Source
const (
	ActionScaleUp   = "autoscale_scale_up"
	ActionScaleDown = "autoscale_scale_down"
)

Action constants for autoscale audit events.

Variables

This section is empty.

Functions

This section is empty.

Types

type AuditRecorder

type AuditRecorder interface {
	LogAuditEvent(p db.AuditEventParams)
}

AuditRecorder is the subset of *db.Store the controller needs to record scale events. db.Store satisfies it; main.go passes store directly.

type AutoscaleMetrics

type AutoscaleMetrics interface {
	RecordAutoscaleScale(direction string) // direction: "up" or "down"
}

AutoscaleMetrics records autoscale scale events for Prometheus. *metrics.Registry satisfies it. Kept as an interface so the autoscale package does not import Prometheus.

type Config

type Config struct {
	// ScanInterval is how often the controller evaluates opted-in apps.
	ScanInterval time.Duration
	// Cooldown is the minimum time between successive scale actions on one app.
	Cooldown time.Duration
	// DrainGrace bounds how long ScaleDown waits for sessions to finish.
	DrainGrace time.Duration
	// RejectWindow is the look-back window for the pool-saturated reject signal.
	RejectWindow time.Duration
	// DefaultTarget is the fallback target fraction when an app's own target is 0.
	DefaultTarget float64
	// DefaultCap is the fallback per-replica session cap when an app's own cap is 0.
	DefaultCap int
	// RuntimeMax is the runtime-wide replica ceiling.
	RuntimeMax int
}

Config holds the controller's resolved runtime settings.

type Controller

type Controller struct {
	// contains filtered or unexported fields
}

Controller evaluates and converges replica counts. Its internal state is owned solely by the Run loop goroutine, so it needs no lock. The per-app cooldown is persisted (apps.last_autoscale_at, read via the app list each tick) so it survives process restart and failover, rather than living in process memory.

func New

func New(cfg Config, lister Lister, signal Signal, scaler Scaler, recorder AuditRecorder, cooldown CooldownStore, log *slog.Logger) *Controller

New builds a controller. log may be nil, in which case the default logger is used.

func (*Controller) Run

func (c *Controller) Run(ctx context.Context)

Run evaluates opted-in apps every ScanInterval until ctx is cancelled. Each tick reads the current time from the DB clock so the cooldown is measured against the same clock that stamped the last action, regardless of which instance is active. A DB-clock read failure skips the tick (the DB is the same one ListAutoscaleApps needs, so it would fail too).

func (*Controller) SetMetrics

func (c *Controller) SetMetrics(m AutoscaleMetrics)

SetMetrics wires a recorder for autoscale Prometheus metrics. Called once at startup before Run; nil-safe so tests that do not need metrics can skip it. Mirrors lifecycle.Watcher.SetMetrics and fargate.Runtime.SetMetrics.

type CooldownStore added in v0.8.0

type CooldownStore interface {
	NowEpoch() (int64, error)
	SetAppLastAutoscaleAt(slug string, epoch int64) error
}

CooldownStore persists the per-app autoscale cooldown so it survives process restart and failover to a standby control-plane instance, and supplies the DB clock the cooldown is measured against. *db.Store satisfies it. The read side of the cooldown is the persisted db.App.LastAutoscaleAt the Lister returns each tick; SetAppLastAutoscaleAt is the write side. NowEpoch returns the DB clock so the armed timestamp and the cooldown check use one clock (immune to wall-clock skew between instances), not the local clock of whichever instance is active.

type Lister

type Lister interface {
	ListAutoscaleApps() ([]*db.App, error)
}

Lister returns the apps that have opted into autoscaling and are actionable.

type Scaler

type Scaler interface {
	ScaleUp(slug string) (bool, error)
	ScaleDown(slug string, grace time.Duration) (bool, error)
}

Scaler drives the incremental scale primitives. ScaleUp grows the pool by one replica; ScaleDown gracefully removes one. Both return (false, nil) for the benign no-op cases (ceiling reached, floor reached, app not running).

type Signal

type Signal interface {
	ReplicaSessionCounts(slug string) []int64
	RejectsByReason(slug string, d time.Duration) map[proxy.RejectReason]uint64
}

Signal is the proxy-level saturation signal: per-replica active session counts and the rolling pool-saturated rejection rollup.

Jump to

Keyboard shortcuts

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