worker

package
v0.24.0 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: AGPL-3.0 Imports: 14 Imported by: 0

Documentation

Overview

Package worker is the production derive loop: it polls the dirty- session queue ingest feeds (see storage.DeriveQueue), debounces bursts, and re-derives one session at a time under a per-session advisory lock. It runs as its own process (`tapes serve derive-worker`) — NEVER inside the API container: a full derive once OOMKilled a 256Mi API pod, so derivation gets its own memory budget and processes exactly one session at a time.

Everything is at-least-once and leans on the deriver's idempotence (re-running an unchanged session prunes 0): a lost clear, a crashed derive, or a duplicate mark only costs a redundant derive.

Index

Constants

View Source
const (
	DefaultPollInterval   = 5 * time.Second
	DefaultDebounce       = 20 * time.Second
	DefaultMaxDeriveLag   = 45 * time.Second
	DefaultSweepInterval  = time.Hour
	DefaultPageSize       = 50
	DefaultMaxPollBackoff = 30 * time.Second
	DefaultDrainTimeout   = 30 * time.Second
	DefaultSweepWindow    = 24 * time.Hour
)

Defaults for Config fields left zero.

Variables

This section is empty.

Functions

func ApplySoftMemoryLimit added in v0.19.0

func ApplySoftMemoryLimit(log *slog.Logger) int64

ApplySoftMemoryLimit sets a soft heap ceiling (GOMEMLIMIT) derived from the cgroup memory limit, so a large session's transient allocation overshoot is GC-paced under the container budget instead of OOMKilling the worker. It is a no-op — leaving the Go default in place — when the operator already pinned GOMEMLIMIT, when no cgroup memory limit is readable (local dev, unconstrained container), or on non-Linux.

Returns the soft limit applied in bytes, or 0 when none was set.

Types

type Config

type Config struct {
	// Project mirrors the ingest worker's configured project tag; it
	// does not participate in node hashes.
	Project string

	// PollInterval is how often the dirty queue is polled.
	PollInterval time.Duration

	// Debounce is how long a session's dirty mark must be quiet before
	// it derives — ingest bursts settle into one derive.
	Debounce time.Duration

	// SweepInterval is the slow backstop cadence: enqueue every session
	// present in the raw layer, catching lost marks. A sweep also runs
	// once at startup.
	SweepInterval time.Duration

	// MaxDeriveLag bounds debounce starvation: a continuously
	// streaming session re-marks on every capture and never settles,
	// so a mark that has waited this long derives anyway. Live views
	// see bounded lag instead of waiting for a quiet gap.
	MaxDeriveLag time.Duration

	// SweepWindow bounds the backstop sweep to sessions with raw
	// activity in the last window, so a worker restart in a large org
	// re-enqueues recent sessions instead of stampeding the queue with
	// all of history (default 24h). Negative disables the bound and
	// sweeps every session ever captured — the escape hatch for a full
	// re-derive after a deriver fix.
	SweepWindow time.Duration

	// PageSize bounds one poll's batch. Sessions are still derived
	// strictly one at a time.
	PageSize int32

	// MaxPollBackoff caps the jittered exponential backoff applied
	// between polls while the queue is unreachable (DB outage). The
	// first failure retries after roughly PollInterval; each further
	// consecutive failure doubles the delay up to this cap.
	MaxPollBackoff time.Duration

	// DrainTimeout bounds the graceful-shutdown window: after the run
	// context is canceled, the in-flight derive may keep running this
	// long before its store operations are aborted. Locks release
	// either way.
	DrainTimeout time.Duration

	// Metrics optionally injects a pre-built metrics surface so the
	// hosting command can mount /metrics (and serve health probes)
	// before the store is even reachable — e.g. while --wait-for-db
	// retries. NewWorker builds a fresh one when nil.
	Metrics *Metrics
}

Config tunes the worker loop. Zero fields take the package defaults.

type Metrics

