chore: pick fixes and bump version to v1.1.2 (#8404)

* fix: improve Grafana metrics dashboards (#8298)

* chore: initial changes

Signed-off-by: evenyag <realevenyag@gmail.com>

* feat: improve troubleshooting dashboard

Signed-off-by: evenyag <realevenyag@gmail.com>

* chore: rm troubleshooting-dashboard.md

Signed-off-by: evenyag <realevenyag@gmail.com>

* chore: optimize metrics dashboard

Signed-off-by: evenyag <realevenyag@gmail.com>

* docs: move troubleshooting-dashboard.md

Signed-off-by: evenyag <realevenyag@gmail.com>

* chore: move mito gc duration panel

Signed-off-by: evenyag <realevenyag@gmail.com>

* chore: cleanup the dashboard

- Overview trend panels are now aggregate-only:
    - Total Ingestion Rate Trend
    - Total Query Rate Trend

- Protocol breakdowns remain in Ingestion and Queries.
- Mito Backpressure and Failures no longer duplicates scan/GC signals.
- Removed Write Stall per Instance.
- Split Object Store and WAL into collapsed Object Store and collapsed
  WAL.
- Moved WAL/logstore panels out of Storage into WAL.
- Normalized OpenDAL “other request” matchers.
- Normalized trigger elapsed p99/p75/avg aggregation.
- Regenerated standalone JSON and dashboard YAML/Markdown.
- Updated docs/troubleshooting-dashboard.md.

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix: rearrange metasrv dashboard panels

Signed-off-by: evenyag <realevenyag@gmail.com>

* feat: improve troubleshooting dashboard layout

Signed-off-by: evenyag <realevenyag@gmail.com>

* docs: remove obsolete troubleshooting dashboard doc

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix: correct cluster dashboard panel queries (missing _bucket, raw counters, rate normalization)

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix: correct trigger panel datasource, collapse flush/compaction, split request latency panels

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix: update grafana metrics dashboard panels

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix: correct Grafana dashboard units

Signed-off-by: evenyag <realevenyag@gmail.com>

* chore: regenerate Grafana dashboards

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix: use throughput unit for index IO bytes

Signed-off-by: evenyag <realevenyag@gmail.com>

---------

Signed-off-by: evenyag <realevenyag@gmail.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix: redact Kafka SASL password in debug output (#8337)

## Summary
- Mask `KafkaClientSaslConfig` password fields in debug output while keeping usernames visible.
- Cover metasrv WAL debug output with a regression test.

## Files
- `src/common/wal/src/config/kafka/common.rs`
- `src/common/wal/src/config.rs`

Signed-off-by: Lei, HUANG <mrsatangel@gmail.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix(query): run optimizer rules before MergeScan (#8339)

* fix(query): push down join filters before MergeScan

Signed-off-by: discord9 <discord9@163.com>

* fix(query): run optimizer before MergeScan pushdown

Signed-off-by: discord9 <discord9@163.com>

* fix(query): narrow pre-MergeScan filter pushdown

Signed-off-by: discord9 <discord9@163.com>

* fix(query): refine pre-MergeScan optimizer prepass

Signed-off-by: discord9 <discord9@163.com>

* fix(query): satisfy predicate extractor clippy

Signed-off-by: discord9 <discord9@163.com>

* test(query): cover pre-MergeScan optimizer edges

Signed-off-by: discord9 <discord9@163.com>

* test(query): cover set comparison prepass

Signed-off-by: discord9 <discord9@163.com>

* fix(query): guard remote scan filter pushdown

Signed-off-by: discord9 <discord9@163.com>

* fix(query): preserve subquery planning errors

Signed-off-by: discord9 <discord9@163.com>

* fix(query): preserve usable scan predicates

Signed-off-by: discord9 <discord9@163.com>

* fix(query): simplify scan predicate extraction

Signed-off-by: discord9 <discord9@163.com>

* fix(query): keep scan filter extraction scoped

Signed-off-by: discord9 <discord9@163.com>

* docs(query): explain pre-MergeScan optimizer

Signed-off-by: discord9 <discord9@163.com>

---------

Signed-off-by: discord9 <discord9@163.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix: preserve bulk write grpc error details (#8349)

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix: include index files in GC listing (#8327)

* fix: include index files in GC listing

Signed-off-by: discord9 <discord9@163.com>

* chore: filter GC index listing to puffins

Signed-off-by: discord9 <discord9@163.com>

* chore: simplify GC index listing stream

Signed-off-by: discord9 <discord9@163.com>

---------

Signed-off-by: discord9 <discord9@163.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix: stream tables for prometheus label discovery (#8341)

Signed-off-by: Ritwij Aryan Parmar <ritwij.aryan.parmar@gmail.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix: account parquet metadata cache size (#8368)

* fix: account parquet metadata cache size

Use Parquet metadata memory sizing for SST metadata cache weight and add regression coverage for byte-array page-index buffers.

Signed-off-by: Lei, HUANG <mrsatangel@gmail.com>

* fix: saturate sst meta cache weight

Signed-off-by: Lei, HUANG <mrsatangel@gmail.com>

---------

Signed-off-by: Lei, HUANG <mrsatangel@gmail.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix: respect gc mailbox timeout for admin gc (#8363)

Signed-off-by: discord9 <discord9@163.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix: record catalog and schema in slow queries (#8387)

* fix: record catalog and schema in slow queries

Add catalog and schema context to slow query records while appending the new columns after existing fields to preserve column order.

- `src/common/frontend/src/slow_query_event.rs`: extend `SlowQueryEvent` schema and rows with `catalog_name` and `schema_name`, and cover append-only ordering.
- `src/catalog/src/process_manager.rs`: carry catalog and schema through `SlowQueryTimer`.
- `src/frontend/src/instance.rs`: capture context for SQL, plan, and PromQL slow query timers.
- `tests-integration/tests/sql.rs`: assert MySQL and PostgreSQL slow query records include catalog and schema.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* fix: address slow query review comment

Use `String::clone` when writing slow query catalog and schema values.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* fix: keep slow query schema only

Remove the slow query `catalog_name` column and keep `schema_name` as a non-null tag dimension.

- `src/common/frontend/src/slow_query_event.rs`: expose only `schema_name` in `SlowQueryEvent` rows and mark it as a tag.

- `src/catalog/src/process_manager.rs`: stop carrying catalog context in `SlowQueryTimer`.

- `src/frontend/src/instance.rs`: pass only schema context to slow query timers.

- `tests-integration/tests/sql.rs`: assert slow query records include `schema_name` without `catalog_name`.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* fix: schema name semantic should be field

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* fix: typo

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

---------

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix: invalidate comment DDL cache and lock by object ID (#8390)

* fix: invalidate comment ddl cache locally

Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix: fix typos

Signed-off-by: WenyXu <wenymedia@gmail.com>

* chore: apply suggestions

Signed-off-by: WenyXu <wenymedia@gmail.com>

---------

Signed-off-by: WenyXu <wenymedia@gmail.com>

* chore: client_ip error logs skip internal API (#8362)

* chore: client_ip error logs skip internal API

Signed-off-by: shuiyisong <xixing.sys@gmail.com>

* fix: fmt

Signed-off-by: shuiyisong <xixing.sys@gmail.com>

* chore: use const

Signed-off-by: shuiyisong <xixing.sys@gmail.com>

* chore: use const

Signed-off-by: shuiyisong <xixing.sys@gmail.com>

---------

Signed-off-by: shuiyisong <xixing.sys@gmail.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* feat: update dashboard to v0.13.6 (#8369)

Signed-off-by: WenyXu <wenymedia@gmail.com>

* chore: use ENV for building dashboard (#8384)

Signed-off-by: shuiyisong <xixing.sys@gmail.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix: handle PromQL time binary aggregation (#8398)

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* perf(mito): prune files by manifest time range (#8352)

* perf(mito): prune files by manifest time range

Signed-off-by: discord9 <discord9@163.com>

* chore(mito): address file pruning review

Signed-off-by: discord9 <discord9@163.com>

* chore(mito): remove verbose file pruning log

Signed-off-by: discord9 <discord9@163.com>

* chore(mito): expose file pruning metric

Signed-off-by: discord9 <discord9@163.com>

* chore(mito): shorten file pruning metric

Signed-off-by: discord9 <discord9@163.com>

* test(mito): cover file pruning edge cases

Signed-off-by: discord9 <discord9@163.com>

---------

Signed-off-by: discord9 <discord9@163.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* perf(mito): skip manifest-pruned file ranges (#8366)

* perf(mito): skip manifest-pruned file ranges

Signed-off-by: discord9 <discord9@163.com>

* test(mito): allow empty prune benchmark output

Signed-off-by: discord9 <discord9@163.com>

* fix(mito): avoid caching stale pruned builders

Signed-off-by: discord9 <discord9@163.com>

* chore(mito): address pruner clippy

Signed-off-by: discord9 <discord9@163.com>

* fix(mito): account worker pruner builder metrics

Signed-off-by: discord9 <discord9@163.com>

* test(mito): keep empty prune benchmark local

Signed-off-by: discord9 <discord9@163.com>

* refactor(mito): share manifest-pruned range skip

Signed-off-by: discord9 <discord9@163.com>

* chore(mito): shorten prune cache comment

Signed-off-by: discord9 <discord9@163.com>

* fix(mito): keep manifest prune state in pruner

Signed-off-by: discord9 <discord9@163.com>

* test(mito): cover manifest prune fast skip edge cases

Signed-off-by: discord9 <discord9@163.com>

* chore: fix typo in logical table alter

Signed-off-by: discord9 <discord9@163.com>

* chore(mito): address pruner review comments

Signed-off-by: discord9 <discord9@163.com>

---------

Signed-off-by: discord9 <discord9@163.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>

* chore: bump version to v1.1.2

Signed-off-by: WenyXu <wenymedia@gmail.com>

---------

Signed-off-by: evenyag <realevenyag@gmail.com>
Signed-off-by: WenyXu <wenymedia@gmail.com>
Signed-off-by: Lei, HUANG <mrsatangel@gmail.com>
Signed-off-by: discord9 <discord9@163.com>
Signed-off-by: jeremyhi <fengjiachun@gmail.com>
Signed-off-by: Ritwij Aryan Parmar <ritwij.aryan.parmar@gmail.com>
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
Signed-off-by: shuiyisong <xixing.sys@gmail.com>
Co-authored-by: Yingwen <realevenyag@gmail.com>
Co-authored-by: Lei, HUANG <6406592+v0y4g3r@users.noreply.github.com>
Co-authored-by: discord9 <discord9@163.com>
Co-authored-by: jeremyhi <jiachun_feng@proton.me>
Co-authored-by: Ritwij Aryan Parmar <88580521+RitwijParmar@users.noreply.github.com>
Co-authored-by: shuiyisong <113876041+shuiyisong@users.noreply.github.com>
Co-authored-by: sun <sunchang_long@163.com>
This commit is contained in:
Weny Xu
2026-07-02 21:21:19 +08:00
committed by GitHub
parent 88fa784810
commit 8ad2d2414c
73 changed files with 31726 additions and 13963 deletions

152
Cargo.lock generated
View File

@@ -213,7 +213,7 @@ checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
[[package]]
name = "api"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"arrow-schema 58.3.0",
"common-base",
@@ -936,7 +936,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "auth"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"async-trait",
@@ -1574,7 +1574,7 @@ dependencies = [
[[package]]
name = "cache"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"catalog",
"common-error",
@@ -1610,7 +1610,7 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "catalog"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"arrow 58.3.0",
@@ -1945,7 +1945,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "cli"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"async-stream",
"async-trait",
@@ -2004,7 +2004,7 @@ dependencies = [
[[package]]
name = "client"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"arc-swap",
@@ -2036,7 +2036,7 @@ dependencies = [
"serde_json",
"snafu 0.8.6",
"store-api",
"substrait 1.1.1",
"substrait 1.1.2",
"tokio",
"tokio-stream",
"tonic 0.14.2",
@@ -2085,7 +2085,7 @@ dependencies = [
[[package]]
name = "cmd"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"async-trait",
@@ -2235,7 +2235,7 @@ dependencies = [
[[package]]
name = "common-base"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"ahash 0.8.12",
"anymap2",
@@ -2255,14 +2255,14 @@ dependencies = [
[[package]]
name = "common-catalog"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"const_format",
]
[[package]]
name = "common-config"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"common-base",
"common-error",
@@ -2286,7 +2286,7 @@ dependencies = [
[[package]]
name = "common-datasource"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"arrow 58.3.0",
"arrow-schema 58.3.0",
@@ -2322,7 +2322,7 @@ dependencies = [
[[package]]
name = "common-decimal"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"bigdecimal 0.4.8",
"common-error",
@@ -2335,7 +2335,7 @@ dependencies = [
[[package]]
name = "common-error"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"common-macro",
"http 1.3.1",
@@ -2346,7 +2346,7 @@ dependencies = [
[[package]]
name = "common-event-recorder"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"async-trait",
@@ -2369,7 +2369,7 @@ dependencies = [
[[package]]
name = "common-frontend"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"async-trait",
@@ -2390,7 +2390,7 @@ dependencies = [
[[package]]
name = "common-function"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"ahash 0.8.12",
"api",
@@ -2453,7 +2453,7 @@ dependencies = [
[[package]]
name = "common-greptimedb-telemetry"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"async-trait",
"common-runtime",
@@ -2470,7 +2470,7 @@ dependencies = [
[[package]]
name = "common-grpc"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"arrow-flight",
@@ -2505,7 +2505,7 @@ dependencies = [
[[package]]
name = "common-grpc-expr"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"common-base",
@@ -2525,7 +2525,7 @@ dependencies = [
[[package]]
name = "common-macro"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"greptime-proto",
"once_cell",
@@ -2536,7 +2536,7 @@ dependencies = [
[[package]]
name = "common-mem-prof"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"anyhow",
"common-error",
@@ -2552,7 +2552,7 @@ dependencies = [
[[package]]
name = "common-memory-manager"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"common-error",
"common-macro",
@@ -2564,7 +2564,7 @@ dependencies = [
[[package]]
name = "common-meta"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"anymap2",
"api",
@@ -2635,7 +2635,7 @@ dependencies = [
[[package]]
name = "common-options"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"common-grpc",
"humantime-serde",
@@ -2645,11 +2645,11 @@ dependencies = [
[[package]]
name = "common-plugins"
version = "1.1.1"
version = "1.1.2"
[[package]]
name = "common-pprof"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"common-error",
"common-macro",
@@ -2660,7 +2660,7 @@ dependencies = [
[[package]]
name = "common-procedure"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"async-stream",
@@ -2689,7 +2689,7 @@ dependencies = [
[[package]]
name = "common-procedure-test"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"async-trait",
"common-procedure",
@@ -2699,7 +2699,7 @@ dependencies = [
[[package]]
name = "common-query"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"async-trait",
@@ -2729,7 +2729,7 @@ dependencies = [
[[package]]
name = "common-recordbatch"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"arc-swap",
"common-base",
@@ -2754,7 +2754,7 @@ dependencies = [
[[package]]
name = "common-runtime"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"async-trait",
"clap",
@@ -2783,7 +2783,7 @@ dependencies = [
[[package]]
name = "common-session"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"serde",
"strum 0.27.1",
@@ -2791,7 +2791,7 @@ dependencies = [
[[package]]
name = "common-sql"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"arrow-schema 58.3.0",
"common-base",
@@ -2811,7 +2811,7 @@ dependencies = [
[[package]]
name = "common-stat"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"common-base",
"common-runtime",
@@ -2826,7 +2826,7 @@ dependencies = [
[[package]]
name = "common-telemetry"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"backtrace",
"common-base",
@@ -2855,7 +2855,7 @@ dependencies = [
[[package]]
name = "common-test-util"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"client",
"common-grpc",
@@ -2868,7 +2868,7 @@ dependencies = [
[[package]]
name = "common-time"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"arrow 58.3.0",
"chrono",
@@ -2886,7 +2886,7 @@ dependencies = [
[[package]]
name = "common-version"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"cargo-manifest",
"const_format",
@@ -2896,7 +2896,7 @@ dependencies = [
[[package]]
name = "common-wal"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"common-base",
"common-error",
@@ -2919,7 +2919,7 @@ dependencies = [
[[package]]
name = "common-workload"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"common-telemetry",
"serde",
@@ -4360,7 +4360,7 @@ dependencies = [
[[package]]
name = "datanode"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"arrow-flight",
@@ -4434,7 +4434,7 @@ checksum = "c286de4e81ea2590afc24d754e0f83810c566f50a1388fa75ebd57928c0d9745"
[[package]]
name = "datatypes"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"arrow 58.3.0",
"arrow-array 58.3.0",
@@ -5171,7 +5171,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "file-engine"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"async-trait",
@@ -5302,7 +5302,7 @@ checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
[[package]]
name = "flow"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"arrow 58.3.0",
@@ -5371,7 +5371,7 @@ dependencies = [
"sql",
"store-api",
"strum 0.27.1",
"substrait 1.1.1",
"substrait 1.1.2",
"table",
"tokio",
"tokio-stream",
@@ -5454,7 +5454,7 @@ checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619"
[[package]]
name = "frontend"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"arc-swap",
@@ -6745,7 +6745,7 @@ dependencies = [
[[package]]
name = "index"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"async-trait",
"asynchronous-codec",
@@ -7844,7 +7844,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "log-query"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"chrono",
"common-error",
@@ -7856,7 +7856,7 @@ dependencies = [
[[package]]
name = "log-store"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"async-stream",
"async-trait",
@@ -8175,7 +8175,7 @@ dependencies = [
[[package]]
name = "meta-client"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"async-trait",
@@ -8207,7 +8207,7 @@ dependencies = [
[[package]]
name = "meta-srv"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"async-trait",
@@ -8307,7 +8307,7 @@ dependencies = [
[[package]]
name = "metric-engine"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"aquamarine",
@@ -8408,7 +8408,7 @@ dependencies = [
[[package]]
name = "mito-codec"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"bytes",
@@ -8433,7 +8433,7 @@ dependencies = [
[[package]]
name = "mito2"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"aquamarine",
@@ -9222,7 +9222,7 @@ dependencies = [
[[package]]
name = "object-store"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"anyhow",
"async-trait",
@@ -9777,7 +9777,7 @@ dependencies = [
[[package]]
name = "operator"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"ahash 0.8.12",
"api",
@@ -9836,7 +9836,7 @@ dependencies = [
"sql",
"sqlparser",
"store-api",
"substrait 1.1.1",
"substrait 1.1.2",
"table",
"tokio",
"tokio-util",
@@ -10121,7 +10121,7 @@ checksum = "e3c406c9e2aa74554e662d2c2ee11cd3e73756988800be7e6f5eddb16fed4699"
[[package]]
name = "partition"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"async-trait",
@@ -10515,7 +10515,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pipeline"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"ahash 0.8.12",
"api",
@@ -10672,7 +10672,7 @@ dependencies = [
[[package]]
name = "plugins"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"auth",
"catalog",
@@ -11012,7 +11012,7 @@ dependencies = [
[[package]]
name = "promql"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"ahash 0.8.12",
"async-trait",
@@ -11364,7 +11364,7 @@ dependencies = [
[[package]]
name = "puffin"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"async-compression",
"async-trait",
@@ -11426,7 +11426,7 @@ dependencies = [
[[package]]
name = "query"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"ahash 0.8.12",
"api",
@@ -11496,7 +11496,7 @@ dependencies = [
"sql",
"sqlparser",
"store-api",
"substrait 1.1.1",
"substrait 1.1.2",
"table",
"tokio",
"tokio-stream",
@@ -13068,7 +13068,7 @@ dependencies = [
[[package]]
name = "servers"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"ahash 0.8.12",
"api",
@@ -13206,7 +13206,7 @@ dependencies = [
[[package]]
name = "session"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"ahash 0.8.12",
"api",
@@ -13559,7 +13559,7 @@ dependencies = [
[[package]]
name = "sql"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"arrow-buffer 58.3.0",
@@ -13620,7 +13620,7 @@ dependencies = [
[[package]]
name = "sqlness-runner"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"async-trait",
"clap",
@@ -13900,7 +13900,7 @@ dependencies = [
[[package]]
name = "standalone"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"async-trait",
"catalog",
@@ -13944,7 +13944,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "store-api"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"aquamarine",
@@ -14137,7 +14137,7 @@ dependencies = [
[[package]]
name = "substrait"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"async-trait",
"bytes",
@@ -14259,7 +14259,7 @@ dependencies = [
[[package]]
name = "table"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"arc-swap",
@@ -14542,7 +14542,7 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
[[package]]
name = "tests-fuzz"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"arbitrary",
"async-trait",
@@ -14587,7 +14587,7 @@ dependencies = [
[[package]]
name = "tests-integration"
version = "1.1.1"
version = "1.1.2"
dependencies = [
"api",
"arrow-flight",
@@ -14667,7 +14667,7 @@ dependencies = [
"sqlx",
"standalone",
"store-api",
"substrait 1.1.1",
"substrait 1.1.2",
"table",
"tempfile",
"time",

View File

@@ -75,7 +75,7 @@ members = [
resolver = "2"
[workspace.package]
version = "1.1.1"
version = "1.1.2"
edition = "2024"
license = "Apache-2.0"

File diff suppressed because it is too large Load Diff

View File

@@ -4,144 +4,154 @@
| Uptime | `time() - process_start_time_seconds` | `stat` | The start time of GreptimeDB. | `prometheus` | `s` | `__auto` |
| Version | `SELECT pkg_version FROM information_schema.build_info` | `stat` | GreptimeDB version. | `mysql` | -- | -- |
| Total Ingestion Rate | `sum(rate(greptime_table_operator_ingest_rows[$__rate_interval]))` | `stat` | Total ingestion rate. | `prometheus` | `rowsps` | `__auto` |
| Total Storage Size | `select SUM(disk_size) from information_schema.region_statistics;` | `stat` | Total number of data file size. | `mysql` | `decbytes` | -- |
| Total Rows | `select SUM(region_rows) from information_schema.region_statistics;` | `stat` | Total number of data rows in the cluster. Calculated by sum of rows from each region. | `mysql` | `sishort` | -- |
| Total Query Rate | `sum(rate(greptime_servers_mysql_query_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + sum(rate(greptime_servers_postgres_query_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + sum(rate(greptime_servers_http_promql_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + sum(rate(greptime_servers_http_sql_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + sum(rate(greptime_frontend_grpc_handle_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))` | `stat` | Total query API call rate across MySQL, PostgreSQL, and PromQL frontends. | `prometheus` | `reqps` | `queries` |
| User-facing Error Rate | `sum(rate(greptime_servers_error{instance=~"$frontend"}[$__rate_interval]))` | `stat` | Server protocol errors returned by frontends. Sustained non-zero values indicate user-visible failures. | `prometheus` | `eps` | `errors` |
| Recent Restarts | `sum(changes(process_start_time_seconds[$__range]))` | `stat` | Process restarts over the selected time range across GreptimeDB roles. | `prometheus` | `short` | `restarts` |
| Deployment | `SELECT count(*) as datanode FROM information_schema.cluster_info WHERE peer_type = 'DATANODE';`<br/>`SELECT count(*) as frontend FROM information_schema.cluster_info WHERE peer_type = 'FRONTEND';`<br/>`SELECT count(*) as metasrv FROM information_schema.cluster_info WHERE peer_type = 'METASRV';`<br/>`SELECT count(*) as flownode FROM information_schema.cluster_info WHERE peer_type = 'FLOWNODE';` | `stat` | The deployment topology of GreptimeDB. | `mysql` | -- | -- |
| Database Resources | `SELECT COUNT(*) as databases FROM information_schema.schemata WHERE schema_name NOT IN ('greptime_private', 'information_schema')`<br/>`SELECT COUNT(*) as tables FROM information_schema.tables WHERE table_schema != 'information_schema'`<br/>`SELECT COUNT(region_id) as regions FROM information_schema.region_peers`<br/>`SELECT COUNT(*) as flows FROM information_schema.flows` | `stat` | The number of the key resources in GreptimeDB. | `mysql` | -- | -- |
| Total Storage Size | `select SUM(disk_size) from information_schema.region_statistics;` | `stat` | Total number of data file size. | `mysql` | `decbytes` | -- |
| Total Rows | `select SUM(region_rows) from information_schema.region_statistics;` | `stat` | Total number of data rows in the cluster. Calculated by sum of rows from each region. | `mysql` | `sishort` | -- |
| Data Size | `SELECT SUM(memtable_size) * 0.42825 as WAL FROM information_schema.region_statistics;`<br/>`SELECT SUM(index_size) as index FROM information_schema.region_statistics;`<br/>`SELECT SUM(manifest_size) as manifest FROM information_schema.region_statistics;` | `stat` | The data size of wal/index/manifest in the GreptimeDB. | `mysql` | `decbytes` | -- |
| Total Ingestion Rate Trend | `sum(rate(greptime_table_operator_ingest_rows{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Total ingestion throughput trend across frontends. Protocol breakdown is in the Ingestion row. | `prometheus` | `rowsps` | `ingestion` |
| Total Query Rate Trend | `sum(rate(greptime_servers_mysql_query_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + sum(rate(greptime_servers_postgres_query_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + sum(rate(greptime_servers_http_promql_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + sum(rate(greptime_servers_http_sql_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + sum(rate(greptime_frontend_grpc_handle_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Total query API call rate trend across frontend protocols. Protocol breakdown is in the Queries row. | `prometheus` | `reqps` | `queries` |
| HTTP Request P99 and Avg | `histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_requests_elapsed_bucket{instance=~"$frontend",path!~"/health\|/metrics"}[$__rate_interval])))`<br/>`sum(rate(greptime_servers_http_requests_elapsed_sum{instance=~"$frontend",path!~"/health\|/metrics"}[$__rate_interval])) / sum(rate(greptime_servers_http_requests_elapsed_count{instance=~"$frontend",path!~"/health\|/metrics"}[$__rate_interval]))` | `timeseries` | Tail and average latency for HTTP requests served by frontends. Excludes health and metrics endpoints. | `prometheus` | `s` | `http-p99` |
| gRPC Request P99 and Avg | `histogram_quantile(0.99, sum by (le) (rate(greptime_servers_grpc_requests_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`sum(rate(greptime_servers_grpc_requests_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum(rate(greptime_servers_grpc_requests_elapsed_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Tail and average latency for gRPC requests served by frontends. | `prometheus` | `s` | `grpc-p99` |
# Ingestion
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Total Ingestion Rate | `sum(rate(greptime_table_operator_ingest_rows{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Total ingestion rate.<br/><br/>Here we listed 3 primary protocols:<br/><br/>- Prometheus remote write<br/>- Greptime's gRPC API (when using our ingest SDK)<br/>- Log ingestion http API<br/> | `prometheus` | `rowsps` | `ingestion` |
| Ingestion Rate by Type | `sum(rate(greptime_servers_http_logs_ingestion_counter[$__rate_interval]))`<br/>`sum(rate(greptime_servers_prometheus_remote_write_samples[$__rate_interval]))` | `timeseries` | Total ingestion rate.<br/><br/>Here we listed 3 primary protocols:<br/><br/>- Prometheus remote write<br/>- Greptime's gRPC API (when using our ingest SDK)<br/>- Log ingestion http API<br/> | `prometheus` | `rowsps` | `http-logs` |
# Queries
| Ingestion Rate by Protocol | `sum(rate(greptime_table_operator_ingest_rows{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_prometheus_remote_write_samples{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_logs_ingestion_counter{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_loki_logs_ingestion_counter{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_elasticsearch_logs_docs_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_frontend_otlp_metrics_rows{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_frontend_otlp_logs_rows{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_frontend_otlp_traces_rows{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Rows, samples, or documents ingested by primary observability and table-ingestion protocols. | `prometheus` | `rowsps` | `table-operator` |
| Ingestion Latency by Protocol | `histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_prometheus_write_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_logs_ingestion_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_loki_logs_ingestion_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_elasticsearch_logs_ingestion_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_otlp_metrics_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_otlp_logs_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_otlp_traces_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`sum(rate(greptime_servers_http_prometheus_write_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum(rate(greptime_servers_http_prometheus_write_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_logs_ingestion_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum(rate(greptime_servers_http_logs_ingestion_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_loki_logs_ingestion_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum(rate(greptime_servers_loki_logs_ingestion_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_elasticsearch_logs_ingestion_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum(rate(greptime_servers_elasticsearch_logs_ingestion_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_otlp_metrics_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum(rate(greptime_servers_http_otlp_metrics_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_otlp_logs_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum(rate(greptime_servers_http_otlp_logs_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_otlp_traces_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum(rate(greptime_servers_http_otlp_traces_elapsed_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | p99 and average HTTP ingestion latency for Prometheus remote write, logs, Loki, Elasticsearch, and OTLP endpoints. | `prometheus` | `s` | `prometheus-write` |
| Bulk Insert Message Rows and Size | `sum(rate(greptime_table_operator_bulk_insert_message_rows_sum{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_table_operator_bulk_insert_message_size_sum{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Bulk-insert message row and byte rates. Spikes here can explain frontend bulk-insert latency. | `prometheus` | `rowsps` | `rows` |
| Prom Store Flush Pipeline | `sum(rate(greptime_prom_store_flush_total{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_prom_store_flush_rows_sum{instance=~"$frontend"}[$__rate_interval]))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_prom_store_flush_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))` | `timeseries` | Remote-write pending-row flush operations, flushed rows, and p99 flush latency. | `prometheus` | `short` | `flush-ops` |
| OTLP Trace Failures | `sum(rate(greptime_frontend_otlp_traces_failure_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | OTLP trace ingestion failures reported by frontends. | `prometheus` | `eps` | `trace-failures` |
# Health
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Total Query Rate | `sum (rate(greptime_servers_mysql_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum (rate(greptime_servers_postgres_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum (rate(greptime_servers_http_promql_elapsed_counte{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Total rate of query API calls by protocol. This metric is collected from frontends.<br/><br/>Here we listed 3 main protocols:<br/>- MySQL<br/>- Postgres<br/>- Prometheus API<br/><br/>Note that there are some other minor query APIs like /sql are not included | `prometheus` | `reqps` | `mysql` |
| Protocol Error Rates | `sum by (protocol) (rate(greptime_servers_error{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum by (code) (rate(greptime_servers_auth_failure_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum by (path, method, code) (rate(greptime_servers_http_requests_elapsed_count{instance=~"$frontend",path!~"/health\|/metrics",code!~"2.."}[$__rate_interval]))`<br/>`sum by (path, code) (rate(greptime_servers_grpc_requests_elapsed_count{instance=~"$frontend",code!~"0\|OK"}[$__rate_interval]))` | `timeseries` | User-facing and protocol-level error rates. Use labels to identify whether failures are server, auth, HTTP, or gRPC related. | `prometheus` | `eps` | `server-{{protocol}}` |
| Frontend and Query Rejections | `sum(rate(greptime_servers_request_memory_rejected_total{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_query_memory_pool_rejected_total[$__rate_interval]))` | `timeseries` | Request and query memory rejections. Non-zero values indicate requests are being rejected before or during execution. | `prometheus` | `rps` | `request-memory` |
| Datanode Write Failures | `sum by (instance, pod) (rate(greptime_datanode_region_request_fail_count{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum by (instance, pod) (rate(greptime_datanode_region_failed_insert_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Region request failures and failed inserts on datanodes. These indicate backend write-path errors after routing. | `prometheus` | `eps` | `region-request-[{{instance}}]-[{{pod}}]` |
| Buffered Ingestion Loss | `sum(rate(greptime_pending_rows_flush_failures{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_pending_rows_flush_dropped_rows{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Pending-row flush failures and dropped rows. Sustained non-zero dropped rows are a data-loss signal. | `prometheus` | `eps` | `flush-failures` |
| Mito Backpressure and Failures | `sum(rate(greptime_mito_write_reject_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum(rate(greptime_mito_write_stall_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum(rate(greptime_mito_flush_failure_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum(rate(greptime_mito_compaction_failure_total{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Storage-engine write rejects, write stalls, flush failures, and compaction failures on datanodes. | `prometheus` | `eps` | `write-reject` |
| Scan and Compaction Memory Rejects | `sum(rate(greptime_mito_scan_requests_rejected_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum(rate(greptime_mito_scan_memory_exhausted_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum(rate(greptime_mito_compaction_memory_rejected_total{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Datanode scan and compaction memory rejection/exhaustion counters. | `prometheus` | `rps` | `scan-rejected` |
| OpenDAL Errors | `sum by (scheme, operation, error) (rate(opendal_operation_errors_total{instance=~"$datanode",error!="NotFound"}[$__rate_interval]))` | `timeseries` | Object-store errors by scheme, operation, and error, excluding NotFound noise. | `prometheus` | `eps` | `{{scheme}}-{{operation}}-{{error}}` |
| Metasrv Failures | `sum(rate(greptime_meta_region_migration_fail[$__rate_interval]))`<br/>`sum(rate(greptime_meta_reconciliation_procedure_error[$__rate_interval]))` | `timeseries` | Region migration and reconciliation failures in metasrv. | `prometheus` | `eps` | `migration-fail` |
| Flow and Trigger Failures | `sum by (code) (rate(greptime_flow_errors[$__rate_interval]))`<br/>`sum(rate(greptime_trigger_evaluate_failure_count[$__rate_interval]))`<br/>`sum(rate(greptime_trigger_send_alert_failure_count[$__rate_interval]))`<br/>`sum(rate(greptime_trigger_save_alert_record_failure_count[$__rate_interval]))` | `timeseries` | Derived-data and alerting pipeline failures. | `prometheus` | `eps` | `flow-{{code}}` |
| Mito GC Failures | `sum(rate(greptime_mito_gc_errors_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum(rate(greptime_mito_gc_orphaned_index_files{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum(rate(greptime_mito_gc_skipped_unparsable_files{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Mito garbage-collection errors and skipped/orphaned files on datanodes. | `prometheus` | `short` | `gc-errors` |
# Capacity
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Runtime Threads | `sum by (instance, pod) (greptime_runtime_threads_alive)`<br/>`sum by (instance, pod) (greptime_runtime_threads_idle)` | `timeseries` | Runtime thread pool size and idle threads by instance. Low idle threads during latency spikes can indicate executor saturation. | `prometheus` | `short` | `alive-[{{instance}}]-[{{pod}}]` |
| Request Memory Utilization | `sum by (instance, pod) (greptime_servers_request_memory_in_use_bytes{instance=~"$frontend"}) / sum by (instance, pod) (greptime_servers_request_memory_limit_bytes{instance=~"$frontend"})` | `timeseries` | Frontend request memory usage divided by configured request memory limit. | `prometheus` | `percentunit` | `[{{instance}}]-[{{pod}}]` |
| Query Memory Usage | `sum by (instance, pod) (greptime_query_memory_pool_usage_bytes)` | `timeseries` | Query memory pool usage. Use this with query memory rejection panels to diagnose query saturation. | `prometheus` | `bytes` | `[{{instance}}]-[{{pod}}]` |
| Scan and Compaction Memory | `sum by (instance, pod) (greptime_mito_scan_memory_usage_bytes{instance=~"$datanode"})`<br/>`sum by (instance, pod) (greptime_mito_compaction_memory_in_use_bytes{instance=~"$datanode"})`<br/>`sum by (instance, pod) (greptime_mito_compaction_memory_limit_bytes{instance=~"$datanode"})` | `timeseries` | Datanode scan memory usage and compaction memory utilization. | `prometheus` | `bytes` | `scan-[{{instance}}]-[{{pod}}]` |
| Write Buffer and Active Stalling | `sum by (instance, pod) (greptime_mito_write_buffer_bytes{instance=~"$datanode"})`<br/>`sum by (instance, pod) (greptime_mito_write_stalling_count{instance=~"$datanode"})` | `timeseries` | Mito write buffer bytes and active write-stalling gauges. Growth here indicates write-path backpressure. | `prometheus` | `bytes` | `buffer-[{{instance}}]-[{{pod}}]` |
| Prom Store Backlog | `sum by (instance, pod) (greptime_prom_store_pending_rows{instance=~"$frontend"})`<br/>`sum by (instance, pod) (greptime_prom_store_pending_batches{instance=~"$frontend"})`<br/>`sum by (instance, pod) (greptime_prom_store_pending_workers{instance=~"$frontend"})` | `timeseries` | Prometheus remote-write pending rows, batches, and workers. Rising pending rows indicate remote-write buffering backlog. | `prometheus` | `short` | `rows-[{{instance}}]-[{{pod}}]` |
| Inflight Flush and Compaction | `sum by (instance, pod) (greptime_mito_inflight_flush_count{instance=~"$datanode"})`<br/>`sum by (instance, pod) (greptime_mito_inflight_compaction_count{instance=~"$datanode"})` | `timeseries` | Current in-flight flush and compaction tasks on datanodes. | `prometheus` | `short` | `flush-[{{instance}}]-[{{pod}}]` |
# Resources
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Datanode Memory per Instance | `sum(process_resident_memory_bytes{instance=~"$datanode"}) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes{instance=~"$datanode"})` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{instance}}]-[{{ pod }}]` |
| Datanode CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{instance=~"$datanode"}[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores{instance=~"$datanode"})` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` |
| Frontend Memory per Instance | `sum(process_resident_memory_bytes{instance=~"$frontend"}) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes{instance=~"$frontend"})` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{ instance }}]-[{{ pod }}]` |
| Frontend CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{instance=~"$frontend"}[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores{instance=~"$frontend"})` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]-cpu` |
| Metasrv Memory per Instance | `sum(process_resident_memory_bytes{instance=~"$metasrv"}) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes{instance=~"$metasrv"})` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{ instance }}]-[{{ pod }}]-resident` |
| Datanode CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{instance=~"$datanode"}[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores{instance=~"$datanode"})` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` |
| Metasrv CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{instance=~"$metasrv"}[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores{instance=~"$metasrv"})` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` |
| Flownode Memory per Instance | `sum(process_resident_memory_bytes{instance=~"$flownode"}) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes{instance=~"$flownode"})` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{ instance }}]-[{{ pod }}]` |
| Frontend Memory per Instance | `sum(process_resident_memory_bytes{instance=~"$frontend"}) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes{instance=~"$frontend"})` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{ instance }}]-[{{ pod }}]` |
| Datanode Memory per Instance | `sum(process_resident_memory_bytes{instance=~"$datanode"}) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes{instance=~"$datanode"})` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{instance}}]-[{{ pod }}]` |
| Metasrv Memory per Instance | `sum(process_resident_memory_bytes{instance=~"$metasrv"}) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes{instance=~"$metasrv"})` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{ instance }}]-[{{ pod }}]-resident` |
| Flownode CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{instance=~"$flownode"}[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores{instance=~"$flownode"})` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` |
| Flownode Memory per Instance | `sum(process_resident_memory_bytes{instance=~"$flownode"}) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes{instance=~"$flownode"})` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{ instance }}]-[{{ pod }}]` |
# Queries
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Query Rate by Protocol | `sum(rate(greptime_servers_mysql_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_postgres_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_promql_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_sql_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_frontend_grpc_handle_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Query API call rates by protocol, collected from frontends. | `prometheus` | `reqps` | `mysql` |
| Query Latency by Protocol | `histogram_quantile(0.95, sum by (le) (rate(greptime_servers_mysql_query_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_mysql_query_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`histogram_quantile(0.95, sum by (le) (rate(greptime_servers_postgres_query_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_postgres_query_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`histogram_quantile(0.95, sum by (le) (rate(greptime_servers_http_promql_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_promql_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`sum(rate(greptime_servers_mysql_query_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum(rate(greptime_servers_mysql_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_postgres_query_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum(rate(greptime_servers_postgres_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_promql_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum(rate(greptime_servers_http_promql_elapsed_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_frontend_grpc_handle_query_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum(rate(greptime_frontend_grpc_handle_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | p95, p99, and average query latency by main frontend protocol. | `prometheus` | `s` | `mysql-p95` |
| Query Stage Latency | `histogram_quantile(0.95, sum by (le, stage) (rate(greptime_query_stage_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le, stage) (rate(greptime_query_stage_elapsed_bucket[$__rate_interval])))` | `timeseries` | p95 and p99 latency by query stage. Use stage labels to identify planning, scan, or merge bottlenecks. | `prometheus` | `s` | `p95-{{stage}}` |
| Merge Scan Fan-out and Errors | `sum by (instance, pod) (greptime_query_merge_scan_regions)`<br/>`sum by (instance, pod) (rate(greptime_query_merge_scan_errors_total[$__rate_interval]))` | `timeseries` | Merge-scan region fan-out and errors. High fan-out can explain slow distributed table scans. | `prometheus` | `short` | `regions-[{{instance}}]-[{{pod}}]` |
| Pushdown Fallback Errors | `sum(rate(greptime_push_down_fallback_errors_total[$__rate_interval]))` | `timeseries` | Failed query pushdown fallback attempts. Non-zero values can indicate optimization paths that increase scan work. | `prometheus` | `eps` | `pushdown-fallback-errors` |
| PromQL Series Count | `sum by (instance, pod) (greptime_promql_series_count)` | `timeseries` | Series count touched by PromQL queries. Correlate this with PromQL latency to identify cardinality-driven slowness. | `prometheus` | `short` | `[{{instance}}]-[{{pod}}]` |
| Connections and Prepared Statements | `sum by (instance, pod) (greptime_servers_mysql_connection_count{instance=~"$frontend"})`<br/>`sum by (instance, pod) (greptime_servers_postgres_connection_count{instance=~"$frontend"})`<br/>`sum by (instance, pod) (rate(greptime_servers_mysql_prepared_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum by (instance, pod) (rate(greptime_servers_postgres_prepared_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | MySQL/PostgreSQL connection and prepared-statement counts. Spikes can indicate client storms or leaks. | `prometheus` | `short` | `mysql-connections-[{{instance}}]-[{{pod}}]` |
# Frontend Requests
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| HTTP QPS per Instance | `sum by(instance, pod, path, method, code) (rate(greptime_servers_http_requests_elapsed_count{instance=~"$frontend",path!~"/health\|/metrics"}[$__rate_interval]))` | `timeseries` | HTTP QPS per Instance. | `prometheus` | `reqps` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]` |
| HTTP P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, path, method, code) (rate(greptime_servers_http_requests_elapsed_bucket{instance=~"$frontend",path!~"/health\|/metrics"}[$__rate_interval])))` | `timeseries` | HTTP P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99` |
| HTTP P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, path, method, code) (rate(greptime_servers_http_requests_elapsed_bucket{instance=~"$frontend",path!~"/health\|/metrics"}[$__rate_interval])))`<br/>`sum by(instance, pod, path, method, code) (rate(greptime_servers_http_requests_elapsed_sum{instance=~"$frontend",path!~"/health\|/metrics"}[$__rate_interval])) / sum by(instance, pod, path, method, code) (rate(greptime_servers_http_requests_elapsed_count{instance=~"$frontend",path!~"/health\|/metrics"}[$__rate_interval]))` | `timeseries` | HTTP P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99` |
| gRPC QPS per Instance | `sum by(instance, pod, path, code) (rate(greptime_servers_grpc_requests_elapsed_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | gRPC QPS per Instance. | `prometheus` | `reqps` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{code}}]` |
| gRPC P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, path, code) (rate(greptime_servers_grpc_requests_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))` | `timeseries` | gRPC P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99` |
| gRPC P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, path, code) (rate(greptime_servers_grpc_requests_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`sum by(instance, pod, path, code) (rate(greptime_servers_grpc_requests_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum by(instance, pod, path, code) (rate(greptime_servers_grpc_requests_elapsed_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | gRPC P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99` |
| MySQL QPS per Instance | `sum by(pod, instance)(rate(greptime_servers_mysql_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | MySQL QPS per Instance. | `prometheus` | `reqps` | `[{{instance}}]-[{{pod}}]` |
| MySQL P99 per Instance | `histogram_quantile(0.99, sum by(pod, instance, le) (rate(greptime_servers_mysql_query_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))` | `timeseries` | MySQL P99 per Instance. | `prometheus` | `s` | `[{{ instance }}]-[{{ pod }}]-p99` |
| MySQL P99 and Avg per Instance | `histogram_quantile(0.99, sum by(pod, instance, le) (rate(greptime_servers_mysql_query_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`sum by(pod, instance) (rate(greptime_servers_mysql_query_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum by(pod, instance) (rate(greptime_servers_mysql_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | MySQL P99 and average per Instance. | `prometheus` | `s` | `[{{ instance }}]-[{{ pod }}]-p99` |
| PostgreSQL QPS per Instance | `sum by(pod, instance)(rate(greptime_servers_postgres_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | PostgreSQL QPS per Instance. | `prometheus` | `reqps` | `[{{instance}}]-[{{pod}}]` |
| PostgreSQL P99 per Instance | `histogram_quantile(0.99, sum by(pod,instance,le) (rate(greptime_servers_postgres_query_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))` | `timeseries` | PostgreSQL P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-p99` |
| PostgreSQL P99 and Avg per Instance | `histogram_quantile(0.99, sum by(pod,instance,le) (rate(greptime_servers_postgres_query_elapsed_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`sum by(pod, instance) (rate(greptime_servers_postgres_query_elapsed_sum{instance=~"$frontend"}[$__rate_interval])) / sum by(pod, instance) (rate(greptime_servers_postgres_query_elapsed_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | PostgreSQL P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-p99` |
# Frontend to Datanode
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Ingest Rows per Instance | `sum by(instance, pod)(rate(greptime_table_operator_ingest_rows{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Ingestion rate by row as in each frontend | `prometheus` | `rowsps` | `[{{instance}}]-[{{pod}}]` |
| Region Call QPS per Instance | `sum by(instance, pod, request_type) (rate(greptime_grpc_region_request_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Region Call QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{request_type}}]` |
| Region Call P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, request_type) (rate(greptime_grpc_region_request_bucket{instance=~"$frontend"}[$__rate_interval])))` | `timeseries` | Region Call P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{request_type}}]` |
| Frontend Handle Bulk Insert Elapsed Time | `sum by(instance, pod, stage) (rate(greptime_table_operator_handle_bulk_insert_sum[$__rate_interval]))/sum by(instance, pod, stage) (rate(greptime_table_operator_handle_bulk_insert_count[$__rate_interval]))`<br/>`histogram_quantile(0.99, sum by(instance, pod, stage, le) (rate(greptime_table_operator_handle_bulk_insert_bucket[$__rate_interval])))` | `timeseries` | Per-stage time for frontend to handle bulk insert requests | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-AVG` |
# Mito Engine
| Region Call P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, request_type) (rate(greptime_grpc_region_request_bucket{instance=~"$frontend"}[$__rate_interval])))`<br/>`sum by(instance, pod, request_type) (rate(greptime_grpc_region_request_sum{instance=~"$frontend"}[$__rate_interval])) / sum by(instance, pod, request_type) (rate(greptime_grpc_region_request_count{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Region Call P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{request_type}}]` |
| Frontend Handle Bulk Insert Elapsed Time | `sum by(instance, pod, stage) (rate(greptime_table_operator_handle_bulk_insert_sum[$__rate_interval]))/sum by(instance, pod, stage) (rate(greptime_table_operator_handle_bulk_insert_count[$__rate_interval]))`<br/>`histogram_quantile(0.99, sum by(instance, pod, stage, le) (rate(greptime_table_operator_handle_bulk_insert_bucket[$__rate_interval])))` | `timeseries` | Per-stage time for frontend to handle bulk insert requests | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-AVG` |
# Datanode
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Region Request Failures and Failed Inserts | `sum by (instance, pod) (rate(greptime_datanode_region_request_fail_count{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum by (instance, pod) (rate(greptime_datanode_region_failed_insert_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Datanode region request failures and failed inserts by instance. | `prometheus` | `eps` | `request-fail-[{{instance}}]-[{{pod}}]` |
| Write Rejects and Stalls | `sum by (instance, pod) (rate(greptime_mito_write_reject_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum by (instance, pod) (rate(greptime_mito_write_stall_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum by (instance, pod) (greptime_mito_write_stalling_count{instance=~"$datanode"})` | `timeseries` | Mito write rejects, write stall events, and active write stalling by datanode. | `prometheus` | `short` | `reject-[{{instance}}]-[{{pod}}]` |
| Flush and Compaction Failures | `sum by (instance, pod) (rate(greptime_mito_flush_failure_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum by (instance, pod) (rate(greptime_mito_compaction_failure_total{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Mito flush and compaction failure rates by datanode. | `prometheus` | `eps` | `flush-[{{instance}}]-[{{pod}}]` |
| Mito GC Health | `sum(rate(greptime_mito_gc_runs_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum(rate(greptime_mito_gc_errors_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum(rate(greptime_mito_gc_files_deleted_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum(rate(greptime_mito_gc_orphaned_index_files{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum(rate(greptime_mito_gc_skipped_unparsable_files{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Mito garbage-collection runs, errors, deleted files, orphaned index files, and skipped unparsable files. | `prometheus` | `short` | `runs` |
| Mito GC Duration | `histogram_quantile(0.99, sum by (le, stage) (rate(greptime_mito_gc_duration_seconds_bucket{instance=~"$datanode"}[$__rate_interval])))`<br/>`sum by (stage) (rate(greptime_mito_gc_duration_seconds_sum{instance=~"$datanode"}[$__rate_interval])) / sum by (stage) (rate(greptime_mito_gc_duration_seconds_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | P99 and average Mito garbage-collection duration by stage. | `prometheus` | `s` | `{{stage}}-p99` |
# Storage
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Request OPS per Instance | `sum by(instance, pod, type) (rate(greptime_mito_handle_request_elapsed_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Request QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
| Request P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, type) (rate(greptime_mito_handle_request_elapsed_bucket{instance=~"$datanode"}[$__rate_interval])))` | `timeseries` | Request P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
| Request P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, type) (rate(greptime_mito_handle_request_elapsed_bucket{instance=~"$datanode"}[$__rate_interval])))`<br/>`sum by(instance, pod, type) (rate(greptime_mito_handle_request_elapsed_sum{instance=~"$datanode"}[$__rate_interval])) / sum by(instance, pod, type) (rate(greptime_mito_handle_request_elapsed_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Request P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
| Request Wait P99 and Avg per Worker | `histogram_quantile(0.95, sum by(instance, pod, worker, le) (rate(greptime_mito_request_wait_time_bucket{instance=~"$datanode"}[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by(instance, pod, worker, le) (rate(greptime_mito_request_wait_time_bucket{instance=~"$datanode"}[$__rate_interval])))`<br/>`sum by(instance, pod, worker) (rate(greptime_mito_request_wait_time_sum{instance=~"$datanode"}[$__rate_interval])) / sum by(instance, pod, worker) (rate(greptime_mito_request_wait_time_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Time Mito requests spend waiting before region worker handling. Use this with request service latency to distinguish queueing from execution time. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{worker}}]-p95` |
| Write Buffer per Instance | `greptime_mito_write_buffer_bytes{instance=~"$datanode"}` | `timeseries` | Write Buffer per Instance. | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]` |
| Write Rows per Instance | `sum by (instance, pod) (rate(greptime_mito_write_rows_total{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Ingestion size by row counts. | `prometheus` | `rowsps` | `[{{instance}}]-[{{pod}}]` |
| Flush OPS per Instance | `sum by(instance, pod, reason) (rate(greptime_mito_flush_requests_total{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Flush QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{reason}}]` |
| Write Stall per Instance | `sum by(instance, pod) (greptime_mito_write_stall_total{instance=~"$datanode"})` | `timeseries` | Write Stall per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]` |
| Read Stage OPS per Instance | `sum by(instance, pod) (rate(greptime_mito_read_stage_elapsed_count{instance=~"$datanode", stage="total"}[$__rate_interval]))` | `timeseries` | Read Stage OPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]` |
| Read Stage P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_read_stage_elapsed_bucket{instance=~"$datanode"}[$__rate_interval])))` | `timeseries` | Read Stage P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]` |
| Write Stage P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_write_stage_elapsed_bucket{instance=~"$datanode"}[$__rate_interval])))` | `timeseries` | Write Stage P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]` |
| Compaction OPS per Instance | `sum by(instance, pod) (rate(greptime_mito_compaction_total_elapsed_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Compaction OPS per Instance. | `prometheus` | `ops` | `[{{ instance }}]-[{{pod}}]` |
| Compaction Elapsed Time per Instance by Stage | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_compaction_stage_elapsed_bucket{instance=~"$datanode"}[$__rate_interval])))`<br/>`sum by(instance, pod, stage) (rate(greptime_mito_compaction_stage_elapsed_sum{instance=~"$datanode"}[$__rate_interval]))/sum by(instance, pod, stage) (rate(greptime_mito_compaction_stage_elapsed_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Compaction latency by stage | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-p99` |
| Compaction P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le,stage) (rate(greptime_mito_compaction_total_elapsed_bucket{instance=~"$datanode"}[$__rate_interval])))` | `timeseries` | Compaction P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-compaction` |
| WAL write size | `histogram_quantile(0.95, sum by(le,instance, pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by(le,instance,pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))`<br/>`sum by (instance, pod)(rate(raft_engine_write_size_sum[$__rate_interval]))` | `timeseries` | Write-ahead logs write size as bytes. This chart includes stats of p95 and p99 size by instance, total WAL write rate. | `prometheus` | `bytes` | `[{{instance}}]-[{{pod}}]-req-size-p95` |
| Read Stage P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_read_stage_elapsed_bucket{instance=~"$datanode"}[$__rate_interval])))`<br/>`sum by(instance, pod, stage) (rate(greptime_mito_read_stage_elapsed_sum{instance=~"$datanode"}[$__rate_interval])) / sum by(instance, pod, stage) (rate(greptime_mito_read_stage_elapsed_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Read Stage P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]` |
| Write Stage P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_write_stage_elapsed_bucket{instance=~"$datanode"}[$__rate_interval])))`<br/>`sum by(instance, pod, stage) (rate(greptime_mito_write_stage_elapsed_sum{instance=~"$datanode"}[$__rate_interval])) / sum by(instance, pod, stage) (rate(greptime_mito_write_stage_elapsed_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Write Stage P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]` |
| Cached Bytes per Instance | `greptime_mito_cache_bytes{instance=~"$datanode"}` | `timeseries` | Cached Bytes per Instance. | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
| Inflight Compaction | `greptime_mito_inflight_compaction_count` | `timeseries` | Ongoing compaction task count | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]` |
| WAL sync duration seconds | `histogram_quantile(0.99, sum by(le, type, node, instance, pod) (rate(raft_engine_sync_log_duration_seconds_bucket[$__rate_interval])))` | `timeseries` | Raft engine (local disk) log store sync latency, p99 | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-p99` |
| Log Store op duration seconds | `histogram_quantile(0.99, sum by(le,logstore,optype,instance, pod) (rate(greptime_logstore_op_elapsed_bucket[$__rate_interval])))` | `timeseries` | Write-ahead log operations latency at p99 | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{logstore}}]-[{{optype}}]-p99` |
| Inflight Flush | `greptime_mito_inflight_flush_count` | `timeseries` | Ongoing flush task count | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]` |
| Compaction Input/Output Bytes | `sum by(instance, pod) (greptime_mito_compaction_input_bytes)`<br/>`sum by(instance, pod) (greptime_mito_compaction_output_bytes)` | `timeseries` | Compaction oinput output bytes | `prometheus` | `bytes` | `[{{instance}}]-[{{pod}}]-input` |
| Region Worker Handle Bulk Insert Requests | `histogram_quantile(0.95, sum by(le,instance, stage, pod) (rate(greptime_region_worker_handle_write_bucket[$__rate_interval])))`<br/>`sum by(instance, stage, pod) (rate(greptime_region_worker_handle_write_sum[$__rate_interval]))/sum by(instance, stage, pod) (rate(greptime_region_worker_handle_write_count[$__rate_interval]))` | `timeseries` | Per-stage elapsed time for region worker to handle bulk insert region requests. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-P95` |
| Active Series and Field Builders Count | `sum by(instance, pod) (greptime_mito_memtable_active_series_count)`<br/>`sum by(instance, pod) (greptime_mito_memtable_field_builder_count)` | `timeseries` | Compaction oinput output bytes | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]-series` |
| Active Series and Field Builders Count | `sum by(instance, pod) (greptime_mito_memtable_active_series_count)`<br/>`sum by(instance, pod) (greptime_mito_memtable_field_builder_count)` | `timeseries` | Active series and field-builder counts per memtable by instance. | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]-series` |
| Region Worker Convert Requests | `histogram_quantile(0.95, sum by(le, instance, stage, pod) (rate(greptime_datanode_convert_region_request_bucket[$__rate_interval])))`<br/>`sum by(le,instance, stage, pod) (rate(greptime_datanode_convert_region_request_sum[$__rate_interval]))/sum by(le,instance, stage, pod) (rate(greptime_datanode_convert_region_request_count[$__rate_interval]))` | `timeseries` | Per-stage elapsed time for region worker to decode requests. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-P95` |
| Cache Miss | `sum by (instance,pod, type) (rate(greptime_mito_cache_miss{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | The local cache miss of the datanode. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
# OpenDAL
# Flush and Compaction
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| QPS per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Read QPS per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation=~"read\|Reader::read"}[$__rate_interval]))` | `timeseries` | Read QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Read P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode",operation=~"read\|Reader::read"}[$__rate_interval])))` | `timeseries` | Read P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Write QPS per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation=~"write\|Writer::write\|Writer::close"}[$__rate_interval]))` | `timeseries` | Write QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Write P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode", operation =~ "Writer::write\|Writer::close\|write"}[$__rate_interval])))` | `timeseries` | Write P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| List QPS per Instance | `sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation="list"}[$__rate_interval]))` | `timeseries` | List QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]` |
| List P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode", operation="list"}[$__rate_interval])))` | `timeseries` | List P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]` |
| Other Requests per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode",operation!~"read\|write\|list\|stat"}[$__rate_interval]))` | `timeseries` | Other Requests per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Other Request P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode", operation!~"read\|write\|list\|Writer::write\|Writer::close\|Reader::read"}[$__rate_interval])))` | `timeseries` | Other Request P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Opendal traffic | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_bytes_sum{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Total traffic as in bytes by instance and operation | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| OpenDAL errors per Instance | `sum by(instance, pod, scheme, operation, error) (rate(opendal_operation_errors_total{instance=~"$datanode", error!="NotFound"}[$__rate_interval]))` | `timeseries` | OpenDAL error counts per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]` |
# Remote WAL
| Flush OPS per Instance | `sum by(instance, pod, reason) (rate(greptime_mito_flush_requests_total{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Flush QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{reason}}]` |
| Flush Elapsed Time | `histogram_quantile(0.95, sum by (instance, pod, le, type) (rate(greptime_mito_flush_elapsed_bucket{instance=~"$datanode"}[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (instance, pod, le, type) (rate(greptime_mito_flush_elapsed_bucket{instance=~"$datanode"}[$__rate_interval])))`<br/>`sum by (instance, pod, type) (rate(greptime_mito_flush_elapsed_sum{instance=~"$datanode"}[$__rate_interval])) / sum by (instance, pod, type) (rate(greptime_mito_flush_elapsed_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Mito flush p95 and p99 elapsed time by datanode and flush type. Use this to identify slow flush jobs. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{type}}]-p95` |
| Flush Throughput | `sum by (instance, pod) (rate(greptime_mito_flush_bytes_total{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum by (instance, pod) (rate(greptime_mito_flush_file_total{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Mito flushed bytes and flushed file rates. Use this with flush elapsed time to distinguish slow jobs from large jobs. | `prometheus` | `Bps` | `[{{instance}}]-[{{pod}}]-bytes` |
| Inflight Flush | `greptime_mito_inflight_flush_count` | `timeseries` | Ongoing flush task count | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]` |
| Compaction OPS per Instance | `sum by(instance, pod) (rate(greptime_mito_compaction_total_elapsed_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Compaction OPS per Instance. | `prometheus` | `ops` | `[{{ instance }}]-[{{pod}}]` |
| Inflight Compaction | `greptime_mito_inflight_compaction_count` | `timeseries` | Ongoing compaction task count | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]` |
| Compaction P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le) (rate(greptime_mito_compaction_total_elapsed_bucket{instance=~"$datanode"}[$__rate_interval])))`<br/>`sum by(instance, pod) (rate(greptime_mito_compaction_total_elapsed_sum{instance=~"$datanode"}[$__rate_interval])) / sum by(instance, pod) (rate(greptime_mito_compaction_total_elapsed_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Compaction P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-p99` |
| Compaction Elapsed Time per Instance by Stage | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_compaction_stage_elapsed_bucket{instance=~"$datanode"}[$__rate_interval])))`<br/>`sum by(instance, pod, stage) (rate(greptime_mito_compaction_stage_elapsed_sum{instance=~"$datanode"}[$__rate_interval]))/sum by(instance, pod, stage) (rate(greptime_mito_compaction_stage_elapsed_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Compaction latency by stage | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-p99` |
| Compaction Input/Output Bytes | `sum by(instance, pod) (rate(greptime_mito_compaction_input_bytes[$__rate_interval]))`<br/>`sum by(instance, pod) (rate(greptime_mito_compaction_output_bytes[$__rate_interval]))` | `timeseries` | Compaction input and output bytes by datanode. Use this to correlate compaction latency with rewritten data volume. | `prometheus` | `Bps` | `[{{instance}}]-[{{pod}}]-input` |
# Index
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Triggered region flush total | `meta_triggered_region_flush_total` | `timeseries` | Triggered region flush total | `prometheus` | `none` | `{{pod}}-{{topic_name}}` |
| Triggered region checkpoint total | `meta_triggered_region_checkpoint_total` | `timeseries` | Triggered region checkpoint total | `prometheus` | `none` | `{{pod}}-{{topic_name}}` |
| Topic estimated replay size | `meta_topic_estimated_replay_size` | `timeseries` | Topic estimated max replay size | `prometheus` | `bytes` | `{{pod}}-{{topic_name}}` |
| Kafka logstore's bytes traffic | `rate(greptime_logstore_kafka_client_bytes_total[$__rate_interval])` | `timeseries` | Kafka logstore's bytes traffic | `prometheus` | `bytes` | `{{pod}}-{{logstore}}` |
| Index Apply Elapsed Time | `histogram_quantile(0.95, sum by (le, type) (rate(greptime_index_apply_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le, type) (rate(greptime_index_apply_elapsed_bucket[$__rate_interval])))` | `timeseries` | Index apply p95 and p99 elapsed time by index type. Slow apply can increase read latency for indexed predicates. | `prometheus` | `s` | `{{type}}-p95` |
| Index Create Elapsed Time | `histogram_quantile(0.95, sum by (le, stage, type) (rate(greptime_index_create_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le, stage, type) (rate(greptime_index_create_elapsed_bucket[$__rate_interval])))` | `timeseries` | Index create p95 and p99 elapsed time by stage and index type. Slow stages can explain flush or compaction delays. | `prometheus` | `s` | `{{type}}-{{stage}}-p95` |
| Index Create Rows and Bytes | `sum by (type) (rate(greptime_index_create_rows_total[$__rate_interval]))`<br/>`sum by (type) (rate(greptime_index_create_bytes_total[$__rate_interval]))` | `timeseries` | Rows and bytes produced by index creation by index type. Spikes here can explain storage write pressure. | `prometheus` | `rowsps` | `{{type}}-rows` |
| Index Memory Usage | `greptime_index_apply_memory_usage`<br/>`sum by (type) (greptime_index_create_memory_usage)` | `timeseries` | Memory used while applying and creating indexes. Growth here can explain memory pressure during indexed flush or compaction work. | `prometheus` | `bytes` | `apply` |
| Index IO Bytes | `sum by (type, file_type) (rate(greptime_index_io_bytes_total[$__rate_interval]))` | `timeseries` | Index read and write byte rates by operation and file type for puffin and intermediate files. | `prometheus` | `Bps` | `{{type}}-{{file_type}}` |
| Index IO Operations | `sum by (type, file_type) (rate(greptime_index_io_op_total[$__rate_interval]))` | `timeseries` | Index IO operation rates by operation and file type, including read, write, seek, and flush operations. | `prometheus` | `ops` | `{{type}}-{{file_type}}` |
| Index Cache | `sum by (type) (rate(greptime_mito_cache_hit{type=~"index.*\|vector_index\|index_result"}[$__rate_interval]))`<br/>`sum by (type) (rate(greptime_mito_cache_miss{type=~"index.*\|vector_index\|index_result"}[$__rate_interval]))`<br/>`sum by (type, cause) (rate(greptime_mito_cache_eviction{type=~"index.*\|vector_index\|index_result"}[$__rate_interval]))` | `timeseries` | Index-related cache hits, misses, and evictions from Mito caches. | `prometheus` | `ops` | `hit-{{type}}` |
# Metasrv
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Inactive and Lease-expired Regions | `sum(greptime_meta_inactive_regions)`<br/>`sum(greptime_lease_expired_region{instance=~"$datanode"})` | `timeseries` | Inactive regions and expired region leases. Non-zero values indicate metasrv or routing health issues. | `prometheus` | `short` | `inactive-regions` |
| Heartbeat Health | `sum(rate(greptime_meta_heartbeat_rate[$__rate_interval]))`<br/>`sum(greptime_meta_heartbeat_connection_num)`<br/>`sum(rate(greptime_frontend_heartbeat_send_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_frontend_heartbeat_recv_count{instance=~"$frontend"}[$__rate_interval]))`<br/>`sum(rate(greptime_datanode_heartbeat_send_count{instance=~"$datanode"}[$__rate_interval]))`<br/>`sum(rate(greptime_datanode_heartbeat_recv_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Metasrv heartbeat receive rate, heartbeat connections, and frontend/datanode heartbeat send/receive counters. | `prometheus` | `short` | `meta-recv-rate` |
| Region migration datanode | `greptime_meta_region_migration_stat{datanode_type="src"}`<br/>`greptime_meta_region_migration_stat{datanode_type="desc"}` | `status-history` | Counter of region migration by source and destination | `prometheus` | -- | `from-datanode-{{datanode_id}}` |
| Region migration error | `greptime_meta_region_migration_error` | `timeseries` | Counter of region migration error | `prometheus` | `none` | `{{pod}}-{{state}}-{{error_type}}` |
| Region migration error | `rate(greptime_meta_region_migration_error[$__rate_interval])` | `timeseries` | Counter of region migration error | `prometheus` | `none` | `{{pod}}-{{state}}-{{error_type}}` |
| Datanode load | `greptime_datanode_load` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `binBps` | `Datanode-{{datanode_id}}-writeload` |
| Rate of SQL Executions (RDS) | `rate(greptime_meta_rds_pg_sql_execute_elapsed_ms_count[$__rate_interval])` | `timeseries` | Displays the rate of SQL executions processed by the Meta service using the RDS backend. | `prometheus` | `none` | `{{pod}} {{op}} {{type}} {{result}} ` |
| SQL Execution Latency (RDS) | `histogram_quantile(0.90, sum by(pod, op, type, result, le) (rate(greptime_meta_rds_pg_sql_execute_elapsed_ms_bucket[$__rate_interval])))` | `timeseries` | Measures the response time of SQL executions via the RDS backend. | `prometheus` | `ms` | `{{pod}} {{op}} {{type}} {{result}} p90` |
| SQL Execution Latency (RDS) | `histogram_quantile(0.90, sum by(pod, op, type, result, le) (rate(greptime_meta_rds_pg_sql_execute_elapsed_ms_bucket[$__rate_interval])))`<br/>`sum by(pod, op, type, result) (rate(greptime_meta_rds_pg_sql_execute_elapsed_ms_sum[$__rate_interval])) / sum by(pod, op, type, result) (rate(greptime_meta_rds_pg_sql_execute_elapsed_ms_count[$__rate_interval]))` | `timeseries` | Measures the response time of SQL executions via the RDS backend. | `prometheus` | `ms` | `{{pod}} {{op}} {{type}} {{result}} p90` |
| Handler Execution Latency | `histogram_quantile(0.90, sum by(pod, le, name) (
rate(greptime_meta_handler_execute_bucket[$__rate_interval])
))` | `timeseries` | Shows latency of Meta handlers by pod and handler name, useful for monitoring handler performance and detecting latency spikes.<br/> | `prometheus` | `s` | `{{pod}} {{name}} p90` |
| Heartbeat Packet Size | `histogram_quantile(0.9, sum by(pod, le) (greptime_meta_heartbeat_stat_memory_size_bucket))` | `timeseries` | Shows p90 heartbeat message sizes, helping track network usage and identify anomalies in heartbeat payload.<br/> | `prometheus` | `bytes` | `{{pod}}` |
| Meta Heartbeat Receive Rate | `rate(greptime_meta_heartbeat_rate[$__rate_interval])` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `{{pod}}` |
| Meta KV Ops Latency | `histogram_quantile(0.99, sum by(pod, le, op, target) (greptime_meta_kv_request_elapsed_bucket))` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `{{pod}}-{{op}} p99` |
| Rate of meta KV Ops | `rate(greptime_meta_kv_request_elapsed_count[$__rate_interval])` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `none` | `{{pod}}-{{op}} p99` |
| DDL Latency | `histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_tables_bucket))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_table))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_view))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_flow))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_drop_table))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_alter_table))` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `CreateLogicalTables-{{step}} p90` |
| Reconciliation stats | `greptime_meta_reconciliation_stats` | `timeseries` | Reconciliation stats | `prometheus` | `s` | `{{pod}}-{{table_type}}-{{type}}` |
| Reconciliation steps | `histogram_quantile(0.9, greptime_meta_reconciliation_procedure_bucket)` | `timeseries` | Elapsed of Reconciliation steps | `prometheus` | `s` | `{{procedure_name}}-{{step}}-P90` |
# Flownode
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Flow Ingest / Output Rate | `sum by(instance, pod, direction) (rate(greptime_flow_processed_rows[$__rate_interval]))` | `timeseries` | Flow Ingest / Output Rate. | `prometheus` | -- | `[{{pod}}]-[{{instance}}]-[{{direction}}]` |
| Flow Ingest Latency | `histogram_quantile(0.95, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod))`<br/>`histogram_quantile(0.99, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod))` | `timeseries` | Flow Ingest Latency. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-p95` |
| Flow Operation Latency | `histogram_quantile(0.95, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type))`<br/>`histogram_quantile(0.99, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type))` | `timeseries` | Flow Operation Latency. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{type}}]-p95` |
| Flow Buffer Size per Instance | `greptime_flow_input_buf_size` | `timeseries` | Flow Buffer Size per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]` |
| Flow Processing Error per Instance | `sum by(instance,pod,code) (rate(greptime_flow_errors[$__rate_interval]))` | `timeseries` | Flow Processing Error per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{code}}]` |
# Trigger
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Trigger Count | `greptime_trigger_count{}` | `timeseries` | Total number of triggers currently defined. | `prometheus` | -- | `__auto` |
| Trigger Eval Elapsed | `histogram_quantile(0.99,
rate(greptime_trigger_evaluate_elapsed_bucket[$__rate_interval])
)`<br/>`histogram_quantile(0.75,
rate(greptime_trigger_evaluate_elapsed_bucket[$__rate_interval])
)` | `timeseries` | Elapsed time for trigger evaluation, including query execution and condition evaluation. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-p99` |
| Trigger Eval Failure Rate | `rate(greptime_trigger_evaluate_failure_count[$__rate_interval])` | `timeseries` | Rate of failed trigger evaluations. | `prometheus` | `none` | `__auto` |
| Send Alert Elapsed | `histogram_quantile(0.99,
rate(greptime_trigger_send_alert_elapsed_bucket[$__rate_interval])
)`<br/>`histogram_quantile(0.75,
rate(greptime_trigger_send_alert_elapsed_bucket[$__rate_interval])
)` | `timeseries` | Elapsed time to send trigger alerts to notification channels. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{channel_type}}]-p99` |
| Send Alert Failure Rate | `rate(greptime_trigger_send_alert_failure_count[$__rate_interval])` | `timeseries` | Rate of failures when sending trigger alerts. | `prometheus` | `none` | `__auto` |
| Save Alert Elapsed | `histogram_quantile(0.99,
rate(greptime_trigger_save_alert_record_elapsed_bucket[$__rate_interval])
)`<br/>`histogram_quantile(0.75,
rate(greptime_trigger_save_alert_record_elapsed_bucket[$__rate_interval])
)` | `timeseries` | Elapsed time to persist trigger alert records. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{storage_type}}]-p99` |
| Save Alert Failure Rate | `rate(greptime_trigger_save_alert_record_failure_count[$__rate_interval])` | `timeseries` | Rate of failures when persisting trigger alert records. | `prometheus` | `none` | `__auto` |
))`<br/>`sum by(pod, name) (rate(greptime_meta_handler_execute_sum[$__rate_interval])) / sum by(pod, name) (rate(greptime_meta_handler_execute_count[$__rate_interval]))` | `timeseries` | Shows latency of Meta handlers by pod and handler name, useful for monitoring handler performance and detecting latency spikes.<br/> | `prometheus` | `s` | `{{pod}} {{name}} p90` |
| Heartbeat Packet Size | `histogram_quantile(0.9, sum by(pod, le) (rate(greptime_meta_heartbeat_stat_memory_size_bucket[$__rate_interval])))` | `timeseries` | Shows p90 heartbeat message sizes, helping track network usage and identify anomalies in heartbeat payload.<br/> | `prometheus` | `bytes` | `{{pod}}` |
| Meta Heartbeat Receive Rate | `rate(greptime_meta_heartbeat_rate[$__rate_interval])` | `timeseries` | Rate of heartbeats received by metasrv from datanodes and frontends. | `prometheus` | `s` | `{{pod}}` |
| Meta KV Ops Latency | `histogram_quantile(0.99, sum by(pod, le, op, target) (rate(greptime_meta_kv_request_elapsed_bucket[$__rate_interval])))`<br/>`sum by(pod, op, target) (rate(greptime_meta_kv_request_elapsed_sum[$__rate_interval])) / sum by(pod, op, target) (rate(greptime_meta_kv_request_elapsed_count[$__rate_interval]))` | `timeseries` | p99 and average latency of metasrv key-value store operations by op and target. | `prometheus` | `s` | `{{pod}}-{{op}} p99` |
| Rate of meta KV Ops | `rate(greptime_meta_kv_request_elapsed_count[$__rate_interval])` | `timeseries` | Rate of metasrv key-value store operations by op. | `prometheus` | `none` | `{{pod}}-{{op}} p99` |
| DDL Latency | `histogram_quantile(0.9, sum by(le, pod, step) (rate(greptime_meta_procedure_create_tables_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (rate(greptime_meta_procedure_create_table_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (rate(greptime_meta_procedure_create_view_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (rate(greptime_meta_procedure_create_flow_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (rate(greptime_meta_procedure_drop_table_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (rate(greptime_meta_procedure_alter_table_bucket[$__rate_interval])))` | `timeseries` | p90 latency of metasrv DDL procedures (create/alter/drop table, create view/flow) by step. | `prometheus` | `s` | `CreateLogicalTables-{{step}} p90` |
| Reconciliation stats | `rate(greptime_meta_reconciliation_stats[$__rate_interval])` | `timeseries` | Reconciliation stats | `prometheus` | `ops` | `{{pod}}-{{table_type}}-{{type}}` |
| Reconciliation steps | `histogram_quantile(0.9, sum by(le, procedure_name, step) (rate(greptime_meta_reconciliation_procedure_bucket[$__rate_interval])))` | `timeseries` | Elapsed of Reconciliation steps | `prometheus` | `s` | `{{procedure_name}}-{{step}}-P90` |
# Hotspot
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
@@ -240,3 +250,45 @@ ORDER BY data_size DESC;` | `piechart` | Distribution of leader regions and data
| Auto Repartition Gate Stops | `sum by (gate, reason) (changes(greptime_auto_repartition_gate_stop_total[$__rate_interval]))` | `timeseries` | Auto repartition gate stop count by gate and reason | `prometheus` | `short` | `{{gate}} / {{reason}}` |
| Auto Repartition Sampling P99 | `histogram_quantile(0.99, sum by (le, stage) (rate(greptime_auto_repartition_sampling_elapsed_bucket[$__rate_interval])))` | `timeseries` | Auto repartition sampling elapsed time by stage | `prometheus` | `s` | `{{stage}}` |
| Auto Repartition Executor P99 | `histogram_quantile(0.99, sum by (le, stage) (rate(greptime_auto_repartition_executor_elapsed_bucket[$__rate_interval])))` | `timeseries` | Auto repartition executor elapsed time by stage | `prometheus` | `s` | `{{stage}}` |
# Object Store
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| QPS per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Read QPS per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation=~"read\|Reader::read"}[$__rate_interval]))` | `timeseries` | Read QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Read P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode",operation=~"read\|Reader::read"}[$__rate_interval])))`<br/>`sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_sum{instance=~"$datanode", operation=~"read\|Reader::read"}[$__rate_interval])) / sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation=~"read\|Reader::read"}[$__rate_interval]))` | `timeseries` | Read P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Write QPS per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation=~"write\|Writer::write\|Writer::close"}[$__rate_interval]))` | `timeseries` | Write QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Write P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode", operation =~ "Writer::write\|Writer::close\|write"}[$__rate_interval])))`<br/>`sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_sum{instance=~"$datanode", operation=~"write\|Writer::write\|Writer::close"}[$__rate_interval])) / sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation=~"write\|Writer::write\|Writer::close"}[$__rate_interval]))` | `timeseries` | Write P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| List QPS per Instance | `sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation="list"}[$__rate_interval]))` | `timeseries` | List QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]` |
| List P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode", operation="list"}[$__rate_interval])))`<br/>`sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_sum{instance=~"$datanode", operation="list"}[$__rate_interval])) / sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation="list"}[$__rate_interval]))` | `timeseries` | List P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]` |
| Other Requests per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation!~"read\|Reader::read\|write\|Writer::write\|Writer::close\|list\|stat"}[$__rate_interval]))` | `timeseries` | Other Requests per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Other Request P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode", operation!~"read\|Reader::read\|write\|Writer::write\|Writer::close\|list\|stat"}[$__rate_interval])))`<br/>`sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_sum{instance=~"$datanode", operation!~"read\|Reader::read\|write\|Writer::write\|Writer::close\|list\|stat"}[$__rate_interval])) / sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation!~"read\|Reader::read\|write\|Writer::write\|Writer::close\|list\|stat"}[$__rate_interval]))` | `timeseries` | Other Request P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Opendal traffic | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_bytes_sum{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Total traffic as in bytes by instance and operation | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| OpenDAL errors per Instance | `sum by(instance, pod, scheme, operation, error) (rate(opendal_operation_errors_total{instance=~"$datanode", error!="NotFound"}[$__rate_interval]))` | `timeseries` | OpenDAL error counts per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]` |
# WAL
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| WAL write size | `histogram_quantile(0.95, sum by(le,instance, pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by(le,instance,pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))`<br/>`sum by (instance, pod)(rate(raft_engine_write_size_sum[$__rate_interval]))` | `timeseries` | Write-ahead logs write size as bytes. This chart includes stats of p95 and p99 size by instance, total WAL write rate. | `prometheus` | `bytes` | `[{{instance}}]-[{{pod}}]-req-size-p95` |
| WAL sync duration seconds | `histogram_quantile(0.99, sum by(le, type, node, instance, pod) (rate(raft_engine_sync_log_duration_seconds_bucket[$__rate_interval])))` | `timeseries` | Raft engine (local disk) log store sync latency, p99 | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-p99` |
| Log Store op duration seconds | `histogram_quantile(0.99, sum by(le,logstore,optype,instance, pod) (rate(greptime_logstore_op_elapsed_bucket[$__rate_interval])))` | `timeseries` | Write-ahead log operations latency at p99 | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{logstore}}]-[{{optype}}]-p99` |
| Triggered region flush total | `meta_triggered_region_flush_total` | `timeseries` | Triggered region flush total | `prometheus` | `none` | `{{pod}}-{{topic_name}}` |
| Triggered region checkpoint total | `meta_triggered_region_checkpoint_total` | `timeseries` | Triggered region checkpoint total | `prometheus` | `none` | `{{pod}}-{{topic_name}}` |
| Topic estimated replay size | `meta_topic_estimated_replay_size` | `timeseries` | Topic estimated max replay size | `prometheus` | `bytes` | `{{pod}}-{{topic_name}}` |
| Kafka logstore's bytes traffic | `rate(greptime_logstore_kafka_client_bytes_total[$__rate_interval])` | `timeseries` | Kafka logstore's bytes traffic | `prometheus` | `bytes` | `{{pod}}-{{logstore}}` |
# Flownode
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Flow Ingest / Output Rate | `sum by(instance, pod, direction) (rate(greptime_flow_processed_rows[$__rate_interval]))` | `timeseries` | Flow Ingest / Output Rate. | `prometheus` | -- | `[{{pod}}]-[{{instance}}]-[{{direction}}]` |
| Flow Ingest Latency | `histogram_quantile(0.95, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod))`<br/>`histogram_quantile(0.99, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod))`<br/>`sum by(instance, pod) (rate(greptime_flow_insert_elapsed_sum[$__rate_interval])) / sum by(instance, pod) (rate(greptime_flow_insert_elapsed_count[$__rate_interval]))` | `timeseries` | Flow Ingest Latency. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-p95` |
| Flow Operation Latency | `histogram_quantile(0.95, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type))`<br/>`histogram_quantile(0.99, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type))` | `timeseries` | Flow Operation Latency. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{type}}]-p95` |
| Flow Buffer Size per Instance | `greptime_flow_input_buf_size` | `timeseries` | Flow Buffer Size per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]` |
| Flow Processing Error per Instance | `sum by(instance,pod,code) (rate(greptime_flow_errors[$__rate_interval]))` | `timeseries` | Flow Processing Error per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{code}}]` |
# Trigger
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Trigger Count | `greptime_trigger_count{}` | `timeseries` | Total number of triggers currently defined. | `prometheus` | -- | `__auto` |
| Trigger Eval Elapsed | `histogram_quantile(0.99, sum by (le) (rate(greptime_trigger_evaluate_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.75, sum by (le) (rate(greptime_trigger_evaluate_elapsed_bucket[$__rate_interval])))`<br/>`sum(rate(greptime_trigger_evaluate_elapsed_sum[$__rate_interval])) / sum(rate(greptime_trigger_evaluate_elapsed_count[$__rate_interval]))` | `timeseries` | Elapsed time for trigger evaluation, including query execution and condition evaluation. | `prometheus` | `s` | `p99` |
| Trigger Eval Failure Rate | `rate(greptime_trigger_evaluate_failure_count[$__rate_interval])` | `timeseries` | Rate of failed trigger evaluations. | `prometheus` | `none` | `__auto` |
| Send Alert Elapsed | `histogram_quantile(0.99, sum by (le) (rate(greptime_trigger_send_alert_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.75, sum by (le) (rate(greptime_trigger_send_alert_elapsed_bucket[$__rate_interval])))`<br/>`sum(rate(greptime_trigger_send_alert_elapsed_sum[$__rate_interval])) / sum(rate(greptime_trigger_send_alert_elapsed_count[$__rate_interval]))` | `timeseries` | Elapsed time to send trigger alerts to notification channels. | `prometheus` | `s` | `p99` |
| Send Alert Failure Rate | `rate(greptime_trigger_send_alert_failure_count[$__rate_interval])` | `timeseries` | Rate of failures when sending trigger alerts. | `prometheus` | `none` | `__auto` |
| Save Alert Elapsed | `histogram_quantile(0.99, sum by (le) (rate(greptime_trigger_save_alert_record_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.75, sum by (le) (rate(greptime_trigger_save_alert_record_elapsed_bucket[$__rate_interval])))`<br/>`sum(rate(greptime_trigger_save_alert_record_elapsed_sum[$__rate_interval])) / sum(rate(greptime_trigger_save_alert_record_elapsed_count[$__rate_interval]))` | `timeseries` | Elapsed time to persist trigger alert records. | `prometheus` | `s` | `p99` |
| Save Alert Failure Rate | `rate(greptime_trigger_save_alert_record_failure_count[$__rate_interval])` | `timeseries` | Rate of failures when persisting trigger alert records. | `prometheus` | `none` | `__auto` |

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -4,144 +4,154 @@
| Uptime | `time() - process_start_time_seconds` | `stat` | The start time of GreptimeDB. | `prometheus` | `s` | `__auto` |
| Version | `SELECT pkg_version FROM information_schema.build_info` | `stat` | GreptimeDB version. | `mysql` | -- | -- |
| Total Ingestion Rate | `sum(rate(greptime_table_operator_ingest_rows[$__rate_interval]))` | `stat` | Total ingestion rate. | `prometheus` | `rowsps` | `__auto` |
| Total Storage Size | `select SUM(disk_size) from information_schema.region_statistics;` | `stat` | Total number of data file size. | `mysql` | `decbytes` | -- |
| Total Rows | `select SUM(region_rows) from information_schema.region_statistics;` | `stat` | Total number of data rows in the cluster. Calculated by sum of rows from each region. | `mysql` | `sishort` | -- |
| Total Query Rate | `sum(rate(greptime_servers_mysql_query_elapsed_count[$__rate_interval])) + sum(rate(greptime_servers_postgres_query_elapsed_count[$__rate_interval])) + sum(rate(greptime_servers_http_promql_elapsed_count[$__rate_interval])) + sum(rate(greptime_servers_http_sql_elapsed_count[$__rate_interval])) + sum(rate(greptime_frontend_grpc_handle_query_elapsed_count[$__rate_interval]))` | `stat` | Total query API call rate across MySQL, PostgreSQL, and PromQL frontends. | `prometheus` | `reqps` | `queries` |
| User-facing Error Rate | `sum(rate(greptime_servers_error[$__rate_interval]))` | `stat` | Server protocol errors returned by frontends. Sustained non-zero values indicate user-visible failures. | `prometheus` | `eps` | `errors` |
| Recent Restarts | `sum(changes(process_start_time_seconds[$__range]))` | `stat` | Process restarts over the selected time range across GreptimeDB roles. | `prometheus` | `short` | `restarts` |
| Deployment | `SELECT count(*) as datanode FROM information_schema.cluster_info WHERE peer_type = 'DATANODE';`<br/>`SELECT count(*) as frontend FROM information_schema.cluster_info WHERE peer_type = 'FRONTEND';`<br/>`SELECT count(*) as metasrv FROM information_schema.cluster_info WHERE peer_type = 'METASRV';`<br/>`SELECT count(*) as flownode FROM information_schema.cluster_info WHERE peer_type = 'FLOWNODE';` | `stat` | The deployment topology of GreptimeDB. | `mysql` | -- | -- |
| Database Resources | `SELECT COUNT(*) as databases FROM information_schema.schemata WHERE schema_name NOT IN ('greptime_private', 'information_schema')`<br/>`SELECT COUNT(*) as tables FROM information_schema.tables WHERE table_schema != 'information_schema'`<br/>`SELECT COUNT(region_id) as regions FROM information_schema.region_peers`<br/>`SELECT COUNT(*) as flows FROM information_schema.flows` | `stat` | The number of the key resources in GreptimeDB. | `mysql` | -- | -- |
| Total Storage Size | `select SUM(disk_size) from information_schema.region_statistics;` | `stat` | Total number of data file size. | `mysql` | `decbytes` | -- |
| Total Rows | `select SUM(region_rows) from information_schema.region_statistics;` | `stat` | Total number of data rows in the cluster. Calculated by sum of rows from each region. | `mysql` | `sishort` | -- |
| Data Size | `SELECT SUM(memtable_size) * 0.42825 as WAL FROM information_schema.region_statistics;`<br/>`SELECT SUM(index_size) as index FROM information_schema.region_statistics;`<br/>`SELECT SUM(manifest_size) as manifest FROM information_schema.region_statistics;` | `stat` | The data size of wal/index/manifest in the GreptimeDB. | `mysql` | `decbytes` | -- |
| Total Ingestion Rate Trend | `sum(rate(greptime_table_operator_ingest_rows[$__rate_interval]))` | `timeseries` | Total ingestion throughput trend across frontends. Protocol breakdown is in the Ingestion row. | `prometheus` | `rowsps` | `ingestion` |
| Total Query Rate Trend | `sum(rate(greptime_servers_mysql_query_elapsed_count[$__rate_interval])) + sum(rate(greptime_servers_postgres_query_elapsed_count[$__rate_interval])) + sum(rate(greptime_servers_http_promql_elapsed_count[$__rate_interval])) + sum(rate(greptime_servers_http_sql_elapsed_count[$__rate_interval])) + sum(rate(greptime_frontend_grpc_handle_query_elapsed_count[$__rate_interval]))` | `timeseries` | Total query API call rate trend across frontend protocols. Protocol breakdown is in the Queries row. | `prometheus` | `reqps` | `queries` |
| HTTP Request P99 and Avg | `histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_requests_elapsed_bucket{path!~"/health\|/metrics"}[$__rate_interval])))`<br/>`sum(rate(greptime_servers_http_requests_elapsed_sum{path!~"/health\|/metrics"}[$__rate_interval])) / sum(rate(greptime_servers_http_requests_elapsed_count{path!~"/health\|/metrics"}[$__rate_interval]))` | `timeseries` | Tail and average latency for HTTP requests served by frontends. Excludes health and metrics endpoints. | `prometheus` | `s` | `http-p99` |
| gRPC Request P99 and Avg | `histogram_quantile(0.99, sum by (le) (rate(greptime_servers_grpc_requests_elapsed_bucket[$__rate_interval])))`<br/>`sum(rate(greptime_servers_grpc_requests_elapsed_sum[$__rate_interval])) / sum(rate(greptime_servers_grpc_requests_elapsed_count[$__rate_interval]))` | `timeseries` | Tail and average latency for gRPC requests served by frontends. | `prometheus` | `s` | `grpc-p99` |
# Ingestion
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Total Ingestion Rate | `sum(rate(greptime_table_operator_ingest_rows{}[$__rate_interval]))` | `timeseries` | Total ingestion rate.<br/><br/>Here we listed 3 primary protocols:<br/><br/>- Prometheus remote write<br/>- Greptime's gRPC API (when using our ingest SDK)<br/>- Log ingestion http API<br/> | `prometheus` | `rowsps` | `ingestion` |
| Ingestion Rate by Type | `sum(rate(greptime_servers_http_logs_ingestion_counter[$__rate_interval]))`<br/>`sum(rate(greptime_servers_prometheus_remote_write_samples[$__rate_interval]))` | `timeseries` | Total ingestion rate.<br/><br/>Here we listed 3 primary protocols:<br/><br/>- Prometheus remote write<br/>- Greptime's gRPC API (when using our ingest SDK)<br/>- Log ingestion http API<br/> | `prometheus` | `rowsps` | `http-logs` |
# Queries
| Total Ingestion Rate | `sum(rate(greptime_table_operator_ingest_rows[$__rate_interval]))` | `timeseries` | Total ingestion rate.<br/><br/>Here we listed 3 primary protocols:<br/><br/>- Prometheus remote write<br/>- Greptime's gRPC API (when using our ingest SDK)<br/>- Log ingestion http API<br/> | `prometheus` | `rowsps` | `ingestion` |
| Ingestion Rate by Protocol | `sum(rate(greptime_table_operator_ingest_rows[$__rate_interval]))`<br/>`sum(rate(greptime_servers_prometheus_remote_write_samples[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_logs_ingestion_counter[$__rate_interval]))`<br/>`sum(rate(greptime_servers_loki_logs_ingestion_counter[$__rate_interval]))`<br/>`sum(rate(greptime_servers_elasticsearch_logs_docs_count[$__rate_interval]))`<br/>`sum(rate(greptime_frontend_otlp_metrics_rows[$__rate_interval]))`<br/>`sum(rate(greptime_frontend_otlp_logs_rows[$__rate_interval]))`<br/>`sum(rate(greptime_frontend_otlp_traces_rows[$__rate_interval]))` | `timeseries` | Rows, samples, or documents ingested by primary observability and table-ingestion protocols. | `prometheus` | `rowsps` | `table-operator` |
| Ingestion Latency by Protocol | `histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_prometheus_write_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_logs_ingestion_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_loki_logs_ingestion_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_elasticsearch_logs_ingestion_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_otlp_metrics_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_otlp_logs_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_otlp_traces_elapsed_bucket[$__rate_interval])))`<br/>`sum(rate(greptime_servers_http_prometheus_write_elapsed_sum[$__rate_interval])) / sum(rate(greptime_servers_http_prometheus_write_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_logs_ingestion_elapsed_sum[$__rate_interval])) / sum(rate(greptime_servers_http_logs_ingestion_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_servers_loki_logs_ingestion_elapsed_sum[$__rate_interval])) / sum(rate(greptime_servers_loki_logs_ingestion_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_servers_elasticsearch_logs_ingestion_elapsed_sum[$__rate_interval])) / sum(rate(greptime_servers_elasticsearch_logs_ingestion_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_otlp_metrics_elapsed_sum[$__rate_interval])) / sum(rate(greptime_servers_http_otlp_metrics_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_otlp_logs_elapsed_sum[$__rate_interval])) / sum(rate(greptime_servers_http_otlp_logs_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_otlp_traces_elapsed_sum[$__rate_interval])) / sum(rate(greptime_servers_http_otlp_traces_elapsed_count[$__rate_interval]))` | `timeseries` | p99 and average HTTP ingestion latency for Prometheus remote write, logs, Loki, Elasticsearch, and OTLP endpoints. | `prometheus` | `s` | `prometheus-write` |
| Bulk Insert Message Rows and Size | `sum(rate(greptime_table_operator_bulk_insert_message_rows_sum[$__rate_interval]))`<br/>`sum(rate(greptime_table_operator_bulk_insert_message_size_sum[$__rate_interval]))` | `timeseries` | Bulk-insert message row and byte rates. Spikes here can explain frontend bulk-insert latency. | `prometheus` | `rowsps` | `rows` |
| Prom Store Flush Pipeline | `sum(rate(greptime_prom_store_flush_total[$__rate_interval]))`<br/>`sum(rate(greptime_prom_store_flush_rows_sum[$__rate_interval]))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_prom_store_flush_elapsed_bucket[$__rate_interval])))` | `timeseries` | Remote-write pending-row flush operations, flushed rows, and p99 flush latency. | `prometheus` | `short` | `flush-ops` |
| OTLP Trace Failures | `sum(rate(greptime_frontend_otlp_traces_failure_count[$__rate_interval]))` | `timeseries` | OTLP trace ingestion failures reported by frontends. | `prometheus` | `eps` | `trace-failures` |
# Health
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Total Query Rate | `sum (rate(greptime_servers_mysql_query_elapsed_count{}[$__rate_interval]))`<br/>`sum (rate(greptime_servers_postgres_query_elapsed_count{}[$__rate_interval]))`<br/>`sum (rate(greptime_servers_http_promql_elapsed_counte{}[$__rate_interval]))` | `timeseries` | Total rate of query API calls by protocol. This metric is collected from frontends.<br/><br/>Here we listed 3 main protocols:<br/>- MySQL<br/>- Postgres<br/>- Prometheus API<br/><br/>Note that there are some other minor query APIs like /sql are not included | `prometheus` | `reqps` | `mysql` |
| Protocol Error Rates | `sum by (protocol) (rate(greptime_servers_error[$__rate_interval]))`<br/>`sum by (code) (rate(greptime_servers_auth_failure_count[$__rate_interval]))`<br/>`sum by (path, method, code) (rate(greptime_servers_http_requests_elapsed_count{path!~"/health\|/metrics",code!~"2.."}[$__rate_interval]))`<br/>`sum by (path, code) (rate(greptime_servers_grpc_requests_elapsed_count{code!~"0\|OK"}[$__rate_interval]))` | `timeseries` | User-facing and protocol-level error rates. Use labels to identify whether failures are server, auth, HTTP, or gRPC related. | `prometheus` | `eps` | `server-{{protocol}}` |
| Frontend and Query Rejections | `sum(rate(greptime_servers_request_memory_rejected_total[$__rate_interval]))`<br/>`sum(rate(greptime_query_memory_pool_rejected_total[$__rate_interval]))` | `timeseries` | Request and query memory rejections. Non-zero values indicate requests are being rejected before or during execution. | `prometheus` | `rps` | `request-memory` |
| Datanode Write Failures | `sum by (instance, pod) (rate(greptime_datanode_region_request_fail_count[$__rate_interval]))`<br/>`sum by (instance, pod) (rate(greptime_datanode_region_failed_insert_count[$__rate_interval]))` | `timeseries` | Region request failures and failed inserts on datanodes. These indicate backend write-path errors after routing. | `prometheus` | `eps` | `region-request-[{{instance}}]-[{{pod}}]` |
| Buffered Ingestion Loss | `sum(rate(greptime_pending_rows_flush_failures[$__rate_interval]))`<br/>`sum(rate(greptime_pending_rows_flush_dropped_rows[$__rate_interval]))` | `timeseries` | Pending-row flush failures and dropped rows. Sustained non-zero dropped rows are a data-loss signal. | `prometheus` | `eps` | `flush-failures` |
| Mito Backpressure and Failures | `sum(rate(greptime_mito_write_reject_total[$__rate_interval]))`<br/>`sum(rate(greptime_mito_write_stall_total[$__rate_interval]))`<br/>`sum(rate(greptime_mito_flush_failure_total[$__rate_interval]))`<br/>`sum(rate(greptime_mito_compaction_failure_total[$__rate_interval]))` | `timeseries` | Storage-engine write rejects, write stalls, flush failures, and compaction failures on datanodes. | `prometheus` | `eps` | `write-reject` |
| Scan and Compaction Memory Rejects | `sum(rate(greptime_mito_scan_requests_rejected_total[$__rate_interval]))`<br/>`sum(rate(greptime_mito_scan_memory_exhausted_total[$__rate_interval]))`<br/>`sum(rate(greptime_mito_compaction_memory_rejected_total[$__rate_interval]))` | `timeseries` | Datanode scan and compaction memory rejection/exhaustion counters. | `prometheus` | `rps` | `scan-rejected` |
| OpenDAL Errors | `sum by (scheme, operation, error) (rate(opendal_operation_errors_total{error!="NotFound"}[$__rate_interval]))` | `timeseries` | Object-store errors by scheme, operation, and error, excluding NotFound noise. | `prometheus` | `eps` | `{{scheme}}-{{operation}}-{{error}}` |
| Metasrv Failures | `sum(rate(greptime_meta_region_migration_fail[$__rate_interval]))`<br/>`sum(rate(greptime_meta_reconciliation_procedure_error[$__rate_interval]))` | `timeseries` | Region migration and reconciliation failures in metasrv. | `prometheus` | `eps` | `migration-fail` |
| Flow and Trigger Failures | `sum by (code) (rate(greptime_flow_errors[$__rate_interval]))`<br/>`sum(rate(greptime_trigger_evaluate_failure_count[$__rate_interval]))`<br/>`sum(rate(greptime_trigger_send_alert_failure_count[$__rate_interval]))`<br/>`sum(rate(greptime_trigger_save_alert_record_failure_count[$__rate_interval]))` | `timeseries` | Derived-data and alerting pipeline failures. | `prometheus` | `eps` | `flow-{{code}}` |
| Mito GC Failures | `sum(rate(greptime_mito_gc_errors_total[$__rate_interval]))`<br/>`sum(rate(greptime_mito_gc_orphaned_index_files[$__rate_interval]))`<br/>`sum(rate(greptime_mito_gc_skipped_unparsable_files[$__rate_interval]))` | `timeseries` | Mito garbage-collection errors and skipped/orphaned files on datanodes. | `prometheus` | `short` | `gc-errors` |
# Capacity
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Runtime Threads | `sum by (instance, pod) (greptime_runtime_threads_alive)`<br/>`sum by (instance, pod) (greptime_runtime_threads_idle)` | `timeseries` | Runtime thread pool size and idle threads by instance. Low idle threads during latency spikes can indicate executor saturation. | `prometheus` | `short` | `alive-[{{instance}}]-[{{pod}}]` |
| Request Memory Utilization | `sum by (instance, pod) (greptime_servers_request_memory_in_use_bytes) / sum by (instance, pod) (greptime_servers_request_memory_limit_bytes)` | `timeseries` | Frontend request memory usage divided by configured request memory limit. | `prometheus` | `percentunit` | `[{{instance}}]-[{{pod}}]` |
| Query Memory Usage | `sum by (instance, pod) (greptime_query_memory_pool_usage_bytes)` | `timeseries` | Query memory pool usage. Use this with query memory rejection panels to diagnose query saturation. | `prometheus` | `bytes` | `[{{instance}}]-[{{pod}}]` |
| Scan and Compaction Memory | `sum by (instance, pod) (greptime_mito_scan_memory_usage_bytes)`<br/>`sum by (instance, pod) (greptime_mito_compaction_memory_in_use_bytes)`<br/>`sum by (instance, pod) (greptime_mito_compaction_memory_limit_bytes)` | `timeseries` | Datanode scan memory usage and compaction memory utilization. | `prometheus` | `bytes` | `scan-[{{instance}}]-[{{pod}}]` |
| Write Buffer and Active Stalling | `sum by (instance, pod) (greptime_mito_write_buffer_bytes)`<br/>`sum by (instance, pod) (greptime_mito_write_stalling_count)` | `timeseries` | Mito write buffer bytes and active write-stalling gauges. Growth here indicates write-path backpressure. | `prometheus` | `bytes` | `buffer-[{{instance}}]-[{{pod}}]` |
| Prom Store Backlog | `sum by (instance, pod) (greptime_prom_store_pending_rows)`<br/>`sum by (instance, pod) (greptime_prom_store_pending_batches)`<br/>`sum by (instance, pod) (greptime_prom_store_pending_workers)` | `timeseries` | Prometheus remote-write pending rows, batches, and workers. Rising pending rows indicate remote-write buffering backlog. | `prometheus` | `short` | `rows-[{{instance}}]-[{{pod}}]` |
| Inflight Flush and Compaction | `sum by (instance, pod) (greptime_mito_inflight_flush_count)`<br/>`sum by (instance, pod) (greptime_mito_inflight_compaction_count)` | `timeseries` | Current in-flight flush and compaction tasks on datanodes. | `prometheus` | `short` | `flush-[{{instance}}]-[{{pod}}]` |
# Resources
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Datanode Memory per Instance | `sum(process_resident_memory_bytes{}) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes{})` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{instance}}]-[{{ pod }}]` |
| Datanode CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores{})` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` |
| Frontend Memory per Instance | `sum(process_resident_memory_bytes{}) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes{})` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{ instance }}]-[{{ pod }}]` |
| Frontend CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores{})` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]-cpu` |
| Metasrv Memory per Instance | `sum(process_resident_memory_bytes{}) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes{})` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{ instance }}]-[{{ pod }}]-resident` |
| Metasrv CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores{})` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` |
| Flownode Memory per Instance | `sum(process_resident_memory_bytes{}) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes{})` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{ instance }}]-[{{ pod }}]` |
| Flownode CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores{})` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` |
| Frontend CPU Usage per Instance | `sum(rate(process_cpu_seconds_total[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores)` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]-cpu` |
| Datanode CPU Usage per Instance | `sum(rate(process_cpu_seconds_total[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores)` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` |
| Metasrv CPU Usage per Instance | `sum(rate(process_cpu_seconds_total[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores)` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` |
| Frontend Memory per Instance | `sum(process_resident_memory_bytes) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes)` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{ instance }}]-[{{ pod }}]` |
| Datanode Memory per Instance | `sum(process_resident_memory_bytes) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes)` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{instance}}]-[{{ pod }}]` |
| Metasrv Memory per Instance | `sum(process_resident_memory_bytes) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes)` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{ instance }}]-[{{ pod }}]-resident` |
| Flownode CPU Usage per Instance | `sum(rate(process_cpu_seconds_total[$__rate_interval]) * 1000) by (instance, pod)`<br/>`max(greptime_cpu_limit_in_millicores)` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` |
| Flownode Memory per Instance | `sum(process_resident_memory_bytes) by (instance, pod)`<br/>`max(greptime_memory_limit_in_bytes)` | `timeseries` | Current memory usage by instance | `prometheus` | `bytes` | `[{{ instance }}]-[{{ pod }}]` |
# Queries
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Query Rate by Protocol | `sum(rate(greptime_servers_mysql_query_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_servers_postgres_query_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_promql_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_sql_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_frontend_grpc_handle_query_elapsed_count[$__rate_interval]))` | `timeseries` | Query API call rates by protocol, collected from frontends. | `prometheus` | `reqps` | `mysql` |
| Query Latency by Protocol | `histogram_quantile(0.95, sum by (le) (rate(greptime_servers_mysql_query_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_mysql_query_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.95, sum by (le) (rate(greptime_servers_postgres_query_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_postgres_query_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.95, sum by (le) (rate(greptime_servers_http_promql_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le) (rate(greptime_servers_http_promql_elapsed_bucket[$__rate_interval])))`<br/>`sum(rate(greptime_servers_mysql_query_elapsed_sum[$__rate_interval])) / sum(rate(greptime_servers_mysql_query_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_servers_postgres_query_elapsed_sum[$__rate_interval])) / sum(rate(greptime_servers_postgres_query_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_servers_http_promql_elapsed_sum[$__rate_interval])) / sum(rate(greptime_servers_http_promql_elapsed_count[$__rate_interval]))`<br/>`sum(rate(greptime_frontend_grpc_handle_query_elapsed_sum[$__rate_interval])) / sum(rate(greptime_frontend_grpc_handle_query_elapsed_count[$__rate_interval]))` | `timeseries` | p95, p99, and average query latency by main frontend protocol. | `prometheus` | `s` | `mysql-p95` |
| Query Stage Latency | `histogram_quantile(0.95, sum by (le, stage) (rate(greptime_query_stage_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le, stage) (rate(greptime_query_stage_elapsed_bucket[$__rate_interval])))` | `timeseries` | p95 and p99 latency by query stage. Use stage labels to identify planning, scan, or merge bottlenecks. | `prometheus` | `s` | `p95-{{stage}}` |
| Merge Scan Fan-out and Errors | `sum by (instance, pod) (greptime_query_merge_scan_regions)`<br/>`sum by (instance, pod) (rate(greptime_query_merge_scan_errors_total[$__rate_interval]))` | `timeseries` | Merge-scan region fan-out and errors. High fan-out can explain slow distributed table scans. | `prometheus` | `short` | `regions-[{{instance}}]-[{{pod}}]` |
| Pushdown Fallback Errors | `sum(rate(greptime_push_down_fallback_errors_total[$__rate_interval]))` | `timeseries` | Failed query pushdown fallback attempts. Non-zero values can indicate optimization paths that increase scan work. | `prometheus` | `eps` | `pushdown-fallback-errors` |
| PromQL Series Count | `sum by (instance, pod) (greptime_promql_series_count)` | `timeseries` | Series count touched by PromQL queries. Correlate this with PromQL latency to identify cardinality-driven slowness. | `prometheus` | `short` | `[{{instance}}]-[{{pod}}]` |
| Connections and Prepared Statements | `sum by (instance, pod) (greptime_servers_mysql_connection_count)`<br/>`sum by (instance, pod) (greptime_servers_postgres_connection_count)`<br/>`sum by (instance, pod) (rate(greptime_servers_mysql_prepared_count[$__rate_interval]))`<br/>`sum by (instance, pod) (rate(greptime_servers_postgres_prepared_count[$__rate_interval]))` | `timeseries` | MySQL/PostgreSQL connection and prepared-statement counts. Spikes can indicate client storms or leaks. | `prometheus` | `short` | `mysql-connections-[{{instance}}]-[{{pod}}]` |
# Frontend Requests
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| HTTP QPS per Instance | `sum by(instance, pod, path, method, code) (rate(greptime_servers_http_requests_elapsed_count{path!~"/health\|/metrics"}[$__rate_interval]))` | `timeseries` | HTTP QPS per Instance. | `prometheus` | `reqps` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]` |
| HTTP P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, path, method, code) (rate(greptime_servers_http_requests_elapsed_bucket{path!~"/health\|/metrics"}[$__rate_interval])))` | `timeseries` | HTTP P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99` |
| gRPC QPS per Instance | `sum by(instance, pod, path, code) (rate(greptime_servers_grpc_requests_elapsed_count{}[$__rate_interval]))` | `timeseries` | gRPC QPS per Instance. | `prometheus` | `reqps` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{code}}]` |
| gRPC P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, path, code) (rate(greptime_servers_grpc_requests_elapsed_bucket{}[$__rate_interval])))` | `timeseries` | gRPC P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99` |
| MySQL QPS per Instance | `sum by(pod, instance)(rate(greptime_servers_mysql_query_elapsed_count{}[$__rate_interval]))` | `timeseries` | MySQL QPS per Instance. | `prometheus` | `reqps` | `[{{instance}}]-[{{pod}}]` |
| MySQL P99 per Instance | `histogram_quantile(0.99, sum by(pod, instance, le) (rate(greptime_servers_mysql_query_elapsed_bucket{}[$__rate_interval])))` | `timeseries` | MySQL P99 per Instance. | `prometheus` | `s` | `[{{ instance }}]-[{{ pod }}]-p99` |
| PostgreSQL QPS per Instance | `sum by(pod, instance)(rate(greptime_servers_postgres_query_elapsed_count{}[$__rate_interval]))` | `timeseries` | PostgreSQL QPS per Instance. | `prometheus` | `reqps` | `[{{instance}}]-[{{pod}}]` |
| PostgreSQL P99 per Instance | `histogram_quantile(0.99, sum by(pod,instance,le) (rate(greptime_servers_postgres_query_elapsed_bucket{}[$__rate_interval])))` | `timeseries` | PostgreSQL P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-p99` |
| HTTP P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, path, method, code) (rate(greptime_servers_http_requests_elapsed_bucket{path!~"/health\|/metrics"}[$__rate_interval])))`<br/>`sum by(instance, pod, path, method, code) (rate(greptime_servers_http_requests_elapsed_sum{path!~"/health\|/metrics"}[$__rate_interval])) / sum by(instance, pod, path, method, code) (rate(greptime_servers_http_requests_elapsed_count{path!~"/health\|/metrics"}[$__rate_interval]))` | `timeseries` | HTTP P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99` |
| gRPC QPS per Instance | `sum by(instance, pod, path, code) (rate(greptime_servers_grpc_requests_elapsed_count[$__rate_interval]))` | `timeseries` | gRPC QPS per Instance. | `prometheus` | `reqps` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{code}}]` |
| gRPC P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, path, code) (rate(greptime_servers_grpc_requests_elapsed_bucket[$__rate_interval])))`<br/>`sum by(instance, pod, path, code) (rate(greptime_servers_grpc_requests_elapsed_sum[$__rate_interval])) / sum by(instance, pod, path, code) (rate(greptime_servers_grpc_requests_elapsed_count[$__rate_interval]))` | `timeseries` | gRPC P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99` |
| MySQL QPS per Instance | `sum by(pod, instance)(rate(greptime_servers_mysql_query_elapsed_count[$__rate_interval]))` | `timeseries` | MySQL QPS per Instance. | `prometheus` | `reqps` | `[{{instance}}]-[{{pod}}]` |
| MySQL P99 and Avg per Instance | `histogram_quantile(0.99, sum by(pod, instance, le) (rate(greptime_servers_mysql_query_elapsed_bucket[$__rate_interval])))`<br/>`sum by(pod, instance) (rate(greptime_servers_mysql_query_elapsed_sum[$__rate_interval])) / sum by(pod, instance) (rate(greptime_servers_mysql_query_elapsed_count[$__rate_interval]))` | `timeseries` | MySQL P99 and average per Instance. | `prometheus` | `s` | `[{{ instance }}]-[{{ pod }}]-p99` |
| PostgreSQL QPS per Instance | `sum by(pod, instance)(rate(greptime_servers_postgres_query_elapsed_count[$__rate_interval]))` | `timeseries` | PostgreSQL QPS per Instance. | `prometheus` | `reqps` | `[{{instance}}]-[{{pod}}]` |
| PostgreSQL P99 and Avg per Instance | `histogram_quantile(0.99, sum by(pod,instance,le) (rate(greptime_servers_postgres_query_elapsed_bucket[$__rate_interval])))`<br/>`sum by(pod, instance) (rate(greptime_servers_postgres_query_elapsed_sum[$__rate_interval])) / sum by(pod, instance) (rate(greptime_servers_postgres_query_elapsed_count[$__rate_interval]))` | `timeseries` | PostgreSQL P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-p99` |
# Frontend to Datanode
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Ingest Rows per Instance | `sum by(instance, pod)(rate(greptime_table_operator_ingest_rows{}[$__rate_interval]))` | `timeseries` | Ingestion rate by row as in each frontend | `prometheus` | `rowsps` | `[{{instance}}]-[{{pod}}]` |
| Region Call QPS per Instance | `sum by(instance, pod, request_type) (rate(greptime_grpc_region_request_count{}[$__rate_interval]))` | `timeseries` | Region Call QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{request_type}}]` |
| Region Call P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, request_type) (rate(greptime_grpc_region_request_bucket{}[$__rate_interval])))` | `timeseries` | Region Call P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{request_type}}]` |
| Frontend Handle Bulk Insert Elapsed Time | `sum by(instance, pod, stage) (rate(greptime_table_operator_handle_bulk_insert_sum[$__rate_interval]))/sum by(instance, pod, stage) (rate(greptime_table_operator_handle_bulk_insert_count[$__rate_interval]))`<br/>`histogram_quantile(0.99, sum by(instance, pod, stage, le) (rate(greptime_table_operator_handle_bulk_insert_bucket[$__rate_interval])))` | `timeseries` | Per-stage time for frontend to handle bulk insert requests | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-AVG` |
# Mito Engine
| Region Call QPS per Instance | `sum by(instance, pod, request_type) (rate(greptime_grpc_region_request_count[$__rate_interval]))` | `timeseries` | Region Call QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{request_type}}]` |
| Region Call P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, request_type) (rate(greptime_grpc_region_request_bucket[$__rate_interval])))`<br/>`sum by(instance, pod, request_type) (rate(greptime_grpc_region_request_sum[$__rate_interval])) / sum by(instance, pod, request_type) (rate(greptime_grpc_region_request_count[$__rate_interval]))` | `timeseries` | Region Call P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{request_type}}]` |
| Frontend Handle Bulk Insert Elapsed Time | `sum by(instance, pod, stage) (rate(greptime_table_operator_handle_bulk_insert_sum[$__rate_interval]))/sum by(instance, pod, stage) (rate(greptime_table_operator_handle_bulk_insert_count[$__rate_interval]))`<br/>`histogram_quantile(0.99, sum by(instance, pod, stage, le) (rate(greptime_table_operator_handle_bulk_insert_bucket[$__rate_interval])))` | `timeseries` | Per-stage time for frontend to handle bulk insert requests | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-AVG` |
# Datanode
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Request OPS per Instance | `sum by(instance, pod, type) (rate(greptime_mito_handle_request_elapsed_count{}[$__rate_interval]))` | `timeseries` | Request QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
| Request P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, type) (rate(greptime_mito_handle_request_elapsed_bucket{}[$__rate_interval])))` | `timeseries` | Request P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
| Write Buffer per Instance | `greptime_mito_write_buffer_bytes{}` | `timeseries` | Write Buffer per Instance. | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]` |
| Write Rows per Instance | `sum by (instance, pod) (rate(greptime_mito_write_rows_total{}[$__rate_interval]))` | `timeseries` | Ingestion size by row counts. | `prometheus` | `rowsps` | `[{{instance}}]-[{{pod}}]` |
| Flush OPS per Instance | `sum by(instance, pod, reason) (rate(greptime_mito_flush_requests_total{}[$__rate_interval]))` | `timeseries` | Flush QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{reason}}]` |
| Write Stall per Instance | `sum by(instance, pod) (greptime_mito_write_stall_total{})` | `timeseries` | Write Stall per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]` |
| Read Stage OPS per Instance | `sum by(instance, pod) (rate(greptime_mito_read_stage_elapsed_count{ stage="total"}[$__rate_interval]))` | `timeseries` | Read Stage OPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]` |
| Read Stage P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_read_stage_elapsed_bucket{}[$__rate_interval])))` | `timeseries` | Read Stage P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]` |
| Write Stage P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_write_stage_elapsed_bucket{}[$__rate_interval])))` | `timeseries` | Write Stage P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]` |
| Compaction OPS per Instance | `sum by(instance, pod) (rate(greptime_mito_compaction_total_elapsed_count{}[$__rate_interval]))` | `timeseries` | Compaction OPS per Instance. | `prometheus` | `ops` | `[{{ instance }}]-[{{pod}}]` |
| Compaction Elapsed Time per Instance by Stage | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_compaction_stage_elapsed_bucket{}[$__rate_interval])))`<br/>`sum by(instance, pod, stage) (rate(greptime_mito_compaction_stage_elapsed_sum{}[$__rate_interval]))/sum by(instance, pod, stage) (rate(greptime_mito_compaction_stage_elapsed_count{}[$__rate_interval]))` | `timeseries` | Compaction latency by stage | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-p99` |
| Compaction P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le,stage) (rate(greptime_mito_compaction_total_elapsed_bucket{}[$__rate_interval])))` | `timeseries` | Compaction P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-compaction` |
| WAL write size | `histogram_quantile(0.95, sum by(le,instance, pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by(le,instance,pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))`<br/>`sum by (instance, pod)(rate(raft_engine_write_size_sum[$__rate_interval]))` | `timeseries` | Write-ahead logs write size as bytes. This chart includes stats of p95 and p99 size by instance, total WAL write rate. | `prometheus` | `bytes` | `[{{instance}}]-[{{pod}}]-req-size-p95` |
| Cached Bytes per Instance | `greptime_mito_cache_bytes{}` | `timeseries` | Cached Bytes per Instance. | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
| Inflight Compaction | `greptime_mito_inflight_compaction_count` | `timeseries` | Ongoing compaction task count | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]` |
| WAL sync duration seconds | `histogram_quantile(0.99, sum by(le, type, node, instance, pod) (rate(raft_engine_sync_log_duration_seconds_bucket[$__rate_interval])))` | `timeseries` | Raft engine (local disk) log store sync latency, p99 | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-p99` |
| Log Store op duration seconds | `histogram_quantile(0.99, sum by(le,logstore,optype,instance, pod) (rate(greptime_logstore_op_elapsed_bucket[$__rate_interval])))` | `timeseries` | Write-ahead log operations latency at p99 | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{logstore}}]-[{{optype}}]-p99` |
| Inflight Flush | `greptime_mito_inflight_flush_count` | `timeseries` | Ongoing flush task count | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]` |
| Compaction Input/Output Bytes | `sum by(instance, pod) (greptime_mito_compaction_input_bytes)`<br/>`sum by(instance, pod) (greptime_mito_compaction_output_bytes)` | `timeseries` | Compaction oinput output bytes | `prometheus` | `bytes` | `[{{instance}}]-[{{pod}}]-input` |
| Region Request Failures and Failed Inserts | `sum by (instance, pod) (rate(greptime_datanode_region_request_fail_count[$__rate_interval]))`<br/>`sum by (instance, pod) (rate(greptime_datanode_region_failed_insert_count[$__rate_interval]))` | `timeseries` | Datanode region request failures and failed inserts by instance. | `prometheus` | `eps` | `request-fail-[{{instance}}]-[{{pod}}]` |
| Write Rejects and Stalls | `sum by (instance, pod) (rate(greptime_mito_write_reject_total[$__rate_interval]))`<br/>`sum by (instance, pod) (rate(greptime_mito_write_stall_total[$__rate_interval]))`<br/>`sum by (instance, pod) (greptime_mito_write_stalling_count)` | `timeseries` | Mito write rejects, write stall events, and active write stalling by datanode. | `prometheus` | `short` | `reject-[{{instance}}]-[{{pod}}]` |
| Flush and Compaction Failures | `sum by (instance, pod) (rate(greptime_mito_flush_failure_total[$__rate_interval]))`<br/>`sum by (instance, pod) (rate(greptime_mito_compaction_failure_total[$__rate_interval]))` | `timeseries` | Mito flush and compaction failure rates by datanode. | `prometheus` | `eps` | `flush-[{{instance}}]-[{{pod}}]` |
| Mito GC Health | `sum(rate(greptime_mito_gc_runs_total[$__rate_interval]))`<br/>`sum(rate(greptime_mito_gc_errors_total[$__rate_interval]))`<br/>`sum(rate(greptime_mito_gc_files_deleted_total[$__rate_interval]))`<br/>`sum(rate(greptime_mito_gc_orphaned_index_files[$__rate_interval]))`<br/>`sum(rate(greptime_mito_gc_skipped_unparsable_files[$__rate_interval]))` | `timeseries` | Mito garbage-collection runs, errors, deleted files, orphaned index files, and skipped unparsable files. | `prometheus` | `short` | `runs` |
| Mito GC Duration | `histogram_quantile(0.99, sum by (le, stage) (rate(greptime_mito_gc_duration_seconds_bucket[$__rate_interval])))`<br/>`sum by (stage) (rate(greptime_mito_gc_duration_seconds_sum[$__rate_interval])) / sum by (stage) (rate(greptime_mito_gc_duration_seconds_count[$__rate_interval]))` | `timeseries` | P99 and average Mito garbage-collection duration by stage. | `prometheus` | `s` | `{{stage}}-p99` |
# Storage
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Request OPS per Instance | `sum by(instance, pod, type) (rate(greptime_mito_handle_request_elapsed_count[$__rate_interval]))` | `timeseries` | Request QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
| Request P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, type) (rate(greptime_mito_handle_request_elapsed_bucket[$__rate_interval])))`<br/>`sum by(instance, pod, type) (rate(greptime_mito_handle_request_elapsed_sum[$__rate_interval])) / sum by(instance, pod, type) (rate(greptime_mito_handle_request_elapsed_count[$__rate_interval]))` | `timeseries` | Request P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
| Request Wait P99 and Avg per Worker | `histogram_quantile(0.95, sum by(instance, pod, worker, le) (rate(greptime_mito_request_wait_time_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by(instance, pod, worker, le) (rate(greptime_mito_request_wait_time_bucket[$__rate_interval])))`<br/>`sum by(instance, pod, worker) (rate(greptime_mito_request_wait_time_sum[$__rate_interval])) / sum by(instance, pod, worker) (rate(greptime_mito_request_wait_time_count[$__rate_interval]))` | `timeseries` | Time Mito requests spend waiting before region worker handling. Use this with request service latency to distinguish queueing from execution time. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{worker}}]-p95` |
| Write Buffer per Instance | `greptime_mito_write_buffer_bytes` | `timeseries` | Write Buffer per Instance. | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]` |
| Write Rows per Instance | `sum by (instance, pod) (rate(greptime_mito_write_rows_total[$__rate_interval]))` | `timeseries` | Ingestion size by row counts. | `prometheus` | `rowsps` | `[{{instance}}]-[{{pod}}]` |
| Read Stage OPS per Instance | `sum by(instance, pod) (rate(greptime_mito_read_stage_elapsed_count{stage="total"}[$__rate_interval]))` | `timeseries` | Read Stage OPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]` |
| Read Stage P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_read_stage_elapsed_bucket[$__rate_interval])))`<br/>`sum by(instance, pod, stage) (rate(greptime_mito_read_stage_elapsed_sum[$__rate_interval])) / sum by(instance, pod, stage) (rate(greptime_mito_read_stage_elapsed_count[$__rate_interval]))` | `timeseries` | Read Stage P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]` |
| Write Stage P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_write_stage_elapsed_bucket[$__rate_interval])))`<br/>`sum by(instance, pod, stage) (rate(greptime_mito_write_stage_elapsed_sum[$__rate_interval])) / sum by(instance, pod, stage) (rate(greptime_mito_write_stage_elapsed_count[$__rate_interval]))` | `timeseries` | Write Stage P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]` |
| Cached Bytes per Instance | `greptime_mito_cache_bytes` | `timeseries` | Cached Bytes per Instance. | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
| Region Worker Handle Bulk Insert Requests | `histogram_quantile(0.95, sum by(le,instance, stage, pod) (rate(greptime_region_worker_handle_write_bucket[$__rate_interval])))`<br/>`sum by(instance, stage, pod) (rate(greptime_region_worker_handle_write_sum[$__rate_interval]))/sum by(instance, stage, pod) (rate(greptime_region_worker_handle_write_count[$__rate_interval]))` | `timeseries` | Per-stage elapsed time for region worker to handle bulk insert region requests. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-P95` |
| Active Series and Field Builders Count | `sum by(instance, pod) (greptime_mito_memtable_active_series_count)`<br/>`sum by(instance, pod) (greptime_mito_memtable_field_builder_count)` | `timeseries` | Compaction oinput output bytes | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]-series` |
| Active Series and Field Builders Count | `sum by(instance, pod) (greptime_mito_memtable_active_series_count)`<br/>`sum by(instance, pod) (greptime_mito_memtable_field_builder_count)` | `timeseries` | Active series and field-builder counts per memtable by instance. | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]-series` |
| Region Worker Convert Requests | `histogram_quantile(0.95, sum by(le, instance, stage, pod) (rate(greptime_datanode_convert_region_request_bucket[$__rate_interval])))`<br/>`sum by(le,instance, stage, pod) (rate(greptime_datanode_convert_region_request_sum[$__rate_interval]))/sum by(le,instance, stage, pod) (rate(greptime_datanode_convert_region_request_count[$__rate_interval]))` | `timeseries` | Per-stage elapsed time for region worker to decode requests. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-P95` |
| Cache Miss | `sum by (instance,pod, type) (rate(greptime_mito_cache_miss{}[$__rate_interval]))` | `timeseries` | The local cache miss of the datanode. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
# OpenDAL
| Cache Miss | `sum by (instance,pod, type) (rate(greptime_mito_cache_miss[$__rate_interval]))` | `timeseries` | The local cache miss of the datanode. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{type}}]` |
# Flush and Compaction
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| QPS per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{}[$__rate_interval]))` | `timeseries` | QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Read QPS per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{ operation=~"read\|Reader::read"}[$__rate_interval]))` | `timeseries` | Read QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Read P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{operation=~"read\|Reader::read"}[$__rate_interval])))` | `timeseries` | Read P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Write QPS per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{ operation=~"write\|Writer::write\|Writer::close"}[$__rate_interval]))` | `timeseries` | Write QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Write P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{ operation =~ "Writer::write\|Writer::close\|write"}[$__rate_interval])))` | `timeseries` | Write P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| List QPS per Instance | `sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{ operation="list"}[$__rate_interval]))` | `timeseries` | List QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]` |
| List P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{ operation="list"}[$__rate_interval])))` | `timeseries` | List P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]` |
| Other Requests per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{operation!~"read\|write\|list\|stat"}[$__rate_interval]))` | `timeseries` | Other Requests per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Other Request P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{ operation!~"read\|write\|list\|Writer::write\|Writer::close\|Reader::read"}[$__rate_interval])))` | `timeseries` | Other Request P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Opendal traffic | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_bytes_sum{}[$__rate_interval]))` | `timeseries` | Total traffic as in bytes by instance and operation | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| OpenDAL errors per Instance | `sum by(instance, pod, scheme, operation, error) (rate(opendal_operation_errors_total{ error!="NotFound"}[$__rate_interval]))` | `timeseries` | OpenDAL error counts per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]` |
# Remote WAL
| Flush OPS per Instance | `sum by(instance, pod, reason) (rate(greptime_mito_flush_requests_total[$__rate_interval]))` | `timeseries` | Flush QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{reason}}]` |
| Flush Elapsed Time | `histogram_quantile(0.95, sum by (instance, pod, le, type) (rate(greptime_mito_flush_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (instance, pod, le, type) (rate(greptime_mito_flush_elapsed_bucket[$__rate_interval])))`<br/>`sum by (instance, pod, type) (rate(greptime_mito_flush_elapsed_sum[$__rate_interval])) / sum by (instance, pod, type) (rate(greptime_mito_flush_elapsed_count[$__rate_interval]))` | `timeseries` | Mito flush p95 and p99 elapsed time by datanode and flush type. Use this to identify slow flush jobs. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{type}}]-p95` |
| Flush Throughput | `sum by (instance, pod) (rate(greptime_mito_flush_bytes_total[$__rate_interval]))`<br/>`sum by (instance, pod) (rate(greptime_mito_flush_file_total[$__rate_interval]))` | `timeseries` | Mito flushed bytes and flushed file rates. Use this with flush elapsed time to distinguish slow jobs from large jobs. | `prometheus` | `Bps` | `[{{instance}}]-[{{pod}}]-bytes` |
| Inflight Flush | `greptime_mito_inflight_flush_count` | `timeseries` | Ongoing flush task count | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]` |
| Compaction OPS per Instance | `sum by(instance, pod) (rate(greptime_mito_compaction_total_elapsed_count[$__rate_interval]))` | `timeseries` | Compaction OPS per Instance. | `prometheus` | `ops` | `[{{ instance }}]-[{{pod}}]` |
| Inflight Compaction | `greptime_mito_inflight_compaction_count` | `timeseries` | Ongoing compaction task count | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]` |
| Compaction P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le) (rate(greptime_mito_compaction_total_elapsed_bucket[$__rate_interval])))`<br/>`sum by(instance, pod) (rate(greptime_mito_compaction_total_elapsed_sum[$__rate_interval])) / sum by(instance, pod) (rate(greptime_mito_compaction_total_elapsed_count[$__rate_interval]))` | `timeseries` | Compaction P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-p99` |
| Compaction Elapsed Time per Instance by Stage | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_compaction_stage_elapsed_bucket[$__rate_interval])))`<br/>`sum by(instance, pod, stage) (rate(greptime_mito_compaction_stage_elapsed_sum[$__rate_interval]))/sum by(instance, pod, stage) (rate(greptime_mito_compaction_stage_elapsed_count[$__rate_interval]))` | `timeseries` | Compaction latency by stage | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-p99` |
| Compaction Input/Output Bytes | `sum by(instance, pod) (rate(greptime_mito_compaction_input_bytes[$__rate_interval]))`<br/>`sum by(instance, pod) (rate(greptime_mito_compaction_output_bytes[$__rate_interval]))` | `timeseries` | Compaction input and output bytes by datanode. Use this to correlate compaction latency with rewritten data volume. | `prometheus` | `Bps` | `[{{instance}}]-[{{pod}}]-input` |
# Index
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Triggered region flush total | `meta_triggered_region_flush_total` | `timeseries` | Triggered region flush total | `prometheus` | `none` | `{{pod}}-{{topic_name}}` |
| Triggered region checkpoint total | `meta_triggered_region_checkpoint_total` | `timeseries` | Triggered region checkpoint total | `prometheus` | `none` | `{{pod}}-{{topic_name}}` |
| Topic estimated replay size | `meta_topic_estimated_replay_size` | `timeseries` | Topic estimated max replay size | `prometheus` | `bytes` | `{{pod}}-{{topic_name}}` |
| Kafka logstore's bytes traffic | `rate(greptime_logstore_kafka_client_bytes_total[$__rate_interval])` | `timeseries` | Kafka logstore's bytes traffic | `prometheus` | `bytes` | `{{pod}}-{{logstore}}` |
| Index Apply Elapsed Time | `histogram_quantile(0.95, sum by (le, type) (rate(greptime_index_apply_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le, type) (rate(greptime_index_apply_elapsed_bucket[$__rate_interval])))` | `timeseries` | Index apply p95 and p99 elapsed time by index type. Slow apply can increase read latency for indexed predicates. | `prometheus` | `s` | `{{type}}-p95` |
| Index Create Elapsed Time | `histogram_quantile(0.95, sum by (le, stage, type) (rate(greptime_index_create_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by (le, stage, type) (rate(greptime_index_create_elapsed_bucket[$__rate_interval])))` | `timeseries` | Index create p95 and p99 elapsed time by stage and index type. Slow stages can explain flush or compaction delays. | `prometheus` | `s` | `{{type}}-{{stage}}-p95` |
| Index Create Rows and Bytes | `sum by (type) (rate(greptime_index_create_rows_total[$__rate_interval]))`<br/>`sum by (type) (rate(greptime_index_create_bytes_total[$__rate_interval]))` | `timeseries` | Rows and bytes produced by index creation by index type. Spikes here can explain storage write pressure. | `prometheus` | `rowsps` | `{{type}}-rows` |
| Index Memory Usage | `greptime_index_apply_memory_usage`<br/>`sum by (type) (greptime_index_create_memory_usage)` | `timeseries` | Memory used while applying and creating indexes. Growth here can explain memory pressure during indexed flush or compaction work. | `prometheus` | `bytes` | `apply` |
| Index IO Bytes | `sum by (type, file_type) (rate(greptime_index_io_bytes_total[$__rate_interval]))` | `timeseries` | Index read and write byte rates by operation and file type for puffin and intermediate files. | `prometheus` | `Bps` | `{{type}}-{{file_type}}` |
| Index IO Operations | `sum by (type, file_type) (rate(greptime_index_io_op_total[$__rate_interval]))` | `timeseries` | Index IO operation rates by operation and file type, including read, write, seek, and flush operations. | `prometheus` | `ops` | `{{type}}-{{file_type}}` |
| Index Cache | `sum by (type) (rate(greptime_mito_cache_hit{type=~"index.*\|vector_index\|index_result"}[$__rate_interval]))`<br/>`sum by (type) (rate(greptime_mito_cache_miss{type=~"index.*\|vector_index\|index_result"}[$__rate_interval]))`<br/>`sum by (type, cause) (rate(greptime_mito_cache_eviction{type=~"index.*\|vector_index\|index_result"}[$__rate_interval]))` | `timeseries` | Index-related cache hits, misses, and evictions from Mito caches. | `prometheus` | `ops` | `hit-{{type}}` |
# Metasrv
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Inactive and Lease-expired Regions | `sum(greptime_meta_inactive_regions)`<br/>`sum(greptime_lease_expired_region)` | `timeseries` | Inactive regions and expired region leases. Non-zero values indicate metasrv or routing health issues. | `prometheus` | `short` | `inactive-regions` |
| Heartbeat Health | `sum(rate(greptime_meta_heartbeat_rate[$__rate_interval]))`<br/>`sum(greptime_meta_heartbeat_connection_num)`<br/>`sum(rate(greptime_frontend_heartbeat_send_count[$__rate_interval]))`<br/>`sum(rate(greptime_frontend_heartbeat_recv_count[$__rate_interval]))`<br/>`sum(rate(greptime_datanode_heartbeat_send_count[$__rate_interval]))`<br/>`sum(rate(greptime_datanode_heartbeat_recv_count[$__rate_interval]))` | `timeseries` | Metasrv heartbeat receive rate, heartbeat connections, and frontend/datanode heartbeat send/receive counters. | `prometheus` | `short` | `meta-recv-rate` |
| Region migration datanode | `greptime_meta_region_migration_stat{datanode_type="src"}`<br/>`greptime_meta_region_migration_stat{datanode_type="desc"}` | `status-history` | Counter of region migration by source and destination | `prometheus` | -- | `from-datanode-{{datanode_id}}` |
| Region migration error | `greptime_meta_region_migration_error` | `timeseries` | Counter of region migration error | `prometheus` | `none` | `{{pod}}-{{state}}-{{error_type}}` |
| Region migration error | `rate(greptime_meta_region_migration_error[$__rate_interval])` | `timeseries` | Counter of region migration error | `prometheus` | `none` | `{{pod}}-{{state}}-{{error_type}}` |
| Datanode load | `greptime_datanode_load` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `binBps` | `Datanode-{{datanode_id}}-writeload` |
| Rate of SQL Executions (RDS) | `rate(greptime_meta_rds_pg_sql_execute_elapsed_ms_count[$__rate_interval])` | `timeseries` | Displays the rate of SQL executions processed by the Meta service using the RDS backend. | `prometheus` | `none` | `{{pod}} {{op}} {{type}} {{result}} ` |
| SQL Execution Latency (RDS) | `histogram_quantile(0.90, sum by(pod, op, type, result, le) (rate(greptime_meta_rds_pg_sql_execute_elapsed_ms_bucket[$__rate_interval])))` | `timeseries` | Measures the response time of SQL executions via the RDS backend. | `prometheus` | `ms` | `{{pod}} {{op}} {{type}} {{result}} p90` |
| SQL Execution Latency (RDS) | `histogram_quantile(0.90, sum by(pod, op, type, result, le) (rate(greptime_meta_rds_pg_sql_execute_elapsed_ms_bucket[$__rate_interval])))`<br/>`sum by(pod, op, type, result) (rate(greptime_meta_rds_pg_sql_execute_elapsed_ms_sum[$__rate_interval])) / sum by(pod, op, type, result) (rate(greptime_meta_rds_pg_sql_execute_elapsed_ms_count[$__rate_interval]))` | `timeseries` | Measures the response time of SQL executions via the RDS backend. | `prometheus` | `ms` | `{{pod}} {{op}} {{type}} {{result}} p90` |
| Handler Execution Latency | `histogram_quantile(0.90, sum by(pod, le, name) (
rate(greptime_meta_handler_execute_bucket[$__rate_interval])
))` | `timeseries` | Shows latency of Meta handlers by pod and handler name, useful for monitoring handler performance and detecting latency spikes.<br/> | `prometheus` | `s` | `{{pod}} {{name}} p90` |
| Heartbeat Packet Size | `histogram_quantile(0.9, sum by(pod, le) (greptime_meta_heartbeat_stat_memory_size_bucket))` | `timeseries` | Shows p90 heartbeat message sizes, helping track network usage and identify anomalies in heartbeat payload.<br/> | `prometheus` | `bytes` | `{{pod}}` |
| Meta Heartbeat Receive Rate | `rate(greptime_meta_heartbeat_rate[$__rate_interval])` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `{{pod}}` |
| Meta KV Ops Latency | `histogram_quantile(0.99, sum by(pod, le, op, target) (greptime_meta_kv_request_elapsed_bucket))` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `{{pod}}-{{op}} p99` |
| Rate of meta KV Ops | `rate(greptime_meta_kv_request_elapsed_count[$__rate_interval])` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `none` | `{{pod}}-{{op}} p99` |
| DDL Latency | `histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_tables_bucket))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_table))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_view))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_flow))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_drop_table))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_alter_table))` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `CreateLogicalTables-{{step}} p90` |
| Reconciliation stats | `greptime_meta_reconciliation_stats` | `timeseries` | Reconciliation stats | `prometheus` | `s` | `{{pod}}-{{table_type}}-{{type}}` |
| Reconciliation steps | `histogram_quantile(0.9, greptime_meta_reconciliation_procedure_bucket)` | `timeseries` | Elapsed of Reconciliation steps | `prometheus` | `s` | `{{procedure_name}}-{{step}}-P90` |
# Flownode
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Flow Ingest / Output Rate | `sum by(instance, pod, direction) (rate(greptime_flow_processed_rows[$__rate_interval]))` | `timeseries` | Flow Ingest / Output Rate. | `prometheus` | -- | `[{{pod}}]-[{{instance}}]-[{{direction}}]` |
| Flow Ingest Latency | `histogram_quantile(0.95, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod))`<br/>`histogram_quantile(0.99, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod))` | `timeseries` | Flow Ingest Latency. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-p95` |
| Flow Operation Latency | `histogram_quantile(0.95, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type))`<br/>`histogram_quantile(0.99, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type))` | `timeseries` | Flow Operation Latency. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{type}}]-p95` |
| Flow Buffer Size per Instance | `greptime_flow_input_buf_size` | `timeseries` | Flow Buffer Size per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]` |
| Flow Processing Error per Instance | `sum by(instance,pod,code) (rate(greptime_flow_errors[$__rate_interval]))` | `timeseries` | Flow Processing Error per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{code}}]` |
# Trigger
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Trigger Count | `greptime_trigger_count{}` | `timeseries` | Total number of triggers currently defined. | `prometheus` | -- | `__auto` |
| Trigger Eval Elapsed | `histogram_quantile(0.99,
rate(greptime_trigger_evaluate_elapsed_bucket[$__rate_interval])
)`<br/>`histogram_quantile(0.75,
rate(greptime_trigger_evaluate_elapsed_bucket[$__rate_interval])
)` | `timeseries` | Elapsed time for trigger evaluation, including query execution and condition evaluation. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-p99` |
| Trigger Eval Failure Rate | `rate(greptime_trigger_evaluate_failure_count[$__rate_interval])` | `timeseries` | Rate of failed trigger evaluations. | `prometheus` | `none` | `__auto` |
| Send Alert Elapsed | `histogram_quantile(0.99,
rate(greptime_trigger_send_alert_elapsed_bucket[$__rate_interval])
)`<br/>`histogram_quantile(0.75,
rate(greptime_trigger_send_alert_elapsed_bucket[$__rate_interval])
)` | `timeseries` | Elapsed time to send trigger alerts to notification channels. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{channel_type}}]-p99` |
| Send Alert Failure Rate | `rate(greptime_trigger_send_alert_failure_count[$__rate_interval])` | `timeseries` | Rate of failures when sending trigger alerts. | `prometheus` | `none` | `__auto` |
| Save Alert Elapsed | `histogram_quantile(0.99,
rate(greptime_trigger_save_alert_record_elapsed_bucket[$__rate_interval])
)`<br/>`histogram_quantile(0.75,
rate(greptime_trigger_save_alert_record_elapsed_bucket[$__rate_interval])
)` | `timeseries` | Elapsed time to persist trigger alert records. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{storage_type}}]-p99` |
| Save Alert Failure Rate | `rate(greptime_trigger_save_alert_record_failure_count[$__rate_interval])` | `timeseries` | Rate of failures when persisting trigger alert records. | `prometheus` | `none` | `__auto` |
))`<br/>`sum by(pod, name) (rate(greptime_meta_handler_execute_sum[$__rate_interval])) / sum by(pod, name) (rate(greptime_meta_handler_execute_count[$__rate_interval]))` | `timeseries` | Shows latency of Meta handlers by pod and handler name, useful for monitoring handler performance and detecting latency spikes.<br/> | `prometheus` | `s` | `{{pod}} {{name}} p90` |
| Heartbeat Packet Size | `histogram_quantile(0.9, sum by(pod, le) (rate(greptime_meta_heartbeat_stat_memory_size_bucket[$__rate_interval])))` | `timeseries` | Shows p90 heartbeat message sizes, helping track network usage and identify anomalies in heartbeat payload.<br/> | `prometheus` | `bytes` | `{{pod}}` |
| Meta Heartbeat Receive Rate | `rate(greptime_meta_heartbeat_rate[$__rate_interval])` | `timeseries` | Rate of heartbeats received by metasrv from datanodes and frontends. | `prometheus` | `s` | `{{pod}}` |
| Meta KV Ops Latency | `histogram_quantile(0.99, sum by(pod, le, op, target) (rate(greptime_meta_kv_request_elapsed_bucket[$__rate_interval])))`<br/>`sum by(pod, op, target) (rate(greptime_meta_kv_request_elapsed_sum[$__rate_interval])) / sum by(pod, op, target) (rate(greptime_meta_kv_request_elapsed_count[$__rate_interval]))` | `timeseries` | p99 and average latency of metasrv key-value store operations by op and target. | `prometheus` | `s` | `{{pod}}-{{op}} p99` |
| Rate of meta KV Ops | `rate(greptime_meta_kv_request_elapsed_count[$__rate_interval])` | `timeseries` | Rate of metasrv key-value store operations by op. | `prometheus` | `none` | `{{pod}}-{{op}} p99` |
| DDL Latency | `histogram_quantile(0.9, sum by(le, pod, step) (rate(greptime_meta_procedure_create_tables_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (rate(greptime_meta_procedure_create_table_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (rate(greptime_meta_procedure_create_view_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (rate(greptime_meta_procedure_create_flow_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (rate(greptime_meta_procedure_drop_table_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (rate(greptime_meta_procedure_alter_table_bucket[$__rate_interval])))` | `timeseries` | p90 latency of metasrv DDL procedures (create/alter/drop table, create view/flow) by step. | `prometheus` | `s` | `CreateLogicalTables-{{step}} p90` |
| Reconciliation stats | `rate(greptime_meta_reconciliation_stats[$__rate_interval])` | `timeseries` | Reconciliation stats | `prometheus` | `ops` | `{{pod}}-{{table_type}}-{{type}}` |
| Reconciliation steps | `histogram_quantile(0.9, sum by(le, procedure_name, step) (rate(greptime_meta_reconciliation_procedure_bucket[$__rate_interval])))` | `timeseries` | Elapsed of Reconciliation steps | `prometheus` | `s` | `{{procedure_name}}-{{step}}-P90` |
# Hotspot
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
@@ -240,3 +250,45 @@ ORDER BY data_size DESC;` | `piechart` | Distribution of leader regions and data
| Auto Repartition Gate Stops | `sum by (gate, reason) (changes(greptime_auto_repartition_gate_stop_total[$__rate_interval]))` | `timeseries` | Auto repartition gate stop count by gate and reason | `prometheus` | `short` | `{{gate}} / {{reason}}` |
| Auto Repartition Sampling P99 | `histogram_quantile(0.99, sum by (le, stage) (rate(greptime_auto_repartition_sampling_elapsed_bucket[$__rate_interval])))` | `timeseries` | Auto repartition sampling elapsed time by stage | `prometheus` | `s` | `{{stage}}` |
| Auto Repartition Executor P99 | `histogram_quantile(0.99, sum by (le, stage) (rate(greptime_auto_repartition_executor_elapsed_bucket[$__rate_interval])))` | `timeseries` | Auto repartition executor elapsed time by stage | `prometheus` | `s` | `{{stage}}` |
# Object Store
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| QPS per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count[$__rate_interval]))` | `timeseries` | QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Read QPS per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{operation=~"read\|Reader::read"}[$__rate_interval]))` | `timeseries` | Read QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Read P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{operation=~"read\|Reader::read"}[$__rate_interval])))`<br/>`sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_sum{operation=~"read\|Reader::read"}[$__rate_interval])) / sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{operation=~"read\|Reader::read"}[$__rate_interval]))` | `timeseries` | Read P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Write QPS per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{operation=~"write\|Writer::write\|Writer::close"}[$__rate_interval]))` | `timeseries` | Write QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Write P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{operation =~ "Writer::write\|Writer::close\|write"}[$__rate_interval])))`<br/>`sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_sum{operation=~"write\|Writer::write\|Writer::close"}[$__rate_interval])) / sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{operation=~"write\|Writer::write\|Writer::close"}[$__rate_interval]))` | `timeseries` | Write P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| List QPS per Instance | `sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{operation="list"}[$__rate_interval]))` | `timeseries` | List QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]` |
| List P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{operation="list"}[$__rate_interval])))`<br/>`sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_sum{operation="list"}[$__rate_interval])) / sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{operation="list"}[$__rate_interval]))` | `timeseries` | List P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]` |
| Other Requests per Instance | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{operation!~"read\|Reader::read\|write\|Writer::write\|Writer::close\|list\|stat"}[$__rate_interval]))` | `timeseries` | Other Requests per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Other Request P99 and Avg per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{operation!~"read\|Reader::read\|write\|Writer::write\|Writer::close\|list\|stat"}[$__rate_interval])))`<br/>`sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_sum{operation!~"read\|Reader::read\|write\|Writer::write\|Writer::close\|list\|stat"}[$__rate_interval])) / sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{operation!~"read\|Reader::read\|write\|Writer::write\|Writer::close\|list\|stat"}[$__rate_interval]))` | `timeseries` | Other Request P99 and average per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Opendal traffic | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_bytes_sum[$__rate_interval]))` | `timeseries` | Total traffic as in bytes by instance and operation | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| OpenDAL errors per Instance | `sum by(instance, pod, scheme, operation, error) (rate(opendal_operation_errors_total{error!="NotFound"}[$__rate_interval]))` | `timeseries` | OpenDAL error counts per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]` |
# WAL
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| WAL write size | `histogram_quantile(0.95, sum by(le,instance, pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.99, sum by(le,instance,pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))`<br/>`sum by (instance, pod)(rate(raft_engine_write_size_sum[$__rate_interval]))` | `timeseries` | Write-ahead logs write size as bytes. This chart includes stats of p95 and p99 size by instance, total WAL write rate. | `prometheus` | `bytes` | `[{{instance}}]-[{{pod}}]-req-size-p95` |
| WAL sync duration seconds | `histogram_quantile(0.99, sum by(le, type, node, instance, pod) (rate(raft_engine_sync_log_duration_seconds_bucket[$__rate_interval])))` | `timeseries` | Raft engine (local disk) log store sync latency, p99 | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-p99` |
| Log Store op duration seconds | `histogram_quantile(0.99, sum by(le,logstore,optype,instance, pod) (rate(greptime_logstore_op_elapsed_bucket[$__rate_interval])))` | `timeseries` | Write-ahead log operations latency at p99 | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{logstore}}]-[{{optype}}]-p99` |
| Triggered region flush total | `meta_triggered_region_flush_total` | `timeseries` | Triggered region flush total | `prometheus` | `none` | `{{pod}}-{{topic_name}}` |
| Triggered region checkpoint total | `meta_triggered_region_checkpoint_total` | `timeseries` | Triggered region checkpoint total | `prometheus` | `none` | `{{pod}}-{{topic_name}}` |
| Topic estimated replay size | `meta_topic_estimated_replay_size` | `timeseries` | Topic estimated max replay size | `prometheus` | `bytes` | `{{pod}}-{{topic_name}}` |
| Kafka logstore's bytes traffic | `rate(greptime_logstore_kafka_client_bytes_total[$__rate_interval])` | `timeseries` | Kafka logstore's bytes traffic | `prometheus` | `bytes` | `{{pod}}-{{logstore}}` |
# Flownode
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Flow Ingest / Output Rate | `sum by(instance, pod, direction) (rate(greptime_flow_processed_rows[$__rate_interval]))` | `timeseries` | Flow Ingest / Output Rate. | `prometheus` | -- | `[{{pod}}]-[{{instance}}]-[{{direction}}]` |
| Flow Ingest Latency | `histogram_quantile(0.95, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod))`<br/>`histogram_quantile(0.99, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod))`<br/>`sum by(instance, pod) (rate(greptime_flow_insert_elapsed_sum[$__rate_interval])) / sum by(instance, pod) (rate(greptime_flow_insert_elapsed_count[$__rate_interval]))` | `timeseries` | Flow Ingest Latency. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-p95` |
| Flow Operation Latency | `histogram_quantile(0.95, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type))`<br/>`histogram_quantile(0.99, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type))` | `timeseries` | Flow Operation Latency. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{type}}]-p95` |
| Flow Buffer Size per Instance | `greptime_flow_input_buf_size` | `timeseries` | Flow Buffer Size per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]` |
| Flow Processing Error per Instance | `sum by(instance,pod,code) (rate(greptime_flow_errors[$__rate_interval]))` | `timeseries` | Flow Processing Error per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{code}}]` |
# Trigger
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Trigger Count | `greptime_trigger_count` | `timeseries` | Total number of triggers currently defined. | `prometheus` | -- | `__auto` |
| Trigger Eval Elapsed | `histogram_quantile(0.99, sum by (le) (rate(greptime_trigger_evaluate_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.75, sum by (le) (rate(greptime_trigger_evaluate_elapsed_bucket[$__rate_interval])))`<br/>`sum(rate(greptime_trigger_evaluate_elapsed_sum[$__rate_interval])) / sum(rate(greptime_trigger_evaluate_elapsed_count[$__rate_interval]))` | `timeseries` | Elapsed time for trigger evaluation, including query execution and condition evaluation. | `prometheus` | `s` | `p99` |
| Trigger Eval Failure Rate | `rate(greptime_trigger_evaluate_failure_count[$__rate_interval])` | `timeseries` | Rate of failed trigger evaluations. | `prometheus` | `none` | `__auto` |
| Send Alert Elapsed | `histogram_quantile(0.99, sum by (le) (rate(greptime_trigger_send_alert_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.75, sum by (le) (rate(greptime_trigger_send_alert_elapsed_bucket[$__rate_interval])))`<br/>`sum(rate(greptime_trigger_send_alert_elapsed_sum[$__rate_interval])) / sum(rate(greptime_trigger_send_alert_elapsed_count[$__rate_interval]))` | `timeseries` | Elapsed time to send trigger alerts to notification channels. | `prometheus` | `s` | `p99` |
| Send Alert Failure Rate | `rate(greptime_trigger_send_alert_failure_count[$__rate_interval])` | `timeseries` | Rate of failures when sending trigger alerts. | `prometheus` | `none` | `__auto` |
| Save Alert Elapsed | `histogram_quantile(0.99, sum by (le) (rate(greptime_trigger_save_alert_record_elapsed_bucket[$__rate_interval])))`<br/>`histogram_quantile(0.75, sum by (le) (rate(greptime_trigger_save_alert_record_elapsed_bucket[$__rate_interval])))`<br/>`sum(rate(greptime_trigger_save_alert_record_elapsed_sum[$__rate_interval])) / sum(rate(greptime_trigger_save_alert_record_elapsed_count[$__rate_interval]))` | `timeseries` | Elapsed time to persist trigger alert records. | `prometheus` | `s` | `p99` |
| Save Alert Failure Rate | `rate(greptime_trigger_save_alert_record_failure_count[$__rate_interval])` | `timeseries` | Rate of failures when persisting trigger alert records. | `prometheus` | `none` | `__auto` |

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,11 @@ DAC_IMAGE=ghcr.io/zyy17/dac:20250423-522bd35
remove_instance_filters() {
# Remove the instance filters for the standalone dashboards.
sed -E 's/instance=~\\"(\$datanode|\$frontend|\$metasrv|\$flownode)\\",?//g' "$CLUSTER_DASHBOARD_DIR/dashboard.json" > "$STANDALONE_DASHBOARD_DIR/dashboard.json"
sed -E 's/instance=~\\"(\$datanode|\$frontend|\$metasrv|\$flownode)\\"[[:space:]]*,?[[:space:]]*//g' "$CLUSTER_DASHBOARD_DIR/dashboard.json" \
| sed -E 's/\{[[:space:]]*,[[:space:]]*/{/g' \
| sed -E 's/,[[:space:]]*\}/}/g' \
| sed -E 's/([A-Za-z_:][A-Za-z0-9_:]*)\{[[:space:]]*\}/\1/g' \
> "$STANDALONE_DASHBOARD_DIR/dashboard.json"
}
generate_intermediate_dashboards_and_docs() {

View File

@@ -1,14 +1,17 @@
#!/usr/bin/env bash
# This script is used to download built dashboard assets from the "GreptimeTeam/dashboard" repository.
set -ex
set -e
declare -r SCRIPT_DIR=$(cd $(dirname ${0}) >/dev/null 2>&1 && pwd)
declare -r ROOT_DIR=$(dirname ${SCRIPT_DIR})
declare -r STATIC_DIR="$ROOT_DIR/src/servers/dashboard"
OUT_DIR="${1:-$SCRIPT_DIR}"
RELEASE_VERSION="$(cat $STATIC_DIR/VERSION | tr -d '\t\r\n ')"
DASHBOARD_REPOSITORY="${DASHBOARD_REPOSITORY:-GreptimeTeam/dashboard}"
DASHBOARD_ASSET="${DASHBOARD_ASSET:-build.tar.gz}"
DASHBOARD_GITHUB_TOKEN="${DASHBOARD_GITHUB_TOKEN:-${GH_TOKEN:-${GITHUB_TOKEN:-}}}"
RELEASE_VERSION="${DASHBOARD_RELEASE_VERSION:-$(cat "$STATIC_DIR/VERSION" | tr -d '\t\r\n ')}"
echo "Downloading assets to dir: $OUT_DIR"
cd $OUT_DIR
@@ -22,8 +25,16 @@ fi
function retry_fetch() {
local url=$1
local filename=$2
local auth_args=()
curl --connect-timeout 10 --retry 3 -fsSL $url --output $filename || {
if [[ -n "$DASHBOARD_GITHUB_TOKEN" ]]; then
auth_args=(
-H "Authorization: Bearer ${DASHBOARD_GITHUB_TOKEN}"
-H "Accept: application/octet-stream"
)
fi
curl --connect-timeout 10 --retry 3 -fsSL "${auth_args[@]}" "$url" --output "$filename" || {
echo "Failed to download $url"
echo "You may try to set http_proxy and https_proxy environment variables."
if [[ -z "$GITHUB_PROXY_URL" ]]; then
@@ -36,10 +47,10 @@ function retry_fetch() {
# Download the SHA256 checksum attached to the release. To verify the integrity
# of the download, this checksum will be used to check the download tar file
# containing the built dashboard assets.
retry_fetch "${GITHUB_URL}/GreptimeTeam/dashboard/releases/download/${RELEASE_VERSION}/sha256.txt" sha256.txt
retry_fetch "${GITHUB_URL}/${DASHBOARD_REPOSITORY}/releases/download/${RELEASE_VERSION}/sha256.txt" sha256.txt
# Download the tar file containing the built dashboard assets.
retry_fetch "${GITHUB_URL}/GreptimeTeam/dashboard/releases/download/${RELEASE_VERSION}/build.tar.gz" build.tar.gz
retry_fetch "${GITHUB_URL}/${DASHBOARD_REPOSITORY}/releases/download/${RELEASE_VERSION}/${DASHBOARD_ASSET}" "$DASHBOARD_ASSET"
# Verify the checksums match; exit if they don't.
case "$(uname -s)" in
@@ -55,8 +66,8 @@ case "$(uname -s)" in
esac
# Extract the assets and clean up.
tar -xzf build.tar.gz -C "$STATIC_DIR"
tar -xzf "$DASHBOARD_ASSET" -C "$STATIC_DIR"
rm sha256.txt
rm build.tar.gz
rm "$DASHBOARD_ASSET"
echo "Successfully download dashboard assets to $STATIC_DIR"

View File

@@ -307,6 +307,7 @@ impl Debug for CancellableProcess {
pub struct SlowQueryTimer {
start: Instant,
stmt: QueryStatement,
schema_name: String,
threshold: Duration,
sample_ratio: f64,
record_type: SlowQueriesRecordType,
@@ -316,6 +317,7 @@ pub struct SlowQueryTimer {
impl SlowQueryTimer {
pub fn new(
stmt: QueryStatement,
schema_name: String,
threshold: Duration,
sample_ratio: f64,
record_type: SlowQueriesRecordType,
@@ -324,6 +326,7 @@ impl SlowQueryTimer {
Self {
start: Instant::now(),
stmt,
schema_name,
threshold,
sample_ratio,
record_type,
@@ -338,6 +341,7 @@ impl SlowQueryTimer {
cost: elapsed.as_millis() as u64,
threshold: self.threshold.as_millis() as u64,
query: "".to_string(),
schema_name: self.schema_name.clone(),
// The following fields are only used for PromQL queries.
is_promql: false,
@@ -388,6 +392,7 @@ impl SlowQueryTimer {
cost = slow_query_event.cost,
threshold = slow_query_event.threshold,
query = slow_query_event.query,
schema_name = slow_query_event.schema_name,
is_promql = slow_query_event.is_promql,
promql_range = slow_query_event.promql_range,
promql_step = slow_query_event.promql_step,

View File

@@ -24,6 +24,7 @@ pub const SLOW_QUERY_TABLE_NAME: &str = "slow_queries";
pub const SLOW_QUERY_TABLE_COST_COLUMN_NAME: &str = "cost";
pub const SLOW_QUERY_TABLE_THRESHOLD_COLUMN_NAME: &str = "threshold";
pub const SLOW_QUERY_TABLE_QUERY_COLUMN_NAME: &str = "query";
pub const SLOW_QUERY_TABLE_SCHEMA_NAME_COLUMN_NAME: &str = "schema_name";
pub const SLOW_QUERY_TABLE_TIMESTAMP_COLUMN_NAME: &str = "timestamp";
pub const SLOW_QUERY_TABLE_IS_PROMQL_COLUMN_NAME: &str = "is_promql";
pub const SLOW_QUERY_TABLE_PROMQL_START_COLUMN_NAME: &str = "promql_start";
@@ -38,6 +39,7 @@ pub struct SlowQueryEvent {
pub cost: u64,
pub threshold: u64,
pub query: String,
pub schema_name: String,
pub is_promql: bool,
pub promql_range: Option<u64>,
pub promql_step: Option<u64>,
@@ -104,6 +106,12 @@ impl Event for SlowQueryEvent {
semantic_type: SemanticType::Field.into(),
..Default::default()
},
ColumnSchema {
column_name: SLOW_QUERY_TABLE_SCHEMA_NAME_COLUMN_NAME.to_string(),
datatype: ColumnDataType::String.into(),
semantic_type: SemanticType::Field.into(),
..Default::default()
},
]
}
@@ -118,6 +126,7 @@ impl Event for SlowQueryEvent {
ValueData::U64Value(self.promql_step.unwrap_or(0)).into(),
ValueData::TimestampMillisecondValue(self.promql_start.unwrap_or(0)).into(),
ValueData::TimestampMillisecondValue(self.promql_end.unwrap_or(0)).into(),
ValueData::StringValue(self.schema_name.clone()).into(),
],
}])
}
@@ -126,3 +135,54 @@ impl Event for SlowQueryEvent {
self
}
}
#[cfg(test)]
mod tests {
use api::v1::value::ValueData;
use common_event_recorder::Event;
use super::*;
#[test]
fn slow_query_event_includes_schema() {
let event = SlowQueryEvent {
cost: 100,
threshold: 10,
query: "SELECT * FROM numbers".to_string(),
schema_name: "public".to_string(),
is_promql: false,
promql_range: None,
promql_step: None,
promql_start: None,
promql_end: None,
};
let schema = event.extra_schema();
let column_names = schema
.iter()
.map(|column| column.column_name.as_str())
.collect::<Vec<_>>();
assert_eq!(
column_names,
vec![
SLOW_QUERY_TABLE_COST_COLUMN_NAME,
SLOW_QUERY_TABLE_THRESHOLD_COLUMN_NAME,
SLOW_QUERY_TABLE_QUERY_COLUMN_NAME,
SLOW_QUERY_TABLE_IS_PROMQL_COLUMN_NAME,
SLOW_QUERY_TABLE_PROMQL_RANGE_COLUMN_NAME,
SLOW_QUERY_TABLE_PROMQL_STEP_COLUMN_NAME,
SLOW_QUERY_TABLE_PROMQL_START_COLUMN_NAME,
SLOW_QUERY_TABLE_PROMQL_END_COLUMN_NAME,
SLOW_QUERY_TABLE_SCHEMA_NAME_COLUMN_NAME,
]
);
assert_eq!(schema[8].semantic_type, SemanticType::Field as i32);
let rows = event.extra_rows().unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(
rows[0].values[8].value_data,
Some(ValueData::StringValue("public".to_string()))
);
}
}

View File

@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::time::Duration;
use common_error::ext::BoxedError;
use common_macro::admin_fn;
use common_meta::rpc::procedure::{GcRegionsRequest, GcTableRequest};
@@ -30,7 +28,6 @@ use snafu::{ResultExt, ensure};
use crate::handlers::ProcedureServiceHandlerRef;
use crate::helper::cast_u64;
const DEFAULT_GC_TIMEOUT: Duration = Duration::from_secs(60);
const DEFAULT_FULL_FILE_LISTING: bool = false;
#[admin_fn(
@@ -50,7 +47,7 @@ pub(crate) async fn gc_regions(
.gc_regions(GcRegionsRequest {
region_ids,
full_file_listing,
timeout: DEFAULT_GC_TIMEOUT,
timeout: None,
})
.await?;
@@ -77,7 +74,7 @@ pub(crate) async fn gc_table(
schema_name,
table_name,
full_file_listing,
timeout: DEFAULT_GC_TIMEOUT,
timeout: None,
})
.await?;
@@ -180,9 +177,18 @@ fn gc_table_signature() -> Signature {
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
use api::v1::meta::ReconcileRequest;
use async_trait::async_trait;
use catalog::CatalogManagerRef;
use common_meta::rpc::procedure::{
GcResponse, ManageRegionFollowerRequest, MigrateRegionRequest, ProcedureStateResponse,
};
use session::context::QueryContext;
use super::*;
use crate::handlers::ProcedureServiceHandler;
#[test]
fn test_parse_gc_regions_params_with_full_file_listing() {
@@ -217,4 +223,80 @@ mod tests {
assert_eq!(table, "t");
assert!(full_file_listing);
}
#[tokio::test]
async fn test_gc_regions_uses_meta_gc_timeout_config() {
let handler = Arc::new(MockProcedureServiceHandler::default());
let handler_ref: ProcedureServiceHandlerRef = handler.clone();
let params = vec![ValueRef::UInt64(1), ValueRef::Boolean(true)];
super::gc_regions(&handler_ref, &QueryContext::arc(), &params)
.await
.unwrap();
let request = handler.gc_regions_request.lock().unwrap().clone().unwrap();
assert_eq!(request.region_ids, vec![1]);
assert!(request.full_file_listing);
assert_eq!(request.timeout, None);
}
#[tokio::test]
async fn test_gc_table_uses_meta_gc_timeout_config() {
let handler = Arc::new(MockProcedureServiceHandler::default());
let handler_ref: ProcedureServiceHandlerRef = handler.clone();
let params = vec![ValueRef::String("public.t"), ValueRef::Boolean(true)];
super::gc_table(&handler_ref, &QueryContext::arc(), &params)
.await
.unwrap();
let request = handler.gc_table_request.lock().unwrap().clone().unwrap();
assert_eq!(request.catalog_name, "greptime");
assert_eq!(request.schema_name, "public");
assert_eq!(request.table_name, "t");
assert!(request.full_file_listing);
assert_eq!(request.timeout, None);
}
#[derive(Default)]
struct MockProcedureServiceHandler {
gc_regions_request: Mutex<Option<GcRegionsRequest>>,
gc_table_request: Mutex<Option<GcTableRequest>>,
}
#[async_trait]
impl ProcedureServiceHandler for MockProcedureServiceHandler {
async fn migrate_region(&self, _request: MigrateRegionRequest) -> Result<Option<String>> {
unreachable!()
}
async fn reconcile(&self, _request: ReconcileRequest) -> Result<Option<String>> {
unreachable!()
}
async fn query_procedure_state(&self, _pid: &str) -> Result<ProcedureStateResponse> {
unreachable!()
}
async fn manage_region_follower(
&self,
_request: ManageRegionFollowerRequest,
) -> Result<()> {
unreachable!()
}
fn catalog_manager(&self) -> &CatalogManagerRef {
unreachable!()
}
async fn gc_regions(&self, request: GcRegionsRequest) -> Result<GcResponse> {
*self.gc_regions_request.lock().unwrap() = Some(request);
Ok(GcResponse::default())
}
async fn gc_table(&self, request: GcTableRequest) -> Result<GcResponse> {
*self.gc_table_request.lock().unwrap() = Some(request);
Ok(GcResponse::default())
}
}
}

View File

@@ -16,7 +16,7 @@ use std::sync::Arc;
use async_trait::async_trait;
use chrono::Utc;
use common_catalog::format_full_table_name;
use common_catalog::{format_full_flow_name, format_full_table_name};
use common_procedure::error::{FromJsonSnafu, Result as ProcedureResult, ToJsonSnafu};
use common_procedure::{Context as ProcedureContext, LockKey, Procedure, Status};
use common_telemetry::tracing::info;
@@ -38,8 +38,8 @@ use crate::key::flow::flow_info::{FlowInfoKey, FlowInfoValue};
use crate::key::table_info::{TableInfoKey, TableInfoValue};
use crate::key::table_name::TableNameKey;
use crate::key::{DeserializedValueWithBytes, FlowId, MetadataKey, MetadataValue};
use crate::lock_key::{CatalogLock, FlowNameLock, SchemaLock, TableNameLock};
use crate::rpc::ddl::{CommentObjectType, CommentOnTask};
use crate::lock_key::{CatalogLock, FlowLock, FlowNameLock, SchemaLock, TableLock, TableNameLock};
use crate::rpc::ddl::{CommentObjectId, CommentObjectType, CommentOnTask};
use crate::rpc::store::PutRequest;
pub struct CommentOnProcedure {
@@ -106,26 +106,29 @@ impl CommentOnProcedure {
}
async fn prepare_table_or_column(&mut self) -> Result<()> {
let table_name_key = TableNameKey::new(
&self.data.catalog_name,
&self.data.schema_name,
&self.data.object_name,
);
let table_id = if let Some(table_id) = self.data.table_id {
table_id
} else {
let table_name_key = TableNameKey::new(
&self.data.catalog_name,
&self.data.schema_name,
&self.data.object_name,
);
let table_id = self
.context
.table_metadata_manager
.table_name_manager()
.get(table_name_key)
.await?
.with_context(|| TableNotFoundSnafu {
table_name: format_full_table_name(
&self.data.catalog_name,
&self.data.schema_name,
&self.data.object_name,
),
})?
.table_id();
self.context
.table_metadata_manager
.table_name_manager()
.get(table_name_key)
.await?
.with_context(|| TableNotFoundSnafu {
table_name: format_full_table_name(
&self.data.catalog_name,
&self.data.schema_name,
&self.data.object_name,
),
})?
.table_id()
};
let table_info = self
.context
@@ -198,17 +201,22 @@ impl CommentOnProcedure {
}
async fn prepare_flow(&mut self) -> Result<()> {
let flow_name_value = self
.context
.flow_metadata_manager
.flow_name_manager()
.get(&self.data.catalog_name, &self.data.object_name)
.await?
.with_context(|| FlowNotFoundSnafu {
flow_name: &self.data.object_name,
})?;
let flow_id = flow_name_value.flow_id();
let flow_id = if let Some(flow_id) = self.data.flow_id {
flow_id
} else {
self.context
.flow_metadata_manager
.flow_name_manager()
.get(&self.data.catalog_name, &self.data.object_name)
.await?
.with_context(|| FlowNotFoundSnafu {
flow_name: format_full_flow_name(
&self.data.catalog_name,
&self.data.object_name,
),
})?
.flow_id()
};
let flow_info = self
.context
.flow_metadata_manager
@@ -216,7 +224,7 @@ impl CommentOnProcedure {
.get_raw(flow_id)
.await?
.with_context(|| FlowNotFoundSnafu {
flow_name: &self.data.object_name,
flow_name: format_full_flow_name(&self.data.catalog_name, &self.data.object_name),
})?;
self.data.flow_id = Some(flow_id);
@@ -411,17 +419,23 @@ impl Procedure for CommentOnProcedure {
let lock_key = match self.data.object_type {
CommentObjectType::Table | CommentObjectType::Column => {
vec![
let mut lock_key = vec![
CatalogLock::Read(catalog).into(),
SchemaLock::read(catalog, schema).into(),
TableNameLock::new(catalog, schema, &self.data.object_name).into(),
]
];
if let Some(table_id) = self.data.table_id {
lock_key.push(TableLock::Write(table_id).into());
}
lock_key.push(TableNameLock::new(catalog, schema, &self.data.object_name).into());
lock_key
}
CommentObjectType::Flow => {
vec![
CatalogLock::Read(catalog).into(),
FlowNameLock::new(catalog, &self.data.object_name).into(),
]
let mut lock_key = vec![CatalogLock::Read(catalog).into()];
if let Some(flow_id) = self.data.flow_id {
lock_key.push(FlowLock::Write(flow_id).into());
}
lock_key.push(FlowNameLock::new(catalog, &self.data.object_name).into());
lock_key
}
};
@@ -466,6 +480,12 @@ pub struct CommentOnData {
impl CommentOnData {
pub fn new(task: CommentOnTask) -> Self {
let (table_id, flow_id) = match task.object_id {
Some(CommentObjectId::Table(table_id)) => (Some(table_id), None),
Some(CommentObjectId::Flow(flow_id)) => (None, Some(flow_id)),
None => (None, None),
};
Self {
state: CommentOnState::Prepare,
catalog_name: task.catalog_name,
@@ -474,9 +494,9 @@ impl CommentOnData {
object_name: task.object_name,
column_name: task.column_name,
comment: task.comment,
table_id: None,
table_id,
table_info: None,
flow_id: None,
flow_id,
flow_info: None,
is_unchanged: false,
}

View File

@@ -574,9 +574,15 @@ impl DdlManager {
#[tracing::instrument(skip_all)]
pub async fn submit_comment_on_task(
&self,
comment_on_task: CommentOnTask,
mut comment_on_task: CommentOnTask,
) -> Result<(ProcedureId, Option<Output>)> {
let context = self.create_context();
comment_on_task
.enrich_object_id(
context.table_metadata_manager.table_name_manager(),
context.flow_metadata_manager.flow_name_manager(),
)
.await?;
let procedure = CommentOnProcedure::new(comment_on_task, context);
let procedure_with_id = ProcedureWithId::with_random_id(Box::new(procedure));

View File

@@ -41,6 +41,7 @@ use api::v1::{
};
use base64::Engine as _;
use base64::engine::general_purpose;
use common_catalog::{format_full_flow_name, format_full_table_name};
use common_error::ext::BoxedError;
use common_time::{DatabaseTimeToLive, Timestamp};
use prost::Message;
@@ -56,7 +57,11 @@ use crate::error::{
self, ConvertTimeRangesSnafu, ExternalSnafu, InvalidSetDatabaseOptionSnafu,
InvalidUnsetDatabaseOptionSnafu, Result,
};
use crate::flow_name::FlowName;
use crate::instruction::CacheIdent;
use crate::key::FlowId;
use crate::key::flow::flow_name::FlowNameManager;
use crate::key::table_name::{TableNameKey, TableNameManager};
/// Reserved query-context extension key for the frontend peer address that submitted a DDL request.
pub const ORIGIN_FRONTEND_ADDR_EXTENSION_KEY: &str = "__greptime_origin_frontend.addr";
@@ -1351,13 +1356,99 @@ pub enum CommentObjectType {
}
impl CommentOnTask {
pub fn table_ref(&self) -> TableReference<'_> {
TableReference {
catalog: &self.catalog_name,
schema: &self.schema_name,
table: &self.object_name,
pub fn table_id(&self) -> Option<TableId> {
match self.object_id.as_ref() {
Some(CommentObjectId::Table(table_id)) => Some(*table_id),
_ => None,
}
}
pub fn flow_id(&self) -> Option<FlowId> {
match self.object_id.as_ref() {
Some(CommentObjectId::Flow(flow_id)) => Some(*flow_id),
_ => None,
}
}
fn set_table_id(&mut self, table_id: TableId) {
self.object_id = Some(CommentObjectId::Table(table_id));
}
fn set_flow_id(&mut self, flow_id: FlowId) {
self.object_id = Some(CommentObjectId::Flow(flow_id));
}
/// Returns the cache identifiers for the object being commented on.
pub fn cache_idents(&self) -> Vec<CacheIdent> {
match self.object_type {
CommentObjectType::Table | CommentObjectType::Column => {
let mut cache_idents = Vec::with_capacity(2);
if let Some(CommentObjectId::Table(table_id)) = self.object_id.as_ref() {
cache_idents.push(CacheIdent::TableId(*table_id));
}
cache_idents.push(CacheIdent::TableName(TableName {
catalog_name: self.catalog_name.clone(),
schema_name: self.schema_name.clone(),
table_name: self.object_name.clone(),
}));
cache_idents
}
CommentObjectType::Flow => {
let mut cache_idents = Vec::with_capacity(2);
if let Some(CommentObjectId::Flow(flow_id)) = self.object_id.as_ref() {
cache_idents.push(CacheIdent::FlowId(*flow_id));
}
cache_idents.push(CacheIdent::FlowName(FlowName {
catalog_name: self.catalog_name.clone(),
flow_name: self.object_name.clone(),
}));
cache_idents
}
}
}
/// Enriches the `object_id` field of the `CommentOnTask`
/// by looking up the corresponding table or flow ID using the provided managers.
pub async fn enrich_object_id(
&mut self,
table_name_manager: &TableNameManager,
flow_name_manager: &FlowNameManager,
) -> Result<()> {
match self.object_type {
CommentObjectType::Table | CommentObjectType::Column => {
let table_id = table_name_manager
.get(TableNameKey::new(
&self.catalog_name,
&self.schema_name,
&self.object_name,
))
.await?
.with_context(|| error::TableNotFoundSnafu {
table_name: format_full_table_name(
&self.catalog_name,
&self.schema_name,
&self.object_name,
),
})?
.table_id();
self.set_table_id(table_id);
}
CommentObjectType::Flow => {
let flow_id = flow_name_manager
.get(&self.catalog_name, &self.object_name)
.await?
.with_context(|| error::FlowNotFoundSnafu {
flow_name: format_full_flow_name(&self.catalog_name, &self.object_name),
})?
.flow_id();
self.set_flow_id(flow_id);
}
}
Ok(())
}
}
// Proto conversions for CommentObjectType

View File

@@ -82,7 +82,7 @@ pub struct RemoveRegionFollowerRequest {
pub struct GcRegionsRequest {
pub region_ids: Vec<u64>,
pub full_file_listing: bool,
pub timeout: Duration,
pub timeout: Option<Duration>,
}
#[derive(Debug, Clone)]
@@ -91,7 +91,7 @@ pub struct GcTableRequest {
pub schema_name: String,
pub table_name: String,
pub full_file_listing: bool,
pub timeout: Duration,
pub timeout: Option<Duration>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]

View File

@@ -261,4 +261,26 @@ mod tests {
};
assert_eq!(datanode_wal_config, DatanodeWalConfig::Kafka(expected));
}
#[test]
fn test_kafka_wal_config_debug_redacts_password() {
let config = MetasrvWalConfig::Kafka(MetasrvKafkaConfig {
connection: KafkaConnectionConfig {
sasl: Some(KafkaClientSasl {
config: KafkaClientSaslConfig::Plain {
username: "greptime".to_string(),
password: "kafka-secret".to_string(),
},
}),
..Default::default()
},
..Default::default()
});
let debug = format!("{config:#?}");
assert!(debug.contains("greptime"));
assert!(debug.contains("<REDACTED>"));
assert!(!debug.contains("kafka-secret"));
}
}

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::fmt;
use std::io::Cursor;
use std::sync::Arc;
use std::time::Duration;
@@ -57,7 +58,7 @@ pub struct KafkaClientSasl {
pub config: KafkaClientSaslConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "type", rename_all = "SCREAMING-KEBAB-CASE")]
pub enum KafkaClientSaslConfig {
Plain {
@@ -76,6 +77,28 @@ pub enum KafkaClientSaslConfig {
},
}
impl fmt::Debug for KafkaClientSaslConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KafkaClientSaslConfig::Plain { username, .. } => f
.debug_struct("Plain")
.field("username", username)
.field("password", &"<REDACTED>")
.finish(),
KafkaClientSaslConfig::ScramSha256 { username, .. } => f
.debug_struct("ScramSha256")
.field("username", username)
.field("password", &"<REDACTED>")
.finish(),
KafkaClientSaslConfig::ScramSha512 { username, .. } => f
.debug_struct("ScramSha512")
.field("username", username)
.field("password", &"<REDACTED>")
.finish(),
}
}
}
impl KafkaClientSaslConfig {
/// Converts to [`SaslConfig`].
pub fn into_sasl_config(self) -> SaslConfig {

View File

@@ -204,6 +204,8 @@ impl Instance {
let is_readonly_stmt = stmt.is_readonly();
if should_track_statement_process(&stmt) {
let catalog_name = query_ctx.current_catalog().to_string();
let schema_name = query_ctx.current_schema();
let slow_query_timer = if is_readonly_stmt {
self.slow_query_options
.enable
@@ -212,6 +214,7 @@ impl Instance {
.map(|event_recorder| {
SlowQueryTimer::new(
CatalogQueryStatement::Sql(stmt.clone()),
schema_name.clone(),
self.slow_query_options.threshold,
self.slow_query_options.sample_ratio,
self.slow_query_options.record_type,
@@ -223,8 +226,8 @@ impl Instance {
};
let ticket = self.process_manager.register_query(
query_ctx.current_catalog().to_string(),
vec![query_ctx.current_schema()],
catalog_name,
vec![schema_name],
stmt.to_string(),
query_ctx.conn_info().to_string(),
Some(query_ctx.process_id()),
@@ -662,6 +665,8 @@ impl Instance {
let plan_is_readonly = is_readonly_plan(&plan);
let result = if should_track_plan_process(stmt.as_ref(), &plan) {
let catalog_name = query_ctx.current_catalog().to_string();
let schema_name = query_ctx.current_schema();
let slow_query_timer = if plan_is_readonly {
self.slow_query_options
.enable
@@ -670,6 +675,7 @@ impl Instance {
.map(|event_recorder| {
SlowQueryTimer::new(
CatalogQueryStatement::Plan(query.clone()),
schema_name.clone(),
self.slow_query_options.threshold,
self.slow_query_options.sample_ratio,
self.slow_query_options.record_type,
@@ -681,8 +687,8 @@ impl Instance {
};
let ticket = self.process_manager.register_query(
query_ctx.current_catalog().to_string(),
vec![query_ctx.current_schema()],
catalog_name,
vec![schema_name],
query,
query_ctx.conn_info().to_string(),
Some(query_ctx.process_id()),
@@ -908,6 +914,7 @@ impl PrometheusHandler for Instance {
.map(|event_recorder| {
SlowQueryTimer::new(
query_statement,
query_ctx.current_schema(),
self.slow_query_options.threshold,
self.slow_query_options.sample_ratio,
self.slow_query_options.record_type,

View File

@@ -286,7 +286,7 @@ impl Inner {
}),
region_ids: request.region_ids,
full_file_listing: request.full_file_listing,
timeout_secs: timeout.as_secs() as u32,
timeout_secs: gc_timeout_secs(timeout),
};
let resp: GcRegionsResponse = self
@@ -294,7 +294,9 @@ impl Inner {
"gc_regions",
move |mut client| {
let mut req = Request::new(req.clone());
req.set_timeout(timeout);
if let Some(timeout) = timeout {
req.set_timeout(timeout);
}
async move { client.gc_regions(req).await.map(|res| res.into_inner()) }
},
|resp: &GcRegionsResponse| &resp.header,
@@ -323,7 +325,7 @@ impl Inner {
schema_name: request.schema_name,
table_name: request.table_name,
full_file_listing: request.full_file_listing,
timeout_secs: timeout.as_secs() as u32,
timeout_secs: gc_timeout_secs(timeout),
};
let resp: GcTableResponse = self
@@ -331,7 +333,9 @@ impl Inner {
"gc_table",
move |mut client| {
let mut req = Request::new(req.clone());
req.set_timeout(timeout);
if let Some(timeout) = timeout {
req.set_timeout(timeout);
}
async move { client.gc_table(req).await.map(|res| res.into_inner()) }
},
|resp: &GcTableResponse| &resp.header,
@@ -413,6 +417,12 @@ impl Inner {
}
}
fn gc_timeout_secs(timeout: Option<Duration>) -> u32 {
timeout
.map(|timeout| timeout.as_secs().max(1).try_into().unwrap_or(u32::MAX))
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use std::time::{Duration, Instant};
@@ -438,8 +448,18 @@ mod tests {
use tonic::codec::CompressionEncoding;
use tonic::{Request, Response, Status};
use super::gc_timeout_secs;
use crate::client::MetaClientBuilder;
#[test]
fn test_gc_timeout_secs() {
assert_eq!(gc_timeout_secs(None), 0);
assert_eq!(gc_timeout_secs(Some(Duration::from_millis(1))), 1);
assert_eq!(gc_timeout_secs(Some(Duration::from_millis(999))), 1);
assert_eq!(gc_timeout_secs(Some(Duration::from_secs(1))), 1);
assert_eq!(gc_timeout_secs(Some(Duration::from_secs(10))), 10);
}
#[derive(Clone)]
struct MockHeartbeat {
leader_addr: String,

View File

@@ -268,7 +268,7 @@ impl procedure_service_server::ProcedureService for Metasrv {
.handle_gc_regions(MetaGcRegionsRequest {
region_ids,
full_file_listing,
timeout: Duration::from_secs(timeout_secs as u64),
timeout: Self::normalize_gc_timeout(Duration::from_secs(timeout_secs as u64)),
})
.await?;
@@ -295,7 +295,7 @@ impl procedure_service_server::ProcedureService for Metasrv {
schema_name,
table_name,
full_file_listing,
timeout: Duration::from_secs(timeout_secs as u64),
timeout: Self::normalize_gc_timeout(Duration::from_secs(timeout_secs as u64)),
})
.await?;
@@ -356,9 +356,8 @@ impl Metasrv {
&self,
region_ids: Vec<RegionId>,
full_file_listing: bool,
timeout: Duration,
timeout: Option<Duration>,
) -> error::Result<GcResponse> {
let timeout = Self::normalize_gc_timeout(timeout);
let gc_ticker = self.gc_ticker().context(error::UnexpectedSnafu {
violated: "GC ticker not available".to_string(),
})?;

View File

@@ -1297,7 +1297,9 @@ impl CacheManagerBuilder {
fn meta_cache_weight(k: &SstMetaKey, v: &Arc<CachedSstMeta>) -> u32 {
// We ignore the size of `Arc`.
(k.estimated_size() + parquet_meta_size(&v.parquet_metadata) + v.region_metadata_weight) as u32
let size =
k.estimated_size() + parquet_meta_size(&v.parquet_metadata) + v.region_metadata_weight;
u32::try_from(size).unwrap_or(u32::MAX)
}
fn vector_cache_weight(_k: &(ConcreteDataType, Value), v: &VectorRef) -> u32 {
@@ -1884,6 +1886,19 @@ mod tests {
);
}
#[test]
fn test_meta_cache_weight_saturates_on_overflow() {
let region_metadata = Arc::new(wide_region_metadata(1));
let metadata = sst_parquet_meta_with_region_metadata(region_metadata.clone());
let mut cached =
CachedSstMeta::try_new("test.parquet", Arc::unwrap_or_clone(metadata)).unwrap();
cached.region_metadata_weight = u32::MAX as usize + 1;
let cached = Arc::new(cached);
let key = SstMetaKey(region_metadata.region_id, FileId::random());
assert_eq!(u32::MAX, meta_cache_weight(&key, &cached));
}
#[test]
fn test_repeated_vector_cache() {
let cache = CacheManager::builder().vector_cache_size(4096).build();

View File

@@ -14,119 +14,52 @@
//! Cache size of different cache value.
use std::mem;
use parquet::basic::ColumnOrder;
use parquet::file::metadata::{
FileMetaData, KeyValue, ParquetColumnIndex, ParquetMetaData, ParquetOffsetIndex,
RowGroupMetaData,
};
use parquet::file::page_index::column_index::ColumnIndexMetaData as Index;
use parquet::file::page_index::offset_index::PageLocation;
use parquet::schema::types::{ColumnDescriptor, SchemaDescriptor, Type};
use parquet::file::metadata::ParquetMetaData;
/// Returns estimated size of [ParquetMetaData].
pub fn parquet_meta_size(meta: &ParquetMetaData) -> usize {
// struct size
let mut size = mem::size_of::<ParquetMetaData>();
// file_metadata
size += file_meta_heap_size(meta.file_metadata());
// row_groups
size += meta
.row_groups()
.iter()
.map(row_group_meta_heap_size)
.sum::<usize>();
// column_index
size += meta
.column_index()
.map(parquet_column_index_heap_size)
.unwrap_or(0);
// offset_index
size += meta
.offset_index()
.map(parquet_offset_index_heap_size)
.unwrap_or(0);
size
meta.memory_size()
}
/// Returns estimated size of [FileMetaData] allocated from heap.
fn file_meta_heap_size(meta: &FileMetaData) -> usize {
// created_by
let mut size = meta.created_by().map(|s| s.len()).unwrap_or(0);
// key_value_metadata
size += meta
.key_value_metadata()
.map(|kvs| {
kvs.iter()
.map(|kv| {
kv.key.len()
+ kv.value.as_ref().map(|v| v.len()).unwrap_or(0)
+ mem::size_of::<KeyValue>()
})
.sum()
})
.unwrap_or(0);
// schema_descr (It's a ptr so we also add size of SchemaDescriptor).
size += mem::size_of::<SchemaDescriptor>();
size += schema_descr_heap_size(meta.schema_descr());
// column_orders
size += meta
.column_orders()
.map(|orders| orders.len() * mem::size_of::<ColumnOrder>())
.unwrap_or(0);
#[cfg(test)]
mod tests {
use std::sync::Arc;
size
}
/// Returns estimated size of [SchemaDescriptor] allocated from heap.
fn schema_descr_heap_size(descr: &SchemaDescriptor) -> usize {
// schema
let mut size = mem::size_of::<Type>();
// leaves
size += descr
.columns()
.iter()
.map(|descr| mem::size_of::<ColumnDescriptor>() + column_descr_heap_size(descr))
.sum::<usize>();
// leaf_to_base
size += descr.num_columns() * mem::size_of::<usize>();
size
}
/// Returns estimated size of [ColumnDescriptor] allocated from heap.
fn column_descr_heap_size(descr: &ColumnDescriptor) -> usize {
descr.path().parts().iter().map(|s| s.len()).sum()
}
/// Returns estimated size of [ColumnDescriptor] allocated from heap.
fn row_group_meta_heap_size(meta: &RowGroupMetaData) -> usize {
mem::size_of_val(meta.columns())
}
/// Returns estimated size of [ParquetColumnIndex] allocated from heap.
fn parquet_column_index_heap_size(column_index: &ParquetColumnIndex) -> usize {
column_index
.iter()
.map(|row_group| row_group.len() * mem::size_of::<Index>() + mem::size_of_val(row_group))
.sum()
}
/// Returns estimated size of [ParquetOffsetIndex] allocated from heap.
fn parquet_offset_index_heap_size(offset_index: &ParquetOffsetIndex) -> usize {
offset_index
.iter()
.map(|row_group| {
row_group
.iter()
.map(|column| {
column.page_locations.len() * mem::size_of::<PageLocation>()
+ mem::size_of_val(column)
})
.sum::<usize>()
+ mem::size_of_val(row_group)
})
.sum()
use parquet::basic::{Repetition, Type as PhysicalType};
use parquet::file::metadata::{ColumnIndexBuilder, FileMetaData, ParquetMetaDataBuilder};
use parquet::schema::types::{SchemaDescriptor, Type as SchemaType};
use super::*;
#[test]
fn parquet_meta_size_counts_byte_array_column_index_buffers() {
let field = Arc::new(
SchemaType::primitive_type_builder("tag", PhysicalType::BYTE_ARRAY)
.with_repetition(Repetition::OPTIONAL)
.build()
.unwrap(),
);
let schema = Arc::new(
SchemaType::group_type_builder("schema")
.with_fields(vec![field])
.build()
.unwrap(),
);
let schema_descr = Arc::new(SchemaDescriptor::new(schema));
let file_metadata = FileMetaData::new(2, 3, None, None, schema_descr, None);
let mut column_index = ColumnIndexBuilder::new(PhysicalType::BYTE_ARRAY);
for page in 0..3u8 {
column_index.append(false, vec![page; 4096], vec![page + 1; 4096], 0);
}
let metadata = ParquetMetaDataBuilder::new(file_metadata)
.set_column_index(Some(vec![vec![column_index.build().unwrap()]]))
.build();
let min_max_bytes = 3 * 4096 * 2;
assert!(
parquet_meta_size(&metadata) >= min_max_bytes,
"metadata size should include the byte-array page-index min/max buffers"
);
}
}

View File

@@ -30,7 +30,7 @@ use common_telemetry::tracing::Instrument as _;
use common_telemetry::{debug, error, info, warn};
use common_time::Timestamp;
use itertools::Itertools;
use object_store::{Entry, Lister};
use object_store::{Entry, ErrorKind, Lister};
use serde::{Deserialize, Serialize};
use snafu::{ResultExt as _, ensure};
use store_api::storage::{FileId, FileRef, FileRefsManifest, GcReport, IndexVersion, RegionId};
@@ -308,7 +308,14 @@ impl LocalGcWorker {
.iter()
.filter_map(|f| f.index_version().map(|v| (f.file_id(), v)))
.collect_vec();
deleted_files.insert(*region_id, files.into_iter().map(|f| f.file_id()).collect());
let data_files = files
.into_iter()
.filter_map(|f| match f {
RemovedFile::File(file_id, _) => Some(file_id),
RemovedFile::Index(_, _) => None,
})
.collect();
deleted_files.insert(*region_id, data_files);
deleted_indexes.insert(*region_id, index_files);
processed_regions.insert(*region_id);
debug!(
@@ -671,22 +678,72 @@ impl LocalGcWorker {
})?;
let lister_cnt = listers.len();
// Step 2: Concurrently list all files in the region directory
let all_entries = self
// Step 2: Concurrently list all parquet files in the region root directory
let mut all_entries = self
.list_region_files_concurrent(listers)
.await
.inspect_err(|_| {
GC_ERRORS_TOTAL.with_label_values(&["list_failed"]).inc();
})?;
let cnt = all_entries.len();
let root_cnt = all_entries.len();
// Step 2b: Flat-list region_dir/index/ for puffin files.
// This is NOT a recursive listing — we only list the index/
// subdirectory to avoid scanning nested dirs/staging/blob/cache.
let index_entries = self
.list_region_index_files(region_id)
.await
.inspect_err(|_| {
GC_ERRORS_TOTAL.with_label_values(&["list_failed"]).inc();
})?;
let index_cnt = index_entries.len();
all_entries.extend(index_entries);
info!(
"gc: full listing mode cost {} secs using {lister_cnt} lister for {cnt} files in region {}.",
"gc: full listing mode cost {} secs using {lister_cnt} lister for root={root_cnt} index={index_cnt} files in region {}.",
start.elapsed().as_secs_f64(),
region_id
);
Ok(all_entries)
}
/// Flat-list puffin files from `region_dir/index/`.
/// If the index directory does not exist, returns an empty vec without error.
/// Only `.puffin` files (not subdirectories) are included.
async fn list_region_index_files(&self, region_id: RegionId) -> Result<Vec<Entry>> {
let region_dir = self.access_layer.build_region_dir(region_id);
let index_dir = object_store::util::join_dir(&region_dir, "index");
let mut lister = match self
.access_layer
.object_store()
.lister_with(&index_dir)
.await
{
Ok(l) => l,
Err(e) if e.kind() == ErrorKind::NotFound => {
// Index dir may not exist — that's fine, just log and return empty.
// object-store backends (especially filesystem) may error on
// non-existent directories.
debug!(
"Index directory not found for region {}: {}. Treating as empty.",
region_id, e
);
return Ok(vec![]);
}
Err(e) => return Err(e).context(OpenDalSnafu),
};
let mut entries = Vec::new();
while let Some(entry) = lister.next().await {
let entry = entry.context(OpenDalSnafu)?;
if entry.metadata().is_file() && entry.name().ends_with(".puffin") {
entries.push(entry);
}
}
Ok(entries)
}
/// Concurrently list all files in the region directory using the provided listers.
/// Returns a vector of all file entries found across all partitions.
async fn list_region_files_concurrent(
@@ -710,7 +767,8 @@ impl LocalGcWorker {
true
}
}
// entry went wrong, log and skip it
// Entry went wrong. Keep listing so the error can be propagated below
// instead of returning a partial listing as success.
Err(err) => {
warn!("Failed to list entry: {}", err);
true
@@ -747,7 +805,9 @@ impl LocalGcWorker {
// Collect all entries from the channel
let mut all_entries = vec![];
while let Some(stream) = rx.recv().await {
all_entries.extend(stream.into_iter().filter_map(Result::ok));
for entry in stream {
all_entries.push(entry.context(OpenDalSnafu)?);
}
}
Ok(all_entries)

View File

@@ -15,7 +15,7 @@
//! Pruner for parallel file pruning across scanner partitions.
use std::collections::{HashMap, HashSet};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Instant;
@@ -117,6 +117,35 @@ impl PartitionPruner {
Ok(ranges)
}
/// Checks whether the file range at `index` can be skipped because the
/// current predicate definitively prunes it at manifest level (no I/O).
///
/// Returns `true` if the range was skipped. When skipped, this method
/// balances the pruner's per-file reference count and merges the resulting
/// reader metrics into `part_metrics`.
///
/// Uses shared per-file state so repeated row groups can skip cheaply after
/// the first manifest-prune decision.
pub fn try_skip_manifest_pruned_file_range(
&self,
index: RowGroupIndex,
part_metrics: &PartitionMetrics,
) -> bool {
let Some(file_index) = self.file_index(index) else {
return false;
};
let mut reader_metrics = ReaderMetrics::default();
let pruned = self
.pruner
.inner
.try_mark_manifest_pruned(file_index, &mut reader_metrics);
if pruned {
self.pruner.skip_file_range(index, &mut reader_metrics);
part_metrics.merge_reader_metrics(&reader_metrics, None);
}
pruned
}
/// Pre-fetches upcoming files starting from the given position.
fn prefetch_upcoming_files(&self, current_pos: usize, partition_metrics: &PartitionMetrics) {
let start = current_pos + 1;
@@ -139,6 +168,14 @@ impl PartitionPruner {
.copied()
.unwrap_or(PreFilterMode::SkipFields)
}
fn file_index(&self, index: RowGroupIndex) -> Option<usize> {
self.pruner
.inner
.stream_ctx
.is_file_range_index(index)
.then(|| index.index - self.pruner.inner.stream_ctx.input.num_memtables())
}
}
/// A pruner that prunes files for all partitions of a scanner.
@@ -155,6 +192,40 @@ struct PrunerInner {
file_entries: Vec<Mutex<FileBuilderEntry>>,
/// StreamContext containing all context needed for pruning.
stream_ctx: Arc<StreamContext>,
/// Positive manifest-prune cache shared across all scan partitions.
///
/// SAFETY: cached positives are valid because dynamic filters only tighten;
/// negative decisions are not cached. Reset by `add_partition_ranges()` for
/// each fresh batch of partition ranges.
manifest_pruned_files: Vec<AtomicBool>,
}
impl PrunerInner {
/// Checks whether manifest-level pruning proves this file is empty given the
/// current predicate. If true, CAS the shared cache from false→true and
/// record `files_time_range_pruned` in `reader_metrics`.
///
/// Returns `true` if already cached or newly proven pruned.
fn try_mark_manifest_pruned(
&self,
file_index: usize,
reader_metrics: &mut ReaderMetrics,
) -> bool {
if self.manifest_pruned_files[file_index].load(Ordering::Relaxed) {
return true;
}
let file = &self.stream_ctx.input.files[file_index];
if !self.stream_ctx.input.can_manifest_prune_file(file) {
return false;
}
if self.manifest_pruned_files[file_index]
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
reader_metrics.filter_metrics.files_time_range_pruned += 1;
}
true
}
}
/// Per-file state tracking.
@@ -196,6 +267,8 @@ impl Pruner {
})
})
.collect();
let manifest_pruned_files: Vec<AtomicBool> =
(0..num_files).map(|_| AtomicBool::new(false)).collect();
// Create channels and collect senders
let mut worker_senders = Vec::with_capacity(num_workers);
let mut receivers = Vec::with_capacity(num_workers);
@@ -209,6 +282,7 @@ impl Pruner {
num_workers,
file_entries,
stream_ctx,
manifest_pruned_files,
});
// Spawn worker tasks with their receivers
@@ -225,8 +299,14 @@ impl Pruner {
}
}
/// Adds reference counts for all partitions' ranges.
/// Adds reference counts for all partitions' ranges and resets the full
/// manifest-prune cache so that dynamic-filter updates are visible to the
/// fresh scan.
pub fn add_partition_ranges(&self, partition_ranges: &[PartitionRange]) {
for pruned in &self.inner.manifest_pruned_files {
pruned.store(false, Ordering::Relaxed);
}
// Add reference counts for each partition range
let num_memtables = self.inner.stream_ctx.input.num_memtables();
for part_range in partition_ranges {
@@ -277,6 +357,19 @@ impl Pruner {
Ok(ranges)
}
/// Skips a file range that has been pruned before entering the file pruner.
///
/// This keeps the pruner's per-file reference counts balanced with
/// `add_partition_ranges()`. It may also clear a cached builder when this was the
/// last remaining range for the file.
pub fn skip_file_range(&self, index: RowGroupIndex, reader_metrics: &mut ReaderMetrics) {
if !self.inner.stream_ctx.is_file_range_index(index) {
return;
}
let file_index = index.index - self.inner.stream_ctx.input.num_memtables();
self.decrement_and_maybe_clear(file_index, reader_metrics);
}
/// Gets or creates the FileRangeBuilder for a file.
async fn get_file_builder(
&self,
@@ -374,25 +467,33 @@ impl Pruner {
pre_filter_mode: PreFilterMode,
reader_metrics: &mut ReaderMetrics,
) -> Result<Arc<FileRangeBuilder>> {
// Check manifest-level prune first (shared cache, no I/O).
if self
.inner
.try_mark_manifest_pruned(file_index, reader_metrics)
{
let arc_builder = Arc::new(FileRangeBuilder::default());
// Do NOT cache an empty manifest-pruned builder; the cache flag
// already records the decision.
return Ok(arc_builder);
}
let file = &self.inner.stream_ctx.input.files[file_index];
let predicate = self.inner.stream_ctx.input.predicate_for_file(file);
let builder = self
.inner
.stream_ctx
.input
.prune_file(file, pre_filter_mode, reader_metrics)
.prune_file_after_manifest_check(file, pre_filter_mode, predicate, reader_metrics)
.await?;
let arc_builder = Arc::new(builder);
// Caches the builder
// Caches the builder only if the file still has remaining ranges.
// `skip_file_range` may have already consumed all ranges for this file.
{
let mut entry = self.inner.file_entries[file_index].lock().unwrap();
if entry.builder.is_none() {
reader_metrics.metadata_mem_size += arc_builder.memory_size() as isize;
reader_metrics.num_range_builders += 1;
entry.builder = Some(arc_builder.clone());
PRUNER_ACTIVE_BUILDERS.inc();
}
cache_builder_if_needed(&mut entry, &arc_builder, reader_metrics);
}
Ok(arc_builder)
@@ -444,7 +545,6 @@ impl Pruner {
}
worker_cache_miss += 1;
// Do the actual pruning (outside lock)
let file = &inner.stream_ctx.input.files[file_index];
pruned_files.push(file.file_id().file_id());
let explain_verbose = partition_metrics
@@ -455,24 +555,45 @@ impl Pruner {
filter_metrics: new_filter_metrics(explain_verbose),
..Default::default()
};
let result = inner
.stream_ctx
.input
.prune_file(file, pre_filter_mode, &mut metrics)
.await;
// Check manifest-level prune first (shared cache, no I/O).
let result = if inner.try_mark_manifest_pruned(file_index, &mut metrics) {
// Manifest-level pruning proved the file empty — produce a
// default builder without reading any parquet metadata.
Ok(FileRangeBuilder::default())
} else {
let predicate = inner.stream_ctx.input.predicate_for_file(file);
inner
.stream_ctx
.input
.prune_file_after_manifest_check(file, pre_filter_mode, predicate, &mut metrics)
.await
};
// Update state and notify waiters
let mut entry = inner.file_entries[file_index].lock().unwrap();
match result {
Ok(builder) => {
let arc_builder = Arc::new(builder);
entry.builder = Some(arc_builder.clone());
PRUNER_ACTIVE_BUILDERS.inc();
let is_background = response_tx.is_none();
// Only cache the builder if the file still has remaining ranges.
// If remaining_ranges == 0, a concurrent `skip_file_range` (e.g. from a
// dynamic filter tightening via manifest-prune fast-skip) already consumed
// all ranges and may have cleared a previously cached builder.
// Skip caching manifest-pruned empty builders; the cache flag is enough.
let did_cache =
if inner.manifest_pruned_files[file_index].load(Ordering::Relaxed) {
false
} else {
cache_builder_if_needed(&mut entry, &arc_builder, &mut metrics)
};
// Notify all waiters
for waiter in entry.waiters.drain(..) {
let _ = waiter.send(Ok(arc_builder.clone()));
}
// Always respond to foreground caller, even if we did not cache.
if let Some(response_tx) = response_tx {
let _ = response_tx.send(Ok(arc_builder));
}
@@ -485,8 +606,13 @@ impl Pruner {
metrics
);
// Merge metrics to partition if provided
if let Some(part_metrics) = &partition_metrics {
// Merge metrics if this is a foreground request, or if the builder
// was cached. Skip stale per-file metrics
// for background requests that completed after the file was already
// fully skipped.
if (!is_background || did_cache)
&& let Some(part_metrics) = &partition_metrics
{
let per_file_metrics = if part_metrics.explain_verbose() {
let file_id = file.file_id();
let mut map = HashMap::new();
@@ -525,3 +651,460 @@ impl Pruner {
);
}
}
#[cfg(test)]
impl Pruner {
/// Returns the remaining range count for a file (test-only).
fn test_remaining_ranges(&self, file_index: usize) -> usize {
self.inner.file_entries[file_index]
.lock()
.unwrap()
.remaining_ranges
}
/// Returns whether a cached builder exists for a file (test-only).
fn test_has_builder(&self, file_index: usize) -> bool {
self.inner.file_entries[file_index]
.lock()
.unwrap()
.builder
.is_some()
}
/// Returns the manifest-pruned flag for a file (test-only).
fn test_is_manifest_pruned(&self, file_index: usize) -> bool {
self.inner.manifest_pruned_files[file_index].load(Ordering::Relaxed)
}
/// Clears a cached builder for a file, simulating stale cleanup (test-only).
#[allow(dead_code)]
fn test_clear_builder(&self, file_index: usize) {
let mut entry = self.inner.file_entries[file_index].lock().unwrap();
if entry.builder.take().is_some() {
PRUNER_ACTIVE_BUILDERS.dec();
}
}
}
/// Returns true if a freshly pruned builder should be cached for this file.
fn should_cache_builder(entry: &FileBuilderEntry) -> bool {
entry.builder.is_none() && entry.remaining_ranges > 0
}
/// Caches a freshly pruned builder if the file still has remaining ranges, and
/// records the corresponding builder memory/count deltas for verbose metrics.
fn cache_builder_if_needed(
entry: &mut FileBuilderEntry,
builder: &Arc<FileRangeBuilder>,
reader_metrics: &mut ReaderMetrics,
) -> bool {
if should_cache_builder(entry) {
reader_metrics.metadata_mem_size += builder.memory_size() as isize;
reader_metrics.num_range_builders += 1;
entry.builder = Some(builder.clone());
PRUNER_ACTIVE_BUILDERS.inc();
true
} else {
false
}
}
#[cfg(test)]
mod tests {
use common_time::Timestamp;
use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
use datafusion_common::ScalarValue;
use datafusion_expr::{Expr, col, lit};
use store_api::region_engine::PartitionRange;
use store_api::storage::{FileId, RegionId};
use super::*;
use crate::read::flat_projection::FlatProjectionMapper;
use crate::read::range::RowGroupIndex;
use crate::read::scan_region::{PredicateGroup, ScanInput};
use crate::read::scan_util::PartitionMetrics;
use crate::sst::file::{FileHandle, FileMeta};
use crate::sst::parquet::reader::ReaderMetrics;
use crate::test_util::memtable_util::metadata_with_primary_key;
use crate::test_util::new_noop_file_purger;
use crate::test_util::scheduler_util::SchedulerEnv;
async fn make_test_pruner(num_files: usize) -> (SchedulerEnv, Arc<Pruner>) {
let env = SchedulerEnv::new().await;
let metadata = Arc::new(metadata_with_primary_key(vec![0, 1], false));
let mapper = FlatProjectionMapper::new(&metadata, [0, 2, 3]).unwrap();
let files: Vec<FileHandle> = (0..num_files)
.map(|_| {
let meta = FileMeta {
region_id: RegionId::new(123, 456),
file_id: FileId::random(),
time_range: (
Timestamp::new_millisecond(0),
Timestamp::new_millisecond(1000),
),
num_row_groups: 1,
num_rows: 1024,
level: 0,
..Default::default()
};
FileHandle::new(meta, new_noop_file_purger())
})
.collect();
let input = ScanInput::new(env.access_layer.clone(), mapper)
.with_files(files)
.with_append_mode(true);
let stream_ctx = Arc::new(StreamContext::unordered_scan_ctx(input));
let pruner = Arc::new(Pruner::new(stream_ctx, 1));
(env, pruner)
}
/// Builds a minimal `PartitionRange` that references `file_index`.
/// `add_partition_ranges` will look up `stream_ctx.ranges[identifier]`
/// and find `row_group_indices[0] == RowGroupIndex { index: file_index,
/// row_group_index: 0 }` because `unordered_scan_ranges` with
/// `num_row_groups=1` produces one range per file.
fn file_partition_range(file_index: usize) -> PartitionRange {
PartitionRange {
start: Timestamp::new_millisecond(0),
end: Timestamp::new_millisecond(1001),
num_rows: 1024,
identifier: file_index,
}
}
async fn make_test_pruner_with_predicate(
num_files: usize,
row_groups_per_file: u64,
predicate_exprs: &[Expr],
) -> (SchedulerEnv, Arc<Pruner>) {
let env = SchedulerEnv::new().await;
let metadata = Arc::new(metadata_with_primary_key(vec![0, 1], false));
let mapper = FlatProjectionMapper::new(&metadata, [0, 2, 3]).unwrap();
let predicate = PredicateGroup::new(&metadata, predicate_exprs).unwrap();
let files: Vec<FileHandle> = (0..num_files)
.map(|_| {
let meta = FileMeta {
region_id: RegionId::new(123, 456),
file_id: FileId::random(),
time_range: (
Timestamp::new_millisecond(0),
Timestamp::new_millisecond(1000),
),
num_row_groups: row_groups_per_file,
num_rows: row_groups_per_file * 1024,
level: 0,
..Default::default()
};
FileHandle::new(meta, new_noop_file_purger())
})
.collect();
let input = ScanInput::new(env.access_layer.clone(), mapper)
.with_files(files)
.with_predicate(predicate)
.with_append_mode(true);
let stream_ctx = Arc::new(StreamContext::unordered_scan_ctx(input));
let pruner = Arc::new(Pruner::new(stream_ctx, 1));
(env, pruner)
}
fn make_partition_metrics() -> PartitionMetrics {
let metrics_set = ExecutionPlanMetricsSet::new();
PartitionMetrics::new(
RegionId::new(123, 456),
0,
"test",
Instant::now(),
false,
&metrics_set,
)
}
#[test]
fn should_cache_builder_when_ranges_remain() {
let entry = FileBuilderEntry {
builder: None,
remaining_ranges: 3,
waiters: Vec::new(),
};
assert!(should_cache_builder(&entry));
}
#[test]
fn should_not_cache_builder_when_no_ranges_remain() {
let entry = FileBuilderEntry {
builder: None,
remaining_ranges: 0,
waiters: Vec::new(),
};
assert!(!should_cache_builder(&entry));
}
#[test]
fn should_not_cache_builder_when_already_cached() {
let entry = FileBuilderEntry {
builder: Some(Arc::new(FileRangeBuilder::default())),
remaining_ranges: 1,
waiters: Vec::new(),
};
assert!(!should_cache_builder(&entry));
}
#[test]
fn cache_builder_records_metrics() {
let mut entry = FileBuilderEntry {
builder: None,
remaining_ranges: 1,
waiters: Vec::new(),
};
let builder = Arc::new(FileRangeBuilder::default());
let mut reader_metrics = ReaderMetrics::default();
assert!(cache_builder_if_needed(
&mut entry,
&builder,
&mut reader_metrics
));
assert!(entry.builder.is_some());
assert_eq!(
reader_metrics.metadata_mem_size,
builder.memory_size() as isize
);
assert_eq!(reader_metrics.num_range_builders, 1);
if entry.builder.take().is_some() {
PRUNER_ACTIVE_BUILDERS.dec();
}
}
#[tokio::test]
async fn skip_file_range_decrements_and_clears_builder() {
let (_env, pruner) = make_test_pruner(1).await;
// Simulate 3 partition ranges for file 0.
let ranges: Vec<PartitionRange> = (0..3).map(|_| file_partition_range(0)).collect();
pruner.add_partition_ranges(&ranges);
assert_eq!(pruner.test_remaining_ranges(0), 3);
// Manually set a cached builder (simulating a previous cache hit).
{
let mut entry = pruner.inner.file_entries[0].lock().unwrap();
entry.builder = Some(Arc::new(FileRangeBuilder::default()));
PRUNER_ACTIVE_BUILDERS.inc();
}
assert!(pruner.test_has_builder(0));
// Skip all 3 ranges; the third should clear the builder.
let mut reader_metrics = ReaderMetrics::default();
for i in 0..3 {
let index = RowGroupIndex {
index: 0,
row_group_index: i as i64,
};
pruner.skip_file_range(index, &mut reader_metrics);
}
assert_eq!(pruner.test_remaining_ranges(0), 0);
assert!(!pruner.test_has_builder(0));
}
#[tokio::test]
async fn worker_does_not_cache_after_skip_file_range_consumed_all() {
let (_env, pruner) = make_test_pruner(1).await;
// Simulate one range for file 0.
let ranges = vec![file_partition_range(0)];
pruner.add_partition_ranges(&ranges);
assert_eq!(pruner.test_remaining_ranges(0), 1);
// Simulate skip_file_range consuming the last range BEFORE the
// background worker finishes. This mirrors the race: a dynamic filter
// tightens and manifest-prune fast-skip zeros out remaining_ranges.
let mut reader_metrics = ReaderMetrics::default();
let index = RowGroupIndex {
index: 0,
row_group_index: 0,
};
pruner.skip_file_range(index, &mut reader_metrics);
assert_eq!(pruner.test_remaining_ranges(0), 0);
assert!(!pruner.test_has_builder(0));
// Now simulate the worker completing: check the caching guard.
let entry = pruner.inner.file_entries[0].lock().unwrap();
let should_cache = should_cache_builder(&entry);
drop(entry);
assert!(!should_cache);
// Ensure the gauge was not incremented for a stale builder.
// (skip_file_range already decremented it if there was one, but here
// there was none, so the gauge should be at baseline.)
}
#[tokio::test]
async fn worker_caches_when_ranges_remain() {
let (_env, pruner) = make_test_pruner(1).await;
// Simulate 2 ranges for file 0.
let ranges: Vec<PartitionRange> = (0..2).map(|_| file_partition_range(0)).collect();
pruner.add_partition_ranges(&ranges);
assert_eq!(pruner.test_remaining_ranges(0), 2);
// Consume only 1 range.
let mut reader_metrics = ReaderMetrics::default();
let index = RowGroupIndex {
index: 0,
row_group_index: 0,
};
pruner.skip_file_range(index, &mut reader_metrics);
assert_eq!(pruner.test_remaining_ranges(0), 1);
// The worker should still cache because remaining_ranges > 0.
let entry = pruner.inner.file_entries[0].lock().unwrap();
assert!(should_cache_builder(&entry));
}
// ── Corner case: fast-skip across multiple row groups ─────────────
/// 1 file × 3 row groups, predicate `ts > 10000ms` prunes the file at
/// manifest level. Fast-skipping each of the 3 row groups must return true
/// and decrement remaining_ranges to 0.
#[tokio::test]
async fn try_skip_manifest_pruned_file_range_multi_row_groups() {
let predicate_exprs: Vec<Expr> =
vec![col("ts").gt(lit(ScalarValue::TimestampMillisecond(Some(10_000), None)))];
let (_env, pruner) = make_test_pruner_with_predicate(1, 3, &predicate_exprs).await;
let ranges = pruner.inner.stream_ctx.partition_ranges();
assert_eq!(ranges.len(), 3);
pruner.add_partition_ranges(&ranges);
assert_eq!(pruner.test_remaining_ranges(0), 3);
let partition_pruner = Arc::new(PartitionPruner::new(pruner.clone(), &ranges));
let partition_metrics = make_partition_metrics();
// Fast-skip each of the 3 row groups.
for rg in 0..3 {
let index = RowGroupIndex {
index: 0, // file_index == 0, no memtables
row_group_index: rg,
};
let skipped =
partition_pruner.try_skip_manifest_pruned_file_range(index, &partition_metrics);
assert!(skipped, "row group {} should be skipped", rg);
}
// All refs consumed.
assert_eq!(pruner.test_remaining_ranges(0), 0);
// manifest_pruned_files is CAS'd exactly once (first call).
assert!(pruner.test_is_manifest_pruned(0));
}
/// A file whose manifest time range may contain matching rows must not be
/// fast-skipped. This protects query correctness over metrics precision.
#[tokio::test]
async fn try_skip_manifest_pruned_file_range_keeps_overlapping_file() {
let predicate_exprs: Vec<Expr> =
vec![col("ts").gt(lit(ScalarValue::TimestampMillisecond(Some(500), None)))];
let (_env, pruner) = make_test_pruner_with_predicate(1, 2, &predicate_exprs).await;
let ranges = pruner.inner.stream_ctx.partition_ranges();
assert_eq!(ranges.len(), 2);
pruner.add_partition_ranges(&ranges);
assert_eq!(pruner.test_remaining_ranges(0), 2);
let partition_pruner = Arc::new(PartitionPruner::new(pruner.clone(), &ranges));
let partition_metrics = make_partition_metrics();
let range_meta = &pruner.inner.stream_ctx.ranges[ranges[0].identifier];
let index = range_meta.row_group_indices[0];
let skipped =
partition_pruner.try_skip_manifest_pruned_file_range(index, &partition_metrics);
assert!(!skipped);
assert_eq!(pruner.test_remaining_ranges(0), 2);
assert!(!pruner.test_is_manifest_pruned(0));
}
// ── Corner case: add_partition_ranges resets the manifest-pruned flag ──
#[tokio::test]
async fn add_partition_ranges_resets_manifest_pruned_flag() {
let predicate_exprs: Vec<Expr> =
vec![col("ts").gt(lit(ScalarValue::TimestampMillisecond(Some(10_000), None)))];
let (_env, pruner) = make_test_pruner_with_predicate(1, 1, &predicate_exprs).await;
// Mark file 0 as manifest-pruned.
let mut reader_metrics = ReaderMetrics::default();
let marked = pruner
.inner
.try_mark_manifest_pruned(0, &mut reader_metrics);
assert!(marked);
assert!(pruner.test_is_manifest_pruned(0));
assert_eq!(reader_metrics.filter_metrics.files_time_range_pruned, 1);
// Calling add_partition_ranges must reset the flag.
let ranges = vec![file_partition_range(0)];
pruner.add_partition_ranges(&ranges);
assert!(!pruner.test_is_manifest_pruned(0));
// remaining_ranges was also incremented.
assert_eq!(pruner.test_remaining_ranges(0), 1);
}
// ── Corner case: prune_file_directly short-circuits via manifest prune ──
#[tokio::test]
async fn prune_file_directly_manifest_pruned_returns_empty_builder() {
let predicate_exprs: Vec<Expr> =
vec![col("ts").gt(lit(ScalarValue::TimestampMillisecond(Some(10_000), None)))];
let (_env, pruner) = make_test_pruner_with_predicate(1, 1, &predicate_exprs).await;
// Ensure there is no cached builder yet.
assert!(!pruner.test_has_builder(0));
let mut reader_metrics = ReaderMetrics::default();
let builder = pruner
.prune_file_directly(0, PreFilterMode::SkipFields, &mut reader_metrics)
.await
.unwrap();
// Should be the default (empty) builder.
assert_eq!(
builder.memory_size(),
FileRangeBuilder::default().memory_size()
);
// builder must NOT be cached — the manifest-pruned flag is enough.
assert!(!pruner.test_has_builder(0));
// files_time_range_pruned was recorded.
assert_eq!(reader_metrics.filter_metrics.files_time_range_pruned, 1);
}
// ── Corner case: try_mark_manifest_pruned does not double-count ───
#[tokio::test]
async fn try_mark_manifest_pruned_only_counts_first_cas() {
let predicate_exprs: Vec<Expr> =
vec![col("ts").gt(lit(ScalarValue::TimestampMillisecond(Some(10_000), None)))];
let (_env, pruner) = make_test_pruner_with_predicate(1, 1, &predicate_exprs).await;
// First call: CAS succeeds, metric incremented.
let mut reader_metrics = ReaderMetrics::default();
let marked = pruner
.inner
.try_mark_manifest_pruned(0, &mut reader_metrics);
assert!(marked);
assert_eq!(reader_metrics.filter_metrics.files_time_range_pruned, 1);
assert!(pruner.test_is_manifest_pruned(0));
// Second call: already true, no metric delta.
let mut reader_metrics2 = ReaderMetrics::default();
let marked2 = pruner
.inner
.try_mark_manifest_pruned(0, &mut reader_metrics2);
assert!(marked2);
assert_eq!(reader_metrics2.filter_metrics.files_time_range_pruned, 0);
}
}

View File

@@ -28,11 +28,14 @@ use common_telemetry::tracing::Instrument;
use common_telemetry::{debug, error, tracing, warn};
use common_time::range::TimestampRange;
use datafusion::physical_plan::expressions::DynamicFilterPhysicalExpr;
use datafusion_common::Column;
use datafusion_common::pruning::PruningStatistics;
use datafusion_common::{Column, ScalarValue};
use datafusion_expr::Expr;
use datafusion_expr::utils::expr_to_columns;
use datatypes::arrow::array::{ArrayRef, BooleanArray, UInt64Array};
use datatypes::extension::json::is_structured_json_field;
use datatypes::types::json_type::JsonNativeType;
use datatypes::value::timestamp_to_scalar_value;
use futures::StreamExt;
use itertools::Itertools;
use partition::expr::PartitionExpr;
@@ -1053,7 +1056,7 @@ impl ScanInput {
ranges
}
fn predicate_for_file(&self, file: &FileHandle) -> Option<Predicate> {
pub(crate) fn predicate_for_file(&self, file: &FileHandle) -> Option<Predicate> {
if self.should_skip_region_partition(file) {
self.predicate.predicate_without_region().cloned()
} else {
@@ -1071,7 +1074,56 @@ impl ScanInput {
}
}
/// Tries to build file-level pruning statistics using only the [FileHandle]'s manifest-level
/// time range, without reading any parquet metadata.
///
/// Returns `None` if timestamp unit conversion overflows (conservative: keep the file).
fn try_file_level_pruning_stats(&self, file: &FileHandle) -> Option<FileLevelPruningStats> {
let (ts_min, ts_max) = file.time_range();
let time_index = self.mapper.metadata().time_index_column();
let time_index_unit = time_index.column_schema.data_type.as_timestamp()?.unit();
// Convert file timestamps to the time index column's unit. Use `convert_to_ceil` for
// the upper bound to avoid accidentally shrinking the manifest range.
let min_ts = ts_min.convert_to(time_index_unit)?;
let max_ts = ts_max.convert_to_ceil(time_index_unit)?;
Some(FileLevelPruningStats {
min_scalar: timestamp_to_scalar_value(time_index_unit, Some(min_ts.value())),
max_scalar: timestamp_to_scalar_value(time_index_unit, Some(max_ts.value())),
time_index_col_name: time_index.column_schema.name.clone(),
})
}
/// Checks whether a file can be definitively pruned using only its manifest-level
/// time range and the current predicate, without reading any parquet metadata.
///
/// Returns `true` if [PruningStatistics] proves the file cannot contain matching rows.
#[inline]
pub(crate) fn can_manifest_prune_file(&self, file: &FileHandle) -> bool {
let predicate = self.predicate_for_file(file);
self.manifest_prunes_file(file, predicate.as_ref())
}
fn manifest_prunes_file(&self, file: &FileHandle, predicate: Option<&Predicate>) -> bool {
if let Some(pred) = predicate
&& !pred.is_empty()
&& let Some(file_level_stats) = self.try_file_level_pruning_stats(file)
{
let pruning_results = pred.prune_with_stats(
&file_level_stats,
self.mapper.metadata().schema.arrow_schema(),
);
pruning_results.first() == Some(&false)
} else {
false
}
}
/// Prunes a file to scan and returns the builder to build readers.
///
/// This is the public entry point used by direct tests and non-pruner callers.
/// It performs its own manifest-level pruning check internally.
#[tracing::instrument(
skip_all,
fields(
@@ -1086,6 +1138,31 @@ impl ScanInput {
reader_metrics: &mut ReaderMetrics,
) -> Result<FileRangeBuilder> {
let predicate = self.predicate_for_file(file);
// Early file-level pruning using manifest time range before any parquet metadata access.
if self.manifest_prunes_file(file, predicate.as_ref()) {
reader_metrics.filter_metrics.files_time_range_pruned += 1;
return Ok(FileRangeBuilder::default());
}
self.prune_file_after_manifest_check(file, pre_filter_mode, predicate, reader_metrics)
.await
}
/// Second half of `prune_file` — performs the actual parquet metadata /
/// reader setup. Callers that already performed manifest-level pruning
/// (e.g. the `Pruner` via its shared `manifest_pruned_files` cache) should
/// call this directly to avoid a redundant manifest check.
///
/// `predicate` is the result of `self.predicate_for_file(file)` computed
/// externally so the caller can reuse it if needed.
pub(crate) async fn prune_file_after_manifest_check(
&self,
file: &FileHandle,
pre_filter_mode: PreFilterMode,
predicate: Option<Predicate>,
reader_metrics: &mut ReaderMetrics,
) -> Result<FileRangeBuilder> {
let may_build_selective_row_selection = predicate.is_some();
let decode_pk_values = !self.compaction
&& self
@@ -1305,6 +1382,57 @@ impl ScanInput {
}
}
/// Lightweight [PruningStatistics] that only uses the file-level time range from manifest
/// metadata, avoiding any parquet metadata reads. Used for early file-level pruning before
/// accessing row-group-level statistics.
pub(crate) struct FileLevelPruningStats {
/// Scalar value for the file's minimum timestamp in the time index column's unit.
pub(crate) min_scalar: ScalarValue,
/// Scalar value for the file's maximum timestamp in the time index column's unit.
pub(crate) max_scalar: ScalarValue,
/// Name of the time index column.
pub(crate) time_index_col_name: String,
}
impl PruningStatistics for FileLevelPruningStats {
fn min_values(&self, column: &Column) -> Option<ArrayRef> {
if column.name == self.time_index_col_name {
ScalarValue::iter_to_array(std::iter::once(self.min_scalar.clone())).ok()
} else {
None
}
}
fn max_values(&self, column: &Column) -> Option<ArrayRef> {
if column.name == self.time_index_col_name {
ScalarValue::iter_to_array(std::iter::once(self.max_scalar.clone())).ok()
} else {
None
}
}
fn num_containers(&self) -> usize {
1
}
fn null_counts(&self, column: &Column) -> Option<ArrayRef> {
if column.name == self.time_index_col_name {
// The time index column is NOT NULL.
Some(Arc::new(UInt64Array::from(vec![0u64])))
} else {
None
}
}
fn row_counts(&self, _column: &Column) -> Option<ArrayRef> {
None
}
fn contained(&self, _column: &Column, _values: &HashSet<ScalarValue>) -> Option<BooleanArray> {
None
}
}
#[cfg(test)]
impl ScanInput {
/// Returns SST file ids to scan.
@@ -1861,9 +1989,15 @@ impl PredicateGroup {
mod tests {
use std::sync::Arc;
use datafusion::physical_plan::expressions::lit as physical_lit;
use common_time::timestamp::{TimeUnit, Timestamp};
use datafusion::physical_plan::expressions::{
binary as physical_binary, col as physical_col, lit as physical_lit,
};
use datafusion_common::ScalarValue;
use datafusion_expr::{col, lit};
use datafusion_expr::{Operator, col, lit};
use datatypes::arrow::datatypes::{
DataType as ArrowDataType, Field, Schema as ArrowSchema, TimeUnit as ArrowTimeUnit,
};
use datatypes::prelude::ConcreteDataType;
use datatypes::schema::ColumnSchema;
use datatypes::types::json_type::JsonObjectType;
@@ -1877,6 +2011,7 @@ mod tests {
use crate::error::InvalidMetadataSnafu;
use crate::read::range_cache::ScanRequestFingerprintBuilder;
use crate::read::read_columns::ReadColumn;
use crate::sst::file::FileMeta;
use crate::test_util::memtable_util::metadata_with_primary_key;
use crate::test_util::scheduler_util::SchedulerEnv;
@@ -1904,6 +2039,60 @@ mod tests {
lit(ScalarValue::TimestampMillisecond(Some(val), None))
}
fn metadata_with_time_index_unit(unit: TimeUnit) -> RegionMetadataRef {
let mut builder = RegionMetadataBuilder::new(RegionId::new(123, 456));
builder
.push_column_metadata(ColumnMetadata {
column_schema: ColumnSchema::new(
"k0".to_string(),
ConcreteDataType::string_datatype(),
false,
),
semantic_type: SemanticType::Tag,
column_id: 0,
})
.push_column_metadata(ColumnMetadata {
column_schema: ColumnSchema::new(
"k1".to_string(),
ConcreteDataType::uint32_datatype(),
false,
),
semantic_type: SemanticType::Tag,
column_id: 1,
})
.push_column_metadata(ColumnMetadata {
column_schema: ColumnSchema::new(
"ts".to_string(),
ConcreteDataType::timestamp_datatype(unit),
false,
),
semantic_type: SemanticType::Timestamp,
column_id: 2,
})
.push_column_metadata(ColumnMetadata {
column_schema: ColumnSchema::new(
"v0".to_string(),
ConcreteDataType::int64_datatype(),
true,
),
semantic_type: SemanticType::Field,
column_id: 3,
})
.primary_key(vec![0, 1]);
Arc::new(builder.build().unwrap())
}
fn file_handle_with_time_range(start: Timestamp, end: Timestamp) -> FileHandle {
FileHandle::new(
FileMeta {
time_range: (start, end),
..Default::default()
},
Arc::new(crate::sst::file_purger::NoopFilePurger),
)
}
#[test]
fn test_fill_json_nested_paths_from_hint() -> Result<()> {
fn json_projection_test_metadata() -> Result<RegionMetadataRef> {
@@ -2129,6 +2318,177 @@ mod tests {
assert_eq!(1, predicate_without_region.dyn_filters().len());
}
#[test]
fn test_file_level_pruning_stats_prunes_old_file() {
let ts_col_name = "ts";
let predicate = Predicate::new(vec![col(ts_col_name).gt(ts_lit(1000))]);
let arrow_schema = Arc::new(ArrowSchema::new(vec![Field::new(
ts_col_name,
ArrowDataType::Timestamp(ArrowTimeUnit::Millisecond, None),
false,
)]));
// File with time range [0ms, 500ms] is completely before `ts > 1000ms`.
let stats = FileLevelPruningStats {
min_scalar: ScalarValue::TimestampMillisecond(Some(0), None),
max_scalar: ScalarValue::TimestampMillisecond(Some(500), None),
time_index_col_name: ts_col_name.to_string(),
};
assert_eq!(
vec![false],
predicate.prune_with_stats(&stats, &arrow_schema)
);
// File with time range [0ms, 2000ms] overlaps `ts > 1000ms`, so keep it.
let stats = FileLevelPruningStats {
min_scalar: ScalarValue::TimestampMillisecond(Some(0), None),
max_scalar: ScalarValue::TimestampMillisecond(Some(2000), None),
time_index_col_name: ts_col_name.to_string(),
};
assert_eq!(
vec![true],
predicate.prune_with_stats(&stats, &arrow_schema)
);
}
#[test]
fn test_file_level_pruning_stats_no_predicate_keeps_all() {
let predicate = Predicate::new(vec![]);
assert!(predicate.is_empty());
let stats = FileLevelPruningStats {
min_scalar: ScalarValue::TimestampMillisecond(Some(0), None),
max_scalar: ScalarValue::TimestampMillisecond(Some(500), None),
time_index_col_name: "ts".to_string(),
};
let arrow_schema = Arc::new(ArrowSchema::new(Vec::<Field>::new()));
assert_eq!(
vec![true],
predicate.prune_with_stats(&stats, &arrow_schema)
);
}
#[tokio::test]
async fn test_file_level_pruning_stats_ceil_max_unit_conversion() {
let metadata = metadata_with_time_index_unit(TimeUnit::Millisecond);
let input = new_scan_input(metadata, vec![]).await;
let file = file_handle_with_time_range(
Timestamp::new(1_000_001, TimeUnit::Nanosecond),
Timestamp::new(1_000_001, TimeUnit::Nanosecond),
);
let stats = input.try_file_level_pruning_stats(&file).unwrap();
assert_eq!(
ScalarValue::TimestampMillisecond(Some(1), None),
stats.min_scalar
);
assert_eq!(
ScalarValue::TimestampMillisecond(Some(2), None),
stats.max_scalar
);
// The actual max timestamp is slightly greater than 1ms. It must be kept for `ts > 1ms`.
let predicate = Predicate::new(vec![col("ts").gt(ts_lit(1))]);
assert_eq!(
vec![true],
predicate.prune_with_stats(&stats, input.mapper.metadata().schema.arrow_schema())
);
}
#[tokio::test]
async fn test_file_level_pruning_stats_overflow_keeps_file() {
let metadata = metadata_with_time_index_unit(TimeUnit::Nanosecond);
let input = new_scan_input(metadata, vec![]).await;
let file = file_handle_with_time_range(
Timestamp::new(0, TimeUnit::Second),
Timestamp::new(i64::MAX, TimeUnit::Second),
);
assert!(input.try_file_level_pruning_stats(&file).is_none());
}
#[test]
fn test_file_level_pruning_stats_keeps_inclusive_boundary() {
let ts_col_name = "ts";
let predicate = Predicate::new(vec![col(ts_col_name).gt_eq(ts_lit(1000))]);
let arrow_schema = Arc::new(ArrowSchema::new(vec![Field::new(
ts_col_name,
ArrowDataType::Timestamp(ArrowTimeUnit::Millisecond, None),
false,
)]));
let stats = FileLevelPruningStats {
min_scalar: ScalarValue::TimestampMillisecond(Some(0), None),
max_scalar: ScalarValue::TimestampMillisecond(Some(1000), None),
time_index_col_name: ts_col_name.to_string(),
};
assert_eq!(
vec![true],
predicate.prune_with_stats(&stats, &arrow_schema)
);
}
#[tokio::test]
async fn test_file_level_pruning_with_dyn_filter_only_predicate() {
let metadata = Arc::new(metadata_with_primary_key(vec![0, 1], false));
let mapper = FlatProjectionMapper::new(&metadata, [0, 2, 3]).unwrap();
let predicate_group = PredicateGroup::new(metadata.as_ref(), &[]).unwrap();
predicate_group.add_dyn_filters(vec![Arc::new(DynamicFilterPhysicalExpr::new(
vec![],
physical_lit(false),
))]);
let input = ScanInput::new(SchedulerEnv::new().await.access_layer.clone(), mapper)
.with_predicate(predicate_group);
let file = file_handle_with_time_range(
Timestamp::new_millisecond(0),
Timestamp::new_millisecond(1000),
);
let mut reader_metrics = ReaderMetrics::default();
let builder = input
.prune_file(&file, PreFilterMode::SkipFields, &mut reader_metrics)
.await
.unwrap();
assert_eq!(1, reader_metrics.filter_metrics.files_time_range_pruned);
let mut ranges = SmallVec::new();
builder.build_ranges(-1, &mut ranges);
assert!(ranges.is_empty());
}
#[tokio::test]
async fn test_manifest_pruning_observes_dynamic_filter_update() {
let metadata = Arc::new(metadata_with_primary_key(vec![0, 1], false));
let mapper = FlatProjectionMapper::new(&metadata, [0, 2, 3]).unwrap();
let predicate_group = PredicateGroup::new(metadata.as_ref(), &[]).unwrap();
let arrow_schema = metadata.schema.arrow_schema();
let ts_expr = physical_col("ts", arrow_schema.as_ref()).unwrap();
let dyn_filter = Arc::new(DynamicFilterPhysicalExpr::new(
vec![ts_expr.clone()],
physical_lit(true),
));
predicate_group.add_dyn_filters(vec![dyn_filter.clone()]);
let input = ScanInput::new(SchedulerEnv::new().await.access_layer.clone(), mapper)
.with_predicate(predicate_group);
let file = file_handle_with_time_range(
Timestamp::new_millisecond(0),
Timestamp::new_millisecond(1000),
);
assert!(!input.can_manifest_prune_file(&file));
let updated = physical_binary(
ts_expr,
Operator::Gt,
physical_lit(ScalarValue::TimestampMillisecond(Some(1000), None)),
arrow_schema.as_ref(),
)
.unwrap();
dyn_filter.update(updated).unwrap();
assert!(input.can_manifest_prune_file(&file));
}
#[tokio::test]
async fn test_range_pre_filter_mode() {
let metadata = Arc::new(metadata_with_primary_key(vec![0, 1], false));

View File

@@ -195,6 +195,8 @@ pub(crate) struct ScanMetricsSet {
pruner_cache_miss: usize,
/// Duration spent waiting for pruner to build file ranges.
pruner_prune_cost: Duration,
/// Number of files filtered by manifest time-range pruning.
files_time_range_pruned: usize,
/// Number of record batches read from SST.
num_sst_record_batches: usize,
/// Number of batches decoded from SST.
@@ -326,6 +328,7 @@ impl fmt::Debug for ScanMetricsSet {
pruner_cache_hit,
pruner_cache_miss,
pruner_prune_cost,
files_time_range_pruned,
num_sst_record_batches,
num_sst_batches,
num_sst_rows,
@@ -390,6 +393,9 @@ impl fmt::Debug for ScanMetricsSet {
}
// Write non-zero filter counters
if *files_time_range_pruned > 0 {
write!(f, ", \"files_time_range_pruned\":{files_time_range_pruned}")?;
}
if *rg_fulltext_filtered > 0 {
write!(f, ", \"rg_fulltext_filtered\":{rg_fulltext_filtered}")?;
}
@@ -689,6 +695,7 @@ impl ScanMetricsSet {
pruner_cache_hit,
pruner_cache_miss,
pruner_prune_cost,
files_time_range_pruned,
inverted_index_apply_metrics,
bloom_filter_apply_metrics,
fulltext_index_apply_metrics,
@@ -706,6 +713,8 @@ impl ScanMetricsSet {
self.build_parts_cost += *build_cost;
self.sst_scan_cost += *scan_cost;
self.files_time_range_pruned += *files_time_range_pruned;
self.rg_total += *rg_total;
self.rg_fulltext_filtered += *rg_fulltext_filtered;
self.rg_inverted_filtered += *rg_inverted_filtered;

View File

@@ -661,6 +661,13 @@ pub(crate) async fn build_flat_sources(
);
ordered_sources[position] = Some(Box::pin(stream) as _);
} else if stream_ctx.is_file_range_index(*index) {
// Common manifest-level fast-skip shared by SeqScan and UnorderedScan.
// Compaction should keep reading its selected input ranges completely.
if !compaction
&& partition_pruner.try_skip_manifest_pruned_file_range(*index, part_metrics)
{
continue;
}
if let Some(semaphore_ref) = semaphore.as_ref() {
// run in parallel, controlled by semaphore
let stream_ctx = stream_ctx.clone();

View File

@@ -133,6 +133,12 @@ impl UnorderedScan {
yield record_batch?;
}
} else if stream_ctx.is_file_range_index(*index) {
// Common manifest-level fast-skip shared by UnorderedScan and SeqScan.
if partition_pruner
.try_skip_manifest_pruned_file_range(*index, &part_metrics)
{
continue;
}
let stream = scan_flat_file_ranges(
stream_ctx.clone(),
part_metrics.clone(),

View File

@@ -1428,6 +1428,8 @@ pub(crate) struct ReaderFilterMetrics {
pub(crate) pruner_cache_miss: usize,
/// Duration spent waiting for pruner to build file ranges.
pub(crate) pruner_prune_cost: Duration,
/// Number of files filtered by manifest time-range pruning.
pub(crate) files_time_range_pruned: usize,
}
impl ReaderFilterMetrics {
@@ -1460,6 +1462,7 @@ impl ReaderFilterMetrics {
self.pruner_cache_hit += other.pruner_cache_hit;
self.pruner_cache_miss += other.pruner_cache_miss;
self.pruner_prune_cost += other.pruner_prune_cost;
self.files_time_range_pruned += other.files_time_range_pruned;
// Merge optional applier metrics
if let Some(other_metrics) = &other.inverted_index_apply_metrics {

View File

@@ -14,6 +14,7 @@
use api::v1::CommentOnExpr;
use common_error::ext::BoxedError;
use common_meta::cache_invalidator::Context;
use common_meta::procedure_executor::ExecutorContext;
use common_meta::rpc::ddl::{CommentObjectType, CommentOnTask, DdlTask, SubmitDdlTaskRequest};
use common_query::Output;
@@ -23,7 +24,9 @@ use snafu::ResultExt;
use sql::ast::ObjectNamePartExt;
use sql::statements::comment::{Comment, CommentObject};
use crate::error::{ExecuteDdlSnafu, ExternalSnafu, InvalidSqlSnafu, Result};
use crate::error::{
self, ExecuteDdlSnafu, ExternalSnafu, InvalidSqlSnafu, Result, TableMetadataManagerSnafu,
};
use crate::statement::StatementExecutor;
use crate::utils::to_meta_query_context;
@@ -39,7 +42,15 @@ impl StatementExecutor {
///
/// A `Result` containing the `Output` of the operation, or an error if the operation fails.
pub async fn comment(&self, stmt: Comment, query_ctx: QueryContextRef) -> Result<Output> {
let comment_on_task = self.create_comment_on_task_from_stmt(stmt, &query_ctx)?;
let mut comment_on_task = self.create_comment_on_task_from_stmt(stmt, &query_ctx)?;
comment_on_task
.enrich_object_id(
self.table_metadata_manager.table_name_manager(),
self.flow_metadata_manager.flow_name_manager(),
)
.await
.context(TableMetadataManagerSnafu)?;
let cache_idents = comment_on_task.cache_idents();
let request = SubmitDdlTaskRequest::new(
to_meta_query_context(query_ctx),
@@ -49,8 +60,15 @@ impl StatementExecutor {
self.procedure_executor
.submit_ddl_task(&ExecutorContext::default(), request)
.await
.context(ExecuteDdlSnafu)
.map(|_| Output::new_with_affected_rows(0))
.context(ExecuteDdlSnafu)?;
// Invalidates local cache ASAP.
self.cache_invalidator
.invalidate(&Context::default(), &cache_idents)
.await
.context(error::InvalidateTableCacheSnafu)?;
Ok(Output::new_with_affected_rows(0))
}
pub async fn comment_by_expr(
@@ -58,7 +76,15 @@ impl StatementExecutor {
expr: CommentOnExpr,
query_ctx: QueryContextRef,
) -> Result<Output> {
let comment_on_task = self.create_comment_on_task_from_expr(expr)?;
let mut comment_on_task = self.create_comment_on_task_from_expr(expr)?;
comment_on_task
.enrich_object_id(
self.table_metadata_manager.table_name_manager(),
self.flow_metadata_manager.flow_name_manager(),
)
.await
.context(TableMetadataManagerSnafu)?;
let cache_idents = comment_on_task.cache_idents();
let request = SubmitDdlTaskRequest::new(
to_meta_query_context(query_ctx),
@@ -68,8 +94,15 @@ impl StatementExecutor {
self.procedure_executor
.submit_ddl_task(&ExecutorContext::default(), request)
.await
.context(ExecuteDdlSnafu)
.map(|_| Output::new_with_affected_rows(0))
.context(ExecuteDdlSnafu)?;
// Invalidates local cache ASAP.
self.cache_invalidator
.invalidate(&Context::default(), &cache_idents)
.await
.context(error::InvalidateTableCacheSnafu)?;
Ok(Output::new_with_affected_rows(0))
}
fn create_comment_on_task_from_expr(&self, expr: CommentOnExpr) -> Result<CommentOnTask> {

View File

@@ -26,6 +26,16 @@ use datafusion_expr::expr::{Exists, InSubquery};
use datafusion_expr::utils::expr_to_columns;
use datafusion_expr::{Expr, LogicalPlan, LogicalPlanBuilder, Subquery, col as col_fn};
use datafusion_optimizer::analyzer::AnalyzerRule;
use datafusion_optimizer::decorrelate_lateral_join::DecorrelateLateralJoin;
use datafusion_optimizer::decorrelate_predicate_subquery::DecorrelatePredicateSubquery;
use datafusion_optimizer::eliminate_filter::EliminateFilter;
use datafusion_optimizer::extract_equijoin_predicate::ExtractEquijoinPredicate;
use datafusion_optimizer::filter_null_join_keys::FilterNullJoinKeys;
use datafusion_optimizer::optimizer::Optimizer;
use datafusion_optimizer::propagate_empty_relation::PropagateEmptyRelation;
use datafusion_optimizer::push_down_filter::PushDownFilter;
use datafusion_optimizer::rewrite_set_comparison::RewriteSetComparison;
use datafusion_optimizer::scalar_subquery_to_join::ScalarSubqueryToJoin;
use promql::extension_plan::SeriesDivide;
use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan};
use table::metadata::TableType;
@@ -111,6 +121,8 @@ impl AnalyzerRule for DistPlannerAnalyzer {
// Aligned with the behavior in `datafusion_optimizer::OptimizerContext::new()`.
config.optimizer.filter_null_join_keys = true;
let config = Arc::new(config);
let opt = config.extensions.get::<DistPlannerOptions>();
let allow_fallback = opt.map(|o| o.allow_query_fallback).unwrap_or(false);
// When the query is running under a scheduled Flow context, carry the
// logical "now" so that `SimplifyExpressions` does not constant-fold
@@ -129,9 +141,31 @@ impl AnalyzerRule for DistPlannerAnalyzer {
let plan = plan
.rewrite_with_subqueries(&mut PlanTreeExpressionSimplifier::new(optimizer_context))?
.data;
let fallback_plan = plan.clone();
let opt = config.extensions.get::<DistPlannerOptions>();
let allow_fallback = opt.map(|o| o.allow_query_fallback).unwrap_or(false);
// Run a filter-focused optimizer subset before MergeScan wraps remote
// inputs. MergeScan intentionally hides its remote_input from later
// optimizer passes; this pass only normalizes/decorrelates enough for
// DataFusion's PushDownFilter to put side-local predicates into scans.
// Keep this narrow: rules like PushDownLimit, OptimizeProjections, and
// DISTINCT rewrites can change global distributed-planning boundaries.
let optimizer_context = PatchOptimizerContext {
inner: datafusion_optimizer::OptimizerContext::new(),
config: config.clone(),
scheduled_time,
};
let plan = match pre_merge_scan_optimizer().optimize(plan, &optimizer_context, |_, _| {}) {
Ok(plan) => plan,
Err(err) => {
if allow_fallback {
common_telemetry::warn!(err; "Failed to pre-optimize plan, using fallback plan rewriter for plan: {fallback_plan}");
PUSH_DOWN_FALLBACK_ERRORS_TOTAL.inc();
return self.use_fallback(fallback_plan);
} else {
return Err(err);
}
}
};
let result = match self.try_push_down(plan.clone()) {
Ok(plan) => plan,
@@ -140,7 +174,7 @@ impl AnalyzerRule for DistPlannerAnalyzer {
common_telemetry::warn!(err; "Failed to push down plan, using fallback plan rewriter for plan: {plan}");
// if push down failed, use fallback plan rewriter
PUSH_DOWN_FALLBACK_ERRORS_TOTAL.inc();
self.use_fallback(plan)?
self.use_fallback(fallback_plan)?
} else {
return Err(err);
}
@@ -151,6 +185,43 @@ impl AnalyzerRule for DistPlannerAnalyzer {
}
}
/// Builds the small optimizer pre-pass that runs before `MergeScan` wrapping.
///
/// This is intentionally not DataFusion's full optimizer. After
/// `PlanRewriter` wraps remote table scans in `MergeScan`,
/// `MergeScanLogicalPlan::inputs()` hides `remote_input`, so ordinary optimizer
/// rules can no longer see into the remote side. The main rule we need here is
/// `PushDownFilter`: it moves side-local join/filter predicates into
/// `TableScan.filters`, where region pruning and scan-level pruning can use
/// them.
///
/// The rules before `PushDownFilter` are only the minimum cleanup/rewrite steps
/// needed to make that filter pushdown safe around subqueries and set
/// comparisons. For example, `RewriteSetComparison` handles ANY/ALL before they
/// can become scan filters, and the decorrelation/subquery rules expose
/// supported predicates as joins/filters instead of leaving raw subquery
/// expressions under a scan.
///
/// Keep this list narrow. Do not add broad plan-shaping rules such as
/// `PushDownLimit`, projection optimization, DISTINCT rewrites, or join-type
/// rewrites here: those can change the local/remote distributed boundary or
/// degrade unrelated planning diagnostics. Such rules belong either before this
/// analyzer or after distributed planning, not in this pre-MergeScan,
/// filter-focused pass.
fn pre_merge_scan_optimizer() -> Optimizer {
Optimizer::with_rules(vec![
Arc::new(RewriteSetComparison::new()),
Arc::new(DecorrelatePredicateSubquery::new()),
Arc::new(ScalarSubqueryToJoin::new()),
Arc::new(DecorrelateLateralJoin::new()),
Arc::new(ExtractEquijoinPredicate::new()),
Arc::new(EliminateFilter::new()),
Arc::new(PropagateEmptyRelation::new()),
Arc::new(FilterNullJoinKeys::default()),
Arc::new(PushDownFilter::new()),
])
}
impl DistPlannerAnalyzer {
/// Try push down as many nodes as possible
fn try_push_down(&self, plan: LogicalPlan) -> DfResult<LogicalPlan> {

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::BTreeSet;
use std::collections::{BTreeSet, HashSet};
use std::pin::Pin;
use std::sync::Arc;
@@ -29,7 +29,8 @@ use datafusion::execution::SessionState;
use datafusion::functions_aggregate::expr_fn::avg;
use datafusion::functions_aggregate::min_max::{max, min};
use datafusion::prelude::SessionContext;
use datafusion_common::{JoinType, ScalarValue};
use datafusion_common::tree_node::TreeNodeRecursion;
use datafusion_common::{ExprSchema, JoinType, ScalarValue};
use datafusion_expr::expr::{Exists, ScalarFunction};
use datafusion_expr::{
AggregateUDF, Expr, ExprSchemable as _, LogicalPlanBuilder, Operator, Subquery, binary_expr,
@@ -99,14 +100,92 @@ fn collect_merge_scan_remote_dyn_filter_producer_id_list(
.unwrap();
}
fn assert_remote_table_scan_filters_are_safe(plan: &LogicalPlan) {
let mut checked_filters = 0;
assert_remote_table_scan_filters_are_safe_inner(plan, false, &mut checked_filters);
assert!(
checked_filters > 0,
"expected at least one remote TableScan filter in plan:\n{plan}"
);
}
fn assert_remote_table_scan_filters_are_safe_inner(
plan: &LogicalPlan,
in_merge_scan_remote_input: bool,
checked_filters: &mut usize,
) {
if let LogicalPlan::Extension(extension) = plan
&& let Some(merge_scan) = extension
.node
.as_any()
.downcast_ref::<MergeScanLogicalPlan>()
{
assert_remote_table_scan_filters_are_safe_inner(merge_scan.input(), true, checked_filters);
}
if in_merge_scan_remote_input && let LogicalPlan::TableScan(table_scan) = plan {
for filter in &table_scan.filters {
assert_table_scan_filter_is_remote_safe(table_scan, filter);
*checked_filters += 1;
}
}
for child in plan.inputs() {
assert_remote_table_scan_filters_are_safe_inner(
child,
in_merge_scan_remote_input,
checked_filters,
);
}
}
fn assert_table_scan_filter_is_remote_safe(
table_scan: &datafusion_expr::logical_plan::TableScan,
filter: &Expr,
) {
filter
.apply(|expr| match expr {
Expr::Exists(_)
| Expr::InSubquery(_)
| Expr::ScalarSubquery(_)
| Expr::SetComparison(_)
| Expr::OuterReferenceColumn(_, _) => {
panic!("remote TableScan filter contains non-scan-local expression: {filter}")
}
_ => Ok(TreeNodeRecursion::Continue),
})
.unwrap();
let mut columns = HashSet::new();
expr_to_columns(filter, &mut columns).unwrap();
for column in columns {
assert!(
table_scan
.projected_schema
.field_from_column(&column)
.is_ok(),
"remote TableScan filter references non-scan column {column}: {filter}\nscan schema: {:?}",
table_scan.projected_schema
);
}
}
pub(crate) struct TestTable;
impl TestTable {
pub fn table_with_name(table_id: TableId, name: String) -> TableRef {
Self::table_with_filter_pushdown(table_id, name, FilterPushDownType::Unsupported)
}
pub fn table_with_filter_pushdown(
table_id: TableId,
name: String,
filter_pushdown: FilterPushDownType,
) -> TableRef {
let data_source = Arc::new(TestDataSource::new(Self::schema()));
let table = Table::new(
Self::table_info(table_id, name, "test_engine".to_string()),
FilterPushDownType::Unsupported,
filter_pushdown,
data_source,
);
Arc::new(table)
@@ -1460,6 +1539,48 @@ fn sibling_merge_scans_have_unique_remote_dyn_filter_producer_ids() {
);
}
#[test]
fn pre_merge_scan_optimizer_eliminates_projected_false_filter() {
init_default_ut_logging();
let left_table =
TestTable::table_with_filter_pushdown(0, "i1".to_string(), FilterPushDownType::Inexact);
let right_table =
TestTable::table_with_filter_pushdown(1, "i2".to_string(), FilterPushDownType::Inexact);
let left_source = Arc::new(DefaultTableSource::new(Arc::new(
DfTableProviderAdapter::new(left_table),
)));
let right_source = Arc::new(DefaultTableSource::new(Arc::new(
DfTableProviderAdapter::new(right_table),
)));
let left = LogicalPlanBuilder::scan_with_filters("i1", left_source, None, vec![])
.unwrap()
.build()
.unwrap();
let right = LogicalPlanBuilder::scan_with_filters("i2", right_source, None, vec![])
.unwrap()
.build()
.unwrap();
let plan = LogicalPlanBuilder::from(left)
.cross_join(right)
.unwrap()
.project(vec![lit(false).alias("cond")])
.unwrap()
.filter(col("cond"))
.unwrap()
.sort(vec![col("cond").sort(true, true)])
.unwrap()
.build()
.unwrap();
let config = ConfigOptions::default();
let result = DistPlannerAnalyzer {}.analyze(plan, &config).unwrap();
assert_eq!("EmptyRelation: rows=0", result.to_string());
}
#[test]
fn test_simplify_now_expression() {
init_default_ut_logging();
@@ -1543,6 +1664,9 @@ fn expand_proj_limit_part_col_aggr_sort() {
let config = ConfigOptions::default();
let result = DistPlannerAnalyzer {}.analyze(plan, &config).unwrap();
// Pre-MergeScan optimizer intentionally excludes PushDownLimit, so the
// remote plan shows an explicit Limit node instead of `fetch=10` on
// TableScan.
let expected = [
"Sort: t.pk2 ASC NULLS LAST",
" Aggregate: groupBy=[[t.pk1, t.pk2]], aggr=[[min(t.number)]]",
@@ -1585,6 +1709,9 @@ fn expand_proj_limit_sort_part_col_aggr() {
let config = ConfigOptions::default();
let result = DistPlannerAnalyzer {}.analyze(plan, &config).unwrap();
// Pre-MergeScan optimizer intentionally excludes PushDownLimit, so the
// remote plan shows an explicit Limit node instead of `fetch=10` on
// TableScan.
let expected = [
"Aggregate: groupBy=[[t.pk1, t.pk2]], aggr=[[min(t.number)]]",
" Sort: t.pk2 ASC NULLS LAST",
@@ -1845,7 +1972,7 @@ fn transform_unalighed_join_with_alias() {
let result = DistPlannerAnalyzer {}.analyze(plan, &config).unwrap();
let expected = [
"Limit: skip=0, fetch=1",
" LeftSemi Join: Filter: t.number = right.number",
" LeftSemi Join: t.number = right.number",
" Projection: t.number",
" MergeScan [is_placeholder=false, remote_input=[",
"TableScan: t",
@@ -2321,3 +2448,195 @@ fn scheduled_none_falls_back_to_wall_clock() {
"Remote should contain TimestampNanosecond:\n{result_str}"
);
}
/// Test that static side-local predicates on a JOIN input reach the remote
/// region TableScan before MergeScan wrapping (issue #8338).
///
/// Plan shape: Filter(t1.pk1 = 'v') -> Join(t1.number = t2.number) -> TableScan(t1), TableScan(t2)
///
/// After PushDownFilter runs, the side-local filter should be pushed into the
/// left child branch (inside the MergeScan remote_input), making it visible for
/// time-index / bloom / skipping pruning.
#[test]
fn test_join_side_local_filter_pushdown_into_merge_scan() {
init_default_ut_logging();
let left_table =
TestTable::table_with_filter_pushdown(0, "t1".to_string(), FilterPushDownType::Inexact);
let right_table =
TestTable::table_with_filter_pushdown(1, "t2".to_string(), FilterPushDownType::Inexact);
let left_source = Arc::new(DefaultTableSource::new(Arc::new(
DfTableProviderAdapter::new(left_table),
)));
let right_source = Arc::new(DefaultTableSource::new(Arc::new(
DfTableProviderAdapter::new(right_table),
)));
let right_plan = LogicalPlanBuilder::scan_with_filters("t2", right_source, None, vec![])
.unwrap()
.build()
.unwrap();
// Plan: Filter -> Join -> TableScan(left), TableScan(right)
let plan = LogicalPlanBuilder::scan_with_filters("t1", left_source, None, vec![])
.unwrap()
.join_on(
right_plan,
JoinType::Inner,
vec![col("t1.number").eq(col("t2.number"))],
)
.unwrap()
.filter(col("t1.pk1").eq(lit("v"))) // side-local filter on left partition column
.unwrap()
.build()
.unwrap();
let config = ConfigOptions::default();
let result = DistPlannerAnalyzer {}.analyze(plan, &config).unwrap();
assert_remote_table_scan_filters_are_safe(&result);
let plan_str = result.to_string();
// After PushDownFilter runs, the predicate `t1.pk1 = Utf8("v")` should appear
// inside the left MergeScan's remote_input. The pre-MergeScan optimizer may
// combine it with join-derived IS NOT NULL pushdowns, so it may not appear as
// a standalone Filter: line. It must still be in TableScan partial_filters
// and below the Inner Join.
assert!(
plan_str.contains("t1.pk1 = Utf8(\"v\")"),
"Expected predicate t1.pk1 = Utf8(\"v\") in plan, got:\n{plan_str}"
);
assert!(
plan_str.contains(
"TableScan: t1, partial_filters=[t1.pk1 = Utf8(\"v\"), t1.number IS NOT NULL]"
),
"Expected t1 TableScan partial_filters to contain pushed predicate, got:\n{plan_str}"
);
// Find the position of the filter and verify it appears after a MergeScan
// opening (i.e., inside remote_input) rather than before the Join.
let filter_pos = plan_str
.find("TableScan: t1, partial_filters=[t1.pk1 = Utf8(\"v\"), t1.number IS NOT NULL]")
.unwrap();
let join_pos = plan_str.find("Inner Join").unwrap();
// The filter should be after the Join (meaning it was pushed down below the Join,
// into a MergeScan's remote_input)
assert!(
filter_pos > join_pos,
"Filter should be pushed below Join (into MergeScan remote_input), but found before Join"
);
}
/// LEFT JOIN preserves the left side, so a left-local WHERE predicate is safe
/// to push into the left scan before MergeScan wrapping.
#[test]
fn test_left_join_left_side_filter_pushdown_into_merge_scan() {
init_default_ut_logging();
let left_table =
TestTable::table_with_filter_pushdown(0, "t1".to_string(), FilterPushDownType::Inexact);
let right_table =
TestTable::table_with_filter_pushdown(1, "t2".to_string(), FilterPushDownType::Inexact);
let left_source = Arc::new(DefaultTableSource::new(Arc::new(
DfTableProviderAdapter::new(left_table),
)));
let right_source = Arc::new(DefaultTableSource::new(Arc::new(
DfTableProviderAdapter::new(right_table),
)));
let right_plan = LogicalPlanBuilder::scan_with_filters("t2", right_source, None, vec![])
.unwrap()
.build()
.unwrap();
let plan = LogicalPlanBuilder::scan_with_filters("t1", left_source, None, vec![])
.unwrap()
.join_on(
right_plan,
JoinType::Left,
vec![col("t1.number").eq(col("t2.number"))],
)
.unwrap()
.filter(col("t1.pk1").eq(lit("v")))
.unwrap()
.build()
.unwrap();
let config = ConfigOptions::default();
let result = DistPlannerAnalyzer {}.analyze(plan, &config).unwrap();
assert_remote_table_scan_filters_are_safe(&result);
let plan_str = result.to_string();
assert!(
plan_str.contains("TableScan: t1, partial_filters=[t1.pk1 = Utf8(\"v\")]"),
"Expected left-side TableScan partial_filters under LEFT JOIN, got:\n{plan_str}"
);
let scan_filter_pos = plan_str
.find("TableScan: t1, partial_filters=[t1.pk1 = Utf8(\"v\")]")
.unwrap();
let join_pos = plan_str.find("Left Join").unwrap();
assert!(
scan_filter_pos > join_pos,
"Left-side filter should be pushed below LEFT JOIN into MergeScan remote_input:\n{plan_str}"
);
}
/// Negative case: cross-table predicate t1.pk1 = t2.pk2 should NOT become a
/// side-local scan filter but remain as a join filter.
#[test]
fn test_join_cross_table_predicate_not_pushed_to_single_side() {
init_default_ut_logging();
let left_table =
TestTable::table_with_filter_pushdown(0, "t1".to_string(), FilterPushDownType::Inexact);
let right_table =
TestTable::table_with_filter_pushdown(1, "t2".to_string(), FilterPushDownType::Inexact);
let left_source = Arc::new(DefaultTableSource::new(Arc::new(
DfTableProviderAdapter::new(left_table),
)));
let right_source = Arc::new(DefaultTableSource::new(Arc::new(
DfTableProviderAdapter::new(right_table),
)));
let right_plan = LogicalPlanBuilder::scan_with_filters("t2", right_source, None, vec![])
.unwrap()
.build()
.unwrap();
// Plan: Filter(t1.pk1 = t2.pk2) -> Join(t1.number = t2.number) -> ...
// The filter involves columns from both tables, so PushDownFilter should
// keep it as a join filter (not push into a single side's scan).
let plan = LogicalPlanBuilder::scan_with_filters("t1", left_source, None, vec![])
.unwrap()
.join_on(
right_plan,
JoinType::Inner,
vec![col("t1.number").eq(col("t2.number"))],
)
.unwrap()
.filter(col("t1.pk1").eq(col("t2.pk2"))) // cross-table predicate
.unwrap()
.build()
.unwrap();
let config = ConfigOptions::default();
let result = DistPlannerAnalyzer {}.analyze(plan, &config).unwrap();
assert_remote_table_scan_filters_are_safe(&result);
let plan_str = result.to_string();
// The cross-table predicate should NOT appear as a filter on a single table's
// scan inside a MergeScan remote_input. It should remain as part of the
// Join's filter.
// The key assertion: it should NOT appear as "Filter: t1.pk1 = t2.pk2"
assert!(
!plan_str.contains("Filter: t1.pk1 = t2.pk2"),
"Cross-table predicate should not become a side-local Filter:\n{plan_str}"
);
assert!(
plan_str.contains("t1.pk1 = t2.pk2") || plan_str.contains("t2.pk2 = t1.pk1"),
"Cross-table predicate should remain in the join plan:\n{plan_str}"
);
assert!(
!plan_str.contains("partial_filters=[t1.pk1 = t2.pk2]")
&& !plan_str.contains("partial_filters=[t2.pk2 = t1.pk1]")
&& !plan_str.contains("full_filters=[t1.pk1 = t2.pk2]")
&& !plan_str.contains("full_filters=[t2.pk2 = t1.pk1]"),
"Cross-table predicate should not become a single-side TableScan filter:\n{plan_str}"
);
}

View File

@@ -88,12 +88,31 @@ impl PredicateExtractor {
Ok(partition_exprs)
}
/// Collect all filter expressions from a logical plan
/// Collect all filter expressions from a logical plan.
///
/// Besides explicit [`LogicalPlan::Filter`] nodes, this must also collect
/// predicates already stored in [`LogicalPlan::TableScan`] filters. The
/// distributed planner runs a focused DataFusion `PushDownFilter` pass
/// before `MergeScan` wrapping, so partition predicates may no longer exist
/// as standalone `Filter` nodes by the time region pruning calls this
/// extractor. If we ignored `TableScan.filters`, region pruning would miss
/// predicates that were successfully pushed down for scan-level pruning.
fn collect_filter_expressions(plan: &LogicalPlan, expressions: &mut Vec<Expr>) -> DfResult<()> {
if let LogicalPlan::Filter(filter) = plan {
expressions.push(filter.predicate.clone());
}
// Collect filters that DataFusion's PushDownFilter stored in TableScan.
// `TableScan.filters` is conjunctive: DataFusion passes scan filters as
// a list but the table scan must satisfy all of them. Preserve that AND
// semantics for partition pruning instead of returning the filters as
// independent top-level expressions.
if let LogicalPlan::TableScan(table_scan) = plan
&& let Some(expr) = Self::conjunction(table_scan.filters.iter().cloned())
{
expressions.push(expr);
}
// Recursively visit children
for child in plan.inputs() {
Self::collect_filter_expressions(child, expressions)?;
@@ -106,6 +125,11 @@ impl PredicateExtractor {
Ok(())
}
fn conjunction(mut expressions: impl Iterator<Item = Expr>) -> Option<Expr> {
let first = expressions.next()?;
Some(expressions.fold(first, |acc, expr| acc.and(expr)))
}
}
/// Result of analyzing an expression for partition pruning safety
@@ -579,6 +603,69 @@ mod tests {
}
}
#[test]
fn test_extracts_table_scan_filters() {
let table_scan = create_test_table_scan();
let filter = col("user_id").gt_eq(lit(100i64));
let LogicalPlan::TableScan(scan) = table_scan else {
panic!("expected test table scan");
};
let plan = LogicalPlan::TableScan(datafusion_expr::logical_plan::TableScan {
filters: vec![filter],
..scan
});
let partition_exprs =
PredicateExtractor::extract_partition_expressions(&plan, &["user_id".to_string()])
.unwrap();
assert_eq!(
partition_exprs,
vec![PartitionExpr::new(
Operand::Column("user_id".to_string()),
RestrictedOp::GtEq,
Operand::Value(Value::Int64(100)),
)]
);
}
#[test]
fn test_combines_table_scan_filters_as_conjunction() {
let table_scan = create_test_table_scan();
let filter_a = col("user_id").eq(lit(10i64));
let filter_b = col("value").eq(lit(20i64));
let LogicalPlan::TableScan(scan) = table_scan else {
panic!("expected test table scan");
};
let plan = LogicalPlan::TableScan(datafusion_expr::logical_plan::TableScan {
filters: vec![filter_a, filter_b],
..scan
});
let partition_exprs = PredicateExtractor::extract_partition_expressions(
&plan,
&["user_id".to_string(), "value".to_string()],
)
.unwrap();
assert_eq!(
partition_exprs,
vec![PartitionExpr::new(
Operand::Expr(PartitionExpr::new(
Operand::Column("user_id".to_string()),
RestrictedOp::Eq,
Operand::Value(Value::Int64(10)),
)),
RestrictedOp::And,
Operand::Expr(PartitionExpr::new(
Operand::Column("value".to_string()),
RestrictedOp::Eq,
Operand::Value(Value::Int64(20)),
)),
)]
);
}
#[test]
fn test_basic_constraints_extraction() {
let cases = vec![

View File

@@ -2465,8 +2465,10 @@ impl PromPlanner {
plan: &LogicalPlan,
out: &mut BTreeSet<String>,
) -> Result<()> {
if let LogicalPlan::TableScan(scan) = plan {
let table = planner.table_from_source(&scan.source)?;
// Derived PromQL plans may contain non-Greptime scans without row-key metadata.
if let LogicalPlan::TableScan(scan) = plan
&& let Ok(table) = planner.table_from_source(&scan.source)
{
for col in table.table_info().meta.row_key_column_names() {
if col != DATA_SCHEMA_TABLE_ID_COLUMN_NAME
&& col != DATA_SCHEMA_TSID_COLUMN_NAME
@@ -6576,6 +6578,51 @@ mod test {
assert!(!aggr_line.contains(DATA_SCHEMA_TSID_COLUMN_NAME));
}
#[tokio::test]
async fn aggregate_over_binary_time_function_expr() {
for op in ["sum", "min", "max", "avg"] {
let prom_expr = parser::parse(&format!(
"{op} by (tag_0, tag_1, tag_2) (time() - some_metric)"
))
.unwrap();
let eval_stmt = EvalStmt {
expr: prom_expr,
start: UNIX_EPOCH,
end: UNIX_EPOCH
.checked_add(Duration::from_secs(100_000))
.unwrap(),
interval: Duration::from_secs(5),
lookback_delta: Duration::from_secs(1),
};
let table_provider = build_test_table_provider_with_tsid(
&[(DEFAULT_SCHEMA_NAME.to_string(), "some_metric".to_string())],
3,
1,
)
.await;
let plan =
PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
.await
.unwrap();
let plan_str = plan.display_indent_schema().to_string();
let aggr_line = plan_str
.lines()
.find(|line| line.contains("Aggregate: groupBy="))
.unwrap();
assert!(aggr_line.contains(op), "{plan_str}");
assert!(aggr_line.contains("first_value"), "{plan_str}");
assert!(
!plan
.schema()
.fields()
.iter()
.any(|field| { field.name() == DATA_SCHEMA_TSID_COLUMN_NAME })
);
}
}
#[tokio::test]
async fn topk_by_does_not_partition_by_tsid() {
let prom_expr = parser::parse("topk by (__tsid) (1, some_metric)").unwrap();

View File

@@ -1 +1 @@
v0.12.2
v0.13.6

View File

@@ -166,8 +166,7 @@ impl GreptimeRequestHandler {
}
}
if let Err(e) =
result_sender.try_send(result.map_err(|e| Status::from_error(Box::new(e))))
if let Err(e) = result_sender.try_send(result.map_err(Status::from))
&& let TrySendError::Closed(_) = e
{
warn!(r#""DoPut" client maybe unreachable, abort handling its message"#);
@@ -298,13 +297,18 @@ impl Drop for RequestTimer {
#[cfg(test)]
mod tests {
use chrono::FixedOffset;
use common_error::GREPTIME_DB_HEADER_ERROR_CODE;
use common_error::ext::BoxedError;
use common_time::Timezone;
use query::options::FLOW_SCHEDULED_TIME_MILLIS;
use session::hints::{
INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY, REMOTE_QUERY_ID_EXTENSION_KEY,
};
use snafu::ResultExt;
use tonic::Code;
use super::*;
use crate::error::{ExecuteGrpcRequestSnafu, InvalidParameterSnafu};
#[test]
fn test_create_query_context() {
@@ -378,4 +382,34 @@ mod tests {
query_context.remote_query_id()
);
}
#[test]
fn test_record_batch_error_to_status_preserves_error_details() {
let inner = InvalidParameterSnafu {
reason: "Column not found, column: new_col",
}
.build();
let err = Err::<(), _>(BoxedError::new(inner))
.context(ExecuteGrpcRequestSnafu)
.unwrap_err();
let status = Status::from(err);
assert_eq!(status.code(), Code::InvalidArgument);
assert!(
status
.message()
.contains("Column not found, column: new_col")
);
assert!(
status
.message()
.contains("Invalid request parameter: Column not found")
);
assert!(
status
.metadata()
.contains_key(GREPTIME_DB_HEADER_ERROR_CODE)
);
}
}

View File

@@ -119,6 +119,7 @@ pub mod test_helpers;
pub const HTTP_API_VERSION: &str = "v1";
pub const HTTP_API_PREFIX: &str = "/v1/";
pub const HTTP_API_PREFIX_WITHOUT_TRAILING_SLASH: &str = "/v1";
/// Default http body limit (64M).
const DEFAULT_BODY_LIMIT: ReadableSize = ReadableSize::mb(64);

View File

@@ -25,16 +25,19 @@ use common_telemetry::warn;
///
/// Extracts client address from [`ConnectInfo`] if available.
pub async fn log_error_with_client_ip(req: Request<Body>, next: Next) -> Response {
let request_info = req
.extensions()
.get::<ConnectInfo<SocketAddr>>()
.map(|c| c.0)
.map(|addr| {
let method = req.method().clone();
let uri = req.uri().clone();
let matched_path = req.extensions().get::<MatchedPath>().cloned();
(addr, method, uri, matched_path)
});
let request_info = if is_public_http_api_path(req.uri().path()) {
req.extensions()
.get::<ConnectInfo<SocketAddr>>()
.map(|c| c.0)
.map(|addr| {
let method = req.method().clone();
let uri = req.uri().clone();
let matched_path = req.extensions().get::<MatchedPath>().cloned();
(addr, method, uri, matched_path)
})
} else {
None
};
let response = next.run(req).await;
@@ -57,6 +60,11 @@ pub async fn log_error_with_client_ip(req: Request<Body>, next: Next) -> Respons
response
}
fn is_public_http_api_path(path: &str) -> bool {
path == super::HTTP_API_PREFIX_WITHOUT_TRAILING_SLASH
|| path.starts_with(super::HTTP_API_PREFIX)
}
#[cfg(test)]
mod tests {
use axum::Router;
@@ -66,6 +74,19 @@ mod tests {
use super::*;
#[test]
fn test_public_http_api_path_matches_v1_prefix() {
assert!(is_public_http_api_path("/v1"));
assert!(is_public_http_api_path("/v1/sql"));
assert!(is_public_http_api_path("/v1/prometheus/api/v1/query"));
assert!(!is_public_http_api_path("/"));
assert!(!is_public_http_api_path("/health"));
assert!(!is_public_http_api_path("/status"));
assert!(!is_public_http_api_path("/metrics"));
assert!(!is_public_http_api_path("/v10/sql"));
}
#[tokio::test]
async fn test_middleware_passes_error_response() {
async fn not_found_handler() -> StatusCode {

View File

@@ -43,8 +43,8 @@ use datafusion_common::ScalarValue;
use datatypes::prelude::ConcreteDataType;
use datatypes::schema::{ColumnSchema, SchemaRef};
use datatypes::types::jsonb_to_string;
use futures::StreamExt;
use futures::future::join_all;
use futures::{StreamExt, TryStreamExt};
use itertools::Itertools;
use promql_parser::label::{METRIC_NAME, MatchOp, Matcher, Matchers};
use promql_parser::parser::token::{self};
@@ -624,13 +624,9 @@ async fn get_all_column_names(
schema: &str,
manager: &CatalogManagerRef,
) -> std::result::Result<HashSet<String>, catalog::error::Error> {
let table_names = manager.table_names(catalog, schema, None).await?;
let mut labels = HashSet::new();
for table_name in table_names {
let Some(table) = manager.table(catalog, schema, &table_name, None).await? else {
continue;
};
let mut tables = manager.tables(catalog, schema, None);
while let Some(table) = tables.try_next().await? {
for column in table.primary_key_columns() {
if column.name != DATA_SCHEMA_TABLE_ID_COLUMN_NAME
&& column.name != DATA_SCHEMA_TSID_COLUMN_NAME
@@ -1683,7 +1679,16 @@ pub async fn parse_query(
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use std::sync::Arc;
use catalog::memory::MemoryCatalogManager;
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
use datatypes::prelude::ConcreteDataType;
use datatypes::schema::{ColumnSchema, Schema};
use promql_parser::parser::value::ValueType;
use table::metadata::{TableInfoBuilder, TableMetaBuilder, TableType, TableVersion};
use table::test_util::EmptyTable;
use super::*;
@@ -1895,4 +1900,52 @@ mod tests {
}
}
}
#[tokio::test]
async fn test_get_all_column_names_uses_tag_columns() {
let schema = Arc::new(Schema::new(vec![
ColumnSchema::new(
"greptime_timestamp",
ConcreteDataType::timestamp_millisecond_datatype(),
false,
)
.with_time_index(true),
ColumnSchema::new("host", ConcreteDataType::string_datatype(), false),
ColumnSchema::new("region", ConcreteDataType::string_datatype(), false),
ColumnSchema::new("value", ConcreteDataType::float64_datatype(), true),
ColumnSchema::new(
DATA_SCHEMA_TSID_COLUMN_NAME,
ConcreteDataType::uint64_datatype(),
true,
),
]));
let meta = TableMetaBuilder::empty()
.schema(schema)
.primary_key_indices(vec![1, 2, 4])
.engine("metric".to_string())
.next_column_id(5)
.build()
.unwrap();
let table_info = TableInfoBuilder::default()
.table_id(1024)
.table_version(0 as TableVersion)
.name("cpu_usage")
.catalog_name(DEFAULT_CATALOG_NAME)
.schema_name(DEFAULT_SCHEMA_NAME)
.table_type(TableType::Base)
.meta(meta)
.build()
.unwrap();
let manager: CatalogManagerRef =
MemoryCatalogManager::new_with_table(EmptyTable::from_table_info(&table_info));
let labels = get_all_column_names(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, &manager)
.await
.unwrap();
assert_eq!(
labels,
HashSet::from(["host".to_string(), "region".to_string()])
);
}
}

View File

@@ -108,7 +108,8 @@ pub struct FileRefsManifest {
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct GcReport {
/// deleted files per region
/// Deleted SST/parquet file ids per region. Index-only deletions are reported via
/// `deleted_indexes` because a naked `FileId` cannot distinguish index versions.
/// TODO(discord9): change to `RemovedFile`?
pub deleted_files: HashMap<RegionId, Vec<FileId>>,
pub deleted_indexes: HashMap<RegionId, Vec<(FileId, IndexVersion)>>,

View File

@@ -22,6 +22,7 @@ use datafusion::catalog::Session;
use datafusion::datasource::{TableProvider, TableType as DfTableType};
use datafusion::error::Result as DfResult;
use datafusion::physical_plan::ExecutionPlan;
use datafusion_common::tree_node::{TreeNode, TreeNodeRecursion};
use datafusion_expr::TableProviderFilterPushDown as DfTableProviderFilterPushDown;
use datafusion_expr::expr::Expr;
use datafusion_physical_expr::PhysicalSortExpr;
@@ -139,10 +140,76 @@ impl TableProvider for DfTableProviderAdapter {
&self,
filters: &[&Expr],
) -> DfResult<Vec<DfTableProviderFilterPushDown>> {
let schema = self.schema();
let filters = filters.iter().map(|&x| x.clone()).collect::<Vec<_>>();
Ok(self
.table
.supports_filters_pushdown(&filters.iter().collect::<Vec<_>>())
.map(|v| v.into_iter().map(Into::into).collect::<Vec<_>>())?)
.map(|v| {
v.into_iter()
.zip(filters.iter())
.map(|(ty, expr)| {
if !is_scan_local(expr, &schema) {
DfTableProviderFilterPushDown::Unsupported
} else {
ty.into()
}
})
.collect::<Vec<_>>()
})?)
}
}
/// Returns true if the expression can be safely evaluated by a remote scan.
/// Rejects outer references and column references unknown to the schema.
fn is_scan_local(expr: &Expr, schema: &DfSchemaRef) -> bool {
let mut problems = false;
let _ = expr.apply(|node| match node {
Expr::OuterReferenceColumn(_, _) => {
problems = true;
Ok(TreeNodeRecursion::Stop)
}
Expr::Column(col) => {
if schema.column_with_name(&col.name).is_none() {
problems = true;
return Ok(TreeNodeRecursion::Stop);
}
Ok(TreeNodeRecursion::Continue)
}
_ => Ok(TreeNodeRecursion::Continue),
});
!problems
}
#[cfg(test)]
mod tests {
use datafusion_common::Column as DfColumn;
use super::*;
#[test]
fn test_is_scan_local_normal_column() {
use datafusion::arrow::datatypes::{DataType, Field, Schema};
let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int64, true)]));
let expr = Expr::Column(DfColumn::new(Some("t"), "x"));
assert!(is_scan_local(&expr, &schema));
}
#[test]
fn test_is_scan_local_unknown_column() {
use datafusion::arrow::datatypes::{DataType, Field, Schema};
let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int64, true)]));
let expr = Expr::Column(DfColumn::new(Some("t"), "z"));
assert!(!is_scan_local(&expr, &schema));
}
#[test]
fn test_is_scan_local_outer_ref() {
use datafusion::arrow::datatypes::Schema;
use datatypes::arrow::datatypes::{DataType, Field};
let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int64, true)]));
let field = Arc::new(Field::new("x", DataType::Int64, true));
let expr = Expr::OuterReferenceColumn(field, DfColumn::new(Some("t"), "x"));
assert!(!is_scan_local(&expr, &schema));
}
}

View File

@@ -17,11 +17,11 @@ use std::time::Duration;
use auth::user_provider_from_option;
use chrono::{DateTime, NaiveDate, NaiveDateTime, SecondsFormat, Utc};
use common_catalog::consts::DEFAULT_PRIVATE_SCHEMA_NAME;
use common_catalog::consts::{DEFAULT_PRIVATE_SCHEMA_NAME, DEFAULT_SCHEMA_NAME};
use common_frontend::slow_query_event::{
SLOW_QUERY_TABLE_COST_COLUMN_NAME, SLOW_QUERY_TABLE_IS_PROMQL_COLUMN_NAME,
SLOW_QUERY_TABLE_NAME, SLOW_QUERY_TABLE_QUERY_COLUMN_NAME,
SLOW_QUERY_TABLE_THRESHOLD_COLUMN_NAME,
SLOW_QUERY_TABLE_SCHEMA_NAME_COLUMN_NAME, SLOW_QUERY_TABLE_THRESHOLD_COLUMN_NAME,
};
use sqlx::mysql::{MySqlConnection, MySqlDatabaseError, MySqlPoolOptions};
use sqlx::postgres::{PgDatabaseError, PgPoolOptions};
@@ -720,10 +720,11 @@ pub async fn test_mysql_slow_query(store_type: StorageType) {
let table = format!("{}.{}", DEFAULT_PRIVATE_SCHEMA_NAME, SLOW_QUERY_TABLE_NAME);
let query = format!(
"SELECT {}, {}, {}, {} FROM {table} WHERE {} = ?",
"SELECT {}, {}, {}, {}, {} FROM {table} WHERE {} = ?",
SLOW_QUERY_TABLE_COST_COLUMN_NAME,
SLOW_QUERY_TABLE_THRESHOLD_COLUMN_NAME,
SLOW_QUERY_TABLE_QUERY_COLUMN_NAME,
SLOW_QUERY_TABLE_SCHEMA_NAME_COLUMN_NAME,
SLOW_QUERY_TABLE_IS_PROMQL_COLUMN_NAME,
SLOW_QUERY_TABLE_QUERY_COLUMN_NAME,
);
@@ -747,10 +748,12 @@ pub async fn test_mysql_slow_query(store_type: StorageType) {
let cost: u64 = row.get(0);
let threshold: u64 = row.get(1);
let query: String = row.get(2);
let is_promql: bool = row.get(3);
let schema_name: String = row.get(3);
let is_promql: bool = row.get(4);
assert!(cost > 0 && threshold > 0 && cost > threshold);
assert_eq!(query, slow_query);
assert_eq!(schema_name, DEFAULT_SCHEMA_NAME);
assert!(!is_promql);
let _ = fe_mysql_server.shutdown().await;
@@ -847,10 +850,11 @@ pub async fn test_postgres_slow_query(store_type: StorageType) {
let table = format!("{}.{}", DEFAULT_PRIVATE_SCHEMA_NAME, SLOW_QUERY_TABLE_NAME);
let query = format!(
"SELECT {}, {}, {}, {} FROM {table} WHERE {} = $1",
"SELECT {}, {}, {}, {}, {} FROM {table} WHERE {} = $1",
SLOW_QUERY_TABLE_COST_COLUMN_NAME,
SLOW_QUERY_TABLE_THRESHOLD_COLUMN_NAME,
SLOW_QUERY_TABLE_QUERY_COLUMN_NAME,
SLOW_QUERY_TABLE_SCHEMA_NAME_COLUMN_NAME,
SLOW_QUERY_TABLE_IS_PROMQL_COLUMN_NAME,
SLOW_QUERY_TABLE_QUERY_COLUMN_NAME,
);
@@ -873,10 +877,12 @@ pub async fn test_postgres_slow_query(store_type: StorageType) {
let cost: Decimal = row.get(0);
let threshold: Decimal = row.get(1);
let query: String = row.get(2);
let is_promql: bool = row.get(3);
let schema_name: String = row.get(3);
let is_promql: bool = row.get(4);
assert!(cost > 0.into() && threshold > 0.into() && cost > threshold);
assert_eq!(query, slow_query);
assert_eq!(schema_name, DEFAULT_SCHEMA_NAME);
assert!(!is_promql);
let _ = fe_pg_server.shutdown().await;

View File

@@ -83,37 +83,45 @@ limit 1;
|_|_Inner Join: t_3.ts = t_4.ts, t_3.vin = t_4.vin_|
|_|_Inner Join: t_2.ts = t_3.ts, t_2.vin = t_3.vin_|
|_|_Inner Join: t_1.ts = t_2.ts, t_1.vin = t_2.vin_|
|_|_Filter: t_1.vin IS NOT NULL_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: t_1_|
|_| ]]_|
|_|_Filter: t_2.vin IS NOT NULL_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: t_2_|
|_| Filter: t_1.vin IS NOT NULL_|
|_|_TableScan: t_1, partial_filters=[t_1.vin IS NOT NULL]_|
|_| ]]_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: t_3_|
|_| Filter: t_2.vin IS NOT NULL_|
|_|_TableScan: t_2, partial_filters=[t_2.vin IS NOT NULL]_|
|_| ]]_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: t_4_|
|_| Filter: t_3.vin IS NOT NULL_|
|_|_TableScan: t_3, partial_filters=[t_3.vin IS NOT NULL]_|
|_| ]]_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: t_5_|
|_| Filter: t_4.vin IS NOT NULL_|
|_|_TableScan: t_4, partial_filters=[t_4.vin IS NOT NULL]_|
|_| ]]_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: t_6_|
|_| Filter: t_5.vin IS NOT NULL_|
|_|_TableScan: t_5, partial_filters=[t_5.vin IS NOT NULL]_|
|_| ]]_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: t_7_|
|_| Filter: t_6.vin IS NOT NULL_|
|_|_TableScan: t_6, partial_filters=[t_6.vin IS NOT NULL]_|
|_| ]]_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: t_8_|
|_| Filter: t_7.vin IS NOT NULL_|
|_|_TableScan: t_7, partial_filters=[t_7.vin IS NOT NULL]_|
|_| ]]_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: t_9_|
|_| Filter: t_8.vin IS NOT NULL_|
|_|_TableScan: t_8, partial_filters=[t_8.vin IS NOT NULL]_|
|_| ]]_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: t_10_|
|_| Filter: t_9.vin IS NOT NULL_|
|_|_TableScan: t_9, partial_filters=[t_9.vin IS NOT NULL]_|
|_| ]]_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| Filter: t_10.vin IS NOT NULL_|
|_|_TableScan: t_10, partial_filters=[t_10.vin IS NOT NULL]_|
|_| ]]_|
| physical_plan | SortPreservingMergeExec: [ts@0 DESC], fetch=1_|
|_|_SortExec: TopK(fetch=1), expr=[ts@0 DESC], preserve_partitioning=[true]_|
@@ -127,10 +135,8 @@ limit 1;
|_|_REDACTED
|_|_REDACTED
|_|_RepartitionExec: partitioning=REDACTED
|_|_FilterExec: vin@1 IS NOT NULL_|
|_|_MergeScanExec: REDACTED
|_|_RepartitionExec: partitioning=REDACTED
|_|_FilterExec: vin@1 IS NOT NULL_|
|_|_MergeScanExec: REDACTED
|_|_RepartitionExec: partitioning=REDACTED
|_|_MergeScanExec: REDACTED

View File

@@ -28,7 +28,7 @@ explain SELECT * FROM multi_partitions_test_table WHERE ts > cast(1000000000 as
|_| Sort: multi_partitions_test_table.host ASC NULLS LAST_|
|_|_Projection: multi_partitions_test_table.host, multi_partitions_test_table.ts, multi_partitions_test_table.cpu, multi_partitions_test_table.memory, multi_partitions_test_table.disk_util |
|_|_Filter: multi_partitions_test_table.ts > TimestampMillisecond(1000000000, None)_|
|_|_TableScan: multi_partitions_test_table_|
|_|_TableScan: multi_partitions_test_table, partial_filters=[multi_partitions_test_table.ts > TimestampMillisecond(1000000000, None)]_|
|_| ]]_|
| physical_plan | SortPreservingMergeExec: [host@0 ASC NULLS LAST]_|
|_|_MergeScanExec: REDACTED

View File

@@ -35,7 +35,7 @@ tql explain (1752591864, 1752592164, '30s') max by (a, b, c) (max_over_time(aggr
| | PromSeriesDivide: tags=["a", "b", "c", "d"] |
| | Sort: aggr_optimize_not.a ASC NULLS FIRST, aggr_optimize_not.b ASC NULLS FIRST, aggr_optimize_not.c ASC NULLS FIRST, aggr_optimize_not.d ASC NULLS FIRST, aggr_optimize_not.greptime_timestamp ASC NULLS FIRST |
| | Filter: aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(1752591744001, None) AND aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(1752592164000, None) |
| | TableScan: aggr_optimize_not |
| | TableScan: aggr_optimize_not, partial_filters=[aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(1752591744001, None), aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(1752592164000, None)] |
| | ]] |
| physical_plan | SortPreservingMergeExec: [a@0 ASC NULLS LAST, b@1 ASC NULLS LAST, c@2 ASC NULLS LAST, greptime_timestamp@3 ASC NULLS LAST] |
| | MergeScanExec: REDACTED
@@ -104,7 +104,7 @@ tql explain (1752591864, 1752592164, '30s') sum by (a, b) (max_over_time(aggr_op
| | PromSeriesDivide: tags=["a", "b", "c", "d"] |
| | Sort: aggr_optimize_not.a ASC NULLS FIRST, aggr_optimize_not.b ASC NULLS FIRST, aggr_optimize_not.c ASC NULLS FIRST, aggr_optimize_not.d ASC NULLS FIRST, aggr_optimize_not.greptime_timestamp ASC NULLS FIRST |
| | Filter: aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(1752591744001, None) AND aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(1752592164000, None) |
| | TableScan: aggr_optimize_not |
| | TableScan: aggr_optimize_not, partial_filters=[aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(1752591744001, None), aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(1752592164000, None)] |
| | ]] |
| physical_plan | SortPreservingMergeExec: [a@0 ASC NULLS LAST, b@1 ASC NULLS LAST, greptime_timestamp@2 ASC NULLS LAST] |
| | SortExec: expr=[a@0 ASC NULLS LAST, b@1 ASC NULLS LAST, greptime_timestamp@2 ASC NULLS LAST], preserve_partitioning=[true] |
@@ -173,7 +173,7 @@ tql explain (1752591864, 1752592164, '30s') avg by (a) (max_over_time(aggr_optim
| | PromSeriesDivide: tags=["a", "b", "c", "d"] |
| | Sort: aggr_optimize_not.a ASC NULLS FIRST, aggr_optimize_not.b ASC NULLS FIRST, aggr_optimize_not.c ASC NULLS FIRST, aggr_optimize_not.d ASC NULLS FIRST, aggr_optimize_not.greptime_timestamp ASC NULLS FIRST |
| | Filter: aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(1752591744001, None) AND aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(1752592164000, None) |
| | TableScan: aggr_optimize_not |
| | TableScan: aggr_optimize_not, partial_filters=[aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(1752591744001, None), aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(1752592164000, None)] |
| | ]] |
| physical_plan | SortPreservingMergeExec: [a@0 ASC NULLS LAST, greptime_timestamp@1 ASC NULLS LAST] |
| | SortExec: expr=[a@0 ASC NULLS LAST, greptime_timestamp@1 ASC NULLS LAST], preserve_partitioning=[true] |
@@ -242,7 +242,7 @@ tql explain (1752591864, 1752592164, '30s') count by (a, b, c, d) (max_over_time
| | PromSeriesDivide: tags=["a", "b", "c", "d"] |
| | Sort: aggr_optimize_not.a ASC NULLS FIRST, aggr_optimize_not.b ASC NULLS FIRST, aggr_optimize_not.c ASC NULLS FIRST, aggr_optimize_not.d ASC NULLS FIRST, aggr_optimize_not.greptime_timestamp ASC NULLS FIRST |
| | Filter: aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(1752591744001, None) AND aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(1752592164000, None) |
| | TableScan: aggr_optimize_not |
| | TableScan: aggr_optimize_not, partial_filters=[aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(1752591744001, None), aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(1752592164000, None)] |
| | ]] |
| physical_plan | SortPreservingMergeExec: [a@0 ASC NULLS LAST, b@1 ASC NULLS LAST, c@2 ASC NULLS LAST, d@3 ASC NULLS LAST, greptime_timestamp@4 ASC NULLS LAST] |
| | MergeScanExec: REDACTED
@@ -307,7 +307,7 @@ tql explain (1752591864, 1752592164, '30s') min by (b, c, d) (max_over_time(aggr
| | PromSeriesDivide: tags=["a", "b", "c", "d"] |
| | Sort: aggr_optimize_not.a ASC NULLS FIRST, aggr_optimize_not.b ASC NULLS FIRST, aggr_optimize_not.c ASC NULLS FIRST, aggr_optimize_not.d ASC NULLS FIRST, aggr_optimize_not.greptime_timestamp ASC NULLS FIRST |
| | Filter: aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(1752591744001, None) AND aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(1752592164000, None) |
| | TableScan: aggr_optimize_not |
| | TableScan: aggr_optimize_not, partial_filters=[aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(1752591744001, None), aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(1752592164000, None)] |
| | ]] |
| physical_plan | SortPreservingMergeExec: [b@0 ASC NULLS LAST, c@1 ASC NULLS LAST, d@2 ASC NULLS LAST, greptime_timestamp@3 ASC NULLS LAST] |
| | SortExec: expr=[b@0 ASC NULLS LAST, c@1 ASC NULLS LAST, d@2 ASC NULLS LAST, greptime_timestamp@3 ASC NULLS LAST], preserve_partitioning=[true] |
@@ -373,7 +373,7 @@ tql explain sum(aggr_optimize_not);
| | PromSeriesDivide: tags=["a", "b", "c", "d"] |
| | Sort: aggr_optimize_not.a ASC NULLS FIRST, aggr_optimize_not.b ASC NULLS FIRST, aggr_optimize_not.c ASC NULLS FIRST, aggr_optimize_not.d ASC NULLS FIRST, aggr_optimize_not.greptime_timestamp ASC NULLS FIRST |
| | Filter: aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(-299999, None) AND aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(0, None) |
| | TableScan: aggr_optimize_not |
| | TableScan: aggr_optimize_not, partial_filters=[aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(-299999, None), aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(0, None)] |
| | ]] |
| physical_plan | SortPreservingMergeExec: [greptime_timestamp@0 ASC NULLS LAST] |
| | SortExec: expr=[greptime_timestamp@0 ASC NULLS LAST], preserve_partitioning=[true] |
@@ -459,7 +459,7 @@ tql explain (1752591864, 1752592164, '30s') sum by (a, b, c) (rate(aggr_optimize
| | PromSeriesDivide: tags=["a", "b", "c", "d"] |
| | Sort: aggr_optimize_not.a ASC NULLS FIRST, aggr_optimize_not.b ASC NULLS FIRST, aggr_optimize_not.c ASC NULLS FIRST, aggr_optimize_not.d ASC NULLS FIRST, aggr_optimize_not.greptime_timestamp ASC NULLS FIRST |
| | Filter: aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(1752591744001, None) AND aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(1752592164000, None) |
| | TableScan: aggr_optimize_not |
| | TableScan: aggr_optimize_not, partial_filters=[aggr_optimize_not.greptime_timestamp >= TimestampMillisecond(1752591744001, None), aggr_optimize_not.greptime_timestamp <= TimestampMillisecond(1752592164000, None)] |
| | ]] |
| | SubqueryAlias: aggr_optimize_not_count |
| | Sort: aggr_optimize_not_count.a ASC NULLS LAST, aggr_optimize_not_count.b ASC NULLS LAST, aggr_optimize_not_count.c ASC NULLS LAST, aggr_optimize_not_count.greptime_timestamp ASC NULLS LAST |
@@ -472,7 +472,7 @@ tql explain (1752591864, 1752592164, '30s') sum by (a, b, c) (rate(aggr_optimize
| | Sort: aggr_optimize_not_count.a ASC NULLS FIRST, aggr_optimize_not_count.b ASC NULLS FIRST, aggr_optimize_not_count.c ASC NULLS FIRST, aggr_optimize_not_count.d ASC NULLS FIRST, aggr_optimize_not_count.greptime_timestamp ASC NULLS FIRST |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Filter: aggr_optimize_not_count.greptime_timestamp >= TimestampMillisecond(1752591744001, None) AND aggr_optimize_not_count.greptime_timestamp <= TimestampMillisecond(1752592164000, None) |
| | TableScan: aggr_optimize_not_count |
| | TableScan: aggr_optimize_not_count, partial_filters=[aggr_optimize_not_count.greptime_timestamp >= TimestampMillisecond(1752591744001, None), aggr_optimize_not_count.greptime_timestamp <= TimestampMillisecond(1752592164000, None)] |
| | ]] |
| physical_plan | ProjectionExec: expr=[a@0 as a, b@1 as b, c@2 as c, greptime_timestamp@3 as greptime_timestamp, sum(prom_rate(greptime_timestamp_range,greptime_value,greptime_timestamp,Int64(120000)))@5 / sum(prom_rate(greptime_timestamp_range,greptime_value,greptime_timestamp,Int64(120000)))@4 as aggr_optimize_not.sum(prom_rate(greptime_timestamp_range,greptime_value,greptime_timestamp,Int64(120000))) / aggr_optimize_not_count.sum(prom_rate(greptime_timestamp_range,greptime_value,greptime_timestamp,Int64(120000)))] |
| | REDACTED
@@ -1159,7 +1159,7 @@ EXPLAIN SELECT SUM(val_col_1), COUNT(*) FROM step_aggr_extended WHERE pk_col_1 =
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| Aggregate: groupBy=[[]], aggr=[[__sum_state(step_aggr_extended.val_col_1), __count_state(step_aggr_extended.ts)]]_|
|_|_Filter: step_aggr_extended.pk_col_1 = Utf8("non_existent")_|
|_|_TableScan: step_aggr_extended_|
|_|_TableScan: step_aggr_extended, partial_filters=[step_aggr_extended.pk_col_1 = Utf8("non_existent")]_|
|_| ]]_|
| physical_plan | ProjectionExec: expr=[sum(step_aggr_extended.val_col_1)@0 as sum(step_aggr_extended.val_col_1), count(Int64(1))@1 as count(*)]_|
|_|_AggregateExec: mode=Final, gby=[], aggr=[sum(step_aggr_extended.val_col_1), count(Int64(1))]_|

View File

@@ -250,7 +250,7 @@ GROUP BY
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| Aggregate: groupBy=[[base_table.env, base_table.service_name, base_table.city, base_table.page, CAST(date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months: 0, days: 0, nanoseconds: 60000000000 }"), base_table.time) AS Timestamp(s)) AS arrow_cast(date_bin(Utf8("60 seconds"),base_table.time),Utf8("Timestamp(s)"))]], aggr=[[__uddsketch_state_state(Int64(128), Float64(0.01), CAST(CASE WHEN base_table.lcp > Int64(0) AND base_table.lcp < Int64(3000000) THEN base_table.lcp ELSE Int64(NULL) END AS Float64)), __max_state(CASE WHEN base_table.lcp > Int64(0) AND base_table.lcp < Int64(3000000) THEN base_table.lcp ELSE Int64(NULL) END), __min_state(CASE WHEN base_table.lcp > Int64(0) AND base_table.lcp < Int64(3000000) THEN base_table.lcp ELSE Int64(NULL) END), __uddsketch_state_state(Int64(128), Float64(0.01), CAST(CASE WHEN base_table.fmp > Int64(0) AND base_table.fmp < Int64(3000000) THEN base_table.fmp ELSE Int64(NULL) END AS Float64)), __max_state(CASE WHEN base_table.fmp > Int64(0) AND base_table.fmp < Int64(3000000) THEN base_table.fmp ELSE Int64(NULL) END), __min_state(CASE WHEN base_table.fmp > Int64(0) AND base_table.fmp < Int64(3000000) THEN base_table.fmp ELSE Int64(NULL) END), __uddsketch_state_state(Int64(128), Float64(0.01), CAST(CASE WHEN base_table.fcp > Int64(0) AND base_table.fcp < Int64(3000000) THEN base_table.fcp ELSE Int64(NULL) END AS Float64)), __max_state(CASE WHEN base_table.fcp > Int64(0) AND base_table.fcp < Int64(3000000) THEN base_table.fcp ELSE Int64(NULL) END), __min_state(CASE WHEN base_table.fcp > Int64(0) AND base_table.fcp < Int64(3000000) THEN base_table.fcp ELSE Int64(NULL) END), __uddsketch_state_state(Int64(128), Float64(0.01), CAST(CASE WHEN base_table.fp > Int64(0) AND base_table.fp < Int64(3000000) THEN base_table.fp ELSE Int64(NULL) END AS Float64)), __max_state(CASE WHEN base_table.fp > Int64(0) AND base_table.fp < Int64(3000000) THEN base_table.fp ELSE Int64(NULL) END), __min_state(CASE WHEN base_table.fp > Int64(0) AND base_table.fp < Int64(3000000) THEN base_table.fp ELSE Int64(NULL) END), __uddsketch_state_state(Int64(128), Float64(0.01), CAST(CASE WHEN base_table.tti > Int64(0) AND base_table.tti < Int64(3000000) THEN base_table.tti ELSE Int64(NULL) END AS Float64)), __max_state(CASE WHEN base_table.tti > Int64(0) AND base_table.tti < Int64(3000000) THEN base_table.tti ELSE Int64(NULL) END), __min_state(CASE WHEN base_table.tti > Int64(0) AND base_table.tti < Int64(3000000) THEN base_table.tti ELSE Int64(NULL) END), __uddsketch_state_state(Int64(128), Float64(0.01), CAST(CASE WHEN base_table.fid > Int64(0) AND base_table.fid < Int64(3000000) THEN base_table.fid ELSE Int64(NULL) END AS Float64)), __max_state(CASE WHEN base_table.fid > Int64(0) AND base_table.fid < Int64(3000000) THEN base_table.fid ELSE Int64(NULL) END), __min_state(CASE WHEN base_table.fid > Int64(0) AND base_table.fid < Int64(3000000) THEN base_table.fid ELSE Int64(NULL) END), __max_state(base_table.shard_key)]]_|
|_|_Filter: (base_table.lcp > Int64(0) AND base_table.lcp < Int64(3000000) OR base_table.fmp > Int64(0) AND base_table.fmp < Int64(3000000) OR base_table.fcp > Int64(0) AND base_table.fcp < Int64(3000000) OR base_table.fp > Int64(0) AND base_table.fp < Int64(3000000) OR base_table.tti > Int64(0) AND base_table.tti < Int64(3000000) OR base_table.fid > Int64(0) AND base_table.fid < Int64(3000000)) AND base_table.time >= TimestampMillisecond(0, None)_|
|_|_TableScan: base_table_|
|_|_TableScan: base_table, partial_filters=[base_table.lcp > Int64(0) AND base_table.lcp < Int64(3000000) OR base_table.fmp > Int64(0) AND base_table.fmp < Int64(3000000) OR base_table.fcp > Int64(0) AND base_table.fcp < Int64(3000000) OR base_table.fp > Int64(0) AND base_table.fp < Int64(3000000) OR base_table.tti > Int64(0) AND base_table.tti < Int64(3000000) OR base_table.fid > Int64(0) AND base_table.fid < Int64(3000000), base_table.time >= TimestampMillisecond(0, None)]_|
|_| ]]_|
| physical_plan | ProjectionExec: expr=[env@0 as env, service_name@1 as service_name, city@2 as city, page@3 as page, uddsketch_state(Int64(128),Float64(0.01),CASE WHEN base_table.lcp > Int64(0) AND base_table.lcp < Int64(3000000) THEN base_table.lcp ELSE NULL END)@5 as lcp_state, max(CASE WHEN base_table.lcp > Int64(0) AND base_table.lcp < Int64(3000000) THEN base_table.lcp ELSE NULL END)@6 as max_lcp, min(CASE WHEN base_table.lcp > Int64(0) AND base_table.lcp < Int64(3000000) THEN base_table.lcp ELSE NULL END)@7 as min_lcp, uddsketch_state(Int64(128),Float64(0.01),CASE WHEN base_table.fmp > Int64(0) AND base_table.fmp < Int64(3000000) THEN base_table.fmp ELSE NULL END)@8 as fmp_state, max(CASE WHEN base_table.fmp > Int64(0) AND base_table.fmp < Int64(3000000) THEN base_table.fmp ELSE NULL END)@9 as max_fmp, min(CASE WHEN base_table.fmp > Int64(0) AND base_table.fmp < Int64(3000000) THEN base_table.fmp ELSE NULL END)@10 as min_fmp, uddsketch_state(Int64(128),Float64(0.01),CASE WHEN base_table.fcp > Int64(0) AND base_table.fcp < Int64(3000000) THEN base_table.fcp ELSE NULL END)@11 as fcp_state, max(CASE WHEN base_table.fcp > Int64(0) AND base_table.fcp < Int64(3000000) THEN base_table.fcp ELSE NULL END)@12 as max_fcp, min(CASE WHEN base_table.fcp > Int64(0) AND base_table.fcp < Int64(3000000) THEN base_table.fcp ELSE NULL END)@13 as min_fcp, uddsketch_state(Int64(128),Float64(0.01),CASE WHEN base_table.fp > Int64(0) AND base_table.fp < Int64(3000000) THEN base_table.fp ELSE NULL END)@14 as fp_state, max(CASE WHEN base_table.fp > Int64(0) AND base_table.fp < Int64(3000000) THEN base_table.fp ELSE NULL END)@15 as max_fp, min(CASE WHEN base_table.fp > Int64(0) AND base_table.fp < Int64(3000000) THEN base_table.fp ELSE NULL END)@16 as min_fp, uddsketch_state(Int64(128),Float64(0.01),CASE WHEN base_table.tti > Int64(0) AND base_table.tti < Int64(3000000) THEN base_table.tti ELSE NULL END)@17 as tti_state, max(CASE WHEN base_table.tti > Int64(0) AND base_table.tti < Int64(3000000) THEN base_table.tti ELSE NULL END)@18 as max_tti, min(CASE WHEN base_table.tti > Int64(0) AND base_table.tti < Int64(3000000) THEN base_table.tti ELSE NULL END)@19 as min_tti, uddsketch_state(Int64(128),Float64(0.01),CASE WHEN base_table.fid > Int64(0) AND base_table.fid < Int64(3000000) THEN base_table.fid ELSE NULL END)@20 as fid_state, max(CASE WHEN base_table.fid > Int64(0) AND base_table.fid < Int64(3000000) THEN base_table.fid ELSE NULL END)@21 as max_fid, min(CASE WHEN base_table.fid > Int64(0) AND base_table.fid < Int64(3000000) THEN base_table.fid ELSE NULL END)@22 as min_fid, max(base_table.shard_key)@23 as shard_key, arrow_cast(date_bin(Utf8("60 seconds"),base_table.time),Utf8("Timestamp(s)"))@4 as arrow_cast(date_bin(Utf8("60 seconds"),base_table.time),Utf8("Timestamp(s)"))]_|
|_|_AggregateExec: mode=FinalPartitioned, gby=[env@0 as env, service_name@1 as service_name, city@2 as city, page@3 as page, arrow_cast(date_bin(Utf8("60 seconds"),base_table.time),Utf8("Timestamp(s)"))@4 as arrow_cast(date_bin(Utf8("60 seconds"),base_table.time),Utf8("Timestamp(s)"))], aggr=[uddsketch_state(Int64(128),Float64(0.01),CASE WHEN base_table.lcp > Int64(0) AND base_table.lcp < Int64(3000000) THEN base_table.lcp ELSE NULL END), max(CASE WHEN base_table.lcp > Int64(0) AND base_table.lcp < Int64(3000000) THEN base_table.lcp ELSE NULL END), min(CASE WHEN base_table.lcp > Int64(0) AND base_table.lcp < Int64(3000000) THEN base_table.lcp ELSE NULL END), uddsketch_state(Int64(128),Float64(0.01),CASE WHEN base_table.fmp > Int64(0) AND base_table.fmp < Int64(3000000) THEN base_table.fmp ELSE NULL END), max(CASE WHEN base_table.fmp > Int64(0) AND base_table.fmp < Int64(3000000) THEN base_table.fmp ELSE NULL END), min(CASE WHEN base_table.fmp > Int64(0) AND base_table.fmp < Int64(3000000) THEN base_table.fmp ELSE NULL END), uddsketch_state(Int64(128),Float64(0.01),CASE WHEN base_table.fcp > Int64(0) AND base_table.fcp < Int64(3000000) THEN base_table.fcp ELSE NULL END), max(CASE WHEN base_table.fcp > Int64(0) AND base_table.fcp < Int64(3000000) THEN base_table.fcp ELSE NULL END), min(CASE WHEN base_table.fcp > Int64(0) AND base_table.fcp < Int64(3000000) THEN base_table.fcp ELSE NULL END), uddsketch_state(Int64(128),Float64(0.01),CASE WHEN base_table.fp > Int64(0) AND base_table.fp < Int64(3000000) THEN base_table.fp ELSE NULL END), max(CASE WHEN base_table.fp > Int64(0) AND base_table.fp < Int64(3000000) THEN base_table.fp ELSE NULL END), min(CASE WHEN base_table.fp > Int64(0) AND base_table.fp < Int64(3000000) THEN base_table.fp ELSE NULL END), uddsketch_state(Int64(128),Float64(0.01),CASE WHEN base_table.tti > Int64(0) AND base_table.tti < Int64(3000000) THEN base_table.tti ELSE NULL END), max(CASE WHEN base_table.tti > Int64(0) AND base_table.tti < Int64(3000000) THEN base_table.tti ELSE NULL END), min(CASE WHEN base_table.tti > Int64(0) AND base_table.tti < Int64(3000000) THEN base_table.tti ELSE NULL END), uddsketch_state(Int64(128),Float64(0.01),CASE WHEN base_table.fid > Int64(0) AND base_table.fid < Int64(3000000) THEN base_table.fid ELSE NULL END), max(CASE WHEN base_table.fid > Int64(0) AND base_table.fid < Int64(3000000) THEN base_table.fid ELSE NULL END), min(CASE WHEN base_table.fid > Int64(0) AND base_table.fid < Int64(3000000) THEN base_table.fid ELSE NULL END), max(base_table.shard_key)]_|
@@ -597,7 +597,7 @@ where
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| Aggregate: groupBy=[[]], aggr=[[__count_state(base_table.time)]]_|
|_|_Filter: base_table.time >= TimestampMillisecond(0, None)_|
|_|_TableScan: base_table_|
|_|_TableScan: base_table, partial_filters=[base_table.time >= TimestampMillisecond(0, None)]_|
|_| ]]_|
| physical_plan | ProjectionExec: expr=[count(Int64(1))@0 as count(*)]_|
|_|_AggregateExec: mode=Final, gby=[], aggr=[count(Int64(1))]_|

View File

@@ -15,12 +15,14 @@ EXPLAIN SELECT * FROM integers WHERE i IN ((SELECT i FROM integers)) ORDER BY i;
| logical_plan_| Sort: integers.i ASC NULLS LAST_|
|_|_LeftSemi Join: integers.i = __correlated_sq_1.i_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: integers_|
|_| Filter: integers.i IS NOT NULL_|
|_|_TableScan: integers, partial_filters=[integers.i IS NOT NULL]_|
|_| ]]_|
|_|_SubqueryAlias: __correlated_sq_1_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| Projection: integers.i_|
|_|_TableScan: integers_|
|_| SubqueryAlias: __correlated_sq_1_|
|_|_Projection: integers.i_|
|_|_Filter: integers.i IS NOT NULL_|
|_|_TableScan: integers, partial_filters=[integers.i IS NOT NULL]_|
|_| ]]_|
| physical_plan | SortPreservingMergeExec: [i@0 ASC NULLS LAST]_|
|_|_SortExec: expr=[i@0 ASC NULLS LAST], preserve_partitioning=[true]_|
@@ -46,12 +48,14 @@ EXPLAIN SELECT * FROM integers i1 WHERE EXISTS(SELECT i FROM integers WHERE i=i1
|_|_LeftSemi Join: i1.i = __correlated_sq_1.i_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| SubqueryAlias: i1_|
|_|_TableScan: integers_|
|_|_Filter: integers.i IS NOT NULL_|
|_|_TableScan: integers, partial_filters=[integers.i IS NOT NULL]_|
|_| ]]_|
|_|_SubqueryAlias: __correlated_sq_1_|
|_|_Projection: integers.i_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: integers_|
|_| SubqueryAlias: __correlated_sq_1_|
|_|_Projection: integers.i_|
|_|_Filter: integers.i IS NOT NULL_|
|_|_TableScan: integers, partial_filters=[integers.i IS NOT NULL]_|
|_| ]]_|
| physical_plan | SortPreservingMergeExec: [i@0 ASC NULLS LAST]_|
|_|_SortExec: expr=[i@0 ASC NULLS LAST], preserve_partitioning=[true]_|
@@ -59,7 +63,6 @@ EXPLAIN SELECT * FROM integers i1 WHERE EXISTS(SELECT i FROM integers WHERE i=i1
|_|_RepartitionExec: partitioning=REDACTED
|_|_MergeScanExec: REDACTED
|_|_RepartitionExec: partitioning=REDACTED
|_|_ProjectionExec: expr=[i@0 as i]_|
|_|_MergeScanExec: REDACTED
|_|_|
+-+-+
@@ -86,10 +89,10 @@ order by t.i desc;
| logical_plan_| Sort: t.i DESC NULLS FIRST_|
|_|_SubqueryAlias: t_|
|_|_Cross Join:_|
|_|_Filter: integers.i IS NOT NULL_|
|_|_Projection: integers.i_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: integers_|
|_| Filter: integers.i IS NOT NULL AND Boolean(true)_|
|_|_TableScan: integers, partial_filters=[integers.i IS NOT NULL, Boolean(true)] |
|_| ]]_|
|_|_Projection:_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
@@ -99,7 +102,6 @@ order by t.i desc;
|_|_SortExec: expr=[i@0 DESC], preserve_partitioning=[true]_|
|_|_CrossJoinExec_|
|_|_CoalescePartitionsExec_|
|_|_FilterExec: i@0 IS NOT NULL_|
|_|_ProjectionExec: expr=[i@0 as i]_|
|_|_MergeScanExec: REDACTED
|_|_ProjectionExec: expr=[]_|
@@ -125,11 +127,11 @@ EXPLAIN INSERT INTO other SELECT i, 2 FROM integers WHERE i=(SELECT MAX(i) FROM
| | MergeScan [is_placeholder=false, remote_input=[ |
| | TableScan: integers |
| | ]] |
| | SubqueryAlias: __scalar_sq_1 |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: max(integers.i) |
| | Aggregate: groupBy=[[]], aggr=[[max(integers.i)]] |
| | TableScan: integers |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: __scalar_sq_1 |
| | Projection: max(integers.i) |
| | Aggregate: groupBy=[[]], aggr=[[max(integers.i)]] |
| | TableScan: integers, partial_filters=[Boolean(true)] |
| | ]] |
| physical_plan_error | This feature is not implemented: Insert into not implemented for this table |
+---------------------+-----------------------------------------------------------------------------+
@@ -165,18 +167,19 @@ EXPLAIN SELECT * FROM integers i1 WHERE EXISTS(SELECT i FROM integers WHERE i=i1
|_|_LeftSemi Join: i1.i = __correlated_sq_1.i_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| SubqueryAlias: i1_|
|_|_TableScan: integers_|
|_|_Filter: integers.i IS NOT NULL_|
|_|_TableScan: integers, partial_filters=[integers.i IS NOT NULL]_|
|_| ]]_|
|_|_SubqueryAlias: __correlated_sq_1_|
|_|_Projection: integers.i_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: integers_|
|_| SubqueryAlias: __correlated_sq_1_|
|_|_Projection: integers.i_|
|_|_Filter: integers.i IS NOT NULL_|
|_|_TableScan: integers, partial_filters=[integers.i IS NOT NULL]_|
|_| ]]_|
| physical_plan | SortPreservingMergeExec: [i@0 ASC NULLS LAST]_|
|_|_SortExec: expr=[i@0 ASC NULLS LAST], preserve_partitioning=[true]_|
|_|_REDACTED
|_|_MergeScanExec: REDACTED
|_|_ProjectionExec: expr=[i@0 as i]_|
|_|_MergeScanExec: REDACTED
|_|_|
+-+-+
@@ -195,20 +198,22 @@ EXPLAIN SELECT * FROM integers i1 WHERE EXISTS(SELECT count(i) FROM integers WHE
|_|_LeftSemi Join: i1.i = __correlated_sq_1.i_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| SubqueryAlias: i1_|
|_|_TableScan: integers_|
|_|_Filter: integers.i IS NOT NULL_|
|_|_TableScan: integers, partial_filters=[integers.i IS NOT NULL]_|
|_| ]]_|
|_|_SubqueryAlias: __correlated_sq_1_|
|_|_Aggregate: groupBy=[[integers.i]], aggr=[[]]_|
|_|_Projection: integers.i_|
|_|_Projection: __correlated_sq_1.i_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: integers_|
|_| SubqueryAlias: __correlated_sq_1_|
|_|_Projection: count(integers.i), integers.i_|
|_|_Aggregate: groupBy=[[integers.i]], aggr=[[count(integers.i)]]_|
|_|_Filter: integers.i IS NOT NULL_|
|_|_TableScan: integers, partial_filters=[integers.i IS NOT NULL]_|
|_| ]]_|
| physical_plan | SortPreservingMergeExec: [i@0 ASC NULLS LAST]_|
|_|_SortExec: expr=[i@0 ASC NULLS LAST], preserve_partitioning=[true]_|
|_|_REDACTED
|_|_MergeScanExec: REDACTED
|_|_AggregateExec: mode=SinglePartitioned, gby=[i@0 as i], aggr=[]_|
|_|_ProjectionExec: expr=[i@0 as i]_|
|_|_ProjectionExec: expr=[i@1 as i]_|
|_|_MergeScanExec: REDACTED
|_|_|
+-+-+
@@ -543,12 +548,14 @@ ORDER BY u1.a;
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| SubqueryAlias: u1_|
|_|_Projection: t1.a_|
|_|_TableScan: t1_|
|_|_Filter: t1.a IS NOT NULL_|
|_|_TableScan: t1, partial_filters=[t1.a IS NOT NULL]_|
|_| ]]_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| SubqueryAlias: u2_|
|_|_Projection: t2.a_|
|_|_TableScan: t2_|
|_|_Filter: t2.a IS NOT NULL_|
|_|_TableScan: t2, partial_filters=[t2.a IS NOT NULL]_|
|_| ]]_|
| physical_plan | SortPreservingMergeExec: [a@0 ASC NULLS LAST]_|
|_|_SortExec: expr=[a@0 ASC NULLS LAST], preserve_partitioning=[true]_|

View File

@@ -100,31 +100,30 @@ TQL EXPLAIN (
)
);
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | HistogramFold: le=le, field=sum(prom_avg_over_time(ts_range,v)), quantile=0.5 |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Sort: test_tsid.le ASC NULLS LAST, test_tsid.tag4 ASC NULLS LAST, test_tsid.tag5 ASC NULLS LAST, test_tsid.ts ASC NULLS LAST |
| | Aggregate: groupBy=[[test_tsid.le, test_tsid.tag4, test_tsid.tag5, test_tsid.ts]], aggr=[[sum(prom_avg_over_time(ts_range,v))]] |
| | Filter: prom_avg_over_time(ts_range,v) IS NOT NULL |
| | Projection: test_tsid.ts, prom_avg_over_time(ts_range, v) AS prom_avg_over_time(ts_range,v), test_tsid.le, test_tsid.tag1, test_tsid.tag2, test_tsid.tag4, test_tsid.tag5, test_tsid.tag6, test_tsid.tag7, test_tsid.tag8, test_tsid.__tsid |
| | PromRangeManipulate: req range=[1769139000000..1769139900000], interval=[60000], eval range=[1800000], time index=[ts], values=["v"] |
| | PromSeriesNormalize: offset=[0], time index=[ts], filter NaN: [true] |
| | PromSeriesDivide: tags=["__tsid"] |
| | Sort: test_tsid.__tsid ASC NULLS FIRST, test_tsid.ts ASC NULLS FIRST |
| | Filter: test_tsid.ts >= TimestampMillisecond(1769137200001, None) AND test_tsid.ts <= TimestampMillisecond(1769139900000, None) |
| | Projection: test_tsid.v, test_tsid.le, test_tsid.tag1, test_tsid.tag2, test_tsid.tag4, test_tsid.tag5, test_tsid.tag6, test_tsid.tag7, test_tsid.tag8, test_tsid.__tsid, test_tsid.ts |
| | SubqueryAlias: test_tsid |
| | Filter: phy.__table_id=UInt32(REDACTED) |
| | TableScan: phy projection=[ts, v, tag1, tag2, le, tag4, tag5, tag6, tag7, tag8, __table_id, __tsid] |
| | ]] |
| physical_plan | HistogramFoldExec: le=@0, field=@4, quantile=0.5 |
| | SortExec: expr=[tag4@1 ASC NULLS LAST, tag5@2 ASC NULLS LAST, ts@3 ASC NULLS LAST, CAST(le@0 AS Float64) ASC NULLS LAST], preserve_partitioning=[true] |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | HistogramFold: le=le, field=sum(prom_avg_over_time(ts_range,v)), quantile=0.5 |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Sort: test_tsid.le ASC NULLS LAST, test_tsid.tag4 ASC NULLS LAST, test_tsid.tag5 ASC NULLS LAST, test_tsid.ts ASC NULLS LAST |
| | Aggregate: groupBy=[[test_tsid.le, test_tsid.tag4, test_tsid.tag5, test_tsid.ts]], aggr=[[sum(prom_avg_over_time(ts_range,v))]] |
| | Filter: prom_avg_over_time(ts_range,v) IS NOT NULL |
| | Projection: test_tsid.ts, prom_avg_over_time(ts_range, v) AS prom_avg_over_time(ts_range,v), test_tsid.le, test_tsid.tag1, test_tsid.tag2, test_tsid.tag4, test_tsid.tag5, test_tsid.tag6, test_tsid.tag7, test_tsid.tag8, test_tsid.__tsid |
| | PromRangeManipulate: req range=[1769139000000..1769139900000], interval=[60000], eval range=[1800000], time index=[ts], values=["v"] |
| | PromSeriesNormalize: offset=[0], time index=[ts], filter NaN: [true] |
| | PromSeriesDivide: tags=["__tsid"] |
| | Sort: test_tsid.__tsid ASC NULLS FIRST, test_tsid.ts ASC NULLS FIRST |
| | Projection: test_tsid.v, test_tsid.le, test_tsid.tag1, test_tsid.tag2, test_tsid.tag4, test_tsid.tag5, test_tsid.tag6, test_tsid.tag7, test_tsid.tag8, test_tsid.__tsid, test_tsid.ts |
| | SubqueryAlias: test_tsid |
| | Filter: phy.ts >= TimestampMillisecond(1769137200001, None) AND phy.ts <= TimestampMillisecond(1769139900000, None) AND phy.__table_id=UInt32(REDACTED) |
| | TableScan: phy projection=[ts, v, tag1, tag2, le, tag4, tag5, tag6, tag7, tag8, __table_id, __tsid], partial_filters=[phy.ts >= TimestampMillisecond(1769137200001, None), phy.ts <= TimestampMillisecond(1769139900000, None), phy.__table_id=UInt32(REDACTED)] |
| | ]] |
| physical_plan | HistogramFoldExec: le=@0, field=@4, quantile=0.5 |
| | SortExec: expr=[tag4@1 ASC NULLS LAST, tag5@2 ASC NULLS LAST, ts@3 ASC NULLS LAST, CAST(le@0 AS Float64) ASC NULLS LAST], preserve_partitioning=[true] |
| | RepartitionExec: REDACTED
| | MergeScanExec: REDACTED
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
CREATE FLOW IF NOT EXISTS test_tsid
SINK TO 'test_tsid_output'

View File

@@ -42,22 +42,22 @@ WHERE c.tier = 'gold';
| | Projection: o.id, o.customer_id, o.amount |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: o |
| | TableScan: orders |
| | Filter: orders.customer_id IS NOT NULL |
| | TableScan: orders, partial_filters=[orders.customer_id IS NOT NULL] |
| | ]] |
| | Filter: c.tier = Utf8("gold") |
| | Projection: c.customer_id, c.name, c.tier |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: c.customer_id, c.name, c.tier |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: c |
| | TableScan: customers |
| | Filter: customers.customer_id IS NOT NULL AND customers.tier = Utf8("gold") |
| | TableScan: customers, partial_filters=[customers.tier = Utf8("gold"), customers.customer_id IS NOT NULL] |
| | ]] |
| physical_plan | HashJoinExec: mode=Partitioned, join_type=Inner, on=[(customer_id@1, customer_id@0)], projection=[id@0, amount@2, name@4, tier@5] |
| | RepartitionExec: REDACTED
| | ProjectionExec: expr=[id@0 as id, customer_id@1 as customer_id, amount@2 as amount] |
| | MergeScanExec: REDACTED
| | RepartitionExec: REDACTED
| | FilterExec: tier@2 = gold |
| | ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] |
| | MergeScanExec: REDACTED
| | ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] |
| | MergeScanExec: REDACTED
| | |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------+
@@ -91,15 +91,16 @@ WHERE c.tier = 'gold';
|_|_|_ProjectionExec: expr=[id@0 as id, customer_id@1 as customer_id, amount@2 as amount] metrics=REDACTED_|
|_|_|_MergeScanExec: REDACTED
|_|_|_RepartitionExec: partitioning=REDACTED
|_|_|_FilterExec: tier@2 = gold metrics=REDACTED_|
|_|_|_ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] metrics=REDACTED_|
|_|_|_MergeScanExec: REDACTED
|_|_|_|
| 1_| 0_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["id", "customer_id", "amount", "ts"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
| 1_| 0_|_FilterExec: customer_id@1 IS NOT NULL metrics=REDACTED_|
|_|_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["id", "customer_id", "amount", "ts"], "filters": ["customer_id IS NOT NULL"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
|_|_|_|
| 1_| 0_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["customer_id", "name", "tier", "ts"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
| 1_| 0_|_FilterExec: customer_id@0 IS NOT NULL AND tier@2 = gold metrics=REDACTED_|
|_|_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["customer_id", "name", "tier", "ts"], "filters": ["tier = Utf8(\"gold\")", "customer_id IS NOT NULL"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
|_|_|_|
|_|_| Total rows: REDACTED_|
+-+-+-+
@@ -160,33 +161,33 @@ FROM (SELECT "id", customer_id as cid, amount, ts FROM orders) o
JOIN customers c ON o.cid = c.customer_id
WHERE c.tier IN ('gold', 'silver');
+---------------+---------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------------+
| logical_plan | Projection: o.id, o.amount, c.name, c.tier |
| | Inner Join: o.cid = c.customer_id |
| | Projection: o.id, o.cid, o.amount |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: o |
| | Projection: orders.id, orders.customer_id AS cid, orders.amount, orders.ts |
| | TableScan: orders |
| | ]] |
| | Filter: c.tier = Utf8("gold") OR c.tier = Utf8("silver") |
| | Projection: c.customer_id, c.name, c.tier |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: c |
| | TableScan: customers |
| | ]] |
| physical_plan | HashJoinExec: mode=Partitioned, join_type=Inner, on=[(cid@1, customer_id@0)], projection=[id@0, amount@2, name@4, tier@5] |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | Projection: o.id, o.amount, c.name, c.tier |
| | Inner Join: o.cid = c.customer_id |
| | Projection: o.id, o.cid, o.amount |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: o |
| | Projection: orders.id, orders.customer_id AS cid, orders.amount, orders.ts |
| | Filter: orders.customer_id IS NOT NULL |
| | TableScan: orders, partial_filters=[orders.customer_id IS NOT NULL] |
| | ]] |
| | Projection: c.customer_id, c.name, c.tier |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: c |
| | Filter: customers.customer_id IS NOT NULL AND (customers.tier = Utf8("gold") OR customers.tier = Utf8("silver")) |
| | TableScan: customers, partial_filters=[customers.tier = Utf8("gold") OR customers.tier = Utf8("silver"), customers.customer_id IS NOT NULL] |
| | ]] |
| physical_plan | HashJoinExec: mode=Partitioned, join_type=Inner, on=[(cid@1, customer_id@0)], projection=[id@0, amount@2, name@4, tier@5] |
| | RepartitionExec: REDACTED
| | ProjectionExec: expr=[id@0 as id, cid@1 as cid, amount@2 as amount] |
| | ProjectionExec: expr=[id@0 as id, cid@1 as cid, amount@2 as amount] |
| | MergeScanExec: REDACTED
| | RepartitionExec: REDACTED
| | FilterExec: tier@2 = gold OR tier@2 = silver |
| | ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] |
| | MergeScanExec: REDACTED
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------------+
| | ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] |
| | MergeScanExec: REDACTED
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------+
-- SQLNESS REPLACE ("metrics_per_partition":\s*.*metrics=) "metrics_per_partition": REDACTED metrics=
-- SQLNESS REPLACE (metrics=\{.*\}) metrics=REDACTED
@@ -218,16 +219,17 @@ WHERE c.tier IN ('gold', 'silver');
|_|_|_ProjectionExec: expr=[id@0 as id, cid@1 as cid, amount@2 as amount] metrics=REDACTED_|
|_|_|_MergeScanExec: REDACTED
|_|_|_RepartitionExec: partitioning=REDACTED
|_|_|_FilterExec: tier@2 = gold OR tier@2 = silver metrics=REDACTED_|
|_|_|_ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] metrics=REDACTED_|
|_|_|_MergeScanExec: REDACTED
|_|_|_|
| 1_| 0_|_ProjectionExec: expr=[id@0 as id, customer_id@1 as cid, amount@2 as amount, ts@3 as ts] metrics=REDACTED_|
|_|_|_FilterExec: customer_id@1 IS NOT NULL metrics=REDACTED_|
|_|_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["id", "customer_id", "amount", "ts"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["id", "customer_id", "amount", "ts"], "filters": ["customer_id IS NOT NULL"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
|_|_|_|
| 1_| 0_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["customer_id", "name", "tier", "ts"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
| 1_| 0_|_FilterExec: customer_id@0 IS NOT NULL AND (tier@2 = gold OR tier@2 = silver) metrics=REDACTED_|
|_|_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["customer_id", "name", "tier", "ts"], "filters": ["tier = Utf8(\"gold\") OR tier = Utf8(\"silver\")", "customer_id IS NOT NULL"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
|_|_|_|
|_|_| Total rows: REDACTED_|
+-+-+-+

View File

@@ -47,35 +47,35 @@ FROM (
JOIN customers c ON top_orders.customer_id = c.customer_id
WHERE c.tier IN ('gold', 'bronze');
+---------------+-----------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | Projection: top_orders.id, top_orders.amount, c.name, c.tier |
| | Inner Join: top_orders.customer_id = c.customer_id |
| | Projection: top_orders.id, top_orders.customer_id, top_orders.amount |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: top_orders |
| | Limit: skip=0, fetch=5 |
| | Sort: orders.amount DESC NULLS FIRST |
| | Projection: orders.id, orders.customer_id, orders.amount, orders.ts |
| | TableScan: orders |
| | ]] |
| | Filter: c.tier = Utf8("gold") OR c.tier = Utf8("bronze") |
| | Projection: c.customer_id, c.name, c.tier |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: c |
| | TableScan: customers |
| | ]] |
| physical_plan | HashJoinExec: mode=Partitioned, join_type=Inner, on=[(customer_id@1, customer_id@0)], projection=[id@0, amount@2, name@4, tier@5] |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | Projection: top_orders.id, top_orders.amount, c.name, c.tier |
| | Inner Join: top_orders.customer_id = c.customer_id |
| | Projection: top_orders.id, top_orders.customer_id, top_orders.amount |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: top_orders |
| | Filter: orders.customer_id IS NOT NULL |
| | Limit: skip=0, fetch=5 |
| | Sort: orders.amount DESC NULLS FIRST |
| | Projection: orders.id, orders.customer_id, orders.amount, orders.ts |
| | TableScan: orders |
| | ]] |
| | Projection: c.customer_id, c.name, c.tier |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: c |
| | Filter: customers.customer_id IS NOT NULL AND (customers.tier = Utf8("gold") OR customers.tier = Utf8("bronze")) |
| | TableScan: customers, partial_filters=[customers.tier = Utf8("gold") OR customers.tier = Utf8("bronze"), customers.customer_id IS NOT NULL] |
| | ]] |
| physical_plan | HashJoinExec: mode=Partitioned, join_type=Inner, on=[(customer_id@1, customer_id@0)], projection=[id@0, amount@2, name@4, tier@5] |
| | RepartitionExec: partitioning=REDACTED
| | ProjectionExec: expr=[id@0 as id, customer_id@1 as customer_id, amount@2 as amount] |
| | ProjectionExec: expr=[id@0 as id, customer_id@1 as customer_id, amount@2 as amount] |
| | MergeScanExec: REDACTED
| | RepartitionExec: partitioning=REDACTED
| | FilterExec: tier@2 = gold OR tier@2 = bronze |
| | ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] |
| | MergeScanExec: REDACTED
| | |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------+
| | ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] |
| | MergeScanExec: REDACTED
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------+
-- SQLNESS REPLACE ("metrics_per_partition":\s*.*metrics=) "metrics_per_partition": REDACTED metrics=
-- SQLNESS REPLACE (metrics=\{.*\}) metrics=REDACTED
@@ -112,16 +112,18 @@ WHERE c.tier IN ('gold', 'bronze');
|_|_|_ProjectionExec: expr=[id@0 as id, customer_id@1 as customer_id, amount@2 as amount] metrics=REDACTED_|
|_|_|_MergeScanExec: REDACTED
|_|_|_RepartitionExec: partitioning=REDACTED
|_|_|_FilterExec: tier@2 = gold OR tier@2 = bronze metrics=REDACTED_|
|_|_|_ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] metrics=REDACTED_|
|_|_|_MergeScanExec: REDACTED
|_|_|_|
| 1_| 0_|_SortPreservingMergeExec: [amount@2 DESC], fetch=5 metrics=REDACTED_|
| 1_| 0_|_FilterExec: customer_id@1 IS NOT NULL metrics=REDACTED_|
|_|_|_RepartitionExec: partitioning=REDACTED
|_|_|_SortPreservingMergeExec: [amount@2 DESC], fetch=5 metrics=REDACTED_|
|_|_|_SortExec: TopK(fetch=5), expr=[amount@2 DESC], preserve_partitioning=[true] metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["id", "customer_id", "amount", "ts"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
|_|_|_|
| 1_| 0_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["customer_id", "name", "tier", "ts"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
| 1_| 0_|_FilterExec: customer_id@0 IS NOT NULL AND (tier@2 = gold OR tier@2 = bronze) metrics=REDACTED_|
|_|_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["customer_id", "name", "tier", "ts"], "filters": ["tier = Utf8(\"gold\") OR tier = Utf8(\"bronze\")", "customer_id IS NOT NULL"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
|_|_|_|
|_|_| Total rows: REDACTED_|
+-+-+-+
@@ -211,13 +213,14 @@ LIMIT 4;
| | Projection: o.id, o.customer_id, o.amount |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: o |
| | TableScan: orders |
| | Filter: orders.customer_id IS NOT NULL |
| | TableScan: orders, partial_filters=[orders.customer_id IS NOT NULL] |
| | ]] |
| | Filter: c.tier = Utf8("gold") OR c.tier = Utf8("silver") |
| | Projection: c.customer_id, c.name, c.tier |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: c.customer_id, c.name, c.tier |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: c |
| | TableScan: customers |
| | Filter: customers.customer_id IS NOT NULL AND (customers.tier = Utf8("gold") OR customers.tier = Utf8("silver")) |
| | TableScan: customers, partial_filters=[customers.tier = Utf8("gold") OR customers.tier = Utf8("silver"), customers.customer_id IS NOT NULL] |
| | ]] |
| physical_plan | SortPreservingMergeExec: [amount@4 DESC], fetch=4 |
| | SortExec: TopK(fetch=4), expr=[amount@4 DESC], preserve_partitioning=[true] |
@@ -227,9 +230,8 @@ LIMIT 4;
| | ProjectionExec: expr=[id@0 as id, customer_id@1 as customer_id, amount@2 as amount] |
| | MergeScanExec: REDACTED
| | RepartitionExec: partitioning=REDACTED
| | FilterExec: tier@2 = gold OR tier@2 = silver |
| | ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] |
| | MergeScanExec: REDACTED
| | ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] |
| | MergeScanExec: REDACTED
| | |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
@@ -271,15 +273,16 @@ LIMIT 4;
|_|_|_ProjectionExec: expr=[id@0 as id, customer_id@1 as customer_id, amount@2 as amount] metrics=REDACTED_|
|_|_|_MergeScanExec: REDACTED
|_|_|_RepartitionExec: partitioning=REDACTED
|_|_|_FilterExec: tier@2 = gold OR tier@2 = silver metrics=REDACTED_|
|_|_|_ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] metrics=REDACTED_|
|_|_|_MergeScanExec: REDACTED
|_|_|_|
| 1_| 0_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["id", "customer_id", "amount", "ts"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
| 1_| 0_|_FilterExec: customer_id@1 IS NOT NULL metrics=REDACTED_|
|_|_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["id", "customer_id", "amount", "ts"], "filters": ["customer_id IS NOT NULL"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
|_|_|_|
| 1_| 0_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["customer_id", "name", "tier", "ts"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
| 1_| 0_|_FilterExec: customer_id@0 IS NOT NULL AND (tier@2 = gold OR tier@2 = silver) metrics=REDACTED_|
|_|_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["customer_id", "name", "tier", "ts"], "filters": ["tier = Utf8(\"gold\") OR tier = Utf8(\"silver\")", "customer_id IS NOT NULL"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
|_|_|_|
|_|_| Total rows: REDACTED_|
+-+-+-+
@@ -377,19 +380,20 @@ WHERE c.tier IN ('gold', 'silver')
| | Projection: o.id, o.customer_id, o.product_id, o.amount |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: o |
| | TableScan: orders |
| | Filter: orders.product_id IS NOT NULL AND orders.customer_id IS NOT NULL |
| | TableScan: orders, partial_filters=[orders.product_id IS NOT NULL, orders.customer_id IS NOT NULL] |
| | ]] |
| | Filter: c.tier = Utf8("gold") OR c.tier = Utf8("silver") |
| | Projection: c.customer_id, c.name, c.tier |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: c.customer_id, c.name, c.tier |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: c |
| | TableScan: customers |
| | Filter: customers.customer_id IS NOT NULL AND (customers.tier = Utf8("gold") OR customers.tier = Utf8("silver")) |
| | TableScan: customers, partial_filters=[customers.tier = Utf8("gold") OR customers.tier = Utf8("silver"), customers.customer_id IS NOT NULL] |
| | ]] |
| | Filter: p.category = Utf8("electronics") |
| | Projection: p.product_id, p.name, p.category |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: p.product_id, p.name, p.category |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: p |
| | TableScan: products |
| | Filter: products.product_id IS NOT NULL AND products.category = Utf8("electronics") |
| | TableScan: products, partial_filters=[products.category = Utf8("electronics"), products.product_id IS NOT NULL] |
| | ]] |
| physical_plan | ProjectionExec: expr=[id@0 as id, amount@1 as amount, name@2 as name, tier@3 as tier, name@4 as product_name, category@5 as category] |
| | HashJoinExec: mode=Partitioned, join_type=Inner, on=[(product_id@1, product_id@0)], projection=[id@0, amount@2, name@3, tier@4, name@6, category@7] |
@@ -399,13 +403,11 @@ WHERE c.tier IN ('gold', 'silver')
| | ProjectionExec: expr=[id@0 as id, customer_id@1 as customer_id, product_id@2 as product_id, amount@3 as amount] |
| | MergeScanExec: REDACTED
| | RepartitionExec: partitioning=REDACTED
| | FilterExec: tier@2 = gold OR tier@2 = silver |
| | ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] |
| | MergeScanExec: REDACTED
| | ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] |
| | MergeScanExec: REDACTED
| | RepartitionExec: partitioning=REDACTED
| | FilterExec: category@2 = electronics |
| | ProjectionExec: expr=[product_id@0 as product_id, name@1 as name, category@2 as category] |
| | MergeScanExec: REDACTED
| | ProjectionExec: expr=[product_id@0 as product_id, name@1 as name, category@2 as category] |
| | MergeScanExec: REDACTED
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
@@ -444,22 +446,23 @@ WHERE c.tier IN ('gold', 'silver')
|_|_|_ProjectionExec: expr=[id@0 as id, customer_id@1 as customer_id, product_id@2 as product_id, amount@3 as amount] metrics=REDACTED_|
|_|_|_MergeScanExec: REDACTED
|_|_|_RepartitionExec: partitioning=REDACTED
|_|_|_FilterExec: tier@2 = gold OR tier@2 = silver metrics=REDACTED_|
|_|_|_ProjectionExec: expr=[customer_id@0 as customer_id, name@1 as name, tier@2 as tier] metrics=REDACTED_|
|_|_|_MergeScanExec: REDACTED
|_|_|_RepartitionExec: partitioning=REDACTED
|_|_|_FilterExec: category@2 = electronics metrics=REDACTED_|
|_|_|_ProjectionExec: expr=[product_id@0 as product_id, name@1 as name, category@2 as category] metrics=REDACTED_|
|_|_|_MergeScanExec: REDACTED
|_|_|_|
| 1_| 0_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["id", "customer_id", "product_id", "amount", "ts"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
| 1_| 0_|_FilterExec: product_id@2 IS NOT NULL AND customer_id@1 IS NOT NULL metrics=REDACTED_|
|_|_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["id", "customer_id", "product_id", "amount", "ts"], "filters": ["product_id IS NOT NULL", "customer_id IS NOT NULL"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
|_|_|_|
| 1_| 0_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["customer_id", "name", "tier", "ts"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
| 1_| 0_|_FilterExec: customer_id@0 IS NOT NULL AND (tier@2 = gold OR tier@2 = silver) metrics=REDACTED_|
|_|_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["customer_id", "name", "tier", "ts"], "filters": ["tier = Utf8(\"gold\") OR tier = Utf8(\"silver\")", "customer_id IS NOT NULL"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
|_|_|_|
| 1_| 0_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["product_id", "name", "category", "ts"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
| 1_| 0_|_FilterExec: product_id@0 IS NOT NULL AND category@2 = electronics metrics=REDACTED_|
|_|_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: region=REDACTED, {"partition_count":REDACTED, "projection": ["product_id", "name", "category", "ts"], "filters": ["category = Utf8(\"electronics\")", "product_id IS NOT NULL"], "dyn_filters": ["DynamicFilter [ REDACTED ]"], "flat_format":REDACTED, "metrics_per_partition": REDACTED metrics=REDACTED_|
|_|_|_|
|_|_| Total rows: REDACTED_|
+-+-+-+

View File

@@ -147,10 +147,12 @@ LIMIT 1;
|_|_|_ProjectionExec: expr=[vec_id@0 as vec_id] metrics=REDACTED_|
|_|_|_MergeScanExec: REDACTED
|_|_|_|
| 1_| 0_|_CooperativeExec metrics=REDACTED_|
| 1_| 0_|_FilterExec: vec_id@0 IS NOT NULL metrics=REDACTED_|
|_|_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: REDACTED
|_|_|_|
| 1_| 0_|_CooperativeExec metrics=REDACTED_|
| 1_| 0_|_FilterExec: vec_id@0 IS NOT NULL metrics=REDACTED_|
|_|_|_CooperativeExec metrics=REDACTED_|
|_|_|_SeqScan: REDACTED
|_|_|_|
|_|_| Total rows: REDACTED_|

View File

@@ -16,26 +16,26 @@ tql explain (0, 100, '1s')
tag_a="ffa",
}[1h])[12h:1h];
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | PromRangeManipulate: req range=[0..100000], interval=[1000], eval range=[43200000], time index=[ts], values=["prom_increase(ts_range,val,ts,Int64(3600000))"] |
| | PromSeriesDivide: tags=["tag_a", "tag_b"] |
| | Sort: count_total.tag_a ASC NULLS FIRST, count_total.tag_b ASC NULLS FIRST, count_total.ts ASC NULLS FIRST |
| | Filter: prom_increase(ts_range,val,ts,Int64(3600000)) IS NOT NULL |
| | Projection: count_total.ts, prom_increase(ts_range, val, count_total.ts, Int64(3600000)) AS prom_increase(ts_range,val,ts,Int64(3600000)), count_total.tag_a, count_total.tag_b |
| | PromRangeManipulate: req range=[-39600000..100000], interval=[3600000], eval range=[3600000], time index=[ts], values=["val"] |
| | PromSeriesNormalize: offset=[0], time index=[ts], filter NaN: [true] |
| | PromSeriesDivide: tags=["tag_a", "tag_b"] |
| | Sort: count_total.tag_a ASC NULLS FIRST, count_total.tag_b ASC NULLS FIRST, count_total.ts ASC NULLS FIRST |
| | Filter: count_total.tag_a = Utf8("ffa") AND count_total.ts >= TimestampMillisecond(-43199999, None) AND count_total.ts <= TimestampMillisecond(100000, None) |
| | TableScan: count_total |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | PromRangeManipulate: req range=[0..100000], interval=[1000], eval range=[43200000], time index=[ts], values=["prom_increase(ts_range,val,ts,Int64(3600000))"] |
| | PromSeriesDivide: tags=["tag_a", "tag_b"] |
| | Sort: count_total.tag_a ASC NULLS FIRST, count_total.tag_b ASC NULLS FIRST, count_total.ts ASC NULLS FIRST |
| | Filter: prom_increase(ts_range,val,ts,Int64(3600000)) IS NOT NULL |
| | Projection: count_total.ts, prom_increase(ts_range, val, count_total.ts, Int64(3600000)) AS prom_increase(ts_range,val,ts,Int64(3600000)), count_total.tag_a, count_total.tag_b |
| | PromRangeManipulate: req range=[-39600000..100000], interval=[3600000], eval range=[3600000], time index=[ts], values=["val"] |
| | PromSeriesNormalize: offset=[0], time index=[ts], filter NaN: [true] |
| | PromSeriesDivide: tags=["tag_a", "tag_b"] |
| | Sort: count_total.tag_a ASC NULLS FIRST, count_total.tag_b ASC NULLS FIRST, count_total.ts ASC NULLS FIRST |
| | Filter: count_total.tag_a = Utf8("ffa") AND count_total.ts >= TimestampMillisecond(-43199999, None) AND count_total.ts <= TimestampMillisecond(100000, None) |
| | TableScan: count_total, partial_filters=[count_total.tag_a = Utf8("ffa"), count_total.ts >= TimestampMillisecond(-43199999, None), count_total.ts <= TimestampMillisecond(100000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
tql eval (0, 100, '1s')
increase(count_total{

View File

@@ -117,10 +117,7 @@ TQL ANALYZE VERBOSE (0, 0, '1s') test{host!~".*"};
+-+-+-+
| stage | node | plan_|
+-+-+-+
| 0_| 0_|_CooperativeExec REDACTED
|_|_|_MergeScanExec: REDACTED
|_|_|_|
| 1_| 0_|_PromInstantManipulateExec: range=[0..0], lookback=[300000], interval=[1000], time index=[ts] REDACTED
| 0_| 0_|_PromInstantManipulateExec: range=[0..0], lookback=[300000], interval=[1000], time index=[ts] REDACTED
|_|_|_RepartitionExec: partitioning=REDACTED
|_|_|_PromSeriesDivideExec: tags=["host"] REDACTED
|_|_|_SortExec: expr=[host@1 ASC, ts@0 ASC], preserve_partitioning=[false] REDACTED

View File

@@ -691,6 +691,19 @@ TQL EVAL (0, 5, '5s') rate(tsid_binary_join_left[5s]) / tsid_binary_join_left;
+------+-----+----+----------------------------------------------------------------------------+
+------+-----+----+----------------------------------------------------------------------------+
-- Regression for aggregating a binary expression over `time()` and a TSID-backed metric.
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (0, 5, '5s') sum by (host, job) (time() - tsid_binary_join_left);
+-------+------+---------------------+-------------------------------------------------------------------+
| host | job | ts | sum(.time / Float64(1000) - tsid_binary_join_left.greptime_value) |
+-------+------+---------------------+-------------------------------------------------------------------+
| host1 | job1 | 1970-01-01T00:00:00 | -12.0 |
| host1 | job1 | 1970-01-01T00:00:05 | -10.0 |
| host2 | job2 | 1970-01-01T00:00:00 | -18.0 |
| host2 | job2 | 1970-01-01T00:00:05 | -16.0 |
+-------+------+---------------------+-------------------------------------------------------------------+
DROP TABLE tsid_binary_join_third;
Affected Rows: 0

View File

@@ -252,6 +252,10 @@ TQL EVAL (0, 5, '5s') (tsid_binary_join_left or tsid_binary_join_right) / tsid_b
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (0, 5, '5s') rate(tsid_binary_join_left[5s]) / tsid_binary_join_left;
-- Regression for aggregating a binary expression over `time()` and a TSID-backed metric.
-- SQLNESS SORT_RESULT 3 1
TQL EVAL (0, 5, '5s') sum by (host, job) (time() - tsid_binary_join_left);
DROP TABLE tsid_binary_join_third;
DROP TABLE tsid_binary_join_right_by_job;
DROP TABLE tsid_binary_join_right;

View File

@@ -55,7 +55,7 @@ WHERE s."id"=e.sid
AND e.grade <= (SELECT AVG(e2.grade) - 1 FROM exams e2 WHERE s."id"=e2.sid OR (e2.curriculum=s.major AND s."year">=e2."year"))
ORDER BY "name", course;
Error: 1001(Unsupported), This feature is not implemented: Physical plan does not support logical expression ScalarSubquery(<subquery>)
Error: 3001(EngineExecuteQuery), Error during planning: Correlated scalar subquery can only be used in Projection, Filter, Aggregate plan nodes
-- Test 3: EXISTS subquery
SELECT "name", major

View File

@@ -12,20 +12,20 @@ Affected Rows: 3
-- SQLNESS REPLACE (peers.*) REDACTED
TQL EXPLAIN (0, 10, '5s') test;
+---------------+-------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j] |
| | PromSeriesDivide: tags=["k"] |
| | Sort: test.k ASC NULLS FIRST, test.j ASC NULLS FIRST |
| | Filter: test.j >= TimestampMillisecond(-299999, None) AND test.j <= TimestampMillisecond(10000, None) |
| | TableScan: test |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j] |
| | PromSeriesDivide: tags=["k"] |
| | Sort: test.k ASC NULLS FIRST, test.j ASC NULLS FIRST |
| | Filter: test.j >= TimestampMillisecond(-299999, None) AND test.j <= TimestampMillisecond(10000, None) |
| | TableScan: test, partial_filters=[test.j >= TimestampMillisecond(-299999, None), test.j <= TimestampMillisecond(10000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-------------------------------------------------------------------------------------------------------------+
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------+
-- 'lookback' parameter is not fully supported, the test has to be updated
-- explain at 0s, 5s and 10s. No point at 0s.
@@ -33,40 +33,40 @@ TQL EXPLAIN (0, 10, '5s') test;
-- SQLNESS REPLACE (peers.*) REDACTED
TQL EXPLAIN (0, 10, '1s', '2s') test;
+---------------+-----------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-----------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | PromInstantManipulate: range=[0..10000], lookback=[2000], interval=[1000], time index=[j] |
| | PromSeriesDivide: tags=["k"] |
| | Sort: test.k ASC NULLS FIRST, test.j ASC NULLS FIRST |
| | Filter: test.j >= TimestampMillisecond(-1999, None) AND test.j <= TimestampMillisecond(10000, None) |
| | TableScan: test |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | PromInstantManipulate: range=[0..10000], lookback=[2000], interval=[1000], time index=[j] |
| | PromSeriesDivide: tags=["k"] |
| | Sort: test.k ASC NULLS FIRST, test.j ASC NULLS FIRST |
| | Filter: test.j >= TimestampMillisecond(-1999, None) AND test.j <= TimestampMillisecond(10000, None) |
| | TableScan: test, partial_filters=[test.j >= TimestampMillisecond(-1999, None), test.j <= TimestampMillisecond(10000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-----------------------------------------------------------------------------------------------------------+
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------+
-- explain at 0s, 5s and 10s. No point at 0s.
-- SQLNESS REPLACE (RoundRobinBatch.*) REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
TQL EXPLAIN ('1970-01-01T00:00:00'::timestamp, '1970-01-01T00:00:00'::timestamp + '10 seconds'::interval, '5s') test;
+---------------+-------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j] |
| | PromSeriesDivide: tags=["k"] |
| | Sort: test.k ASC NULLS FIRST, test.j ASC NULLS FIRST |
| | Filter: test.j >= TimestampMillisecond(-299999, None) AND test.j <= TimestampMillisecond(10000, None) |
| | TableScan: test |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j] |
| | PromSeriesDivide: tags=["k"] |
| | Sort: test.k ASC NULLS FIRST, test.j ASC NULLS FIRST |
| | Filter: test.j >= TimestampMillisecond(-299999, None) AND test.j <= TimestampMillisecond(10000, None) |
| | TableScan: test, partial_filters=[test.j >= TimestampMillisecond(-299999, None), test.j <= TimestampMillisecond(10000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-------------------------------------------------------------------------------------------------------------+
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------+
-- explain verbose at 0s, 5s and 10s. No point at 0s.
-- SQLNESS REPLACE (-+) -
@@ -98,7 +98,7 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test;
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test.k ASC NULLS FIRST, test.j ASC NULLS FIRST_|
|_|_Filter: test.j >= TimestampMillisecond(-299999, None) AND test.j <= TimestampMillisecond(10000, None)_|
|_|_TableScan: test_|
|_|_TableScan: test, partial_filters=[test.j >= TimestampMillisecond(-299999, None), test.j <= TimestampMillisecond(10000, None)] |
|_| ]]_|
| logical_plan after FixStateUdafOrderingAnalyzer_| SAME TEXT AS ABOVE_|
| analyzed_logical_plan_| SAME TEXT AS ABOVE_|
@@ -130,7 +130,7 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test;
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test.k ASC NULLS FIRST, test.j ASC NULLS FIRST_|
|_|_Filter: test.j >= TimestampMillisecond(-299999, None) AND test.j <= TimestampMillisecond(10000, None)_|
|_|_TableScan: test_|
|_|_TableScan: test, partial_filters=[test.j >= TimestampMillisecond(-299999, None), test.j <= TimestampMillisecond(10000, None)] |
|_| ]]_|
| logical_plan after ScanHintRule_| SAME TEXT AS ABOVE_|
| logical_plan after JsonTypeConcretizeRule_| SAME TEXT AS ABOVE_|
@@ -165,7 +165,7 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test;
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test.k ASC NULLS FIRST, test.j ASC NULLS FIRST_|
|_|_Filter: test.j >= TimestampMillisecond(-299999, None) AND test.j <= TimestampMillisecond(10000, None)_|
|_|_TableScan: test_|
|_|_TableScan: test, partial_filters=[test.j >= TimestampMillisecond(-299999, None), test.j <= TimestampMillisecond(10000, None)] |
|_| ]]_|
| initial_physical_plan_| MergeScanExec: REDACTED
|_|_|
@@ -247,7 +247,7 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test AS series;
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test.k ASC NULLS FIRST, test.j ASC NULLS FIRST_|
|_|_Filter: test.j >= TimestampMillisecond(-299999, None) AND test.j <= TimestampMillisecond(10000, None)_|
|_|_TableScan: test_|
|_|_TableScan: test, partial_filters=[test.j >= TimestampMillisecond(-299999, None), test.j <= TimestampMillisecond(10000, None)] |
|_| ]]_|
| logical_plan after FixStateUdafOrderingAnalyzer_| SAME TEXT AS ABOVE_|
| analyzed_logical_plan_| SAME TEXT AS ABOVE_|
@@ -280,7 +280,7 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test AS series;
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test.k ASC NULLS FIRST, test.j ASC NULLS FIRST_|
|_|_Filter: test.j >= TimestampMillisecond(-299999, None) AND test.j <= TimestampMillisecond(10000, None)_|
|_|_TableScan: test_|
|_|_TableScan: test, partial_filters=[test.j >= TimestampMillisecond(-299999, None), test.j <= TimestampMillisecond(10000, None)] |
|_| ]]_|
| logical_plan after ScanHintRule_| SAME TEXT AS ABOVE_|
| logical_plan after JsonTypeConcretizeRule_| SAME TEXT AS ABOVE_|
@@ -316,7 +316,7 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test AS series;
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test.k ASC NULLS FIRST, test.j ASC NULLS FIRST_|
|_|_Filter: test.j >= TimestampMillisecond(-299999, None) AND test.j <= TimestampMillisecond(10000, None)_|
|_|_TableScan: test_|
|_|_TableScan: test, partial_filters=[test.j >= TimestampMillisecond(-299999, None), test.j <= TimestampMillisecond(10000, None)] |
|_| ]]_|
| initial_physical_plan_| MergeScanExec: REDACTED
|_|_|
@@ -380,29 +380,25 @@ Affected Rows: 3
-- SQLNESS REPLACE (RepartitionExec:.*) RepartitionExec: REDACTED
TQL EXPLAIN (0, 10, '5s') test_nano;
+---------------+-----------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j] |
| | PromSeriesDivide: tags=["k"] |
| | Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST |
| | Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j |
| | Projection: test_nano.i, test_nano.j, test_nano.k |
| | Filter: __common_expr_3 >= TimestampMillisecond(-299999, None) AND __common_expr_3 <= TimestampMillisecond(10000, None) |
| | Projection: CAST(test_nano.j AS Timestamp(ms)) AS __common_expr_3, test_nano.i, test_nano.j, test_nano.k |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | TableScan: test_nano |
| | ]] |
| physical_plan | PromInstantManipulateExec: range=[0..10000], lookback=[300000], interval=[5000], time index=[j] |
| | PromSeriesDivideExec: tags=["k"] |
| | SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[true] |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j] |
| | PromSeriesDivide: tags=["k"] |
| | Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST |
| | Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Filter: CAST(test_nano.j AS Timestamp(ms)) >= TimestampMillisecond(-299999, None) AND CAST(test_nano.j AS Timestamp(ms)) <= TimestampMillisecond(10000, None) |
| | TableScan: test_nano, partial_filters=[CAST(test_nano.j AS Timestamp(ms)) >= TimestampMillisecond(-299999, None), CAST(test_nano.j AS Timestamp(ms)) <= TimestampMillisecond(10000, None)] |
| | ]] |
| physical_plan | PromInstantManipulateExec: range=[0..10000], lookback=[300000], interval=[5000], time index=[j] |
| | PromSeriesDivideExec: tags=["k"] |
| | SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[true] |
| | RepartitionExec: REDACTED
| | ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j] |
| | FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000, projection=[i@1, j@2, k@3] |
| | ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k] |
| | MergeScanExec: REDACTED
| | |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------+
| | ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j] |
| | MergeScanExec: REDACTED
| | |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- explain verbose at 0s, 5s and 10s for a nanosecond time index.
-- SQLNESS REPLACE (-+) -
@@ -432,11 +428,11 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test_nano;
| logical_plan after DistPlannerAnalyzer_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST_|
|_|_Filter: test_nano.j >= TimestampMillisecond(-299999, None) AND test_nano.j <= TimestampMillisecond(10000, None)_|
|_|_Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: test_nano_|
|_| Filter: CAST(test_nano.j AS Timestamp(ms)) >= TimestampMillisecond(-299999, None) AND CAST(test_nano.j AS Timestamp(ms)) <= TimestampMillisecond(10000, None)_|
|_|_TableScan: test_nano, partial_filters=[CAST(test_nano.j AS Timestamp(ms)) >= TimestampMillisecond(-299999, None), CAST(test_nano.j AS Timestamp(ms)) <= TimestampMillisecond(10000, None)] |
|_| ]]_|
| logical_plan after FixStateUdafOrderingAnalyzer_| SAME TEXT AS ABOVE_|
| analyzed_logical_plan_| SAME TEXT AS ABOVE_|
@@ -457,39 +453,19 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test_nano;
| logical_plan after filter_null_join_keys_| SAME TEXT AS ABOVE_|
| logical_plan after eliminate_outer_join_| SAME TEXT AS ABOVE_|
| logical_plan after push_down_limit_| SAME TEXT AS ABOVE_|
| logical_plan after push_down_filter_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST_|
|_|_Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Filter: CAST(test_nano.j AS Timestamp(ms)) >= TimestampMillisecond(-299999, None) AND CAST(test_nano.j AS Timestamp(ms)) <= TimestampMillisecond(10000, None)_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: test_nano_|
|_| ]]_|
| logical_plan after push_down_filter_| SAME TEXT AS ABOVE_|
| logical_plan after single_distinct_aggregation_to_group_by | SAME TEXT AS ABOVE_|
| logical_plan after eliminate_group_by_constant_| SAME TEXT AS ABOVE_|
| logical_plan after common_sub_expression_eliminate_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST_|
|_|_Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Filter: __common_expr_1 >= TimestampMillisecond(-299999, None) AND __common_expr_1 <= TimestampMillisecond(10000, None)_|
|_|_Projection: CAST(test_nano.j AS Timestamp(ms)) AS __common_expr_1, test_nano.i, test_nano.j, test_nano.k_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: test_nano_|
|_| ]]_|
| logical_plan after common_sub_expression_eliminate_| SAME TEXT AS ABOVE_|
| logical_plan after extract_leaf_expressions_| SAME TEXT AS ABOVE_|
| logical_plan after push_down_leaf_projections_| SAME TEXT AS ABOVE_|
| logical_plan after optimize_projections_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST_|
|_|_Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Filter: __common_expr_1 >= TimestampMillisecond(-299999, None) AND __common_expr_1 <= TimestampMillisecond(10000, None)_|
|_|_Projection: CAST(test_nano.j AS Timestamp(ms)) AS __common_expr_1, test_nano.i, test_nano.j, test_nano.k_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: test_nano_|
|_| Filter: CAST(test_nano.j AS Timestamp(ms)) >= TimestampMillisecond(-299999, None) AND CAST(test_nano.j AS Timestamp(ms)) <= TimestampMillisecond(10000, None)_|
|_|_TableScan: test_nano, partial_filters=[CAST(test_nano.j AS Timestamp(ms)) >= TimestampMillisecond(-299999, None), CAST(test_nano.j AS Timestamp(ms)) <= TimestampMillisecond(10000, None)] |
|_| ]]_|
| logical_plan after ScanHintRule_| SAME TEXT AS ABOVE_|
| logical_plan after JsonTypeConcretizeRule_| SAME TEXT AS ABOVE_|
@@ -510,134 +486,39 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test_nano;
| logical_plan after filter_null_join_keys_| SAME TEXT AS ABOVE_|
| logical_plan after eliminate_outer_join_| SAME TEXT AS ABOVE_|
| logical_plan after push_down_limit_| SAME TEXT AS ABOVE_|
| logical_plan after push_down_filter_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST_|
|_|_Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Projection: CAST(test_nano.j AS Timestamp(ms)) AS __common_expr_1, test_nano.i, test_nano.j, test_nano.k_|
|_|_Filter: CAST(test_nano.j AS Timestamp(ms)) >= TimestampMillisecond(-299999, None) AND CAST(test_nano.j AS Timestamp(ms)) <= TimestampMillisecond(10000, None)_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: test_nano_|
|_| ]]_|
| logical_plan after push_down_filter_| SAME TEXT AS ABOVE_|
| logical_plan after single_distinct_aggregation_to_group_by | SAME TEXT AS ABOVE_|
| logical_plan after eliminate_group_by_constant_| SAME TEXT AS ABOVE_|
| logical_plan after common_sub_expression_eliminate_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST_|
|_|_Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Projection: CAST(test_nano.j AS Timestamp(ms)) AS __common_expr_1, test_nano.i, test_nano.j, test_nano.k_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Filter: __common_expr_2 >= TimestampMillisecond(-299999, None) AND __common_expr_2 <= TimestampMillisecond(10000, None)_|
|_|_Projection: CAST(test_nano.j AS Timestamp(ms)) AS __common_expr_2, test_nano.i, test_nano.j, test_nano.k_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: test_nano_|
|_| ]]_|
| logical_plan after common_sub_expression_eliminate_| SAME TEXT AS ABOVE_|
| logical_plan after extract_leaf_expressions_| SAME TEXT AS ABOVE_|
| logical_plan after push_down_leaf_projections_| SAME TEXT AS ABOVE_|
| logical_plan after optimize_projections_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST_|
|_|_Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Filter: __common_expr_2 >= TimestampMillisecond(-299999, None) AND __common_expr_2 <= TimestampMillisecond(10000, None)_|
|_|_Projection: CAST(test_nano.j AS Timestamp(ms)) AS __common_expr_2, test_nano.i, test_nano.j, test_nano.k_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: test_nano_|
|_| ]]_|
| logical_plan after ScanHintRule_| SAME TEXT AS ABOVE_|
| logical_plan after JsonTypeConcretizeRule_| SAME TEXT AS ABOVE_|
| logical_plan after rewrite_set_comparison_| SAME TEXT AS ABOVE_|
| logical_plan after optimize_unions_| SAME TEXT AS ABOVE_|
| logical_plan after simplify_expressions_| SAME TEXT AS ABOVE_|
| logical_plan after replace_distinct_aggregate_| SAME TEXT AS ABOVE_|
| logical_plan after eliminate_join_| SAME TEXT AS ABOVE_|
| logical_plan after decorrelate_predicate_subquery_| SAME TEXT AS ABOVE_|
| logical_plan after scalar_subquery_to_join_| SAME TEXT AS ABOVE_|
| logical_plan after decorrelate_lateral_join_| SAME TEXT AS ABOVE_|
| logical_plan after extract_equijoin_predicate_| SAME TEXT AS ABOVE_|
| logical_plan after eliminate_duplicated_expr_| SAME TEXT AS ABOVE_|
| logical_plan after eliminate_filter_| SAME TEXT AS ABOVE_|
| logical_plan after eliminate_cross_join_| SAME TEXT AS ABOVE_|
| logical_plan after eliminate_limit_| SAME TEXT AS ABOVE_|
| logical_plan after propagate_empty_relation_| SAME TEXT AS ABOVE_|
| logical_plan after filter_null_join_keys_| SAME TEXT AS ABOVE_|
| logical_plan after eliminate_outer_join_| SAME TEXT AS ABOVE_|
| logical_plan after push_down_limit_| SAME TEXT AS ABOVE_|
| logical_plan after push_down_filter_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST_|
|_|_Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Projection: CAST(test_nano.j AS Timestamp(ms)) AS __common_expr_2, test_nano.i, test_nano.j, test_nano.k_|
|_|_Filter: CAST(test_nano.j AS Timestamp(ms)) >= TimestampMillisecond(-299999, None) AND CAST(test_nano.j AS Timestamp(ms)) <= TimestampMillisecond(10000, None)_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: test_nano_|
|_| ]]_|
| logical_plan after single_distinct_aggregation_to_group_by | SAME TEXT AS ABOVE_|
| logical_plan after eliminate_group_by_constant_| SAME TEXT AS ABOVE_|
| logical_plan after common_sub_expression_eliminate_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST_|
|_|_Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Projection: CAST(test_nano.j AS Timestamp(ms)) AS __common_expr_2, test_nano.i, test_nano.j, test_nano.k_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Filter: __common_expr_3 >= TimestampMillisecond(-299999, None) AND __common_expr_3 <= TimestampMillisecond(10000, None)_|
|_|_Projection: CAST(test_nano.j AS Timestamp(ms)) AS __common_expr_3, test_nano.i, test_nano.j, test_nano.k_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: test_nano_|
|_| ]]_|
| logical_plan after extract_leaf_expressions_| SAME TEXT AS ABOVE_|
| logical_plan after push_down_leaf_projections_| SAME TEXT AS ABOVE_|
| logical_plan after optimize_projections_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST_|
|_|_Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Filter: __common_expr_3 >= TimestampMillisecond(-299999, None) AND __common_expr_3 <= TimestampMillisecond(10000, None)_|
|_|_Projection: CAST(test_nano.j AS Timestamp(ms)) AS __common_expr_3, test_nano.i, test_nano.j, test_nano.k_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: test_nano_|
|_| ]]_|
| logical_plan after optimize_projections_| SAME TEXT AS ABOVE_|
| logical_plan after ScanHintRule_| SAME TEXT AS ABOVE_|
| logical_plan after JsonTypeConcretizeRule_| SAME TEXT AS ABOVE_|
| logical_plan_| PromInstantManipulate: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivide: tags=["k"]_|
|_|_Sort: test_nano.k ASC NULLS FIRST, test_nano.j ASC NULLS FIRST_|
|_|_Projection: test_nano.i, test_nano.k, CAST(test_nano.j AS Timestamp(ms)) AS j_|
|_|_Projection: test_nano.i, test_nano.j, test_nano.k_|
|_|_Filter: __common_expr_3 >= TimestampMillisecond(-299999, None) AND __common_expr_3 <= TimestampMillisecond(10000, None)_|
|_|_Projection: CAST(test_nano.j AS Timestamp(ms)) AS __common_expr_3, test_nano.i, test_nano.j, test_nano.k_|
|_|_MergeScan [is_placeholder=false, remote_input=[_|
|_| TableScan: test_nano_|
|_| Filter: CAST(test_nano.j AS Timestamp(ms)) >= TimestampMillisecond(-299999, None) AND CAST(test_nano.j AS Timestamp(ms)) <= TimestampMillisecond(10000, None)_|
|_|_TableScan: test_nano, partial_filters=[CAST(test_nano.j AS Timestamp(ms)) >= TimestampMillisecond(-299999, None), CAST(test_nano.j AS Timestamp(ms)) <= TimestampMillisecond(10000, None)] |
|_| ]]_|
| initial_physical_plan_| PromInstantManipulateExec: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivideExec: tags=["k"]_|
|_|_SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[false]_|
|_|_ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j]_|
|_|_ProjectionExec: expr=[i@1 as i, j@2 as j, k@3 as k]_|
|_|_FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000_|
|_|_ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k]_|
|_|_MergeScanExec: REDACTED
|_|_|
| initial_physical_plan_with_stats_| PromInstantManipulateExec: range=[0..10000], lookback=[300000], interval=[5000], time index=[j], statistics=[Rows=Inexact(2), Bytes=Absent, [(Col[0]:),(Col[1]:),(Col[2]:)]]_|
|_|_PromSeriesDivideExec: tags=["k"], statistics=[Rows=Absent, Bytes=Absent, [(Col[0]:),(Col[1]:),(Col[2]:)]]_|
|_|_SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[false], statistics=[Rows=Absent, Bytes=Absent, [(Col[0]:),(Col[1]:),(Col[2]:)]]_|
|_|_ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j], statistics=[Rows=Absent, Bytes=Absent, [(Col[0]:),(Col[1]:),(Col[2]:)]]_|
|_|_ProjectionExec: expr=[i@1 as i, j@2 as j, k@3 as k], statistics=[Rows=Absent, Bytes=Absent, [(Col[0]:),(Col[1]:),(Col[2]:)]]_|
|_|_FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000, statistics=[Rows=Absent, Bytes=Absent, [(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:)]]_|
|_|_ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k], statistics=[Rows=Absent, Bytes=Absent, [(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:)]] |
|_|_MergeScanExec: REDACTED
|_|_|
| initial_physical_plan_with_schema_| PromInstantManipulateExec: range=[0..10000], lookback=[300000], interval=[5000], time index=[j], schema=[i:Float64;N, k:Utf8;N, j:Timestamp(ms)]_|
|_|_PromSeriesDivideExec: tags=["k"], schema=[i:Float64;N, k:Utf8;N, j:Timestamp(ms)]_|
|_|_SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[false], schema=[i:Float64;N, k:Utf8;N, j:Timestamp(ms)]_|
|_|_ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j], schema=[i:Float64;N, k:Utf8;N, j:Timestamp(ms)]_|
|_|_ProjectionExec: expr=[i@1 as i, j@2 as j, k@3 as k], schema=[i:Float64;N, j:Timestamp(ns), k:Utf8;N]_|
|_|_FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000, schema=[__common_expr_3:Timestamp(ms), i:Float64;N, j:Timestamp(ns), k:Utf8;N]_|
|_|_ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k], schema=[__common_expr_3:Timestamp(ms), i:Float64;N, j:Timestamp(ns), k:Utf8;N]_|
|_|_MergeScanExec: REDACTED
|_|_|
| physical_plan after OutputRequirements_| OutputRequirementExec: order_by=[], dist_by=Unspecified_|
@@ -645,9 +526,6 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test_nano;
|_|_PromSeriesDivideExec: tags=["k"]_|
|_|_SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[false]_|
|_|_ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j]_|
|_|_ProjectionExec: expr=[i@1 as i, j@2 as j, k@3 as k]_|
|_|_FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000_|
|_|_ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k]_|
|_|_MergeScanExec: REDACTED
|_|_|
| physical_plan after aggregate_statistics_| SAME TEXT AS ABOVE_|
@@ -662,9 +540,6 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test_nano;
|_|_PromSeriesDivideExec: tags=["k"]_|
|_|_SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[true]_|
|_|_ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j]_|
|_|_ProjectionExec: expr=[i@1 as i, j@2 as j, k@3 as k]_|
|_|_FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000_|
|_|_ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k]_|
|_|_MergeScanExec: REDACTED
|_|_|
| physical_plan after EnforceDistribution_| OutputRequirementExec: order_by=[], dist_by=Unspecified_|
@@ -674,9 +549,6 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test_nano;
|_|_RepartitionExec: REDACTED
|_|_SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[true]_|
|_|_ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j]_|
|_|_ProjectionExec: expr=[i@1 as i, j@2 as j, k@3 as k]_|
|_|_FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000_|
|_|_ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k]_|
|_|_MergeScanExec: REDACTED
|_|_|
| physical_plan after CombinePartialFinalAggregate_| SAME TEXT AS ABOVE_|
@@ -686,29 +558,15 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test_nano;
|_|_SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[true]_|
|_|_RepartitionExec: REDACTED
|_|_ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j]_|
|_|_ProjectionExec: expr=[i@1 as i, j@2 as j, k@3 as k]_|
|_|_FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000_|
|_|_ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k]_|
|_|_MergeScanExec: REDACTED
|_|_|
| physical_plan after OptimizeAggregateOrder_| SAME TEXT AS ABOVE_|
| physical_plan after ProjectionPushdown_| OutputRequirementExec: order_by=[], dist_by=Unspecified_|
|_|_PromInstantManipulateExec: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivideExec: tags=["k"]_|
|_|_SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[true]_|
|_|_RepartitionExec: REDACTED
|_|_ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j]_|
|_|_FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000, projection=[i@1, j@2, k@3]_|
|_|_ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k]_|
|_|_MergeScanExec: REDACTED
|_|_|
| physical_plan after ProjectionPushdown_| SAME TEXT AS ABOVE_|
| physical_plan after OutputRequirements_| PromInstantManipulateExec: range=[0..10000], lookback=[300000], interval=[5000], time index=[j]_|
|_|_PromSeriesDivideExec: tags=["k"]_|
|_|_SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[true]_|
|_|_RepartitionExec: REDACTED
|_|_ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j]_|
|_|_FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000, projection=[i@1, j@2, k@3]_|
|_|_ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k]_|
|_|_MergeScanExec: REDACTED
|_|_|
| physical_plan after LimitAggregation_| SAME TEXT AS ABOVE_|
@@ -727,8 +585,6 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test_nano;
|_|_SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[true]_|
|_|_RepartitionExec: REDACTED
|_|_ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j]_|
|_|_FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000, projection=[i@1, j@2, k@3]_|
|_|_ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k]_|
|_|_MergeScanExec: REDACTED
|_|_|
| physical_plan_with_stats_| PromInstantManipulateExec: range=[0..10000], lookback=[300000], interval=[5000], time index=[j], statistics=[Rows=Inexact(2), Bytes=Absent, [(Col[0]:),(Col[1]:),(Col[2]:)]]_|
@@ -736,8 +592,6 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test_nano;
|_|_SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[true], statistics=[Rows=Absent, Bytes=Absent, [(Col[0]:),(Col[1]:),(Col[2]:)]]_|
|_|_RepartitionExec: REDACTED
|_|_ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j], statistics=[Rows=Absent, Bytes=Absent, [(Col[0]:),(Col[1]:),(Col[2]:)]]_|
|_|_FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000, projection=[i@1, j@2, k@3], statistics=[Rows=Absent, Bytes=Absent, [(Col[0]:),(Col[1]:),(Col[2]:)]]_|
|_|_ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k], statistics=[Rows=Absent, Bytes=Absent, [(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:)]] |
|_|_MergeScanExec: REDACTED
|_|_|
| physical_plan_with_schema_| PromInstantManipulateExec: range=[0..10000], lookback=[300000], interval=[5000], time index=[j], schema=[i:Float64;N, k:Utf8;N, j:Timestamp(ms)]_|
@@ -745,8 +599,6 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test_nano;
|_|_SortExec: expr=[k@1 ASC, j@2 ASC], preserve_partitioning=[true], schema=[i:Float64;N, k:Utf8;N, j:Timestamp(ms)]_|
|_|_RepartitionExec: REDACTED
|_|_ProjectionExec: expr=[i@0 as i, k@2 as k, CAST(j@1 AS Timestamp(ms)) as j], schema=[i:Float64;N, k:Utf8;N, j:Timestamp(ms)]_|
|_|_FilterExec: __common_expr_3@0 >= -299999 AND __common_expr_3@0 <= 10000, projection=[i@1, j@2, k@3], schema=[i:Float64;N, j:Timestamp(ns), k:Utf8;N]_|
|_|_ProjectionExec: expr=[CAST(j@1 AS Timestamp(ms)) as __common_expr_3, i@0 as i, j@1 as j, k@2 as k], schema=[__common_expr_3:Timestamp(ms), i:Float64;N, j:Timestamp(ns), k:Utf8;N]_|
|_|_MergeScanExec: REDACTED
|_|_|
+-+-+

View File

@@ -35,10 +35,9 @@ TQL analyze (0, 10, '1s') sum by(job) (irate(cpu_usage{job="fire"}[5s])) / 1e9;
|_|_|_PromRangeManipulateExec: req range=[0..10000], interval=[1000], eval range=[5000], time index=[ts] REDACTED
|_|_|_PromSeriesNormalizeExec: offset=[0], time index=[ts], filter NaN: [true] REDACTED
|_|_|_PromSeriesDivideExec: tags=["job"] REDACTED
|_|_|_SortExec: expr=[ts@2 ASC], preserve_partitioning=[true] REDACTED
|_|_|_SortExec: expr=[job@1 ASC, ts@2 ASC], preserve_partitioning=[true] REDACTED
|_|_|_RepartitionExec: partitioning=REDACTED
|_|_|_ProjectionExec: expr=[value@1 as value, job@0 as job, CAST(ts@2 AS Timestamp(ms)) as ts] REDACTED
|_|_|_FilterExec: job@0 = fire AND ts@2 >= -4999000000 AND ts@2 <= 10000000000 REDACTED
|_|_|_MergeScanExec: REDACTED
|_|_|_|
| 1_| 0_|_CooperativeExec REDACTED

View File

@@ -52,21 +52,21 @@ EXPLAIN WITH tql as (
)
SELECT * FROM tql;
+---------------+---------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: tql.ts, tql.val |
| | SubqueryAlias: tql |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: tql.ts, tql.val |
| | SubqueryAlias: tql |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric, partial_filters=[metric.ts >= TimestampMillisecond(-299999, None), metric.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------+
-- TQL CTE with column aliases
WITH tql (the_timestamp, the_value) as (
@@ -91,22 +91,22 @@ EXPLAIN WITH tql (the_timestamp, the_value) as (
)
SELECT * FROM tql;
+---------------+-----------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-----------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: tql.the_timestamp, tql.the_value |
| | SubqueryAlias: tql |
| | Projection: metric.ts AS the_timestamp, metric.val AS the_value |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: tql.the_timestamp, tql.the_value |
| | SubqueryAlias: tql |
| | Projection: metric.ts AS the_timestamp, metric.val AS the_value |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric, partial_filters=[metric.ts >= TimestampMillisecond(-299999, None), metric.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-----------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------+
-- Explain TQL CTE
-- SQLNESS REPLACE (peers.*) REDACTED
@@ -115,21 +115,21 @@ EXPLAIN WITH tql AS (
TQL EVAL (0, 40, '10s') metric
) SELECT * FROM tql;
+---------------+---------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: tql.ts, tql.val |
| | SubqueryAlias: tql |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: tql.ts, tql.val |
| | SubqueryAlias: tql |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric, partial_filters=[metric.ts >= TimestampMillisecond(-299999, None), metric.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------+
-- Hybrid CTEs (TQL + SQL)
WITH
@@ -150,26 +150,26 @@ EXPLAIN WITH
filtered AS (SELECT * FROM tql_data WHERE val > 5)
SELECT count(*) FROM filtered;
+---------------+-------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: count(Int64(1)) AS count(*) |
| | Aggregate: groupBy=[[]], aggr=[[count(Int64(1))]] |
| | SubqueryAlias: filtered |
| | Projection: tql_data.ts, tql_data.val |
| | Filter: tql_data.val > Float64(5) |
| | SubqueryAlias: tql_data |
| | Projection: metric.ts AS ts, metric.val AS val |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: count(Int64(1)) AS count(*) |
| | Aggregate: groupBy=[[]], aggr=[[count(Int64(1))]] |
| | SubqueryAlias: filtered |
| | Projection: tql_data.ts, tql_data.val |
| | SubqueryAlias: tql_data |
| | Projection: metric.ts AS ts, metric.val AS val |
| | Filter: metric.val > Float64(5) |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric, partial_filters=[metric.ts >= TimestampMillisecond(-299999, None), metric.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
-- TQL CTE with complex PromQL expressions
WITH
@@ -190,30 +190,29 @@ EXPLAIN WITH
filtered (ts, val) AS (SELECT * FROM tql_data WHERE val > 0)
SELECT sum(val) FROM filtered;
+---------------+---------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: sum(filtered.val) |
| | Aggregate: groupBy=[[]], aggr=[[sum(filtered.val)]] |
| | SubqueryAlias: filtered |
| | Projection: tql_data.ts AS ts, tql_data.val AS val |
| | Projection: tql_data.ts, tql_data.val |
| | Filter: tql_data.val > Float64(0) |
| | SubqueryAlias: tql_data |
| | Projection: metric.ts AS ts, prom_rate(ts_range,val,ts,Int64(20000)) AS val |
| | Filter: prom_rate(ts_range,val,ts,Int64(20000)) IS NOT NULL |
| | Projection: metric.ts, prom_rate(ts_range, val, metric.ts, Int64(20000)) AS prom_rate(ts_range,val,ts,Int64(20000)) |
| | PromRangeManipulate: req range=[0..40000], interval=[10000], eval range=[20000], time index=[ts], values=["val"] |
| | PromSeriesNormalize: offset=[0], time index=[ts], filter NaN: [true] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-19999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: sum(filtered.val) |
| | Aggregate: groupBy=[[]], aggr=[[sum(filtered.val)]] |
| | SubqueryAlias: filtered |
| | Projection: tql_data.ts AS ts, tql_data.val AS val |
| | Projection: tql_data.ts, tql_data.val |
| | SubqueryAlias: tql_data |
| | Projection: metric.ts AS ts, prom_rate(ts_range,val,ts,Int64(20000)) AS val |
| | Filter: prom_rate(ts_range,val,ts,Int64(20000)) > Float64(0) AND prom_rate(ts_range,val,ts,Int64(20000)) IS NOT NULL |
| | Projection: metric.ts, prom_rate(ts_range, val, metric.ts, Int64(20000)) AS prom_rate(ts_range,val,ts,Int64(20000)) |
| | PromRangeManipulate: req range=[0..40000], interval=[10000], eval range=[20000], time index=[ts], values=["val"] |
| | PromSeriesNormalize: offset=[0], time index=[ts], filter NaN: [true] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-19999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric, partial_filters=[metric.ts >= TimestampMillisecond(-19999, None), metric.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- TQL CTE with aggregation functions
WITH tql_agg(ts, summary) AS (
@@ -234,24 +233,24 @@ EXPLAIN WITH tql_agg(ts, summary) AS (
)
SELECT round(avg(summary)) as avg_sum FROM tql_agg;
+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: round(avg(tql_agg.summary)) AS avg_sum |
| | Aggregate: groupBy=[[]], aggr=[[avg(tql_agg.summary)]] |
| | SubqueryAlias: tql_agg |
| | Projection: labels.ts AS ts, sum(labels.cpu) AS summary |
| | Aggregate: groupBy=[[labels.ts]], aggr=[[sum(labels.cpu)]] |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.host ~ Utf8("^(?:host.*)$") AND labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: round(avg(tql_agg.summary)) AS avg_sum |
| | Aggregate: groupBy=[[]], aggr=[[avg(tql_agg.summary)]] |
| | SubqueryAlias: tql_agg |
| | Projection: labels.ts AS ts, sum(labels.cpu) AS summary |
| | Aggregate: groupBy=[[labels.ts]], aggr=[[sum(labels.cpu)]] |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.host ~ Utf8("^(?:host.*)$") AND labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels, partial_filters=[labels.host ~ Utf8("^(?:host.*)$"), labels.ts >= TimestampMillisecond(-299999, None), labels.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- TQL CTE with label selectors
WITH host_metrics AS (
@@ -272,22 +271,22 @@ EXPLAIN WITH host_metrics AS (
)
SELECT count(*) as host1_points FROM host_metrics;
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: count(Int64(1)) AS count(*) AS host1_points |
| | Aggregate: groupBy=[[]], aggr=[[count(host_metrics.ts) AS count(Int64(1))]] |
| | SubqueryAlias: host_metrics |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.host = Utf8("host1") AND labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: count(Int64(1)) AS count(*) AS host1_points |
| | Aggregate: groupBy=[[]], aggr=[[count(host_metrics.ts) AS count(Int64(1))]] |
| | SubqueryAlias: host_metrics |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.host = Utf8("host1") AND labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels, partial_filters=[labels.host = Utf8("host1"), labels.ts >= TimestampMillisecond(-299999, None), labels.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- TQL CTE with column reference
WITH host_metrics AS (
@@ -312,21 +311,21 @@ EXPLAIN WITH host_metrics AS (
)
SELECT host_metrics.ts, host_metrics.host FROM host_metrics;
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: host_metrics.ts, host_metrics.host |
| | SubqueryAlias: host_metrics |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.host = Utf8("host1") AND labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: host_metrics.ts, host_metrics.host |
| | SubqueryAlias: host_metrics |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.host = Utf8("host1") AND labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels, partial_filters=[labels.host = Utf8("host1"), labels.ts >= TimestampMillisecond(-299999, None), labels.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- Multiple TQL CTEs referencing different tables
WITH
@@ -361,44 +360,44 @@ WHERE m.ts = l.ts
ORDER BY m.ts
LIMIT 3;
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | Projection: metric_val, label_val |
| | Sort: m.ts ASC NULLS LAST, fetch=3 |
| | Projection: m.val AS metric_val, l.cpu AS label_val, m.ts |
| | Inner Join: m.ts = l.ts |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: m |
| | SubqueryAlias: metric_data |
| | Projection: metric.ts AS ts, metric.val AS val |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric |
| | ]] |
| | Projection: l.ts, l.cpu |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: l |
| | SubqueryAlias: label_data |
| | Projection: labels.ts AS ts, labels.host AS host, labels.cpu AS cpu |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.host = Utf8("host2") AND labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels |
| | ]] |
| physical_plan | ProjectionExec: expr=[metric_val@0 as metric_val, label_val@1 as label_val] |
| | SortPreservingMergeExec: [ts@2 ASC NULLS LAST], fetch=3 |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | Projection: metric_val, label_val |
| | Sort: m.ts ASC NULLS LAST, fetch=3 |
| | Projection: m.val AS metric_val, l.cpu AS label_val, m.ts |
| | Inner Join: m.ts = l.ts |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: m |
| | SubqueryAlias: metric_data |
| | Projection: metric.ts AS ts, metric.val AS val |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric, partial_filters=[metric.ts >= TimestampMillisecond(-299999, None), metric.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| | Projection: l.ts, l.cpu |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: l |
| | SubqueryAlias: label_data |
| | Projection: labels.ts AS ts, labels.host AS host, labels.cpu AS cpu |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.host = Utf8("host2") AND labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels, partial_filters=[labels.host = Utf8("host2"), labels.ts >= TimestampMillisecond(-299999, None), labels.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | ProjectionExec: expr=[metric_val@0 as metric_val, label_val@1 as label_val] |
| | SortPreservingMergeExec: [ts@2 ASC NULLS LAST], fetch=3 |
| | SortExec: TopK(fetch=3), expr=[ts@2 ASC NULLS LAST], preserve_REDACTED
| | ProjectionExec: expr=[val@1 as metric_val, cpu@2 as label_val, ts@0 as ts] |
| | HashJoinExec: mode=Partitioned, join_type=Inner, on=[(ts@0, ts@0)], projection=[ts@0, val@1, cpu@3] |
| | ProjectionExec: expr=[val@1 as metric_val, cpu@2 as label_val, ts@0 as ts] |
| | HashJoinExec: mode=Partitioned, join_type=Inner, on=[(ts@0, ts@0)], projection=[ts@0, val@1, cpu@3] |
| | RepartitionExec: REDACTED
| | MergeScanExec: REDACTED
| | RepartitionExec: REDACTED
| | ProjectionExec: expr=[ts@0 as ts, cpu@2 as cpu] |
| | ProjectionExec: expr=[ts@0 as ts, cpu@2 as cpu] |
| | MergeScanExec: REDACTED
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- TQL CTE with mathematical operations
WITH computed(ts, val) AS (
@@ -419,25 +418,25 @@ EXPLAIN WITH computed(ts, val) AS (
)
SELECT min(val) as min_computed, max(val) as max_computed FROM computed;
+---------------+-----------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-----------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: min(computed.val) AS min_computed, max(computed.val) AS max_computed |
| | Aggregate: groupBy=[[]], aggr=[[min(computed.val), max(computed.val)]] |
| | SubqueryAlias: computed |
| | Projection: metric.ts AS ts, val * Float64(2) + Float64(1) AS val |
| | Projection: metric.ts, CAST(val * Float64(2) AS Float64) + Float64(1) AS val * Float64(2) + Float64(1) |
| | Projection: metric.ts, CAST(metric.val AS Float64) * Float64(2) AS val * Float64(2) |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: min(computed.val) AS min_computed, max(computed.val) AS max_computed |
| | Aggregate: groupBy=[[]], aggr=[[min(computed.val), max(computed.val)]] |
| | SubqueryAlias: computed |
| | Projection: metric.ts AS ts, val * Float64(2) + Float64(1) AS val |
| | Projection: metric.ts, CAST(val * Float64(2) AS Float64) + Float64(1) AS val * Float64(2) + Float64(1) |
| | Projection: metric.ts, CAST(metric.val AS Float64) * Float64(2) AS val * Float64(2) |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric, partial_filters=[metric.ts >= TimestampMillisecond(-299999, None), metric.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-----------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
-- TQL CTE with window functions in SQL part
WITH tql_base(ts, val) AS (
@@ -484,7 +483,7 @@ ORDER BY ts;
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric |
| | TableScan: metric, partial_filters=[metric.ts >= TimestampMillisecond(-299999, None), metric.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | ProjectionExec: expr=[ts@0 as ts, val@1 as val, lag(tql_base.val,Int64(1)) ORDER BY [tql_base.ts ASC NULLS LAST] RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW@2 as prev_value] |
| | BoundedWindowAggExec: wdw=[lag(tql_base.val,Int64(1)) ORDER BY [tql_base.ts ASC NULLS LAST] RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: Field { "lag(tql_base.val,Int64(1)) ORDER BY [tql_base.ts ASC NULLS LAST] RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW": nullable Float64 }, frame: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW], mode=[Sorted] |
@@ -523,24 +522,24 @@ FROM tql_grouped
GROUP BY minute
HAVING count(*) > 1;
+---------------+---------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: date_trunc(Utf8("minute"),tql_grouped.ts) AS minute, count(Int64(1)) AS count(*) AS point_count |
| | Filter: count(Int64(1)) > Int64(1) |
| | Aggregate: groupBy=[[date_trunc(Utf8("minute"), tql_grouped.ts)]], aggr=[[count(tql_grouped.ts) AS count(Int64(1))]] |
| | SubqueryAlias: tql_grouped |
| | Projection: labels.ts AS ts, labels.host AS host, labels.cpu AS cpu |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: date_trunc(Utf8("minute"),tql_grouped.ts) AS minute, count(Int64(1)) AS count(*) AS point_count |
| | Filter: count(Int64(1)) > Int64(1) |
| | Aggregate: groupBy=[[date_trunc(Utf8("minute"), tql_grouped.ts)]], aggr=[[count(tql_grouped.ts) AS count(Int64(1))]] |
| | SubqueryAlias: tql_grouped |
| | Projection: labels.ts AS ts, labels.host AS host, labels.cpu AS cpu |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels, partial_filters=[labels.ts >= TimestampMillisecond(-299999, None), labels.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
-- TQL CTE with UNION
-- SQLNESS SORT_RESULT 3 1
@@ -575,35 +574,35 @@ SELECT 'host1' as source, ts, cpu FROM host1_data
UNION ALL
SELECT 'host2' as source, ts, cpu FROM host2_data;
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | Union |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: Utf8("host1") AS source, host1_data.ts, host1_data.cpu |
| | SubqueryAlias: host1_data |
| | Projection: labels.ts AS ts, labels.host AS host, labels.cpu AS cpu |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.host = Utf8("host1") AND labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels |
| | ]] |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: Utf8("host2") AS source, host2_data.ts, host2_data.cpu |
| | SubqueryAlias: host2_data |
| | Projection: labels.ts AS ts, labels.host AS host, labels.cpu AS cpu |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.host = Utf8("host2") AND labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels |
| | ]] |
| physical_plan | InterleaveExec |
| | CooperativeExec |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | Union |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: Utf8("host1") AS source, host1_data.ts, host1_data.cpu |
| | SubqueryAlias: host1_data |
| | Projection: labels.ts AS ts, labels.host AS host, labels.cpu AS cpu |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.host = Utf8("host1") AND labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels, partial_filters=[labels.host = Utf8("host1"), labels.ts >= TimestampMillisecond(-299999, None), labels.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: Utf8("host2") AS source, host2_data.ts, host2_data.cpu |
| | SubqueryAlias: host2_data |
| | Projection: labels.ts AS ts, labels.host AS host, labels.cpu AS cpu |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.host = Utf8("host2") AND labels.ts >= TimestampMillisecond(-299999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels, partial_filters=[labels.host = Utf8("host2"), labels.ts >= TimestampMillisecond(-299999, None), labels.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | InterleaveExec |
| | CooperativeExec |
| | MergeScanExec: REDACTED
| | CooperativeExec |
| | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- Nested CTEs with TQL
WITH
@@ -638,31 +637,30 @@ EXPLAIN WITH
)
SELECT count(*) as high_values FROM final;
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: count(Int64(1)) AS count(*) AS high_values |
| | Aggregate: groupBy=[[]], aggr=[[count(Int64(1))]] |
| | SubqueryAlias: final |
| | Projection: processed.ts AS ts, processed.percent AS percent |
| | Projection: processed.ts, processed.percent |
| | Filter: processed.percent > Float64(200) |
| | SubqueryAlias: processed |
| | Projection: base_tql.ts AS ts, percent AS percent |
| | Projection: base_tql.ts, base_tql.val * Float64(100) AS percent |
| | Filter: base_tql.val > Float64(0) |
| | SubqueryAlias: base_tql |
| | Projection: metric.ts AS ts, metric.val AS val |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: count(Int64(1)) AS count(*) AS high_values |
| | Aggregate: groupBy=[[]], aggr=[[count(Int64(1))]] |
| | SubqueryAlias: final |
| | Projection: processed.ts AS ts, processed.percent AS percent |
| | Projection: processed.ts, processed.percent |
| | SubqueryAlias: processed |
| | Projection: base_tql.ts AS ts, percent AS percent |
| | Projection: base_tql.ts, base_tql.val * Float64(100) AS percent |
| | SubqueryAlias: base_tql |
| | Projection: metric.ts AS ts, metric.val AS val |
| | Filter: metric.val * Float64(100) > Float64(200) AND metric.val > Float64(0) |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-299999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric, partial_filters=[metric.ts >= TimestampMillisecond(-299999, None), metric.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- TQL CTE with time-based functions
WITH time_shifted AS (
@@ -680,22 +678,22 @@ EXPLAIN WITH time_shifted AS (
)
SELECT * FROM time_shifted;
+---------------+------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: time_shifted.ts, time_shifted.val |
| | SubqueryAlias: time_shifted |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesNormalize: offset=[50000], time index=[ts], filter NaN: [false] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-349999, None) AND metric.ts <= TimestampMillisecond(-10000, None) |
| | TableScan: metric |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: time_shifted.ts, time_shifted.val |
| | SubqueryAlias: time_shifted |
| | PromInstantManipulate: range=[0..40000], lookback=[300000], interval=[10000], time index=[ts] |
| | PromSeriesNormalize: offset=[50000], time index=[ts], filter NaN: [false] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-349999, None) AND metric.ts <= TimestampMillisecond(-10000, None) |
| | TableScan: metric, partial_filters=[metric.ts >= TimestampMillisecond(-349999, None), metric.ts <= TimestampMillisecond(-10000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+------------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------+
-- TQL CTE with JOIN between TQL and regular table
-- SQLNESS SORT_RESULT 3 1
@@ -754,16 +752,16 @@ LIMIT 5;
| | PromSeriesNormalize: offset=[0], time index=[ts], filter NaN: [true] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.ts >= TimestampMillisecond(-29999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels |
| | TableScan: labels, partial_filters=[labels.ts >= TimestampMillisecond(-29999, None), labels.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| | Filter: l.host = Utf8("host1") |
| | Projection: l.ts, l.host |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: l.ts, l.host |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: l |
| | TableScan: labels |
| | Filter: labels.host = Utf8("host1") |
| | TableScan: labels, partial_filters=[labels.host = Utf8("host1")] |
| | ]] |
| physical_plan | SortPreservingMergeExec: [ts@0 ASC NULLS LAST, avg_value@1 ASC NULLS LAST], fetch=5 |
| | SortExec: TopK(fetch=5), expr=[ts@0 ASC NULLS LAST, avg_value@1 ASC NULLS LAST], preserve_REDACTED
| physical_plan | SortPreservingMergeExec: [ts@0 ASC NULLS LAST, host@2 ASC NULLS LAST, avg_value@1 ASC NULLS LAST], fetch=5 |
| | SortExec: TopK(fetch=5), expr=[ts@0 ASC NULLS LAST, host@2 ASC NULLS LAST, avg_value@1 ASC NULLS LAST], preserve_REDACTED
| | ProjectionExec: expr=[ts@0 as ts, cpu@1 as avg_value, host@2 as host] |
| | HashJoinExec: mode=Partitioned, join_type=Inner, on=[(date_trunc(Utf8("second"),t.ts)@2, date_trunc(Utf8("second"),l.ts)@2)], projection=[ts@0, cpu@1, host@4] |
| | RepartitionExec: REDACTED
@@ -771,9 +769,7 @@ LIMIT 5;
| | MergeScanExec: REDACTED
| | RepartitionExec: REDACTED
| | ProjectionExec: expr=[ts@0 as ts, host@1 as host, date_trunc(second, ts@0) as date_trunc(Utf8("second"),l.ts)] |
| | FilterExec: host@1 = host1 |
| | ProjectionExec: expr=[ts@0 as ts, host@1 as host] |
| | MergeScanExec: REDACTED
| | MergeScanExec: REDACTED
| | |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
@@ -834,16 +830,16 @@ LIMIT 5;
| | PromSeriesNormalize: offset=[0], time index=[ts], filter NaN: [true] |
| | PromSeriesDivide: tags=["host"] |
| | Filter: labels.ts >= TimestampMillisecond(-29999, None) AND labels.ts <= TimestampMillisecond(40000, None) |
| | TableScan: labels |
| | TableScan: labels, partial_filters=[labels.ts >= TimestampMillisecond(-29999, None), labels.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| | Filter: l.host = Utf8("host1") |
| | Projection: l.ts, l.host |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: l.ts, l.host |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: l |
| | TableScan: labels |
| | Filter: labels.host = Utf8("host1") |
| | TableScan: labels, partial_filters=[labels.host = Utf8("host1")] |
| | ]] |
| physical_plan | SortPreservingMergeExec: [ts@0 ASC NULLS LAST, avg_value@1 ASC NULLS LAST], fetch=5 |
| | SortExec: TopK(fetch=5), expr=[ts@0 ASC NULLS LAST, avg_value@1 ASC NULLS LAST], preserve_REDACTED
| physical_plan | SortPreservingMergeExec: [ts@0 ASC NULLS LAST, host@2 ASC NULLS LAST, avg_value@1 ASC NULLS LAST], fetch=5 |
| | SortExec: TopK(fetch=5), expr=[ts@0 ASC NULLS LAST, host@2 ASC NULLS LAST, avg_value@1 ASC NULLS LAST], preserve_REDACTED
| | ProjectionExec: expr=[ts@1 as ts, cpu@0 as avg_value, host@2 as host] |
| | HashJoinExec: mode=Partitioned, join_type=Inner, on=[(date_trunc(Utf8("second"),t.ts)@2, date_trunc(Utf8("second"),l.ts)@2)], projection=[cpu@0, ts@1, host@4] |
| | RepartitionExec: REDACTED
@@ -851,9 +847,7 @@ LIMIT 5;
| | MergeScanExec: REDACTED
| | RepartitionExec: REDACTED
| | ProjectionExec: expr=[ts@0 as ts, host@1 as host, date_trunc(second, ts@0) as date_trunc(Utf8("second"),l.ts)] |
| | FilterExec: host@1 = host1 |
| | ProjectionExec: expr=[ts@0 as ts, host@1 as host] |
| | MergeScanExec: REDACTED
| | MergeScanExec: REDACTED
| | |
+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
@@ -910,22 +904,22 @@ EXPLAIN WITH tql_lookback AS (
)
SELECT count(*) FROM tql_lookback;
+---------------+----------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+----------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: count(Int64(1)) AS count(*) |
| | Aggregate: groupBy=[[]], aggr=[[count(tql_lookback.ts) AS count(Int64(1))]] |
| | SubqueryAlias: tql_lookback |
| | PromInstantManipulate: range=[0..40000], lookback=[15000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-14999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric |
| | ]] |
| physical_plan | CooperativeExec |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: count(Int64(1)) AS count(*) |
| | Aggregate: groupBy=[[]], aggr=[[count(tql_lookback.ts) AS count(Int64(1))]] |
| | SubqueryAlias: tql_lookback |
| | PromInstantManipulate: range=[0..40000], lookback=[15000], interval=[10000], time index=[ts] |
| | PromSeriesDivide: tags=[] |
| | Filter: metric.ts >= TimestampMillisecond(-14999, None) AND metric.ts <= TimestampMillisecond(40000, None) |
| | TableScan: metric, partial_filters=[metric.ts >= TimestampMillisecond(-14999, None), metric.ts <= TimestampMillisecond(40000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+----------------------------------------------------------------------------------------------------------------------+
| | |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
drop table metric;

View File

@@ -22,20 +22,13 @@ EXPLAIN SELECT * FROM (SELECT SUM(number) FROM numbers LIMIT 100000000000) LIMIT
EXPLAIN SELECT * FROM (SELECT SUM(number) FROM numbers LIMIT 100000000000) WHERE 1=0;
+---------------+-------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: sum(numbers.number) |
| | Filter: Boolean(false) |
| | Limit: skip=0, fetch=100000000000 |
| | Projection: sum(numbers.number) |
| | Aggregate: groupBy=[[]], aggr=[[sum(CAST(numbers.number AS UInt64))]] |
| | TableScan: numbers |
| | ]] |
| physical_plan | EmptyExec |
| | |
+---------------+-------------------------------------------------------------------------------+
+---------------+-----------------------+
| plan_type | plan |
+---------------+-----------------------+
| logical_plan | EmptyRelation: rows=0 |
| physical_plan | EmptyExec |
| | |
+---------------+-----------------------+
CREATE TABLE test (a TIMESTAMP TIME INDEX, b INTEGER);

View File

@@ -0,0 +1,138 @@
-- Regression test for issue #8338: static side-local predicates on a JOIN input
-- should reach the remote region TableScan/SeqScan filters before MergeScan wrapping.
CREATE TABLE parent (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
v DOUBLE,
PRIMARY KEY (k)
) ENGINE = mito;
Affected Rows: 0
CREATE TABLE child (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
PRIMARY KEY (k)
) ENGINE = mito;
Affected Rows: 0
INSERT INTO parent VALUES
('2024-01-30 00:00:00', 'a', 1.0),
('2024-01-30 01:00:00', 'b', 2.0);
Affected Rows: 2
INSERT INTO child VALUES
('2024-01-30 00:00:00', 'a'),
('2024-01-31 00:00:00', 'c');
Affected Rows: 2
ADMIN FLUSH_TABLE('parent');
+-----------------------------+
| ADMIN FLUSH_TABLE('parent') |
+-----------------------------+
| 0 |
+-----------------------------+
ADMIN FLUSH_TABLE('child');
+----------------------------+
| ADMIN FLUSH_TABLE('child') |
+----------------------------+
| 0 |
+----------------------------+
-- Query A: single table with time index filter (baseline).
-- The scan should have partial_filters with the time condition.
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE peers=\[\d+\(\d+,\s+\d+\),\s\] peers=[REDACTED]
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT * FROM parent WHERE ts >= '2024-01-30 00:00:00';
+---------------+-------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: parent.ts, parent.k, parent.v |
| | Filter: parent.ts >= TimestampMillisecond(1706572800000, None) |
| | TableScan: parent, partial_filters=[parent.ts >= TimestampMillisecond(1706572800000, None)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: peers=[REDACTED] |
| | |
+---------------+-------------------------------------------------------------------------------------------------+
-- Query B: flat LEFT JOIN with WHERE on parent's ts.
-- The filter should be pushed into the left MergeScan remote_input,
-- appearing as partial_filters=[...] in the parent TableScan.
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE peers=\[\d+\(\d+,\s+\d+\),\s\] peers=[REDACTED]
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT * FROM parent p LEFT JOIN child c ON p.k = c.k WHERE p.ts >= '2024-01-30 00:00:00';
+---------------+-------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-------------------------------------------------------------------------------------------------+
| logical_plan | Left Join: p.k = c.k |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: p |
| | Filter: parent.ts >= TimestampMillisecond(1706572800000, None) |
| | TableScan: parent, partial_filters=[parent.ts >= TimestampMillisecond(1706572800000, None)] |
| | ]] |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: c |
| | Filter: child.k IS NOT NULL |
| | TableScan: child, partial_filters=[child.k IS NOT NULL] |
| | ]] |
| physical_plan | HashJoinExec: mode=Partitioned, join_type=Left, on=[(k@1, k@1)] |
| | RepartitionExec: partitioning=Hash([REDACTED
| | MergeScanExec: peers=[REDACTED] |
| | RepartitionExec: partitioning=Hash([REDACTED
| | MergeScanExec: peers=[REDACTED] |
| | |
+---------------+-------------------------------------------------------------------------------------------------+
-- Query C: subquery pre-filter (workaround from #8338).
-- Should produce the same logical scan filter shape as query B.
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE peers=\[\d+\(\d+,\s+\d+\),\s\] peers=[REDACTED]
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT * FROM (SELECT * FROM parent WHERE ts >= '2024-01-30 00:00:00') p LEFT JOIN child c ON p.k = c.k;
+---------------+---------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------+
| logical_plan | Left Join: p.k = c.k |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: p |
| | Projection: parent.ts, parent.k, parent.v |
| | Filter: parent.ts >= TimestampMillisecond(1706572800000, None) |
| | TableScan: parent, partial_filters=[parent.ts >= TimestampMillisecond(1706572800000, None)] |
| | ]] |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: c |
| | Filter: child.k IS NOT NULL |
| | TableScan: child, partial_filters=[child.k IS NOT NULL] |
| | ]] |
| physical_plan | HashJoinExec: mode=Partitioned, join_type=Left, on=[(k@1, k@1)] |
| | RepartitionExec: partitioning=Hash([REDACTED
| | MergeScanExec: peers=[REDACTED] |
| | RepartitionExec: partitioning=Hash([REDACTED
| | MergeScanExec: peers=[REDACTED] |
| | |
+---------------+---------------------------------------------------------------------------------------------------+
DROP TABLE parent;
Affected Rows: 0
DROP TABLE child;
Affected Rows: 0

View File

@@ -0,0 +1,54 @@
-- Regression test for issue #8338: static side-local predicates on a JOIN input
-- should reach the remote region TableScan/SeqScan filters before MergeScan wrapping.
CREATE TABLE parent (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
v DOUBLE,
PRIMARY KEY (k)
) ENGINE = mito;
CREATE TABLE child (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
PRIMARY KEY (k)
) ENGINE = mito;
INSERT INTO parent VALUES
('2024-01-30 00:00:00', 'a', 1.0),
('2024-01-30 01:00:00', 'b', 2.0);
INSERT INTO child VALUES
('2024-01-30 00:00:00', 'a'),
('2024-01-31 00:00:00', 'c');
ADMIN FLUSH_TABLE('parent');
ADMIN FLUSH_TABLE('child');
-- Query A: single table with time index filter (baseline).
-- The scan should have partial_filters with the time condition.
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE peers=\[\d+\(\d+,\s+\d+\),\s\] peers=[REDACTED]
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT * FROM parent WHERE ts >= '2024-01-30 00:00:00';
-- Query B: flat LEFT JOIN with WHERE on parent's ts.
-- The filter should be pushed into the left MergeScan remote_input,
-- appearing as partial_filters=[...] in the parent TableScan.
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE peers=\[\d+\(\d+,\s+\d+\),\s\] peers=[REDACTED]
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT * FROM parent p LEFT JOIN child c ON p.k = c.k WHERE p.ts >= '2024-01-30 00:00:00';
-- Query C: subquery pre-filter (workaround from #8338).
-- Should produce the same logical scan filter shape as query B.
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE peers=\[\d+\(\d+,\s+\d+\),\s\] peers=[REDACTED]
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT * FROM (SELECT * FROM parent WHERE ts >= '2024-01-30 00:00:00') p LEFT JOIN child c ON p.k = c.k;
DROP TABLE parent;
DROP TABLE child;

View File

@@ -0,0 +1,285 @@
-- Edge-case coverage for the selected pre-MergeScan optimizer prepass.
-- These cases focus on keeping distributed pushdown safe around subqueries,
-- nullable join keys, deterministic expressions, and nested predicates.
CREATE TABLE fact (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
val DOUBLE,
`region` STRING,
PRIMARY KEY (k)
) ENGINE = mito;
Affected Rows: 0
CREATE TABLE dim (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
label STRING,
PRIMARY KEY (k)
) ENGINE = mito;
Affected Rows: 0
CREATE TABLE vals (
ts TIMESTAMP(3) TIME INDEX,
x INT,
y INT
) ENGINE = mito;
Affected Rows: 0
INSERT INTO fact VALUES
('2024-01-30 00:00:00', 'a', 1.0, 'us'),
('2024-01-30 01:00:00', 'b', 2.0, 'eu'),
('2024-01-30 02:00:00', 'c', 3.0, 'us'),
('2024-01-30 03:00:00', 'd', 0.5, 'eu');
Affected Rows: 4
INSERT INTO dim VALUES
('2024-01-30 00:00:00', 'a', 'label_a'),
('2024-01-31 00:00:00', 'c', 'label_c'),
('2024-02-01 00:00:00', NULL, 'label_null'),
('2024-02-02 00:00:00', 'e', 'label_e');
Affected Rows: 4
INSERT INTO vals VALUES
('2024-01-30 00:00:00', 1, 10),
('2024-01-30 01:00:00', 2, 20),
('2024-01-30 02:00:00', 3, NULL),
('2024-01-30 03:00:00', NULL, 40);
Affected Rows: 4
ADMIN FLUSH_TABLE('fact');
+---------------------------+
| ADMIN FLUSH_TABLE('fact') |
+---------------------------+
| 0 |
+---------------------------+
ADMIN FLUSH_TABLE('dim');
+--------------------------+
| ADMIN FLUSH_TABLE('dim') |
+--------------------------+
| 0 |
+--------------------------+
ADMIN FLUSH_TABLE('vals');
+---------------------------+
| ADMIN FLUSH_TABLE('vals') |
+---------------------------+
| 0 |
+---------------------------+
-- Correlated EXISTS plus LEFT JOIN: static fact-side filters should still be
-- pushed into the fact scan, while the correlated predicate is decorrelated
-- before PushDownFilter so no raw subquery or outer reference reaches
-- partial_filters.
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT f.k, f.val FROM fact f
LEFT JOIN dim d ON f.k = d.k
WHERE f.ts >= '2024-01-30 00:00:00'
AND f.val > 1.0
AND EXISTS (SELECT 1 FROM vals v WHERE v.x = CAST(f.val AS INT));
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | LeftSemi Join: CAST(f.val AS Int32) = __correlated_sq_1.x |
| | Projection: f.k, f.val |
| | Left Join: f.k = d.k |
| | Projection: f.k, f.val |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: f |
| | Filter: CAST(fact.val AS Int32) IS NOT NULL AND fact.ts >= TimestampMillisecond(1706572800000, None) AND fact.val > Float64(1) |
| | TableScan: fact, partial_filters=[fact.ts >= TimestampMillisecond(1706572800000, None), fact.val > Float64(1), CAST(fact.val AS Int32) IS NOT NULL] |
| | ]] |
| | Projection: d.k |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: d |
| | Filter: dim.k IS NOT NULL |
| | TableScan: dim, partial_filters=[dim.k IS NOT NULL] |
| | ]] |
| | Projection: __correlated_sq_1.x |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: __correlated_sq_1 |
| | Projection: Int64(1), v.x |
| | SubqueryAlias: v |
| | Filter: vals.x IS NOT NULL |
| | TableScan: vals, partial_filters=[vals.x IS NOT NULL] |
| | ]] |
| physical_plan | HashJoinExec: mode=Partitioned, join_type=LeftSemi, on=[(CAST(f.val AS Int32)@2, x@0)], projection=[k@0, val@1] |
| | RepartitionExec: partitioning=Hash([REDACTED
| | ProjectionExec: expr=[k@0 as k, val@1 as val, CAST(val@1 AS Int32) as CAST(f.val AS Int32)] |
| | HashJoinExec: mode=Partitioned, join_type=Left, on=[(k@0, k@0)], projection=[k@0, val@1] |
| | RepartitionExec: partitioning=Hash([REDACTED
| | ProjectionExec: expr=[k@1 as k, val@2 as val] |
| | MergeScanExec: REDACTED
| | RepartitionExec: partitioning=Hash([REDACTED
| | ProjectionExec: expr=[k@1 as k] |
| | MergeScanExec: REDACTED
| | RepartitionExec: partitioning=Hash([REDACTED
| | ProjectionExec: expr=[x@1 as x] |
| | MergeScanExec: REDACTED
| | |
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
SELECT f.k, f.val, d.label FROM fact f
LEFT JOIN dim d ON f.k = d.k
WHERE f.ts >= '2024-01-30 00:00:00'
AND f.val > 1.0
AND EXISTS (SELECT 1 FROM vals v WHERE v.x = CAST(f.val AS INT))
ORDER BY f.k;
+---+-----+---------+
| k | val | label |
+---+-----+---------+
| b | 2.0 | |
| c | 3.0 | label_c |
+---+-----+---------+
-- Semi/anti joins with nullable build-side keys. NULL keys from dim must not
-- produce matches, and must not suppress NOT EXISTS results.
SELECT f.k, f.val FROM fact f
WHERE f.k IN (SELECT k FROM dim)
ORDER BY f.k;
+---+-----+
| k | val |
+---+-----+
| a | 1.0 |
| c | 3.0 |
+---+-----+
SELECT f.k, f.val FROM fact f
WHERE EXISTS (SELECT 1 FROM dim d WHERE d.k = f.k)
ORDER BY f.k;
+---+-----+
| k | val |
+---+-----+
| a | 1.0 |
| c | 3.0 |
+---+-----+
SELECT f.k, f.val FROM fact f
WHERE NOT EXISTS (SELECT 1 FROM dim d WHERE d.k = f.k)
ORDER BY f.k;
+---+-----+
| k | val |
+---+-----+
| b | 2.0 |
| d | 0.5 |
+---+-----+
-- Deterministic expression pushdown: casts may be pushed, but only as scan-local
-- expressions that the remote scan can evaluate.
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT * FROM fact WHERE CAST(fact.val AS INT) > 0;
+---------------+---------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: fact.ts, fact.k, fact.val, fact.region |
| | Filter: CAST(fact.val AS Int32) > Int32(0) |
| | TableScan: fact, partial_filters=[CAST(fact.val AS Int32) > Int32(0)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+---------------------------------------------------------------------------+
CREATE TABLE edge_events (
ts TIMESTAMP(3) TIME INDEX,
host STRING,
rack INT,
cpu DOUBLE,
PRIMARY KEY (host)
)
PARTITION ON COLUMNS (rack) (
rack < 10,
rack >= 10 AND rack < 20,
rack >= 20
) ENGINE = mito;
Affected Rows: 0
INSERT INTO edge_events VALUES
('2024-01-30 00:00:00', 'h1', 5, 0.5),
('2024-01-30 01:00:00', 'h2', 15, 0.8),
('2024-01-30 02:00:00', 'h3', 25, 0.3),
('2024-01-30 03:00:00', 'h4', 25, 0.1);
Affected Rows: 4
ADMIN FLUSH_TABLE('edge_events');
+----------------------------------+
| ADMIN FLUSH_TABLE('edge_events') |
+----------------------------------+
| 0 |
+----------------------------------+
-- Nested OR/AND over a partition column plus non-partition predicates. This
-- covers filter extraction without treating conjunctive scan filters as
-- independent top-level pruning predicates.
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT * FROM edge_events
WHERE (rack > 5 AND cpu > 0.4) OR (host = 'h3');
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: edge_events.ts, edge_events.host, edge_events.rack, edge_events.cpu |
| | Filter: edge_events.rack > Int32(5) AND edge_events.cpu > Float64(0.4) OR edge_events.host = Utf8("h3") |
| | TableScan: edge_events, partial_filters=[edge_events.rack > Int32(5) AND edge_events.cpu > Float64(0.4) OR edge_events.host = Utf8("h3")] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
| | |
+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
SELECT host, rack, cpu FROM edge_events
WHERE (rack > 5 AND cpu > 0.4) OR (host = 'h3')
ORDER BY host;
+------+------+-----+
| host | rack | cpu |
+------+------+-----+
| h2 | 15 | 0.8 |
| h3 | 25 | 0.3 |
+------+------+-----+
DROP TABLE fact;
Affected Rows: 0
DROP TABLE dim;
Affected Rows: 0
DROP TABLE vals;
Affected Rows: 0
DROP TABLE edge_events;
Affected Rows: 0

View File

@@ -0,0 +1,129 @@
-- Edge-case coverage for the selected pre-MergeScan optimizer prepass.
-- These cases focus on keeping distributed pushdown safe around subqueries,
-- nullable join keys, deterministic expressions, and nested predicates.
CREATE TABLE fact (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
val DOUBLE,
`region` STRING,
PRIMARY KEY (k)
) ENGINE = mito;
CREATE TABLE dim (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
label STRING,
PRIMARY KEY (k)
) ENGINE = mito;
CREATE TABLE vals (
ts TIMESTAMP(3) TIME INDEX,
x INT,
y INT
) ENGINE = mito;
INSERT INTO fact VALUES
('2024-01-30 00:00:00', 'a', 1.0, 'us'),
('2024-01-30 01:00:00', 'b', 2.0, 'eu'),
('2024-01-30 02:00:00', 'c', 3.0, 'us'),
('2024-01-30 03:00:00', 'd', 0.5, 'eu');
INSERT INTO dim VALUES
('2024-01-30 00:00:00', 'a', 'label_a'),
('2024-01-31 00:00:00', 'c', 'label_c'),
('2024-02-01 00:00:00', NULL, 'label_null'),
('2024-02-02 00:00:00', 'e', 'label_e');
INSERT INTO vals VALUES
('2024-01-30 00:00:00', 1, 10),
('2024-01-30 01:00:00', 2, 20),
('2024-01-30 02:00:00', 3, NULL),
('2024-01-30 03:00:00', NULL, 40);
ADMIN FLUSH_TABLE('fact');
ADMIN FLUSH_TABLE('dim');
ADMIN FLUSH_TABLE('vals');
-- Correlated EXISTS plus LEFT JOIN: static fact-side filters should still be
-- pushed into the fact scan, while the correlated predicate is decorrelated
-- before PushDownFilter so no raw subquery or outer reference reaches
-- partial_filters.
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT f.k, f.val FROM fact f
LEFT JOIN dim d ON f.k = d.k
WHERE f.ts >= '2024-01-30 00:00:00'
AND f.val > 1.0
AND EXISTS (SELECT 1 FROM vals v WHERE v.x = CAST(f.val AS INT));
SELECT f.k, f.val, d.label FROM fact f
LEFT JOIN dim d ON f.k = d.k
WHERE f.ts >= '2024-01-30 00:00:00'
AND f.val > 1.0
AND EXISTS (SELECT 1 FROM vals v WHERE v.x = CAST(f.val AS INT))
ORDER BY f.k;
-- Semi/anti joins with nullable build-side keys. NULL keys from dim must not
-- produce matches, and must not suppress NOT EXISTS results.
SELECT f.k, f.val FROM fact f
WHERE f.k IN (SELECT k FROM dim)
ORDER BY f.k;
SELECT f.k, f.val FROM fact f
WHERE EXISTS (SELECT 1 FROM dim d WHERE d.k = f.k)
ORDER BY f.k;
SELECT f.k, f.val FROM fact f
WHERE NOT EXISTS (SELECT 1 FROM dim d WHERE d.k = f.k)
ORDER BY f.k;
-- Deterministic expression pushdown: casts may be pushed, but only as scan-local
-- expressions that the remote scan can evaluate.
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT * FROM fact WHERE CAST(fact.val AS INT) > 0;
CREATE TABLE edge_events (
ts TIMESTAMP(3) TIME INDEX,
host STRING,
rack INT,
cpu DOUBLE,
PRIMARY KEY (host)
)
PARTITION ON COLUMNS (rack) (
rack < 10,
rack >= 10 AND rack < 20,
rack >= 20
) ENGINE = mito;
INSERT INTO edge_events VALUES
('2024-01-30 00:00:00', 'h1', 5, 0.5),
('2024-01-30 01:00:00', 'h2', 15, 0.8),
('2024-01-30 02:00:00', 'h3', 25, 0.3),
('2024-01-30 03:00:00', 'h4', 25, 0.1);
ADMIN FLUSH_TABLE('edge_events');
-- Nested OR/AND over a partition column plus non-partition predicates. This
-- covers filter extraction without treating conjunctive scan filters as
-- independent top-level pruning predicates.
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT * FROM edge_events
WHERE (rack > 5 AND cpu > 0.4) OR (host = 'h3');
SELECT host, rack, cpu FROM edge_events
WHERE (rack > 5 AND cpu > 0.4) OR (host = 'h3')
ORDER BY host;
DROP TABLE fact;
DROP TABLE dim;
DROP TABLE vals;
DROP TABLE edge_events;

View File

@@ -0,0 +1,93 @@
-- Document the current aliased SQL LATERAL limitation and guard the remote
-- scan boundary. DataFusion's DecorrelateLateralJoin does not currently match
-- the SubqueryAlias(Subquery) shape produced by `LATERAL (...) d`, so this query
-- is still expected to fail physical planning with an outer_ref expression. The
-- important regression assertion is that the remaining outer_ref predicate must
-- NOT be advertised as a remote TableScan.partial_filters predicate.
CREATE TABLE lateral_fact (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
val DOUBLE,
PRIMARY KEY (k)
) ENGINE = mito;
Affected Rows: 0
CREATE TABLE lateral_dim (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
threshold DOUBLE,
PRIMARY KEY (k)
) ENGINE = mito;
Affected Rows: 0
INSERT INTO lateral_fact VALUES
('2024-01-30 00:00:00', 'a', 10.0),
('2024-01-30 01:00:00', 'b', 20.0);
Affected Rows: 2
INSERT INTO lateral_dim VALUES
('2024-01-30 00:00:00', 'a', 5.0),
('2024-01-30 01:00:00', 'b', 25.0);
Affected Rows: 2
ADMIN FLUSH_TABLE('lateral_fact');
+-----------------------------------+
| ADMIN FLUSH_TABLE('lateral_fact') |
+-----------------------------------+
| 0 |
+-----------------------------------+
ADMIN FLUSH_TABLE('lateral_dim');
+----------------------------------+
| ADMIN FLUSH_TABLE('lateral_dim') |
+----------------------------------+
| 0 |
+----------------------------------+
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
EXPLAIN SELECT f.k, d.threshold
FROM lateral_fact f,
LATERAL (
SELECT threshold FROM lateral_dim d WHERE d.k = f.k
) d
WHERE f.val > d.threshold
ORDER BY f.k;
+---------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | Sort: f.k ASC NULLS LAST |
| | Projection: f.k, d.threshold |
| | Inner Join: Filter: f.val > d.threshold |
| | Projection: f.k, f.val |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: f |
| | TableScan: lateral_fact |
| | ]] |
| | SubqueryAlias: d |
| | Subquery: |
| | SubqueryAlias: d |
| | Projection: lateral_dim.threshold |
| | Filter: lateral_dim.k = outer_ref(f.k) |
| | Projection: lateral_dim.k, lateral_dim.threshold |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | TableScan: lateral_dim |
| | ]] |
| physical_plan_error | This feature is not implemented: Physical plan does not support logical expression OuterReferenceColumn(Field { name: "k", data_type: Utf8, nullable: true }, Column { relation: Some(Bare { table: "f" }), name: "k" }) |
+---------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
DROP TABLE lateral_fact;
Affected Rows: 0
DROP TABLE lateral_dim;
Affected Rows: 0

View File

@@ -0,0 +1,44 @@
-- Document the current aliased SQL LATERAL limitation and guard the remote
-- scan boundary. DataFusion's DecorrelateLateralJoin does not currently match
-- the SubqueryAlias(Subquery) shape produced by `LATERAL (...) d`, so this query
-- is still expected to fail physical planning with an outer_ref expression. The
-- important regression assertion is that the remaining outer_ref predicate must
-- NOT be advertised as a remote TableScan.partial_filters predicate.
CREATE TABLE lateral_fact (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
val DOUBLE,
PRIMARY KEY (k)
) ENGINE = mito;
CREATE TABLE lateral_dim (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
threshold DOUBLE,
PRIMARY KEY (k)
) ENGINE = mito;
INSERT INTO lateral_fact VALUES
('2024-01-30 00:00:00', 'a', 10.0),
('2024-01-30 01:00:00', 'b', 20.0);
INSERT INTO lateral_dim VALUES
('2024-01-30 00:00:00', 'a', 5.0),
('2024-01-30 01:00:00', 'b', 25.0);
ADMIN FLUSH_TABLE('lateral_fact');
ADMIN FLUSH_TABLE('lateral_dim');
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
EXPLAIN SELECT f.k, d.threshold
FROM lateral_fact f,
LATERAL (
SELECT threshold FROM lateral_dim d WHERE d.k = f.k
) d
WHERE f.val > d.threshold
ORDER BY f.k;
DROP TABLE lateral_fact;
DROP TABLE lateral_dim;

View File

@@ -0,0 +1,158 @@
-- Set-comparison subqueries (`ANY`/`ALL`) must be rewritten before
-- PushDownFilter. Otherwise the set-comparison subquery can be pushed into
-- TableScan.partial_filters, which is not a valid remote scan filter.
CREATE TABLE sc_t (
ts TIMESTAMP(3) TIME INDEX,
v INT,
PRIMARY KEY (v)
) ENGINE = mito;
Affected Rows: 0
CREATE TABLE sc_s (
ts TIMESTAMP(3) TIME INDEX,
v INT
) ENGINE = mito;
Affected Rows: 0
INSERT INTO sc_t VALUES
('2024-01-30 00:00:00', 1),
('2024-01-30 01:00:00', 6),
('2024-01-30 02:00:00', 10);
Affected Rows: 3
INSERT INTO sc_s VALUES
('2024-01-30 00:00:00', 5),
('2024-01-30 01:00:00', NULL);
Affected Rows: 2
ADMIN FLUSH_TABLE('sc_t');
+---------------------------+
| ADMIN FLUSH_TABLE('sc_t') |
+---------------------------+
| 0 |
+---------------------------+
ADMIN FLUSH_TABLE('sc_s');
+---------------------------+
| ADMIN FLUSH_TABLE('sc_s') |
+---------------------------+
| 0 |
+---------------------------+
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
EXPLAIN SELECT v FROM sc_t WHERE v > ANY(SELECT v FROM sc_s) ORDER BY v;
+---------------+---------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+---------------------------------------------------------------------------------------------------------------+
| logical_plan | Sort: sc_t.v ASC NULLS LAST |
| | Projection: sc_t.v |
| | Filter: __correlated_sq_1.mark OR __correlated_sq_2.mark AND NOT __correlated_sq_1.mark AND Boolean(NULL) |
| | LeftMark Join: Filter: sc_t.v > __correlated_sq_2.v IS NULL |
| | Filter: __correlated_sq_1.mark OR NOT __correlated_sq_1.mark AND Boolean(NULL) |
| | LeftMark Join: Filter: sc_t.v > __correlated_sq_1.v IS TRUE |
| | Projection: sc_t.v |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | TableScan: sc_t |
| | ]] |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: __correlated_sq_1.v |
| | SubqueryAlias: __correlated_sq_1 |
| | Projection: sc_s.v |
| | TableScan: sc_s |
| | ]] |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: __correlated_sq_2.v |
| | SubqueryAlias: __correlated_sq_2 |
| | Projection: sc_s.v |
| | TableScan: sc_s |
| | ]] |
| physical_plan | SortPreservingMergeExec: [v@0 ASC NULLS LAST] |
| | SortExec: expr=[v@0 ASC NULLS LAST], preserve_partitioning=[true] |
| | FilterExec: mark@1 OR mark@2 AND NOT mark@1 AND NULL, projection=[v@0] |
| | NestedLoopJoinExec: join_type=LeftMark, filter=v@0 > v@1 IS NULL |
| | CoalescePartitionsExec |
| | FilterExec: mark@1 OR NOT mark@1 AND NULL |
| | NestedLoopJoinExec: join_type=LeftMark, filter=(v@0 > v@1) IS NOT DISTINCT FROM true |
| | CoalescePartitionsExec |
| | ProjectionExec: expr=[v@1 as v] |
| | MergeScanExec: REDACTED
| | MergeScanExec: REDACTED
| | MergeScanExec: REDACTED
| | |
+---------------+---------------------------------------------------------------------------------------------------------------+
SELECT v FROM sc_t WHERE v > ANY(SELECT v FROM sc_s) ORDER BY v;
+----+
| v |
+----+
| 6 |
| 10 |
+----+
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
EXPLAIN SELECT v FROM sc_t WHERE v != ALL(SELECT v FROM sc_s) ORDER BY v;
+---------------+----------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+----------------------------------------------------------------------------------------------------+
| logical_plan | Sort: sc_t.v ASC NULLS LAST |
| | Projection: sc_t.v |
| | Filter: __correlated_sq_2.mark AND Boolean(NULL) OR NOT __correlated_sq_2.mark |
| | LeftMark Join: Filter: sc_t.v != __correlated_sq_2.v IS NULL |
| | Projection: sc_t.v |
| | Filter: NOT __correlated_sq_1.mark |
| | LeftMark Join: Filter: sc_t.v != __correlated_sq_1.v IS FALSE |
| | Projection: sc_t.v |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | TableScan: sc_t |
| | ]] |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: __correlated_sq_1.v |
| | SubqueryAlias: __correlated_sq_1 |
| | Projection: sc_s.v |
| | TableScan: sc_s |
| | ]] |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: __correlated_sq_2.v |
| | SubqueryAlias: __correlated_sq_2 |
| | Projection: sc_s.v |
| | TableScan: sc_s |
| | ]] |
| physical_plan | SortPreservingMergeExec: [v@0 ASC NULLS LAST] |
| | SortExec: expr=[v@0 ASC NULLS LAST], preserve_partitioning=[true] |
| | FilterExec: mark@1 AND NULL OR NOT mark@1, projection=[v@0] |
| | NestedLoopJoinExec: join_type=LeftMark, filter=v@0 != v@1 IS NULL |
| | CoalescePartitionsExec |
| | FilterExec: NOT mark@1, projection=[v@0] |
| | NestedLoopJoinExec: join_type=LeftMark, filter=(v@0 != v@1) IS NOT DISTINCT FROM false |
| | CoalescePartitionsExec |
| | ProjectionExec: expr=[v@1 as v] |
| | MergeScanExec: REDACTED
| | MergeScanExec: REDACTED
| | MergeScanExec: REDACTED
| | |
+---------------+----------------------------------------------------------------------------------------------------+
SELECT v FROM sc_t WHERE v != ALL(SELECT v FROM sc_s) ORDER BY v;
++
++
DROP TABLE sc_t;
Affected Rows: 0
DROP TABLE sc_s;
Affected Rows: 0

View File

@@ -0,0 +1,41 @@
-- Set-comparison subqueries (`ANY`/`ALL`) must be rewritten before
-- PushDownFilter. Otherwise the set-comparison subquery can be pushed into
-- TableScan.partial_filters, which is not a valid remote scan filter.
CREATE TABLE sc_t (
ts TIMESTAMP(3) TIME INDEX,
v INT,
PRIMARY KEY (v)
) ENGINE = mito;
CREATE TABLE sc_s (
ts TIMESTAMP(3) TIME INDEX,
v INT
) ENGINE = mito;
INSERT INTO sc_t VALUES
('2024-01-30 00:00:00', 1),
('2024-01-30 01:00:00', 6),
('2024-01-30 02:00:00', 10);
INSERT INTO sc_s VALUES
('2024-01-30 00:00:00', 5),
('2024-01-30 01:00:00', NULL);
ADMIN FLUSH_TABLE('sc_t');
ADMIN FLUSH_TABLE('sc_s');
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
EXPLAIN SELECT v FROM sc_t WHERE v > ANY(SELECT v FROM sc_s) ORDER BY v;
SELECT v FROM sc_t WHERE v > ANY(SELECT v FROM sc_s) ORDER BY v;
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
EXPLAIN SELECT v FROM sc_t WHERE v != ALL(SELECT v FROM sc_s) ORDER BY v;
SELECT v FROM sc_t WHERE v != ALL(SELECT v FROM sc_s) ORDER BY v;
DROP TABLE sc_t;
DROP TABLE sc_s;

View File

@@ -0,0 +1,114 @@
-- Scalar subquery predicates must be converted before PushDownFilter. Otherwise
-- a scalar subquery can be pushed into TableScan.partial_filters, which is not a
-- valid remote scan filter.
CREATE TABLE scalar_fact (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
val DOUBLE,
PRIMARY KEY (k)
) ENGINE = mito;
Affected Rows: 0
CREATE TABLE scalar_dim (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
threshold DOUBLE,
PRIMARY KEY (k)
) ENGINE = mito;
Affected Rows: 0
INSERT INTO scalar_fact VALUES
('2024-01-30 00:00:00', 'a', 10.0),
('2024-01-30 01:00:00', 'b', 20.0),
('2024-01-30 02:00:00', 'c', 30.0),
('2024-01-30 03:00:00', 'd', 40.0);
Affected Rows: 4
INSERT INTO scalar_dim VALUES
('2024-01-30 00:00:00', 'a', 5.0),
('2024-01-30 01:00:00', 'b', 25.0),
('2024-01-30 02:00:00', 'c', NULL);
Affected Rows: 3
ADMIN FLUSH_TABLE('scalar_fact');
+----------------------------------+
| ADMIN FLUSH_TABLE('scalar_fact') |
+----------------------------------+
| 0 |
+----------------------------------+
ADMIN FLUSH_TABLE('scalar_dim');
+---------------------------------+
| ADMIN FLUSH_TABLE('scalar_dim') |
+---------------------------------+
| 0 |
+---------------------------------+
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT f.k, f.val FROM scalar_fact f
WHERE f.val > (
SELECT max(d.threshold) FROM scalar_dim d WHERE d.k = f.k
)
ORDER BY f.k;
+---------------+----------------------------------------------------------------------------------------------------------------------------------+
| plan_type | plan |
+---------------+----------------------------------------------------------------------------------------------------------------------------------+
| logical_plan | Sort: f.k ASC NULLS LAST |
| | Projection: f.k, f.val |
| | Inner Join: f.k = __scalar_sq_1.k Filter: f.val > __scalar_sq_1.max(d.threshold) |
| | Projection: f.k, f.val |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: f |
| | TableScan: scalar_fact |
| | ]] |
| | Projection: __scalar_sq_1.max(d.threshold), __scalar_sq_1.k |
| | MergeScan [is_placeholder=false, remote_input=[ |
| | SubqueryAlias: __scalar_sq_1 |
| | Projection: max(d.threshold), d.k, __always_true |
| | Aggregate: groupBy=[[d.k, Boolean(true) AS __always_true]], aggr=[[max(d.threshold)]] |
| | SubqueryAlias: d |
| | Filter: scalar_dim.k IS NOT NULL |
| | TableScan: scalar_dim, partial_filters=[scalar_dim.k IS NOT NULL] |
| | ]] |
| physical_plan | SortPreservingMergeExec: [k@0 ASC NULLS LAST] |
| | SortExec: expr=[k@0 ASC NULLS LAST], preserve_partitioning=[true] |
| | HashJoinExec: mode=Partitioned, join_type=Inner, on=[(k@0, k@1)], filter=val@0 > max(d.threshold)@1, projection=[k@0, val@1] |
| | RepartitionExec: partitioning=Hash([REDACTED
| | ProjectionExec: expr=[k@1 as k, val@2 as val] |
| | MergeScanExec: REDACTED
| | RepartitionExec: partitioning=Hash([REDACTED
| | ProjectionExec: expr=[max(d.threshold)@0 as max(d.threshold), k@1 as k] |
| | MergeScanExec: REDACTED
| | |
+---------------+----------------------------------------------------------------------------------------------------------------------------------+
SELECT f.k, f.val FROM scalar_fact f
WHERE f.val > (
SELECT max(d.threshold) FROM scalar_dim d WHERE d.k = f.k
)
ORDER BY f.k;
+---+------+
| k | val |
+---+------+
| a | 10.0 |
+---+------+
DROP TABLE scalar_fact;
Affected Rows: 0
DROP TABLE scalar_dim;
Affected Rows: 0

View File

@@ -0,0 +1,50 @@
-- Scalar subquery predicates must be converted before PushDownFilter. Otherwise
-- a scalar subquery can be pushed into TableScan.partial_filters, which is not a
-- valid remote scan filter.
CREATE TABLE scalar_fact (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
val DOUBLE,
PRIMARY KEY (k)
) ENGINE = mito;
CREATE TABLE scalar_dim (
ts TIMESTAMP(3) TIME INDEX,
k STRING,
threshold DOUBLE,
PRIMARY KEY (k)
) ENGINE = mito;
INSERT INTO scalar_fact VALUES
('2024-01-30 00:00:00', 'a', 10.0),
('2024-01-30 01:00:00', 'b', 20.0),
('2024-01-30 02:00:00', 'c', 30.0),
('2024-01-30 03:00:00', 'd', 40.0);
INSERT INTO scalar_dim VALUES
('2024-01-30 00:00:00', 'a', 5.0),
('2024-01-30 01:00:00', 'b', 25.0),
('2024-01-30 02:00:00', 'c', NULL);
ADMIN FLUSH_TABLE('scalar_fact');
ADMIN FLUSH_TABLE('scalar_dim');
-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED
-- SQLNESS REPLACE (peers.*) REDACTED
-- SQLNESS REPLACE Hash\(\[[^\]]+\],.* Hash([REDACTED
-- SQLNESS REPLACE input_partitions=\d+ input_partitions=REDACTED
EXPLAIN SELECT f.k, f.val FROM scalar_fact f
WHERE f.val > (
SELECT max(d.threshold) FROM scalar_dim d WHERE d.k = f.k
)
ORDER BY f.k;
SELECT f.k, f.val FROM scalar_fact f
WHERE f.val > (
SELECT max(d.threshold) FROM scalar_dim d WHERE d.k = f.k
)
ORDER BY f.k;
DROP TABLE scalar_fact;
DROP TABLE scalar_dim;

View File

@@ -67,7 +67,7 @@ WHERE
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: cpu.rack |
| | Filter: cpu.usage_small = Int16(10) OR cpu.usage_small = Int16(20) |
| | TableScan: cpu |
| | TableScan: cpu, partial_filters=[cpu.usage_small = Int16(10) OR cpu.usage_small = Int16(20)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED
@@ -88,7 +88,7 @@ WHERE
| logical_plan | MergeScan [is_placeholder=false, remote_input=[ |
| | Projection: cpu.rack |
| | Filter: cpu.usage_small >= Int16(10) AND cpu.usage_small <= Int16(20) |
| | TableScan: cpu |
| | TableScan: cpu, partial_filters=[cpu.usage_small >= Int16(10), cpu.usage_small <= Int16(20)] |
| | ]] |
| physical_plan | CooperativeExec |
| | MergeScanExec: REDACTED