pipeline

package
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Aug 2, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package pipeline runs one shard's supply cycle: it looks at what is due, asks the planner what it may serve, fetches that, and pushes it to the candidate cache the workers drain.

One cycle has five phases, and they name everything here:

sleep -> tallying -> planning -> fetching -> pushing

There is no loop and no goroutine in this package. Cycle paces itself - it sleeps at the FRONT, for whatever remains of the interval since the last cycle began - so a caller drives it in a tight loop and holds no cadence policy of its own:

for {
    select {
    case <-ctx.Done():
        return
    default:
    }
    observe(p.Cycle(ctx))
}

Any delay the caller adds between calls is therefore absorbed rather than added to the period.

Cycle NEVER returns an error, only a Result carrying one: every failure it can hit is already dealt with here, and the next cycle retries. A caller reads the Result for its log line and carries on.

SetInterval and SetMinGap are live and safe to call from anywhere; Cycle itself is not safe for concurrent use - one Pipeline per shard, driven by one goroutine.

Index

Constants

View Source
const (
	// DefaultInterval is the starting cycle period. A caller that derives its own replaces it through
	// SetInterval; this only keeps a freshly-built pipeline from scanning flat out.
	DefaultInterval = 50 * time.Millisecond
	// DefaultMinGap is the starting quiet time between cycles.
	DefaultMinGap = 20 * time.Millisecond
)
View Source
const NoBand = math.MaxInt

NoBand is the band reported when nothing is due - on this shard (Result.Band) or anywhere in the fleet (Result.GlobalBand).

Variables

This section is empty.

Functions

This section is empty.

Types

type Pipeline

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

Pipeline runs one shard's supply cycle.

Cycle is NOT safe for concurrent use - one Pipeline per shard, driven by one goroutine, is the whole intended shape, and the cadence timestamps are unsynchronized on that basis. SetInterval is the one exception and may be called from anywhere.

func New

func New(shard int, source Source, planner *planner.Planner, cache *candidates.Cache) (*Pipeline, error)

New returns a Pipeline for one shard, paced at DefaultInterval and DefaultMinGap until told otherwise. It is where a wiring mistake is caught, which is why Cycle itself has no error return to spend on one.

func (*Pipeline) Cycle

func (p *Pipeline) Cycle(ctx context.Context) Result

Cycle runs one full cycle - sleeping first for whatever the cadence still owes - and reports what happened. It never returns an error; see the package doc.

func (*Pipeline) Interval

func (p *Pipeline) Interval() time.Duration

Interval is the current cycle period.

func (*Pipeline) MinGap

func (p *Pipeline) MinGap() time.Duration

MinGap is the current minimum quiet time between cycles.

func (*Pipeline) SetInterval

func (p *Pipeline) SetInterval(d time.Duration)

SetInterval updates the cycle period - start of tallying to start of tallying - live. The next cycle picks it up: the value is read once per cycle rather than captured, so a caller that re-derives it takes effect without a restart. Zero paces nothing, which is what driving cycles back to back wants.

func (*Pipeline) SetMinGap

func (p *Pipeline) SetMinGap(d time.Duration)

SetMinGap updates the minimum quiet time between the END of one cycle and the START of the next. It is the fuse for the case the interval alone cannot cover: a cycle that outruns its interval would otherwise leave no gap at all and scan back to back, which is the duty cycle the interval exists to prevent. Zero disables it.

func (*Pipeline) Shard

func (p *Pipeline) Shard() int

Shard is the shard this pipeline supplies.

func (*Pipeline) WorkingFor

func (p *Pipeline) WorkingFor() time.Duration

WorkingFor is how long the current cycle has been inside its queries, or zero when none is - excluding the pace it sleeps first.

A DURATION rather than a bool, and that is the load-bearing part. It exists for a caller publishing this shard's liveness on its own clock, where a completed cycle is the ordinary evidence but one scan can outrun any sane publishing cadence on a deep backlog (phase one is O(backlog) on every dialect without the run-condition early-stop). A bool cannot serve that: a cycle whose scan fails INSTANTLY is also briefly inside its queries - building the error, recording the phase, logging it - and a caller sampling often enough will keep catching that. Measured at ~1.2% of samples with a failing scan, which is easily enough to keep a broken shard looking alive indefinitely. Only a cycle that has run longer than the caller's own expectations is evidence, and a duration lets the caller decide what that means.

Safe to call from any goroutine.

type Result

type Result struct {
	// Per-phase durations. Total spans tallying through pushing and EXCLUDES Slept, so it is the cycle's
	// cost rather than its period; the period is Slept+Total.
	Slept    time.Duration
	Tallying time.Duration
	Planning time.Duration
	Fetching time.Duration
	Pushing  time.Duration
	Total    time.Duration

	// Band is this shard's own minimum due band, GlobalBand the best band any shard holds. Band >
	// GlobalBand means this shard was outranked and served nothing - the ordinary strict-priority case,
	// not a fault. Either is NoBand when nothing is due.
	Band       int
	GlobalBand int

	// Selected is how many candidates were pushed, Discarded how many un-popped ones that replaced.
	// Discarded rising toward Selected means the cycle is turning faster than the workers drain.
	Selected  int
	Discarded int

	// Err is set when the cycle ended early, wrapped with the phase that failed. The cycle has already
	// dealt with it; this is for the log line.
	Err error
}

Result is one cycle's outcome, for logging and metrics. Nothing here is a control signal: a caller reads it and carries on.

type Source

type Source interface {
	// ScanBand reports this shard's minimum due priority band and one tally per fairness key at that
	// band, or math.MaxInt and no tallies when nothing is due here. It returns O(distinct keys) rows,
	// never O(backlog); each tally's Count is expected to be capped at the planning capacity.
	ScanBand(ctx context.Context, shard int) (band int, tallies []planner.Tally, err error)
	// FetchSteps returns up to perKey step ids for each of keys at the given band, OLDEST FIRST within
	// each key. The ordering is the Source's responsibility - the cycle consumes the lists in order and
	// does not re-sort them. A key may come back short or missing.
	FetchSteps(ctx context.Context, shard, band int, keys []string, perKey int) (map[string][]int, error)
}

Source is the database side of a cycle - the only two queries a cycle makes, both read-only.

Jump to

Keyboard shortcuts

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