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 ¶
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.
const SubjectRoot = "queryrun.progress"
SubjectRoot is the subject family carrying inflight progress.
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 ¶
EncodeTick renders a tick for the wire.
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 ¶
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 (*Poller) Tick ¶
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 ¶
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.
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 ¶
DecodeTick parses a tick off the wire.