type Metrics struct {

	// Derives counts per-session derive attempts by outcome:
	// ok / error / locked (another worker holds the session) /
	// skipped (cleared by a peer or re-dirtied inside the debounce).
	Derives *prometheus.CounterVec

	// Requeued counts successful derives whose conditional clear
	// matched nothing — the session was re-dirtied mid-derive and
	// stays queued.
	Requeued prometheus.Counter

	NodesUpserted  prometheus.Counter
	NodesPruned    prometheus.Counter
	DeriveDuration prometheus.Histogram

	// UnknownCalls counts calls the classifier could not match to any
	// cataloged tell (derive.KindUnknown). A sustained non-zero rate
	// means the harness grew a new call shape the catalog is missing.
	UnknownCalls prometheus.Counter

	// ParseFailures counts raw rows whose request or response no
	// longer parses. Non-zero means a provider parser regressed or a
	// raw row predates a parser fix — the raw row is intact and
	// re-derivable later, but the signal deserves an alert.
	ParseFailures prometheus.Counter

	Sweeps        *prometheus.CounterVec
	SweepEnqueued prometheus.Counter

	PollErrors prometheus.Counter

	// ConsecutiveFailures mirrors the worker's in-memory outage
	// counter: non-zero means the queue is currently unreachable and
	// polls are backing off. Alert on sustained non-zero.
	ConsecutiveFailures prometheus.Gauge

	// QueueDepth and DeriveLag are refreshed once per successful poll:
	// how many sessions are dirty, and how stale the oldest dirty mark
	// is (now - oldest dirtied_at, seconds; 0 when the queue is empty).
	QueueDepth prometheus.Gauge
	DeriveLag  prometheus.Gauge
	// contains filtered or unexported fields
}

Metrics is the Prometheus surface for the derive worker. Each worker owns its own registry (same rationale as api.Metrics: tests scrape in isolation; the hosting command mounts Handler on its listener).

func NewMetrics

func NewMetrics() *Metrics

NewMetrics constructs the derive worker's counters on a fresh registry.

func (*Metrics) Handler

func (m *Metrics) Handler() http.Handler

Handler returns the /metrics scrape handler for this worker's registry.

func (*Metrics) Registry

func (m *Metrics) Registry() *prometheus.Registry

Registry exposes the registry so tests can scrape it.

type Store

type Store interface {
	// ListDeriveDirty returns dirty sessions that have settled OR
	// waited past the max-lag bound, oldest first.
	ListDeriveDirty(ctx context.Context, dirtiedBefore, firstDirtiedBefore time.Time, limit int32) ([]storage.DeriveQueueEntry, error)

	// GetDeriveDirty re-reads one queue entry (nil when clean).
	GetDeriveDirty(ctx context.Context, orgID, harnessID, harnessSessionID string) (*storage.DeriveQueueEntry, error)

	// ClearDeriveDirty removes the entry only if DirtiedAt is unchanged.
	ClearDeriveDirty(ctx context.Context, e storage.DeriveQueueEntry) (bool, error)

	// SweepDeriveDirty enqueues every raw-layer session active at or
	// after activeSince (zero time = everything).
	SweepDeriveDirty(ctx context.Context, activeSince time.Time) (int64, error)

	// DeriveQueueStats reports queue depth and the oldest dirty mark
	// (the worker's depth/lag gauges and readiness probe).
	DeriveQueueStats(ctx context.Context) (storage.DeriveQueueStats, error)

	// TryDeriveSessionLock takes the per-session advisory lock.
	// acquired=false (nil error) means another worker holds it.
	TryDeriveSessionLock(ctx context.Context, orgID, harnessID, harnessSessionID string) (release func(), acquired bool, err error)

	// RederiveSession re-derives and persists one harness session.
	RederiveSession(ctx context.Context, project, orgID, harnessID, harnessSessionID string) (*derive.RederiveReport, error)
}

Store is the storage capability surface the worker drives. The Postgres driver satisfies it; tests substitute a fake.

type Worker

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

Worker owns the poll/debounce/lock/derive/clear loop.

func NewWorker

func NewWorker(cfg Config, store Store, logger *slog.Logger) *Worker

NewWorker creates a derive worker. logger must be non-nil.

func (*Worker) Metrics

func (w *Worker) Metrics() *Metrics

Metrics exposes the worker's Prometheus surface so the hosting command can mount a scrape endpoint.

func (*Worker) Ready

func (w *Worker) Ready(ctx context.Context) error

Ready reports whether the worker can serve its purpose right now: the store answers and the dirty queue is pollable. This is the /readyz signal — it intentionally exercises the same query the poll loop depends on rather than a bare connection ping.

func (*Worker) Run

func (w *Worker) Run(ctx context.Context) error

Run blocks until ctx is canceled. One sweep runs immediately at startup (catching anything queued — or never queued — before this worker existed), then the poll and sweep tickers take over.

Shutdown is graceful: canceling ctx stops polling, but the in-flight derive keeps running on a detached context for up to DrainTimeout — finishing and clearing rather than aborting mid-write. A derive still running at the deadline is aborted; either way the advisory lock releases (release runs on a fresh background context) and Run returns nil.

Jump to

Keyboard shortcuts

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