Query gRPC API
Streaming query execution over gRPC
The query service exposes a gRPC API on port 9510 alongside the HTTP API. The gRPC API provides streaming query results, making it suitable for large result sets and real-time progress updates.
QueryService
service QueryService {
rpc ExecuteQuery(ExecuteQueryRequest) returns (stream ExecuteQueryResultFrame);
}ExecuteQuery returns a stream of frames. Clients receive schema, row batches, progress updates, and metadata as separate frames, allowing incremental rendering of results.
Frame Types
| Frame | Description |
|---|---|
schema | Table schema (sent before any row batches for that table) |
batch | Row data. Each batch has a result_iteration_id — when the ID changes, discard previous rows |
progress | Execution statistics: rows processed, chunks scanned/skipped, timing |
done | Query complete, close the stream |
error | Error with code, message, and source location |
metadata | Warnings and visualization hints |
Result Iterations
Row batches carry a result_iteration_id. When a new iteration starts, all previous rows should be discarded. This enables incremental refinement of results. The is_iteration_complete flag on each batch indicates whether the iteration is fully delivered.
Proto Definition
// The query service executes BQL queries and streams results back to the client.
service QueryService {
// Execute a BQL query and receive results as a stream of typed frames.
// The stream delivers Schema, RowBatch, Progress, and Metadata frames,
// terminated by a Completion frame or an Error frame.
rpc ExecuteQuery(ExecuteQueryRequest) returns (stream ExecuteQueryResultFrame);
// List the queries this pod is executing right now.
//
// Deliberately per-pod: a query runs on exactly one query pod, so the
// cluster-wide view is the union across pods. The gateway fans this out over
// the endpoint set it already resolves for balancing and merges the results,
// which is what `bzrk ps` consumes. Answering from a single pod would show
// only the fraction that landed there.
rpc ListActiveQueries(ListActiveQueriesRequest) returns (ListActiveQueriesResponse);
// Stop a query this pod is running, by the id `ListActiveQueries` reports.
//
// Cancellation is cooperative: the query stops starting new work at its next
// boundary rather than being interrupted, so this returns as soon as the
// signal is delivered, not when the work has wound down. The client running
// the query sees its stream end with gRPC `CANCELLED` — distinct from a
// Completion frame, so it can report the query as killed rather than done.
//
// Per-pod like the listing: the gateway fans out and exactly one pod matches.
rpc CancelQuery(CancelQueryRequest) returns (CancelQueryResponse);
}
message CancelQueryRequest {
string query_id = 1;
}
message CancelQueryResponse {
CancelOutcome outcome = 1;
}
// Why a `CancelQuery` call ended the way it did.
//
// An enum rather than a bool: "not on this pod" and "here, but already
// finishing" are different answers an operator wants distinguished, and once
// this ships a bool could only be widened by adding a second field and
// deprecating the first.
enum CancelOutcome {
CANCEL_OUTCOME_UNSPECIFIED = 0;
// This pod is not running that query. The normal answer from every pod but
// one when the gateway fans out, and not an error.
CANCEL_OUTCOME_NOT_FOUND = 1;
// The signal was delivered. Cancellation is cooperative, so the query stops
// starting new work at its next boundary rather than immediately.
CANCEL_OUTCOME_SIGNALLED = 2;
}
message ListActiveQueriesRequest {}
message ListActiveQueriesResponse {
repeated ActiveQuery queries = 1;
}
// One executing query.
message ActiveQuery {
// Server-side query id, and the handle `CancelQuery` takes.
string query_id = 1;
// Database the query resolves names against, as the client named it — the
// same reference type `ExecuteQueryRequest` takes, rather than a rendered
// string, so no server-side representation leaks into this contract.
berserk.DatabaseRef database = 2;
// How long it has been running.
uint64 elapsed_ms = 3;
// Since the last frame reached the client, and how many have. Elapsed time
// says how long a query has run; this says whether it is still delivering.
// A query four minutes in whose last frame was 200ms ago is working; one
// whose last frame was three minutes ago is stuck, and `elapsed_ms` alone
// cannot tell them apart. Equals `elapsed_ms` when nothing has been
// delivered yet.
uint64 idle_ms = 4;
uint64 frames_sent = 5;
// Which pod is running it. Filled in by the gateway as it merges, so a
// merged listing stays attributable.
string pod = 6;
// Execution statistics so far — the same `Progress` message this query's own
// client receives mid-stream, which is already the published contract for
// these numbers. A parallel listing-only copy would only drift from it.
// Unset before the first progress report: planning has not finished yet.
optional Progress progress = 7;
// The query as submitted, verbatim — the one field that answers "why is this
// running" without a second lookup. Truncated to a bounded length by the pod
// (with a trailing ellipsis) so one pathological client cannot make the
// listing unreadable.
string query = 8;
// Who asked for it and from where. See `QueryOrigin`.
QueryOrigin origin = 9;
// What the query will scan. See `QueryWindow`.
QueryWindow window = 10;
}
// The window a query scans, as the server resolved it.
//
// Not a claim and not a guess: these are the bounds the request carried, parsed
// and resolved against the server's captured `now`, and they are what the
// planner works from. A ten-minute lookback and a thirty-day one produce the
// same row otherwise — same elapsed time, same progress, wildly different
// amounts of work — so the span is what tells an operator whether a heavy query
// is heavy for a reason.
message QueryWindow {
// Event-time (`timestamp`) bounds as nanoseconds since the Unix epoch. Both
// set or neither: unset means the request imposed no range and the query
// carries its own bounds in KQL, which the coordinator derives while
// planning rather than here.
optional sint64 time_range_start_nanos = 1;
optional sint64 time_range_end_nanos = 2;
// Ingest-time (`ingest_time`) bounds — the slicing axis — same shape. Set
// independently of the event window: a query can bound either, both or
// neither.
optional sint64 ingest_time_range_start_nanos = 3;
optional sint64 ingest_time_range_end_nanos = 4;
// The `now` captured once when the request arrived, which every relative
// bound (`24h ago`) resolved against. It is what makes an absolute window
// readable as "the last hour" rather than a pair of timestamps.
sint64 now_unix_nanos = 5;
// This execution skips the map-reduce state cache on both read and write, so
// it rescans a window the cluster has already summarized. Rare and
// deliberate (benchmarking), and worth seeing next to an expensive row.
bool bypass_map_reduce_state_cache = 6;
}
// Where a query came from, split by who asserted it.
//
// The split is the point of the message: an operator reading `bzrk ps` must be
// able to tell a fact the server established from a string the client typed
// about itself. Merging them into one flat block would make the trustworthy
// half indistinguishable from the decorative one.
message QueryOrigin {
// Established server-side. Cannot be set by the caller.
AttestedOrigin attested = 1;
// Asserted by the caller about itself. Unverified.
ClaimedOrigin claimed = 2;
}
// The half the server established.
//
// Gateway resolves the caller's credential and injects these as `x-bzrk-*`
// headers; the public edge strips that whole namespace on inbound, so a client
// cannot forge them. Fields are empty for callers that reached the pod without
// passing gateway (in-cluster service calls), never wrong.
message AttestedOrigin {
// `"user"` | `"service"`.
string principal_type = 1;
// User id or service-principal id. The identity key; the name below is a
// label that can change.
string principal_id = 2;
// Email for users, service-principal name for services.
string principal_name = 3;
// `"session"` | `"cli_token"` | `"service_token"` | `"trusted_proxy"` — how
// the caller proved the principal, which is what separates a human at a
// browser from a token running unattended.
string authn_method = 4;
// Row id of the proving credential. A correlation handle for audit, not an
// authorization key.
string credential_id = 5;
// Originating client address as gateway resolved it (left-most
// `X-Forwarded-For`, else the TCP peer). Empty when the query did not come
// through gateway.
string client_ip = 6;
}
// The half the caller asserted about itself.
//
// Free-form and unverified by construction — a client can put anything here,
// including nothing. Display and attribution only: nothing in the engine may
// branch on these values. Fields are length-capped and stripped of control
// characters by the receiving pod so a hostile client cannot corrupt an
// operator's terminal or bloat the registry.
message ClaimedOrigin {
// Program that issued the query: `"bzrk-cli"`, `"grafana-plugin"`, `"ui"`.
string app = 1;
// Version of that program, so a bad query can be pinned to a release.
string app_version = 2;
// What the caller was doing: `"cli"`, `"dashboard"`, `"explore"`,
// `"alerting"`, `"variable"`.
string surface = 3;
// The human behind a shared credential. Grafana authenticates every
// dashboard in an org with one service token, so without this every panel in
// the company is the same principal; the plugin fills it from the Grafana
// login. Distinct from `AttestedOrigin.principal_name`, which is the identity
// the server actually verified.
string end_user = 4;
// Grafana dashboard the panel lives on. The uid is what a URL is built from
// (`/d/<uid>`); the title is what an operator recognises.
string dashboard_uid = 5;
string dashboard_title = 6;
// Panel within that dashboard, same pairing as the dashboard fields.
string panel_id = 7;
string panel_title = 8;
// The caller's own id for the request that issued this query. A dashboard
// refresh fires one per panel under a single id, which is what makes a
// refresh storm legible as one event rather than nine unrelated queries.
string client_request_id = 9;
// The agent driving the client, when one is: `"Claude Code"`, `"Codex"`.
// A query nobody is watching is a different thing from one a person is
// waiting on, and only the client knows which it is.
string agent = 10;
}
// Request to execute a BQL query.
// Versions are listed in client preference order; zero is invalid.
message CapabilityOffer {
string name = 1;
repeated uint32 versions = 2;
bool required = 3;
}
// Present, even with no offers, requests an explicit first-frame acknowledgement.
// Names: [a-z][a-z0-9_.-]{0,63}; at most 32 unique names and 16 unique versions/name.
message CapabilityNegotiationRequest {
repeated CapabilityOffer offers = 1;
}
message CapabilitySelection {
string name = 1;
uint32 version = 2;
}
// Exactly one offered version per accepted capability; omitted optional offers are declined.
// Selections apply to this query only and cannot change after this frame.
message CapabilityNegotiationResponse {
repeated CapabilitySelection selected = 1;
}
message ExecuteQueryRequest {
// The BQL query string to execute.
string query = 1;
// Start of the time range (inclusive). Accepts relative expressions like "1h ago"
// or absolute timestamps like "2024-01-01T00:00:00Z".
string since = 2;
// End of the time range (exclusive). Same format as `since`. Defaults to now.
string until = 3;
// IANA timezone name for time-based operations (e.g., "America/New_York").
// Defaults to UTC if not specified.
string timezone = 4;
// Database to resolve unqualified table names against. Required.
// Callers may send either a UUID (`identifier.id`) or a name
// (`identifier.name`); the server resolves names once per request via
// the metadata service. Empty / unset oneof rejected with
// `InvalidArgument`.
berserk.DatabaseRef database = 5;
// INTERNAL — QCS→QC relay only. The fields below are set by the
// query supervisor when it relays a query to a spawned per-query
// coordinator child over its private UDS. They carry the captured
// time semantics and the QWS member snapshot so the child neither
// re-parses `since`/`until` (drift) nor re-reads the cluster
// registry. The child does its own meta work (catalog, segment
// listing) over its own connection. The public endpoint rejects
// requests that set any of them with `InvalidArgument`.
// The supervisor's captured now(), as nanoseconds since the Unix
// epoch. When set, the child uses it verbatim instead of reading its
// own clock, and `since`/`until` are ignored in favor of the
// time-range nanos pair below.
optional sint64 now_unix_nanos = 6;
// Resolved query time range (inclusive start, exclusive end), as
// nanoseconds since the Unix epoch. Both set or both unset. Optional:
// when the API request carried no explicit range the child derives
// the scan window from the query itself, exactly like in-process.
optional sint64 time_range_start_nanos = 7;
optional sint64 time_range_end_nanos = 8;
// Postcard-serialized QWS cluster-member snapshot (the supervisor's
// polled `ClusterPool` view) the child builds its frozen pool from.
// The only meta-derived input relayed; everything else (catalog,
// segment listing) the child fetches itself.
optional bytes qws_members = 9;
// Ingest-time window (the slicing axis), independent of `since`/`until`
// (which bound event time). Same format as `since`/`until`. When set it
// restricts `ingest_time` and combines with any in-query
// `where ingest_time > ago(..)` bound (tighter wins). Optional on both
// ends; an unset end means "up to now".
string ingest_since = 10;
string ingest_until = 11;
// INTERNAL — QCS→QC relay only, the ingest-window counterpart of
// `time_range_start_nanos`/`end_nanos`: the supervisor's captured, resolved
// ingest window so the child doesn't re-parse `ingest_since`/`ingest_until`.
// Both set or both unset.
optional sint64 ingest_time_range_start_nanos = 12;
optional sint64 ingest_time_range_end_nanos = 13;
// Per-execution map-reduce-state-cache bypass. When true the coordinator
// treats this one query as if no state cache were wired: it skips both the
// reuse-read and the capture write-back, independent of the per-process
// `map_reduce_state_cache_disabled` config. Forces a full cold scan of an
// already-cached window for repeatable benchmarking, without evicting
// anything (no race with concurrent queries). Default false.
bool bypass_map_reduce_state_cache = 14;
// INTERNAL — QCS→QC relay only. The run id the supervisor already published
// to `bzrk ps`, so the child's coordinator adopts it instead of minting a
// second one. Every task id is built from the run id, so without this the
// child's whole log trail sits under an id no operator has. Ignored (and
// rejected like the other internal fields) on the public endpoint.
optional string internal_run_id = 15;
// What the client says about itself — program, version, and the dashboard or
// panel behind the query. Surfaced by `ListActiveQueries` and on the
// query's completion record, so an operator seeing a heavy query can tell
// where it came from. Optional, unverified, and never load-bearing: the
// server never branches on it. The verified half of the origin is derived
// from the caller's credential and cannot be sent here.
ClaimedOrigin claimed_origin = 16;
// Absent: legacy stream, no acknowledgement. Unsupported required offers fail
// before execution. result_encoding version 1 is the existing snapshot stream.
CapabilityNegotiationRequest capabilities = 17;
}
// A single frame in the streaming response for a query.
// A requested capabilities acknowledgement comes first. Then Schema frames,
// interleaved RowBatch and Progress frames,
// and finally a Completion or Error frame.
message ExecuteQueryResultFrame {
// Unique identifier for this request, echoed from the server.
string request_id = 1;
oneof payload {
// First frame when capabilities were requested, before any other frame.
CapabilityNegotiationResponse capabilities = 9;
// Table schema — sent once per table before any RowBatch frames for that table.
TableSchema schema = 2;
// A batch of result rows. Multiple batches may arrive for the same table and iteration.
// When `result_iteration_id` changes, discard all previous rows and start fresh.
RowBatch batch = 3;
// Signals that the query has completed and no more frames will be sent.
Completion done = 5;
// Cumulative execution statistics. Sent periodically during query execution.
// Each Progress frame supersedes the previous one.
Progress progress = 4;
// A query execution error. The stream ends after this frame.
Error error = 6;
// Warnings, partial failures, and visualization hints for the current result set.
ResultMetadata metadata = 7;
}
// INTERNAL — QCS→QC relay only. Postcard-encoded authoritative engine
// metadata (typed ExecutionStats, structural-type annotations,
// visualization, exact error) the spawned coordinator child attaches
// so the supervisor reconstructs results byte-exact with the
// in-process path. Never set on the public endpoint; clients ignore it.
optional bytes internal_sidecar = 8;
}
// Schema definition for a result table.
message TableSchema {
// Table name (e.g., "PrimaryResult", "ExtraTable_0", or a fork branch name).
string name = 1;
// Ordered list of columns in this table.
repeated Column columns = 2;
}
// A column definition within a table schema.
message Column {
// Column name.
string name = 1;
// Data type of the column.
ColumnType type = 2;
// Whether the column may contain null values.
bool nullable = 3;
// Berserk extension. Optional structural type for `dynamic` columns
// when the engine knows the inner shape (e.g. an `Array<Real>`
// produced by `make-series`, or an `Object{...}` produced by
// `bag_pack`). Absent means "opaque dynamic, infer by convention"
// — the same shape ADX returns. Clients that don't know this
// field ignore it (proto3 unknown-field tolerance) and continue
// operating against a plain `dynamic` column type.
optional StructuralType structural_type = 4;
}
// Structural type information for a `dynamic` column. Recursive
// because dynamics can be arrays of objects, objects with array
// fields, etc. Mirrors the engine-internal `AnnotationType` ADT.
message StructuralType {
oneof kind {
// Element is a scalar of the given type. The outer `Column.type`
// is `dynamic`; this `scalar` carries the inner type.
ColumnType scalar = 1;
// Element is a homogeneous array of the given inner shape.
// E.g. `make-series s = sum(v) on t step 5s` produces:
// - `s`: array_elem = scalar(LONG) — array of longs
// - `t`: array_elem = scalar(DATETIME) — array of datetimes
StructuralType array_elem = 2;
// Element is an object (property bag) with named fields. Each
// field carries its own structural type. E.g. `series_decompose`
// returns `{baseline, seasonal, trend, residual}` each of which
// is an array of reals.
ObjectSchema object = 3;
}
}
// Object schema for a `dynamic` column whose inner shape is a
// property bag with known named fields.
message ObjectSchema {
repeated ObjectField fields = 1;
}
// One named field within an `ObjectSchema`.
message ObjectField {
string name = 1;
StructuralType type = 2;
}
// BQL data types for column definitions.
enum ColumnType {
COLUMN_TYPE_UNSPECIFIED = 0;
COLUMN_TYPE_BOOL = 1;
COLUMN_TYPE_INT = 2;
COLUMN_TYPE_LONG = 3;
COLUMN_TYPE_REAL = 4;
COLUMN_TYPE_STRING = 5;
COLUMN_TYPE_DATETIME = 6;
COLUMN_TYPE_TIMESPAN = 7;
COLUMN_TYPE_GUID = 8;
COLUMN_TYPE_DYNAMIC = 9;
}
// A batch of rows belonging to a single table and result iteration.
message RowBatch {
// Name of the table this batch belongs to (matches a TableSchema.name).
string table_name = 1;
// Opaque identifier for the current result iteration. When this value changes,
// all previously received rows for all tables must be discarded — the new iteration
// represents a more complete result set that supersedes the previous one.
string result_iteration_id = 2;
// Rows in this batch. Column order matches the TableSchema for this table.
repeated ValueRow rows = 3;
// True when this is the last batch for this table in the current iteration.
// Clients should show the first batch immediately for fast feedback, then
// accumulate subsequent batches until this flag is true.
bool is_iteration_complete = 4;
// result_encoding v2 only: rows must be empty; decode this against the
// previous completed result for this table before delivering this batch.
RowDelta delta = 5;
}
// A chunk of a table replacement. Versions start at 1 per table per RPC.
// Every chunk of one iteration has the same version and base_version.
message RowDelta {
uint64 version = 1;
uint64 base_version = 2;
uint32 row_offset = 3;
uint32 row_count = 4;
repeated RowDeltaOperation operations = 5;
}
message RowDeltaOperation {
oneof operation {
RowDeltaCopy copy = 1;
RowDeltaInsert insert = 2;
}
}
// Ranges refer to the immutable previous completed result, never this iteration.
message RowDeltaCopy {
uint32 start = 1;
uint32 count = 2;
}
message RowDeltaInsert {
// Exact protobuf ValueRow bytes; equality includes all nested values.
repeated bytes rows = 1;
}
// A single row of dynamically-typed values.
message ValueRow {
// Cell values in column order. Each value corresponds to the column at the same
// index in the TableSchema.
repeated berserk.BqlValue values = 1;
}
// Cumulative execution statistics for the running query.
// Each Progress frame contains the total counts since the query started —
// always use the latest frame and discard earlier ones.
message Progress {
// Total rows processed across all chunks.
uint64 rows_processed = 1;
// Total number of chunks in the query's time range.
uint64 chunks_total = 2;
// Chunks that were scanned (read and evaluated).
uint64 chunks_scanned = 3;
// Chunks skipped because their time range didn't overlap the query range.
uint64 chunks_skipped_range = 4;
// Chunks skipped by bloom filter (no matching values).
uint64 chunks_skipped_bloom = 5;
// Chunks skipped by shard hash (not matching the target shard).
uint64 chunks_skipped_shard = 6;
// Total predicate evaluations performed during scanning.
uint64 predicate_checks = 7;
reserved 8;
reserved "bloom_checks";
// True when the query completed early via short-circuit optimization.
bool short_circuit_completion = 9;
// Total uncompressed bytes of scanned chunk bodies.
uint64 chunk_scanned_raw_body_size = 10;
// Total uncompressed bytes of skipped chunk bodies.
uint64 chunk_skipped_raw_body_size = 11;
// Total compressed bytes of skipped chunks.
uint64 chunk_skipped_compressed_size = 12;
// Wall-clock time spent scanning chunks (nanoseconds).
uint64 chunk_scan_time_nanos = 13;
// Total query execution time (nanoseconds).
uint64 query_time_nanos = 14;
// Total compressed bytes of scanned chunks.
uint64 chunk_scanned_compressed_size = 15;
// Per-bin completion progress for `summarize ... by bin()` queries.
optional BinProgress bin_progress = 16;
// Time spent waiting in the query queue before execution started (nanoseconds).
// Present when the query was queued due to concurrent query limits.
optional uint64 queue_wait_nanos = 17;
// Segment planning progress. Present during planning, absent after planning completes.
optional PlanningProgress planning_progress = 18;
// Total bytes of bloom filter data evaluated during bloom filtering.
uint64 bloom_filter_bytes = 19;
// Wall-clock time spent in merge and delivery across all query threads (nanoseconds).
uint64 merge_time_nanos = 20;
// Chunks that were scanned but yielded zero matching rows (false positives from pre-filtering).
uint64 chunks_empty_scan = 21;
// Chunks that encountered errors during row processing (e.g., type conversion failure).
uint64 chunks_errored = 22;
// Chunks skipped because required input fields were absent from the chunk schema.
uint64 chunks_skipped_required_fields = 23;
// Per-operator diagnostic telemetry in a stable key-value envelope.
repeated OperatorDiagnostics operator_diagnostics = 24;
// Chunks scanned where the range predicate could not be resolved at chunk level and
// required per-row evaluation (i.e., the hoisted range did not fully cover the chunk).
uint64 chunks_range_per_row = 25;
// Number of merge+delivery invocations summed across all query threads.
// Pair counter for `merge_time_nanos`.
uint64 merge_count = 26;
// Wall-clock time spent cloning reducer state during merge invocations
// (nanoseconds, summed across all threads).
uint64 reducer_clone_time_nanos = 27;
// Number of reducer-state clones recorded into `reducer_clone_time_nanos`.
uint64 reducer_clone_count = 28;
// Cumulative wall-clock time the coordinator spent building intermediate
// streaming snapshots (excludes the final result build). Nanoseconds.
uint64 snapshot_build_time_nanos = 29;
// Number of intermediate snapshot builds counted into
// `snapshot_build_time_nanos`. Pair counter; consumers can compute the
// average build duration that drives adaptive snapshot pacing.
uint64 snapshot_build_count = 30;
// Sum of thread CPU time spent inside the chunks arm of
// `query_thread_loop` (nanoseconds, across all worker threads). Pair
// with the chunks-arm wall (`worker_processing_time_nanos` engine-side):
// wall ≫ CPU implies threads are parked on I/O / mutexes /
// oversubscription; wall ≈ CPU implies CPU is the bottleneck.
uint64 worker_chunks_arm_cpu_nanos = 31;
// Sum of wall-clock time worker threads spent inside
// `reader.fetch_chunk_data().await` — i.e. the cache_server fetch path
// for chunk bytes. Nanoseconds, across all worker threads.
uint64 worker_fetching_chunk_nanos = 32;
// Sum of wall-clock time inside the synchronous chunk-body closure
// (decompression + per-row scan + branch accounting). Nanoseconds,
// across all worker threads. Pair with `worker_fetching_chunk_nanos`
// to split chunk-arm wall into I/O wait vs scan body.
uint64 worker_chunk_body_nanos = 33;
// Generic counter bag (`CustomStats` from #2830). Lets engine emit
// new measurements without a proto schema change for each one.
// Keys are dotted lowercase namespaces — `bloom.short_circuit_chunks`,
// `storage.bytes_fetched_cold`, `predicate.<i>.rows_kept`. Values are
// sums across all worker threads; the coordinator merges per-key
// before serializing.
map<string, uint64> custom_stats = 34;
// Cumulative cache-layer S3 retries observed answering this query.
// Surfaced by `SegmentCacheTrait::fetch_stats_snapshot`; the
// cache_server reports per-`OpenedItem` retries from its bounded
// `get_cache_handle_inner` loop and the client tallies them on the
// session. Non-zero is normal under flaky-backend conditions; a
// sustained delta says the backend is the bottleneck.
uint64 s3_retries_total = 35;
// Cumulative cache-layer S3 give-ups for this query — one per cache
// open that hit the bounded retry deadline or returned a
// non-retriable error. Each give-up typically corresponds to a
// PartialFailure for the segment it was trying to open.
uint64 s3_giveups_total = 36;
// Map-reduce-state cache: how many slices were seeded from cache
// this run. Non-zero means at least one interior slice was reused
// from a prior captured entry. Zero for unsliced queries, cold
// queries, and queries where the cache is disabled / not eligible.
uint64 map_reduce_state_cache_slices_reused = 37;
// Map-reduce-state cache: total slices in the coordinator's slicing
// grid for this run. Pair with `_slices_reused` for the per-run
// reuse fraction. Zero means the cache was never consulted (not
// sliced, no cache wired, or not a cacheable single branch).
uint64 map_reduce_state_cache_slices_total = 38;
// Opinionated query performance quality score, recomputed on every
// snapshot. Absent until the coordinator attaches it.
optional QueryQuality quality = 39;
}
// Query performance quality score. Every field is [0;1] with 1.0 = good;
// clients render as a percentage or however they like. See
// docs/dev/query-quality-score.md.
message QueryQuality {
// Headline = scanning * transformation * aggregation.
float total = 1;
// How well bloom/range/shard filters let us skip load+decompress.
float scanning = 2;
// Per-row mapper work; 1.0 = no transforms.
float transformation = 3;
// Reducer weight; 1.0 = light/bounded.
float aggregation = 4;
// Blend weight: 0 = pure plan estimate, 1 = pure live-stats measure.
float measured = 5;
}
// Diagnostic telemetry for a specific query operator.
message OperatorDiagnostics {
// Operator kind (e.g., "summarize", "join").
string kind = 1;
// Operator ID within the query plan.
uint32 operator_id = 2;
// Key-value diagnostic entries.
repeated KeyValue values = 3;
}
// A string key-value pair.
message KeyValue {
string key = 1;
string value = 2;
}
// Segment planning progress.
message PlanningProgress {
// Number of segments that have completed planning.
uint64 segments_done = 1;
// Total number of segments to plan.
uint64 segments_total = 2;
}
// Per-bin completion progress for `summarize ... by bin()` queries.
message BinProgress {
reserved 4;
reserved "overall_percent";
// Start value of the first bin boundary.
// Bin N covers [first_bin_start + N * bin_span, first_bin_start + (N+1) * bin_span).
sint64 first_bin_start = 1;
// Width of each bin.
uint64 bin_span = 2;
// Completion percentage (0-100) for each bin, one byte per bin.
// Index i corresponds to bin i. Value 100 means fully scanned.
bytes completion_percentages = 3;
// Numerator of overall completion: sum across slices of the number of
// segments whose planning has finished. Paired with overall_total to
// form the segment-weighted overall progress — larger slices (more
// overlapping segments) contribute proportionally more than small
// slices, which matches actual work better than a flat arithmetic mean
// across the per-bin bars. Both zero when no segment totals are known
// yet (initial planning still running).
uint64 overall_done = 5;
// Denominator of overall completion: sum across slices of the total
// segments overlapping that slice. Zero when unknown — clients should
// treat (0, 0) as "no overall percentage available yet".
uint64 overall_total = 6;
}
// Signals that the query has completed successfully.
// How a query ended. Every value but a fault is a controlled completion: the
// answer is well-defined, bounded by wherever the query stopped. A client that
// wants only unbounded answers checks for COMPLETED; the rest still carry
// results and stats worth using, and it is the client's call whether a result
// bounded that way suits its task.
enum QueryOutcome {
QUERY_OUTCOME_UNSPECIFIED = 0;
// Ran to the natural end of its input.
QUERY_OUTCOME_COMPLETED = 1;
// The input asked for no more — a satisfied `take N`.
QUERY_OUTCOME_COMPLETED_EARLY = 2;
// The submitting client hung up or asked to stop. It already knows.
QUERY_OUTCOME_STOPPED_BY_SUBMITTER = 3;
// An operator stopped it. The client did not ask for this.
QUERY_OUTCOME_STOPPED_BY_OPERATOR = 4;
// The server stopped it deliberately: an admission limit, a quota, a
// shutdown. Working as intended — nothing to report.
QUERY_OUTCOME_STOPPED_BY_SYSTEM = 5;
// The deadline ran out.
QUERY_OUTCOME_DEADLINE_EXCEEDED = 6;
// The server stopped it because something went wrong on our side. The
// result, if any, is partial. This says only that we consider the stop a
// failure — not what failed — so a client knows this one is worth
// reporting, citing the query id, where the outcomes above are not.
QUERY_OUTCOME_STOPPED_BY_FAULT = 7;
}
message Completion {
// Why the stream ended. Absent on servers predating this field, which is
// indistinguishable from UNSPECIFIED — treat that as COMPLETED.
QueryOutcome outcome = 1;
}
// A query execution error.
message Error {
// Error code discriminator (e.g., "UnknownFunction", "TypeMismatch").
string code = 1;
// Brief error title.
string title = 2;
// Support ticket ID for tracking.
string support_ticket_id = 4;
// Human-readable error message with source annotations.
string message = 5;
// Source code location where the error occurred.
Location location = 7;
// Structured error details as JSON.
string details = 8;
}
// A range within the query source text.
message Location {
uint32 start_byte = 1;
uint32 end_byte = 2;
uint32 start_line = 3;
uint32 start_column = 4;
uint32 end_line = 5;
uint32 end_column = 6;
}
// A partial failure for one or more segments that could not be read.
message PartialFailure {
// OBSOLETE — always empty, never populated. Segment ids are internal
// storage identifiers no client can act on; only the node holding the
// segments needs them, and it logs them locally against the query's
// trace id. The field stays in the schema so existing generated clients
// keep compiling; read `segment_count` instead.
repeated string segment_ids = 1 [deprecated = true];
// Human-readable error description.
string message = 2;
// How many segments this failure covers — enough to judge how much of
// the result is missing.
uint64 segment_count = 3;
}
// Visualization metadata from the `render` operator.
message VisualizationMetadata {
// Visualization type (e.g., "table", "timechart", "piechart", "linechart").
optional string visualization_type = 1;
// Visualization properties (e.g., x-column, y-columns, legend).
map<string, string> properties = 2;
}
// Advisory highlight pattern extracted from a positive text predicate.
// Best-effort only: the engine ships the patterns it filtered on, not the
// spans it actually matched. A missed or spurious highlight is not a
// correctness bug. Renderers that cannot honor a hint should skip it.
message HighlightHint {
// Literal to mark (or the regex anchor / pattern when `regex` is set).
string term = 1;
// equality | substring | prefix | suffix | word | word_prefix | word_suffix
string kind = 2;
bool case_sensitive = 3;
// Optional predicate field scope (unscoped search / wildcard is unset).
optional string field = 4;
// Optional regex pattern source. A renderer whose regex flavor rejects
// the pattern falls back to marking `anchor` (or `term`).
optional string regex = 5;
// Optional literal extracted for the SIMD prescan / fallback mark.
optional string anchor = 6;
// Output column names this hint applies to.
repeated string columns = 7;
}
// Metadata for the current result set.
message ResultMetadata {
// Data is MISSING from the result: rows the query should have covered were
// not read (unreachable segments, an unavailable stream group, a lost
// worker, a deadline cut). Consumers must treat the result as incomplete —
// the completeness watermark and result caching are suppressed whenever any
// entry is present. Contrast with `warnings`.
repeated PartialFailure partial_failures = 1;
// Visualization hints from the `render` operator, if present.
optional VisualizationMetadata visualization = 2;
// Advisory only: the result is COMPLETE, but how the query ran deserves
// attention (e.g. "summarize memory limit reached", "result truncated",
// planner fallbacks). Anything that implies missing rows belongs in
// `partial_failures`, never here.
repeated QueryWarning warnings = 3;
// Advisory query-term highlights. Sent once alongside the schema.
repeated HighlightHint highlights = 4;
}
// A warning produced during query execution.
message QueryWarning {
// Operator ID for source correlation.
uint32 operator_id = 1;
// Branch index for fork queries (0 = main branch).
optional uint32 branch_index = 2;
// Warning kind discriminator (e.g., "SummarizeMemoryLimit", "ResultTruncated").
string kind = 3;
// Source location where the warning originated.
optional Location location = 4;
// Human-readable warning message.
string message = 5;
// Structured warning details as JSON.
string details = 6;
}