queryprogress

package
v0.0.19 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package queryprogress observes inflight query runs and publishes what it sees on the bus — the E7 extension point of doc/explanation/query-system-requirements.md.

The requirement is R8, issuer-decoupled observability: a run's progress must be visible to parties other than the connection holder. In-band progress headers cannot provide that — they arrive only on the socket that issued the query, so a second window, or ops tooling, or a dashboard watching a run somebody else started, has no way in. Polling `system.processes` does, because the server already knows about every run regardless of who is holding its connection.

Three guarantees define what a consumer may conclude, and two of them are about what this package refuses to do:

  • **Ticks only.** The poller NEVER synthesises a terminal frame. A run vanishing from system.processes is ambiguous — finished, killed, or failed — and guessing would let a wrong outcome into the one place R9 makes authoritative. Terminal truth comes from the result path or from query_log; this plane does not compete with it.
  • **Absence means nothing.** A run shorter than one tick never appears at all. No progress frame is not evidence of no progress, of a stall, or of anything else.
  • **Self-excluding.** The poller's own statements are structurally invisible: it reports only ids that were explicitly registered, and it never registers its own.

Staleness is bounded by the tick and by nothing else: a tick reports what the server said at that moment, and the next says nothing until it arrives.

Index

Constants

View Source
const (
	MinInterval     = 100 * time.Millisecond
	MaxInterval     = 60 * time.Second
	DefaultInterval = 500 * time.Millisecond
)

Interval bounds. A tick costs one small query against system.processes, so the floor is about not hammering a server rather than about cost here.

View Source
const SubjectRoot = "queryrun.progress"

SubjectRoot is the subject family carrying inflight progress.

View Source
const SubjectWildcard = SubjectRoot + ".>"

SubjectWildcard matches every run's progress subject. It is the pattern a subscriber that watches more than one run asks for, and the pattern the poller publishes under.

Variables

This section is empty.

Functions

func EncodeTick

func EncodeTick(t Tick) (payload []byte, err error)

EncodeTick renders a tick for the wire.

func FrameOf

func FrameOf[T any](t Tick) (f runstream.Frame[T])

FrameOf lifts a received tick into a runstream progress frame.

func PublishFilter

func PublishFilter() (f app.SubjectFilter)

PublishFilter is the capability a poller needs. One process's poller publishes for every run it watches, so the grant is the whole family.

func Subject

func Subject(queryID string) (subject string)

Subject returns the subject a run's ticks are published on. queryID must already have passed runid.Valid — the poller only ever watches ids that have.

func SubscribeFilter

func SubscribeFilter() (f app.SubjectFilter)

SubscribeFilter is the capability an observer needs. Holding it means seeing the progress of runs this process did not issue, which is the entire point of the plane (R8) and the reason it is a capability rather than an assumption.

Types

type Options

type Options struct {
	// Endpoint is the ClickHouse HTTP endpoint of the ONE server this
	// poller watches. A poller is bound to a single server because
	// system.processes is per-server: a run is only visible where it runs
	// (R10), and pointing one poller at a cluster address would silently
	// report whichever member answered.
	Endpoint string
	// User and Password authenticate the polling statement.
	User     string
	Password string
	// HTTPClient defaults to a client with Interval-derived timeout.
	HTTPClient *http.Client
	// Bus receives the ticks. Needs PublishFilter.
	Bus app.BusI
	// Interval is the tick period, clamped to [MinInterval, MaxInterval].
	Interval time.Duration
	Log      zerolog.Logger
}

Options configures a Poller. Endpoint and Bus are required.

type Poller

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

Poller watches one server and publishes a tick per registered run per tick. Safe for concurrent use: Watch and Unwatch may be called from any goroutine while the loop runs.

func New

func New(opts Options) (inst *Poller, err error)

New builds a poller. It does not start ticking; call Start.

func (*Poller) Close

func (inst *Poller) Close() (err error)

Close stops the tick loop and waits for it to finish.

func (*Poller) Start

func (inst *Poller) Start()

Start begins ticking until Close.

func (*Poller) Tick

func (inst *Poller) Tick(ctx context.Context)

Tick performs one observation: a single query covering every registered run, and one published frame per run the server reported as inflight. Exported so a caller can drive the cadence itself, and so tests need no wall clock.

One query per tick, not one per run: a watch set of thirty runs must not become thirty round trips, and batching also makes the ids share a single consistent view of the server's process list.

func (*Poller) Unwatch

func (inst *Poller) Unwatch(queryID string)

Unwatch deregisters a run.

The CALLER decides when: deregistration belongs to whoever holds the result path, at the moment it delivers the terminal frame. The poller will not do it on its own, because the only signal it has — the run leaving system.processes — cannot distinguish finished from killed from failed, and acting on it would amount to inventing an outcome.

func (*Poller) Watch

func (inst *Poller) Watch(queryID string) (err error)

Watch registers a run. Ticks are published on Subject(queryID) until Unwatch.

Registration is what makes a run visible here at all, and it is also the self-exclusion: the poller's own statements are never registered, so they can never be reported.

func (*Poller) Watched

func (inst *Poller) Watched() (ids []string)

Watched returns the registered ids, sorted.

type Tick

type Tick struct {
	QueryID  string             `json:"queryId"`
	Seq      uint64             `json:"seq"`
	Progress runstream.Progress `json:"progress"`
}

Tick is the wire payload of one progress observation.

It is deliberately not a whole runstream.Frame: a frame is generic in its data payload, and this plane never carries data — only the advisory progress of a run somebody else is reading. FrameOf converts a received tick into a frame of whatever payload type the subscriber uses.

func DecodeTick

func DecodeTick(payload []byte) (t Tick, err error)

DecodeTick parses a tick off the wire.

Jump to

Keyboard shortcuts

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