* ci(query-regression): bump RUNNER_IMAGE_EPOCH to 7 (#9395) ci(query-regression): bump RUNNER_IMAGE_EPOCH to 7 for image m-0xihbfzm5xpbxolybo6i Signed-off-by: greptimedb-ci <greptimedb-ci@greptime.com> Co-authored-by: greptimedb-ci <greptimedb-ci@greptime.com> (cherry picked from commit886e02f0dc) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): apply offset to subquery evaluation window (#9364) * fix(promql): apply offset to subquery evaluation window `prom_subquery_expr_to_plan` destructured `SubqueryExpr` without reading `offset`, so `<subquery>[range:step] offset <d>` planned exactly the same window as the un-offset form and silently returned data for the wrong time range. The plain vector/matrix-selector paths already threaded the offset through `selector_to_series_normalize_plan` and `RangeManipulate`. Shift the inner evaluation window back by the offset and pass the offset to the subquery's `RangeManipulate`, which maps the inner samples forward onto the evaluation timeline before bucketing them into ranges. This matches Prometheus, whose `evaluator.subqueryTimeRange` evaluates the inner expression over `(start - offset - range, end - offset]` and whose `evalSubquery` then hands the samples to the outer range-vector function as a `MatrixSelector` that still carries the subquery offset. An offset on the inner selector composes additively, as `subqueryTimes` documents. `RangeManipulate`'s protobuf message has no offset field and recovers it on decode from an immediately underlying `SeriesNormalize`. Since `RangeManipulate` is commutative in `dist_plan` and can be pushed below a `MergeScan`, insert that carrier node so the offset survives distributed planning instead of decoding as zero. Known divergence, unchanged by this commit: Prometheus anchors subquery step points on absolute epoch multiples of the step, while GreptimeDB anchors them on the evaluation start. The two agree whenever the offset is a multiple of the subquery step; the added sqlness cases stay within that range. Closes #9330 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Aarav <aaravsjadav@gmail.com> * docs(promql): correct the subquery-offset rationale and pin the histogram shift Follow-up toe132c267, which stated two things inaccurately and left one path untested. No behaviour change beyond a conditional clone. The `SeriesNormalize` carrier was described as existing "so the offset survives distributed planning", implying standalone is unaffected. That is wrong. `local_offset` is read on the substrait decode path (`range_manipulate.rs`, `instant_manipulate.rs`), and GreptimeDB routes PromQL plans through `MergeScan`/substrait in standalone too -- the standalone results `promql/encode_substrait.result` and `precisions.result` both show `MergeScan [is_placeholder=false, remote_input=[...]]`. Deleting the carrier therefore empties subquery-with-offset results in standalone as well, while `count_over_time_subquery_with_offset` keeps passing because it asserts the pre-serialization plan. Say so at the node, so the next reader does not remove it believing standalone is safe. The carrier is also not semantically inert: `SeriesNormalizeStream::normalize` shifts native histogram `start_timestamp` payloads by `offset`. On the subquery path that runs on a computed inner result, on top of any offset the inner selector already applied, and nothing pinned it. It is consistent -- `RangeManipulate` moves the millisecond time index forward by the same `offset_ms` -- so the distance from a histogram's start timestamp to the sample carrying it, which reset/rate detection reads, is invariant. Add `subquery_offset_shifts_histogram_start_and_time_index_together`, which executes the subquery's node stack over native histogram samples for a zero and a non-zero inner offset. It cannot be a sqlness case: native histograms are a struct column with no SQL type or literal (`sql_data_type_to_concrete_data_type` rejects structs) and the sqlness runner speaks only MySQL/Postgres, so such rows only arrive over gRPC/remote-write v2. The divergence note blamed sub-step offsets. Measured, that condition is too narrow: an unaligned evaluation timestamp alone diverges, with no offset at all -- `sum_over_time(fine[20s:10s])` at t=57 samples 47s and 57s here against Prometheus's 40s and 50s. The real condition is that `(start - offset)` is not a multiple of the subquery step, and it is pre-existing: the committed `tql eval (359, 359, '1s') sum_over_time(metric_total[60s:10s])` case is already an instance of it (`359 - 60 + 10 = 309`). Reword the comment and the sqlness header accordingly. A sub-step offset stays accepted: `foo[20s:10s] offset 5s` is valid PromQL, erroring on it would be a regression and would not close the gap anyway. This repo has no PromQL-compatibility documentation page, so there is nowhere else to record it. Also stop cloning the series key columns when there is no offset, and stop the `offset 0s` sqlness comment implying its parser error is evidence about this fix -- the same message comes from a `u32` overflow in `promql-parser`'s shared duration check (`offset 9999999999d`). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Aarav <aaravsjadav@gmail.com> * refactor(promql): trim subquery-offset comments and tests per review Shorten the planner and sqlness comments to the semantic contract, build `SeriesDivide` with a clone instead of an `Option`, and drop the normalize.rs histogram test and the `offset 0s` parser case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bvab9gpXYjKyLN8Mo6JWWL Signed-off-by: Aarav <aaravsjadav@gmail.com> * fix(promql): record the subquery offset as the range fold offset The subquery's `RangeManipulate` now shifts the payload timestamps by the offset, so `predict_linear` over an offset subquery must recover the evaluation time with that offset instead of 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bvab9gpXYjKyLN8Mo6JWWL Signed-off-by: Aarav <aaravsjadav@gmail.com> --------- Signed-off-by: Aarav <aaravsjadav@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commitd6474b961c) Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: retry failed nightly release on following weekdays (#9394) Previously the nightly release was scheduled only on Mondays, so a failed run left users without nightly builds for a whole week. Now the schedule triggers every weekday at 00:00 UTC, but the release only proceeds when the latest published nightly release is older than NIGHTLY_RELEASE_MAX_AGE_DAYS (5) days. The check runs in allocate-runners before any EC2 runner is allocated, and gates all schedule-driven jobs (including the Slack notification, so a skipped nightly is silent). Tag pushes and manual dispatches are unaffected. Signed-off-by: Ning Sun <sunning@greptime.com> (cherry picked from commita7f99b3425) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(client): yield Flight batches and affected rows without waiting for next message (#8918) * fix(client): avoid Flight metrics lookahead stalls Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * refactor(client): extract trailing Flight metrics task Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(client): synchronize trailing metrics and bound cancellation Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commitabf1396c28) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): resolve dotted column names as unqualified columns (#9391) * fix(promql): resolve dotted column names as unqualified columns col(), From<&str>/From<String> for Column and string join keys go through Column::from_qualified_name, which splits `service.name` into relation `service` and column `name` and lowercases unquoted identifiers. Build PromQL column references with Column::from_name / ident() instead. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(servers): resolve remote read matcher labels as unqualified columns Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test(promql): cover same-name columns differing in case and without() Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commit218000e21b) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): align timestamp(), label_join and label_replace with Prometheus semantics (#9385) * fix(promql): align timestamp() and label_join with Prometheus semantics - timestamp() over a selector reports the selected sample's timestamp, without adding the offset. - timestamp() over any other expression reports the evaluation time instead of the input value. - label_join that overwrites an existing label rejects duplicate label sets at runtime. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): align label_replace and label_join edge cases with Prometheus - label_replace leaves non-matching series unchanged instead of copying the source value into the destination label. - label_replace may overwrite an existing label; duplicate label sets are rejected at runtime like label_join. - label_join with no source labels removes the destination label, and an empty source label name is rejected. - Empty label values produced by these functions are NULL, the same as an absent label. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): join absent labels as empty strings in label_join concat_ws skips NULL arguments together with their separator, so a label that is absent (NULL) dropped its separator, e.g. label_join(label_join(vector(1), "a", "", "missing"), "b", "-", "a", "a") lost b="-". Read every source label as coalesce(label, '') like label_replace does. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): skip rows without a sample in timestamp() timestamp() replaced the input fields with the timestamp before the empty-value filter ran, so a row whose fields were all NULL got a timestamp, e.g. timestamp(-m) for a NULL sample. Filter such rows before the projection, for both the selector and the evaluation-time paths. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commit78a7b93292) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(mito2): truncate parquet column index min/max for SST writes (#9420) * fix(mito2): truncate parquet column index min/max for SST writes The SST writer and the bulk part encoder disabled column index truncation together with statistics truncation in #6977. Mito never reads the column index, but every page of a large string or binary column still stored its full min and max values, uncompressed. Truncate the column index to the parquet default of 64 bytes and keep chunk statistics untruncated, since primary key pruning decodes them. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * docs(mito2): note that the column index must not be used to decode primary keys Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commita314ac4281) Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: upgrade EC2 runner action to restore runner registration (#9430) ci: update EC2 runner action for minimum runner version enforcement Signed-off-by: evenyag <realevenyag@gmail.com> (cherry picked from commit0d502e1fc1) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(catalog): skip backend batch_get on full cache hits (#9431) fix(catalog): return cached batch values without an empty backend call Count backend batch_get invocations in regression tests and cover empty input. Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commitacebd6b35b) Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: make query regression non-blocking for scheduled nightly releases (#9434) * ci: make query regression non-blocking for scheduled nightly releases Scheduled nightly releases no longer gate publishing on the query regression release test. The test still runs (validation policy stays 'all' for automatic releases) and uploads its report, but any result - failure, timeout, cancellation, or skip - no longer blocks image publishing or the GitHub release for schedule-triggered runs. Since reusable-workflow caller jobs cannot use continue-on-error, the gate is relaxed at the consumers instead: the if conditions of release-images-to-dockerhub and publish-github-release now also pass when github.event_name == 'schedule' regardless of the query-regression result. All other gates (runner allocation, artifact builds, prepare-release-validation) are unchanged. Tag-push and manual-dispatch releases remain fully blocking unless a skip policy is explicitly chosen via the release_validation input. Signed-off-by: Ning Sun <sunning@greptime.com> * ci: preserve downstream nightly release jobs after regression failures Signed-off-by: evenyag <realevenyag@gmail.com> --------- Signed-off-by: Ning Sun <sunning@greptime.com> Signed-off-by: evenyag <realevenyag@gmail.com> Co-authored-by: evenyag <realevenyag@gmail.com> (cherry picked from commita9ce6e2d01) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix: coalesce equal histogram quantile bucket boundaries (#9451) * fix: coalesce equal histogram quantile bucket boundaries Merge numerically equivalent bucket boundaries before monotonicity repair, preserving the allocation-free path for unique boundaries. Share coalescing with histogram_fraction and cover duplicate boundaries with unit and SQL regressions. Fixes #9443 Signed-off-by: evenyag <realevenyag@gmail.com> * fix: keep duplicate infinity buckets in one histogram group Confirm classic histogram completion at a series/timestamp boundary or EOF instead of the first positive infinity. Retain lookahead across batches and reject incomplete optimistic groups so equivalent infinity labels reach numeric coalescing together. Add stream and SQL regressions for duplicate infinity labels, including established bucket counts and EOF. Correct older fixtures to use distinct keys for distinct histogram groups. Signed-off-by: evenyag <realevenyag@gmail.com> * fix: avoid buffering histogram groups with invalid trailing bounds Discover candidate bucket counts at the first group boundary and let optimistic validation fall back to safe mode for malformed bounds. Document discovery and cover trailing invalid and null labels with a focused streaming regression. Signed-off-by: evenyag <realevenyag@gmail.com> --------- Signed-off-by: evenyag <realevenyag@gmail.com> (cherry picked from commit97913b4b3a) Signed-off-by: WenyXu <wenymedia@gmail.com> * test(sqlness): wait for mirrored inserts before flushing streaming flows (#9424) Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commitb78dc4b33b) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix: accept a bool value for the CLI --auto-create-schema flag (#9450) * fix: accept a value for the CLI --auto-create-schema flag clap derives ArgAction::SetTrue for bool fields, so --auto-create-schema took no value and could only set true, which is already the default. Users could not turn automatic schema creation off from the CLI, unlike metasrv's auto_create_schema = false. Set action = clap::ArgAction::Set so the flag accepts an explicit true/false value while keeping true as the default. Signed-off-by: fengyue-xve <1759519137@qq.com> * chore: enhance CLI argument for auto_create_schema option Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Signed-off-by: fengyue-xve <1759519137@qq.com> Co-authored-by: dennis zhuang <killme2008@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> (cherry picked from commit329edc88ec) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(otlp): coerce empty and "-" trace attribute strings to null (#9461) * fix(otlp): coerce empty and "-" trace attribute strings to null Instrumentations fill numeric attributes from absent headers with an empty string (PSR-7 getHeaderLine returns ""). Coercing that into an existing numeric column failed and rejected the whole span. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test: trim redundant row from empty-string rewrite test Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commitd640ab8bc5) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(meta-client): bound heartbeat handshake timeout (#9438) * fix(meta-client): bound heartbeat handshake timeout Signed-off-by: Aetherance <inkToAether@gmail.com> * chore(meta-client): fix test license header Signed-off-by: Aetherance <inkToAether@gmail.com> * test(meta-client): prefix heartbeat tests with test Signed-off-by: Aetherance <inkToAether@gmail.com> --------- Signed-off-by: Aetherance <inkToAether@gmail.com> (cherry picked from commit9c69e381ae) Signed-off-by: WenyXu <wenymedia@gmail.com> * docs(config): align example configs and config.md with actual defaults (#9460) * docs(config): align example configs and config.md with actual defaults Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test(cmd): compare example defaults inside optional sections Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commite2fd8c5695) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(partition): preserve region mapping during repartition (#9458) Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commit90f117b957) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): restore exact filtering for nullable label matchers (#9418) * fix(promql): restore exact filtering for nullable label matchers Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test: verify compound filter semantics and last-row selection Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test: consolidate label filter regression coverage Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * refactor: separate filter evaluation and timestamp adaptation Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * refactor: inline predicate and timestamp tests Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * refactor: keep direct IN predicates outside simple filters Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix: coerce partition bounds before simple filtering Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test: cover partition bound overflow after timestamp widening Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commit526dd48bda) Signed-off-by: WenyXu <wenymedia@gmail.com> * chore: update rust toolchain to 1.98.1 (#9429) (cherry picked from commit7f0f592b6b) Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: update dev-builder image tag (#9469) Signed-off-by: greptimedb-ci <greptimedb-ci@greptime.com> Co-authored-by: greptimedb-ci <greptimedb-ci@greptime.com> (cherry picked from commit5affb50bb5) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(metasrv): retry failed heartbeat address updates (#9437) * fix(metasrv): retry failed heartbeat address updates Signed-off-by: WenyXu <wenymedia@gmail.com> * refactor(metasrv): move shared heartbeat helpers to utils Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commit65b9a35692) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(operator): bound FlowMirrorTask pending rows to prevent frontend OOM (#9421) * fix(operator): bound FlowMirrorTask pending rows to prevent frontend OOM When flownode is slow or unreachable, FlowMirrorTask::detach spawns unbounded tasks that each pin a full insert batch for ~10s (gRPC timeout). With continuous writes, in-flight memory grows unboundedly (throughput x 10s x peers), causing frontend-write OOMKill and a million-per-hour ERROR storm. Add a MAX_MIRROR_PENDING_ROWS cap (1M rows) on Inserter: detach fetch_adds the batch's row count before spawning; if the cap is exceeded the reservation is rolled back, the batch is dropped, a DIST_MIRROR_DROPPED_ROW_COUNT metric is bumped, and a warn is logged instead of spawning. On normal completion the reservation is released in both success and error paths. This keeps core writes alive while degrading flow mirror writes to best-effort, which is the intended trade-off: protect the primary insert path at the cost of possible flow aggregation gaps. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: address review - balanced reservation, gauge race, log rate limit P1: Compute pending_rows() from actual per-peer payloads in self.requests instead of the pre-fanout num_rows field. This fixes under-reservation for same-table multi-region batches and over-release in multi-flownode fanout. Each peer task now releases exactly its own share so reservations and releases balance. P2: Replace load+set gauge publication with add/sub symmetry. reserve_mirror_pending_rows does fetch_add + gauge.add, release does fetch_update + gauge.sub, eliminating the stale-value race. P3: Rate-limit the dropped-batch warning to at most once per 10s via MirrorDropLog (atomic compare_exchange), accumulating batch/row counts between reports. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: fix race in dual-peer test - wait for gauge release before unlocking The dual-peer test previously only waited for the pending budget to return to its start value before releasing MIRROR_METRIC_TEST_LOCK. But release_mirror_pending_rows moves the budget before the gauge, so a worker's gauge.sub could still be in flight when the next test acquires the lock and records its baseline, polluting the result. Now waits for both pending == start AND gauge == gauge_before. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(operator): preserve instant writes and limit mirror failure logs Reject saturated requests containing instant-TTL rows with the existing retryable write-admission error before mirror or datanode dispatch. Keep normal source-table inserts independent of best-effort mirror drops. Reuse a smaller process-wide event limiter for both dropped batches and failed peer tasks, reporting at most once per 10 seconds with failure count, peer context, and error chain. Cover actual mirror construction, mixed and instant admission, fast/slow peer shares, failed-task release, and log concurrency. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(operator): reject oversized instant mirror batches permanently Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commit198d92407c) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): align subqueries to the absolute evaluation grid (#9400) * fix(promql): align subqueries to the absolute evaluation grid Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * docs(promql): streamline subquery comments Remove redundant arithmetic narration and restated test comments. Keep grid, fallback, validation and overflow contracts, and preserve all regression tests and assertions. Regenerate SQLness output only for comment echoes. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commit0d2cb1aa7e) Signed-off-by: WenyXu <wenymedia@gmail.com> * build(deps): update opensrv-mysql to fix MariaDB 12.3 USE information_schema hang (#9357) Update opensrv-mysql from f3135e5 to 1f3d88c, which replies a proper EOF packet to COM_FIELD_LIST instead of a malformed OK packet that MariaDB 12.3 misparses and hangs on USE information_schema. Fixes #9158 Signed-off-by: tian1220A <1573612521@qq.com> (cherry picked from commit21536a092b) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): keep vector labels when a computed scalar is on the right (#9478) * fix(promql): keep vector labels when a computed scalar is on the right `metric + time()` or `metric * scalar(other)` planned the join with the scalar side's context, which has no tags, so every series collapsed into one unlabeled series. Use the vector side's context when the right operand is scalar-typed. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test(promql): update results for labels kept with a computed scalar on the right v * pi() and v + time() now keep the vector's labels and time index name, as in Prometheus. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): qualify columns of an unnamed operand in vector joins A comparison filter's projection has no table name, so after the join its time index resolved unqualified and was ambiguous when the other operand had the same column. Also keep the empty-metric branch unchanged for scalar left operands. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test(promql): cover comparison filters joined with a scalar Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commit6da5352c53) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(otlp): convert boolean metric attributes into label values (#9468) Signed-off-by: fengyue-xve <1759519137@qq.com> (cherry picked from commit2ebb03fbf6) Signed-off-by: WenyXu <wenymedia@gmail.com> * perf(catalog): optimize single-table lookups in information_schema.partitions (#9473) * perf(catalog): optimize single-table lookups in information_schema.partitions Signed-off-by: 风月 <1759519137@qq.com> * style: apply rustfmt formatting to partitions tests Signed-off-by: 风月 <1759519137@qq.com> --------- Signed-off-by: 风月 <1759519137@qq.com> (cherry picked from commit4ade13d9b6) Signed-off-by: WenyXu <wenymedia@gmail.com> * test(promql): cover label_replace normalization with distinct series (#9485) The duplicate-label-set rejection must not fire when a label rewrite keeps every series distinct through another label. Normalizing mixed histogram boundary representations (`10.0` -> `10`) while `instance` still differs is the false positive reported in #9444. The check already accounts for it since it became a per-timestamp row count instead of a planning-time rejection, but no fixture covered the positive case. Add one, together with the aggregation variant that produces a genuine duplicate and must still fail. Signed-off-by: fengyue-xve <1759519137@qq.com> (cherry picked from commit764ab8bc1a) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): return no point for zero-interval irate/idelta (#9455) fix(promql): irate/idelta return no point for a zero-ms sample gap Signed-off-by: Niladri Adhikary <niladrix719@gmail.com> (cherry picked from commit0dde3a7b70) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(metasrv): stop parking the heartbeat response on the region supervisor queue (#9474) * fix(meta-srv): stop parking the heartbeat response on the region supervisor queue Signed-off-by: lczllx <2181719471@qq.com> * test(meta-srv): cover the heartbeat response path at the handler level Signed-off-by: lczllx <2181719471@qq.com> --------- Signed-off-by: lczllx <2181719471@qq.com> (cherry picked from commitd1151b0ed6) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): keep metric name for comparison operators (#9332) * fix(promql): keep metric name for comparison operators `collect_metric_names` dropped the response `__name__` for every binary operator except `and` / `or` / `unless`, so comparisons such as `node_load1 > 5` came back without the metric name. Grafana rendered them as `{instance="..."}` and alert templates reading `$labels.__name__` got nothing. Follow Prometheus' `shouldDropMetricName` / `resultMetric`: arithmetic operators drop the name, comparisons keep the left-hand name, except when the comparison returns a bool or is a one-to-one `on(...)` match. A `group_left` / `group_right` modifier does not reduce the labels, so the name is kept there. Both the HTTP PromQL API and the gRPC gateway share `retrieve_metric_name_and_result_type`, so one fix covers both. Signed-off-by: Aumnertic <hznuzc@gmail.com> * docs: document binary_keeps_metric_name Signed-off-by: Aumnertic <hznuzc@gmail.com> * fix(promql): handle on(__name__), scalar lhs and group_right Signed-off-by: Aumnertic <hznuzc@gmail.com> * chore(promql): correct result-side docs and drop unreachable String arm Signed-off-by: Aumnertic <hznuzc@gmail.com> * fix(promql): drop metric name when explicitly ignored Handle ignoring(__name__) for one-to-one comparisons and cover comparison, group_left, group_right and unless behavior. Signed-off-by: Aumnertic <hznuzc@gmail.com> --------- Signed-off-by: Aumnertic <hznuzc@gmail.com> (cherry picked from commitc33a6cfa56) Signed-off-by: WenyXu <wenymedia@gmail.com> * perf(promql): reduce timestamp payload allocations and use narrow cursor scans (#9417) * perf(promql): zero-copy timestamp payload for millisecond zero-offset ranges RangeManipulate rebuilt the timestamp range payload per sample through wide-precision i128 conversion even for millisecond input with zero offset, where the input is already millisecond and nothing changes. That turned a previously zero-copy path into a per-sample loop plus a Vec allocation, regressing range-function queries. Reuse the input timestamp array directly when the time index is already millisecond and the offset is zero. Other units and non-zero offsets keep the wide-precision conversion (required so ns/us data near the native type limits stays correct under large offsets). Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> Signed-off-by: discord9 <discord9@163.com> * perf(promql): vectorize timestamp payload with plain i64 RangeManipulate rebuilt the timestamp range payload per sample through wide-precision i128 conversion, turning a hot path into a scalar loop with a Vec allocation. Realistic timestamps and offsets stay far inside i64, so the wide path is unnecessary. - millisecond + zero offset reuses the input array with zero copy; - other units / offsets shift in one vectorized i64 pass (no i128, no per-sample Option, no overflow branch). Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> Signed-off-by: discord9 <discord9@163.com> * perf(promql): check timestamp payload overflow via arrow try_unary Replace the silent wrapping shift with try_unary + checked_mul/checked_add. checked arithmetic lowers to a per-lane overflow flag so the single pass still vectorizes over the dense i64 buffer; any overflow short-circuits to an error instead of silently wrapping to a wrong timestamp. No i128, no per-sample Option. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> Signed-off-by: discord9 <discord9@163.com> * perf(promql): vectorize cursor scans with i64 fast path RangeManipulate::calculate_range and InstantManipulate::manipulate compared every sample through i128 wide-precision arithmetic. For realistic timestamps and offsets the shifted values stay far inside i64, so the wide path was pure overhead. Split both scans into an i64 fast path (checked arithmetic precomputed once per batch) and an i128 exact fallback. Any input that would overflow i64 falls back to the i128 scan, keeping window membership semantics identical. Verified: 47/47 range_manipulate + instant_manipulate tests, 275/275 promql crate tests, 1612 randomized cases byte-identical to i128 scan. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> Signed-off-by: discord9 <discord9@163.com> * refactor(promql): generalize cursor scans and share endpoint precheck Address review comments on PR #9417: - Generalize scan_ranges/scan_instants over Nanoseconds (i64/i128), eliminating the duplicated scan bodies. - Extract shared narrow_scan precheck used by both RangeManipulate and InstantManipulate, with identical checked endpoint logic. - Trim doc comments to one line; fix factual errors about fallback trigger (2262 for ms/s, not native type limits). Semantics unchanged: same cursor algorithm, same overflow fallback, same i64-vs-i128 selection, same stale/null/tie handling. Verified: 47/47 range_manipulate + instant_manipulate tests, clippy clean. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> Signed-off-by: discord9 <discord9@163.com> * perf(promql): vectorize payload with try_unary and add scan carrier bench Replace the per-sample i128 payload loop with a single arrow try_unary pass: i128 intermediate holds any native timestamp shift, only the final millisecond result must fit i64. This is semantically equivalent to the original i128 implementation (no silent wrap, no per-sample branching). Add cursor_scan_carriers A/B benchmark: i64 fast path vs i128 exact fallback on four workloads. Add five deterministic differential tests pinning scan_ranges::<i64> and scan_ranges::<i128> to be pointwise equivalent on boundary shapes (sub-ms samples, fallback triggers, nanosecond extremes, seeded precision matrix). Bench (N=4096, 15s cadence): - payload wall_time 20x15s: 74.94us -> 48.98us (-35%) - payload wall_time 240x15s: 102.44us - scan i64 fast path: 10.97us vs i128 19.84us (1.81x) Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * perf(promql): restore zero-copy payload for ms+offset=0 and fix comment Restore the zero-copy fast path for millisecond timestamps with zero offset, guarded by the input column's exact data type (Timestamp (Millisecond, None)) rather than self.time_unit. The old time_unit guard would accept a tz-carrying millisecond column and produce a dictionary value type disagreeing with the output schema; the datatype guard is the correct check. Also fix the comment: arrow's try_unary is a fallible kernel that LLVM cannot auto-vectorize; the win over the hand-written loop comes from eliminating the per-sample Option/Vec and branches, not SIMD. Add a regression test with a timezone-carrying millisecond index to prove it falls back to the try_unary path. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * perf(promql): replace try_unary with unary + Cell overflow flag Arrow's try_unary is a fallible kernel that LLVM cannot auto-vectorize. Replace it with the infallible unary kernel: the closure is a plain i64 -> i64 mapping that sets a Cell<bool> on overflow instead of returning Err per sample. The flag is checked once after the loop. This removes the per-lane Result construction and lets LLVM vectorize the shift+truncate sequence. Bench (N=4096, 15s cadence): - window4x15s: 42.12us -> 28.77us (-32%) - window20x15s: 48.98us -> 27.68us (-43%) - window240x15s: 102.44us -> 27.61us (-73%) - regular_sparse: 90.04us -> 29.20us (-68%) All cases now run at ~27-29us regardless of window size; the zero-copy path dominates for ms+offset=0. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * perf(promql): dispatch payload conversion on null_count; drop wall-time bench Null-free batches (dominant PromQL case) take arrow's vectorizable unary kernel with a Cell<bool> overflow flag; nullable batches take try_unary so a NULL slot's arbitrary underlying value cannot raise a false batch-wide overflow error. Add a regression test exercising both paths. Remove bench_range_manipulate_wall_time (all-ms zero-offset cases only exercised the zero-copy path, measuring nothing about the conversion) and its orphaned helpers; restore the cursor-scan bench helpers it shared. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(promql): keep nullable timestamp cursor scans on i128 NULL slots may contain unordered arbitrary values, so the endpoint-only narrow-scan proof cannot bound their interior timestamp arithmetic. Both range and instant scans now attempt i64 only for null-free timestamp arrays and otherwise retain the existing exact i128 path. Add focused interior-NULL regressions through both active entrypoints, checking concrete selections and output against the existing wide scan. The null-free fast path and timestamp payload conversion are unchanged. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> Signed-off-by: discord9 <discord9@163.com> (cherry picked from commitb3c756a7dd) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(metasrv): stop holding the pusher registry lock across mailbox sends (#9445) fix(meta-srv): stop holding the pusher registry lock across mailbox sends Signed-off-by: lczllx <2181719471@qq.com> (cherry picked from commit7605f27608) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(mito): flush memtables containing skip-WAL writes on region close (#9440) * fix(mito): flush unlogged writes when closing regions Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(mito): align skip-WAL naming and metric recovery tests Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commita026b3b280) Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: enhance benchmark workflows with version comparisons and diagnostics (#9383) * ci: update Agent and Traces benchmark runtime Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: use unrestricted benchmark runtimes by default Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: retry benchmark image pulls before generating datasets Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: increase tracesbench defaults to 8 CPU and 32 GiB Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: configure tracesbench query timeout and update runtime Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: update tracesbench runtime and configure import scan workers Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: use shared parallel tracesbench import preparation Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: update tracesbench runtime to preserve Tempo trace requests Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: summarize tracesbench query results and lifecycle Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: show benchmark resource usage and update parallel loader runtime Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(ci): resolve shared summary module in existing test harness Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: select tracesbench engines and GreptimeDB image version Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: update benchmark runtime with parallel Agent preparation Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: select benchmark runtime registry by cloud provider Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: retry long-range benchmark image pulls Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: compare baseline and candidate GreptimeDB benchmarks Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: pin published dual-version benchmark runtime Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: prepare agent benchmark indexes with updated runtime Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: validate agent admission and render traces version comparison Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: use runtime with traces index preparation Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: use consolidated runtime for benchmark version reports Signed-off-by: WenyXu <wenymedia@gmail.com> * test: add license header to benchmark comparison tests Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(ci): read legacy Tempo ingestion metrics in traces summaries Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: require tracesbench query plan artifacts Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: use optimized tracesbench report runtime Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: update tracesbench runtime to merged report validation Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commit6e1a137411) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): accept evaluation times before the Unix epoch (#9480) * fix(promql): accept evaluation times before the Unix epoch Prometheus accepts negative timestamps in query parameters; GreptimeDB rejected them while parsing and would panic converting them in the planner. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test: update flow_tql error for a negative TQL end time A negative end now parses and fails the end >= start check instead of the timestamp conversion. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): reject evaluation times outside the millisecond range The signed millisecond conversion cast u128 to i64 unchecked, so a far-past time wrapped into the future. Reuse the checked conversion from label_values and return an error instead. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * chore: drop an unused import Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): round query times to milliseconds like Prometheus Numeric times round the fraction of a second to milliseconds, and pre-epoch times floor sub-millisecond parts, matching Prometheus' parseTime and timestamp.FromTime. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): round the float fraction before converting to nanoseconds Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test: skip the overflow case where SystemTime cannot hold it Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commita8e293fd79) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix: unify unknown flow option rejection across OSS and enterprise builds (#9319) * fix(operator): unify unknown flow option rejection across OSS and enterprise builds Remove the enterprise-gated passthrough that let unknown flow options through SQL-layer validation. Both OSS and enterprise binaries now reject unknown flow options with a single 1004(InvalidArguments) error at the SQL layer, producing identical sqlness output. The metasrv-side validate_flow_options remains as a defensive check. Signed-off-by: discord9 <discord9@163.com> * fix(flow): use DISTINCT output columns as sink key for streaming flows When a streaming flow's source table has no primary key, sink key inference produced an empty key. For SELECT DISTINCT v, the DISTINCT establishes v as the output key, but the inference only looked at source primary keys. Combined with the zero-valued placeholder timestamp, all output rows shared the same storage identity and were deduplicated into a single row. Use all non-time-index output columns as the sink key when the plan is a DISTINCT and no source primary key was inherited. Signed-off-by: discord9 <discord9@163.com> * fix(flow): preserve extension options and complete DISTINCT sink identity Keep enterprise CREATE FLOW options available to the existing handler and report unknown ordinary options as InvalidArguments with the OSS SQL error text. Infer DISTINCT sink keys from every selected non-time-index output column, including when source key columns survive. Wait for asynchronous mirrors in show_create_flow and exercise 1000 distinct sink rows in the SQL regression. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(flow): align meta unknown option status assertions Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(flow): use valid aliases in DISTINCT sink regression SQL Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(flow): record complete DISTINCT sink SQL results Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <discord9@163.com> Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commit178a0021b8) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): return range-vector results for instant queries (#9477) * fix(promql): return range-vector results for instant queries An instant query of a range selector or subquery (e.g. `up[5m]`) returned the internal range arrays, which panicked when the frontend decoded them as dictionaries. Plan such queries as the selected samples with their own timestamps, and reject range-vector expressions in range queries like Prometheus does. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test(promql): keep encode_substrait on a valid range query A range query of a range-vector expression now fails like in Prometheus. Wrap the subquery in max_over_time so the case still encodes both RangeManipulate nodes through substrait. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): plan wrapped range-vector roots and reject a zero subquery step TQL EXPLAIN, ANALYZE and AS alias wrap the root in an extension, which the range-vector path did not unwrap. A zero subquery step divided by zero. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test(promql): cover wrapped range-vector roots and a zero subquery step Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): default subquery step and reject range vectors in range queries A root subquery without a step uses the one-minute default instead of the query step. The HTTP range query endpoint rejects range-vector expressions even when start equals end. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test: cover root subquery default step Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * refactor(promql): align root subqueries with subquery_child_window Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): return millisecond timestamps for range-vector results Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * refactor(promql): keep the time index timezone and name its table in errors Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commitb913be2af7) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(query): stop reporting region scans as hash partitioned (#9491) * fix(query): stop reporting region scans as hash partitioned Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(query): use current DataFusion plan downcasts Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(query): match null rendering in join regression Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(query): refresh union plan for unknown scan partitioning Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(query): refresh shared plans after distribution rule removal Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(query): cover semi joins with remote dynamic filtering Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(query): strengthen partitioned merge scan regressions Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(query): remove unused merge scan test helpers Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(perf): measure merge scan hash repartition cost Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(query): adapt merge scan deserialization to two-arg constructor Adapt the production and test serializer callers added by #9206 and remove the obsolete partition_cols assertion. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(query): adapt nested merge scan integration constructor Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commit32e8467cda) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(table): require complete primary keys for hash partitioning (#9507) Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commit28ec3ff5aa) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(deps): bump OpenDAL to 0.59.4 for OSS RRSA credentials (#9520) Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commite6f3ca10a5) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): default a stepless subquery to a one-minute step (#9481) * fix(promql): default a stepless subquery to a one-minute step `x[5m:]` evaluated the inner expression at the query step, which is 1s for instant queries. Prometheus uses the evaluation interval, one minute by default. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test(promql): cover the default subquery step Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test(promql): cover the default subquery step in a range query Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commit845fc0af9c) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): follow IEEE 754 float semantics in comparisons and aggregations (#9479) * fix(promql): follow IEEE 754 float semantics in operators and aggregations - Comparisons: Arrow orders floats totally (NaN == NaN, NaN > +Inf); PromQL comparisons with NaN are false except !=. - min/max: skip NaN unless the whole group is NaN. DataFusion's grouped min/max let NaN replace and be replaced by neighbours, so the result depended on input order. - topk/bottomk rank NaN last. - round() rounds ties up and returns NaN for a zero step, like Prometheus. - sqrt() of a negative number is NaN instead of an error. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): round() without a step rounds to integers The planner passed a zero step for single-argument round(), which now yields NaN like Prometheus; pass 1 instead. Found by the compatibility harness. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): apply the sqrt guard to builtin functions and register min/max for plan decoding sqrt resolves through the session's builtin functions, not the UDF branch. Distributed plans are decoded with the serializer's session, which did not know prom_min/prom_max. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test(promql): cover NaN and rounding semantics Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): keep the nested count rewrite for PromQL min/max count(min(x) by (l)) was rewritten by count_nest_aggr only for the builtin min/max names. prom_min/prom_max return NULL for the same groups, so the rewrite stays valid. Update plan snapshots for the IEEE comparison, sqrt, topk and min/max changes. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): register prom_min/prom_max in the function registry Distributed plans split an aggregation into state and merge steps only for functions whose state function exists in FUNCTION_REGISTRY; prom_min and prom_max lived in the promql crate, so min/max stopped being pushed down to datanodes. Move them to common-function and register them before the state functions are derived. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test: update sqlness results for PromQL float semantics Plans and column names now show prom_min/prom_max, the NaN guards of comparisons and sqrt, and the NaN sort key of topk. round() results follow Prometheus' formula. Distributed min/max keep the state/merge split. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): keep column names and NULLs in IEEE comparisons and sqrt Rewriting comparisons as `a op b AND NOT (isnan(a) OR isnan(b))` changed TQL output column names and turned NULL operands into true for `!=` and false for the bool forms. Implement comparisons and sqrt as PromQL UDFs that propagate NULL and report the column name of the expression they replace; prom_min/prom_max report min/max column names the same way. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test: restore column names in sqlness results for PromQL float semantics Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test(promql): share the prom_min/prom_max groups accumulator with tests Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(promql): keep distinct names for prom_min/prom_max Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commitf3a0841f4f) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(query): keep filter fetch when rewriting matches term (#9521) * test: cover matches term limit dropped by constant rewrite Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(query): keep filter fetch when rewriting matches term Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commit483d1e49a3) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix: adapt release OptionMap iteration to Rust 1.98 Signed-off-by: WenyXu <wenymedia@gmail.com> * build: reduce checkout-specific inputs for cache reuse (#9198) * test(mito2): cover regex inverted index pruning Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * build: keep build inputs checkout-relative for cross-worktree cache reuse Two small changes so compilations are byte-identical across worktrees of the same commit and can share a build cache (mbx, sccache): - src/common/version/build.rs: derive the workspace root relative to CARGO_MANIFEST_DIR instead of the CARGO_WORKSPACE_DIR injected by .cargo/config.toml (an absolute path). The env var remains as an explicit override for the submodule case; the [env] injection in .cargo/config.toml is removed so the relative derivation is used by default. - Replace three env!("CARGO_MANIFEST_DIR") uses in test/bench code (servers prom_decode bench + decode test, common-meta test_util) with a runtime std::env::var lookup, so the compiled artifact no longer embeds the checkout path. No behavior change: same files are read, same version info emitted. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * build: address cross-worktree cache review feedback Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commitf50f42140a) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(mito): preserve version ordering in bulk merges and flushes (#9517) * fix(mito): preserve delete barriers in flat last-non-null dedup Signed-off-by: evenyag <realevenyag@gmail.com> * fix(mito): respect sequence ranges in bulk merges and flushes Signed-off-by: evenyag <realevenyag@gmail.com> * perf(mito): avoid merge selection on the bulk write path Signed-off-by: evenyag <realevenyag@gmail.com> --------- Signed-off-by: evenyag <realevenyag@gmail.com> (cherry picked from commit674ba72669) Signed-off-by: WenyXu <wenymedia@gmail.com> * ci: benchmark the object store WAL against S3 from an EC2 runner (#9536) * ci: benchmark the object store WAL against S3 from an EC2 runner Add a workflow_dispatch workflow that loads the o11ybench agent observability dataset into a fresh GreptimeDB container per cell and compares three WAL configurations that differ only in [wal]: Raft Engine with sync_write on the instance store, and the object store WAL in durable and enqueued mode, with the data files on S3 in the runner's region for all three. Rounds alternate the configuration order. A summary reports rows per second, D/R and E/R as the geometric mean over rounds, append acknowledgement p50 and p99, and the object store WAL counters. A smoke mode runs the same chain on ubuntu-latest against the MinIO fixture. Signed-off-by: jeremyhi <fengjiachun@gmail.com> * ci: scrape WAL metrics on failed loads and keep logic out of the benchmark YAML Scrape the object store WAL metrics before stopping a cell's server whether the load succeeded or failed, so failed cells keep their stall, poison and create failure counters. Build the summary from the plan so that cells which never ran are listed and leave their ratios undefined. Move budget, region, runtime image, manifest and credential scan logic from the workflow into the helper scripts, and drive the cell lifecycle in the tests through fake docker and curl binaries. Signed-off-by: jeremyhi <fengjiachun@gmail.com> * ci: drop redundant required flags from benchmark inputs Signed-off-by: jeremyhi <fengjiachun@gmail.com> * ci: fail a benchmark cell whose metrics scrape fails and zero untouched WAL counters A load that succeeds without its metrics scrape now fails the cell with exit code 4 and records metrics_scraped in cell.json. The summary reports an event counter that never registered as zero when the cell is an object store WAL cell whose scrape succeeded and created objects, and as unknown otherwise. Planned cells that never ran are read through the same path as recorded ones. Signed-off-by: jeremyhi <fengjiachun@gmail.com> * ci: register the S3 benchmark workflow for dispatch from its branch A workflow_dispatch workflow can only be dispatched once GitHub has registered it, which happens on the default branch or when one of its triggers fires. A push trigger limited to this branch registers it; validate runs only for workflow_dispatch, so a push run skips every job. Signed-off-by: jeremyhi <fengjiachun@gmail.com> * ci: delete the benchmark S3 prefix one object at a time boto3 now sends a CRC checksum instead of Content-MD5 with DeleteObjects, which the MinIO fixture of the smoke run rejects. DeleteObject needs neither and the prefix of one run holds at most a few thousand objects. Signed-off-by: jeremyhi <fengjiachun@gmail.com> * ci: default the S3 benchmark to c6id.2xlarge c7id is not offered in the runner region ap-southeast-1, where DescribeInstanceTypes rejects it. c6id is the compute optimized x86 family with a local NVMe instance store that the region offers, with the same 8 vCPU and 16 GiB at 2xlarge. Signed-off-by: jeremyhi <fengjiachun@gmail.com> * ci: drop the registration trigger of the S3 benchmark workflow The workflow is registered now, so dispatches no longer need a push trigger on its branch. Signed-off-by: jeremyhi <fengjiachun@gmail.com> * ci: fail an S3 benchmark run on EC2 without a local instance store Without an instance store the Raft Engine WAL of the R cells silently stayed on the root EBS volume, so the run measured a different baseline. Only a smoke run may keep it on the root volume now. Signed-off-by: jeremyhi <fengjiachun@gmail.com> * ci: let the instance store check fail its workflow step The step piped wal-disk into tee without pipefail, so a rejected host still passed. wal-disk now writes its record itself and the step runs it alone. Signed-off-by: jeremyhi <fengjiachun@gmail.com> --------- Signed-off-by: jeremyhi <fengjiachun@gmail.com> (cherry picked from commitc1a0ca9595) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(mito2): ignore missing inverted indexes in old SSTs (#9545) Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commit287ced2de2) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix: prevent stale leader metadata after concurrent writes (#9549) fix: fence leader cache fills around store mutations Signed-off-by: jeremyhi <fengjiachun@gmail.com> (cherry picked from commit394562aa9e) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): stop fabricating histogram fold hash partitioning (#9509) * fix(promql): stop fabricating histogram fold hash partitioning Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(promql): cover histogram fraction binary join missing rows Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(promql): normalize dictionary labels in histogram regression Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: cover histogram joins on logical metric tables Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: specify insert columns for logical metric fixture Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: use supported histogram child replacement API Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(promql): keep histogram fold fixtures truthful Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commit2039f508fc) Signed-off-by: WenyXu <wenymedia@gmail.com> * feat(client): support region request reuse and route refresh hints (#9540) * feat(client): support shared region requests without deep cloning Signed-off-by: WenyXu <wenymedia@gmail.com> * feat: propagate route refresh retry hints Signed-off-by: WenyXu <wenymedia@gmail.com> * style: preserve local error variant imports in retry hints Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(mito2): distinguish staging rejection from write backpressure Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(meta): preserve legacy retry hints in instruction replies Signed-off-by: WenyXu <wenymedia@gmail.com> * test(client): inline retry hint compatibility cases Signed-off-by: WenyXu <wenymedia@gmail.com> * test(datanode): align batch-open retry hint assertion Signed-off-by: WenyXu <wenymedia@gmail.com> * style: use imported types in region request tests Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commit684c8ff291) Signed-off-by: WenyXu <wenymedia@gmail.com> * refactor(operator): centralize write partitioning and dispatch (#9541) * refactor(operator): centralize write partitioning and dispatch Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(operator): preserve write dispatch behavior during refactor Signed-off-by: WenyXu <wenymedia@gmail.com> * style(operator): import write dispatch error paths Signed-off-by: WenyXu <wenymedia@gmail.com> * style(operator): align dispatch imports and test utilities Signed-off-by: WenyXu <wenymedia@gmail.com> * style(operator): use imported types in partition tests Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commita2d28e6060) Signed-off-by: WenyXu <wenymedia@gmail.com> * feat(operator): retry failed writes with refreshed partitions (#9542) * feat(operator): retry failed writes with refreshed partitions Signed-off-by: WenyXu <wenymedia@gmail.com> * style(operator): simplify retry test type references Signed-off-by: WenyXu <wenymedia@gmail.com> * refactor(operator): clarify write request rebuilding names Signed-off-by: WenyXu <wenymedia@gmail.com> * refactor(operator): encapsulate write retry attempts Signed-off-by: WenyXu <wenymedia@gmail.com> * style(operator): group inserter configuration methods Signed-off-by: WenyXu <wenymedia@gmail.com> * refactor(operator): share routed peers and separate retry decisions Signed-off-by: WenyXu <wenymedia@gmail.com> * refactor(operator): avoid temporary write routing allocations Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commitd86741d0ad) Signed-off-by: WenyXu <wenymedia@gmail.com> * feat(frontend): retry transient distributed write failures (#9543) * refactor(operator): route shared requests through NodeManager Remove the concrete NodeClients requester API and use the existing node manager for owned and shared requests. Preserve the original write hint while the legacy error adapter remains in place. Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(meta): preserve retry policy through error hints Propagate client errors through External and let DDL read retry hints while preserving cleanup and terminal-child policies. Region transport failures retain the legacy Retryable classification, which now also participates in bounded write retries. Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(operator): restore sparse partition tags when rebuilding writes Decode retained primary keys when refreshed partition expressions need different tags. Keep the partitioner and multi-region sparse write regression tests with the fix. Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(operator): stop detached write retries when callers cancel Signed-off-by: WenyXu <wenymedia@gmail.com> * feat(frontend): configure retries across distributed write paths Wire the shared dispatcher through frontend and logical batching, preserving partition columns and request headers. Keep standalone retries disabled and include configuration, batching, and storage regression coverage. Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commit863eebdecb) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(table): stop claiming tag order for logical metric scans (#9522) * test: cover metric logical scan tag ordering Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(table): only claim projected primary key prefix ordering Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(table): keep physical key ordering for metric projections Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(table): compare unknown partition counts explicitly Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(table): model scoped internal TSID scan properties Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commit75bc4ce46d) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(promql): stop claiming scalar calculate preserves order (#9523) * test: cover scalar calculate order contract Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(promql): stop claiming scalar calculate preserves order Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * docs(test): describe scalar ordering regression as pre-fix behavior Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commit0e40281262) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix: align fulltext bloom tokens with matches_term and use them for LIKE (#9470) * fix: align fulltext bloom tokens with matches_term and use them for LIKE The bloom fulltext index stored tokens that did not follow matches_term's word boundaries (`_`, ASCII next to Han, Han substrings), so pruning dropped rows matches_term accepts. Tokens and query probes now share the character classes of matches_term, Han runs are indexed as bigrams, and LIKE patterns use the same probes. The bloom blob is written under a new key; blobs from older versions are no longer probed. Tantivy no longer serves matches_term. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * chore: drop unused lazy_static from index and avoid typo-flagged test tokens Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * docs: link like_probes to tokenize instead of the downstream ScriptTokenizer Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * refactor: name probe extraction with verbs and document CharClass Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * feat: rebuild legacy fulltext bloom indexes and probe lower(col) The manifest records the fulltext bloom version per column, so ADMIN BUILD_INDEX now rebuilds SSTs whose bloom index predates the current tokens, and ssts_index_meta shows the version. Case-insensitive bloom indexes also store the tokens of the whole lowercased text for rows where it can differ (U+0130, U+212A, sigma), so matches_term(lower(col), ..) and lower(col) LIKE .. use the index. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * test: cover legacy bloom index rewrite by compaction in the compat case Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * chore: log outdated fulltext bloom indexes in the consistency check Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * docs: explain the unrecorded fulltext bloom version Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commit5c4d9eb4f8) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(query): guard deep expressions during query planning (#9564) * fix(query): avoid speculative clones in constant normalization Signed-off-by: evenyag <realevenyag@gmail.com> * fix(query): reject deeply nested expressions before optimization Signed-off-by: evenyag <realevenyag@gmail.com> --------- Signed-off-by: evenyag <realevenyag@gmail.com> (cherry picked from commit00aa65de7a) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(operator): reuse catalog table resolution for write retries (#9574) * fix(operator): inherit logical table partitions when retrying writes Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(operator): resolve retried writes through catalog manager Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commit36423a95c6) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(query): accept narrowed nullability in MergeScan remote schema check (#9592) On append-mode tables a region reports an exact row count. When it is zero, DataFusion's AggregateStatistics replaces the region-side __min_state/__max_state aggregate with a non-null struct literal, so the remote stream advertises the state column as non-nullable. The MergeScan schema check required equal nullability and failed the query. Accept a remote field that is non-nullable where the expected field is nullable, matching DataFusion's own physical-plan schema invariant. Batches are already rebuilt with the expected schema. Widening is still rejected, and nested field nullability is still compared exactly as part of the data type. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commitef1298ce02) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(table): stop claiming exact null and distinct counts for append scans (#9593) RegionScanExec reported null_count = 0 and distinct_count = total_rows for every column of an append-only table. DataFusion answers count(col) and count(DISTINCT col) from these statistics, so both returned the row count. Keep the exact row count and an exact zero null count for non-nullable columns, so count(*) is still answered from statistics. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commit5cfdc30a69) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix: avoid pinning memtables during series-index reconciliation (#9588) Signed-off-by: evenyag <realevenyag@gmail.com> (cherry picked from commitce66d8d4ac) Signed-off-by: WenyXu <wenymedia@gmail.com> * perf(query): infer safe aggregate input bounds from HAVING (#9582) * perf(query): infer safe aggregate input bounds from HAVING Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * test(query): refresh explain snapshots for aggregate bound inference Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * fix(query): avoid reinferring bounds across MergeScan Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * fix(query): validate aggregate bound column provenance Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * test(query): retain zero-match groups for nonpositive HAVING thresholds Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * test(query): assert inferred bounds in remote scan plans Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> --------- Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> (cherry picked from commitaf396e60a3) Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(query): keep state column names unique when splitting aggregates (#9594) * fix(query): keep state column names unique when splitting aggregates Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(query): probe allocated names when renaming colliding state columns Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commitf7a3a7033c) Signed-off-by: WenyXu <wenymedia@gmail.com> * chore: bump version to 1.3.0-beta.2 Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: greptimedb-ci <greptimedb-ci@greptime.com> Signed-off-by: WenyXu <wenymedia@gmail.com> Signed-off-by: Aarav <aaravsjadav@gmail.com> Signed-off-by: Ning Sun <sunning@greptime.com> Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> Signed-off-by: Dennis Zhuang <killme2008@gmail.com> Signed-off-by: evenyag <realevenyag@gmail.com> Signed-off-by: fengyue-xve <1759519137@qq.com> Signed-off-by: Aetherance <inkToAether@gmail.com> Signed-off-by: tian1220A <1573612521@qq.com> Signed-off-by: 风月 <1759519137@qq.com> Signed-off-by: Niladri Adhikary <niladrix719@gmail.com> Signed-off-by: lczllx <2181719471@qq.com> Signed-off-by: Aumnertic <hznuzc@gmail.com> Signed-off-by: discord9 <discord9@163.com> Signed-off-by: jeremyhi <fengjiachun@gmail.com> Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> Co-authored-by: LFC <990479+MichaelScofield@users.noreply.github.com> Co-authored-by: greptimedb-ci <greptimedb-ci@greptime.com> Co-authored-by: DeviousCardi <115358213+DeviousCardi@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Ning Sun <sunng@protonmail.com> Co-authored-by: discord9 <discord9@163.com> Co-authored-by: dennis zhuang <killme2008@gmail.com> Co-authored-by: Yingwen <realevenyag@gmail.com> Co-authored-by: fengyue-xve <1759519137@qq.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Aether <98370028+Aetherance@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: zhaiAohan <1573612521@qq.com> Co-authored-by: Niladri Adhikary <91966855+niladrix719@users.noreply.github.com> Co-authored-by: Dragon Roar <2181719471@qq.com> Co-authored-by: Aum <hznuzc@gmail.com> Co-authored-by: jeremyhi <jiachun_feng@proton.me> Co-authored-by: Lei, HUANG <6406592+v0y4g3r@users.noreply.github.com>
Query performance regression harness
This directory is for query performance cases that compare a base build with a candidate build. It is not a replacement for sqlness: the goal is to measure the effect of optimizer/query-engine changes on realistic scan work.
The Prometheus remote-write cases are end-to-end regression coverage for the
automatic write, flush, SST, and query path. They do not replace controlled
encoding experiments that explicitly compare plain/dictionary,
no_dictionary, BYTE_STREAM_SPLIT, or Auto policies.
Phase 1: direct readable SST fixtures
Phase 1 should generate data by writing readable Mito SST files and matching
manifest checkpoints directly. This follows the gc_readable_sst_fixture lab
approach from ~/greptimedb-gc-huge-stress: use Mito's SST writer to create
queryable files, then write a checkpoint and _last_checkpoint that reference
those files.
The generator itself must be generic. It should not know about a specific issue such as #7913 or a specific PromQL query. Cases provide declarative table schema, data layout, distributions, and queries; the generator turns those declarations into readable SST fixtures.
The intended flow for each case is:
- Start a GreptimeDB build and create an empty table to seed catalog/table metadata.
- Stop the process.
- Use the seed region metadata/manifest to generate deterministic readable SSTs and a replacement manifest checkpoint offline.
- Start the same build on the generated data directory.
- Run warmup and measured queries.
- Repeat the same fixture/query process for the candidate build.
- Compare base vs candidate metrics and write a regression report.
Direct SST fixtures are the default for phase 1 because they provide stable file counts, time ranges, row groups, and label distributions without spending CI time on ingestion and flush. Ingestion-path cases can be added later for nightly or release-level realism.
Prometheus remote-write scenario
The outer driver supports scenario.kind = "prom_remote_write_then_query" for a
bounded write-path smoke/regression flow. This path is explicit and separate from
the direct-SST fixture path: it starts the base and candidate distributed clusters,
writes deterministic Prometheus remote-write v1 samples through
/v1/prometheus/write, flushes the configured physical metric table, checks
visibility, runs the configured SQL/TQL queries, stops and awaits the datanode,
then optionally inspects the landed SST/Parquet footer, encoding, and size and
runs the read-bench against the quiescent data directory.
Remote-write cases configure one database, one logical metric, and one physical
table under [scenario.remote_write]:
[scenario]
kind = "prom_remote_write_then_query"
[scenario.remote_write]
database = "public"
metric = "prom_remote_write_seeded_random"
physical_table = "greptime_physical_table"
series_count = 512
samples_per_series = 1440
sample_chunk_size = 480
flush_every_sample_chunks = 1
start_unix_millis = 1_704_067_200_000
step_millis = 60_000
chunk_series_count = 128
timeout_seconds = 180
visibility_timeout_seconds = 120
# Optional: split by time so each helper invocation writes only this many samples
# per series, then periodically flush the physical metric table to produce
# multiple time-interval SSTs.
[scenario.remote_write.prom_store]
pending_rows_flush_interval = "1s"
max_batch_rows = 100000
Cases can optionally add [scenario.remote_write.value] to control the generated
sample values without changing label cardinality or the runner lifecycle:
[scenario.remote_write.value]
pattern = "quantized_signal" # linear, constant, modulo, unique, seeded_random,
# run_length, quantized_signal,
# signal_with_sporadic_stalls, mixed_signal_repeated,
# bounded_mixed
base = 0.0
step = 0.125
cardinality = 4096 # buckets for modulo/seeded_random/quantized_signal/run_length;
# baseline buckets for bounded_mixed
seed = 12345 # deterministic seeded_random input and bounded_mixed series hash
run_length = 8 # adjacent samples per bucket for run_length/quantized_signal;
# interpolation interval for bounded_mixed
stall_every = 100 # interval for signal_with_sporadic_stalls
stall_length = 16 # held samples inside each stall interval
mixed_every = 5 # every Nth sample becomes the repeated base value;
# one fractional bounded_mixed series per N series
The default linear pattern preserves the helper's historical formula. Use
constant or low-cardinality modulo/seeded_random values for repeated-value
data shapes, run_length for run-heavy low-cardinality series, quantized_signal
for signal-like values collapsed into a finite bucket set, signal_with_sporadic_stalls
for mostly continuous signals with periodic flat spots, and mixed_signal_repeated
for signal-plus-periodic-default mixtures. unique or high-cardinality buckets
still work for broad sample-value distributions.
bounded_mixed is synthetic rather than an empirical workload. For series s,
h = splitmix64(s ^ seed) selects a span from [10, 1_000, 100_000, 10_000_000]
using its top two bits, multiplied by 1 + ((h >> 32) % 10). Its baseline is
(h % max(cardinality, 1)) * span / 10. Ranges can overlap. At local sample
l = sample_offset + sample_idx, it linearly interpolates hash-derived anchors
in [-span + 1, span - 1], changing anchors every max(run_length, 1) samples.
It applies base + step * signal, then rounds to an integer. Every
max(mixed_every, 1)-th series adds a hash-derived fraction from 1/1000 to
999/1000. The 95/5 finite, nonintegral guarantee applies to the case's bounded
parameters, not arbitrary huge or nonfinite base/step inputs. With base = 0
and step = 1, output lies within baseline ± span, allowing one unit for
rounding and the fractional addition. The explicit case uses 1,000 equal-length
series and mixed_every = 20, giving exactly 95% integer-valued and 5%
fractional Float64 samples. Its ranges and temporal shape are synthetic design
parameters, not measured properties from the survey.
This is a generic sample-value control for query/ingestion cases; it does not inspect or assert storage encoding, Parquet footers, or storage policy choices. For chunked remote-write ingestion, the runner passes the sample offset and total sample count to the helper so non-linear value patterns use a stable global/per-series ordinal across chunks.
Case schema, value distribution defaults, storage defaults, and read-bench
defaults are owned by Rust. The outer CI driver calls
query_perf_fixture plan --case <case.toml> once, then uses the normalized plan
to select the Rust runner lifecycle. The fixture helper exposes direct-sst,
prom-remote-write, and inspect-footer subcommands.
Remote-write cases that need to validate storage output can add
[scenario.remote_write.storage]. When present, the scenario body becomes:
remote-write → flush → visibility check → query measurement → stop and await
datanode → Parquet footer/size/encoding inspection → optional read-bench. Footer
inspection is handled by query_perf_fixture inspect-footer, which reads local
Parquet footers directly; it does not require Python pyarrow.
[scenario.remote_write.storage]
inspect = true
column = "greptime_value"
include_metadata_files = false
# Optional: inspect below datanode data home instead of the whole fresh data dir.
# root_suffix = "greptime/public/<table_id>"
min_files = 1
min_files_with_column = 1
require_encodings = ["BYTE_STREAM_SPLIT"]
forbid_encodings = ["PLAIN_DICTIONARY"]
max_total_file_size_bytes = 104857600
max_column_compressed_size_bytes = 52428800
max_column_uncompressed_size_bytes = 209715200
max_candidate_total_file_size_regression_pct = 10.0
max_candidate_column_compressed_size_regression_pct = 10.0
max_candidate_column_uncompressed_size_regression_pct = 10.0
The *_pct storage thresholds are percent regression limits comparing candidate
against base; pct means percent, not percentile. Per-target storage checks
(min_files, min_files_with_column, required/forbidden encodings, and absolute
max_*_bytes) run for both base and candidate. Comparative
max_candidate_*_regression_pct checks remain base-vs-candidate. These checks
are generic footer and byte-size assertions and do not encode product-specific
heuristics. When storage inspection is enabled, min_files and
min_files_with_column default to 1 even if omitted, so dry-runs show these
planned checks and an empty flush or missing target column fails the scenario.
By default the inspector root is the target datanode data home, which is suitable
for a fresh single-case data directory. For reused or more complex data homes, set
root_suffix to a path relative to the datanode data home to narrow inspection.
When storage inspection is enabled, [scenario.remote_write.read_bench] defaults
to enabled with both datanode parquetbench and scanbench. Disable it with
enabled = false. parquetbench measures per-SST reader cost, scanbench
measures region scan cost, and query measurements still exercise the SQL/TQL
frontend path. Treat all performance conclusions as release-only; debug builds
are suitable only for command wiring and correctness checks.
prepare-remote creates the configured database if needed. A remote-write case
can provide base_setup_sql and candidate_setup_sql lists; each target runs its
own complete statements in order after database creation and before ingestion.
The outer driver writes a per-target frontend config enabling [prom_store] with
metric engine storage and a non-zero pending_rows_flush_interval, and validates
that the logical metric table reaches series_count * samples_per_series rows
before trusting the query measurements. Use
--fixture-generator /path/to/query_perf_fixture to provide the Rust helper to
the outer driver.
Large manual remote-write cases can set sample_chunk_size to split ingestion by
time. For each chunk, prepare-remote invokes query_perf_fixture prom-remote-write with the
same series cardinality but a shorter --samples-per-series and an advanced
--start-unix-millis. flush_every_sample_chunks controls periodic
ADMIN FLUSH_TABLE('<physical_table>') calls; with flush_every_sample_chunks = 1,
each time chunk is flushed separately. The final visible SST/file-range layout is
still determined by the storage engine's normal compaction policy, so cases that
need multi-window file distribution should span multiple compaction windows. If
sample_chunk_size is omitted, the runner keeps the older single-helper-invocation
behavior and flushes once at the end.
The default remote-write coverage set contains four cases with the same 2048
series × 14,400 samples (29,491,200 rows per target) shape. Each writes ten
one-day chunks, flushes every chunk, requires at least two visible SSTs with the
value column, runs seven read-bench iterations with parquetbench capped at
four SSTs while scanbench covers the landed region, and measures one ten-day
TQL selector with two warmups and 15 iterations:
prom_remote_write_seeded_random: high-distinct seeded-random values with cardinality 29,491,200 and seed 8444.prom_remote_write_run_heavy: low-cardinality values in exact 16-sample runs.prom_remote_write_mixed_every: continuous signal values with every fifth sample repeated at the base value.prom_remote_write_integer_counter: strictly increasing integer counter values, with disjoint ranges for each series.
These cases record normal storage/footer and read-bench results but do not gate specific value encodings or storage-size outcomes. Use the controlled encoding experiment matrix for those policy comparisons.
tests/perf/query_cases/prom_remote_write_7913/case.toml is a larger manual
case for issue #7913. It writes 8192 series × 20160 samples through remote-write
in 1440-sample daily time chunks, flushing after each chunk before running 1d/7d/14d
TQL selectors. It is not included in the default all case set because ingestion
cost dominates routine CI validation. Commenting /query-regression heavy runs
only this case; /query-regression runs the nine routine default cases, including
sst_float_bss, promql_instant_last_row_9034, and
mito_prefilter_all_match. Manual
workflow dispatch accepts the heavy token to select this case.
The routine default set also includes
tests/perf/query_cases/mito_prefilter_all_match/case.toml. It covers the
Mito prefilter all-match path, a mixed integer/float filter, and integer and
float selective controls over a 262144-row direct SST fixture.
OTLP trace load scenario
scenario.kind = "otlp_trace_load" runs a bounded native otelgen process
against each local distributed cluster. The outer driver runs the base target
alone with Rust run-otlp-target, fully stops it, then runs the candidate alone
and calls Rust finalize-otlp to aggregate metrics, thresholds, and the final
report. The case is intentionally outside the default set until its variance is
known.
Build base/candidate greptime binaries with the same profile and build the
candidate query_perf_fixture and query_regression_runner, then run the outer
driver explicitly:
WORK_DIR="$(mktemp -d /tmp/query-perf-otlp.XXXXXX)"
uv run --no-project python .github/scripts/query-regression-run.py \
--cases tests/perf/query_cases/otlp_trace_load/case.toml \
--base-bin /path/to/base/target/nightly/greptime \
--candidate-bin /path/to/candidate/target/nightly/greptime \
--fixture-generator /path/to/candidate/target/nightly/query_perf_fixture \
--runner /path/to/candidate/target/nightly/query_regression_runner \
--otelgen-bin /path/to/otelgen \
--work-dir "$WORK_DIR"
REPORT="$WORK_DIR/otlp_trace_load/query-regression-report.json"
For each target, accepted_spans should equal table_rows, and failures
should stay within max_failure_count. Throughput is better when
spans_per_second is higher; its actual_pct is
(base - candidate) / base * 100. Latency is better when mean_latency_ms is
lower; its actual_pct is (candidate - base) / base * 100. A positive
actual_pct is a candidate regression, while a negative value is an
improvement. The case passes when every actual_pct is at or below its
limit_pct and every failure-count threshold passes. For local results, run
the case at least three times on an otherwise idle machine and compare the
median regressions rather than relying on one run.
The CI runner image includes the pinned otelgen binary. Until this case is
added to the default set, run it explicitly with workflow_dispatch:
gh workflow run query-regression.yml \
--ref <workflow-branch> \
-f case=tests/perf/query_cases/otlp_trace_load/case.toml \
-f base_ref=<full-base-sha> \
-f candidate_ref=<full-candidate-sha> \
-f cargo_profile=nightly \
-f http_timeout=300 \
-f runner=aliyun-ecs
The aliyun-ecs path provisions a fresh ECS instance per run from the custom
image built from the current query-regression Dockerfile; see
.github/runner-scale-sets/query-regression/README.md for its configuration.
Generator contract
The direct-SST generator should accept a case definition with:
- one or more table definitions: columns, semantic types, primary key, time index, SST format, append mode
- deterministic distributions: seed, series/tag cardinalities, label/value functions, timestamp layout
- physical layout: regions, SST count, rows per SST, row group size, time ranges per SST, optional overlap/skew
- output paths for object-store files, manifest checkpoints, and fixture metadata
This keeps query regression cases reusable: the same generator can produce PromQL, SQL, pruning, projection, join, or aggregation fixtures by changing only case config.
What a case owns
Each optimization PR should add or update the query case for the pattern it is expected to affect. A case should define:
- schema and seed table SQL
- deterministic data shape: seed, series count, rows per SST, SST count, time range layout, label distribution, region/partition layout
- queries to run
- warmup/measurement repetitions
- metrics to collect
- base-vs-candidate thresholds
The [case] table is metadata for reports. The executable regression config
lives under [scenario]. A scenario owns data generation, queries, and
thresholds:
[case]
name = "example"
description = "what this regression protects"
[scenario]
kind = "direct_readable_sst"
seed = 12345
[[scenario.tables]]
# table schema and distributions
[scenario.layout]
# SST and series layout
[[scenario.queries]]
# query, warmups, iterations, thresholds
The outer driver currently supports direct_readable_sst,
prom_remote_write_then_query, and otlp_trace_load.
Metrics
Primary gates should compare query work rather than plan text:
- scanned files / file ranges
- scanned rows or row groups
- bytes read when available
- pruning ratio
- query latency median/p95
- output row count as a sanity check
Plan details such as pushed filters are useful diagnostics, but should not be the main pass/fail signal.
Runner lifecycle
.github/scripts/query-regression-run.py is the outer CI driver. It resolves the
base and candidate greptime binaries, candidate query_perf_fixture, and
candidate query_regression_runner; allocates disjoint localhost
meta/datanode/frontend HTTP, gRPC, MySQL, and Postgres ports for each target; and
writes component logs below <work-dir>/<target>/logs/.
The Rust runner consumes the normalized plan and endpoint ports. For
direct_readable_sst, the driver starts both clusters, runs prepare-direct,
stops only both datanodes, writes File-object-store destination TOMLs, invokes
materialize for every fixture and target, restarts datanodes, then runs
measure (with one frontend restart retry). materialize uses OpenDAL and only
replaces the fixture's exact region prefix in each target data home.
For prom_remote_write_then_query, it renders a target frontend configuration
with render-remote-config, starts both clusters, runs prepare-remote and
measure, stops both datanodes, then runs finalize-remote. Finalization owns
storage inspection and read-bench against the quiescent data homes. Both paths
write query-regression-report.json in the case work directory and always clean
up the remaining components.
For local orchestration after building the three binaries, invoke the outer driver directly:
uv run --no-project python .github/scripts/query-regression-run.py \
--cases tests/perf/query_cases/smoke_direct_sst/case.toml \
--base-bin /path/to/base/greptime \
--candidate-bin /path/to/candidate/greptime \
--fixture-generator /path/to/query_perf_fixture \
--runner /path/to/query_regression_runner \
--work-dir /tmp/query-regression-work
SST float BSS comparison
tests/perf/query_cases/sst_float_bss/case.toml is included in the routine
all default case group and compares a default empty physical metric table with
a candidate byte-stream-split (BSS) physical table. It writes 1,000 series × 4,320 samples
(4,320,000 rows) using synthetic bounded_mixed values: 95% integral series and
5% nonintegral series, with bounded per-series fluctuation rather than globally
unique values. This deliberately matches a 95/5 design; it is not an empirical
claim about any production population. A separate survey sample found 94.79%
integral values, but that observation does not make its values globally unique.
Current mixed-data evidence, including SST inspection, exact-bit row verification,
warmed endpoint SQL, and both warm-reader projections, is recorded in
query_cases/sst_float_bss/RESULTS.md.
Timing observations were collected while other builds saturated the shared host;
they are not performance acceptance evidence. Before rerunning timings, ensure
the host is idle (not merely this agent), record load throughout, and avoid
concurrent builds. CPU affinity alone does not isolate memory or I/O contention.
The historical unique-integer workload is preserved only in Git history and does
not apply to this case.
Both targets must use the exact same release greptime binary; only the
per-target table setup SQL differs. Run the existing driver from a checkout
containing that binary, with absolute paths and fresh data directories for every
run:
REPO="$(pwd -P)"
RELEASE_GREPTIME="/absolute/path/to/release/greptime"
FIXTURE_GENERATOR="/absolute/path/to/release/query_perf_fixture"
RUNNER="/absolute/path/to/release/query_regression_runner"
WORK_DIR="/absolute/path/to/fresh/sst-float-bss-run-1"
cd "$REPO"
uv run --no-project python "$REPO/.github/scripts/query-regression-run.py" \
--cases "$REPO/tests/perf/query_cases/sst_float_bss/case.toml" \
--base-src "$REPO" \
--candidate-src . \
--base-bin "$RELEASE_GREPTIME" \
--candidate-bin "$RELEASE_GREPTIME" \
--fixture-generator "$FIXTURE_GENERATOR" \
--runner "$RUNNER" \
--work-dir "$WORK_DIR" \
--summary-script "$REPO/.github/scripts/query-regression-summary.py"
Repeat the command three times with a different fresh absolute WORK_DIR each
run. In each query-regression-report.json, compare base and candidate
targets[].storage_inspection.summary.summary.total_file_size, and each query's
targets[].measurements[].latency_ms_median. Storage percentage is
(candidate_total_file_size - base_total_file_size) / base_total_file_size * 100;
query latency percentage is
(candidate_latency_ms_median - base_latency_ms_median) / base_latency_ms_median * 100.
The configured three warmups occur after the initial query validation and before
that query's 15 measured endpoint requests, so query latency is a warmed
frontend/cache measurement. The case restricts storage inspection to
data/greptime/public, excluding greptime_private SSTs. After the datanodes
stop, it also runs seven iterations of value-only parquetbench for all
inspected SST files and sequential scanbench over the corresponding regions,
with parallelism one. The three explicit flushes yielded six SSTs in the observed
mixed runs—three 1,105,920-row files and three 334,080-row files—because storage split each
flush; this is an observation, not a guaranteed file layout. Their per-run
output and aggregate parquetbench_median_average_ms and
scanbench_median_average_ms are under
targets[].read_bench; they are quiescent local-file read/scan diagnostics, not
warmed frontend-query latency measurements. Bench averages include iteration one;
no OS cache is dropped, and the driver runs base before candidate.
Historical PR #8548 reported storage savings alongside warm-read slowdowns for a different mixed counter/gauge study. It used value-only and all-column projections, discarded the first iteration, and alternated target order. This case reuses that reader measurement approach, not its dataset or results. For the supplementary warm comparison, reuse recorded commands against stopped data directories with eight iterations, discard iteration one, and alternate target order for eight rounds. For parquetbench, sum all per-file warm medians before taking the outer median; for scanbench, take the outer median of whole-region warm medians. Keep post-flush and post-compaction measurements separate.
The case's -5.0 storage target and 25 query-latency guardrail are experimental
acceptance targets, not observed-benefit claims; do not relax them if a run
fails. Its three periodic flushes and shared high TWCS trigger avoid the normal
four-file compaction trigger from confounding the layout. Check footer encodings
(BSS on candidate and no BSS on base) and data equality from the artifacts rather
than through a new harness framework.
For a focused manual reproduction of the Mito prefilter all-match optimization, use the existing lifecycle command above with --cases tests/perf/query_cases/mito_prefilter_all_match/case.toml. Its four count probes require explicit result inspection rather than automatic validation: expect 262144, 131072, 16896, and 16896 in query order. The default lifecycle retains caches, so the optimization remains exercised but its timing includes warm-cache effects; use an explicitly configured cold environment when a cold comparison is required.
The Rust runner subcommands are also useful for focused diagnostics:
query_regression_runner prepare-direct --case <case.toml> --fixture-generator <fixture> \
--base-http-port <port> --candidate-http-port <port> --fixture-dir <dir> --output <json>
query_regression_runner materialize --fixture-dir <dir> --destination <toml>
query_regression_runner measure --case <case.toml> --fixture-generator <fixture> \
--base-http-port <port> --candidate-http-port <port> --output <report.json>
GitHub Actions
.github/workflows/query-regression.yml provides an opt-in CI entrypoint for
query regression runs. It builds its own binaries for now:
- base
greptimefrom the PR base commit, orworkflow_dispatchbase_ref - candidate
greptime,query_perf_fixture, andquery_regression_runnerfrom the PR merge ref/current candidate checkout - outer driver and summary formatter from the candidate checkout
The workflow builds base and candidate greptime as normal release-equivalent
binaries. Candidate query_perf_fixture and query_regression_runner are the
extra head-side helpers; finalize-remote uses candidate greptime datanode parquetbench/scanbench as the read-bench tool against each target's data directory.
The workflow runs when an allowlisted repository admin comments
/query-regression on a non-draft PR. It does not rerun on pushes,
ready-for-review, or reopen events. /query-regression runs the nine routine
default cases, including sst_float_bss, promql_instant_last_row_9034, and
mito_prefilter_all_match; /query-regression heavy runs only the
high-cardinality remote-write #7913 case. PR runs build base/candidate once and
use --allow-large-fixture. Manual workflow_dispatch runs can pass all,
heavy, one case path, or a comma/whitespace-separated list of case paths, and
can override refs.
Comment admission is two workflows. slash-command-dispatch.yml uses
peter-evans/slash-command-dispatch
to parse the first line of a PR comment and, for /query-regression,
repository_dispatch a payload with the comment, actor, args, and PR number.
query-regression-slash.yml handles that event: it requires the dispatch
sender to be github-actions[bot], re-checks
QUERY_REGRESSION_COMMENT_ALLOWLIST (comma/whitespace-separated GitHub logins)
and repository admin permission, requires the current PR head to match the
head SHA peter-evans snapshotted at comment time, then snapshots
merge/head/base SHAs and calls the reusable workflow. If the head moved while
the handler was queued, admission denies and asks for another
/query-regression. Same-repo dispatch uses github.token with
contents: write; GitHub starts the handler for GITHUB_TOKEN-created
repository_dispatch events. The handler runs on the default branch, so it
has secrets even for fork PRs. A later head
change does not retarget an already queued run; comment /query-regression
again after reviewing the new revision. To add another slash command, list it
in the dispatcher and add a repository_dispatch handler.
The main report artifact uploads only aggregate/per-target JSON reports,
component logs, and query-regression-summary.md with seven-day retention;
fixture data, SSTs, and cluster state are excluded. PR runs also upload a
runner comment artifact with reports. The ubuntu-latest admission job posts a
hidden HMAC-signed marker comment on the admitted PR (QUERY_REGRESSION_ADMISSION_HMAC)
and uploads query-regression-admission as a lookup hint. The sticky-comment
workflow verifies that marker before posting; the ECS job cannot forge it
because it has neither issues write nor the HMAC secret. The workflow writes
the Markdown summary to the workflow step summary and updates a sticky PR
comment through the trusted follow-up workflow.
Built-in cases
The promql_pushdown_7913 case is only one case using the generic fixture
format. It generates a high-cardinality metric-like table with a nanosecond time
index and many SSTs with non-overlapping time ranges. Its timestamp_major
series layout writes one sample for every series at each scrape timestamp, so
short PromQL/TQL selector windows still scan realistic raw sample volumes. The
queries should show scan-level time filters, tight SST pruning, and enough raw
rows to make distributed PromQL pipeline placement meaningful instead of a
millisecond-scale canary.
Additional SQL optimizer cases:
sql_topk_order_by: single-table TopK /ORDER BYon a DOUBLE field with time and tag predicates.sql_aggregate_order_by: grouped aggregate ordered by aggregate value with aLIMIT.sql_join_filter_order: two direct-SST tables joined on a shared tag with time filters, aggregate ordering, andLIMIT.