diff --git a/.coderabbit.yaml b/.coderabbit.yaml deleted file mode 100644 index 01bc346444..0000000000 --- a/.coderabbit.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json -language: "en-US" -early_access: false -reviews: - profile: "chill" - request_changes_workflow: false - high_level_summary: true - poem: true - review_status: true - collapse_walkthrough: false - auto_review: - enabled: false - drafts: false -chat: - auto_reply: true diff --git a/.github/actions/setup-greptimedb-cluster/with-remote-wal.yaml b/.github/actions/setup-greptimedb-cluster/with-remote-wal.yaml index a97f921f8c..26e354f3d0 100644 --- a/.github/actions/setup-greptimedb-cluster/with-remote-wal.yaml +++ b/.github/actions/setup-greptimedb-cluster/with-remote-wal.yaml @@ -2,13 +2,14 @@ meta: configData: |- [runtime] global_rt_size = 4 - + [wal] provider = "kafka" broker_endpoints = ["kafka.kafka-cluster.svc.cluster.local:9092"] num_topics = 3 + auto_prune_interval = "30s" + trigger_flush_threshold = 100 - [datanode] [datanode.client] timeout = "120s" @@ -22,6 +23,7 @@ datanode: provider = "kafka" broker_endpoints = ["kafka.kafka-cluster.svc.cluster.local:9092"] linger = "2ms" + overwrite_entry_start_id = true frontend: configData: |- [runtime] diff --git a/.github/scripts/create-version.sh b/.github/scripts/create-version.sh index e87c74cffb..0e8218ba01 100755 --- a/.github/scripts/create-version.sh +++ b/.github/scripts/create-version.sh @@ -10,22 +10,22 @@ set -e function create_version() { # Read from envrionment variables. if [ -z "$GITHUB_EVENT_NAME" ]; then - echo "GITHUB_EVENT_NAME is empty" + echo "GITHUB_EVENT_NAME is empty" >&2 exit 1 fi if [ -z "$NEXT_RELEASE_VERSION" ]; then - echo "NEXT_RELEASE_VERSION is empty" - exit 1 + echo "NEXT_RELEASE_VERSION is empty, use version from Cargo.toml" >&2 + export NEXT_RELEASE_VERSION=$(grep '^version = ' Cargo.toml | cut -d '"' -f 2 | head -n 1) fi if [ -z "$NIGHTLY_RELEASE_PREFIX" ]; then - echo "NIGHTLY_RELEASE_PREFIX is empty" + echo "NIGHTLY_RELEASE_PREFIX is empty" >&2 exit 1 fi # Reuse $NEXT_RELEASE_VERSION to identify whether it's a nightly build. - # It will be like 'nigtly-20230808-7d0d8dc6'. + # It will be like 'nightly-20230808-7d0d8dc6'. if [ "$NEXT_RELEASE_VERSION" = nightly ]; then echo "$NIGHTLY_RELEASE_PREFIX-$(date "+%Y%m%d")-$(git rev-parse --short HEAD)" exit 0 @@ -35,7 +35,7 @@ function create_version() { # It will be like 'dev-2023080819-f0e7216c'. if [ "$NEXT_RELEASE_VERSION" = dev ]; then if [ -z "$COMMIT_SHA" ]; then - echo "COMMIT_SHA is empty in dev build" + echo "COMMIT_SHA is empty in dev build" >&2 exit 1 fi echo "dev-$(date "+%Y%m%d-%s")-$(echo "$COMMIT_SHA" | cut -c1-8)" @@ -45,7 +45,7 @@ function create_version() { # Note: Only output 'version=xxx' to stdout when everything is ok, so that it can be used in GitHub Actions Outputs. if [ "$GITHUB_EVENT_NAME" = push ]; then if [ -z "$GITHUB_REF_NAME" ]; then - echo "GITHUB_REF_NAME is empty in push event" + echo "GITHUB_REF_NAME is empty in push event" >&2 exit 1 fi echo "$GITHUB_REF_NAME" @@ -54,15 +54,15 @@ function create_version() { elif [ "$GITHUB_EVENT_NAME" = schedule ]; then echo "$NEXT_RELEASE_VERSION-$NIGHTLY_RELEASE_PREFIX-$(date "+%Y%m%d")" else - echo "Unsupported GITHUB_EVENT_NAME: $GITHUB_EVENT_NAME" + echo "Unsupported GITHUB_EVENT_NAME: $GITHUB_EVENT_NAME" >&2 exit 1 fi } # You can run as following examples: -# GITHUB_EVENT_NAME=push NEXT_RELEASE_VERSION=v0.4.0 NIGHTLY_RELEASE_PREFIX=nigtly GITHUB_REF_NAME=v0.3.0 ./create-version.sh -# GITHUB_EVENT_NAME=workflow_dispatch NEXT_RELEASE_VERSION=v0.4.0 NIGHTLY_RELEASE_PREFIX=nigtly ./create-version.sh -# GITHUB_EVENT_NAME=schedule NEXT_RELEASE_VERSION=v0.4.0 NIGHTLY_RELEASE_PREFIX=nigtly ./create-version.sh -# GITHUB_EVENT_NAME=schedule NEXT_RELEASE_VERSION=nightly NIGHTLY_RELEASE_PREFIX=nigtly ./create-version.sh -# GITHUB_EVENT_NAME=workflow_dispatch COMMIT_SHA=f0e7216c4bb6acce9b29a21ec2d683be2e3f984a NEXT_RELEASE_VERSION=dev NIGHTLY_RELEASE_PREFIX=nigtly ./create-version.sh +# GITHUB_EVENT_NAME=push NEXT_RELEASE_VERSION=v0.4.0 NIGHTLY_RELEASE_PREFIX=nightly GITHUB_REF_NAME=v0.3.0 ./create-version.sh +# GITHUB_EVENT_NAME=workflow_dispatch NEXT_RELEASE_VERSION=v0.4.0 NIGHTLY_RELEASE_PREFIX=nightly ./create-version.sh +# GITHUB_EVENT_NAME=schedule NEXT_RELEASE_VERSION=v0.4.0 NIGHTLY_RELEASE_PREFIX=nightly ./create-version.sh +# GITHUB_EVENT_NAME=schedule NEXT_RELEASE_VERSION=nightly NIGHTLY_RELEASE_PREFIX=nightly ./create-version.sh +# GITHUB_EVENT_NAME=workflow_dispatch COMMIT_SHA=f0e7216c4bb6acce9b29a21ec2d683be2e3f984a NEXT_RELEASE_VERSION=dev NIGHTLY_RELEASE_PREFIX=nightly ./create-version.sh create_version diff --git a/.github/workflows/grafana.yml b/.github/workflows/grafana.yml index 139ea85b05..29fa182998 100644 --- a/.github/workflows/grafana.yml +++ b/.github/workflows/grafana.yml @@ -21,32 +21,6 @@ jobs: run: sudo apt-get install -y jq # Make the check.sh script executable - - name: Make check.sh executable - run: chmod +x grafana/check.sh - - # Run the check.sh script - - name: Run check.sh - run: ./grafana/check.sh - - # Only run summary.sh for pull_request events (not for merge queues or final pushes) - - name: Check if this is a pull request - id: check-pr + - name: Check grafana dashboards run: | - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - echo "is_pull_request=true" >> $GITHUB_OUTPUT - else - echo "is_pull_request=false" >> $GITHUB_OUTPUT - fi - - # Make the summary.sh script executable - - name: Make summary.sh executable - if: steps.check-pr.outputs.is_pull_request == 'true' - run: chmod +x grafana/summary.sh - - # Run the summary.sh script and add its output to the GitHub Job Summary - - name: Run summary.sh and add to Job Summary - if: steps.check-pr.outputs.is_pull_request == 'true' - run: | - SUMMARY=$(./grafana/summary.sh) - echo "### Summary of Grafana Panels" >> $GITHUB_STEP_SUMMARY - echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY + make check-dashboards diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 52b61320be..b3c7ee4cdd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -90,8 +90,6 @@ env: # The scheduled version is '${{ env.NEXT_RELEASE_VERSION }}-nightly-YYYYMMDD', like v0.2.0-nigthly-20230313; NIGHTLY_RELEASE_PREFIX: nightly - # Note: The NEXT_RELEASE_VERSION should be modified manually by every formal release. - NEXT_RELEASE_VERSION: v0.14.0 jobs: allocate-runners: @@ -135,7 +133,6 @@ jobs: env: GITHUB_EVENT_NAME: ${{ github.event_name }} GITHUB_REF_NAME: ${{ github.ref_name }} - NEXT_RELEASE_VERSION: ${{ env.NEXT_RELEASE_VERSION }} NIGHTLY_RELEASE_PREFIX: ${{ env.NIGHTLY_RELEASE_PREFIX }} - name: Allocate linux-amd64 runner @@ -317,7 +314,7 @@ jobs: image-registry-username: ${{ secrets.DOCKERHUB_USERNAME }} image-registry-password: ${{ secrets.DOCKERHUB_TOKEN }} version: ${{ needs.allocate-runners.outputs.version }} - push-latest-tag: true + push-latest-tag: ${{ github.ref_type == 'tag' && !contains(github.ref_name, 'nightly') && github.event_name != 'schedule' }} - name: Set build image result id: set-build-image-result @@ -364,7 +361,7 @@ jobs: dev-mode: false upload-to-s3: true update-version-info: true - push-latest-tag: true + push-latest-tag: ${{ github.ref_type == 'tag' && !contains(github.ref_name, 'nightly') && github.event_name != 'schedule' }} publish-github-release: name: Create GitHub release and upload artifacts diff --git a/Cargo.lock b/Cargo.lock index e9a41b4d52..1603528b22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,9 +173,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "anymap2" @@ -185,7 +185,7 @@ checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" [[package]] name = "api" -version = "0.14.0" +version = "0.15.0" dependencies = [ "common-base", "common-decimal", @@ -266,25 +266,61 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "arrow" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a3ec4fe573f9d1f59d99c085197ef669b00b088ba1d7bb75224732d9357a74" +dependencies = [ + "arrow-arith 53.4.1", + "arrow-array 53.4.1", + "arrow-buffer 53.4.1", + "arrow-cast 53.4.1", + "arrow-csv 53.4.1", + "arrow-data 53.4.1", + "arrow-ipc 53.4.1", + "arrow-json 53.4.1", + "arrow-ord 53.4.1", + "arrow-row 53.4.1", + "arrow-schema 53.4.1", + "arrow-select 53.4.1", + "arrow-string 53.4.1", +] + [[package]] name = "arrow" version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc208515aa0151028e464cc94a692156e945ce5126abd3537bb7fd6ba2143ed1" dependencies = [ - "arrow-arith", - "arrow-array", - "arrow-buffer", - "arrow-cast", - "arrow-csv", - "arrow-data", - "arrow-ipc", - "arrow-json", - "arrow-ord", - "arrow-row", - "arrow-schema", - "arrow-select", - "arrow-string", + "arrow-arith 54.2.1", + "arrow-array 54.2.1", + "arrow-buffer 54.3.1", + "arrow-cast 54.2.1", + "arrow-csv 54.2.1", + "arrow-data 54.3.1", + "arrow-ipc 54.2.1", + "arrow-json 54.2.1", + "arrow-ord 54.2.1", + "arrow-row 54.2.1", + "arrow-schema 54.3.1", + "arrow-select 54.2.1", + "arrow-string 54.2.1", +] + +[[package]] +name = "arrow-arith" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dcf19f07792d8c7f91086c67b574a79301e367029b17fcf63fb854332246a10" +dependencies = [ + "arrow-array 53.4.1", + "arrow-buffer 53.4.1", + "arrow-data 53.4.1", + "arrow-schema 53.4.1", + "chrono", + "half", + "num", ] [[package]] @@ -293,14 +329,30 @@ version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e07e726e2b3f7816a85c6a45b6ec118eeeabf0b2a8c208122ad949437181f49a" dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", + "arrow-array 54.2.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", "chrono", "num", ] +[[package]] +name = "arrow-array" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7845c32b41f7053e37a075b3c2f29c6f5ea1b3ca6e5df7a2d325ee6e1b4a63cf" +dependencies = [ + "ahash 0.8.11", + "arrow-buffer 53.4.1", + "arrow-data 53.4.1", + "arrow-schema 53.4.1", + "chrono", + "half", + "hashbrown 0.15.2", + "num", +] + [[package]] name = "arrow-array" version = "54.2.1" @@ -308,9 +360,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2262eba4f16c78496adfd559a29fe4b24df6088efc9985a873d58e92be022d5" dependencies = [ "ahash 0.8.11", - "arrow-buffer", - "arrow-data", - "arrow-schema", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", "chrono", "chrono-tz", "half", @@ -318,6 +370,17 @@ dependencies = [ "num", ] +[[package]] +name = "arrow-buffer" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5c681a99606f3316f2a99d9c8b6fa3aad0b1d34d8f6d7a1b471893940219d8" +dependencies = [ + "bytes", + "half", + "num", +] + [[package]] name = "arrow-buffer" version = "54.3.1" @@ -329,17 +392,37 @@ dependencies = [ "num", ] +[[package]] +name = "arrow-cast" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6365f8527d4f87b133eeb862f9b8093c009d41a210b8f101f91aa2392f61daac" +dependencies = [ + "arrow-array 53.4.1", + "arrow-buffer 53.4.1", + "arrow-data 53.4.1", + "arrow-schema 53.4.1", + "arrow-select 53.4.1", + "atoi", + "base64 0.22.1", + "chrono", + "half", + "lexical-core", + "num", + "ryu", +] + [[package]] name = "arrow-cast" version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4103d88c5b441525ed4ac23153be7458494c2b0c9a11115848fdb9b81f6f886a" dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "arrow-select", + "arrow-array 54.2.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", + "arrow-select 54.2.1", "atoi", "base64 0.22.1", "chrono", @@ -350,15 +433,34 @@ dependencies = [ "ryu", ] +[[package]] +name = "arrow-csv" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30dac4d23ac769300349197b845e0fd18c7f9f15d260d4659ae6b5a9ca06f586" +dependencies = [ + "arrow-array 53.4.1", + "arrow-buffer 53.4.1", + "arrow-cast 53.4.1", + "arrow-data 53.4.1", + "arrow-schema 53.4.1", + "chrono", + "csv", + "csv-core", + "lazy_static", + "lexical-core", + "regex", +] + [[package]] name = "arrow-csv" version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d3cb0914486a3cae19a5cad2598e44e225d53157926d0ada03c20521191a65" dependencies = [ - "arrow-array", - "arrow-cast", - "arrow-schema", + "arrow-array 54.2.1", + "arrow-cast 54.2.1", + "arrow-schema 54.3.1", "chrono", "csv", "csv-core", @@ -366,14 +468,26 @@ dependencies = [ "regex", ] +[[package]] +name = "arrow-data" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd962fc3bf7f60705b25bcaa8eb3318b2545aa1d528656525ebdd6a17a6cd6fb" +dependencies = [ + "arrow-buffer 53.4.1", + "arrow-schema 53.4.1", + "half", + "num", +] + [[package]] name = "arrow-data" version = "54.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61cfdd7d99b4ff618f167e548b2411e5dd2c98c0ddebedd7df433d34c20a4429" dependencies = [ - "arrow-buffer", - "arrow-schema", + "arrow-buffer 54.3.1", + "arrow-schema 54.3.1", "half", "num", ] @@ -384,11 +498,11 @@ version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7408f2bf3b978eddda272c7699f439760ebc4ac70feca25fefa82c5b8ce808d" dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-cast", - "arrow-ipc", - "arrow-schema", + "arrow-array 54.2.1", + "arrow-buffer 54.3.1", + "arrow-cast 54.2.1", + "arrow-ipc 54.2.1", + "arrow-schema 54.3.1", "base64 0.22.1", "bytes", "futures", @@ -397,32 +511,67 @@ dependencies = [ "tonic 0.12.3", ] +[[package]] +name = "arrow-ipc" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3527365b24372f9c948f16e53738eb098720eea2093ae73c7af04ac5e30a39b" +dependencies = [ + "arrow-array 53.4.1", + "arrow-buffer 53.4.1", + "arrow-cast 53.4.1", + "arrow-data 53.4.1", + "arrow-schema 53.4.1", + "flatbuffers", + "zstd 0.13.2", +] + [[package]] name = "arrow-ipc" version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddecdeab02491b1ce88885986e25002a3da34dd349f682c7cfe67bab7cc17b86" dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", + "arrow-array 54.2.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", "flatbuffers", "lz4_flex", "zstd 0.13.2", ] +[[package]] +name = "arrow-json" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdec0024749fc0d95e025c0b0266d78613727b3b3a5d4cf8ea47eb6d38afdd1" +dependencies = [ + "arrow-array 53.4.1", + "arrow-buffer 53.4.1", + "arrow-cast 53.4.1", + "arrow-data 53.4.1", + "arrow-schema 53.4.1", + "chrono", + "half", + "indexmap 2.9.0", + "lexical-core", + "num", + "serde", + "serde_json", +] + [[package]] name = "arrow-json" version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d03b9340013413eb84868682ace00a1098c81a5ebc96d279f7ebf9a4cac3c0fd" dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-cast", - "arrow-data", - "arrow-schema", + "arrow-array 54.2.1", + "arrow-buffer 54.3.1", + "arrow-cast 54.2.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", "chrono", "half", "indexmap 2.9.0", @@ -432,17 +581,46 @@ dependencies = [ "serde_json", ] +[[package]] +name = "arrow-ord" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79af2db0e62a508d34ddf4f76bfd6109b6ecc845257c9cba6f939653668f89ac" +dependencies = [ + "arrow-array 53.4.1", + "arrow-buffer 53.4.1", + "arrow-data 53.4.1", + "arrow-schema 53.4.1", + "arrow-select 53.4.1", + "half", + "num", +] + [[package]] name = "arrow-ord" version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f841bfcc1997ef6ac48ee0305c4dfceb1f7c786fe31e67c1186edf775e1f1160" dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "arrow-select", + "arrow-array 54.2.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", + "arrow-select 54.2.1", +] + +[[package]] +name = "arrow-row" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da30e9d10e9c52f09ea0cf15086d6d785c11ae8dcc3ea5f16d402221b6ac7735" +dependencies = [ + "ahash 0.8.11", + "arrow-array 53.4.1", + "arrow-buffer 53.4.1", + "arrow-data 53.4.1", + "arrow-schema 53.4.1", + "half", ] [[package]] @@ -451,13 +629,19 @@ version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1eeb55b0a0a83851aa01f2ca5ee5648f607e8506ba6802577afdda9d75cdedcd" dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", + "arrow-array 54.2.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", "half", ] +[[package]] +name = "arrow-schema" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b0f9c0c3582dd55db0f136d3b44bfa0189df07adcf7dc7f2f2e74db0f52eb8" + [[package]] name = "arrow-schema" version = "54.3.1" @@ -467,6 +651,20 @@ dependencies = [ "serde", ] +[[package]] +name = "arrow-select" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92fc337f01635218493c23da81a364daf38c694b05fc20569c3193c11c561984" +dependencies = [ + "ahash 0.8.11", + "arrow-array 53.4.1", + "arrow-buffer 53.4.1", + "arrow-data 53.4.1", + "arrow-schema 53.4.1", + "num", +] + [[package]] name = "arrow-select" version = "54.2.1" @@ -474,24 +672,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e2932aece2d0c869dd2125feb9bd1709ef5c445daa3838ac4112dcfa0fda52c" dependencies = [ "ahash 0.8.11", - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", + "arrow-array 54.2.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", "num", ] +[[package]] +name = "arrow-string" +version = "53.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d596a9fc25dae556672d5069b090331aca8acb93cae426d8b7dcdf1c558fa0ce" +dependencies = [ + "arrow-array 53.4.1", + "arrow-buffer 53.4.1", + "arrow-data 53.4.1", + "arrow-schema 53.4.1", + "arrow-select 53.4.1", + "memchr", + "num", + "regex", + "regex-syntax 0.8.5", +] + [[package]] name = "arrow-string" version = "54.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "912e38bd6a7a7714c1d9b61df80315685553b7455e8a6045c27531d8ecd5b458" dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "arrow-select", + "arrow-array 54.2.1", + "arrow-buffer 54.3.1", + "arrow-data 54.3.1", + "arrow-schema 54.3.1", + "arrow-select 54.2.1", "memchr", "num", "regex", @@ -700,7 +915,7 @@ dependencies = [ [[package]] name = "auth" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "async-trait", @@ -1322,7 +1537,7 @@ dependencies = [ [[package]] name = "cache" -version = "0.14.0" +version = "0.15.0" dependencies = [ "catalog", "common-error", @@ -1346,11 +1561,11 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "catalog" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", - "arrow", - "arrow-schema", + "arrow 54.2.1", + "arrow-schema 54.3.1", "async-stream", "async-trait", "bytes", @@ -1382,7 +1597,7 @@ dependencies = [ "partition", "paste", "prometheus", - "rustc-hash 2.0.0", + "rustc-hash 2.1.1", "serde_json", "session", "snafu 0.8.5", @@ -1404,9 +1619,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.24" +version = "1.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" dependencies = [ "jobserver", "libc", @@ -1659,7 +1874,7 @@ checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "cli" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-trait", "auth", @@ -1691,7 +1906,7 @@ dependencies = [ "humantime", "meta-client", "nu-ansi-term", - "opendal", + "opendal 0.51.2", "query", "rand 0.9.0", "reqwest", @@ -1702,7 +1917,7 @@ dependencies = [ "session", "snafu 0.8.5", "store-api", - "substrait 0.14.0", + "substrait 0.15.0", "table", "tempfile", "tokio", @@ -1711,13 +1926,14 @@ dependencies = [ [[package]] name = "client" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "arc-swap", "arrow-flight", "async-stream", "async-trait", + "base64 0.22.1", "common-catalog", "common-error", "common-grpc", @@ -1728,6 +1944,7 @@ dependencies = [ "common-recordbatch", "common-telemetry", "enum_dispatch", + "futures", "futures-util", "lazy_static", "moka", @@ -1738,7 +1955,7 @@ dependencies = [ "rand 0.9.0", "serde_json", "snafu 0.8.5", - "substrait 0.14.0", + "substrait 0.15.0", "substrait 0.37.3", "tokio", "tokio-stream", @@ -1779,7 +1996,7 @@ dependencies = [ [[package]] name = "cmd" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-trait", "auth", @@ -1832,7 +2049,6 @@ dependencies = [ "regex", "reqwest", "rexpect", - "rustyline", "serde", "serde_json", "servers", @@ -1840,7 +2056,7 @@ dependencies = [ "similar-asserts", "snafu 0.8.5", "store-api", - "substrait 0.14.0", + "substrait 0.15.0", "table", "temp-env", "tempfile", @@ -1886,7 +2102,7 @@ checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" [[package]] name = "common-base" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anymap2", "async-trait", @@ -1908,11 +2124,11 @@ dependencies = [ [[package]] name = "common-catalog" -version = "0.14.0" +version = "0.15.0" [[package]] name = "common-config" -version = "0.14.0" +version = "0.15.0" dependencies = [ "common-base", "common-error", @@ -1937,10 +2153,10 @@ dependencies = [ [[package]] name = "common-datasource" -version = "0.14.0" +version = "0.15.0" dependencies = [ - "arrow", - "arrow-schema", + "arrow 54.2.1", + "arrow-schema 54.3.1", "async-compression 0.3.15", "async-trait", "bytes", @@ -1974,7 +2190,7 @@ dependencies = [ [[package]] name = "common-decimal" -version = "0.14.0" +version = "0.15.0" dependencies = [ "bigdecimal 0.4.8", "common-error", @@ -1987,8 +2203,9 @@ dependencies = [ [[package]] name = "common-error" -version = "0.14.0" +version = "0.15.0" dependencies = [ + "common-macro", "http 1.1.0", "snafu 0.8.5", "strum 0.27.1", @@ -1997,7 +2214,7 @@ dependencies = [ [[package]] name = "common-frontend" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-trait", "common-error", @@ -2007,7 +2224,7 @@ dependencies = [ [[package]] name = "common-function" -version = "0.14.0" +version = "0.15.0" dependencies = [ "ahash 0.8.11", "api", @@ -2015,6 +2232,7 @@ dependencies = [ "arc-swap", "async-trait", "bincode", + "catalog", "chrono", "common-base", "common-catalog", @@ -2059,7 +2277,7 @@ dependencies = [ [[package]] name = "common-greptimedb-telemetry" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-trait", "common-runtime", @@ -2076,7 +2294,7 @@ dependencies = [ [[package]] name = "common-grpc" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "arrow-flight", @@ -2096,15 +2314,18 @@ dependencies = [ "lazy_static", "prost 0.13.5", "rand 0.9.0", + "serde", + "serde_json", "snafu 0.8.5", "tokio", + "tokio-util", "tonic 0.12.3", "tower 0.5.2", ] [[package]] name = "common-grpc-expr" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "common-base", @@ -2123,7 +2344,7 @@ dependencies = [ [[package]] name = "common-macro" -version = "0.14.0" +version = "0.15.0" dependencies = [ "arc-swap", "common-query", @@ -2137,7 +2358,7 @@ dependencies = [ [[package]] name = "common-mem-prof" -version = "0.14.0" +version = "0.15.0" dependencies = [ "common-error", "common-macro", @@ -2150,7 +2371,7 @@ dependencies = [ [[package]] name = "common-meta" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anymap2", "api", @@ -2211,7 +2432,7 @@ dependencies = [ [[package]] name = "common-options" -version = "0.14.0" +version = "0.15.0" dependencies = [ "common-grpc", "humantime-serde", @@ -2220,11 +2441,11 @@ dependencies = [ [[package]] name = "common-plugins" -version = "0.14.0" +version = "0.15.0" [[package]] name = "common-pprof" -version = "0.14.0" +version = "0.15.0" dependencies = [ "common-error", "common-macro", @@ -2236,7 +2457,7 @@ dependencies = [ [[package]] name = "common-procedure" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-stream", "async-trait", @@ -2263,7 +2484,7 @@ dependencies = [ [[package]] name = "common-procedure-test" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-trait", "common-procedure", @@ -2272,7 +2493,7 @@ dependencies = [ [[package]] name = "common-query" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "async-trait", @@ -2289,7 +2510,7 @@ dependencies = [ "futures-util", "serde", "snafu 0.8.5", - "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=e98e6b322426a9d397a71efef17075966223c089)", + "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e)", "sqlparser_derive 0.1.1", "statrs", "store-api", @@ -2298,7 +2519,7 @@ dependencies = [ [[package]] name = "common-recordbatch" -version = "0.14.0" +version = "0.15.0" dependencies = [ "arc-swap", "common-error", @@ -2309,6 +2530,7 @@ dependencies = [ "datatypes", "futures", "pin-project", + "regex", "serde", "serde_json", "snafu 0.8.5", @@ -2317,7 +2539,7 @@ dependencies = [ [[package]] name = "common-runtime" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-trait", "clap 4.5.19", @@ -2347,14 +2569,14 @@ dependencies = [ [[package]] name = "common-session" -version = "0.14.0" +version = "0.15.0" dependencies = [ "strum 0.27.1", ] [[package]] name = "common-telemetry" -version = "0.14.0" +version = "0.15.0" dependencies = [ "atty", "backtrace", @@ -2382,7 +2604,7 @@ dependencies = [ [[package]] name = "common-test-util" -version = "0.14.0" +version = "0.15.0" dependencies = [ "client", "common-query", @@ -2394,9 +2616,9 @@ dependencies = [ [[package]] name = "common-time" -version = "0.14.0" +version = "0.15.0" dependencies = [ - "arrow", + "arrow 54.2.1", "chrono", "chrono-tz", "common-error", @@ -2412,7 +2634,7 @@ dependencies = [ [[package]] name = "common-version" -version = "0.14.0" +version = "0.15.0" dependencies = [ "build-data", "const_format", @@ -2422,7 +2644,7 @@ dependencies = [ [[package]] name = "common-wal" -version = "0.14.0" +version = "0.15.0" dependencies = [ "common-base", "common-error", @@ -2724,9 +2946,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -2888,19 +3110,19 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "datafusion" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ - "arrow", - "arrow-array", - "arrow-ipc", - "arrow-schema", + "arrow 54.2.1", + "arrow-array 54.2.1", + "arrow-ipc 54.2.1", + "arrow-schema 54.3.1", "async-compression 0.4.13", "async-trait", "bytes", @@ -2946,9 +3168,9 @@ dependencies = [ [[package]] name = "datafusion-catalog" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ - "arrow", + "arrow 54.2.1", "async-trait", "dashmap", "datafusion-common", @@ -2966,10 +3188,10 @@ dependencies = [ [[package]] name = "datafusion-catalog-listing" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ - "arrow", - "arrow-schema", + "arrow 54.2.1", + "arrow-schema 54.3.1", "chrono", "datafusion-catalog", "datafusion-common", @@ -2989,13 +3211,13 @@ dependencies = [ [[package]] name = "datafusion-common" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ "ahash 0.8.11", - "arrow", - "arrow-array", - "arrow-ipc", - "arrow-schema", + "arrow 54.2.1", + "arrow-array 54.2.1", + "arrow-ipc 54.2.1", + "arrow-schema 54.3.1", "base64 0.22.1", "half", "hashbrown 0.14.5", @@ -3014,7 +3236,7 @@ dependencies = [ [[package]] name = "datafusion-common-runtime" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ "log", "tokio", @@ -3023,14 +3245,14 @@ dependencies = [ [[package]] name = "datafusion-doc" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" [[package]] name = "datafusion-execution" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ - "arrow", + "arrow 54.2.1", "dashmap", "datafusion-common", "datafusion-expr", @@ -3046,9 +3268,9 @@ dependencies = [ [[package]] name = "datafusion-expr" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ - "arrow", + "arrow 54.2.1", "chrono", "datafusion-common", "datafusion-doc", @@ -3066,9 +3288,9 @@ dependencies = [ [[package]] name = "datafusion-expr-common" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ - "arrow", + "arrow 54.2.1", "datafusion-common", "itertools 0.14.0", "paste", @@ -3077,10 +3299,10 @@ dependencies = [ [[package]] name = "datafusion-functions" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ - "arrow", - "arrow-buffer", + "arrow 54.2.1", + "arrow-buffer 54.3.1", "base64 0.22.1", "blake2", "blake3", @@ -3106,11 +3328,11 @@ dependencies = [ [[package]] name = "datafusion-functions-aggregate" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ "ahash 0.8.11", - "arrow", - "arrow-schema", + "arrow 54.2.1", + "arrow-schema 54.3.1", "datafusion-common", "datafusion-doc", "datafusion-execution", @@ -3127,10 +3349,10 @@ dependencies = [ [[package]] name = "datafusion-functions-aggregate-common" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ "ahash 0.8.11", - "arrow", + "arrow 54.2.1", "datafusion-common", "datafusion-expr-common", "datafusion-physical-expr-common", @@ -3139,12 +3361,12 @@ dependencies = [ [[package]] name = "datafusion-functions-nested" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ - "arrow", - "arrow-array", - "arrow-ord", - "arrow-schema", + "arrow 54.2.1", + "arrow-array 54.2.1", + "arrow-ord 54.2.1", + "arrow-schema 54.3.1", "datafusion-common", "datafusion-doc", "datafusion-execution", @@ -3161,9 +3383,9 @@ dependencies = [ [[package]] name = "datafusion-functions-table" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ - "arrow", + "arrow 54.2.1", "async-trait", "datafusion-catalog", "datafusion-common", @@ -3176,7 +3398,7 @@ dependencies = [ [[package]] name = "datafusion-functions-window" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ "datafusion-common", "datafusion-doc", @@ -3192,7 +3414,7 @@ dependencies = [ [[package]] name = "datafusion-functions-window-common" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ "datafusion-common", "datafusion-physical-expr-common", @@ -3201,7 +3423,7 @@ dependencies = [ [[package]] name = "datafusion-macros" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ "datafusion-expr", "quote", @@ -3211,9 +3433,9 @@ dependencies = [ [[package]] name = "datafusion-optimizer" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ - "arrow", + "arrow 54.2.1", "chrono", "datafusion-common", "datafusion-expr", @@ -3229,12 +3451,12 @@ dependencies = [ [[package]] name = "datafusion-physical-expr" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ "ahash 0.8.11", - "arrow", - "arrow-array", - "arrow-schema", + "arrow 54.2.1", + "arrow-array 54.2.1", + "arrow-schema 54.3.1", "datafusion-common", "datafusion-expr", "datafusion-expr-common", @@ -3252,10 +3474,10 @@ dependencies = [ [[package]] name = "datafusion-physical-expr-common" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ "ahash 0.8.11", - "arrow", + "arrow 54.2.1", "datafusion-common", "datafusion-expr-common", "hashbrown 0.14.5", @@ -3265,10 +3487,10 @@ dependencies = [ [[package]] name = "datafusion-physical-optimizer" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ - "arrow", - "arrow-schema", + "arrow 54.2.1", + "arrow-schema 54.3.1", "datafusion-common", "datafusion-execution", "datafusion-expr", @@ -3286,13 +3508,13 @@ dependencies = [ [[package]] name = "datafusion-physical-plan" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ "ahash 0.8.11", - "arrow", - "arrow-array", - "arrow-ord", - "arrow-schema", + "arrow 54.2.1", + "arrow-array 54.2.1", + "arrow-ord 54.2.1", + "arrow-schema 54.3.1", "async-trait", "chrono", "datafusion-common", @@ -3316,11 +3538,11 @@ dependencies = [ [[package]] name = "datafusion-sql" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ - "arrow", - "arrow-array", - "arrow-schema", + "arrow 54.2.1", + "arrow-array 54.2.1", + "arrow-schema 54.3.1", "bigdecimal 0.4.8", "datafusion-common", "datafusion-expr", @@ -3334,7 +3556,7 @@ dependencies = [ [[package]] name = "datafusion-substrait" version = "45.0.0" -source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=07dd0bee9e524d83228847c15af6c12f438349ab#07dd0bee9e524d83228847c15af6c12f438349ab" +source = "git+https://github.com/waynexia/arrow-datafusion.git?rev=e104c7cf62b11dd5fe41461b82514978234326b4#e104c7cf62b11dd5fe41461b82514978234326b4" dependencies = [ "async-recursion", "async-trait", @@ -3350,7 +3572,7 @@ dependencies = [ [[package]] name = "datanode" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "arrow-flight", @@ -3402,7 +3624,7 @@ dependencies = [ "session", "snafu 0.8.5", "store-api", - "substrait 0.14.0", + "substrait 0.15.0", "table", "tokio", "toml 0.8.19", @@ -3411,11 +3633,11 @@ dependencies = [ [[package]] name = "datatypes" -version = "0.14.0" +version = "0.15.0" dependencies = [ - "arrow", - "arrow-array", - "arrow-schema", + "arrow 54.2.1", + "arrow-array 54.2.1", + "arrow-schema 54.3.1", "base64 0.22.1", "common-base", "common-decimal", @@ -3434,7 +3656,7 @@ dependencies = [ "serde", "serde_json", "snafu 0.8.5", - "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=e98e6b322426a9d397a71efef17075966223c089)", + "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e)", "sqlparser_derive 0.1.1", ] @@ -4037,7 +4259,7 @@ dependencies = [ [[package]] name = "file-engine" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "async-trait", @@ -4160,11 +4382,11 @@ checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" [[package]] name = "flow" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", - "arrow", - "arrow-schema", + "arrow 54.2.1", + "arrow-schema 54.3.1", "async-recursion", "async-trait", "bytes", @@ -4222,7 +4444,7 @@ dependencies = [ "snafu 0.8.5", "store-api", "strum 0.27.1", - "substrait 0.14.0", + "substrait 0.15.0", "table", "tokio", "tonic 0.12.3", @@ -4277,12 +4499,13 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "frontend" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "arc-swap", "async-trait", "auth", + "bytes", "cache", "catalog", "client", @@ -4317,6 +4540,7 @@ dependencies = [ "num_cpus", "opentelemetry-proto 0.27.0", "operator", + "otel-arrow-rust", "partition", "pipeline", "prometheus", @@ -4329,10 +4553,10 @@ dependencies = [ "session", "snafu 0.8.5", "sql", - "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=e98e6b322426a9d397a71efef17075966223c089)", + "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e)", "store-api", "strfmt", - "substrait 0.14.0", + "substrait 0.15.0", "table", "tokio", "toml 0.8.19", @@ -4720,7 +4944,7 @@ dependencies = [ [[package]] name = "greptime-proto" version = "0.1.0" -source = "git+https://github.com/GreptimeTeam/greptime-proto.git?rev=dd4a1996982534636734674db66e44464b0c0d83#dd4a1996982534636734674db66e44464b0c0d83" +source = "git+https://github.com/GreptimeTeam/greptime-proto.git?rev=e82b0158cd38d4021edb4e4c0ae77f999051e62f#e82b0158cd38d4021edb4e4c0ae77f999051e62f" dependencies = [ "prost 0.13.5", "serde", @@ -5571,7 +5795,7 @@ dependencies = [ [[package]] name = "index" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-trait", "asynchronous-codec", @@ -5589,6 +5813,7 @@ dependencies = [ "greptime-proto", "itertools 0.14.0", "jieba-rs", + "lazy_static", "mockall", "pin-project", "prost 0.13.5", @@ -6374,13 +6599,13 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "log-query" -version = "0.14.0" +version = "0.15.0" dependencies = [ "chrono", "common-error", @@ -6392,7 +6617,7 @@ dependencies = [ [[package]] name = "log-store" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-stream", "async-trait", @@ -6408,6 +6633,7 @@ dependencies = [ "common-test-util", "common-time", "common-wal", + "dashmap", "delta-encoding", "derive_builder 0.20.1", "futures", @@ -6685,7 +6911,7 @@ dependencies = [ [[package]] name = "meta-client" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "async-trait", @@ -6713,7 +6939,7 @@ dependencies = [ [[package]] name = "meta-srv" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "async-trait", @@ -6803,7 +7029,7 @@ dependencies = [ [[package]] name = "metric-engine" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "aquamarine", @@ -6815,12 +7041,14 @@ dependencies = [ "common-macro", "common-query", "common-recordbatch", + "common-runtime", "common-telemetry", "common-test-util", "common-time", "datafusion", "datatypes", "futures-util", + "humantime-serde", "itertools 0.14.0", "lazy_static", "mito2", @@ -6890,7 +7118,7 @@ dependencies = [ [[package]] name = "mito2" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "aquamarine", @@ -7537,6 +7765,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "num_threads" version = "0.1.7" @@ -7575,7 +7824,7 @@ dependencies = [ [[package]] name = "object-store" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "bytes", @@ -7585,7 +7834,7 @@ dependencies = [ "lazy_static", "md5", "moka", - "opendal", + "opendal 0.52.0", "prometheus", "tokio", "uuid", @@ -7614,9 +7863,9 @@ dependencies = [ [[package]] name = "object_store_opendal" -version = "0.49.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b18eb960885330175ec89daa991b0bc9050dc7f259b31a887fbfbb297312f83" +checksum = "59db4de9230e630346e3fd821e2b8d1fd03ddd509c32c964836588e82241762a" dependencies = [ "async-trait", "bytes", @@ -7624,7 +7873,7 @@ dependencies = [ "futures", "futures-util", "object_store", - "opendal", + "opendal 0.52.0", "pin-project", "tokio", ] @@ -7655,6 +7904,35 @@ name = "opendal" version = "0.51.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b1063ea459fa9e94584115743b06330f437902dd1d9f692b863ef1875a20548" +dependencies = [ + "anyhow", + "async-trait", + "backon", + "base64 0.22.1", + "bytes", + "chrono", + "crc32c", + "futures", + "getrandom 0.2.15", + "http 1.1.0", + "log", + "md-5", + "once_cell", + "percent-encoding", + "quick-xml 0.36.2", + "reqsign", + "reqwest", + "serde", + "serde_json", + "tokio", + "uuid", +] + +[[package]] +name = "opendal" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55c840b5a6ad96106d6c0612fabb8f35a5ace826e0474fc55ebda33042b8d33" dependencies = [ "anyhow", "async-trait", @@ -7841,7 +8119,7 @@ dependencies = [ [[package]] name = "operator" -version = "0.14.0" +version = "0.15.0" dependencies = [ "ahash 0.8.11", "api", @@ -7888,9 +8166,9 @@ dependencies = [ "session", "snafu 0.8.5", "sql", - "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=e98e6b322426a9d397a71efef17075966223c089)", + "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e)", "store-api", - "substrait 0.14.0", + "substrait 0.15.0", "table", "tokio", "tokio-util", @@ -7902,7 +8180,7 @@ name = "orc-rust" version = "0.6.0" source = "git+https://github.com/datafusion-contrib/orc-rust?rev=3134cab581a8e91b942d6a23aca2916ea965f6bb#3134cab581a8e91b942d6a23aca2916ea965f6bb" dependencies = [ - "arrow", + "arrow 54.2.1", "async-trait", "bytemuck", "bytes", @@ -7988,6 +8266,24 @@ version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +[[package]] +name = "otel-arrow-rust" +version = "0.1.0" +source = "git+https://github.com/open-telemetry/otel-arrow?rev=5d551412d2a12e689cde4d84c14ef29e36784e51#5d551412d2a12e689cde4d84c14ef29e36784e51" +dependencies = [ + "arrow 53.4.1", + "arrow-ipc 53.4.1", + "lazy_static", + "num_enum", + "opentelemetry-proto 0.27.0", + "paste", + "prost 0.13.5", + "serde", + "snafu 0.8.5", + "tonic 0.12.3", + "tonic-build 0.12.3", +] + [[package]] name = "overload" version = "0.1.1" @@ -8086,13 +8382,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f88838dca3b84d41444a0341b19f347e8098a3898b0f21536654b8b799e11abd" dependencies = [ "ahash 0.8.11", - "arrow-array", - "arrow-buffer", - "arrow-cast", - "arrow-data", - "arrow-ipc", - "arrow-schema", - "arrow-select", + "arrow-array 54.2.1", + "arrow-buffer 54.3.1", + "arrow-cast 54.2.1", + "arrow-data 54.3.1", + "arrow-ipc 54.2.1", + "arrow-schema 54.3.1", + "arrow-select 54.2.1", "base64 0.22.1", "brotli", "bytes", @@ -8127,7 +8423,7 @@ dependencies = [ [[package]] name = "partition" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "async-trait", @@ -8135,16 +8431,19 @@ dependencies = [ "common-macro", "common-meta", "common-query", + "criterion 0.5.1", "datafusion-common", "datafusion-expr", + "datafusion-physical-expr", "datatypes", "itertools 0.14.0", + "rand 0.8.5", "serde", "serde_json", "session", "snafu 0.8.5", "sql", - "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=e98e6b322426a9d397a71efef17075966223c089)", + "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e)", "store-api", "table", ] @@ -8406,11 +8705,11 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pipeline" -version = "0.14.0" +version = "0.15.0" dependencies = [ "ahash 0.8.11", "api", - "arrow", + "arrow 54.2.1", "async-trait", "catalog", "chrono", @@ -8548,7 +8847,7 @@ dependencies = [ [[package]] name = "plugins" -version = "0.14.0" +version = "0.15.0" dependencies = [ "auth", "clap 4.5.19", @@ -8828,7 +9127,7 @@ dependencies = [ [[package]] name = "promql" -version = "0.14.0" +version = "0.15.0" dependencies = [ "ahash 0.8.11", "async-trait", @@ -8852,9 +9151,9 @@ dependencies = [ [[package]] name = "promql-parser" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c6b1429bdd199d53bd58b745075c1652efedbe2746e5d4f0d56d3184dda48ec" +checksum = "60d851f6523a8215e2fbf86b6cef4548433f8b76092e9ffb607105de52ae63fd" dependencies = [ "cfgrammar", "chrono", @@ -8928,7 +9227,7 @@ dependencies = [ "log", "multimap", "once_cell", - "petgraph 0.6.5", + "petgraph 0.7.1", "prettyplease", "prost 0.13.5", "prost-types 0.13.5", @@ -9074,7 +9373,7 @@ dependencies = [ [[package]] name = "puffin" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-compression 0.4.13", "async-trait", @@ -9115,13 +9414,13 @@ dependencies = [ [[package]] name = "query" -version = "0.14.0" +version = "0.15.0" dependencies = [ "ahash 0.8.11", "api", "arc-swap", - "arrow", - "arrow-schema", + "arrow 54.2.1", + "arrow-schema 54.3.1", "async-recursion", "async-stream", "async-trait", @@ -9178,10 +9477,10 @@ dependencies = [ "session", "snafu 0.8.5", "sql", - "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=e98e6b322426a9d397a71efef17075966223c089)", + "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e)", "statrs", "store-api", - "substrait 0.14.0", + "substrait 0.15.0", "table", "tokio", "tokio-stream", @@ -9228,7 +9527,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.0.0", + "rustc-hash 2.1.1", "rustls", "socket2", "thiserror 1.0.64", @@ -9245,7 +9544,7 @@ dependencies = [ "bytes", "rand 0.8.5", "ring", - "rustc-hash 2.0.0", + "rustc-hash 2.1.1", "rustls", "slab", "thiserror 1.0.64", @@ -9522,9 +9821,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -9706,15 +10005,14 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] @@ -10035,9 +10333,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -10532,14 +10830,14 @@ dependencies = [ [[package]] name = "servers" -version = "0.14.0" +version = "0.15.0" dependencies = [ "ahash 0.8.11", "api", - "arrow", + "arrow 54.2.1", "arrow-flight", - "arrow-ipc", - "arrow-schema", + "arrow-ipc 54.2.1", + "arrow-schema 54.3.1", "async-trait", "auth", "axum 0.8.1", @@ -10602,6 +10900,7 @@ dependencies = [ "openmetrics-parser", "opensrv-mysql", "opentelemetry-proto 0.27.0", + "otel-arrow-rust", "parking_lot 0.12.3", "permutation", "pgwire", @@ -10651,7 +10950,7 @@ dependencies = [ [[package]] name = "session" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "arc-swap", @@ -10859,9 +11158,9 @@ dependencies = [ [[package]] name = "smallbitvec" -version = "2.5.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3fc564a4b53fd1e8589628efafe57602d91bde78be18186b5f61e8faea470" +checksum = "d31d263dd118560e1a492922182ab6ca6dc1d03a3bf54e7699993f31a4150e3f" [[package]] name = "smallvec" @@ -10976,7 +11275,7 @@ dependencies = [ [[package]] name = "sql" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "chrono", @@ -11004,10 +11303,11 @@ dependencies = [ "serde", "serde_json", "snafu 0.8.5", - "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=e98e6b322426a9d397a71efef17075966223c089)", + "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e)", "sqlparser_derive 0.1.1", "store-api", "table", + "uuid", ] [[package]] @@ -11030,7 +11330,7 @@ dependencies = [ [[package]] name = "sqlness-runner" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-trait", "clap 4.5.19", @@ -11072,7 +11372,7 @@ dependencies = [ [[package]] name = "sqlparser" version = "0.54.0" -source = "git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=e98e6b322426a9d397a71efef17075966223c089#e98e6b322426a9d397a71efef17075966223c089" +source = "git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e#0cf6c04490d59435ee965edd2078e8855bd8471e" dependencies = [ "lazy_static", "log", @@ -11080,7 +11380,7 @@ dependencies = [ "regex", "serde", "sqlparser 0.54.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sqlparser_derive 0.3.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=e98e6b322426a9d397a71efef17075966223c089)", + "sqlparser_derive 0.3.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e)", ] [[package]] @@ -11108,7 +11408,7 @@ dependencies = [ [[package]] name = "sqlparser_derive" version = "0.3.0" -source = "git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=e98e6b322426a9d397a71efef17075966223c089#e98e6b322426a9d397a71efef17075966223c089" +source = "git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e#0cf6c04490d59435ee965edd2078e8855bd8471e" dependencies = [ "proc-macro2", "quote", @@ -11349,7 +11649,7 @@ dependencies = [ [[package]] name = "store-api" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "aquamarine", @@ -11498,7 +11798,7 @@ dependencies = [ [[package]] name = "substrait" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-trait", "bytes", @@ -11678,7 +11978,7 @@ dependencies = [ [[package]] name = "table" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "async-trait", @@ -11929,7 +12229,7 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "tests-fuzz" -version = "0.14.0" +version = "0.15.0" dependencies = [ "arbitrary", "async-trait", @@ -11963,7 +12263,7 @@ dependencies = [ "serde_yaml", "snafu 0.8.5", "sql", - "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=e98e6b322426a9d397a71efef17075966223c089)", + "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e)", "sqlx", "store-api", "strum 0.27.1", @@ -11973,7 +12273,7 @@ dependencies = [ [[package]] name = "tests-integration" -version = "0.14.0" +version = "0.15.0" dependencies = [ "api", "arrow-flight", @@ -12040,7 +12340,7 @@ dependencies = [ "sql", "sqlx", "store-api", - "substrait 0.14.0", + "substrait 0.15.0", "table", "tempfile", "time", @@ -12051,6 +12351,7 @@ dependencies = [ "tower 0.5.2", "url", "uuid", + "yaml-rust", "zstd 0.13.2", ] diff --git a/Cargo.toml b/Cargo.toml index 78d45f9bf5..b0a049bbd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,16 +68,16 @@ members = [ resolver = "2" [workspace.package] -version = "0.14.0" +version = "0.15.0" edition = "2021" license = "Apache-2.0" [workspace.lints] -clippy.print_stdout = "warn" -clippy.print_stderr = "warn" clippy.dbg_macro = "warn" clippy.implicit_clone = "warn" -clippy.readonly_write_lock = "allow" +clippy.result_large_err = "allow" +clippy.large_enum_variant = "allow" +clippy.doc_overindented_list_items = "allow" rust.unknown_lints = "deny" rust.unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] } @@ -113,15 +113,15 @@ clap = { version = "4.4", features = ["derive"] } config = "0.13.0" crossbeam-utils = "0.8" dashmap = "6.1" -datafusion = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "07dd0bee9e524d83228847c15af6c12f438349ab" } -datafusion-common = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "07dd0bee9e524d83228847c15af6c12f438349ab" } -datafusion-expr = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "07dd0bee9e524d83228847c15af6c12f438349ab" } -datafusion-functions = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "07dd0bee9e524d83228847c15af6c12f438349ab" } -datafusion-optimizer = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "07dd0bee9e524d83228847c15af6c12f438349ab" } -datafusion-physical-expr = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "07dd0bee9e524d83228847c15af6c12f438349ab" } -datafusion-physical-plan = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "07dd0bee9e524d83228847c15af6c12f438349ab" } -datafusion-sql = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "07dd0bee9e524d83228847c15af6c12f438349ab" } -datafusion-substrait = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "07dd0bee9e524d83228847c15af6c12f438349ab" } +datafusion = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" } +datafusion-common = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" } +datafusion-expr = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" } +datafusion-functions = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" } +datafusion-optimizer = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" } +datafusion-physical-expr = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" } +datafusion-physical-plan = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" } +datafusion-sql = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" } +datafusion-substrait = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" } deadpool = "0.12" deadpool-postgres = "0.14" derive_builder = "0.20" @@ -130,7 +130,7 @@ etcd-client = "0.14" fst = "0.4.7" futures = "0.3" futures-util = "0.3" -greptime-proto = { git = "https://github.com/GreptimeTeam/greptime-proto.git", rev = "dd4a1996982534636734674db66e44464b0c0d83" } +greptime-proto = { git = "https://github.com/GreptimeTeam/greptime-proto.git", rev = "e82b0158cd38d4021edb4e4c0ae77f999051e62f" } hex = "0.4" http = "1" humantime = "2.1" @@ -148,7 +148,7 @@ moka = "0.12" nalgebra = "0.33" notify = "8.0" num_cpus = "1.16" -object_store_opendal = "0.49.0" +object_store_opendal = "0.50" once_cell = "1.18" opentelemetry-proto = { version = "0.27", features = [ "gen-tonic", @@ -162,7 +162,7 @@ parquet = { version = "54.2", default-features = false, features = ["arrow", "as paste = "1.0" pin-project = "1.0" prometheus = { version = "0.13.3", features = ["process"] } -promql-parser = { version = "0.5", features = ["ser"] } +promql-parser = { version = "0.5.1", features = ["ser"] } prost = "0.13" raft-engine = { version = "0.4.1", default-features = false } rand = "0.9" @@ -192,7 +192,7 @@ simd-json = "0.15" similar-asserts = "1.6.0" smallvec = { version = "1", features = ["serde"] } snafu = "0.8" -sqlparser = { git = "https://github.com/GreptimeTeam/sqlparser-rs.git", rev = "e98e6b322426a9d397a71efef17075966223c089", features = [ +sqlparser = { git = "https://github.com/GreptimeTeam/sqlparser-rs.git", rev = "0cf6c04490d59435ee965edd2078e8855bd8471e", features = [ "visitor", "serde", ] } # branch = "v0.54.x" @@ -270,6 +270,9 @@ metric-engine = { path = "src/metric-engine" } mito2 = { path = "src/mito2" } object-store = { path = "src/object-store" } operator = { path = "src/operator" } +otel-arrow-rust = { git = "https://github.com/open-telemetry/otel-arrow", rev = "5d551412d2a12e689cde4d84c14ef29e36784e51", features = [ + "server", +] } partition = { path = "src/partition" } pipeline = { path = "src/pipeline" } plugins = { path = "src/plugins" } diff --git a/Makefile b/Makefile index a0e4b590aa..7c1cff9821 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,10 @@ ifneq ($(strip $(BUILD_JOBS)),) NEXTEST_OPTS += --build-jobs=${BUILD_JOBS} endif +ifneq ($(strip $(BUILD_JOBS)),) + SQLNESS_OPTS += --jobs ${BUILD_JOBS} +endif + ifneq ($(strip $(CARGO_PROFILE)),) CARGO_BUILD_OPTS += --profile ${CARGO_PROFILE} endif @@ -218,6 +222,16 @@ start-cluster: ## Start the greptimedb cluster with etcd by using docker compose stop-cluster: ## Stop the greptimedb cluster that created by docker compose. docker compose -f ./docker/docker-compose/cluster-with-etcd.yaml stop +##@ Grafana + +.PHONY: check-dashboards +check-dashboards: ## Check the Grafana dashboards. + @./grafana/scripts/check.sh + +.PHONY: dashboards +dashboards: ## Generate the Grafana dashboards for standalone mode and intermediate dashboards. + @./grafana/scripts/gen-dashboards.sh + ##@ Docs config-docs: ## Generate configuration documentation from toml files. docker run --rm \ diff --git a/README.md b/README.md index 07da173117..b7381a1e4d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

-

Unified & Cost-Effective Observerability Database for Metrics, Logs, and Events

+

Real-Time & Cloud-Native Observability Database
for metrics, logs, and traces

@@ -62,7 +62,7 @@ ## Introduction -**GreptimeDB** is an open-source unified & cost-effective observerability database for **Metrics**, **Logs**, and **Events** (also **Traces** in plan). You can gain real-time insights from Edge to Cloud at Any Scale. +**GreptimeDB** is an open-source, cloud-native, unified & cost-effective observability database for **Metrics**, **Logs**, and **Traces**. You can gain real-time insights from Edge to Cloud at Any Scale. ## News @@ -70,27 +70,27 @@ ## Why GreptimeDB -Our core developers have been building observerability data platforms for years. Based on our best practices, GreptimeDB was born to give you: +Our core developers have been building observability data platforms for years. Based on our best practices, GreptimeDB was born to give you: -* **Unified Processing of Metrics, Logs, and Events** +* **Unified Processing of Observability Data** - GreptimeDB unifies observerability data processing by treating all data - whether metrics, logs, or events - as timestamped events with context. Users can analyze this data using either [SQL](https://docs.greptime.com/user-guide/query-data/sql) or [PromQL](https://docs.greptime.com/user-guide/query-data/promql) and leverage stream processing ([Flow](https://docs.greptime.com/user-guide/flow-computation/overview)) to enable continuous aggregation. [Read more](https://docs.greptime.com/user-guide/concepts/data-model). + A unified database that treats metrics, logs, and traces as timestamped wide events with context, supporting [SQL](https://docs.greptime.com/user-guide/query-data/sql)/[PromQL](https://docs.greptime.com/user-guide/query-data/promql) queries and [stream processing](https://docs.greptime.com/user-guide/flow-computation/overview) to simplify complex data stacks. + +* **High Performance and Cost-effective** + + Written in Rust, combines a distributed query engine with [rich indexing](https://docs.greptime.com/user-guide/manage-data/data-index) (inverted, fulltext, skip data, and vector) and optimized columnar storage to deliver sub-second responses on petabyte-scale data and high-cost efficiency. * **Cloud-native Distributed Database** Built for [Kubernetes](https://docs.greptime.com/user-guide/deployments/deploy-on-kubernetes/greptimedb-operator-management). GreptimeDB achieves seamless scalability with its [cloud-native architecture](https://docs.greptime.com/user-guide/concepts/architecture) of separated compute and storage, built on object storage (AWS S3, Azure Blob Storage, etc.) while enabling cross-cloud deployment through a unified data access layer. -* **Performance and Cost-effective** +* **Developer-Friendly** - Written in pure Rust for superior performance and reliability. GreptimeDB features a distributed query engine with intelligent indexing to handle high cardinality data efficiently. Its optimized columnar storage achieves 50x cost efficiency on cloud object storage through advanced compression. [Benchmark reports](https://www.greptime.com/blogs/2024-09-09-report-summary). + Access standardized SQL/PromQL interfaces through built-in web dashboard, REST API, and MySQL/PostgreSQL protocols. Supports widely adopted data ingestion [protocols](https://docs.greptime.com/user-guide/protocols/overview) for seamless migration and integration. -* **Cloud-Edge Collaboration** +* **Flexible Deployment Options** - GreptimeDB seamlessly operates across cloud and edge (ARM/Android/Linux), providing consistent APIs and control plane for unified data management and efficient synchronization. [Learn how to run on Android](https://docs.greptime.com/user-guide/deployments/run-on-android/). - -* **Multi-protocol Ingestion, SQL & PromQL Ready** - - Widely adopted database protocols and APIs, including MySQL, PostgreSQL, InfluxDB, OpenTelemetry, Loki and Prometheus, etc. Effortless Adoption & Seamless Migration. [Supported Protocols Overview](https://docs.greptime.com/user-guide/protocols/overview). + Deploy GreptimeDB anywhere from ARM-based edge devices to cloud environments with unified APIs and bandwidth-efficient data synchronization. Query edge and cloud data seamlessly through identical APIs. [Learn how to run on Android](https://docs.greptime.com/user-guide/deployments/run-on-android/). For more detailed info please read [Why GreptimeDB](https://docs.greptime.com/user-guide/concepts/why-greptimedb). @@ -233,3 +233,5 @@ Special thanks to all the contributors who have propelled GreptimeDB forward. Fo - GreptimeDB's query engine is powered by [Apache Arrow DataFusion™](https://arrow.apache.org/datafusion/). - [Apache OpenDAL™](https://opendal.apache.org) gives GreptimeDB a very general and elegant data access abstraction layer. - GreptimeDB's meta service is based on [etcd](https://etcd.io/). + +Known Users \ No newline at end of file diff --git a/config/config.md b/config/config.md index ba2540f2c6..f3230190c9 100644 --- a/config/config.md +++ b/config/config.md @@ -96,6 +96,8 @@ | `procedure.max_running_procedures` | Integer | `128` | Max running procedures.
The maximum number of procedures that can be running at the same time.
If the number of running procedures exceeds this limit, the procedure will be rejected. | | `flow` | -- | -- | flow engine options. | | `flow.num_workers` | Integer | `0` | The number of flow worker in flownode.
Not setting(or set to 0) this value will use the number of CPU cores divided by 2. | +| `query` | -- | -- | The query engine options. | +| `query.parallelism` | Integer | `0` | Parallelism of the query engine.
Default to 0, which means the number of CPU cores. | | `storage` | -- | -- | The data storage options. | | `storage.data_home` | String | `./greptimedb_data/` | The working home directory. | | `storage.type` | String | `File` | The storage type used to store the data.
- `File`: the data is stored in the local file system.
- `S3`: the data is stored in the S3 object storage.
- `Gcs`: the data is stored in the Google Cloud Storage.
- `Azblob`: the data is stored in the Azure Blob Storage.
- `Oss`: the data is stored in the Aliyun OSS. | @@ -270,6 +272,8 @@ | `meta_client.metadata_cache_max_capacity` | Integer | `100000` | The configuration about the cache of the metadata. | | `meta_client.metadata_cache_ttl` | String | `10m` | TTL of the metadata cache. | | `meta_client.metadata_cache_tti` | String | `5m` | -- | +| `query` | -- | -- | The query engine options. | +| `query.parallelism` | Integer | `0` | Parallelism of the query engine.
Default to 0, which means the number of CPU cores. | | `datanode` | -- | -- | Datanode options. | | `datanode.client` | -- | -- | Datanode client options. | | `datanode.client.connect_timeout` | String | `10s` | -- | @@ -315,6 +319,7 @@ | `selector` | String | `round_robin` | Datanode selector type.
- `round_robin` (default value)
- `lease_based`
- `load_based`
For details, please see "https://docs.greptime.com/developer-guide/metasrv/selector". | | `use_memory_store` | Bool | `false` | Store data in memory. | | `enable_region_failover` | Bool | `false` | Whether to enable region failover.
This feature is only available on GreptimeDB running on cluster mode and
- Using Remote WAL
- Using shared storage (e.g., s3). | +| `allow_region_failover_on_local_wal` | Bool | `false` | Whether to allow region failover on local WAL.
**This option is not recommended to be set to true, because it may lead to data loss during failover.** | | `node_max_idle_time` | String | `24hours` | Max allowed idle time before removing node info from metasrv memory. | | `enable_telemetry` | Bool | `true` | Whether to enable greptimedb telemetry. Enabled by default. | | `runtime` | -- | -- | The runtime options. | @@ -339,6 +344,9 @@ | `wal.provider` | String | `raft_engine` | -- | | `wal.broker_endpoints` | Array | -- | The broker endpoints of the Kafka cluster. | | `wal.auto_create_topics` | Bool | `true` | Automatically create topics for WAL.
Set to `true` to automatically create topics for WAL.
Otherwise, use topics named `topic_name_prefix_[0..num_topics)` | +| `wal.auto_prune_interval` | String | `0s` | Interval of automatically WAL pruning.
Set to `0s` to disable automatically WAL pruning which delete unused remote WAL entries periodically. | +| `wal.trigger_flush_threshold` | Integer | `0` | The threshold to trigger a flush operation of a region in automatically WAL pruning.
Metasrv will send a flush request to flush the region when:
`trigger_flush_threshold` + `prunable_entry_id` < `max_prunable_entry_id`
where:
- `prunable_entry_id` is the maximum entry id that can be pruned of the region.
- `max_prunable_entry_id` is the maximum prunable entry id among all regions in the same topic.
Set to `0` to disable the flush operation. | +| `wal.auto_prune_parallelism` | Integer | `10` | Concurrent task limit for automatically WAL pruning. | | `wal.num_topics` | Integer | `64` | Number of topics. | | `wal.selector_type` | String | `round_robin` | Topic selector type.
Available selector types:
- `round_robin` (default) | | `wal.topic_name_prefix` | String | `greptimedb_wal_topic` | A Kafka topic is constructed by concatenating `topic_name_prefix` and `topic_id`.
Only accepts strings that match the following regular expression pattern:
[a-zA-Z_:-][a-zA-Z0-9_:\-\.@#]*
i.g., greptimedb_wal_topic_0, greptimedb_wal_topic_1. | @@ -429,6 +437,8 @@ | `wal.create_index` | Bool | `true` | Whether to enable WAL index creation.
**It's only used when the provider is `kafka`**. | | `wal.dump_index_interval` | String | `60s` | The interval for dumping WAL indexes.
**It's only used when the provider is `kafka`**. | | `wal.overwrite_entry_start_id` | Bool | `false` | Ignore missing entries during read WAL.
**It's only used when the provider is `kafka`**.

This option ensures that when Kafka messages are deleted, the system
can still successfully replay memtable data without throwing an
out-of-range error.
However, enabling this option might lead to unexpected data loss,
as the system will skip over missing entries instead of treating
them as critical errors. | +| `query` | -- | -- | The query engine options. | +| `query.parallelism` | Integer | `0` | Parallelism of the query engine.
Default to 0, which means the number of CPU cores. | | `storage` | -- | -- | The data storage options. | | `storage.data_home` | String | `./greptimedb_data/` | The working home directory. | | `storage.type` | String | `File` | The storage type used to store the data.
- `File`: the data is stored in the local file system.
- `S3`: the data is stored in the S3 object storage.
- `Gcs`: the data is stored in the Google Cloud Storage.
- `Azblob`: the data is stored in the Azure Blob Storage.
- `Oss`: the data is stored in the Aliyun OSS. | diff --git a/config/datanode.example.toml b/config/datanode.example.toml index af6b5571d2..46beb51a23 100644 --- a/config/datanode.example.toml +++ b/config/datanode.example.toml @@ -243,6 +243,12 @@ overwrite_entry_start_id = false # credential = "base64-credential" # endpoint = "https://storage.googleapis.com" +## The query engine options. +[query] +## Parallelism of the query engine. +## Default to 0, which means the number of CPU cores. +parallelism = 0 + ## The data storage options. [storage] ## The working home directory. diff --git a/config/frontend.example.toml b/config/frontend.example.toml index 3d4cd78144..2e3ee4a69d 100644 --- a/config/frontend.example.toml +++ b/config/frontend.example.toml @@ -179,6 +179,12 @@ metadata_cache_ttl = "10m" # TTI of the metadata cache. metadata_cache_tti = "5m" +## The query engine options. +[query] +## Parallelism of the query engine. +## Default to 0, which means the number of CPU cores. +parallelism = 0 + ## Datanode options. [datanode] ## Datanode client options. diff --git a/config/metasrv.example.toml b/config/metasrv.example.toml index 0eb9900c2a..0e7f9b74f0 100644 --- a/config/metasrv.example.toml +++ b/config/metasrv.example.toml @@ -50,6 +50,10 @@ use_memory_store = false ## - Using shared storage (e.g., s3). enable_region_failover = false +## Whether to allow region failover on local WAL. +## **This option is not recommended to be set to true, because it may lead to data loss during failover.** +allow_region_failover_on_local_wal = false + ## Max allowed idle time before removing node info from metasrv memory. node_max_idle_time = "24hours" @@ -130,6 +134,22 @@ broker_endpoints = ["127.0.0.1:9092"] ## Otherwise, use topics named `topic_name_prefix_[0..num_topics)` auto_create_topics = true +## Interval of automatically WAL pruning. +## Set to `0s` to disable automatically WAL pruning which delete unused remote WAL entries periodically. +auto_prune_interval = "0s" + +## The threshold to trigger a flush operation of a region in automatically WAL pruning. +## Metasrv will send a flush request to flush the region when: +## `trigger_flush_threshold` + `prunable_entry_id` < `max_prunable_entry_id` +## where: +## - `prunable_entry_id` is the maximum entry id that can be pruned of the region. +## - `max_prunable_entry_id` is the maximum prunable entry id among all regions in the same topic. +## Set to `0` to disable the flush operation. +trigger_flush_threshold = 0 + +## Concurrent task limit for automatically WAL pruning. +auto_prune_parallelism = 10 + ## Number of topics. num_topics = 64 diff --git a/config/standalone.example.toml b/config/standalone.example.toml index bdef754712..0e72cfcc7e 100644 --- a/config/standalone.example.toml +++ b/config/standalone.example.toml @@ -334,6 +334,12 @@ max_running_procedures = 128 # credential = "base64-credential" # endpoint = "https://storage.googleapis.com" +## The query engine options. +[query] +## Parallelism of the query engine. +## Default to 0, which means the number of CPU cores. +parallelism = 0 + ## The data storage options. [storage] ## The working home directory. diff --git a/docs/how-to/how-to-profile-memory.md b/docs/how-to/how-to-profile-memory.md index 06a063acca..83343b9b2f 100644 --- a/docs/how-to/how-to-profile-memory.md +++ b/docs/how-to/how-to-profile-memory.md @@ -1,6 +1,6 @@ # Profile memory usage of GreptimeDB -This crate provides an easy approach to dump memory profiling info. +This crate provides an easy approach to dump memory profiling info. A set of ready to use scripts is provided in [docs/how-to/memory-profile-scripts](docs/how-to/memory-profile-scripts). ## Prerequisites ### jemalloc diff --git a/docs/how-to/memory-profile-scripts/scripts/README.md b/docs/how-to/memory-profile-scripts/scripts/README.md new file mode 100644 index 0000000000..3ac1cd90fa --- /dev/null +++ b/docs/how-to/memory-profile-scripts/scripts/README.md @@ -0,0 +1,52 @@ +# Memory Analysis Process +This section will guide you through the process of analyzing memory usage for greptimedb. + +1. Get the `jeprof` tool script, see the next section("Getting the `jeprof` tool") for details. + +2. After starting `greptimedb`(with env var `MALLOC_CONF=prof:true`), execute the `dump.sh` script with the PID of the `greptimedb` process as an argument. This continuously monitors memory usage and captures profiles when exceeding thresholds (e.g. +20MB within 10 minutes). Outputs `greptime-{timestamp}.gprof` files. + +3. With 2-3 gprof files, run `gen_flamegraph.sh` in the same environment to generate flame graphs showing memory allocation call stacks. + +4. **NOTE:** The `gen_flamegraph.sh` script requires `jeprof` and optionally `flamegraph.pl` to be in the current directory. If needed to gen flamegraph now, run the `get_flamegraph_tool.sh` script, which downloads the flame graph generation tool `flamegraph.pl` to the current directory. + The usage of `gen_flamegraph.sh` is: + + `Usage: ./gen_flamegraph.sh ` + where `` is the path to the greptimedb binary, `` is the directory containing the gprof files(the directory `dump.sh` is dumping profiles to). + Example call: `./gen_flamegraph.sh ./greptime .` + + Generating the flame graph might take a few minutes. The generated flame graphs are located in the `/flamegraphs` directory. Or if no `flamegraph.pl` is found, it will only contain `.collapse` files which is also fine. +5. You can send the generated flame graphs(the entire folder of `/flamegraphs`) to developers for further analysis. + + +## Getting the `jeprof` tool +there are three ways to get `jeprof`, list in here from simple to complex, using any one of those methods is ok, as long as it's the same environment as the `greptimedb` will be running on: +1. If you are compiling greptimedb from source, then `jeprof` is already produced during compilation. After running `cargo build`, execute `find_compiled_jeprof.sh`. This will copy `jeprof` to the current directory. +2. Or, if you have the Rust toolchain installed locally, simply follow these commands: +```bash +cargo new get_jeprof +cd get_jeprof +``` +Then add this line to `Cargo.toml`: +```toml +[dependencies] +tikv-jemalloc-ctl = { version = "0.6", features = ["use_std", "stats"] } +``` +then run: +```bash +cargo build +``` +after that the `jeprof` tool is produced. Now run `find_compiled_jeprof.sh` in current directory, it will copy the `jeprof` tool to the current directory. + +3. compile jemalloc from source +you can first clone this repo, and checkout to this commit: +```bash +git clone https://github.com/tikv/jemalloc.git +cd jemalloc +git checkout e13ca993e8ccb9ba9847cc330696e02839f328f7 +``` +then run: +```bash +./configure +make +``` +and `jeprof` is in `.bin/` directory. Copy it to the current directory. diff --git a/docs/how-to/memory-profile-scripts/scripts/dump.sh b/docs/how-to/memory-profile-scripts/scripts/dump.sh new file mode 100755 index 0000000000..d84bee75a5 --- /dev/null +++ b/docs/how-to/memory-profile-scripts/scripts/dump.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Monitors greptime process memory usage every 10 minutes +# Triggers memory profile capture via `curl -X POST localhost:4000/debug/prof/mem > greptime-{timestamp}.gprof` +# when memory increases by more than 20MB since last check +# Generated profiles can be analyzed using flame graphs as described in `how-to-profile-memory.md` +# (jeprof is compiled with the database - see documentation) +# Alternative: Share binaries + profiles for analysis (Docker images preferred) + +# Threshold in Kilobytes (20 MB) +threshold_kb=$((20 * 1024)) +sleep_interval=$((10 * 60)) + +# Variable to store the last measured memory usage in KB +last_mem_kb=0 + +echo "Starting memory monitoring for 'greptime' process..." + +while true; do + + # Check if PID is provided as an argument + if [ -z "$1" ]; then + echo "$(date): PID must be provided as a command-line argument." + exit 1 + fi + + pid="$1" + + # Validate that the PID is a number + if ! [[ "$pid" =~ ^[0-9]+$ ]]; then + echo "$(date): Invalid PID: '$pid'. PID must be a number." + exit 1 + fi + + # Get the current Resident Set Size (RSS) in Kilobytes + current_mem_kb=$(ps -o rss= -p "$pid") + + # Check if ps command was successful and returned a number + if ! [[ "$current_mem_kb" =~ ^[0-9]+$ ]]; then + echo "$(date): Failed to get memory usage for PID $pid. Skipping check." + # Keep last_mem_kb to avoid false positives if the process briefly becomes unreadable. + continue + fi + + echo "$(date): Current memory usage for PID $pid: ${current_mem_kb} KB" + + # Compare with the last measurement + # if it's the first run, also do a baseline dump just to make sure we can dump + + diff_kb=$((current_mem_kb - last_mem_kb)) + echo "$(date): Memory usage change since last check: ${diff_kb} KB" + + if [ "$diff_kb" -gt "$threshold_kb" ]; then + echo "$(date): Memory increase (${diff_kb} KB) exceeded threshold (${threshold_kb} KB). Dumping profile..." + timestamp=$(date +%Y%m%d%H%M%S) + profile_file="greptime-${timestamp}.gprof" + # Execute curl and capture output to file + if curl -sf -X POST localhost:4000/debug/prof/mem > "$profile_file"; then + echo "$(date): Memory profile saved to $profile_file" + else + echo "$(date): Failed to dump memory profile (curl exit code: $?)." + # Remove the potentially empty/failed profile file + rm -f "$profile_file" + fi + else + echo "$(date): Memory increase (${diff_kb} KB) is within the threshold (${threshold_kb} KB)." + fi + + + # Update the last memory usage + last_mem_kb=$current_mem_kb + + # Wait for 5 minutes + echo "$(date): Sleeping for $sleep_interval seconds..." + sleep $sleep_interval +done + +echo "Memory monitoring script stopped." # This line might not be reached in normal operation diff --git a/docs/how-to/memory-profile-scripts/scripts/find_compiled_jeprof.sh b/docs/how-to/memory-profile-scripts/scripts/find_compiled_jeprof.sh new file mode 100755 index 0000000000..b59488d1b4 --- /dev/null +++ b/docs/how-to/memory-profile-scripts/scripts/find_compiled_jeprof.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Locates compiled jeprof binary (memory analysis tool) after cargo build +# Copies it to current directory from target/ build directories + +JPROF_PATH=$(find . -name 'jeprof' -print -quit) +if [ -n "$JPROF_PATH" ]; then + echo "Found jeprof at $JPROF_PATH" + cp "$JPROF_PATH" . + chmod +x jeprof + echo "Copied jeprof to current directory and made it executable." +else + echo "jeprof not found" + exit 1 +fi diff --git a/docs/how-to/memory-profile-scripts/scripts/gen_flamegraph.sh b/docs/how-to/memory-profile-scripts/scripts/gen_flamegraph.sh new file mode 100755 index 0000000000..454d3da8ae --- /dev/null +++ b/docs/how-to/memory-profile-scripts/scripts/gen_flamegraph.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +# Generate flame graphs from a series of `.gprof` files +# First argument: Path to the binary executable +# Second argument: Path to directory containing gprof files +# Requires `jeprof` and `flamegraph.pl` in current directory +# What this script essentially does is: +# ./jeprof --collapse | ./flamegraph.pl > +# For differential analysis between consecutive profiles: +# ./jeprof --base --collapse | ./flamegraph.pl > + +set -e # Exit immediately if a command exits with a non-zero status. + +# Check for required tools +if [ ! -f "./jeprof" ]; then + echo "Error: jeprof not found in the current directory." + exit 1 +fi + +if [ ! -f "./flamegraph.pl" ]; then + echo "Error: flamegraph.pl not found in the current directory." + exit 1 +fi + +# Check arguments +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +BINARY_PATH=$1 +GPROF_DIR=$2 +OUTPUT_DIR="${GPROF_DIR}/flamegraphs" # Store outputs in a subdirectory + +if [ ! -f "$BINARY_PATH" ]; then + echo "Error: Binary file not found at $BINARY_PATH" + exit 1 +fi + +if [ ! -d "$GPROF_DIR" ]; then + echo "Error: gprof directory not found at $GPROF_DIR" + exit 1 +fi + +mkdir -p "$OUTPUT_DIR" +echo "Generating flamegraphs in $OUTPUT_DIR" + +# Find and sort gprof files +# Use find + sort -V for natural sort of version numbers if present in filenames +# Use null-terminated strings for safety with find/xargs/sort +mapfile -d $'\0' gprof_files < <(find "$GPROF_DIR" -maxdepth 1 -name '*.gprof' -print0 | sort -zV) + +if [ ${#gprof_files[@]} -eq 0 ]; then + echo "No .gprof files found in $GPROF_DIR" + exit 0 +fi + +prev_gprof="" + +# Generate flamegraphs +for gprof_file in "${gprof_files[@]}"; do + # Skip empty entries if any + if [ -z "$gprof_file" ]; then + continue + fi + + filename=$(basename "$gprof_file" .gprof) + output_collapse="${OUTPUT_DIR}/${filename}.collapse" + output_svg="${OUTPUT_DIR}/${filename}.svg" + echo "Generating collapse file for $gprof_file -> $output_collapse" + ./jeprof "$BINARY_PATH" "$gprof_file" --collapse > "$output_collapse" + echo "Generating flamegraph for $gprof_file -> $output_svg" + ./flamegraph.pl "$output_collapse" > "$output_svg" || true + + # Generate diff flamegraph if not the first file + if [ -n "$prev_gprof" ]; then + prev_filename=$(basename "$prev_gprof" .gprof) + diff_output_collapse="${OUTPUT_DIR}/${prev_filename}_vs_${filename}_diff.collapse" + diff_output_svg="${OUTPUT_DIR}/${prev_filename}_vs_${filename}_diff.svg" + echo "Generating diff collapse file for $prev_gprof vs $gprof_file -> $diff_output_collapse" + ./jeprof "$BINARY_PATH" --base "$prev_gprof" "$gprof_file" --collapse > "$diff_output_collapse" + echo "Generating diff flamegraph for $prev_gprof vs $gprof_file -> $diff_output_svg" + ./flamegraph.pl "$diff_output_collapse" > "$diff_output_svg" || true + fi + + prev_gprof="$gprof_file" +done + +echo "Flamegraph generation complete." diff --git a/docs/how-to/memory-profile-scripts/scripts/gen_from_collapse.sh b/docs/how-to/memory-profile-scripts/scripts/gen_from_collapse.sh new file mode 100755 index 0000000000..0546ede38e --- /dev/null +++ b/docs/how-to/memory-profile-scripts/scripts/gen_from_collapse.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Generate flame graphs from .collapse files +# Argument: Path to directory containing collapse files +# Requires `flamegraph.pl` in current directory + +# Check if flamegraph.pl exists +if [ ! -f "./flamegraph.pl" ]; then + echo "Error: flamegraph.pl not found in the current directory." + exit 1 +fi + +# Check if directory argument is provided +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +COLLAPSE_DIR=$1 + +# Check if the provided argument is a directory +if [ ! -d "$COLLAPSE_DIR" ]; then + echo "Error: '$COLLAPSE_DIR' is not a valid directory." + exit 1 +fi + +echo "Generating flame graphs from collapse files in '$COLLAPSE_DIR'..." + +# Find and process each .collapse file +find "$COLLAPSE_DIR" -maxdepth 1 -name "*.collapse" -print0 | while IFS= read -r -d $'\0' collapse_file; do + if [ -f "$collapse_file" ]; then + # Construct the output SVG filename + svg_file="${collapse_file%.collapse}.svg" + echo "Generating $svg_file from $collapse_file..." + ./flamegraph.pl "$collapse_file" > "$svg_file" + if [ $? -ne 0 ]; then + echo "Error generating flame graph for $collapse_file" + else + echo "Successfully generated $svg_file" + fi + fi +done + +echo "Flame graph generation complete." diff --git a/docs/how-to/memory-profile-scripts/scripts/get_flamegraph_tool.sh b/docs/how-to/memory-profile-scripts/scripts/get_flamegraph_tool.sh new file mode 100755 index 0000000000..d299d71698 --- /dev/null +++ b/docs/how-to/memory-profile-scripts/scripts/get_flamegraph_tool.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Download flamegraph.pl to current directory - this is the flame graph generation tool script + +curl https://raw.githubusercontent.com/brendangregg/FlameGraph/master/flamegraph.pl > ./flamegraph.pl +chmod +x ./flamegraph.pl diff --git a/flake.lock b/flake.lock index cfea27d34b..f2b2521130 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1737613896, - "narHash": "sha256-ldqXIglq74C7yKMFUzrS9xMT/EVs26vZpOD68Sh7OcU=", + "lastModified": 1742452566, + "narHash": "sha256-sVuLDQ2UIWfXUBbctzrZrXM2X05YjX08K7XHMztt36E=", "owner": "nix-community", "repo": "fenix", - "rev": "303a062fdd8e89f233db05868468975d17855d80", + "rev": "7d9ba794daf5e8cc7ee728859bc688d8e26d5f06", "type": "github" }, "original": { @@ -41,11 +41,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1737569578, - "narHash": "sha256-6qY0pk2QmUtBT9Mywdvif0i/CLVgpCjMUn6g9vB+f3M=", + "lastModified": 1743576891, + "narHash": "sha256-vXiKURtntURybE6FMNFAVpRPr8+e8KoLPrYs9TGuAKc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "47addd76727f42d351590c905d9d1905ca895b82", + "rev": "44a69ed688786e98a101f02b712c313f1ade37ab", "type": "github" }, "original": { @@ -65,11 +65,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1737581772, - "narHash": "sha256-t1P2Pe3FAX9TlJsCZbmJ3wn+C4qr6aSMypAOu8WNsN0=", + "lastModified": 1742296961, + "narHash": "sha256-gCpvEQOrugHWLimD1wTFOJHagnSEP6VYBDspq96Idu0=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "582af7ee9c8d84f5d534272fc7de9f292bd849be", + "rev": "15d87419f1a123d8f888d608129c3ce3ff8f13d4", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index a6d9fbc0df..225f631721 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ lib = nixpkgs.lib; rustToolchain = fenix.packages.${system}.fromToolchainName { name = (lib.importTOML ./rust-toolchain.toml).toolchain.channel; - sha256 = "sha256-f/CVA1EC61EWbh0SjaRNhLL0Ypx2ObupbzigZp8NmL4="; + sha256 = "sha256-i0Sh/ZFFsHlZ3oFZFc24qdk6Cd8Do8OPU4HJQsrKOeM="; }; in { diff --git a/grafana/README.md b/grafana/README.md index 233dcdd4d6..db86581e0b 100644 --- a/grafana/README.md +++ b/grafana/README.md @@ -1,61 +1,89 @@ -Grafana dashboard for GreptimeDB --------------------------------- +# Grafana dashboards for GreptimeDB -GreptimeDB's official Grafana dashboard. +## Overview -Status notify: we are still working on this config. It's expected to change frequently in the recent days. Please feel free to submit your feedback and/or contribution to this dashboard 🤗 +This repository maintains the Grafana dashboards for GreptimeDB. It has two types of dashboards: -If you use Helm [chart](https://github.com/GreptimeTeam/helm-charts) to deploy GreptimeDB cluster, you can enable self-monitoring by setting the following values in your Helm chart: +- `cluster/dashboard.json`: The Grafana dashboard for the GreptimeDB cluster. Read the [dashboard.md](./dashboards/cluster/dashboard.md) for more details. +- `standalone/dashboard.json`: The Grafana dashboard for the standalone GreptimeDB instance. **It's generated from the `cluster/dashboard.json` by removing the instance filter through the `make dashboards` command**. Read the [dashboard.md](./dashboards/standalone/dashboard.md) for more details. + +As the rapid development of GreptimeDB, the metrics may be changed, and please feel free to submit your feedback and/or contribution to this dashboard 🤗 + +**NOTE**: + +- The Grafana version should be greater than 9.0. + +- If you want to modify the dashboards, you only need to modify the `cluster/dashboard.json` and run the `make dashboards` command to generate the `standalone/dashboard.json` and other related files. + +To maintain the dashboards easily, we use the [`dac`](https://github.com/zyy17/dac) tool to generate the intermediate dashboards and markdown documents: + +- `cluster/dashboard.yaml`: The intermediate dashboard for the GreptimeDB cluster. +- `standalone/dashboard.yaml`: The intermediate dashboard for the standalone GreptimeDB instance. + +## Data Sources + +There are two data sources for the dashboards to fetch the metrics: + +- **Prometheus**: Expose the metrics of GreptimeDB. +- **Information Schema**: It is the MySQL port of the current monitored instance. The `overview` dashboard will use this datasource to show the information schema of the current instance. + +## Instance Filters + +To deploy the dashboards for multiple scenarios (K8s, bare metal, etc.), we prefer to use the `instance` label when filtering instances. + +Additionally, we recommend including the `pod` label in the legend to make it easier to identify each instance, even though this field will be empty in bare metal scenarios. + +For example, the following query is recommended: + +```promql +sum(process_resident_memory_bytes{instance=~"$datanode"}) by (instance, pod) +``` + +And the legend will be like: `[{{instance}}]-[{{ pod }}]`. + +## Deployment + +### Helm + +If you use the Helm [chart](https://github.com/GreptimeTeam/helm-charts) to deploy a GreptimeDB cluster, you can enable self-monitoring by setting the following values in your Helm chart: - `monitoring.enabled=true`: Deploys a standalone GreptimeDB instance dedicated to monitoring the cluster; - `grafana.enabled=true`: Deploys Grafana and automatically imports the monitoring dashboard; -The standalone GreptimeDB instance will collect metrics from your cluster and the dashboard will be available in the Grafana UI. For detailed deployment instructions, please refer to our [Kubernetes deployment guide](https://docs.greptime.com/nightly/user-guide/deployments/deploy-on-kubernetes/getting-started). +The standalone GreptimeDB instance will collect metrics from your cluster, and the dashboard will be available in the Grafana UI. For detailed deployment instructions, please refer to our [Kubernetes deployment guide](https://docs.greptime.com/nightly/user-guide/deployments/deploy-on-kubernetes/getting-started). -# How to use +### Self-host Prometheus and import dashboards manually -## `greptimedb.json` +1. **Configure Prometheus to scrape the cluster** -Open Grafana Dashboard page, choose `New` -> `Import`. And upload `greptimedb.json` file. + The following is an example configuration(**Please modify it according to your actual situation**): -## `greptimedb-cluster.json` + ```yml + # example config + # only to indicate how to assign labels to each target + # modify yours accordingly + scrape_configs: + - job_name: metasrv + static_configs: + - targets: [':'] -This cluster dashboard provides a comprehensive view of incoming requests, response statuses, and internal activities such as flush and compaction, with a layered structure from frontend to datanode. Designed with a focus on alert functionality, its primary aim is to highlight any anomalies in metrics, allowing users to quickly pinpoint the cause of errors. + - job_name: datanode + static_configs: + - targets: [':', ':', ':'] -We use Prometheus to scrape off metrics from nodes in GreptimeDB cluster, Grafana to visualize the diagram. Any compatible stack should work too. + - job_name: frontend + static_configs: + - targets: [':'] + ``` -__Note__: This dashboard is still in an early stage of development. Any issue or advice on improvement is welcomed. +2. **Configure the data sources in Grafana** -### Configuration + You need to add two data sources in Grafana: -Please ensure the following configuration before importing the dashboard into Grafana. + - Prometheus: It is the Prometheus instance that scrapes the GreptimeDB metrics. + - Information Schema: It is the MySQL port of the current monitored instance. The dashboard will use this datasource to show the information schema of the current instance. -__1. Prometheus scrape config__ +3. **Import the dashboards based on your deployment scenario** -Configure Prometheus to scrape the cluster. - -```yml -# example config -# only to indicate how to assign labels to each target -# modify yours accordingly -scrape_configs: - - job_name: metasrv - static_configs: - - targets: [':'] - - - job_name: datanode - static_configs: - - targets: [':', ':', ':'] - - - job_name: frontend - static_configs: - - targets: [':'] -``` - -__2. Grafana config__ - -Create a Prometheus data source in Grafana before using this dashboard. We use `datasource` as a variable in Grafana dashboard so that multiple environments are supported. - -### Usage - -Use `datasource` or `instance` on the upper-left corner to filter data from certain node. + - **Cluster**: Import the `cluster/dashboard.json` dashboard. + - **Standalone**: Import the `standalone/dashboard.json` dashboard. diff --git a/grafana/check.sh b/grafana/check.sh deleted file mode 100755 index 9cab07391c..0000000000 --- a/grafana/check.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -BASEDIR=$(dirname "$0") - -# Use jq to check for panels with empty or missing descriptions -invalid_panels=$(cat $BASEDIR/greptimedb-cluster.json | jq -r ' - .panels[] - | select((.type == "stats" or .type == "timeseries") and (.description == "" or .description == null)) -') - -# Check if any invalid panels were found -if [[ -n "$invalid_panels" ]]; then - echo "Error: The following panels have empty or missing descriptions:" - echo "$invalid_panels" - exit 1 -else - echo "All panels with type 'stats' or 'timeseries' have valid descriptions." - exit 0 -fi diff --git a/grafana/dashboards/cluster/dashboard.json b/grafana/dashboards/cluster/dashboard.json new file mode 100644 index 0000000000..91633ae6ab --- /dev/null +++ b/grafana/dashboards/cluster/dashboard.json @@ -0,0 +1,7193 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "The Grafana dashboards for GreptimeDB.", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 279, + "panels": [], + "title": "Overview", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "The start time of GreptimeDB.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "fieldMinMax": false, + "mappings": [], + "max": 2, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 1 + }, + "id": 265, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "limit": 1, + "values": true + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "time() - process_start_time_seconds", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "description": "GreptimeDB version.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 3, + "y": 1 + }, + "id": 239, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^pkg_version$/", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT pkg_version FROM information_schema.build_info", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Version", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Total ingestion rate.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "fieldMinMax": false, + "mappings": [], + "max": 2, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "rowsps" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 5, + "y": 1 + }, + "id": 249, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(rate(greptime_table_operator_ingest_rows[$__rate_interval]))", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Total Ingestion Rate", + "type": "stat" + }, + { + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "description": "Total number of data file size.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 9, + "y": 1 + }, + "id": 248, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "select SUM(disk_size) from information_schema.region_statistics;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Total Storage Size", + "type": "stat" + }, + { + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "description": "Total number of data rows in the cluster. Calculated by sum of rows from each region.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "sishort" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 13, + "y": 1 + }, + "id": 254, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "select SUM(region_rows) from information_schema.region_statistics;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Total Rows", + "type": "stat" + }, + { + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "description": "The deployment topology of GreptimeDB.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 0, + "y": 5 + }, + "id": 243, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT count(*) as datanode FROM information_schema.cluster_info WHERE peer_type = 'DATANODE';", + "refId": "datanode", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT count(*) as frontend FROM information_schema.cluster_info WHERE peer_type = 'FRONTEND';", + "refId": "frontend", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT count(*) as metasrv FROM information_schema.cluster_info WHERE peer_type = 'METASRV';", + "refId": "metasrv", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT count(*) as flownode FROM information_schema.cluster_info WHERE peer_type = 'FLOWNODE';", + "refId": "flownode", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Deployment", + "type": "stat" + }, + { + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "description": "The number of the key resources in GreptimeDB.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 5, + "y": 5 + }, + "id": 247, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT COUNT(*) as databases FROM information_schema.schemata WHERE schema_name NOT IN ('greptime_private', 'information_schema')", + "refId": "databases", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(*) as tables FROM information_schema.tables WHERE table_schema != 'information_schema'", + "refId": "tables", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT COUNT(region_id) as regions FROM information_schema.region_peers", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT COUNT(*) as flows FROM information_schema.flows", + "refId": "B", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Database Resources", + "type": "stat" + }, + { + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "description": "The data size of wal/index/manifest in the GreptimeDB.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 10, + "y": 5 + }, + "id": 278, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT SUM(memtable_size) * 0.42825 as WAL FROM information_schema.region_statistics;\n", + "refId": "WAL", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT SUM(index_size) as index FROM information_schema.region_statistics;\n", + "refId": "Index", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT SUM(manifest_size) as manifest FROM information_schema.region_statistics;\n", + "refId": "manifest", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Data Size", + "type": "stat" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 275, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Total ingestion rate.\n\nHere we listed 3 primary protocols:\n\n- Prometheus remote write\n- Greptime's gRPC API (when using our ingest SDK)\n- Log ingestion http API\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "rowsps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 193, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(rate(greptime_table_operator_ingest_rows{instance=~\"$frontend\"}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "ingestion", + "range": true, + "refId": "C" + } + ], + "title": "Total Ingestion Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Total ingestion rate.\n\nHere we listed 3 primary protocols:\n\n- Prometheus remote write\n- Greptime's gRPC API (when using our ingest SDK)\n- Log ingestion http API\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "rowsps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 784 + }, + "id": 277, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(rate(greptime_servers_http_logs_ingestion_counter[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "http-logs", + "range": true, + "refId": "http_logs" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(rate(greptime_servers_prometheus_remote_write_samples[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "prometheus-remote-write", + "range": true, + "refId": "prometheus-remote-write" + } + ], + "title": "Ingestion Rate by Type", + "type": "timeseries" + } + ], + "title": "Ingestion", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 276, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Total rate of query API calls by protocol. This metric is collected from frontends.\n\nHere we listed 3 main protocols:\n- MySQL\n- Postgres\n- Prometheus API\n\nNote that there are some other minor query APIs like /sql are not included", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 1589 + }, + "id": 255, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum (rate(greptime_servers_mysql_query_elapsed_count{instance=~\"$frontend\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "mysql", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum (rate(greptime_servers_postgres_query_elapsed_count{instance=~\"$frontend\"}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "pg", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum (rate(greptime_servers_http_promql_elapsed_counte{instance=~\"$frontend\"}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "promql", + "range": true, + "refId": "C" + } + ], + "title": "Total Query Rate", + "type": "timeseries" + } + ], + "title": "Queries", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 11 + }, + "id": 274, + "panels": [], + "title": "Resources", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current memory usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 12 + }, + "id": 256, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(process_resident_memory_bytes{instance=~\"$datanode\"}) by (instance, pod)", + "instant": false, + "legendFormat": "[{{instance}}]-[{{ pod }}]", + "range": true, + "refId": "A" + } + ], + "title": "Datanode Memory per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current cpu usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 12 + }, + "id": 262, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(process_cpu_seconds_total{instance=~\"$datanode\"}[$__rate_interval]) * 1000) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]", + "range": true, + "refId": "A" + } + ], + "title": "Datanode CPU Usage per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current memory usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 22 + }, + "id": 266, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(process_resident_memory_bytes{instance=~\"$frontend\"}) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]", + "range": true, + "refId": "A" + } + ], + "title": "Frontend Memory per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current cpu usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 22 + }, + "id": 268, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(process_cpu_seconds_total{instance=~\"$frontend\"}[$__rate_interval]) * 1000) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]-cpu", + "range": true, + "refId": "A" + } + ], + "title": "Frontend CPU Usage per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current memory usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 32 + }, + "id": 269, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(process_resident_memory_bytes{instance=~\"$metasrv\"}) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]-resident", + "range": true, + "refId": "A" + } + ], + "title": "Metasrv Memory per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current cpu usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 32 + }, + "id": 271, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(process_cpu_seconds_total{instance=~\"$metasrv\"}[$__rate_interval]) * 1000) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]", + "range": true, + "refId": "A" + } + ], + "title": "Metasrv CPU Usage per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current memory usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 42 + }, + "id": 272, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(process_resident_memory_bytes{instance=~\"$flownode\"}) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]", + "range": true, + "refId": "A" + } + ], + "title": "Flownode Memory per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current cpu usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 42 + }, + "id": 273, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(process_cpu_seconds_total{instance=~\"$flownode\"}[$__rate_interval]) * 1000) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]", + "range": true, + "refId": "A" + } + ], + "title": "Flownode CPU Usage per Instance", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 52 + }, + "id": 280, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "HTTP QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "[10.244.1.81:4000]-[mycluster-frontend-5bdf57f86-kshxt]-[/v1/prometheus/write]-[POST]-[500]" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1507 + }, + "id": 281, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, path, method, code) (rate(greptime_servers_http_requests_elapsed_count{instance=~\"$frontend\",path!~\"/health|/metrics\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]", + "range": true, + "refId": "A" + } + ], + "title": "HTTP QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "HTTP P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1507 + }, + "id": 282, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "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])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "HTTP P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "gRPC QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1515 + }, + "id": 283, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, path, code) (rate(greptime_servers_grpc_requests_elapsed_count{instance=~\"$frontend\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{path}}]-[{{code}}]", + "range": true, + "refId": "A" + } + ], + "title": "gRPC QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "gRPC P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1515 + }, + "id": 284, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, path, code) (rate(greptime_servers_grpc_requests_elapsed_bucket{instance=~\"$frontend\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "gRPC P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "MySQL QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1523 + }, + "id": 285, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(pod, instance)(rate(greptime_servers_mysql_query_elapsed_count{instance=~\"$frontend\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "MySQL QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "MySQL P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1523 + }, + "id": 286, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "histogram_quantile(0.99, sum by(pod, instance, le) (rate(greptime_servers_mysql_query_elapsed_bucket{instance=~\"$frontend\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "MySQL P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "PostgreSQL QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1531 + }, + "id": 287, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(pod, instance)(rate(greptime_servers_postgres_query_elapsed_count{instance=~\"$frontend\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "PostgreSQL QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "PostgreSQL P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1531 + }, + "id": 288, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(pod,instance,le) (rate(greptime_servers_postgres_query_elapsed_bucket{instance=~\"$frontend\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "PostgreSQL P99 per Instance", + "type": "timeseries" + } + ], + "title": "Frontend Requests", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 53 + }, + "id": 289, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Ingestion rate by row as in each frontend", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "rowsps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 292, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod)(rate(greptime_table_operator_ingest_rows{instance=~\"$frontend\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Ingest Rows per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Region Call QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 792 + }, + "id": 290, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, request_type) (rate(greptime_grpc_region_request_count{instance=~\"$frontend\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{request_type}}]", + "range": true, + "refId": "A" + } + ], + "title": "Region Call QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Region Call P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 792 + }, + "id": 291, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, request_type) (rate(greptime_grpc_region_request_bucket{instance=~\"$frontend\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{request_type}}]", + "range": true, + "refId": "A" + } + ], + "title": "Region Call P99 per Instance", + "type": "timeseries" + } + ], + "title": "Frontend to Datanode", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 293, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Request QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 212 + }, + "id": 294, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, type) (rate(greptime_mito_handle_request_elapsed_count{instance=~\"$datanode\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{type}}]", + "range": true, + "refId": "A" + } + ], + "title": "Request OPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Request P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 212 + }, + "id": 295, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, type) (rate(greptime_mito_handle_request_elapsed_bucket{instance=~\"$datanode\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{type}}]", + "range": true, + "refId": "A" + } + ], + "title": "Request P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write Buffer per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 265 + }, + "id": 296, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_mito_write_buffer_bytes{instance=~\"$datanode\"}", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Write Buffer per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Ingestion size by row counts.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "rowsps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 265 + }, + "id": 297, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by (instance, pod) (rate(greptime_mito_write_rows_total{instance=~\"$datanode\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Write Rows per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Flush QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 273 + }, + "id": 298, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, reason) (rate(greptime_mito_flush_requests_total{instance=~\"$datanode\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{reason}}]", + "range": true, + "refId": "A" + } + ], + "title": "Flush OPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write Stall per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 273 + }, + "id": 299, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod) (greptime_mito_write_stall_total{instance=~\"$datanode\"})", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Write Stall per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Read Stage OPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 281 + }, + "id": 300, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod) (rate(greptime_mito_read_stage_elapsed_count{instance=~\"$datanode\", stage=\"total\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Read Stage OPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Read Stage P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 281 + }, + "id": 301, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_read_stage_elapsed_bucket{instance=~\"$datanode\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{stage}}]", + "range": true, + "refId": "A" + } + ], + "title": "Read Stage P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write Stage P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 289 + }, + "id": 302, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Last *", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_write_stage_elapsed_bucket{instance=~\"$datanode\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{stage}}]", + "range": true, + "refId": "A" + } + ], + "title": "Write Stage P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Compaction OPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 289 + }, + "id": 303, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod) (rate(greptime_mito_compaction_total_elapsed_count{instance=~\"$datanode\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Compaction OPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Compaction latency by stage", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 297 + }, + "id": 304, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_compaction_stage_elapsed_bucket{instance=~\"$datanode\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{stage}}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "Compaction P99 per Instance by Stage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Compaction P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 297 + }, + "id": 305, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le,stage) (rate(greptime_mito_compaction_total_elapsed_bucket{instance=~\"$datanode\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{stage}}]-compaction", + "range": true, + "refId": "A" + } + ], + "title": "Compaction P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write-ahead logs write size as bytes. This chart includes stats of p95 and p99 size by instance, total WAL write rate.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 305 + }, + "id": 306, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by(le,instance, pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-req-size-p95", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(le,instance,pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))", + "hide": false, + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-req-size-p99", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by (instance, pod)(rate(raft_engine_write_size_sum[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-throughput", + "range": true, + "refId": "C" + } + ], + "title": "WAL write size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Cached Bytes per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 305 + }, + "id": 307, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_mito_cache_bytes{instance=~\"$datanode\"}", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{type}}]", + "range": true, + "refId": "A" + } + ], + "title": "Cached Bytes per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Ongoing compaction task count", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 313 + }, + "id": 308, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_mito_inflight_compaction_count", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Inflight Compaction", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Raft engine (local disk) log store sync latency, p99", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 313 + }, + "id": 310, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(le, type, node, instance, pod) (rate(raft_engine_sync_log_duration_seconds_bucket[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "WAL sync duration seconds", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write-ahead log operations latency at p99", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 321 + }, + "id": 311, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(le,logstore,optype,instance, pod) (rate(greptime_logstore_op_elapsed_bucket[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{logstore}}]-[{{optype}}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "Log Store op duration seconds", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Ongoing flush task count", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 321 + }, + "id": 312, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_mito_inflight_flush_count", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Inflight Flush", + "type": "timeseries" + } + ], + "title": "Mito Engine", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 55 + }, + "id": 313, + "panels": [], + "title": "OpenDAL", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 56 + }, + "id": 314, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~\"$datanode\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]", + "range": true, + "refId": "A" + } + ], + "title": "QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Read QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 66 + }, + "id": 315, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{instance=~\"$datanode\", operation=\"read\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]", + "range": true, + "refId": "A" + } + ], + "title": "Read QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Read P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 66 + }, + "id": 316, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{instance=~\"$datanode\",operation=\"read\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-{{scheme}}", + "range": true, + "refId": "A" + } + ], + "title": "Read P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 73 + }, + "id": 317, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{instance=~\"$datanode\", operation=\"write\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-{{scheme}}", + "range": true, + "refId": "A" + } + ], + "title": "Write QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 73 + }, + "id": 318, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{instance=~\"$datanode\", operation=\"write\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]", + "range": true, + "refId": "A" + } + ], + "title": "Write P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "List QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 80 + }, + "id": 319, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{instance=~\"$datanode\", operation=\"list\"}[$__rate_interval]))", + "instant": false, + "interval": "", + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]", + "range": true, + "refId": "A" + } + ], + "title": "List QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "List P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 80 + }, + "id": 320, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{instance=~\"$datanode\", operation=\"list\"}[$__rate_interval])))", + "instant": false, + "interval": "", + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]", + "range": true, + "refId": "A" + } + ], + "title": "List P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Other Requests per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 87 + }, + "id": 321, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~\"$datanode\",operation!~\"read|write|list|stat\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]", + "range": true, + "refId": "A" + } + ], + "title": "Other Requests per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Other Request P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 87 + }, + "id": 322, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{instance=~\"$datanode\", operation!~\"read|write|list\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]", + "range": true, + "refId": "A" + } + ], + "title": "Other Request P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Total traffic as in bytes by instance and operation", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 94 + }, + "id": 323, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme, operation) (rate(opendal_operation_bytes_sum{instance=~\"$datanode\"}[$__rate_interval]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Opendal traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "OpenDAL error counts per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 94 + }, + "id": 334, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme, operation, error) (rate(opendal_operation_errors_total{instance=~\"$datanode\", error!=\"NotFound\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]", + "range": true, + "refId": "A" + } + ], + "title": "OpenDAL errors per Instance", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 101 + }, + "id": 324, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Counter of region migration by source and destination", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 325, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_meta_region_migration_stat{datanode_type=\"src\"}", + "instant": false, + "legendFormat": "from-datanode-{{datanode_id}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_meta_region_migration_stat{datanode_type=\"desc\"}", + "hide": false, + "instant": false, + "legendFormat": "to-datanode-{{datanode_id}}", + "range": true, + "refId": "B" + } + ], + "title": "Region migration datanode", + "type": "state-timeline" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Counter of region migration error", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 326, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_meta_region_migration_error", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Region migration error", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1199 + }, + "id": 327, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_datanode_load", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Datanode load", + "type": "timeseries" + } + ], + "title": "Metasrv", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 102 + }, + "id": 328, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Flow Ingest / Output Rate.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1200 + }, + "id": 329, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, direction) (rate(greptime_flow_processed_rows[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{pod}}]-[{{instance}}]-[{{direction}}]", + "range": true, + "refId": "A" + } + ], + "title": "Flow Ingest / Output Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Flow Ingest Latency.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1200 + }, + "id": 330, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-p95", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod))", + "hide": false, + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-p99", + "range": true, + "refId": "B" + } + ], + "title": "Flow Ingest Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Flow Operation Latency.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 0, + "y": 1208 + }, + "id": 331, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{type}}]-p95", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type))", + "hide": false, + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{type}}]-p99", + "range": true, + "refId": "B" + } + ], + "title": "Flow Operation Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Flow Buffer Size per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 9, + "y": 1208 + }, + "id": 332, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_flow_input_buf_size", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}]", + "range": true, + "refId": "A" + } + ], + "title": "Flow Buffer Size per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Flow Processing Error per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 1208 + }, + "id": 333, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance,pod,code) (rate(greptime_flow_errors[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{code}}]", + "range": true, + "refId": "A" + } + ], + "title": "Flow Processing Error per Instance", + "type": "timeseries" + } + ], + "title": "Flownode", + "type": "row" + } + ], + "refresh": "10s", + "schemaVersion": 40, + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "includeAll": false, + "name": "metrics", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "current": {}, + "includeAll": false, + "name": "information_schema", + "options": [], + "query": "mysql", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "definition": "label_values(greptime_app_version{app=\"greptime-datanode\"},instance)", + "hide": 2, + "includeAll": true, + "multi": true, + "name": "datanode", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(greptime_app_version{app=\"greptime-datanode\"},instance)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "definition": "label_values(greptime_app_version{app=\"greptime-frontend\"},instance)", + "hide": 2, + "includeAll": true, + "multi": true, + "name": "frontend", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(greptime_app_version{app=\"greptime-frontend\"},instance)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "definition": "label_values(greptime_app_version{app=\"greptime-metasrv\"},instance)", + "hide": 2, + "includeAll": true, + "multi": true, + "name": "metasrv", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(greptime_app_version{app=\"greptime-metasrv\"},instance)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "definition": "label_values(greptime_app_version{app=\"greptime-flownode\"},instance)", + "hide": 2, + "includeAll": true, + "multi": true, + "name": "flownode", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(greptime_app_version{app=\"greptime-flownode\"},instance)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "GreptimeDB", + "uid": "dejf3k5e7g2kgb", + "version": 2, + "weekStart": "" +} diff --git a/grafana/dashboards/cluster/dashboard.md b/grafana/dashboards/cluster/dashboard.md new file mode 100644 index 0000000000..2de3016bbf --- /dev/null +++ b/grafana/dashboards/cluster/dashboard.md @@ -0,0 +1,97 @@ +# Overview +| Title | Query | Type | Description | Datasource | Unit | Legend Format | +| --- | --- | --- | --- | --- | --- | --- | +| 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` | -- | +| Deployment | `SELECT count(*) as datanode FROM information_schema.cluster_info WHERE peer_type = 'DATANODE';`
`SELECT count(*) as frontend FROM information_schema.cluster_info WHERE peer_type = 'FRONTEND';`
`SELECT count(*) as metasrv FROM information_schema.cluster_info WHERE peer_type = 'METASRV';`
`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')`
`SELECT COUNT(*) as tables FROM information_schema.tables WHERE table_schema != 'information_schema'`
`SELECT COUNT(region_id) as regions FROM information_schema.region_peers`
`SELECT COUNT(*) as flows FROM information_schema.flows` | `stat` | The number of the key resources in GreptimeDB. | `mysql` | -- | -- | +| Data Size | `SELECT SUM(memtable_size) * 0.42825 as WAL FROM information_schema.region_statistics;`
`SELECT SUM(index_size) as index FROM information_schema.region_statistics;`
`SELECT SUM(manifest_size) as manifest FROM information_schema.region_statistics;` | `stat` | The data size of wal/index/manifest in the GreptimeDB. | `mysql` | `decbytes` | -- | +# 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.

Here we listed 3 primary protocols:

- Prometheus remote write
- Greptime's gRPC API (when using our ingest SDK)
- Log ingestion http API
| `prometheus` | `rowsps` | `ingestion` | +| Ingestion Rate by Type | `sum(rate(greptime_servers_http_logs_ingestion_counter[$__rate_interval]))`
`sum(rate(greptime_servers_prometheus_remote_write_samples[$__rate_interval]))` | `timeseries` | Total ingestion rate.

Here we listed 3 primary protocols:

- Prometheus remote write
- Greptime's gRPC API (when using our ingest SDK)
- Log ingestion http API
| `prometheus` | `rowsps` | `http-logs` | +# Queries +| Title | Query | Type | Description | Datasource | Unit | Legend Format | +| --- | --- | --- | --- | --- | --- | --- | +| 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_counte{instance=~"$frontend"}[$__rate_interval]))` | `timeseries` | Total rate of query API calls by protocol. This metric is collected from frontends.

Here we listed 3 main protocols:
- MySQL
- Postgres
- Prometheus API

Note that there are some other minor query APIs like /sql are not included | `prometheus` | `reqps` | `mysql` | +# Resources +| Title | Query | Type | Description | Datasource | Unit | Legend Format | +| --- | --- | --- | --- | --- | --- | --- | +| Datanode Memory per Instance | `sum(process_resident_memory_bytes{instance=~"$datanode"}) by (instance, pod)` | `timeseries` | Current memory usage by instance | `prometheus` | `decbytes` | `[{{instance}}]-[{{ pod }}]` | +| Datanode CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{instance=~"$datanode"}[$__rate_interval]) * 1000) by (instance, pod)` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` | +| Frontend Memory per Instance | `sum(process_resident_memory_bytes{instance=~"$frontend"}) by (instance, pod)` | `timeseries` | Current memory usage by instance | `prometheus` | `decbytes` | `[{{ instance }}]-[{{ pod }}]` | +| Frontend CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{instance=~"$frontend"}[$__rate_interval]) * 1000) by (instance, pod)` | `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)` | `timeseries` | Current memory usage by instance | `prometheus` | `decbytes` | `[{{ instance }}]-[{{ pod }}]-resident` | +| Metasrv CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{instance=~"$metasrv"}[$__rate_interval]) * 1000) by (instance, pod)` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` | +| Flownode Memory per Instance | `sum(process_resident_memory_bytes{instance=~"$flownode"}) by (instance, pod)` | `timeseries` | Current memory usage by instance | `prometheus` | `decbytes` | `[{{ instance }}]-[{{ pod }}]` | +| Flownode CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{instance=~"$flownode"}[$__rate_interval]) * 1000) by (instance, pod)` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ 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` | +| 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` | +| 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` | +| 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` | +# 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}}]` | +# Mito Engine +| 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}}]` | +| 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 P99 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])))` | `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])))`
`histogram_quantile(0.99, sum by(le,instance,pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))`
`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{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}}]` | +# OpenDAL +| 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) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation="read"}[$__rate_interval]))` | `timeseries` | Read QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]` | +| Read P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode",operation="read"}[$__rate_interval])))` | `timeseries` | Read P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-{{scheme}}` | +| Write QPS per Instance | `sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation="write"}[$__rate_interval]))` | `timeseries` | Write QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-{{scheme}}` | +| Write P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode", operation="write"}[$__rate_interval])))` | `timeseries` | Write P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]` | +| 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"}[$__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}}]` | +# Metasrv +| Title | Query | Type | Description | Datasource | Unit | Legend Format | +| --- | --- | --- | --- | --- | --- | --- | +| Region migration datanode | `greptime_meta_region_migration_stat{datanode_type="src"}`
`greptime_meta_region_migration_stat{datanode_type="desc"}` | `state-timeline` | Counter of region migration by source and destination | `prometheus` | `none` | `from-datanode-{{datanode_id}}` | +| Region migration error | `greptime_meta_region_migration_error` | `timeseries` | Counter of region migration error | `prometheus` | `none` | `__auto` | +| 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` | `none` | `__auto` | +# 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))`
`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))`
`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}}]` | diff --git a/grafana/dashboards/cluster/dashboard.yaml b/grafana/dashboards/cluster/dashboard.yaml new file mode 100644 index 0000000000..e67ed3e960 --- /dev/null +++ b/grafana/dashboards/cluster/dashboard.yaml @@ -0,0 +1,769 @@ +groups: + - title: Overview + panels: + - title: Uptime + type: stat + description: The start time of GreptimeDB. + unit: s + queries: + - expr: time() - process_start_time_seconds + datasource: + type: prometheus + uid: ${metrics} + legendFormat: __auto + - title: Version + type: stat + description: GreptimeDB version. + queries: + - expr: SELECT pkg_version FROM information_schema.build_info + datasource: + type: mysql + uid: ${information_schema} + - title: Total Ingestion Rate + type: stat + description: Total ingestion rate. + unit: rowsps + queries: + - expr: sum(rate(greptime_table_operator_ingest_rows[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: __auto + - title: Total Storage Size + type: stat + description: Total number of data file size. + unit: decbytes + queries: + - expr: select SUM(disk_size) from information_schema.region_statistics; + datasource: + type: mysql + uid: ${information_schema} + - title: Total Rows + type: stat + description: Total number of data rows in the cluster. Calculated by sum of rows from each region. + unit: sishort + queries: + - expr: select SUM(region_rows) from information_schema.region_statistics; + datasource: + type: mysql + uid: ${information_schema} + - title: Deployment + type: stat + description: The deployment topology of GreptimeDB. + queries: + - expr: SELECT count(*) as datanode FROM information_schema.cluster_info WHERE peer_type = 'DATANODE'; + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT count(*) as frontend FROM information_schema.cluster_info WHERE peer_type = 'FRONTEND'; + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT count(*) as metasrv FROM information_schema.cluster_info WHERE peer_type = 'METASRV'; + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT count(*) as flownode FROM information_schema.cluster_info WHERE peer_type = 'FLOWNODE'; + datasource: + type: mysql + uid: ${information_schema} + - title: Database Resources + type: stat + description: The number of the key resources in GreptimeDB. + queries: + - expr: SELECT COUNT(*) as databases FROM information_schema.schemata WHERE schema_name NOT IN ('greptime_private', 'information_schema') + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT COUNT(*) as tables FROM information_schema.tables WHERE table_schema != 'information_schema' + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT COUNT(region_id) as regions FROM information_schema.region_peers + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT COUNT(*) as flows FROM information_schema.flows + datasource: + type: mysql + uid: ${information_schema} + - title: Data Size + type: stat + description: The data size of wal/index/manifest in the GreptimeDB. + unit: decbytes + queries: + - expr: SELECT SUM(memtable_size) * 0.42825 as WAL FROM information_schema.region_statistics; + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT SUM(index_size) as index FROM information_schema.region_statistics; + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT SUM(manifest_size) as manifest FROM information_schema.region_statistics; + datasource: + type: mysql + uid: ${information_schema} + - title: Ingestion + panels: + - title: Total Ingestion Rate + type: timeseries + description: | + Total ingestion rate. + + Here we listed 3 primary protocols: + + - Prometheus remote write + - Greptime's gRPC API (when using our ingest SDK) + - Log ingestion http API + unit: rowsps + queries: + - expr: sum(rate(greptime_table_operator_ingest_rows{instance=~"$frontend"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: ingestion + - title: Ingestion Rate by Type + type: timeseries + description: | + Total ingestion rate. + + Here we listed 3 primary protocols: + + - Prometheus remote write + - Greptime's gRPC API (when using our ingest SDK) + - Log ingestion http API + unit: rowsps + queries: + - expr: sum(rate(greptime_servers_http_logs_ingestion_counter[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: http-logs + - expr: sum(rate(greptime_servers_prometheus_remote_write_samples[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: prometheus-remote-write + - title: Queries + panels: + - title: Total Query Rate + type: timeseries + description: |- + Total rate of query API calls by protocol. This metric is collected from frontends. + + Here we listed 3 main protocols: + - MySQL + - Postgres + - Prometheus API + + Note that there are some other minor query APIs like /sql are not included + unit: reqps + queries: + - expr: sum (rate(greptime_servers_mysql_query_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: mysql + - expr: sum (rate(greptime_servers_postgres_query_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: pg + - expr: sum (rate(greptime_servers_http_promql_elapsed_counte{instance=~"$frontend"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: promql + - title: Resources + panels: + - title: Datanode Memory per Instance + type: timeseries + description: Current memory usage by instance + unit: decbytes + queries: + - expr: sum(process_resident_memory_bytes{instance=~"$datanode"}) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{ pod }}]' + - title: Datanode CPU Usage per Instance + type: timeseries + description: Current cpu usage by instance + unit: none + queries: + - expr: sum(rate(process_cpu_seconds_total{instance=~"$datanode"}[$__rate_interval]) * 1000) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]' + - title: Frontend Memory per Instance + type: timeseries + description: Current memory usage by instance + unit: decbytes + queries: + - expr: sum(process_resident_memory_bytes{instance=~"$frontend"}) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]' + - title: Frontend CPU Usage per Instance + type: timeseries + description: Current cpu usage by instance + unit: none + queries: + - expr: sum(rate(process_cpu_seconds_total{instance=~"$frontend"}[$__rate_interval]) * 1000) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]-cpu' + - title: Metasrv Memory per Instance + type: timeseries + description: Current memory usage by instance + unit: decbytes + queries: + - expr: sum(process_resident_memory_bytes{instance=~"$metasrv"}) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]-resident' + - title: Metasrv CPU Usage per Instance + type: timeseries + description: Current cpu usage by instance + unit: none + queries: + - expr: sum(rate(process_cpu_seconds_total{instance=~"$metasrv"}[$__rate_interval]) * 1000) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]' + - title: Flownode Memory per Instance + type: timeseries + description: Current memory usage by instance + unit: decbytes + queries: + - expr: sum(process_resident_memory_bytes{instance=~"$flownode"}) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]' + - title: Flownode CPU Usage per Instance + type: timeseries + description: Current cpu usage by instance + unit: none + queries: + - expr: sum(rate(process_cpu_seconds_total{instance=~"$flownode"}[$__rate_interval]) * 1000) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]' + - title: Frontend Requests + panels: + - title: HTTP QPS per Instance + type: timeseries + description: HTTP QPS per Instance. + unit: reqps + queries: + - expr: sum by(instance, pod, path, method, code) (rate(greptime_servers_http_requests_elapsed_count{instance=~"$frontend",path!~"/health|/metrics"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]' + - title: HTTP P99 per Instance + type: timeseries + description: HTTP P99 per Instance. + unit: s + queries: + - expr: 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]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99' + - title: gRPC QPS per Instance + type: timeseries + description: gRPC QPS per Instance. + unit: reqps + queries: + - expr: sum by(instance, pod, path, code) (rate(greptime_servers_grpc_requests_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{path}}]-[{{code}}]' + - title: gRPC P99 per Instance + type: timeseries + description: gRPC P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, path, code) (rate(greptime_servers_grpc_requests_elapsed_bucket{instance=~"$frontend"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99' + - title: MySQL QPS per Instance + type: timeseries + description: MySQL QPS per Instance. + unit: reqps + queries: + - expr: sum by(pod, instance)(rate(greptime_servers_mysql_query_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: MySQL P99 per Instance + type: timeseries + description: MySQL P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(pod, instance, le) (rate(greptime_servers_mysql_query_elapsed_bucket{instance=~"$frontend"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]-p99' + - title: PostgreSQL QPS per Instance + type: timeseries + description: PostgreSQL QPS per Instance. + unit: reqps + queries: + - expr: sum by(pod, instance)(rate(greptime_servers_postgres_query_elapsed_count{instance=~"$frontend"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: PostgreSQL P99 per Instance + type: timeseries + description: PostgreSQL P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(pod,instance,le) (rate(greptime_servers_postgres_query_elapsed_bucket{instance=~"$frontend"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-p99' + - title: Frontend to Datanode + panels: + - title: Ingest Rows per Instance + type: timeseries + description: Ingestion rate by row as in each frontend + unit: rowsps + queries: + - expr: sum by(instance, pod)(rate(greptime_table_operator_ingest_rows{instance=~"$frontend"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: Region Call QPS per Instance + type: timeseries + description: Region Call QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, request_type) (rate(greptime_grpc_region_request_count{instance=~"$frontend"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{request_type}}]' + - title: Region Call P99 per Instance + type: timeseries + description: Region Call P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, request_type) (rate(greptime_grpc_region_request_bucket{instance=~"$frontend"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{request_type}}]' + - title: Mito Engine + panels: + - title: Request OPS per Instance + type: timeseries + description: Request QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, type) (rate(greptime_mito_handle_request_elapsed_count{instance=~"$datanode"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{type}}]' + - title: Request P99 per Instance + type: timeseries + description: Request P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, type) (rate(greptime_mito_handle_request_elapsed_bucket{instance=~"$datanode"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{type}}]' + - title: Write Buffer per Instance + type: timeseries + description: Write Buffer per Instance. + unit: decbytes + queries: + - expr: greptime_mito_write_buffer_bytes{instance=~"$datanode"} + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: Write Rows per Instance + type: timeseries + description: Ingestion size by row counts. + unit: rowsps + queries: + - expr: sum by (instance, pod) (rate(greptime_mito_write_rows_total{instance=~"$datanode"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: Flush OPS per Instance + type: timeseries + description: Flush QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, reason) (rate(greptime_mito_flush_requests_total{instance=~"$datanode"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{reason}}]' + - title: Write Stall per Instance + type: timeseries + description: Write Stall per Instance. + queries: + - expr: sum by(instance, pod) (greptime_mito_write_stall_total{instance=~"$datanode"}) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: Read Stage OPS per Instance + type: timeseries + description: Read Stage OPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod) (rate(greptime_mito_read_stage_elapsed_count{instance=~"$datanode", stage="total"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: Read Stage P99 per Instance + type: timeseries + description: Read Stage P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_read_stage_elapsed_bucket{instance=~"$datanode"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{stage}}]' + - title: Write Stage P99 per Instance + type: timeseries + description: Write Stage P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_write_stage_elapsed_bucket{instance=~"$datanode"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{stage}}]' + - title: Compaction OPS per Instance + type: timeseries + description: Compaction OPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod) (rate(greptime_mito_compaction_total_elapsed_count{instance=~"$datanode"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{pod}}]' + - title: Compaction P99 per Instance by Stage + type: timeseries + description: Compaction latency by stage + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_compaction_stage_elapsed_bucket{instance=~"$datanode"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{stage}}]-p99' + - title: Compaction P99 per Instance + type: timeseries + description: Compaction P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le,stage) (rate(greptime_mito_compaction_total_elapsed_bucket{instance=~"$datanode"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{stage}}]-compaction' + - title: WAL write size + type: timeseries + description: Write-ahead logs write size as bytes. This chart includes stats of p95 and p99 size by instance, total WAL write rate. + unit: bytes + queries: + - expr: histogram_quantile(0.95, sum by(le,instance, pod) (rate(raft_engine_write_size_bucket[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-req-size-p95' + - expr: histogram_quantile(0.99, sum by(le,instance,pod) (rate(raft_engine_write_size_bucket[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-req-size-p99' + - expr: sum by (instance, pod)(rate(raft_engine_write_size_sum[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-throughput' + - title: Cached Bytes per Instance + type: timeseries + description: Cached Bytes per Instance. + unit: decbytes + queries: + - expr: greptime_mito_cache_bytes{instance=~"$datanode"} + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{type}}]' + - title: Inflight Compaction + type: timeseries + description: Ongoing compaction task count + unit: none + queries: + - expr: greptime_mito_inflight_compaction_count + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: WAL sync duration seconds + type: timeseries + description: Raft engine (local disk) log store sync latency, p99 + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(le, type, node, instance, pod) (rate(raft_engine_sync_log_duration_seconds_bucket[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-p99' + - title: Log Store op duration seconds + type: timeseries + description: Write-ahead log operations latency at p99 + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(le,logstore,optype,instance, pod) (rate(greptime_logstore_op_elapsed_bucket[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{logstore}}]-[{{optype}}]-p99' + - title: Inflight Flush + type: timeseries + description: Ongoing flush task count + unit: none + queries: + - expr: greptime_mito_inflight_flush_count + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: OpenDAL + panels: + - title: QPS per Instance + type: timeseries + description: QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]' + - title: Read QPS per Instance + type: timeseries + description: Read QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation="read"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]' + - title: Read P99 per Instance + type: timeseries + description: Read P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode",operation="read"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-{{scheme}}' + - title: Write QPS per Instance + type: timeseries + description: Write QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation="write"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-{{scheme}}' + - title: Write P99 per Instance + type: timeseries + description: Write P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode", operation="write"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]' + - title: List QPS per Instance + type: timeseries + description: List QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode", operation="list"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]' + - title: List P99 per Instance + type: timeseries + description: List P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode", operation="list"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]' + - title: Other Requests per Instance + type: timeseries + description: Other Requests per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{instance=~"$datanode",operation!~"read|write|list|stat"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]' + - title: Other Request P99 per Instance + type: timeseries + description: Other Request P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode", operation!~"read|write|list"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]' + - title: Opendal traffic + type: timeseries + description: Total traffic as in bytes by instance and operation + unit: decbytes + queries: + - expr: sum by(instance, pod, scheme, operation) (rate(opendal_operation_bytes_sum{instance=~"$datanode"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]' + - title: OpenDAL errors per Instance + type: timeseries + description: OpenDAL error counts per Instance. + queries: + - expr: sum by(instance, pod, scheme, operation, error) (rate(opendal_operation_errors_total{instance=~"$datanode", error!="NotFound"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]' + - title: Metasrv + panels: + - title: Region migration datanode + type: state-timeline + description: Counter of region migration by source and destination + unit: none + queries: + - expr: greptime_meta_region_migration_stat{datanode_type="src"} + datasource: + type: prometheus + uid: ${metrics} + legendFormat: from-datanode-{{datanode_id}} + - expr: greptime_meta_region_migration_stat{datanode_type="desc"} + datasource: + type: prometheus + uid: ${metrics} + legendFormat: to-datanode-{{datanode_id}} + - title: Region migration error + type: timeseries + description: Counter of region migration error + unit: none + queries: + - expr: greptime_meta_region_migration_error + datasource: + type: prometheus + uid: ${metrics} + legendFormat: __auto + - title: Datanode load + type: timeseries + description: Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. + unit: none + queries: + - expr: greptime_datanode_load + datasource: + type: prometheus + uid: ${metrics} + legendFormat: __auto + - title: Flownode + panels: + - title: Flow Ingest / Output Rate + type: timeseries + description: Flow Ingest / Output Rate. + queries: + - expr: sum by(instance, pod, direction) (rate(greptime_flow_processed_rows[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{pod}}]-[{{instance}}]-[{{direction}}]' + - title: Flow Ingest Latency + type: timeseries + description: Flow Ingest Latency. + queries: + - expr: histogram_quantile(0.95, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod)) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-p95' + - expr: histogram_quantile(0.99, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod)) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-p99' + - title: Flow Operation Latency + type: timeseries + description: Flow Operation Latency. + queries: + - expr: histogram_quantile(0.95, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type)) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{type}}]-p95' + - expr: histogram_quantile(0.99, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type)) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{type}}]-p99' + - title: Flow Buffer Size per Instance + type: timeseries + description: Flow Buffer Size per Instance. + queries: + - expr: greptime_flow_input_buf_size + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}]' + - title: Flow Processing Error per Instance + type: timeseries + description: Flow Processing Error per Instance. + queries: + - expr: sum by(instance,pod,code) (rate(greptime_flow_errors[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{code}}]' diff --git a/grafana/dashboards/standalone/dashboard.json b/grafana/dashboards/standalone/dashboard.json new file mode 100644 index 0000000000..d799cd7e0f --- /dev/null +++ b/grafana/dashboards/standalone/dashboard.json @@ -0,0 +1,7193 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "The Grafana dashboards for GreptimeDB.", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 279, + "panels": [], + "title": "Overview", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "The start time of GreptimeDB.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "fieldMinMax": false, + "mappings": [], + "max": 2, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 1 + }, + "id": 265, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "limit": 1, + "values": true + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "time() - process_start_time_seconds", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "description": "GreptimeDB version.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 3, + "y": 1 + }, + "id": 239, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^pkg_version$/", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT pkg_version FROM information_schema.build_info", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Version", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Total ingestion rate.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "fieldMinMax": false, + "mappings": [], + "max": 2, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "rowsps" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 5, + "y": 1 + }, + "id": 249, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(rate(greptime_table_operator_ingest_rows[$__rate_interval]))", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Total Ingestion Rate", + "type": "stat" + }, + { + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "description": "Total number of data file size.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 9, + "y": 1 + }, + "id": 248, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "select SUM(disk_size) from information_schema.region_statistics;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Total Storage Size", + "type": "stat" + }, + { + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "description": "Total number of data rows in the cluster. Calculated by sum of rows from each region.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "sishort" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 13, + "y": 1 + }, + "id": 254, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "select SUM(region_rows) from information_schema.region_statistics;", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Total Rows", + "type": "stat" + }, + { + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "description": "The deployment topology of GreptimeDB.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 0, + "y": 5 + }, + "id": 243, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT count(*) as datanode FROM information_schema.cluster_info WHERE peer_type = 'DATANODE';", + "refId": "datanode", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT count(*) as frontend FROM information_schema.cluster_info WHERE peer_type = 'FRONTEND';", + "refId": "frontend", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT count(*) as metasrv FROM information_schema.cluster_info WHERE peer_type = 'METASRV';", + "refId": "metasrv", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT count(*) as flownode FROM information_schema.cluster_info WHERE peer_type = 'FLOWNODE';", + "refId": "flownode", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Deployment", + "type": "stat" + }, + { + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "description": "The number of the key resources in GreptimeDB.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 5, + "y": 5 + }, + "id": 247, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT COUNT(*) as databases FROM information_schema.schemata WHERE schema_name NOT IN ('greptime_private', 'information_schema')", + "refId": "databases", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "rawQuery": true, + "rawSql": "SELECT COUNT(*) as tables FROM information_schema.tables WHERE table_schema != 'information_schema'", + "refId": "tables", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT COUNT(region_id) as regions FROM information_schema.region_peers", + "refId": "A", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT COUNT(*) as flows FROM information_schema.flows", + "refId": "B", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Database Resources", + "type": "stat" + }, + { + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "description": "The data size of wal/index/manifest in the GreptimeDB.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 10, + "y": 5 + }, + "id": 278, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT SUM(memtable_size) * 0.42825 as WAL FROM information_schema.region_statistics;\n", + "refId": "WAL", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT SUM(index_size) as index FROM information_schema.region_statistics;\n", + "refId": "Index", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + }, + { + "dataset": "information_schema", + "datasource": { + "type": "mysql", + "uid": "${information_schema}" + }, + "editorMode": "code", + "format": "table", + "hide": false, + "rawQuery": true, + "rawSql": "SELECT SUM(manifest_size) as manifest FROM information_schema.region_statistics;\n", + "refId": "manifest", + "sql": { + "columns": [ + { + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + ], + "title": "Data Size", + "type": "stat" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 275, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Total ingestion rate.\n\nHere we listed 3 primary protocols:\n\n- Prometheus remote write\n- Greptime's gRPC API (when using our ingest SDK)\n- Log ingestion http API\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "rowsps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 193, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(rate(greptime_table_operator_ingest_rows{}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "ingestion", + "range": true, + "refId": "C" + } + ], + "title": "Total Ingestion Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Total ingestion rate.\n\nHere we listed 3 primary protocols:\n\n- Prometheus remote write\n- Greptime's gRPC API (when using our ingest SDK)\n- Log ingestion http API\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "rowsps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 784 + }, + "id": 277, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(rate(greptime_servers_http_logs_ingestion_counter[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "http-logs", + "range": true, + "refId": "http_logs" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(rate(greptime_servers_prometheus_remote_write_samples[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "prometheus-remote-write", + "range": true, + "refId": "prometheus-remote-write" + } + ], + "title": "Ingestion Rate by Type", + "type": "timeseries" + } + ], + "title": "Ingestion", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 276, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Total rate of query API calls by protocol. This metric is collected from frontends.\n\nHere we listed 3 main protocols:\n- MySQL\n- Postgres\n- Prometheus API\n\nNote that there are some other minor query APIs like /sql are not included", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 1589 + }, + "id": 255, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum (rate(greptime_servers_mysql_query_elapsed_count{}[$__rate_interval]))", + "instant": false, + "legendFormat": "mysql", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum (rate(greptime_servers_postgres_query_elapsed_count{}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "pg", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum (rate(greptime_servers_http_promql_elapsed_counte{}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "promql", + "range": true, + "refId": "C" + } + ], + "title": "Total Query Rate", + "type": "timeseries" + } + ], + "title": "Queries", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 11 + }, + "id": 274, + "panels": [], + "title": "Resources", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current memory usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 12 + }, + "id": 256, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(process_resident_memory_bytes{}) by (instance, pod)", + "instant": false, + "legendFormat": "[{{instance}}]-[{{ pod }}]", + "range": true, + "refId": "A" + } + ], + "title": "Datanode Memory per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current cpu usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 12 + }, + "id": 262, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]", + "range": true, + "refId": "A" + } + ], + "title": "Datanode CPU Usage per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current memory usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 22 + }, + "id": 266, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(process_resident_memory_bytes{}) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]", + "range": true, + "refId": "A" + } + ], + "title": "Frontend Memory per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current cpu usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 22 + }, + "id": 268, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]-cpu", + "range": true, + "refId": "A" + } + ], + "title": "Frontend CPU Usage per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current memory usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 32 + }, + "id": 269, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(process_resident_memory_bytes{}) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]-resident", + "range": true, + "refId": "A" + } + ], + "title": "Metasrv Memory per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current cpu usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 32 + }, + "id": 271, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]", + "range": true, + "refId": "A" + } + ], + "title": "Metasrv CPU Usage per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current memory usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 42 + }, + "id": 272, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum(process_resident_memory_bytes{}) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]", + "range": true, + "refId": "A" + } + ], + "title": "Flownode Memory per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Current cpu usage by instance", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 42 + }, + "id": 273, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod)", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]", + "range": true, + "refId": "A" + } + ], + "title": "Flownode CPU Usage per Instance", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 52 + }, + "id": 280, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "HTTP QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "[10.244.1.81:4000]-[mycluster-frontend-5bdf57f86-kshxt]-[/v1/prometheus/write]-[POST]-[500]" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1507 + }, + "id": 281, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, path, method, code) (rate(greptime_servers_http_requests_elapsed_count{path!~\"/health|/metrics\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]", + "range": true, + "refId": "A" + } + ], + "title": "HTTP QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "HTTP P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1507 + }, + "id": 282, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, path, method, code) (rate(greptime_servers_http_requests_elapsed_bucket{path!~\"/health|/metrics\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "HTTP P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "gRPC QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1515 + }, + "id": 283, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, path, code) (rate(greptime_servers_grpc_requests_elapsed_count{}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{path}}]-[{{code}}]", + "range": true, + "refId": "A" + } + ], + "title": "gRPC QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "gRPC P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1515 + }, + "id": 284, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, path, code) (rate(greptime_servers_grpc_requests_elapsed_bucket{}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "gRPC P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "MySQL QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1523 + }, + "id": 285, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(pod, instance)(rate(greptime_servers_mysql_query_elapsed_count{}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "MySQL QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "MySQL P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1523 + }, + "id": 286, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "histogram_quantile(0.99, sum by(pod, instance, le) (rate(greptime_servers_mysql_query_elapsed_bucket{}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{ pod }}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "MySQL P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "PostgreSQL QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1531 + }, + "id": 287, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(pod, instance)(rate(greptime_servers_postgres_query_elapsed_count{}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "PostgreSQL QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "PostgreSQL P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1531 + }, + "id": 288, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(pod,instance,le) (rate(greptime_servers_postgres_query_elapsed_bucket{}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "PostgreSQL P99 per Instance", + "type": "timeseries" + } + ], + "title": "Frontend Requests", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 53 + }, + "id": 289, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Ingestion rate by row as in each frontend", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "rowsps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 292, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod)(rate(greptime_table_operator_ingest_rows{}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Ingest Rows per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Region Call QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 792 + }, + "id": 290, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, request_type) (rate(greptime_grpc_region_request_count{}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{request_type}}]", + "range": true, + "refId": "A" + } + ], + "title": "Region Call QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Region Call P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 792 + }, + "id": 291, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, request_type) (rate(greptime_grpc_region_request_bucket{}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{request_type}}]", + "range": true, + "refId": "A" + } + ], + "title": "Region Call P99 per Instance", + "type": "timeseries" + } + ], + "title": "Frontend to Datanode", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 293, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Request QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 212 + }, + "id": 294, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, type) (rate(greptime_mito_handle_request_elapsed_count{}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{type}}]", + "range": true, + "refId": "A" + } + ], + "title": "Request OPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Request P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 212 + }, + "id": 295, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, type) (rate(greptime_mito_handle_request_elapsed_bucket{}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{type}}]", + "range": true, + "refId": "A" + } + ], + "title": "Request P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write Buffer per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 265 + }, + "id": 296, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_mito_write_buffer_bytes{}", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Write Buffer per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Ingestion size by row counts.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "rowsps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 265 + }, + "id": 297, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by (instance, pod) (rate(greptime_mito_write_rows_total{}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Write Rows per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Flush QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 273 + }, + "id": 298, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, reason) (rate(greptime_mito_flush_requests_total{}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{reason}}]", + "range": true, + "refId": "A" + } + ], + "title": "Flush OPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write Stall per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 273 + }, + "id": 299, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod) (greptime_mito_write_stall_total{})", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Write Stall per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Read Stage OPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 281 + }, + "id": 300, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod) (rate(greptime_mito_read_stage_elapsed_count{ stage=\"total\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Read Stage OPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Read Stage P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 281 + }, + "id": 301, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_read_stage_elapsed_bucket{}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{stage}}]", + "range": true, + "refId": "A" + } + ], + "title": "Read Stage P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write Stage P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 289 + }, + "id": 302, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Last *", + "sortDesc": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_write_stage_elapsed_bucket{}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{stage}}]", + "range": true, + "refId": "A" + } + ], + "title": "Write Stage P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Compaction OPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 289 + }, + "id": 303, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod) (rate(greptime_mito_compaction_total_elapsed_count{}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{ instance }}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Compaction OPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Compaction latency by stage", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 297 + }, + "id": 304, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_compaction_stage_elapsed_bucket{}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{stage}}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "Compaction P99 per Instance by Stage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Compaction P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 297 + }, + "id": 305, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le,stage) (rate(greptime_mito_compaction_total_elapsed_bucket{}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{stage}}]-compaction", + "range": true, + "refId": "A" + } + ], + "title": "Compaction P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write-ahead logs write size as bytes. This chart includes stats of p95 and p99 size by instance, total WAL write rate.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 305 + }, + "id": 306, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by(le,instance, pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-req-size-p95", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(le,instance,pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))", + "hide": false, + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-req-size-p99", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by (instance, pod)(rate(raft_engine_write_size_sum[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-throughput", + "range": true, + "refId": "C" + } + ], + "title": "WAL write size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Cached Bytes per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 305 + }, + "id": 307, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_mito_cache_bytes{}", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{type}}]", + "range": true, + "refId": "A" + } + ], + "title": "Cached Bytes per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Ongoing compaction task count", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 313 + }, + "id": 308, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_mito_inflight_compaction_count", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Inflight Compaction", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Raft engine (local disk) log store sync latency, p99", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 313 + }, + "id": 310, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(le, type, node, instance, pod) (rate(raft_engine_sync_log_duration_seconds_bucket[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "WAL sync duration seconds", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write-ahead log operations latency at p99", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 321 + }, + "id": 311, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(le,logstore,optype,instance, pod) (rate(greptime_logstore_op_elapsed_bucket[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{logstore}}]-[{{optype}}]-p99", + "range": true, + "refId": "A" + } + ], + "title": "Log Store op duration seconds", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Ongoing flush task count", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 321 + }, + "id": 312, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_mito_inflight_flush_count", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]", + "range": true, + "refId": "A" + } + ], + "title": "Inflight Flush", + "type": "timeseries" + } + ], + "title": "Mito Engine", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 55 + }, + "id": 313, + "panels": [], + "title": "OpenDAL", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 56 + }, + "id": 314, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]", + "range": true, + "refId": "A" + } + ], + "title": "QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Read QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 66 + }, + "id": 315, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{ operation=\"read\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]", + "range": true, + "refId": "A" + } + ], + "title": "Read QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Read P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 66 + }, + "id": 316, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{operation=\"read\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-{{scheme}}", + "range": true, + "refId": "A" + } + ], + "title": "Read P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 73 + }, + "id": 317, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{ operation=\"write\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-{{scheme}}", + "range": true, + "refId": "A" + } + ], + "title": "Write QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Write P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 73 + }, + "id": 318, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{ operation=\"write\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]", + "range": true, + "refId": "A" + } + ], + "title": "Write P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "List QPS per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 80 + }, + "id": 319, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{ operation=\"list\"}[$__rate_interval]))", + "instant": false, + "interval": "", + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]", + "range": true, + "refId": "A" + } + ], + "title": "List QPS per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "List P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 80 + }, + "id": 320, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{ operation=\"list\"}[$__rate_interval])))", + "instant": false, + "interval": "", + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]", + "range": true, + "refId": "A" + } + ], + "title": "List P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Other Requests per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 87 + }, + "id": 321, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{operation!~\"read|write|list|stat\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]", + "range": true, + "refId": "A" + } + ], + "title": "Other Requests per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Other Request P99 per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 87 + }, + "id": 322, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{ operation!~\"read|write|list\"}[$__rate_interval])))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]", + "range": true, + "refId": "A" + } + ], + "title": "Other Request P99 per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Total traffic as in bytes by instance and operation", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 94 + }, + "id": 323, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme, operation) (rate(opendal_operation_bytes_sum{}[$__rate_interval]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Opendal traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "OpenDAL error counts per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 94 + }, + "id": 334, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.1.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, scheme, operation, error) (rate(opendal_operation_errors_total{ error!=\"NotFound\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]", + "range": true, + "refId": "A" + } + ], + "title": "OpenDAL errors per Instance", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 101 + }, + "id": 324, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Counter of region migration by source and destination", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "fillOpacity": 70, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 325, + "options": { + "alignValue": "left", + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_meta_region_migration_stat{datanode_type=\"src\"}", + "instant": false, + "legendFormat": "from-datanode-{{datanode_id}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_meta_region_migration_stat{datanode_type=\"desc\"}", + "hide": false, + "instant": false, + "legendFormat": "to-datanode-{{datanode_id}}", + "range": true, + "refId": "B" + } + ], + "title": "Region migration datanode", + "type": "state-timeline" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Counter of region migration error", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 326, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_meta_region_migration_error", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Region migration error", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1199 + }, + "id": 327, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_datanode_load", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Datanode load", + "type": "timeseries" + } + ], + "title": "Metasrv", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 102 + }, + "id": 328, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Flow Ingest / Output Rate.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1200 + }, + "id": 329, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance, pod, direction) (rate(greptime_flow_processed_rows[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{pod}}]-[{{instance}}]-[{{direction}}]", + "range": true, + "refId": "A" + } + ], + "title": "Flow Ingest / Output Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Flow Ingest Latency.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1200 + }, + "id": 330, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-p95", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod))", + "hide": false, + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-p99", + "range": true, + "refId": "B" + } + ], + "title": "Flow Ingest Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Flow Operation Latency.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 0, + "y": 1208 + }, + "id": 331, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{type}}]-p95", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type))", + "hide": false, + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{type}}]-p99", + "range": true, + "refId": "B" + } + ], + "title": "Flow Operation Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Flow Buffer Size per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 9, + "y": 1208 + }, + "id": 332, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "greptime_flow_input_buf_size", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}]", + "range": true, + "refId": "A" + } + ], + "title": "Flow Buffer Size per Instance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "description": "Flow Processing Error per Instance.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 1208 + }, + "id": 333, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "editorMode": "code", + "expr": "sum by(instance,pod,code) (rate(greptime_flow_errors[$__rate_interval]))", + "instant": false, + "legendFormat": "[{{instance}}]-[{{pod}}]-[{{code}}]", + "range": true, + "refId": "A" + } + ], + "title": "Flow Processing Error per Instance", + "type": "timeseries" + } + ], + "title": "Flownode", + "type": "row" + } + ], + "refresh": "10s", + "schemaVersion": 40, + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "includeAll": false, + "name": "metrics", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "current": {}, + "includeAll": false, + "name": "information_schema", + "options": [], + "query": "mysql", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "definition": "label_values(greptime_app_version{app=\"greptime-datanode\"},instance)", + "hide": 2, + "includeAll": true, + "multi": true, + "name": "datanode", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(greptime_app_version{app=\"greptime-datanode\"},instance)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "definition": "label_values(greptime_app_version{app=\"greptime-frontend\"},instance)", + "hide": 2, + "includeAll": true, + "multi": true, + "name": "frontend", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(greptime_app_version{app=\"greptime-frontend\"},instance)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "definition": "label_values(greptime_app_version{app=\"greptime-metasrv\"},instance)", + "hide": 2, + "includeAll": true, + "multi": true, + "name": "metasrv", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(greptime_app_version{app=\"greptime-metasrv\"},instance)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${metrics}" + }, + "definition": "label_values(greptime_app_version{app=\"greptime-flownode\"},instance)", + "hide": 2, + "includeAll": true, + "multi": true, + "name": "flownode", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(greptime_app_version{app=\"greptime-flownode\"},instance)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "GreptimeDB", + "uid": "dejf3k5e7g2kgb", + "version": 2, + "weekStart": "" +} diff --git a/grafana/dashboards/standalone/dashboard.md b/grafana/dashboards/standalone/dashboard.md new file mode 100644 index 0000000000..673faf3357 --- /dev/null +++ b/grafana/dashboards/standalone/dashboard.md @@ -0,0 +1,97 @@ +# Overview +| Title | Query | Type | Description | Datasource | Unit | Legend Format | +| --- | --- | --- | --- | --- | --- | --- | +| 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` | -- | +| Deployment | `SELECT count(*) as datanode FROM information_schema.cluster_info WHERE peer_type = 'DATANODE';`
`SELECT count(*) as frontend FROM information_schema.cluster_info WHERE peer_type = 'FRONTEND';`
`SELECT count(*) as metasrv FROM information_schema.cluster_info WHERE peer_type = 'METASRV';`
`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')`
`SELECT COUNT(*) as tables FROM information_schema.tables WHERE table_schema != 'information_schema'`
`SELECT COUNT(region_id) as regions FROM information_schema.region_peers`
`SELECT COUNT(*) as flows FROM information_schema.flows` | `stat` | The number of the key resources in GreptimeDB. | `mysql` | -- | -- | +| Data Size | `SELECT SUM(memtable_size) * 0.42825 as WAL FROM information_schema.region_statistics;`
`SELECT SUM(index_size) as index FROM information_schema.region_statistics;`
`SELECT SUM(manifest_size) as manifest FROM information_schema.region_statistics;` | `stat` | The data size of wal/index/manifest in the GreptimeDB. | `mysql` | `decbytes` | -- | +# 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.

Here we listed 3 primary protocols:

- Prometheus remote write
- Greptime's gRPC API (when using our ingest SDK)
- Log ingestion http API
| `prometheus` | `rowsps` | `ingestion` | +| Ingestion Rate by Type | `sum(rate(greptime_servers_http_logs_ingestion_counter[$__rate_interval]))`
`sum(rate(greptime_servers_prometheus_remote_write_samples[$__rate_interval]))` | `timeseries` | Total ingestion rate.

Here we listed 3 primary protocols:

- Prometheus remote write
- Greptime's gRPC API (when using our ingest SDK)
- Log ingestion http API
| `prometheus` | `rowsps` | `http-logs` | +# Queries +| Title | Query | Type | Description | Datasource | Unit | Legend Format | +| --- | --- | --- | --- | --- | --- | --- | +| 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_counte{}[$__rate_interval]))` | `timeseries` | Total rate of query API calls by protocol. This metric is collected from frontends.

Here we listed 3 main protocols:
- MySQL
- Postgres
- Prometheus API

Note that there are some other minor query APIs like /sql are not included | `prometheus` | `reqps` | `mysql` | +# Resources +| Title | Query | Type | Description | Datasource | Unit | Legend Format | +| --- | --- | --- | --- | --- | --- | --- | +| Datanode Memory per Instance | `sum(process_resident_memory_bytes{}) by (instance, pod)` | `timeseries` | Current memory usage by instance | `prometheus` | `decbytes` | `[{{instance}}]-[{{ pod }}]` | +| Datanode CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod)` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` | +| Frontend Memory per Instance | `sum(process_resident_memory_bytes{}) by (instance, pod)` | `timeseries` | Current memory usage by instance | `prometheus` | `decbytes` | `[{{ instance }}]-[{{ pod }}]` | +| Frontend CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod)` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]-cpu` | +| Metasrv Memory per Instance | `sum(process_resident_memory_bytes{}) by (instance, pod)` | `timeseries` | Current memory usage by instance | `prometheus` | `decbytes` | `[{{ instance }}]-[{{ pod }}]-resident` | +| Metasrv CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod)` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ instance }}]-[{{ pod }}]` | +| Flownode Memory per Instance | `sum(process_resident_memory_bytes{}) by (instance, pod)` | `timeseries` | Current memory usage by instance | `prometheus` | `decbytes` | `[{{ instance }}]-[{{ pod }}]` | +| Flownode CPU Usage per Instance | `sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod)` | `timeseries` | Current cpu usage by instance | `prometheus` | `none` | `[{{ 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` | +# 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}}]` | +# Mito Engine +| 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 P99 per Instance by Stage | `histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_compaction_stage_elapsed_bucket{}[$__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])))`
`histogram_quantile(0.99, sum by(le,instance,pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))`
`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}}]` | +# OpenDAL +| 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) (rate(opendal_operation_duration_seconds_count{ operation="read"}[$__rate_interval]))` | `timeseries` | Read QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]` | +| Read P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{operation="read"}[$__rate_interval])))` | `timeseries` | Read P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-{{scheme}}` | +| Write QPS per Instance | `sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{ operation="write"}[$__rate_interval]))` | `timeseries` | Write QPS per Instance. | `prometheus` | `ops` | `[{{instance}}]-[{{pod}}]-{{scheme}}` | +| Write P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{ operation="write"}[$__rate_interval])))` | `timeseries` | Write P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]` | +| 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"}[$__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}}]` | +# Metasrv +| Title | Query | Type | Description | Datasource | Unit | Legend Format | +| --- | --- | --- | --- | --- | --- | --- | +| Region migration datanode | `greptime_meta_region_migration_stat{datanode_type="src"}`
`greptime_meta_region_migration_stat{datanode_type="desc"}` | `state-timeline` | Counter of region migration by source and destination | `prometheus` | `none` | `from-datanode-{{datanode_id}}` | +| Region migration error | `greptime_meta_region_migration_error` | `timeseries` | Counter of region migration error | `prometheus` | `none` | `__auto` | +| 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` | `none` | `__auto` | +# 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))`
`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))`
`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}}]` | diff --git a/grafana/dashboards/standalone/dashboard.yaml b/grafana/dashboards/standalone/dashboard.yaml new file mode 100644 index 0000000000..828ac1dffc --- /dev/null +++ b/grafana/dashboards/standalone/dashboard.yaml @@ -0,0 +1,769 @@ +groups: + - title: Overview + panels: + - title: Uptime + type: stat + description: The start time of GreptimeDB. + unit: s + queries: + - expr: time() - process_start_time_seconds + datasource: + type: prometheus + uid: ${metrics} + legendFormat: __auto + - title: Version + type: stat + description: GreptimeDB version. + queries: + - expr: SELECT pkg_version FROM information_schema.build_info + datasource: + type: mysql + uid: ${information_schema} + - title: Total Ingestion Rate + type: stat + description: Total ingestion rate. + unit: rowsps + queries: + - expr: sum(rate(greptime_table_operator_ingest_rows[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: __auto + - title: Total Storage Size + type: stat + description: Total number of data file size. + unit: decbytes + queries: + - expr: select SUM(disk_size) from information_schema.region_statistics; + datasource: + type: mysql + uid: ${information_schema} + - title: Total Rows + type: stat + description: Total number of data rows in the cluster. Calculated by sum of rows from each region. + unit: sishort + queries: + - expr: select SUM(region_rows) from information_schema.region_statistics; + datasource: + type: mysql + uid: ${information_schema} + - title: Deployment + type: stat + description: The deployment topology of GreptimeDB. + queries: + - expr: SELECT count(*) as datanode FROM information_schema.cluster_info WHERE peer_type = 'DATANODE'; + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT count(*) as frontend FROM information_schema.cluster_info WHERE peer_type = 'FRONTEND'; + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT count(*) as metasrv FROM information_schema.cluster_info WHERE peer_type = 'METASRV'; + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT count(*) as flownode FROM information_schema.cluster_info WHERE peer_type = 'FLOWNODE'; + datasource: + type: mysql + uid: ${information_schema} + - title: Database Resources + type: stat + description: The number of the key resources in GreptimeDB. + queries: + - expr: SELECT COUNT(*) as databases FROM information_schema.schemata WHERE schema_name NOT IN ('greptime_private', 'information_schema') + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT COUNT(*) as tables FROM information_schema.tables WHERE table_schema != 'information_schema' + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT COUNT(region_id) as regions FROM information_schema.region_peers + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT COUNT(*) as flows FROM information_schema.flows + datasource: + type: mysql + uid: ${information_schema} + - title: Data Size + type: stat + description: The data size of wal/index/manifest in the GreptimeDB. + unit: decbytes + queries: + - expr: SELECT SUM(memtable_size) * 0.42825 as WAL FROM information_schema.region_statistics; + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT SUM(index_size) as index FROM information_schema.region_statistics; + datasource: + type: mysql + uid: ${information_schema} + - expr: SELECT SUM(manifest_size) as manifest FROM information_schema.region_statistics; + datasource: + type: mysql + uid: ${information_schema} + - title: Ingestion + panels: + - title: Total Ingestion Rate + type: timeseries + description: | + Total ingestion rate. + + Here we listed 3 primary protocols: + + - Prometheus remote write + - Greptime's gRPC API (when using our ingest SDK) + - Log ingestion http API + unit: rowsps + queries: + - expr: sum(rate(greptime_table_operator_ingest_rows{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: ingestion + - title: Ingestion Rate by Type + type: timeseries + description: | + Total ingestion rate. + + Here we listed 3 primary protocols: + + - Prometheus remote write + - Greptime's gRPC API (when using our ingest SDK) + - Log ingestion http API + unit: rowsps + queries: + - expr: sum(rate(greptime_servers_http_logs_ingestion_counter[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: http-logs + - expr: sum(rate(greptime_servers_prometheus_remote_write_samples[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: prometheus-remote-write + - title: Queries + panels: + - title: Total Query Rate + type: timeseries + description: |- + Total rate of query API calls by protocol. This metric is collected from frontends. + + Here we listed 3 main protocols: + - MySQL + - Postgres + - Prometheus API + + Note that there are some other minor query APIs like /sql are not included + unit: reqps + queries: + - expr: sum (rate(greptime_servers_mysql_query_elapsed_count{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: mysql + - expr: sum (rate(greptime_servers_postgres_query_elapsed_count{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: pg + - expr: sum (rate(greptime_servers_http_promql_elapsed_counte{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: promql + - title: Resources + panels: + - title: Datanode Memory per Instance + type: timeseries + description: Current memory usage by instance + unit: decbytes + queries: + - expr: sum(process_resident_memory_bytes{}) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{ pod }}]' + - title: Datanode CPU Usage per Instance + type: timeseries + description: Current cpu usage by instance + unit: none + queries: + - expr: sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]' + - title: Frontend Memory per Instance + type: timeseries + description: Current memory usage by instance + unit: decbytes + queries: + - expr: sum(process_resident_memory_bytes{}) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]' + - title: Frontend CPU Usage per Instance + type: timeseries + description: Current cpu usage by instance + unit: none + queries: + - expr: sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]-cpu' + - title: Metasrv Memory per Instance + type: timeseries + description: Current memory usage by instance + unit: decbytes + queries: + - expr: sum(process_resident_memory_bytes{}) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]-resident' + - title: Metasrv CPU Usage per Instance + type: timeseries + description: Current cpu usage by instance + unit: none + queries: + - expr: sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]' + - title: Flownode Memory per Instance + type: timeseries + description: Current memory usage by instance + unit: decbytes + queries: + - expr: sum(process_resident_memory_bytes{}) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]' + - title: Flownode CPU Usage per Instance + type: timeseries + description: Current cpu usage by instance + unit: none + queries: + - expr: sum(rate(process_cpu_seconds_total{}[$__rate_interval]) * 1000) by (instance, pod) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]' + - title: Frontend Requests + panels: + - title: HTTP QPS per Instance + type: timeseries + description: HTTP QPS per Instance. + unit: reqps + queries: + - expr: sum by(instance, pod, path, method, code) (rate(greptime_servers_http_requests_elapsed_count{path!~"/health|/metrics"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]' + - title: HTTP P99 per Instance + type: timeseries + description: HTTP P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, path, method, code) (rate(greptime_servers_http_requests_elapsed_bucket{path!~"/health|/metrics"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99' + - title: gRPC QPS per Instance + type: timeseries + description: gRPC QPS per Instance. + unit: reqps + queries: + - expr: sum by(instance, pod, path, code) (rate(greptime_servers_grpc_requests_elapsed_count{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{path}}]-[{{code}}]' + - title: gRPC P99 per Instance + type: timeseries + description: gRPC P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, path, code) (rate(greptime_servers_grpc_requests_elapsed_bucket{}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99' + - title: MySQL QPS per Instance + type: timeseries + description: MySQL QPS per Instance. + unit: reqps + queries: + - expr: sum by(pod, instance)(rate(greptime_servers_mysql_query_elapsed_count{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: MySQL P99 per Instance + type: timeseries + description: MySQL P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(pod, instance, le) (rate(greptime_servers_mysql_query_elapsed_bucket{}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{ pod }}]-p99' + - title: PostgreSQL QPS per Instance + type: timeseries + description: PostgreSQL QPS per Instance. + unit: reqps + queries: + - expr: sum by(pod, instance)(rate(greptime_servers_postgres_query_elapsed_count{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: PostgreSQL P99 per Instance + type: timeseries + description: PostgreSQL P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(pod,instance,le) (rate(greptime_servers_postgres_query_elapsed_bucket{}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-p99' + - title: Frontend to Datanode + panels: + - title: Ingest Rows per Instance + type: timeseries + description: Ingestion rate by row as in each frontend + unit: rowsps + queries: + - expr: sum by(instance, pod)(rate(greptime_table_operator_ingest_rows{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: Region Call QPS per Instance + type: timeseries + description: Region Call QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, request_type) (rate(greptime_grpc_region_request_count{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{request_type}}]' + - title: Region Call P99 per Instance + type: timeseries + description: Region Call P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, request_type) (rate(greptime_grpc_region_request_bucket{}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{request_type}}]' + - title: Mito Engine + panels: + - title: Request OPS per Instance + type: timeseries + description: Request QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, type) (rate(greptime_mito_handle_request_elapsed_count{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{type}}]' + - title: Request P99 per Instance + type: timeseries + description: Request P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, type) (rate(greptime_mito_handle_request_elapsed_bucket{}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{type}}]' + - title: Write Buffer per Instance + type: timeseries + description: Write Buffer per Instance. + unit: decbytes + queries: + - expr: greptime_mito_write_buffer_bytes{} + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: Write Rows per Instance + type: timeseries + description: Ingestion size by row counts. + unit: rowsps + queries: + - expr: sum by (instance, pod) (rate(greptime_mito_write_rows_total{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: Flush OPS per Instance + type: timeseries + description: Flush QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, reason) (rate(greptime_mito_flush_requests_total{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{reason}}]' + - title: Write Stall per Instance + type: timeseries + description: Write Stall per Instance. + queries: + - expr: sum by(instance, pod) (greptime_mito_write_stall_total{}) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: Read Stage OPS per Instance + type: timeseries + description: Read Stage OPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod) (rate(greptime_mito_read_stage_elapsed_count{ stage="total"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: Read Stage P99 per Instance + type: timeseries + description: Read Stage P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_read_stage_elapsed_bucket{}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{stage}}]' + - title: Write Stage P99 per Instance + type: timeseries + description: Write Stage P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_write_stage_elapsed_bucket{}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{stage}}]' + - title: Compaction OPS per Instance + type: timeseries + description: Compaction OPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod) (rate(greptime_mito_compaction_total_elapsed_count{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{ instance }}]-[{{pod}}]' + - title: Compaction P99 per Instance by Stage + type: timeseries + description: Compaction latency by stage + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, stage) (rate(greptime_mito_compaction_stage_elapsed_bucket{}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{stage}}]-p99' + - title: Compaction P99 per Instance + type: timeseries + description: Compaction P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le,stage) (rate(greptime_mito_compaction_total_elapsed_bucket{}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{stage}}]-compaction' + - title: WAL write size + type: timeseries + description: Write-ahead logs write size as bytes. This chart includes stats of p95 and p99 size by instance, total WAL write rate. + unit: bytes + queries: + - expr: histogram_quantile(0.95, sum by(le,instance, pod) (rate(raft_engine_write_size_bucket[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-req-size-p95' + - expr: histogram_quantile(0.99, sum by(le,instance,pod) (rate(raft_engine_write_size_bucket[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-req-size-p99' + - expr: sum by (instance, pod)(rate(raft_engine_write_size_sum[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-throughput' + - title: Cached Bytes per Instance + type: timeseries + description: Cached Bytes per Instance. + unit: decbytes + queries: + - expr: greptime_mito_cache_bytes{} + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{type}}]' + - title: Inflight Compaction + type: timeseries + description: Ongoing compaction task count + unit: none + queries: + - expr: greptime_mito_inflight_compaction_count + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: WAL sync duration seconds + type: timeseries + description: Raft engine (local disk) log store sync latency, p99 + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(le, type, node, instance, pod) (rate(raft_engine_sync_log_duration_seconds_bucket[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-p99' + - title: Log Store op duration seconds + type: timeseries + description: Write-ahead log operations latency at p99 + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(le,logstore,optype,instance, pod) (rate(greptime_logstore_op_elapsed_bucket[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{logstore}}]-[{{optype}}]-p99' + - title: Inflight Flush + type: timeseries + description: Ongoing flush task count + unit: none + queries: + - expr: greptime_mito_inflight_flush_count + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]' + - title: OpenDAL + panels: + - title: QPS per Instance + type: timeseries + description: QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]' + - title: Read QPS per Instance + type: timeseries + description: Read QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{ operation="read"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]' + - title: Read P99 per Instance + type: timeseries + description: Read P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{operation="read"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-{{scheme}}' + - title: Write QPS per Instance + type: timeseries + description: Write QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{ operation="write"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-{{scheme}}' + - title: Write P99 per Instance + type: timeseries + description: Write P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{ operation="write"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]' + - title: List QPS per Instance + type: timeseries + description: List QPS per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, scheme) (rate(opendal_operation_duration_seconds_count{ operation="list"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]' + - title: List P99 per Instance + type: timeseries + description: List P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{ operation="list"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]' + - title: Other Requests per Instance + type: timeseries + description: Other Requests per Instance. + unit: ops + queries: + - expr: sum by(instance, pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{operation!~"read|write|list|stat"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]' + - title: Other Request P99 per Instance + type: timeseries + description: Other Request P99 per Instance. + unit: s + queries: + - expr: histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{ operation!~"read|write|list"}[$__rate_interval]))) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]' + - title: Opendal traffic + type: timeseries + description: Total traffic as in bytes by instance and operation + unit: decbytes + queries: + - expr: sum by(instance, pod, scheme, operation) (rate(opendal_operation_bytes_sum{}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]' + - title: OpenDAL errors per Instance + type: timeseries + description: OpenDAL error counts per Instance. + queries: + - expr: sum by(instance, pod, scheme, operation, error) (rate(opendal_operation_errors_total{ error!="NotFound"}[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]' + - title: Metasrv + panels: + - title: Region migration datanode + type: state-timeline + description: Counter of region migration by source and destination + unit: none + queries: + - expr: greptime_meta_region_migration_stat{datanode_type="src"} + datasource: + type: prometheus + uid: ${metrics} + legendFormat: from-datanode-{{datanode_id}} + - expr: greptime_meta_region_migration_stat{datanode_type="desc"} + datasource: + type: prometheus + uid: ${metrics} + legendFormat: to-datanode-{{datanode_id}} + - title: Region migration error + type: timeseries + description: Counter of region migration error + unit: none + queries: + - expr: greptime_meta_region_migration_error + datasource: + type: prometheus + uid: ${metrics} + legendFormat: __auto + - title: Datanode load + type: timeseries + description: Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. + unit: none + queries: + - expr: greptime_datanode_load + datasource: + type: prometheus + uid: ${metrics} + legendFormat: __auto + - title: Flownode + panels: + - title: Flow Ingest / Output Rate + type: timeseries + description: Flow Ingest / Output Rate. + queries: + - expr: sum by(instance, pod, direction) (rate(greptime_flow_processed_rows[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{pod}}]-[{{instance}}]-[{{direction}}]' + - title: Flow Ingest Latency + type: timeseries + description: Flow Ingest Latency. + queries: + - expr: histogram_quantile(0.95, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod)) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-p95' + - expr: histogram_quantile(0.99, sum(rate(greptime_flow_insert_elapsed_bucket[$__rate_interval])) by (le, instance, pod)) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-p99' + - title: Flow Operation Latency + type: timeseries + description: Flow Operation Latency. + queries: + - expr: histogram_quantile(0.95, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type)) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{type}}]-p95' + - expr: histogram_quantile(0.99, sum(rate(greptime_flow_processing_time_bucket[$__rate_interval])) by (le,instance,pod,type)) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{type}}]-p99' + - title: Flow Buffer Size per Instance + type: timeseries + description: Flow Buffer Size per Instance. + queries: + - expr: greptime_flow_input_buf_size + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}]' + - title: Flow Processing Error per Instance + type: timeseries + description: Flow Processing Error per Instance. + queries: + - expr: sum by(instance,pod,code) (rate(greptime_flow_errors[$__rate_interval])) + datasource: + type: prometheus + uid: ${metrics} + legendFormat: '[{{instance}}]-[{{pod}}]-[{{code}}]' diff --git a/grafana/greptimedb-cluster.json b/grafana/greptimedb-cluster.json deleted file mode 100644 index 512139db07..0000000000 --- a/grafana/greptimedb-cluster.json +++ /dev/null @@ -1,7518 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_MYSQL", - "label": "mysql", - "description": "", - "type": "datasource", - "pluginId": "mysql", - "pluginName": "MySQL" - }, - { - "name": "DS_PROMETHEUS", - "label": "prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "10.2.3" - }, - { - "type": "datasource", - "id": "mysql", - "name": "MySQL", - "version": "1.0.0" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "state-timeline", - "name": "State timeline", - "version": "" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "description": "The Grafana dashboard of GreptimeDB cluster.", - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 1, - "id": null, - "links": [], - "liveNow": false, - "panels": [ - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 189, - "panels": [], - "title": "Overview", - "type": "row" - }, - { - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "description": "Greptime DB version.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 0, - "y": 1 - }, - "id": 239, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "/^pkg_version$/", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "dataset": "information_schema", - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "SELECT pkg_version FROM information_schema.build_info", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Version", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Total memory allocated by frontend node. Calculated from jemalloc metrics and may vary from system metrics.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 3, - "x": 2, - "y": 1 - }, - "id": 240, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(sys_jemalloc_resident{pod=~\"$frontend\"})", - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Frontend Memory", - "type": "stat" - }, - { - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "description": "Total number of active frontend nodes in the cluster.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 5, - "y": 1 - }, - "id": 241, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "dataset": "information_schema", - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "SELECT count(*) FROM information_schema.cluster_info WHERE peer_type = 'FRONTEND';", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Frontend Num", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Total memory allocated by datanodes. Calculated from jemalloc metrics and may vary from system metrics.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 3, - "x": 7, - "y": 1 - }, - "id": 242, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(sys_jemalloc_resident{pod=~\"$datanode\"})", - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Datanode Memory", - "type": "stat" - }, - { - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "description": "Total number of active datanodes in the cluster.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 10, - "y": 1 - }, - "id": 243, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "dataset": "information_schema", - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "SELECT count(*) FROM information_schema.cluster_info WHERE peer_type = 'DATANODE';", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Datanode Num", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Total memory allocated by metasrv. Calculated from jemalloc metrics and may vary from system metrics.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 3, - "x": 12, - "y": 1 - }, - "id": 244, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(sys_jemalloc_resident{pod=~\"$metasrv\"})", - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Metasrv Memory", - "type": "stat" - }, - { - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "description": "Total number of active metasrv instances", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 15, - "y": 1 - }, - "id": 245, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "dataset": "information_schema", - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "SELECT count(*) FROM information_schema.cluster_info WHERE peer_type = 'METASRV';", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Metasrv Num", - "type": "stat" - }, - { - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "description": "User-created database count.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 3, - "x": 0, - "y": 4 - }, - "id": 246, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "dataset": "information_schema", - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name NOT IN ('greptime_private', 'information_schema')", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Databases", - "type": "stat" - }, - { - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "description": "Total number of tables.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 3, - "x": 3, - "y": 4 - }, - "id": 247, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "dataset": "information_schema", - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema != 'information_schema'", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Tables", - "type": "stat" - }, - { - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "description": "Total number of data file size.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 6, - "y": 4 - }, - "id": 248, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "dataset": "information_schema", - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "select SUM(disk_size) from information_schema.region_statistics;", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Storage Size", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Total number of rows ingested into the cluster, per second.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "fieldMinMax": false, - "mappings": [], - "max": 2, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "rowsps" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 10, - "y": 4 - }, - "id": 249, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(rate(greptime_table_operator_ingest_rows[$__rate_interval]))", - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Ingest Rows Rate", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Total number of rows ingested via /events/logs endpoint, per second.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "fieldMinMax": false, - "mappings": [], - "max": 2, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "rowsps" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 14, - "y": 4 - }, - "id": 265, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "sum(rate(greptime_servers_http_logs_ingestion_counter[$__rate_interval]))", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Log Ingest Rows Rate", - "type": "stat" - }, - { - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "description": "The approximate size of write-ahead logs", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 0, - "y": 8 - }, - "id": 250, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "dataset": "information_schema", - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "select SUM(memtable_size) * 0.42825 from information_schema.region_statistics;", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "WAL Size", - "type": "stat" - }, - { - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "description": "Total size of index files.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 2, - "y": 8 - }, - "id": 251, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "dataset": "information_schema", - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "select SUM(index_size) from information_schema.region_statistics;", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Index Size", - "type": "stat" - }, - { - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "description": "Total size of manifest file size. Manifest is a our table format metadata stored on object storage. ", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 4, - "y": 8 - }, - "id": 252, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "dataset": "information_schema", - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "select SUM(manifest_size) from information_schema.region_statistics;", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Manifest Size", - "type": "stat" - }, - { - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "description": "Total number of partitions in the cluster.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 6, - "y": 8 - }, - "id": 253, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "dataset": "information_schema", - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "SELECT COUNT(region_id) FROM information_schema.region_peers", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Regions Count", - "type": "stat" - }, - { - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "description": "Total number of data rows in the cluster. Calculated by sum of rows from each region.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "sishort" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 8, - "y": 8 - }, - "id": 254, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "dataset": "information_schema", - "datasource": { - "type": "mysql", - "uid": "${DS_MYSQL}" - }, - "editorMode": "code", - "format": "table", - "rawQuery": true, - "rawSql": "select SUM(region_rows) from information_schema.region_statistics;", - "refId": "A", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - ], - "title": "Region Rows", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Total rate data ingestion API calls by protocol.\n\nHere we listed 3 primary protocols:\n\n- Prometheus remote write\n- Greptime's gRPC API (when using our ingest SDK)\n- Log ingestion http API\n", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 11 - }, - "id": 193, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum (rate(greptime_servers_http_prometheus_write_elapsed_count{pod=~\"$frontend\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "prometheus-remote-write", - "range": true, - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum (rate(greptime_servers_grpc_requests_elapsed_count{pod=~\"$frontend\"}[$__rate_interval]))", - "hide": false, - "instant": false, - "legendFormat": "gRPC", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum (rate(greptime_servers_http_logs_ingestion_elapsed_count{pod=~\"$frontend\"}[$__rate_interval]))", - "hide": false, - "instant": false, - "legendFormat": "http_logs", - "range": true, - "refId": "C" - } - ], - "title": "Ingestion", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Total rate of query API calls by protocol. This metric is collected from frontends.\n\nHere we listed 3 main protocols:\n- MySQL\n- Postgres\n- Prometheus API\n\nNote that there are some other minor query APIs like /sql are not included", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 17 - }, - "id": 255, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum (rate(greptime_servers_mysql_query_elapsed_count{pod=~\"$frontend\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "mysql", - "range": true, - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum (rate(greptime_servers_postgres_query_elapsed_count{pod=~\"$frontend\"}[$__rate_interval]))", - "hide": false, - "instant": false, - "legendFormat": "pg", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum (rate(greptime_servers_http_promql_elapsed_counte{pod=~\"$frontend\"}[$__rate_interval]))", - "hide": false, - "instant": false, - "legendFormat": "promql", - "range": true, - "refId": "C" - } - ], - "title": "Queries", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 23 - }, - "id": 237, - "panels": [], - "title": "Resources", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Memory usage information of datanodes.\n\nThere are three types of the metrics:\n\n- allocated from jemalloc\n- resident memory as stat from jemalloc\n- process virtual memory", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 0, - "y": 24 - }, - "id": 234, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "sortBy": "Last *", - "sortDesc": false - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(sys_jemalloc_allocated{pod=~\"$datanode\"})", - "instant": false, - "legendFormat": "allocated", - "range": true, - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(sys_jemalloc_resident{pod=~\"$datanode\"})", - "hide": false, - "instant": false, - "legendFormat": "resident", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(process_virtual_memory_bytes{pod=~\"$datanode\"})", - "hide": false, - "instant": false, - "legendFormat": "virtual-memory", - "range": true, - "refId": "C" - } - ], - "title": "Datanode Memory", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Memory usage information of frontend.\n\nThere are three types of the metrics:\n\n- allocated from jemalloc\n- resident memory as stat from jemalloc\n- process virtual memory", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 8, - "y": 24 - }, - "id": 233, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "sortBy": "Last *", - "sortDesc": false - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(sys_jemalloc_allocated{pod=~\"$frontend\"})", - "instant": false, - "legendFormat": "allocated", - "range": true, - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(sys_jemalloc_resident{pod=~\"$frontend\"})", - "hide": false, - "instant": false, - "legendFormat": "resident", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(process_virtual_memory_bytes{pod=~\"$frontend\"})", - "hide": false, - "instant": false, - "legendFormat": "virtual-memory", - "range": true, - "refId": "C" - } - ], - "title": "Frontend Memory", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Memory usage information of metasrv.\n\nThere are three types of the metrics:\n\n- allocated from jemalloc\n- resident memory as stat from jemalloc\n- process virtual memory", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 16, - "y": 24 - }, - "id": 235, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "sortBy": "Last *", - "sortDesc": false - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(sys_jemalloc_allocated{pod=~\"$metasrv\"})", - "instant": false, - "legendFormat": "allocated", - "range": true, - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(sys_jemalloc_resident{pod=~\"$metasrv\"})", - "hide": false, - "instant": false, - "legendFormat": "resident", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(process_virtual_memory_bytes{pod=~\"$metasrv\"})", - "hide": false, - "instant": false, - "legendFormat": "virtual-memory", - "range": true, - "refId": "C" - } - ], - "title": "Metasrv Memory", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Current memory usage by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 0, - "y": 34 - }, - "id": 256, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(sys_jemalloc_resident{pod=~\"$datanode\"}) by (pod)", - "instant": false, - "legendFormat": "[{{ pod }}]-resident", - "range": true, - "refId": "A" - } - ], - "title": "Datanode Memory per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Current memory usage by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 8, - "y": 34 - }, - "id": 257, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(sys_jemalloc_resident{pod=~\"$frontend\"}) by (pod)", - "instant": false, - "legendFormat": "[{{ pod }}]-resident", - "range": true, - "refId": "A" - } - ], - "title": "Frontend Memory per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Current memory usage by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 16, - "y": 34 - }, - "id": 258, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(sys_jemalloc_resident{pod=~\"$metasrv\"}) by (pod)", - "instant": false, - "legendFormat": "[{{ pod }}]-resident", - "range": true, - "refId": "A" - } - ], - "title": "Metasrv Memory per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Current cpu usage of all instances accumulated", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 0, - "y": 44 - }, - "id": 259, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "sortBy": "Last *", - "sortDesc": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(rate(process_cpu_seconds_total{pod=~\"$datanode\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "cpu", - "range": true, - "refId": "A" - } - ], - "title": "Datanode CPU Usage", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Current cpu usage of all instances accumulated", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 8, - "y": 44 - }, - "id": 260, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "sortBy": "Last *", - "sortDesc": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(rate(process_cpu_seconds_total{pod=~\"$frontend\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "cpu", - "range": true, - "refId": "A" - } - ], - "title": "Frontend CPU Usage", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Current cpu usage of all instances accumulated", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 16, - "y": 44 - }, - "id": 261, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "sortBy": "Last *", - "sortDesc": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(rate(process_cpu_seconds_total{pod=~\"$metasrv\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "cpu", - "range": true, - "refId": "A" - } - ], - "title": "Metasrv CPU Usage", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Current cpu usage by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 0, - "y": 54 - }, - "id": 262, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "sortBy": "Last *", - "sortDesc": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(rate(process_cpu_seconds_total{pod=~\"$datanode\"}[$__rate_interval])) by (pod)", - "instant": false, - "legendFormat": "[{{ pod }}]-cpu", - "range": true, - "refId": "A" - } - ], - "title": "Datanode CPU Usage per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Current cpu usage of all instances accumulated", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 8, - "y": 54 - }, - "id": 263, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "sortBy": "Last *", - "sortDesc": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(rate(process_cpu_seconds_total{pod=~\"$frontend\"}[$__rate_interval])) by (pod)", - "instant": false, - "legendFormat": "[{{ pod }}]-cpu", - "range": true, - "refId": "A" - } - ], - "title": "Frontend CPU Usage per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Current cpu usage of all instances accumulated", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 10, - "w": 8, - "x": 16, - "y": 54 - }, - "id": 264, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "sortBy": "Last *", - "sortDesc": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum(rate(process_cpu_seconds_total{pod=~\"$metasrv\"}[$__rate_interval])) by (pod)", - "instant": false, - "legendFormat": "[{{ pod }}]-cpu", - "range": true, - "refId": "A" - } - ], - "title": "Metasrv CPU Usage per Instance", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 64 - }, - "id": 192, - "panels": [], - "title": "Frontend APIs", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "HTTP APIs QPS by instance, request url, http method and response status code", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 65 - }, - "id": 202, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum by(pod, path, method, code) (rate(greptime_servers_http_requests_elapsed_count{pod=~\"$frontend\",path!~\"/health|/metrics\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-qps", - "range": true, - "refId": "A" - } - ], - "title": "HTTP QPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "P99 latency of HTTP requests by instance, request url, http method and response code", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 65 - }, - "id": 203, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(pod, le, path, method, code) (rate(greptime_servers_http_requests_elapsed_bucket{pod=~\"$frontend\",path!~\"/health|/metrics\"}[$__rate_interval])))", - "instant": false, - "legendFormat": "[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99", - "range": true, - "refId": "A" - } - ], - "title": "HTTP P99 per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "gRPC requests QPS on frontends by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "[mycluster-frontend-5f94445cf8-mcmhf]-[/v1/prometheus/write]-[POST]-[204]-qps" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": true - } - } - ] - } - ] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 73 - }, - "id": 211, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum by(pod, path, code) (rate(greptime_servers_grpc_requests_elapsed_count{pod=~\"$frontend\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "[{{pod}}]-[{{path}}]-[{{code}}]-qps", - "range": true, - "refId": "A" - } - ], - "title": "gRPC QPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "gRPC latency p99 by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 73 - }, - "id": 212, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(pod, le, path, code) (rate(greptime_servers_grpc_requests_elapsed_bucket{pod=~\"$frontend\"}[$__rate_interval])))", - "instant": false, - "legendFormat": "[{{pod}}]-[{{path}}]-[{{method}}]-[{{code}}]-p99", - "range": true, - "refId": "A" - } - ], - "title": "gRPC P99 per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "MySQL query rate by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "[mycluster-frontend-5c59b4cc9b-kpb6q]-qps" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": true - } - } - ] - } - ] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 81 - }, - "id": 213, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum by(pod)(rate(greptime_servers_mysql_query_elapsed_count{pod=~\"$frontend\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "[{{pod}}]-qps", - "range": true, - "refId": "A" - } - ], - "title": "MySQL QPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "MySQL query latency p99 by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 81 - }, - "id": 214, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "exemplar": false, - "expr": "histogram_quantile(0.99, sum by(pod, le) (rate(greptime_servers_mysql_query_elapsed_bucket{pod=~\"$frontend\"}[$__rate_interval])))", - "instant": false, - "legendFormat": "[{{ pod }}]-p99", - "range": true, - "refId": "A" - } - ], - "title": "MySQL P99 per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Postgres query rate by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "[mycluster-frontend-5f94445cf8-mcmhf]-[/v1/prometheus/write]-[POST]-[204]-qps" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": true - } - } - ] - } - ] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 89 - }, - "id": 215, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum by(pod) (rate(greptime_servers_postgres_query_elapsed_count{pod=~\"$frontend\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "[{{pod}}]-qps", - "range": true, - "refId": "A" - } - ], - "title": "PostgreSQL QPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Postgres query latency p99 by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 89 - }, - "id": 216, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(pod, le) (rate(greptime_servers_postgres_query_elapsed_count{pod=~\"$frontend\"}[$__rate_interval])))", - "instant": false, - "legendFormat": "[{{pod}}]-p99", - "range": true, - "refId": "A" - } - ], - "title": "PostgreSQL P99 per Instance", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 97 - }, - "id": 217, - "panels": [], - "title": "Frontend <-> Datanode", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Ingestion rate by row as in each frontend", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "rowsps" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 98 - }, - "id": 218, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum by(pod)(rate(greptime_table_operator_ingest_rows{pod=~\"$frontend\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "[{{pod}}]-rps", - "range": true, - "refId": "A" - } - ], - "title": "Ingest Rows per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Datanode query rate issued by each frontend", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 104 - }, - "id": 219, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum by(pod, request_type) (rate(greptime_grpc_region_request_count{pod=~\"$frontend\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "[{{pod}}]-[{{request_type}}]-qps", - "range": true, - "refId": "A" - } - ], - "title": "Region Call QPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Datanode query latency at p99 as seen by each frontend", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 104 - }, - "id": 220, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(pod, le, request_type) (rate(greptime_grpc_region_request_bucket{pod=~\"$frontend\"}[$__rate_interval])))", - "instant": false, - "legendFormat": "[{{pod}}]-[{{request_type}}]-p99", - "range": true, - "refId": "A" - } - ], - "title": "Region Call P99 per Instance", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 112 - }, - "id": 273, - "panels": [], - "title": "Metasrv", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Counter of region migration by source and destination", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "fillOpacity": 70, - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineWidth": 0, - "spanNulls": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 113 - }, - "id": 274, - "options": { - "alignValue": "left", - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "mergeValues": true, - "rowHeight": 0.9, - "showValue": "auto", - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "greptime_meta_region_migration_stat{datanode_type=\"src\"}", - "instant": false, - "legendFormat": "from-datanode-{{datanode_id}}", - "range": true, - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "greptime_meta_region_migration_stat{datanode_type=\"desc\"}", - "hide": false, - "instant": false, - "legendFormat": "to-datanode-{{datanode_id}}", - "range": true, - "refId": "B" - } - ], - "title": "Region migration datanode", - "type": "state-timeline" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Counter of region migration error", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 113 - }, - "id": 275, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "greptime_meta_region_migration_error", - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Region migration error", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 121 - }, - "id": 276, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "greptime_datanode_load", - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Datanode load", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 129 - }, - "id": 194, - "panels": [], - "title": "Mito Engine", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Datanode storage engine QPS by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 130 - }, - "id": 201, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum by(pod, type) (rate(greptime_mito_handle_request_elapsed_count{pod=~\"$datanode\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "[{{pod}}]-[{{type}}]-qps", - "range": true, - "refId": "A" - } - ], - "title": "Request QPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Storage query latency at p99 by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 130 - }, - "id": 222, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(pod, le, type) (rate(greptime_mito_handle_request_elapsed_bucket{pod=~\"$datanode\"}[$__rate_interval])))", - "instant": false, - "legendFormat": "[{{pod}}]-[{{type}}]-p99", - "range": true, - "refId": "A" - } - ], - "title": "Request P99 per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Memtable size on each instance.\n\nThe memtable holds unflushed data in memory and will flush it to object storage periodically or when size exceed configured limit.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 138 - }, - "id": 200, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "greptime_mito_write_buffer_bytes{pod=~\"$datanode\"}", - "instant": false, - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "title": "Write Buffer per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Ingestion size by row counts.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 138 - }, - "id": 277, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rate(greptime_mito_write_rows_total{pod=~\"$datanode\"}[$__rate_interval])", - "instant": false, - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "title": "Write Rows per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Memtable flush rate by reason and instance.\n\nThere are several reasons when memtable get flushed. For example, it's full as in size, or reaching the time-to-flush, or by an artificial request.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 146 - }, - "id": 224, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum by(pod, reason) (rate(greptime_mito_flush_requests_total{pod=~\"$datanode\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "[{{pod}}]-[{{reason}}]-success", - "range": true, - "refId": "A" - } - ], - "title": "Flush QPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Current counts for stalled write requests by instance\n\nWrite stalls when memtable is full and pending for flush\n\n", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 146 - }, - "id": 221, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum by(pod) (greptime_mito_write_stall_total{pod=~\"$datanode\"})", - "instant": false, - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "title": "Write Stall per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Read QPS from the storage engine by instance.\n\n", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 154 - }, - "id": 227, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum by(pod) (rate(greptime_mito_read_stage_elapsed_count{pod=~\"$datanode\", stage=\"total\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "{{pod}}-p99", - "range": true, - "refId": "A" - } - ], - "title": "Read Stage QPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Cache size by instance.\n", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 154 - }, - "id": 229, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "greptime_mito_cache_bytes{pod=~\"$datanode\"}", - "instant": false, - "legendFormat": "{{pod}}-{{type}}", - "range": true, - "refId": "A" - } - ], - "title": "Cached Bytes per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Compaction operation rate.\n\nCompaction happens when storage to merge and optimise data files.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 162 - }, - "id": 231, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum by(pod) (rate(greptime_mito_compaction_total_elapsed_count{pod=~\"$datanode\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "title": "Compaction OPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "P99 latency of each type of reads by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 162 - }, - "id": 228, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "sortBy": "Last *", - "sortDesc": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(pod, le, stage) (rate(greptime_mito_read_stage_elapsed_bucket{pod=~\"$datanode\"}[$__rate_interval])))", - "instant": false, - "legendFormat": "{{pod}}-{{stage}}-p99", - "range": true, - "refId": "A" - } - ], - "title": "Read Stage P99 per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Write latency by instance and stage type", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 170 - }, - "id": 225, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true, - "sortBy": "Last *", - "sortDesc": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(pod, le, stage) (rate(greptime_mito_write_stage_elapsed_bucket{pod=~\"$datanode\"}[$__rate_interval])))", - "instant": false, - "legendFormat": "{{pod}}-{{stage}}-p99", - "range": true, - "refId": "A" - } - ], - "title": "Write Stage P99 per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Latency of compaction task, at p99", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 170 - }, - "id": 230, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(pod, le) (rate(greptime_mito_compaction_total_elapsed_bucket{pod=~\"$datanode\"}[$__rate_interval])))", - "instant": false, - "legendFormat": "[{{pod}}]-compaction-p99", - "range": true, - "refId": "A" - } - ], - "title": "Compaction P99 per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Write-ahead logs write size as bytes. This chart includes stats of p95 and p99 size by instance, total WAL write rate.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 178 - }, - "id": 268, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le,pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))", - "instant": false, - "legendFormat": "{{pod}}-req-size-p95", - "range": true, - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le,pod) (rate(raft_engine_write_size_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "{{pod}}-req-size-p99", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rate(raft_engine_write_size_sum[$__rate_interval])", - "hide": false, - "instant": false, - "legendFormat": "{{pod}}-throughput", - "range": true, - "refId": "C" - } - ], - "title": "WAL write size", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Compaction latency by stage", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 178 - }, - "id": 232, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(pod, le, stage) (rate(greptime_mito_compaction_stage_elapsed_bucket{pod=~\"$datanode\"}[$__rate_interval])))", - "instant": false, - "legendFormat": "{{pod}}-{{stage}}-p99", - "range": true, - "refId": "A" - } - ], - "title": "Compaction P99 per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Raft engine (local disk) log store sync latency, p99", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 186 - }, - "id": 270, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, type, node, pod) (rate(raft_engine_sync_log_duration_seconds_bucket[$__rate_interval])))", - "instant": false, - "legendFormat": "{{pod}}-p99", - "range": true, - "refId": "A" - } - ], - "title": "WAL sync duration seconds", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Write-ahead log operations latency at p99", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 186 - }, - "id": 269, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le,logstore,optype,pod) (rate(greptime_logstore_op_elapsed_bucket[$__rate_interval])))", - "instant": false, - "legendFormat": "{{pod}}-{{logstore}}-{{optype}}-p99", - "range": true, - "refId": "A" - } - ], - "title": "Log Store op duration seconds", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Ongoing flush task count", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 194 - }, - "id": 272, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "greptime_mito_inflight_flush_count", - "instant": false, - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "title": "Inflight Flush", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Ongoing compaction task count", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 194 - }, - "id": 271, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.1.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "greptime_mito_inflight_compaction_count", - "instant": false, - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "title": "Inflight Compaction", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 202 - }, - "id": 195, - "panels": [], - "title": "OpenDAL", - "type": "row" - }, - { - "datasource": { - "default": false, - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "object storage query rate by datanode and operation type", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 203 - }, - "id": 209, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "sum by(pod, scheme, operation) (rate(opendal_operation_bytes_count{pod=~\"$datanode\"}[$__rate_interval]))", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "[{{pod}}]-[{{scheme}}]-[{{operation}}]-qps", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "QPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "default": false, - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Total traffic as in bytes by instance and operation", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "[mycluster-datanode-0]-[fs]-[Writer::write]" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": true - } - } - ] - } - ] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 203 - }, - "id": 267, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "sum by(pod, scheme, operation) (rate(opendal_operation_bytes_sum{pod=~\"$datanode\"}[$__rate_interval]))", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "[{{pod}}]-[{{scheme}}]-[{{operation}}]", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Opendal traffic", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Object storage read traffic rate as in bytes per second by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 210 - }, - "id": 196, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "sum by(pod, scheme) (rate(opendal_operation_bytes_count{pod=~\"$datanode\", operation=\"Reader::read\"}[$__rate_interval]))", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "[{{pod}}]-[{{scheme}}]-qps", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Read QPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Read operation latency at p99", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 210 - }, - "id": 198, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "histogram_quantile(0.99, sum by(pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{pod=~\"$datanode\", operation=\"Reader::read\"}[$__rate_interval])))", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "[{{pod}}]-{{scheme}}-p99", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Read P99 per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Object storage write traffic rate as in bytes per second by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 217 - }, - "id": 199, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "sum by(pod, scheme) (rate(opendal_operation_duration_seconds_count{pod=~\"$datanode\", operation=\"Writer::write\"}[$__rate_interval]))", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "[{{pod}}]-[{{scheme}}]-qps", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Write QPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Write operation latency at p99", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 217 - }, - "id": 204, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{pod=~\"$datanode\", operation=\"Writer::write\"}[$__rate_interval])))", - "instant": false, - "legendFormat": "[{{pod}}]-[{{scheme}}]-p99", - "range": true, - "refId": "A" - } - ], - "title": "Write P99 per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Object storage list traffic rate as in bytes per second by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 224 - }, - "id": 205, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "sum by(pod, scheme) (rate(opendal_operation_duration_seconds_count{pod=~\"$datanode\", operation=\"list\"}[$__rate_interval]))", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "interval": "", - "legendFormat": "[{{pod}}]-[{{scheme}}]-qps", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "List QPS per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "List operation latency at p99", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 224 - }, - "id": 206, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(pod, le, scheme) (rate(opendal_operation_duration_seconds_bucket{pod=~\"$datanode\", operation=\"list\"}[$__rate_interval])))", - "instant": false, - "interval": "", - "legendFormat": "[{{pod}}]-[{{scheme}}]-p99", - "range": true, - "refId": "A" - } - ], - "title": "List P99 per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Object storage traffic rate other than read/write/list/stat as in bytes per second by instance", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 231 - }, - "id": 207, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "sum by(pod, scheme, operation) (rate(opendal_operation_duration_seconds_count{pod=~\"$datanode\",operation!~\"read|write|list|stat\"}[$__rate_interval]))", - "instant": false, - "legendFormat": "[{{pod}}]-[{{scheme}}]-[{{operation}}]-qps", - "range": true, - "refId": "A" - } - ], - "title": "Other Requests per Instance", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "All other operation latency at p99", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 3, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 231 - }, - "id": 210, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{pod=~\"$datanode\", operation!~\"read|write|list\"}[$__rate_interval])))", - "instant": false, - "legendFormat": "[{{pod}}]-[{{scheme}}]-[{{operation}}]-p99", - "range": true, - "refId": "A" - } - ], - "title": "Other Request P99 per Instance", - "type": "timeseries" - } - ], - "refresh": "10s", - "schemaVersion": 39, - "tags": [], - "templating": { - "list": [ - { - "current": { - "selected": false, - "text": "No data sources found", - "value": "" - }, - "hide": 0, - "includeAll": false, - "multi": false, - "name": "metrics", - "options": [], - "query": "prometheus", - "queryValue": "", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "type": "datasource" - }, - { - "current": { - "selected": false, - "text": "No data sources found", - "value": "" - }, - "hide": 0, - "includeAll": false, - "multi": false, - "name": "information_schema", - "options": [], - "query": "mysql", - "queryValue": "", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "type": "datasource" - }, - { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "definition": "label_values(greptime_app_version{app=~\"greptime-datanode|greptime-frontend|greptime-metasrv|greptime-flownode\"},app)", - "hide": 0, - "includeAll": true, - "multi": true, - "name": "roles", - "options": [], - "query": { - "qryType": 1, - "query": "label_values(greptime_app_version{app=~\"greptime-datanode|greptime-frontend|greptime-metasrv|greptime-flownode\"},app)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "definition": "label_values(greptime_app_version{app=~\"$roles\"},pod)", - "hide": 0, - "includeAll": true, - "multi": true, - "name": "pods", - "options": [], - "query": { - "qryType": 1, - "query": "label_values(greptime_app_version{app=~\"$roles\"},pod)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "definition": "label_values(greptime_app_version{app=\"greptime-datanode\"},pod)", - "hide": 2, - "includeAll": true, - "multi": true, - "name": "datanode", - "options": [], - "query": { - "qryType": 1, - "query": "label_values(greptime_app_version{app=\"greptime-datanode\"},pod)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "definition": "label_values(greptime_app_version{app=\"greptime-frontend\"},pod)", - "hide": 2, - "includeAll": true, - "multi": true, - "name": "frontend", - "options": [], - "query": { - "qryType": 1, - "query": "label_values(greptime_app_version{app=\"greptime-frontend\"},pod)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "definition": "label_values(greptime_app_version{app=\"greptime-metasrv\"},pod)", - "hide": 2, - "includeAll": true, - "multi": true, - "name": "metasrv", - "options": [], - "query": { - "qryType": 1, - "query": "label_values(greptime_app_version{app=\"greptime-metasrv\"},pod)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "definition": "label_values(greptime_app_version{app=\"greptime-flownode\"},pod)", - "hide": 2, - "includeAll": true, - "multi": true, - "name": "flownode", - "options": [], - "query": { - "qryType": 1, - "query": "label_values(greptime_app_version{app=\"greptime-flownode\"},pod)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - } - ] - }, - "time": { - "from": "now-1h", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "GreptimeDB Cluster Metrics", - "uid": "ce3q6xwn3xa0qs", - "version": 10, - "weekStart": "" -} diff --git a/grafana/greptimedb.json b/grafana/greptimedb.json deleted file mode 100644 index a5913ee8e8..0000000000 --- a/grafana/greptimedb.json +++ /dev/null @@ -1,4159 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_PROMETHEUS", - "label": "prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "10.2.3" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "state-timeline", - "name": "State timeline", - "version": "" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 1, - "id": null, - "links": [], - "liveNow": false, - "panels": [ - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 23, - "panels": [], - "title": "Resource", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "center", - "cellOptions": { - "type": "auto" - }, - "filterable": false, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "short_version" - }, - "properties": [ - { - "id": "custom.width", - "value": 147 - } - ] - } - ] - }, - "gridPos": { - "h": 3, - "w": 8, - "x": 0, - "y": 1 - }, - "id": 29, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "enablePagination": false, - "fields": [], - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "10.2.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "exemplar": false, - "expr": "greptime_app_version", - "format": "table", - "instant": true, - "interval": "", - "legendFormat": "{{short_version}}", - "range": false, - "refId": "A" - } - ], - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "Value": true, - "__name__": true, - "instance": true, - "job": true - }, - "includeByName": {}, - "indexByName": {}, - "renameByName": {} - } - } - ], - "transparent": true, - "type": "table" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "transparent", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 8, - "y": 1 - }, - "id": 30, - "options": { - "colorMode": "background", - "graphMode": "none", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.2.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "greptime_catalog_schema_count", - "instant": false, - "legendFormat": "database", - "range": true, - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "greptime_table_operator_create_table_count", - "hide": false, - "instant": false, - "legendFormat": "table", - "range": true, - "refId": "B" - } - ], - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "fieldMinMax": false, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 12, - "y": 1 - }, - "id": 31, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.2.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "process_threads", - "instant": false, - "legendFormat": "threads", - "range": true, - "refId": "A" - } - ], - "title": "Threads", - "transformations": [], - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "fillOpacity": 70, - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineWidth": 0, - "spanNulls": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 18, - "y": 1 - }, - "id": 32, - "options": { - "alignValue": "center", - "legend": { - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "mergeValues": true, - "rowHeight": 0.9, - "showValue": "auto", - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "exemplar": false, - "expr": "up{}", - "instant": false, - "legendFormat": "_", - "range": true, - "refId": "A" - } - ], - "title": "Up", - "transparent": true, - "type": "state-timeline" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "fieldMinMax": false, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 700 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 12, - "x": 0, - "y": 4 - }, - "id": 27, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "pluginVersion": "10.2.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "irate(process_cpu_seconds_total[1m])", - "instant": false, - "legendFormat": "{{instance}}", - "range": true, - "refId": "A" - } - ], - "title": "CPU", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 0, - "fieldMinMax": false, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 12, - "x": 12, - "y": 4 - }, - "id": 28, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "pluginVersion": "10.2.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "process_resident_memory_bytes", - "instant": false, - "legendFormat": "{{instance}}", - "range": true, - "refId": "A" - } - ], - "title": "Memory", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 10 - }, - "id": 24, - "panels": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 11 - }, - "id": 34, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, db) (rate(greptime_servers_http_promql_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "promql-{{db}}-p95", - "range": true, - "refId": "PromQL P95", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, db) (rate(greptime_servers_http_promql_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "promql-{{db}}-p99", - "range": true, - "refId": "PromQL P99", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, db) (rate(greptime_servers_http_sql_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "sql-{{db}}-p95", - "range": true, - "refId": "SQL P95" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, db) (rate(greptime_servers_http_sql_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "sql-{{db}}-p99", - "range": true, - "refId": "SQL P99" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, db) (rate(greptime_servers_http_prometheus_read_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "promstore-read-{{db}}-p95", - "range": true, - "refId": "PromStore Read P95" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, db) (rate(greptime_servers_http_prometheus_read_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "promstore-read-{{db}}-p99", - "range": true, - "refId": "PromStore Read P99" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, db, method) (rate(greptime_servers_http_prometheus_promql_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "prom-promql-{{db}}-{{method}}-p95", - "range": true, - "refId": "Prometheus PromQL P95" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, db, method) (rate(greptime_servers_http_prometheus_promql_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "prom-promql-{{db}}-{{method}}-p99", - "range": true, - "refId": "Prometheus PromQL P99" - } - ], - "title": "HTTP query elapsed", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 11 - }, - "id": 35, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, db) (rate(greptime_servers_http_influxdb_write_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "influx-{{db}}-p95", - "range": true, - "refId": "InfluxDB Line Protocol P95", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, db) (rate(greptime_servers_http_influxdb_write_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "influx-{{db}}-p99", - "range": true, - "refId": "InfluxDB Line Protocol P99", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, db) (rate(greptime_servers_http_prometheus_write_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "promstore-{{db}}-p95", - "range": true, - "refId": "PromStore Write P95" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, db) (rate(greptime_servers_http_prometheus_write_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "promstore-{{db}}-p99", - "range": true, - "refId": "PromStore Write P99" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, db) (rate(greptime_servers_http_otlp_metrics_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "otlp-metric-{{db}}-p95", - "range": true, - "refId": "OTLP Metric P95" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, db) (rate(greptime_servers_http_otlp_metrics_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "otlp-metric-{{db}}-p99", - "range": true, - "refId": "OTLP Metric P99" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, db) (rate(greptime_servers_http_otlp_traces_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "otlp-trace-{{db}}-p95", - "range": true, - "refId": "OTLP Trace P95" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, db) (rate(greptime_servers_http_otlp_traces_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "otlp-trace-{{db}}-p99", - "range": true, - "refId": "OTLP Trace P99" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, db) (rate(greptime_servers_http_logs_transform_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "log-transform-{{db}}-p95", - "range": true, - "refId": "Log Transform P95" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, db) (rate(greptime_servers_http_logs_transform_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "log-transform-{{db}}-p99", - "range": true, - "refId": "Log Transform P99" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, db) (rate(greptime_servers_http_logs_ingestion_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "log-ingest-{{db}}-p99", - "range": true, - "refId": "Log Ingest P95" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, db) (rate(greptime_servers_http_logs_ingestion_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "log-ingest-{{db}}-p99", - "range": true, - "refId": "Log Ingest P99" - } - ], - "title": "HTTP write elapsed", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 18 - }, - "id": 38, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "sum by(path) (rate(greptime_servers_http_requests_total[$__rate_interval]))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "HTTP request rate", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 18 - }, - "id": 36, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "sum by(db) (rate(greptime_servers_http_logs_ingestion_counter[$__rate_interval]))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{db}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Logs ingest rate (number of lines)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 25 - }, - "id": 13, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, db) (rate(greptime_servers_grpc_requests_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{db}}-p95", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, db) (rate(greptime_servers_grpc_requests_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{db}}-p99", - "range": true, - "refId": "B", - "useBackend": false - } - ], - "title": "gRPC insert elapsed", - "type": "timeseries" - } - ], - "title": "Protocol", - "type": "row" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 11 - }, - "id": 25, - "panels": [], - "title": "Mito Engine", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 12 - }, - "id": 1, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, type) (rate(greptime_mito_handle_request_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{type}}-p95", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, type) (rate(greptime_mito_handle_request_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{type}}-p99", - "range": true, - "refId": "B", - "useBackend": false - } - ], - "title": "Handle request elapsed", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 12 - }, - "id": 7, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "rate(greptime_mito_write_rows_total[$__rate_interval])", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{type}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Write rows total", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 19 - }, - "id": 3, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, stage) (rate(greptime_mito_read_stage_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Mito engine read stage duration", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 19 - }, - "id": 11, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, stage) (rate(greptime_mito_write_stage_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{stage}}-p95", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, stage) (rate(greptime_mito_write_stage_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{stage}}-p99", - "range": true, - "refId": "B", - "useBackend": false - } - ], - "title": "Write stage duration", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 26 - }, - "id": 15, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "idelta(greptime_mito_compaction_stage_elapsed_count{stage=\"merge\"}[5m])", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "compaction-{{stage}}", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, type) (rate(greptime_mito_flush_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "flush-{{type}}", - "range": true, - "refId": "B" - } - ], - "title": "Flush / compaction duration", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 26 - }, - "id": 39, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "greptime_mito_inflight_compaction_count", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "compaction-{{instance}}", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "greptime_mito_inflight_flush_count", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "flush-{{instance}}", - "range": true, - "refId": "B", - "useBackend": false - } - ], - "title": "Flush / compaction count", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 33 - }, - "id": 9, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "greptime_mito_write_buffer_bytes", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{instance}}", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "greptime_mito_memtable_dict_bytes", - "hide": false, - "instant": false, - "legendFormat": "{{instance}}", - "range": true, - "refId": "B" - } - ], - "title": "Write buffer size", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 33 - }, - "id": 40, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "rate(greptime_mito_write_stall_total[$__rate_interval])", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{instance}}-worker-{{worker}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Write stall count", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 40 - }, - "id": 41, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "greptime_mito_cache_bytes", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{instance}}-{{type}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Cache size", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 40 - }, - "id": 42, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "sum(increase(greptime_mito_cache_hit[$__rate_interval])) by (instance, type) / (sum(increase(greptime_mito_cache_miss[$__rate_interval])) by (instance, type) + sum(increase(greptime_mito_cache_hit[$__rate_interval])) by (instance, type))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{instance}}-{{type}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Cache hit", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 47 - }, - "id": 26, - "panels": [], - "title": "Metric Engine", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 48 - }, - "id": 22, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, operation) (rate(greptime_metric_engine_mito_op_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "p95-{{operation}}", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, operation) (rate(greptime_metric_engine_mito_op_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "p99-{{operation}}", - "range": true, - "refId": "B" - } - ], - "title": "Metric engine to mito R/W duration", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 48 - }, - "id": 33, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, operation) (rate(greptime_metric_engine_mito_ddl_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "p95-{{operation}}", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, operation) (rate(greptime_metric_engine_mito_ddl_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "p99-{{label_name}}", - "range": true, - "refId": "B" - } - ], - "title": "Metric engine to mito DDL duration", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 55 - }, - "id": 21, - "panels": [], - "title": "Storage Components", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 56 - }, - "id": 18, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "rate(opendal_operation_bytes_sum[$__rate_interval])", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{scheme}}-{{operation}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "OpenDAL traffic", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 56 - }, - "id": 2, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, operation, schema) (rate(opendal_operation_duration_seconds_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "OpenDAL operation duration", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 63 - }, - "id": 43, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "greptime_object_store_lru_cache_bytes", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{instance}}-{{type}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Object store read cache size", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 63 - }, - "id": 44, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "sum(increase(greptime_object_store_lru_cache_hit[$__rate_interval])) by (instance) / (sum(increase(greptime_object_store_lru_cache_miss[$__rate_interval])) by (instance) + sum(increase(greptime_object_store_lru_cache_hit[$__rate_interval])) by (instance))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{instance}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Object store read cache hit", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 70 - }, - "id": 10, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le,logstore,optype) (rate(greptime_logstore_op_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "{{logstore}}-{{optype}}-p95", - "range": true, - "refId": "Log Store P95" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le,logstore,optype) (rate(greptime_logstore_op_elapsed_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "{{logstore}}-{{optype}}-p99", - "range": true, - "refId": "Log Store P99" - } - ], - "title": "Log Store op duration seconds", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 70 - }, - "id": 12, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le) (rate(raft_engine_write_size_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "req-size-p95", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le) (rate(raft_engine_write_size_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "req-size-p99", - "range": true, - "refId": "C", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "rate(raft_engine_write_size_sum[$__rate_interval])", - "hide": false, - "instant": false, - "legendFormat": "throughput", - "range": true, - "refId": "B" - } - ], - "title": "WAL write size", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 77 - }, - "id": 37, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, type, node) (rate(raft_engine_sync_log_duration_seconds_bucket[$__rate_interval])))", - "hide": false, - "instant": false, - "legendFormat": "{{node}}-{{type}}-p99", - "range": true, - "refId": "Log Store P95" - } - ], - "title": "WAL sync duration seconds", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 84 - }, - "id": 46, - "panels": [], - "title": "Index", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 85 - }, - "id": 45, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "greptime_index_create_memory_usage", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{instance}}-{{type}}", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "code", - "expr": "greptime_index_apply_memory_usage", - "hide": false, - "instant": false, - "legendFormat": "{{instance}}", - "range": true, - "refId": "B" - } - ], - "title": "Index memory usage", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 85 - }, - "id": 19, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, type) (rate(greptime_index_apply_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "apply-{{type}}-p95", - "range": true, - "refId": "Apply P95", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, type) (rate(greptime_index_apply_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "apply-{{type}}-p95", - "range": true, - "refId": "Apply P99", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by(le, type) (rate(greptime_index_create_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "create-{{type}}-p95", - "range": true, - "refId": "Create P95", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum by(le, type) (rate(greptime_index_create_elapsed_bucket[$__rate_interval])))", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "create-{{type}}-p95", - "range": true, - "refId": "Create P99", - "useBackend": false - } - ], - "title": "Index elapsed", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 92 - }, - "id": 47, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "rate(greptime_index_create_rows_total[$__rate_interval])", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{type}}", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Index create rows total", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 92 - }, - "id": 48, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "sum by(instance, type) (rate(greptime_index_create_bytes_total[$__rate_interval]))", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{instance}}-{{type}}", - "range": true, - "refId": "B", - "useBackend": false - } - ], - "title": "Index create bytes", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 99 - }, - "id": 49, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "sum by(instance, type, file_type) (rate(greptime_index_io_bytes_total[$__rate_interval]))", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{instance}}-{{type}}-{{file_type}}", - "range": true, - "refId": "B", - "useBackend": false - } - ], - "title": "Index IO bytes", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 99 - }, - "id": 50, - "interval": "1s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "sum by(instance, type, file_type) (rate(greptime_index_io_op_total[$__rate_interval]))", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "{{instance}}-{{type}}-{{file_type}}", - "range": true, - "refId": "B", - "useBackend": false - } - ], - "title": "Index IO op", - "type": "timeseries" - } - ], - "refresh": "10s", - "schemaVersion": 39, - "tags": [], - "templating": { - "list": [] - }, - "time": { - "from": "now-30m", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "GreptimeDB", - "uid": "e7097237-669b-4f8d-b751-13067afbfb68", - "version": 19, - "weekStart": "" -} diff --git a/grafana/scripts/check.sh b/grafana/scripts/check.sh new file mode 100755 index 0000000000..78d133e105 --- /dev/null +++ b/grafana/scripts/check.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +DASHBOARD_DIR=${1:-grafana/dashboards} + +check_dashboard_description() { + for dashboard in $(find $DASHBOARD_DIR -name "*.json"); do + echo "Checking $dashboard description" + + # Use jq to check for panels with empty or missing descriptions + invalid_panels=$(cat $dashboard | jq -r ' + .panels[] + | select((.type == "stats" or .type == "timeseries") and (.description == "" or .description == null))') + + # Check if any invalid panels were found + if [[ -n "$invalid_panels" ]]; then + echo "Error: The following panels have empty or missing descriptions:" + echo "$invalid_panels" + exit 1 + else + echo "All panels with type 'stats' or 'timeseries' have valid descriptions." + fi + done +} + +check_dashboards_generation() { + ./grafana/scripts/gen-dashboards.sh + + if [[ -n "$(git diff --name-only grafana/dashboards)" ]]; then + echo "Error: The dashboards are not generated correctly. You should execute the `make dashboards` command." + exit 1 + fi +} + +check_datasource() { + for dashboard in $(find $DASHBOARD_DIR -name "*.json"); do + echo "Checking $dashboard datasource" + jq -r '.panels[] | select(.type != "row") | .targets[] | [.datasource.type, .datasource.uid] | @tsv' $dashboard | while read -r type uid; do + # if the datasource is prometheus, check if the uid is ${metrics} + if [[ "$type" == "prometheus" && "$uid" != "\${metrics}" ]]; then + echo "Error: The datasource uid of $dashboard is not valid. It should be \${metrics}, got $uid" + exit 1 + fi + # if the datasource is mysql, check if the uid is ${information_schema} + if [[ "$type" == "mysql" && "$uid" != "\${information_schema}" ]]; then + echo "Error: The datasource uid of $dashboard is not valid. It should be \${information_schema}, got $uid" + exit 1 + fi + done + done +} + +check_dashboards_generation +check_dashboard_description +check_datasource diff --git a/grafana/scripts/gen-dashboards.sh b/grafana/scripts/gen-dashboards.sh new file mode 100755 index 0000000000..9488986bf9 --- /dev/null +++ b/grafana/scripts/gen-dashboards.sh @@ -0,0 +1,25 @@ +#! /usr/bin/env bash + +CLUSTER_DASHBOARD_DIR=${1:-grafana/dashboards/cluster} +STANDALONE_DASHBOARD_DIR=${2:-grafana/dashboards/standalone} +DAC_IMAGE=ghcr.io/zyy17/dac:20250423-522bd35 + +remove_instance_filters() { + # Remove the instance filters for the standalone dashboards. + sed 's/instance=~\\"$datanode\\",//; s/instance=~\\"$datanode\\"//; s/instance=~\\"$frontend\\",//; s/instance=~\\"$frontend\\"//; s/instance=~\\"$metasrv\\",//; s/instance=~\\"$metasrv\\"//; s/instance=~\\"$flownode\\",//; s/instance=~\\"$flownode\\"//;' $CLUSTER_DASHBOARD_DIR/dashboard.json > $STANDALONE_DASHBOARD_DIR/dashboard.json +} + +generate_intermediate_dashboards_and_docs() { + docker run -v ${PWD}:/greptimedb --rm ${DAC_IMAGE} \ + -i /greptimedb/$CLUSTER_DASHBOARD_DIR/dashboard.json \ + -o /greptimedb/$CLUSTER_DASHBOARD_DIR/dashboard.yaml \ + -m /greptimedb/$CLUSTER_DASHBOARD_DIR/dashboard.md + + docker run -v ${PWD}:/greptimedb --rm ${DAC_IMAGE} \ + -i /greptimedb/$STANDALONE_DASHBOARD_DIR/dashboard.json \ + -o /greptimedb/$STANDALONE_DASHBOARD_DIR/dashboard.yaml \ + -m /greptimedb/$STANDALONE_DASHBOARD_DIR/dashboard.md +} + +remove_instance_filters +generate_intermediate_dashboards_and_docs diff --git a/grafana/summary.sh b/grafana/summary.sh deleted file mode 100755 index 4e63fd3bd7..0000000000 --- a/grafana/summary.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -BASEDIR=$(dirname "$0") -echo '| Title | Description | Expressions | -|---|---|---|' - -cat $BASEDIR/greptimedb-cluster.json | jq -r ' - .panels | - map(select(.type == "stat" or .type == "timeseries")) | - .[] | "| \(.title) | \(.description | gsub("\n"; "
")) | \(.targets | map(.expr // .rawSql | "`\(.|gsub("\n"; "
"))`") | join("
")) |" -' diff --git a/rust-toolchain.toml b/rust-toolchain.toml index eb2546003b..5d547223f2 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2024-12-25" +channel = "nightly-2025-04-15" diff --git a/src/api/src/helper.rs b/src/api/src/helper.rs index 6a398b05d3..5c4a02f335 100644 --- a/src/api/src/helper.rs +++ b/src/api/src/helper.rs @@ -514,6 +514,7 @@ fn query_request_type(request: &QueryRequest) -> &'static str { Some(Query::Sql(_)) => "query.sql", Some(Query::LogicalPlan(_)) => "query.logical_plan", Some(Query::PromRangeQuery(_)) => "query.prom_range", + Some(Query::InsertIntoPlan(_)) => "query.insert_into_plan", None => "query.empty", } } diff --git a/src/catalog/src/system_schema/information_schema/key_column_usage.rs b/src/catalog/src/system_schema/information_schema/key_column_usage.rs index 9f08839303..ffcd5eaaa5 100644 --- a/src/catalog/src/system_schema/information_schema/key_column_usage.rs +++ b/src/catalog/src/system_schema/information_schema/key_column_usage.rs @@ -24,7 +24,7 @@ use datafusion::physical_plan::stream::RecordBatchStreamAdapter as DfRecordBatch use datafusion::physical_plan::streaming::PartitionStream as DfPartitionStream; use datafusion::physical_plan::SendableRecordBatchStream as DfSendableRecordBatchStream; use datatypes::prelude::{ConcreteDataType, MutableVector, ScalarVectorBuilder, VectorRef}; -use datatypes::schema::{ColumnSchema, Schema, SchemaRef}; +use datatypes::schema::{ColumnSchema, FulltextBackend, Schema, SchemaRef}; use datatypes::value::Value; use datatypes::vectors::{ConstantVector, StringVector, StringVectorBuilder, UInt32VectorBuilder}; use futures_util::TryStreamExt; @@ -47,20 +47,38 @@ pub const TABLE_SCHEMA: &str = "table_schema"; pub const TABLE_NAME: &str = "table_name"; pub const COLUMN_NAME: &str = "column_name"; pub const ORDINAL_POSITION: &str = "ordinal_position"; +/// The type of the index. +pub const GREPTIME_INDEX_TYPE: &str = "greptime_index_type"; const INIT_CAPACITY: usize = 42; -/// Primary key constraint name -pub(crate) const PRI_CONSTRAINT_NAME: &str = "PRIMARY"; /// Time index constraint name -pub(crate) const TIME_INDEX_CONSTRAINT_NAME: &str = "TIME INDEX"; +pub(crate) const CONSTRAINT_NAME_TIME_INDEX: &str = "TIME INDEX"; + +/// Primary key constraint name +pub(crate) const CONSTRAINT_NAME_PRI: &str = "PRIMARY"; +/// Primary key index type +pub(crate) const INDEX_TYPE_PRI: &str = "greptime-primary-key-v1"; + /// Inverted index constraint name -pub(crate) const INVERTED_INDEX_CONSTRAINT_NAME: &str = "INVERTED INDEX"; +pub(crate) const CONSTRAINT_NAME_INVERTED_INDEX: &str = "INVERTED INDEX"; +/// Inverted index type +pub(crate) const INDEX_TYPE_INVERTED_INDEX: &str = "greptime-inverted-index-v1"; + /// Fulltext index constraint name -pub(crate) const FULLTEXT_INDEX_CONSTRAINT_NAME: &str = "FULLTEXT INDEX"; +pub(crate) const CONSTRAINT_NAME_FULLTEXT_INDEX: &str = "FULLTEXT INDEX"; +/// Fulltext index v1 type +pub(crate) const INDEX_TYPE_FULLTEXT_TANTIVY: &str = "greptime-fulltext-index-v1"; +/// Fulltext index bloom type +pub(crate) const INDEX_TYPE_FULLTEXT_BLOOM: &str = "greptime-fulltext-index-bloom"; + /// Skipping index constraint name -pub(crate) const SKIPPING_INDEX_CONSTRAINT_NAME: &str = "SKIPPING INDEX"; +pub(crate) const CONSTRAINT_NAME_SKIPPING_INDEX: &str = "SKIPPING INDEX"; +/// Skipping index type +pub(crate) const INDEX_TYPE_SKIPPING_INDEX: &str = "greptime-bloom-filter-v1"; /// The virtual table implementation for `information_schema.KEY_COLUMN_USAGE`. +/// +/// Provides an extra column `greptime_index_type` for the index type of the key column. #[derive(Debug)] pub(super) struct InformationSchemaKeyColumnUsage { schema: SchemaRef, @@ -120,6 +138,11 @@ impl InformationSchemaKeyColumnUsage { ConcreteDataType::string_datatype(), true, ), + ColumnSchema::new( + GREPTIME_INDEX_TYPE, + ConcreteDataType::string_datatype(), + true, + ), ])) } @@ -184,6 +207,7 @@ struct InformationSchemaKeyColumnUsageBuilder { column_name: StringVectorBuilder, ordinal_position: UInt32VectorBuilder, position_in_unique_constraint: UInt32VectorBuilder, + greptime_index_type: StringVectorBuilder, } impl InformationSchemaKeyColumnUsageBuilder { @@ -206,6 +230,7 @@ impl InformationSchemaKeyColumnUsageBuilder { column_name: StringVectorBuilder::with_capacity(INIT_CAPACITY), ordinal_position: UInt32VectorBuilder::with_capacity(INIT_CAPACITY), position_in_unique_constraint: UInt32VectorBuilder::with_capacity(INIT_CAPACITY), + greptime_index_type: StringVectorBuilder::with_capacity(INIT_CAPACITY), } } @@ -229,34 +254,47 @@ impl InformationSchemaKeyColumnUsageBuilder { for (idx, column) in schema.column_schemas().iter().enumerate() { let mut constraints = vec![]; + let mut greptime_index_type = vec![]; if column.is_time_index() { self.add_key_column_usage( &predicates, &schema_name, - TIME_INDEX_CONSTRAINT_NAME, + CONSTRAINT_NAME_TIME_INDEX, &catalog_name, &schema_name, table_name, &column.name, 1, //always 1 for time index + "", ); } // TODO(dimbtp): foreign key constraint not supported yet if keys.contains(&idx) { - constraints.push(PRI_CONSTRAINT_NAME); + constraints.push(CONSTRAINT_NAME_PRI); + greptime_index_type.push(INDEX_TYPE_PRI); } if column.is_inverted_indexed() { - constraints.push(INVERTED_INDEX_CONSTRAINT_NAME); + constraints.push(CONSTRAINT_NAME_INVERTED_INDEX); + greptime_index_type.push(INDEX_TYPE_INVERTED_INDEX); } - if column.is_fulltext_indexed() { - constraints.push(FULLTEXT_INDEX_CONSTRAINT_NAME); + if let Ok(Some(options)) = column.fulltext_options() { + if options.enable { + constraints.push(CONSTRAINT_NAME_FULLTEXT_INDEX); + let index_type = match options.backend { + FulltextBackend::Bloom => INDEX_TYPE_FULLTEXT_BLOOM, + FulltextBackend::Tantivy => INDEX_TYPE_FULLTEXT_TANTIVY, + }; + greptime_index_type.push(index_type); + } } if column.is_skipping_indexed() { - constraints.push(SKIPPING_INDEX_CONSTRAINT_NAME); + constraints.push(CONSTRAINT_NAME_SKIPPING_INDEX); + greptime_index_type.push(INDEX_TYPE_SKIPPING_INDEX); } if !constraints.is_empty() { let aggregated_constraints = constraints.join(", "); + let aggregated_index_types = greptime_index_type.join(", "); self.add_key_column_usage( &predicates, &schema_name, @@ -266,6 +304,7 @@ impl InformationSchemaKeyColumnUsageBuilder { table_name, &column.name, idx as u32 + 1, + &aggregated_index_types, ); } } @@ -288,6 +327,7 @@ impl InformationSchemaKeyColumnUsageBuilder { table_name: &str, column_name: &str, ordinal_position: u32, + index_types: &str, ) { let row = [ (CONSTRAINT_SCHEMA, &Value::from(constraint_schema)), @@ -297,6 +337,7 @@ impl InformationSchemaKeyColumnUsageBuilder { (TABLE_NAME, &Value::from(table_name)), (COLUMN_NAME, &Value::from(column_name)), (ORDINAL_POSITION, &Value::from(ordinal_position)), + (GREPTIME_INDEX_TYPE, &Value::from(index_types)), ]; if !predicates.eval(&row) { @@ -313,6 +354,7 @@ impl InformationSchemaKeyColumnUsageBuilder { self.column_name.push(Some(column_name)); self.ordinal_position.push(Some(ordinal_position)); self.position_in_unique_constraint.push(None); + self.greptime_index_type.push(Some(index_types)); } fn finish(&mut self) -> Result { @@ -336,6 +378,7 @@ impl InformationSchemaKeyColumnUsageBuilder { null_string_vector.clone(), null_string_vector.clone(), null_string_vector, + Arc::new(self.greptime_index_type.finish()), ]; RecordBatch::new(self.schema.clone(), columns).context(CreateRecordBatchSnafu) } diff --git a/src/catalog/src/system_schema/information_schema/table_constraints.rs b/src/catalog/src/system_schema/information_schema/table_constraints.rs index a1f9d899f4..77ac93632f 100644 --- a/src/catalog/src/system_schema/information_schema/table_constraints.rs +++ b/src/catalog/src/system_schema/information_schema/table_constraints.rs @@ -36,7 +36,7 @@ use crate::error::{ CreateRecordBatchSnafu, InternalSnafu, Result, UpgradeWeakCatalogManagerRefSnafu, }; use crate::information_schema::key_column_usage::{ - PRI_CONSTRAINT_NAME, TIME_INDEX_CONSTRAINT_NAME, + CONSTRAINT_NAME_PRI, CONSTRAINT_NAME_TIME_INDEX, }; use crate::information_schema::Predicates; use crate::system_schema::information_schema::{InformationTable, TABLE_CONSTRAINTS}; @@ -188,7 +188,7 @@ impl InformationSchemaTableConstraintsBuilder { self.add_table_constraint( &predicates, &schema_name, - TIME_INDEX_CONSTRAINT_NAME, + CONSTRAINT_NAME_TIME_INDEX, &schema_name, &table.table_info().name, TIME_INDEX_CONSTRAINT_TYPE, @@ -199,7 +199,7 @@ impl InformationSchemaTableConstraintsBuilder { self.add_table_constraint( &predicates, &schema_name, - PRI_CONSTRAINT_NAME, + CONSTRAINT_NAME_PRI, &schema_name, &table.table_info().name, PRI_KEY_CONSTRAINT_TYPE, diff --git a/src/catalog/src/system_schema/pg_catalog/pg_namespace/oid_map.rs b/src/catalog/src/system_schema/pg_catalog/pg_namespace/oid_map.rs index edbdac25c7..a2165d731c 100644 --- a/src/catalog/src/system_schema/pg_catalog/pg_namespace/oid_map.rs +++ b/src/catalog/src/system_schema/pg_catalog/pg_namespace/oid_map.rs @@ -84,12 +84,6 @@ mod tests { let key1 = "3178510"; let key2 = "4215648"; - // have collision - assert_eq!( - oid_map.hasher.hash_one(key1) as u32, - oid_map.hasher.hash_one(key2) as u32 - ); - // insert them into oid_map let oid1 = oid_map.get_oid(key1); let oid2 = oid_map.get_oid(key2); diff --git a/src/catalog/src/table_source.rs b/src/catalog/src/table_source.rs index 3cb3b5087d..caf6778214 100644 --- a/src/catalog/src/table_source.rs +++ b/src/catalog/src/table_source.rs @@ -27,7 +27,7 @@ use session::context::QueryContextRef; use snafu::{ensure, OptionExt, ResultExt}; use table::metadata::TableType; use table::table::adapter::DfTableProviderAdapter; -mod dummy_catalog; +pub mod dummy_catalog; use dummy_catalog::DummyCatalogList; use table::TableRef; diff --git a/src/cli/src/error.rs b/src/cli/src/error.rs index be852e7d73..2c18531aaa 100644 --- a/src/cli/src/error.rs +++ b/src/cli/src/error.rs @@ -17,7 +17,6 @@ use std::any::Any; use common_error::ext::{BoxedError, ErrorExt}; use common_error::status_code::StatusCode; use common_macro::stack_trace_debug; -use rustyline::error::ReadlineError; use snafu::{Location, Snafu}; #[derive(Snafu)] @@ -105,52 +104,6 @@ pub enum Error { #[snafu(display("Invalid REPL command: {reason}"))] InvalidReplCommand { reason: String }, - #[snafu(display("Cannot create REPL"))] - ReplCreation { - #[snafu(source)] - error: ReadlineError, - #[snafu(implicit)] - location: Location, - }, - - #[snafu(display("Error reading command"))] - Readline { - #[snafu(source)] - error: ReadlineError, - #[snafu(implicit)] - location: Location, - }, - - #[snafu(display("Failed to request database, sql: {sql}"))] - RequestDatabase { - sql: String, - #[snafu(source)] - source: client::Error, - #[snafu(implicit)] - location: Location, - }, - - #[snafu(display("Failed to collect RecordBatches"))] - CollectRecordBatches { - #[snafu(implicit)] - location: Location, - source: common_recordbatch::error::Error, - }, - - #[snafu(display("Failed to pretty print Recordbatches"))] - PrettyPrintRecordBatches { - #[snafu(implicit)] - location: Location, - source: common_recordbatch::error::Error, - }, - - #[snafu(display("Failed to start Meta client"))] - StartMetaClient { - #[snafu(implicit)] - location: Location, - source: meta_client::error::Error, - }, - #[snafu(display("Failed to parse SQL: {}", sql))] ParseSql { sql: String, @@ -166,13 +119,6 @@ pub enum Error { source: query::error::Error, }, - #[snafu(display("Failed to encode logical plan in substrait"))] - SubstraitEncodeLogicalPlan { - #[snafu(implicit)] - location: Location, - source: substrait::error::Error, - }, - #[snafu(display("Failed to load layered config"))] LoadLayeredConfig { #[snafu(source(from(common_config::error::Error, Box::new)))] @@ -318,17 +264,10 @@ impl ErrorExt for Error { Error::StartProcedureManager { source, .. } | Error::StopProcedureManager { source, .. } => source.status_code(), Error::StartWalOptionsAllocator { source, .. } => source.status_code(), - Error::ReplCreation { .. } | Error::Readline { .. } | Error::HttpQuerySql { .. } => { - StatusCode::Internal - } - Error::RequestDatabase { source, .. } => source.status_code(), - Error::CollectRecordBatches { source, .. } - | Error::PrettyPrintRecordBatches { source, .. } => source.status_code(), - Error::StartMetaClient { source, .. } => source.status_code(), + Error::HttpQuerySql { .. } => StatusCode::Internal, Error::ParseSql { source, .. } | Error::PlanStatement { source, .. } => { source.status_code() } - Error::SubstraitEncodeLogicalPlan { source, .. } => source.status_code(), Error::SerdeJson { .. } | Error::FileIo { .. } diff --git a/src/cli/src/lib.rs b/src/cli/src/lib.rs index 3991f3a666..113e88f1c1 100644 --- a/src/cli/src/lib.rs +++ b/src/cli/src/lib.rs @@ -23,15 +23,12 @@ mod helper; // Wait for https://github.com/GreptimeTeam/greptimedb/issues/2373 mod database; mod import; -#[allow(unused)] -mod repl; use async_trait::async_trait; use clap::Parser; use common_error::ext::BoxedError; pub use database::DatabaseClient; use error::Result; -pub use repl::Repl; pub use crate::bench::BenchTableMetadataCommand; pub use crate::export::ExportCommand; diff --git a/src/cli/src/repl.rs b/src/cli/src/repl.rs deleted file mode 100644 index 8b5e3aa389..0000000000 --- a/src/cli/src/repl.rs +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright 2023 Greptime Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Instant; - -use cache::{ - build_fundamental_cache_registry, with_default_composite_cache_registry, TABLE_CACHE_NAME, - TABLE_ROUTE_CACHE_NAME, -}; -use catalog::information_extension::DistributedInformationExtension; -use catalog::kvbackend::{ - CachedKvBackend, CachedKvBackendBuilder, KvBackendCatalogManager, MetaKvBackend, -}; -use client::{Client, Database, OutputData, DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; -use common_base::Plugins; -use common_config::Mode; -use common_error::ext::ErrorExt; -use common_meta::cache::{CacheRegistryBuilder, LayeredCacheRegistryBuilder}; -use common_meta::kv_backend::KvBackendRef; -use common_query::Output; -use common_recordbatch::RecordBatches; -use common_telemetry::debug; -use either::Either; -use meta_client::client::{ClusterKvBackend, MetaClientBuilder}; -use query::datafusion::DatafusionQueryEngine; -use query::parser::QueryLanguageParser; -use query::query_engine::{DefaultSerializer, QueryEngineState}; -use query::QueryEngine; -use rustyline::error::ReadlineError; -use rustyline::Editor; -use session::context::QueryContext; -use snafu::{OptionExt, ResultExt}; -use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan}; - -use crate::cmd::ReplCommand; -use crate::error::{ - CollectRecordBatchesSnafu, ParseSqlSnafu, PlanStatementSnafu, PrettyPrintRecordBatchesSnafu, - ReadlineSnafu, ReplCreationSnafu, RequestDatabaseSnafu, Result, StartMetaClientSnafu, - SubstraitEncodeLogicalPlanSnafu, -}; -use crate::helper::RustylineHelper; -use crate::{error, AttachCommand}; - -/// Captures the state of the repl, gathers commands and executes them one by one -pub struct Repl { - /// Rustyline editor for interacting with user on command line - rl: Editor, - - /// Current prompt - prompt: String, - - /// Client for interacting with GreptimeDB - database: Database, - - query_engine: Option, -} - -#[allow(clippy::print_stdout)] -impl Repl { - fn print_help(&self) { - println!("{}", ReplCommand::help()) - } - - pub(crate) async fn try_new(cmd: &AttachCommand) -> Result { - let mut rl = Editor::new().context(ReplCreationSnafu)?; - - if !cmd.disable_helper { - rl.set_helper(Some(RustylineHelper::default())); - - let history_file = history_file(); - if let Err(e) = rl.load_history(&history_file) { - debug!( - "failed to load history file on {}, error: {e}", - history_file.display() - ); - } - } - - let client = Client::with_urls([&cmd.grpc_addr]); - let database = Database::new(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, client); - - let query_engine = if let Some(meta_addr) = &cmd.meta_addr { - create_query_engine(meta_addr).await.map(Some)? - } else { - None - }; - - Ok(Self { - rl, - prompt: "> ".to_string(), - database, - query_engine, - }) - } - - /// Parse the next command - fn next_command(&mut self) -> Result { - match self.rl.readline(&self.prompt) { - Ok(ref line) => { - let request = line.trim(); - - let _ = self.rl.add_history_entry(request.to_string()); - - request.try_into() - } - Err(ReadlineError::Eof) | Err(ReadlineError::Interrupted) => Ok(ReplCommand::Exit), - // Some sort of real underlying error - Err(e) => Err(e).context(ReadlineSnafu), - } - } - - /// Read Evaluate Print Loop (interactive command line) for GreptimeDB - /// - /// Inspired / based on repl.rs from InfluxDB IOX - pub(crate) async fn run(&mut self) -> Result<()> { - println!("Ready for commands. (Hint: try 'help')"); - - loop { - match self.next_command()? { - ReplCommand::Help => { - self.print_help(); - } - ReplCommand::UseDatabase { db_name } => { - if self.execute_sql(format!("USE {db_name}")).await { - println!("Using {db_name}"); - self.database.set_schema(&db_name); - self.prompt = format!("[{db_name}] > "); - } - } - ReplCommand::Sql { sql } => { - let _ = self.execute_sql(sql).await; - } - ReplCommand::Exit => { - return Ok(()); - } - } - } - } - - async fn execute_sql(&self, sql: String) -> bool { - self.do_execute_sql(sql) - .await - .map_err(|e| { - let status_code = e.status_code(); - let root_cause = e.output_msg(); - println!("Error: {}({status_code}), {root_cause}", status_code as u32) - }) - .is_ok() - } - - async fn do_execute_sql(&self, sql: String) -> Result<()> { - let start = Instant::now(); - - let output = if let Some(query_engine) = &self.query_engine { - let query_ctx = Arc::new(QueryContext::with( - self.database.catalog(), - self.database.schema(), - )); - - let stmt = QueryLanguageParser::parse_sql(&sql, &query_ctx) - .with_context(|_| ParseSqlSnafu { sql: sql.clone() })?; - - let plan = query_engine - .planner() - .plan(&stmt, query_ctx.clone()) - .await - .context(PlanStatementSnafu)?; - - let plan = query_engine - .optimize(&query_engine.engine_context(query_ctx), &plan) - .context(PlanStatementSnafu)?; - - let plan = DFLogicalSubstraitConvertor {} - .encode(&plan, DefaultSerializer) - .context(SubstraitEncodeLogicalPlanSnafu)?; - - self.database.logical_plan(plan.to_vec()).await - } else { - self.database.sql(&sql).await - } - .context(RequestDatabaseSnafu { sql: &sql })?; - - let either = match output.data { - OutputData::Stream(s) => { - let x = RecordBatches::try_collect(s) - .await - .context(CollectRecordBatchesSnafu)?; - Either::Left(x) - } - OutputData::RecordBatches(x) => Either::Left(x), - OutputData::AffectedRows(rows) => Either::Right(rows), - }; - - let end = Instant::now(); - - match either { - Either::Left(recordbatches) => { - let total_rows: usize = recordbatches.iter().map(|x| x.num_rows()).sum(); - if total_rows > 0 { - println!( - "{}", - recordbatches - .pretty_print() - .context(PrettyPrintRecordBatchesSnafu)? - ); - } - println!("Total Rows: {total_rows}") - } - Either::Right(rows) => println!("Affected Rows: {rows}"), - }; - - println!("Cost {} ms", (end - start).as_millis()); - Ok(()) - } -} - -impl Drop for Repl { - fn drop(&mut self) { - if self.rl.helper().is_some() { - let history_file = history_file(); - if let Err(e) = self.rl.save_history(&history_file) { - debug!( - "failed to save history file on {}, error: {e}", - history_file.display() - ); - } - } - } -} - -/// Return the location of the history file (defaults to $HOME/".greptimedb_cli_history") -fn history_file() -> PathBuf { - let mut buf = match std::env::var("HOME") { - Ok(home) => PathBuf::from(home), - Err(_) => PathBuf::new(), - }; - buf.push(".greptimedb_cli_history"); - buf -} - -async fn create_query_engine(meta_addr: &str) -> Result { - let mut meta_client = MetaClientBuilder::default().enable_store().build(); - meta_client - .start([meta_addr]) - .await - .context(StartMetaClientSnafu)?; - let meta_client = Arc::new(meta_client); - - let cached_meta_backend = Arc::new( - CachedKvBackendBuilder::new(Arc::new(MetaKvBackend::new(meta_client.clone()))).build(), - ); - let layered_cache_builder = LayeredCacheRegistryBuilder::default().add_cache_registry( - CacheRegistryBuilder::default() - .add_cache(cached_meta_backend.clone()) - .build(), - ); - let fundamental_cache_registry = - build_fundamental_cache_registry(Arc::new(MetaKvBackend::new(meta_client.clone()))); - let layered_cache_registry = Arc::new( - with_default_composite_cache_registry( - layered_cache_builder.add_cache_registry(fundamental_cache_registry), - ) - .context(error::BuildCacheRegistrySnafu)? - .build(), - ); - - let information_extension = Arc::new(DistributedInformationExtension::new(meta_client.clone())); - let catalog_manager = KvBackendCatalogManager::new( - information_extension, - cached_meta_backend.clone(), - layered_cache_registry, - None, - ); - let plugins: Plugins = Default::default(); - let state = Arc::new(QueryEngineState::new( - catalog_manager, - None, - None, - None, - None, - false, - plugins.clone(), - )); - - Ok(DatafusionQueryEngine::new(state, plugins)) -} diff --git a/src/client/Cargo.toml b/src/client/Cargo.toml index f8702fe6ac..99d0c97806 100644 --- a/src/client/Cargo.toml +++ b/src/client/Cargo.toml @@ -16,6 +16,7 @@ arc-swap = "1.6" arrow-flight.workspace = true async-stream.workspace = true async-trait.workspace = true +base64.workspace = true common-catalog.workspace = true common-error.workspace = true common-grpc.workspace = true @@ -25,6 +26,7 @@ common-query.workspace = true common-recordbatch.workspace = true common-telemetry.workspace = true enum_dispatch = "0.3" +futures.workspace = true futures-util.workspace = true lazy_static.workspace = true moka = { workspace = true, features = ["future"] } diff --git a/src/client/src/database.rs b/src/client/src/database.rs index 2479240562..c9dc9b08e5 100644 --- a/src/client/src/database.rs +++ b/src/client/src/database.rs @@ -12,36 +12,49 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::pin::Pin; +use std::str::FromStr; + use api::v1::auth_header::AuthScheme; use api::v1::ddl_request::Expr as DdlExpr; use api::v1::greptime_database_client::GreptimeDatabaseClient; use api::v1::greptime_request::Request; use api::v1::query_request::Query; use api::v1::{ - AlterTableExpr, AuthHeader, CreateTableExpr, DdlRequest, GreptimeRequest, InsertRequests, - QueryRequest, RequestHeader, + AlterTableExpr, AuthHeader, Basic, CreateTableExpr, DdlRequest, GreptimeRequest, + InsertRequests, QueryRequest, RequestHeader, }; -use arrow_flight::Ticket; +use arrow_flight::{FlightData, Ticket}; use async_stream::stream; +use base64::prelude::BASE64_STANDARD; +use base64::Engine; +use common_catalog::build_db_string; +use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; use common_error::ext::{BoxedError, ErrorExt}; +use common_grpc::flight::do_put::DoPutResponse; use common_grpc::flight::{FlightDecoder, FlightMessage}; use common_query::Output; use common_recordbatch::error::ExternalSnafu; use common_recordbatch::RecordBatchStreamWrapper; use common_telemetry::error; use common_telemetry::tracing_context::W3cTrace; -use futures_util::StreamExt; +use futures::future; +use futures_util::{Stream, StreamExt, TryStreamExt}; use prost::Message; use snafu::{ensure, ResultExt}; -use tonic::metadata::AsciiMetadataKey; +use tonic::metadata::{AsciiMetadataKey, MetadataValue}; use tonic::transport::Channel; use crate::error::{ ConvertFlightDataSnafu, Error, FlightGetSnafu, IllegalFlightMessagesSnafu, InvalidAsciiSnafu, - ServerSnafu, + InvalidTonicMetadataValueSnafu, ServerSnafu, }; use crate::{from_grpc_response, Client, Result}; +type FlightDataStream = Pin + Send>>; + +type DoPutResponseStream = Pin>>>; + #[derive(Clone, Debug, Default)] pub struct Database { // The "catalog" and "schema" to be used in processing the requests at the server side. @@ -108,16 +121,24 @@ impl Database { self.catalog = catalog.into(); } - pub fn catalog(&self) -> &String { - &self.catalog + fn catalog_or_default(&self) -> &str { + if self.catalog.is_empty() { + DEFAULT_CATALOG_NAME + } else { + &self.catalog + } } pub fn set_schema(&mut self, schema: impl Into) { self.schema = schema.into(); } - pub fn schema(&self) -> &String { - &self.schema + fn schema_or_default(&self) -> &str { + if self.schema.is_empty() { + DEFAULT_SCHEMA_NAME + } else { + &self.schema + } } pub fn set_timezone(&mut self, timezone: impl Into) { @@ -310,6 +331,41 @@ impl Database { } } } + + /// Ingest a stream of [RecordBatch]es that belong to a table, using Arrow Flight's "`DoPut`" + /// method. The return value is also a stream, produces [DoPutResponse]s. + pub async fn do_put(&self, stream: FlightDataStream) -> Result { + let mut request = tonic::Request::new(stream); + + if let Some(AuthHeader { + auth_scheme: Some(AuthScheme::Basic(Basic { username, password })), + }) = &self.ctx.auth_header + { + let encoded = BASE64_STANDARD.encode(format!("{username}:{password}")); + let value = + MetadataValue::from_str(&encoded).context(InvalidTonicMetadataValueSnafu)?; + request.metadata_mut().insert("x-greptime-auth", value); + } + + let db_to_put = if !self.dbname.is_empty() { + &self.dbname + } else { + &build_db_string(self.catalog_or_default(), self.schema_or_default()) + }; + request.metadata_mut().insert( + "x-greptime-db-name", + MetadataValue::from_str(db_to_put).context(InvalidTonicMetadataValueSnafu)?, + ); + + let mut client = self.client.make_flight_client()?; + let response = client.mut_inner().do_put(request).await?; + let response = response + .into_inner() + .map_err(Into::into) + .and_then(|x| future::ready(DoPutResponse::try_from(x).context(ConvertFlightDataSnafu))) + .boxed(); + Ok(response) + } } #[derive(Default, Debug, Clone)] diff --git a/src/client/src/error.rs b/src/client/src/error.rs index ed0c1b5ccb..3f680b1427 100644 --- a/src/client/src/error.rs +++ b/src/client/src/error.rs @@ -19,6 +19,7 @@ use common_error::status_code::{convert_tonic_code_to_status_code, StatusCode}; use common_error::{GREPTIME_DB_HEADER_ERROR_CODE, GREPTIME_DB_HEADER_ERROR_MSG}; use common_macro::stack_trace_debug; use snafu::{location, Location, Snafu}; +use tonic::metadata::errors::InvalidMetadataValue; use tonic::{Code, Status}; #[derive(Snafu)] @@ -115,6 +116,14 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display("Invalid Tonic metadata value"))] + InvalidTonicMetadataValue { + #[snafu(source)] + error: InvalidMetadataValue, + #[snafu(implicit)] + location: Location, + }, } pub type Result = std::result::Result; @@ -135,7 +144,9 @@ impl ErrorExt for Error { | Error::CreateTlsChannel { source, .. } => source.status_code(), Error::IllegalGrpcClientState { .. } => StatusCode::Unexpected, - Error::InvalidAscii { .. } => StatusCode::InvalidArguments, + Error::InvalidAscii { .. } | Error::InvalidTonicMetadataValue { .. } => { + StatusCode::InvalidArguments + } } } diff --git a/src/client/src/lib.rs b/src/client/src/lib.rs index 125c185a5a..7078e71795 100644 --- a/src/client/src/lib.rs +++ b/src/client/src/lib.rs @@ -16,7 +16,7 @@ mod client; pub mod client_manager; -mod database; +pub mod database; pub mod error; pub mod flow; pub mod load_balance; diff --git a/src/cmd/Cargo.toml b/src/cmd/Cargo.toml index c3328fbc8d..b3ffd479a6 100644 --- a/src/cmd/Cargo.toml +++ b/src/cmd/Cargo.toml @@ -68,7 +68,6 @@ query.workspace = true rand.workspace = true regex.workspace = true reqwest.workspace = true -rustyline = "10.1" serde.workspace = true serde_json.workspace = true servers.workspace = true diff --git a/src/cmd/src/error.rs b/src/cmd/src/error.rs index 8697710985..a671290503 100644 --- a/src/cmd/src/error.rs +++ b/src/cmd/src/error.rs @@ -17,7 +17,6 @@ use std::any::Any; use common_error::ext::{BoxedError, ErrorExt}; use common_error::status_code::StatusCode; use common_macro::stack_trace_debug; -use rustyline::error::ReadlineError; use snafu::{Location, Snafu}; #[derive(Snafu)] @@ -181,52 +180,6 @@ pub enum Error { #[snafu(display("Invalid REPL command: {reason}"))] InvalidReplCommand { reason: String }, - #[snafu(display("Cannot create REPL"))] - ReplCreation { - #[snafu(source)] - error: ReadlineError, - #[snafu(implicit)] - location: Location, - }, - - #[snafu(display("Error reading command"))] - Readline { - #[snafu(source)] - error: ReadlineError, - #[snafu(implicit)] - location: Location, - }, - - #[snafu(display("Failed to request database, sql: {sql}"))] - RequestDatabase { - sql: String, - #[snafu(source)] - source: client::Error, - #[snafu(implicit)] - location: Location, - }, - - #[snafu(display("Failed to collect RecordBatches"))] - CollectRecordBatches { - #[snafu(implicit)] - location: Location, - source: common_recordbatch::error::Error, - }, - - #[snafu(display("Failed to pretty print Recordbatches"))] - PrettyPrintRecordBatches { - #[snafu(implicit)] - location: Location, - source: common_recordbatch::error::Error, - }, - - #[snafu(display("Failed to start Meta client"))] - StartMetaClient { - #[snafu(implicit)] - location: Location, - source: meta_client::error::Error, - }, - #[snafu(display("Failed to parse SQL: {}", sql))] ParseSql { sql: String, @@ -242,13 +195,6 @@ pub enum Error { source: query::error::Error, }, - #[snafu(display("Failed to encode logical plan in substrait"))] - SubstraitEncodeLogicalPlan { - #[snafu(implicit)] - location: Location, - source: substrait::error::Error, - }, - #[snafu(display("Failed to load layered config"))] LoadLayeredConfig { #[snafu(source(from(common_config::error::Error, Box::new)))] @@ -395,17 +341,10 @@ impl ErrorExt for Error { | Error::StopProcedureManager { source, .. } => source.status_code(), Error::BuildWalOptionsAllocator { source, .. } | Error::StartWalOptionsAllocator { source, .. } => source.status_code(), - Error::ReplCreation { .. } | Error::Readline { .. } | Error::HttpQuerySql { .. } => { - StatusCode::Internal - } - Error::RequestDatabase { source, .. } => source.status_code(), - Error::CollectRecordBatches { source, .. } - | Error::PrettyPrintRecordBatches { source, .. } => source.status_code(), - Error::StartMetaClient { source, .. } => source.status_code(), + Error::HttpQuerySql { .. } => StatusCode::Internal, Error::ParseSql { source, .. } | Error::PlanStatement { source, .. } => { source.status_code() } - Error::SubstraitEncodeLogicalPlan { source, .. } => source.status_code(), Error::SerdeJson { .. } | Error::FileIo { .. } diff --git a/src/cmd/src/flownode.rs b/src/cmd/src/flownode.rs index fc23d37c23..3fc8249349 100644 --- a/src/cmd/src/flownode.rs +++ b/src/cmd/src/flownode.rs @@ -32,7 +32,9 @@ use common_meta::key::TableMetadataManager; use common_telemetry::info; use common_telemetry::logging::TracingOptions; use common_version::{short_version, version}; -use flow::{FlownodeBuilder, FlownodeInstance, FlownodeServiceBuilder, FrontendInvoker}; +use flow::{ + FlownodeBuilder, FlownodeInstance, FlownodeServiceBuilder, FrontendClient, FrontendInvoker, +}; use meta_client::{MetaClientOptions, MetaClientType}; use snafu::{ensure, OptionExt, ResultExt}; use tracing_appender::non_blocking::WorkerGuard; @@ -313,12 +315,14 @@ impl StartCommand { ); let flow_metadata_manager = Arc::new(FlowMetadataManager::new(cached_meta_backend.clone())); + let frontend_client = FrontendClient::from_meta_client(meta_client.clone()); let flownode_builder = FlownodeBuilder::new( opts.clone(), Plugins::new(), table_metadata_manager, catalog_manager.clone(), flow_metadata_manager, + Arc::new(frontend_client), ) .with_heartbeat_task(heartbeat_task); @@ -341,7 +345,7 @@ impl StartCommand { let client = Arc::new(NodeClients::new(channel_config)); let invoker = FrontendInvoker::build_from( - flownode.flow_worker_manager().clone(), + flownode.flow_engine().streaming_engine(), catalog_manager.clone(), cached_meta_backend.clone(), layered_cache_registry.clone(), @@ -351,7 +355,9 @@ impl StartCommand { .await .context(StartFlownodeSnafu)?; flownode - .flow_worker_manager() + .flow_engine() + .streaming_engine() + // TODO(discord9): refactor and avoid circular reference .set_frontend_invoker(invoker) .await; diff --git a/src/cmd/src/metasrv.rs b/src/cmd/src/metasrv.rs index dc455dcf65..da017e71cd 100644 --- a/src/cmd/src/metasrv.rs +++ b/src/cmd/src/metasrv.rs @@ -132,7 +132,7 @@ impl SubCommand { } #[derive(Debug, Default, Parser)] -struct StartCommand { +pub struct StartCommand { /// The address to bind the gRPC server. #[clap(long, alias = "bind-addr")] rpc_bind_addr: Option, @@ -172,7 +172,7 @@ struct StartCommand { } impl StartCommand { - fn load_options(&self, global_options: &GlobalOptions) -> Result { + pub fn load_options(&self, global_options: &GlobalOptions) -> Result { let mut opts = MetasrvOptions::load_layered_options( self.config_file.as_deref(), self.env_prefix.as_ref(), @@ -261,7 +261,7 @@ impl StartCommand { Ok(()) } - async fn build(&self, opts: MetasrvOptions) -> Result { + pub async fn build(&self, opts: MetasrvOptions) -> Result { common_runtime::init_global_runtimes(&opts.runtime); let guard = common_telemetry::init_global_logging( diff --git a/src/cmd/src/standalone.rs b/src/cmd/src/standalone.rs index 4504927cc8..5877329698 100644 --- a/src/cmd/src/standalone.rs +++ b/src/cmd/src/standalone.rs @@ -56,8 +56,8 @@ use datanode::datanode::{Datanode, DatanodeBuilder}; use datanode::region_server::RegionServer; use file_engine::config::EngineConfig as FileEngineConfig; use flow::{ - FlowConfig, FlowWorkerManager, FlownodeBuilder, FlownodeInstance, FlownodeOptions, - FrontendInvoker, + FlowConfig, FlownodeBuilder, FlownodeInstance, FlownodeOptions, FrontendClient, + FrontendInvoker, GrpcQueryHandlerWithBoxedError, StreamingEngine, }; use frontend::frontend::{Frontend, FrontendOptions}; use frontend::instance::builder::FrontendBuilder; @@ -523,12 +523,18 @@ impl StartCommand { flow: opts.flow.clone(), ..Default::default() }; + + // for standalone not use grpc, but get a handler to frontend grpc client without + // actually make a connection + let (frontend_client, frontend_instance_handler) = + FrontendClient::from_empty_grpc_handler(); let flow_builder = FlownodeBuilder::new( flownode_options, plugins.clone(), table_metadata_manager.clone(), catalog_manager.clone(), flow_metadata_manager.clone(), + Arc::new(frontend_client.clone()), ); let flownode = flow_builder .build() @@ -538,15 +544,15 @@ impl StartCommand { // set the ref to query for the local flow state { - let flow_worker_manager = flownode.flow_worker_manager(); + let flow_streaming_engine = flownode.flow_engine().streaming_engine(); information_extension - .set_flow_worker_manager(flow_worker_manager.clone()) + .set_flow_streaming_engine(flow_streaming_engine) .await; } let node_manager = Arc::new(StandaloneDatanodeManager { region_server: datanode.region_server(), - flow_server: flownode.flow_worker_manager(), + flow_server: flownode.flow_engine(), }); let table_id_sequence = Arc::new( @@ -600,10 +606,19 @@ impl StartCommand { .context(error::StartFrontendSnafu)?; let fe_instance = Arc::new(fe_instance); - let flow_worker_manager = flownode.flow_worker_manager(); + // set the frontend client for flownode + let grpc_handler = fe_instance.clone() as Arc; + let weak_grpc_handler = Arc::downgrade(&grpc_handler); + frontend_instance_handler + .lock() + .unwrap() + .replace(weak_grpc_handler); + + // set the frontend invoker for flownode + let flow_streaming_engine = flownode.flow_engine().streaming_engine(); // flow server need to be able to use frontend to write insert requests back let invoker = FrontendInvoker::build_from( - flow_worker_manager.clone(), + flow_streaming_engine.clone(), catalog_manager.clone(), kv_backend.clone(), layered_cache_registry.clone(), @@ -612,7 +627,7 @@ impl StartCommand { ) .await .context(error::StartFlownodeSnafu)?; - flow_worker_manager.set_frontend_invoker(invoker).await; + flow_streaming_engine.set_frontend_invoker(invoker).await; let export_metrics_task = ExportMetricsTask::try_new(&opts.export_metrics, Some(&plugins)) .context(error::ServersSnafu)?; @@ -688,7 +703,7 @@ pub struct StandaloneInformationExtension { region_server: RegionServer, procedure_manager: ProcedureManagerRef, start_time_ms: u64, - flow_worker_manager: RwLock>>, + flow_streaming_engine: RwLock>>, } impl StandaloneInformationExtension { @@ -697,14 +712,14 @@ impl StandaloneInformationExtension { region_server, procedure_manager, start_time_ms: common_time::util::current_time_millis() as u64, - flow_worker_manager: RwLock::new(None), + flow_streaming_engine: RwLock::new(None), } } - /// Set the flow worker manager for the standalone instance. - pub async fn set_flow_worker_manager(&self, flow_worker_manager: Arc) { - let mut guard = self.flow_worker_manager.write().await; - *guard = Some(flow_worker_manager); + /// Set the flow streaming engine for the standalone instance. + pub async fn set_flow_streaming_engine(&self, flow_streaming_engine: Arc) { + let mut guard = self.flow_streaming_engine.write().await; + *guard = Some(flow_streaming_engine); } } @@ -773,6 +788,8 @@ impl InformationExtension for StandaloneInformationExtension { sst_size: region_stat.sst_size, index_size: region_stat.index_size, region_manifest: region_stat.manifest.into(), + data_topic_latest_entry_id: region_stat.data_topic_latest_entry_id, + metadata_topic_latest_entry_id: region_stat.metadata_topic_latest_entry_id, } }) .collect::>(); @@ -781,7 +798,7 @@ impl InformationExtension for StandaloneInformationExtension { async fn flow_stats(&self) -> std::result::Result, Self::Error> { Ok(Some( - self.flow_worker_manager + self.flow_streaming_engine .read() .await .as_ref() diff --git a/src/cmd/tests/cli.rs b/src/cmd/tests/cli.rs deleted file mode 100644 index dfea9afc3e..0000000000 --- a/src/cmd/tests/cli.rs +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2023 Greptime Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#[cfg(target_os = "macos")] -mod tests { - use std::path::PathBuf; - use std::process::{Command, Stdio}; - use std::time::Duration; - - use common_test_util::temp_dir::create_temp_dir; - use rexpect::session::PtyReplSession; - - struct Repl { - repl: PtyReplSession, - } - - impl Repl { - fn send_line(&mut self, line: &str) { - let _ = self.repl.send_line(line).unwrap(); - - // read a line to consume the prompt - let _ = self.read_line(); - } - - fn read_line(&mut self) -> String { - self.repl.read_line().unwrap() - } - - fn read_expect(&mut self, expect: &str) { - assert_eq!(self.read_line(), expect); - } - - fn read_contains(&mut self, pat: &str) { - assert!(self.read_line().contains(pat)); - } - } - - // TODO(LFC): Un-ignore this REPL test. - // Ignore this REPL test because some logical plans like create database are not supported yet in Datanode. - #[ignore] - #[test] - fn test_repl() { - let data_home = create_temp_dir("data"); - let wal_dir = create_temp_dir("wal"); - - let mut bin_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - bin_path.push("../../target/debug"); - let bin_path = bin_path.to_str().unwrap(); - - let mut datanode = Command::new("./greptime") - .current_dir(bin_path) - .args([ - "datanode", - "start", - "--rpc-bind-addr=0.0.0.0:4321", - "--node-id=1", - &format!("--data-home={}", data_home.path().display()), - &format!("--wal-dir={}", wal_dir.path().display()), - ]) - .stdout(Stdio::null()) - .spawn() - .unwrap(); - - // wait for Datanode actually started - std::thread::sleep(Duration::from_secs(3)); - - let mut repl_cmd = Command::new("./greptime"); - let _ = repl_cmd.current_dir(bin_path).args([ - "--log-level=off", - "cli", - "attach", - "--grpc-bind-addr=0.0.0.0:4321", - // history commands can sneaky into stdout and mess up our tests, so disable it - "--disable-helper", - ]); - let pty_session = rexpect::session::spawn_command(repl_cmd, Some(5_000)).unwrap(); - let repl = PtyReplSession { - prompt: "> ".to_string(), - pty_session, - quit_command: None, - echo_on: false, - }; - let repl = &mut Repl { repl }; - repl.read_expect("Ready for commands. (Hint: try 'help')"); - - test_create_database(repl); - - test_use_database(repl); - - test_create_table(repl); - - test_insert(repl); - - test_select(repl); - - datanode.kill().unwrap(); - let _ = datanode.wait().unwrap(); - } - - fn test_create_database(repl: &mut Repl) { - repl.send_line("CREATE DATABASE db;"); - repl.read_expect("Affected Rows: 1"); - repl.read_contains("Cost"); - } - - fn test_use_database(repl: &mut Repl) { - repl.send_line("USE db"); - repl.read_expect("Total Rows: 0"); - repl.read_contains("Cost"); - repl.read_expect("Using db"); - } - - fn test_create_table(repl: &mut Repl) { - repl.send_line("CREATE TABLE t(x STRING, ts TIMESTAMP TIME INDEX);"); - repl.read_expect("Affected Rows: 0"); - repl.read_contains("Cost"); - } - - fn test_insert(repl: &mut Repl) { - repl.send_line("INSERT INTO t(x, ts) VALUES ('hello', 1676895812239);"); - repl.read_expect("Affected Rows: 1"); - repl.read_contains("Cost"); - } - - fn test_select(repl: &mut Repl) { - repl.send_line("SELECT * FROM t;"); - - repl.read_expect("+-------+-------------------------+"); - repl.read_expect("| x | ts |"); - repl.read_expect("+-------+-------------------------+"); - repl.read_expect("| hello | 2023-02-20T12:23:32.239 |"); - repl.read_expect("+-------+-------------------------+"); - repl.read_expect("Total Rows: 1"); - - repl.read_contains("Cost"); - } -} diff --git a/src/cmd/tests/load_config_test.rs b/src/cmd/tests/load_config_test.rs index 73d1295417..07be39dddb 100644 --- a/src/cmd/tests/load_config_test.rs +++ b/src/cmd/tests/load_config_test.rs @@ -74,6 +74,7 @@ fn test_load_datanode_example_config() { RegionEngineConfig::File(FileEngineConfig {}), RegionEngineConfig::Metric(MetricEngineConfig { experimental_sparse_primary_key_encoding: false, + flush_metadata_region_interval: Duration::from_secs(30), }), ], logging: LoggingOptions { @@ -216,6 +217,7 @@ fn test_load_standalone_example_config() { RegionEngineConfig::File(FileEngineConfig {}), RegionEngineConfig::Metric(MetricEngineConfig { experimental_sparse_primary_key_encoding: false, + flush_metadata_region_interval: Duration::from_secs(30), }), ], storage: StorageConfig { diff --git a/src/common/base/src/plugins.rs b/src/common/base/src/plugins.rs index c392422b64..5d27ad1ac1 100644 --- a/src/common/base/src/plugins.rs +++ b/src/common/base/src/plugins.rs @@ -31,7 +31,8 @@ impl Plugins { } pub fn insert(&self, value: T) { - let _ = self.write().insert(value); + let last = self.write().insert(value); + assert!(last.is_none(), "each type of plugins must be one and only"); } pub fn get(&self) -> Option { @@ -137,4 +138,12 @@ mod tests { assert_eq!(plugins.len(), 2); assert!(!plugins.is_empty()); } + + #[test] + #[should_panic(expected = "each type of plugins must be one and only")] + fn test_plugin_uniqueness() { + let plugins = Plugins::new(); + plugins.insert(1i32); + plugins.insert(2i32); + } } diff --git a/src/common/error/Cargo.toml b/src/common/error/Cargo.toml index 148e2c6633..031f944dbd 100644 --- a/src/common/error/Cargo.toml +++ b/src/common/error/Cargo.toml @@ -12,3 +12,6 @@ http.workspace = true snafu.workspace = true strum.workspace = true tonic.workspace = true + +[dev-dependencies] +common-macro.workspace = true diff --git a/src/common/error/src/ext.rs b/src/common/error/src/ext.rs index 3b4d15a835..3f95c5fe1a 100644 --- a/src/common/error/src/ext.rs +++ b/src/common/error/src/ext.rs @@ -42,7 +42,7 @@ pub trait ErrorExt: StackError { if let Some(external_error) = error.source() { let external_root = external_error.sources().last().unwrap(); - if error.to_string().is_empty() { + if error.transparent() { format!("{external_root}") } else { format!("{error}: {external_root}") @@ -86,6 +86,14 @@ pub trait StackError: std::error::Error { } result } + + /// Indicates whether this error is "transparent", that it delegates its "display" and "source" + /// to the underlying error. Could be useful when you are just wrapping some external error, + /// **AND** can not or would not provide meaningful contextual info. For example, the + /// `DataFusionError`. + fn transparent(&self) -> bool { + false + } } impl StackError for Arc { diff --git a/src/common/error/tests/ext.rs b/src/common/error/tests/ext.rs new file mode 100644 index 0000000000..26eaf2d5e2 --- /dev/null +++ b/src/common/error/tests/ext.rs @@ -0,0 +1,115 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::any::Any; + +use common_error::ext::{ErrorExt, PlainError, StackError}; +use common_error::status_code::StatusCode; +use common_macro::stack_trace_debug; +use snafu::{Location, ResultExt, Snafu}; + +#[derive(Snafu)] +#[stack_trace_debug] +enum MyError { + #[snafu(display(r#"A normal error with "display" attribute, message "{}""#, message))] + Normal { + message: String, + #[snafu(source)] + error: PlainError, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(transparent)] + Transparent { + #[snafu(source)] + error: PlainError, + #[snafu(implicit)] + location: Location, + }, +} + +impl ErrorExt for MyError { + fn status_code(&self) -> StatusCode { + StatusCode::Unexpected + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +fn normal_error() -> Result<(), MyError> { + let plain_error = PlainError::new("".to_string(), StatusCode::Unexpected); + Err(plain_error).context(NormalSnafu { message: "blabla" }) +} + +fn transparent_error() -> Result<(), MyError> { + let plain_error = PlainError::new("".to_string(), StatusCode::Unexpected); + Err(plain_error)? +} + +#[test] +fn test_output_msg() { + let result = normal_error(); + assert_eq!( + result.unwrap_err().output_msg(), + r#"A normal error with "display" attribute, message "blabla": "# + ); + + let result = transparent_error(); + assert_eq!(result.unwrap_err().output_msg(), ""); +} + +#[test] +fn test_to_string() { + let result = normal_error(); + assert_eq!( + result.unwrap_err().to_string(), + r#"A normal error with "display" attribute, message "blabla""# + ); + + let result = transparent_error(); + assert_eq!(result.unwrap_err().to_string(), ""); +} + +#[test] +fn test_debug_format() { + let result = normal_error(); + let debug_output = format!("{:?}", result.unwrap_err()); + let normalized_output = debug_output.replace('\\', "/"); + assert_eq!( + normalized_output, + r#"0: A normal error with "display" attribute, message "blabla", at src/common/error/tests/ext.rs:55:22 +1: PlainError { msg: "", status_code: Unexpected }"# + ); + + let result = transparent_error(); + let debug_output = format!("{:?}", result.unwrap_err()); + let normalized_output = debug_output.replace('\\', "/"); + assert_eq!( + normalized_output, + r#"0: , at src/common/error/tests/ext.rs:60:5 +1: PlainError { msg: "", status_code: Unexpected }"# + ); +} + +#[test] +fn test_transparent_flag() { + let result = normal_error(); + assert!(!result.unwrap_err().transparent()); + + let result = transparent_error(); + assert!(result.unwrap_err().transparent()); +} diff --git a/src/common/function/Cargo.toml b/src/common/function/Cargo.toml index 7a4c968a3e..73821a896a 100644 --- a/src/common/function/Cargo.toml +++ b/src/common/function/Cargo.toml @@ -18,6 +18,7 @@ api.workspace = true arc-swap = "1.0" async-trait.workspace = true bincode = "1.3" +catalog.workspace = true chrono.workspace = true common-base.workspace = true common-catalog.workspace = true diff --git a/src/common/function/src/admin/migrate_region.rs b/src/common/function/src/admin/migrate_region.rs index 0a487973d3..b1f79c0c07 100644 --- a/src/common/function/src/admin/migrate_region.rs +++ b/src/common/function/src/admin/migrate_region.rs @@ -25,12 +25,13 @@ use session::context::QueryContextRef; use crate::handlers::ProcedureServiceHandlerRef; use crate::helper::cast_u64; -const DEFAULT_TIMEOUT_SECS: u64 = 30; +/// The default timeout for migrate region procedure. +const DEFAULT_TIMEOUT_SECS: u64 = 300; /// A function to migrate a region from source peer to target peer. /// Returns the submitted procedure id if success. Only available in cluster mode. /// -/// - `migrate_region(region_id, from_peer, to_peer)`, with timeout(30 seconds). +/// - `migrate_region(region_id, from_peer, to_peer)`, with timeout(300 seconds). /// - `migrate_region(region_id, from_peer, to_peer, timeout(secs))`. /// /// The parameters: diff --git a/src/common/function/src/handlers.rs b/src/common/function/src/handlers.rs index 1d994731d5..bcb6ce5460 100644 --- a/src/common/function/src/handlers.rs +++ b/src/common/function/src/handlers.rs @@ -15,6 +15,7 @@ use std::sync::Arc; use async_trait::async_trait; +use catalog::CatalogManagerRef; use common_base::AffectedRows; use common_meta::rpc::procedure::{ AddRegionFollowerRequest, MigrateRegionRequest, ProcedureStateResponse, @@ -72,6 +73,9 @@ pub trait ProcedureServiceHandler: Send + Sync { /// Remove a region follower from a region. async fn remove_region_follower(&self, request: RemoveRegionFollowerRequest) -> Result<()>; + + /// Get the catalog manager + fn catalog_manager(&self) -> &CatalogManagerRef; } /// This flow service handler is only use for flush flow for now. diff --git a/src/common/function/src/scalars/matches_term.rs b/src/common/function/src/scalars/matches_term.rs index c99c5ca572..54cf556e85 100644 --- a/src/common/function/src/scalars/matches_term.rs +++ b/src/common/function/src/scalars/matches_term.rs @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt; +use std::iter::repeat_n; use std::sync::Arc; -use std::{fmt, iter}; use common_query::error::{InvalidFuncArgsSnafu, Result}; use common_query::prelude::Volatility; @@ -126,9 +127,10 @@ impl Function for MatchesTermFunction { let term = term_column.get_ref(0).as_string().unwrap(); match term { None => { - return Ok(Arc::new(BooleanVector::from_iter( - iter::repeat(None).take(text_column.len()), - ))); + return Ok(Arc::new(BooleanVector::from_iter(repeat_n( + None, + text_column.len(), + )))); } Some(term) => Some(MatchesTermFinder::new(term)), } @@ -217,7 +219,7 @@ impl MatchesTermFinder { } let mut pos = 0; - while let Some(found_pos) = self.finder.find(text[pos..].as_bytes()) { + while let Some(found_pos) = self.finder.find(&text.as_bytes()[pos..]) { let actual_pos = pos + found_pos; let prev_ok = self.starts_with_non_alnum diff --git a/src/common/function/src/scalars/uddsketch_calc.rs b/src/common/function/src/scalars/uddsketch_calc.rs index 5c0beb4fec..f429766eb7 100644 --- a/src/common/function/src/scalars/uddsketch_calc.rs +++ b/src/common/function/src/scalars/uddsketch_calc.rs @@ -115,6 +115,13 @@ impl Function for UddSketchCalcFunction { } }; + // Check if the sketch is empty, if so, return null + // This is important to avoid panics when calling estimate_quantile on an empty sketch + // In practice, this will happen if input is all null + if sketch.bucket_iter().count() == 0 { + builder.push_null(); + continue; + } // Compute the estimated quantile from the sketch let result = sketch.estimate_quantile(perc); builder.push(Some(result)); diff --git a/src/common/function/src/state.rs b/src/common/function/src/state.rs index 66f5463fa2..211f7e1438 100644 --- a/src/common/function/src/state.rs +++ b/src/common/function/src/state.rs @@ -34,6 +34,7 @@ impl FunctionState { use api::v1::meta::ProcedureStatus; use async_trait::async_trait; + use catalog::CatalogManagerRef; use common_base::AffectedRows; use common_meta::rpc::procedure::{ AddRegionFollowerRequest, MigrateRegionRequest, ProcedureStateResponse, @@ -80,6 +81,10 @@ impl FunctionState { ) -> Result<()> { Ok(()) } + + fn catalog_manager(&self) -> &CatalogManagerRef { + unimplemented!() + } } #[async_trait] diff --git a/src/common/grpc/Cargo.toml b/src/common/grpc/Cargo.toml index d20e751e41..f15d0761d1 100644 --- a/src/common/grpc/Cargo.toml +++ b/src/common/grpc/Cargo.toml @@ -23,8 +23,11 @@ flatbuffers = "24" hyper.workspace = true lazy_static.workspace = true prost.workspace = true +serde.workspace = true +serde_json.workspace = true snafu.workspace = true tokio.workspace = true +tokio-util.workspace = true tonic.workspace = true tower.workspace = true diff --git a/src/common/grpc/src/channel_manager.rs b/src/common/grpc/src/channel_manager.rs index 0127829567..713ad58d81 100644 --- a/src/common/grpc/src/channel_manager.rs +++ b/src/common/grpc/src/channel_manager.rs @@ -22,6 +22,7 @@ use dashmap::mapref::entry::Entry; use dashmap::DashMap; use lazy_static::lazy_static; use snafu::{OptionExt, ResultExt}; +use tokio_util::sync::CancellationToken; use tonic::transport::{ Certificate, Channel as InnerChannel, ClientTlsConfig, Endpoint, Identity, Uri, }; @@ -39,18 +40,48 @@ lazy_static! { static ref ID: AtomicU64 = AtomicU64::new(0); } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct ChannelManager { + inner: Arc, +} + +#[derive(Debug)] +struct Inner { id: u64, config: ChannelConfig, client_tls_config: Option, pool: Arc, - channel_recycle_started: Arc, + channel_recycle_started: AtomicBool, + cancel: CancellationToken, } -impl Default for ChannelManager { +impl Default for Inner { fn default() -> Self { - ChannelManager::with_config(ChannelConfig::default()) + Self::with_config(ChannelConfig::default()) + } +} + +impl Drop for Inner { + fn drop(&mut self) { + // Cancel the channel recycle task. + self.cancel.cancel(); + } +} + +impl Inner { + fn with_config(config: ChannelConfig) -> Self { + let id = ID.fetch_add(1, Ordering::Relaxed); + let pool = Arc::new(Pool::default()); + let cancel = CancellationToken::new(); + + Self { + id, + config, + client_tls_config: None, + pool, + channel_recycle_started: AtomicBool::new(false), + cancel, + } } } @@ -60,19 +91,14 @@ impl ChannelManager { } pub fn with_config(config: ChannelConfig) -> Self { - let id = ID.fetch_add(1, Ordering::Relaxed); - let pool = Arc::new(Pool::default()); + let inner = Inner::with_config(config); Self { - id, - config, - client_tls_config: None, - pool, - channel_recycle_started: Arc::new(AtomicBool::new(false)), + inner: Arc::new(inner), } } pub fn with_tls_config(config: ChannelConfig) -> Result { - let mut cm = Self::with_config(config.clone()); + let mut inner = Inner::with_config(config.clone()); // setup tls let path_config = config.client_tls.context(InvalidTlsConfigSnafu { @@ -88,17 +114,23 @@ impl ChannelManager { .context(InvalidConfigFilePathSnafu)?; let client_identity = Identity::from_pem(client_cert, client_key); - cm.client_tls_config = Some( + inner.client_tls_config = Some( ClientTlsConfig::new() .ca_certificate(server_root_ca_cert) .identity(client_identity), ); - Ok(cm) + Ok(Self { + inner: Arc::new(inner), + }) } pub fn config(&self) -> &ChannelConfig { - &self.config + &self.inner.config + } + + fn pool(&self) -> &Arc { + &self.inner.pool } pub fn get(&self, addr: impl AsRef) -> Result { @@ -106,12 +138,12 @@ impl ChannelManager { let addr = addr.as_ref(); // It will acquire the read lock. - if let Some(inner_ch) = self.pool.get(addr) { + if let Some(inner_ch) = self.pool().get(addr) { return Ok(inner_ch); } // It will acquire the write lock. - let entry = match self.pool.entry(addr.to_string()) { + let entry = match self.pool().entry(addr.to_string()) { Entry::Occupied(entry) => { entry.get().increase_access(); entry.into_ref() @@ -150,7 +182,7 @@ impl ChannelManager { access: AtomicUsize::new(1), use_default_connector: false, }; - self.pool.put(addr, channel); + self.pool().put(addr, channel); Ok(inner_channel) } @@ -159,11 +191,11 @@ impl ChannelManager { where F: FnMut(&String, &mut Channel) -> bool, { - self.pool.retain_channel(f); + self.pool().retain_channel(f); } fn build_endpoint(&self, addr: &str) -> Result { - let http_prefix = if self.client_tls_config.is_some() { + let http_prefix = if self.inner.client_tls_config.is_some() { "https" } else { "http" @@ -172,51 +204,52 @@ impl ChannelManager { let mut endpoint = Endpoint::new(format!("{http_prefix}://{addr}")).context(CreateChannelSnafu)?; - if let Some(dur) = self.config.timeout { + if let Some(dur) = self.config().timeout { endpoint = endpoint.timeout(dur); } - if let Some(dur) = self.config.connect_timeout { + if let Some(dur) = self.config().connect_timeout { endpoint = endpoint.connect_timeout(dur); } - if let Some(limit) = self.config.concurrency_limit { + if let Some(limit) = self.config().concurrency_limit { endpoint = endpoint.concurrency_limit(limit); } - if let Some((limit, dur)) = self.config.rate_limit { + if let Some((limit, dur)) = self.config().rate_limit { endpoint = endpoint.rate_limit(limit, dur); } - if let Some(size) = self.config.initial_stream_window_size { + if let Some(size) = self.config().initial_stream_window_size { endpoint = endpoint.initial_stream_window_size(size); } - if let Some(size) = self.config.initial_connection_window_size { + if let Some(size) = self.config().initial_connection_window_size { endpoint = endpoint.initial_connection_window_size(size); } - if let Some(dur) = self.config.http2_keep_alive_interval { + if let Some(dur) = self.config().http2_keep_alive_interval { endpoint = endpoint.http2_keep_alive_interval(dur); } - if let Some(dur) = self.config.http2_keep_alive_timeout { + if let Some(dur) = self.config().http2_keep_alive_timeout { endpoint = endpoint.keep_alive_timeout(dur); } - if let Some(enabled) = self.config.http2_keep_alive_while_idle { + if let Some(enabled) = self.config().http2_keep_alive_while_idle { endpoint = endpoint.keep_alive_while_idle(enabled); } - if let Some(enabled) = self.config.http2_adaptive_window { + if let Some(enabled) = self.config().http2_adaptive_window { endpoint = endpoint.http2_adaptive_window(enabled); } - if let Some(tls_config) = &self.client_tls_config { + if let Some(tls_config) = &self.inner.client_tls_config { endpoint = endpoint .tls_config(tls_config.clone()) .context(CreateChannelSnafu)?; } endpoint = endpoint - .tcp_keepalive(self.config.tcp_keepalive) - .tcp_nodelay(self.config.tcp_nodelay); + .tcp_keepalive(self.config().tcp_keepalive) + .tcp_nodelay(self.config().tcp_nodelay); Ok(endpoint) } fn trigger_channel_recycling(&self) { if self + .inner .channel_recycle_started .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) .is_err() @@ -224,13 +257,15 @@ impl ChannelManager { return; } - let pool = self.pool.clone(); - let _handle = common_runtime::spawn_global(async { - recycle_channel_in_loop(pool, RECYCLE_CHANNEL_INTERVAL_SECS).await; + let pool = self.pool().clone(); + let cancel = self.inner.cancel.clone(); + let id = self.inner.id; + let _handle = common_runtime::spawn_global(async move { + recycle_channel_in_loop(pool, id, cancel, RECYCLE_CHANNEL_INTERVAL_SECS).await; }); info!( "ChannelManager: {}, channel recycle is started, running in the background!", - self.id + self.inner.id ); } } @@ -443,11 +478,23 @@ impl Pool { } } -async fn recycle_channel_in_loop(pool: Arc, interval_secs: u64) { +async fn recycle_channel_in_loop( + pool: Arc, + id: u64, + cancel: CancellationToken, + interval_secs: u64, +) { let mut interval = tokio::time::interval(Duration::from_secs(interval_secs)); loop { - let _ = interval.tick().await; + tokio::select! { + _ = cancel.cancelled() => { + info!("Stop channel recycle, ChannelManager id: {}", id); + break; + }, + _ = interval.tick() => {} + } + pool.retain_channel(|_, c| c.access.swap(0, Ordering::Relaxed) != 0) } } @@ -461,11 +508,7 @@ mod tests { #[should_panic] #[test] fn test_invalid_addr() { - let pool = Arc::new(Pool::default()); - let mgr = ChannelManager { - pool, - ..Default::default() - }; + let mgr = ChannelManager::default(); let addr = "http://test"; let _ = mgr.get(addr).unwrap(); @@ -475,7 +518,9 @@ mod tests { async fn test_access_count() { let mgr = ChannelManager::new(); // Do not start recycle - mgr.channel_recycle_started.store(true, Ordering::Relaxed); + mgr.inner + .channel_recycle_started + .store(true, Ordering::Relaxed); let mgr = Arc::new(mgr); let addr = "test_uri"; @@ -493,12 +538,12 @@ mod tests { join.await.unwrap(); } - assert_eq!(1000, mgr.pool.get_access(addr).unwrap()); + assert_eq!(1000, mgr.pool().get_access(addr).unwrap()); - mgr.pool + mgr.pool() .retain_channel(|_, c| c.access.swap(0, Ordering::Relaxed) != 0); - assert_eq!(0, mgr.pool.get_access(addr).unwrap()); + assert_eq!(0, mgr.pool().get_access(addr).unwrap()); } #[test] @@ -624,4 +669,49 @@ mod tests { true }); } + + #[tokio::test] + async fn test_pool_release_with_channel_recycle() { + let mgr = ChannelManager::new(); + + let pool_holder = mgr.pool().clone(); + + // start channel recycle task + let addr = "test_addr"; + let _ = mgr.get(addr); + + let mgr_clone_1 = mgr.clone(); + let mgr_clone_2 = mgr.clone(); + assert_eq!(3, Arc::strong_count(mgr.pool())); + + drop(mgr_clone_1); + drop(mgr_clone_2); + assert_eq!(3, Arc::strong_count(mgr.pool())); + + drop(mgr); + + // wait for the channel recycle task to finish + tokio::time::sleep(Duration::from_millis(10)).await; + + assert_eq!(1, Arc::strong_count(&pool_holder)); + } + + #[tokio::test] + async fn test_pool_release_without_channel_recycle() { + let mgr = ChannelManager::new(); + + let pool_holder = mgr.pool().clone(); + + let mgr_clone_1 = mgr.clone(); + let mgr_clone_2 = mgr.clone(); + assert_eq!(2, Arc::strong_count(mgr.pool())); + + drop(mgr_clone_1); + drop(mgr_clone_2); + assert_eq!(2, Arc::strong_count(mgr.pool())); + + drop(mgr); + + assert_eq!(1, Arc::strong_count(&pool_holder)); + } } diff --git a/src/common/grpc/src/error.rs b/src/common/grpc/src/error.rs index d0ca7d970c..af194f2501 100644 --- a/src/common/grpc/src/error.rs +++ b/src/common/grpc/src/error.rs @@ -97,6 +97,14 @@ pub enum Error { #[snafu(display("Not supported: {}", feat))] NotSupported { feat: String }, + + #[snafu(display("Failed to serde Json"))] + SerdeJson { + #[snafu(source)] + error: serde_json::error::Error, + #[snafu(implicit)] + location: Location, + }, } impl ErrorExt for Error { @@ -110,7 +118,8 @@ impl ErrorExt for Error { Error::CreateChannel { .. } | Error::Conversion { .. } - | Error::DecodeFlightData { .. } => StatusCode::Internal, + | Error::DecodeFlightData { .. } + | Error::SerdeJson { .. } => StatusCode::Internal, Error::CreateRecordBatch { source, .. } => source.status_code(), Error::ConvertArrowSchema { source, .. } => source.status_code(), diff --git a/src/common/grpc/src/flight.rs b/src/common/grpc/src/flight.rs index 26f3676ce1..872897ccbf 100644 --- a/src/common/grpc/src/flight.rs +++ b/src/common/grpc/src/flight.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod do_put; + use std::collections::HashMap; use std::sync::Arc; diff --git a/src/common/grpc/src/flight/do_put.rs b/src/common/grpc/src/flight/do_put.rs new file mode 100644 index 0000000000..15011fc74b --- /dev/null +++ b/src/common/grpc/src/flight/do_put.rs @@ -0,0 +1,93 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use arrow_flight::PutResult; +use common_base::AffectedRows; +use serde::{Deserialize, Serialize}; +use snafu::ResultExt; + +use crate::error::{Error, SerdeJsonSnafu}; + +/// The metadata for "DoPut" requests and responses. +/// +/// Currently, there's only a "request_id", for coordinating requests and responses in the streams. +/// Client can set a unique request id in this metadata, and the server will return the same id in +/// the corresponding response. In doing so, a client can know how to do with its pending requests. +#[derive(Serialize, Deserialize)] +pub struct DoPutMetadata { + request_id: i64, +} + +impl DoPutMetadata { + pub fn new(request_id: i64) -> Self { + Self { request_id } + } + + pub fn request_id(&self) -> i64 { + self.request_id + } +} + +/// The response in the "DoPut" returned stream. +#[derive(Serialize, Deserialize)] +pub struct DoPutResponse { + /// The same "request_id" in the request; see the [DoPutMetadata]. + request_id: i64, + /// The successfully ingested rows number. + affected_rows: AffectedRows, +} + +impl DoPutResponse { + pub fn new(request_id: i64, affected_rows: AffectedRows) -> Self { + Self { + request_id, + affected_rows, + } + } + + pub fn request_id(&self) -> i64 { + self.request_id + } + + pub fn affected_rows(&self) -> AffectedRows { + self.affected_rows + } +} + +impl TryFrom for DoPutResponse { + type Error = Error; + + fn try_from(value: PutResult) -> Result { + serde_json::from_slice(&value.app_metadata).context(SerdeJsonSnafu) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serde_do_put_metadata() { + let serialized = r#"{"request_id":42}"#; + let metadata = serde_json::from_str::(serialized).unwrap(); + assert_eq!(metadata.request_id(), 42); + } + + #[test] + fn test_serde_do_put_response() { + let x = DoPutResponse::new(42, 88); + let serialized = serde_json::to_string(&x).unwrap(); + assert_eq!(serialized, r#"{"request_id":42,"affected_rows":88}"#); + } +} diff --git a/src/common/macro/src/stack_trace_debug.rs b/src/common/macro/src/stack_trace_debug.rs index fbc24260f1..f82f4746d3 100644 --- a/src/common/macro/src/stack_trace_debug.rs +++ b/src/common/macro/src/stack_trace_debug.rs @@ -14,7 +14,7 @@ //! implement `::common_error::ext::StackError` -use proc_macro2::{Span, TokenStream as TokenStream2}; +use proc_macro2::{Literal, Span, TokenStream as TokenStream2, TokenTree}; use quote::{quote, quote_spanned}; use syn::spanned::Spanned; use syn::{parenthesized, Attribute, Ident, ItemEnum, Variant}; @@ -32,6 +32,7 @@ pub fn stack_trace_style_impl(args: TokenStream2, input: TokenStream2) -> TokenS variants.push(variant); } + let transparent_fn = build_transparent_fn(enum_name.clone(), &variants); let debug_fmt_fn = build_debug_fmt_impl(enum_name.clone(), variants.clone()); let next_fn = build_next_impl(enum_name.clone(), variants); let debug_impl = build_debug_impl(enum_name.clone()); @@ -43,6 +44,7 @@ pub fn stack_trace_style_impl(args: TokenStream2, input: TokenStream2) -> TokenS impl ::common_error::ext::StackError for #enum_name { #debug_fmt_fn #next_fn + #transparent_fn } #debug_impl @@ -115,6 +117,7 @@ struct ErrorVariant { has_source: bool, has_external_cause: bool, display: TokenStream2, + transparent: bool, span: Span, cfg_attr: Option, } @@ -140,6 +143,7 @@ impl ErrorVariant { } let mut display = None; + let mut transparent = false; let mut cfg_attr = None; for attr in variant.attrs { if attr.path().is_ident("snafu") { @@ -150,17 +154,29 @@ impl ErrorVariant { let display_ts: TokenStream2 = content.parse()?; display = Some(display_ts); Ok(()) + } else if meta.path.is_ident("transparent") { + display = Some(TokenStream2::from(TokenTree::Literal(Literal::string( + "", + )))); + transparent = true; + Ok(()) } else { Err(meta.error("unrecognized repr")) } }) - .expect("Each error should contains a display attribute"); + .unwrap_or_else(|e| panic!("{e}")); } if attr.path().is_ident("cfg") { cfg_attr = Some(attr); } } + let display = display.unwrap_or_else(|| { + panic!( + r#"Error "{}" must be annotated with attribute "display" or "transparent"."#, + variant.ident, + ) + }); let field_ident = variant .fields @@ -174,7 +190,8 @@ impl ErrorVariant { has_location, has_source, has_external_cause, - display: display.unwrap(), + display, + transparent, span, cfg_attr, } @@ -275,4 +292,44 @@ impl ErrorVariant { } } } + + fn build_transparent_match_arm(&self) -> TokenStream2 { + let cfg = if let Some(cfg) = &self.cfg_attr { + quote_spanned!(cfg.span() => #cfg) + } else { + quote! {} + }; + let name = &self.name; + let fields = &self.fields; + + if self.transparent { + quote_spanned! { + self.span => #cfg #[allow(unused_variables)] #name { #(#fields),* } => { + true + }, + } + } else { + quote_spanned! { + self.span => #cfg #[allow(unused_variables)] #name { #(#fields),* } =>{ + false + } + } + } + } +} + +fn build_transparent_fn(enum_name: Ident, variants: &[ErrorVariant]) -> TokenStream2 { + let match_arms = variants + .iter() + .map(|v| v.build_transparent_match_arm()) + .collect::>(); + + quote! { + fn transparent(&self) -> bool { + use #enum_name::*; + match self { + #(#match_arms)* + } + } + } } diff --git a/src/common/meta/src/cache/flow/table_flownode.rs b/src/common/meta/src/cache/flow/table_flownode.rs index c9eb883b76..b285088822 100644 --- a/src/common/meta/src/cache/flow/table_flownode.rs +++ b/src/common/meta/src/cache/flow/table_flownode.rs @@ -187,6 +187,7 @@ mod tests { }, flownode_ids: BTreeMap::from([(0, 1), (1, 2), (2, 3)]), catalog_name: DEFAULT_CATALOG_NAME.to_string(), + query_context: None, flow_name: "my_flow".to_string(), raw_sql: "sql".to_string(), expire_after: Some(300), diff --git a/src/common/meta/src/datanode.rs b/src/common/meta/src/datanode.rs index ed1957cbd7..499ed865a2 100644 --- a/src/common/meta/src/datanode.rs +++ b/src/common/meta/src/datanode.rs @@ -94,6 +94,13 @@ pub struct RegionStat { pub index_size: u64, /// The manifest infoof the region. pub region_manifest: RegionManifestInfo, + /// The latest entry id of topic used by data. + /// **Only used by remote WAL prune.** + pub data_topic_latest_entry_id: u64, + /// The latest entry id of topic used by metadata. + /// **Only used by remote WAL prune.** + /// In mito engine, this is the same as `data_topic_latest_entry_id`. + pub metadata_topic_latest_entry_id: u64, } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -142,6 +149,43 @@ impl Stat { self.wcus = self.region_stats.iter().map(|s| s.wcus).sum(); self.region_num = self.region_stats.len() as u64; } + + pub fn memory_size(&self) -> usize { + // timestamp_millis, rcus, wcus + std::mem::size_of::() * 3 + + // id, region_num, node_epoch + std::mem::size_of::() * 3 + + // addr + std::mem::size_of::() + self.addr.capacity() + + // region_stats + self.region_stats.iter().map(|s| s.memory_size()).sum::() + } +} + +impl RegionStat { + pub fn memory_size(&self) -> usize { + // role + std::mem::size_of::() + + // id + std::mem::size_of::() + + // rcus, wcus, approximate_bytes, num_rows + std::mem::size_of::() * 4 + + // memtable_size, manifest_size, sst_size, index_size + std::mem::size_of::() * 4 + + // engine + std::mem::size_of::() + self.engine.capacity() + + // region_manifest + self.region_manifest.memory_size() + } +} + +impl RegionManifestInfo { + pub fn memory_size(&self) -> usize { + match self { + RegionManifestInfo::Mito { .. } => std::mem::size_of::() * 2, + RegionManifestInfo::Metric { .. } => std::mem::size_of::() * 4, + } + } } impl TryFrom<&HeartbeatRequest> for Stat { @@ -227,6 +271,8 @@ impl From<&api::v1::meta::RegionStat> for RegionStat { sst_size: region_stat.sst_size, index_size: region_stat.index_size, region_manifest: region_stat.manifest.into(), + data_topic_latest_entry_id: region_stat.data_topic_latest_entry_id, + metadata_topic_latest_entry_id: region_stat.metadata_topic_latest_entry_id, } } } diff --git a/src/common/meta/src/ddl/alter_logical_tables.rs b/src/common/meta/src/ddl/alter_logical_tables.rs index ea741accf3..74706a8ddb 100644 --- a/src/common/meta/src/ddl/alter_logical_tables.rs +++ b/src/common/meta/src/ddl/alter_logical_tables.rs @@ -18,10 +18,12 @@ mod region_request; mod table_cache_keys; mod update_metadata; +use api::region::RegionResponse; use async_trait::async_trait; +use common_catalog::format_full_table_name; use common_procedure::error::{FromJsonSnafu, Result as ProcedureResult, ToJsonSnafu}; use common_procedure::{Context, LockKey, Procedure, Status}; -use common_telemetry::{info, warn}; +use common_telemetry::{error, info, warn}; use futures_util::future; use serde::{Deserialize, Serialize}; use snafu::{ensure, ResultExt}; @@ -30,7 +32,7 @@ use store_api::metric_engine_consts::ALTER_PHYSICAL_EXTENSION_KEY; use strum::AsRefStr; use table::metadata::TableId; -use crate::ddl::utils::add_peer_context_if_needed; +use crate::ddl::utils::{add_peer_context_if_needed, sync_follower_regions}; use crate::ddl::DdlContext; use crate::error::{DecodeJsonSnafu, Error, MetadataCorruptionSnafu, Result}; use crate::key::table_info::TableInfoValue; @@ -39,7 +41,7 @@ use crate::key::DeserializedValueWithBytes; use crate::lock_key::{CatalogLock, SchemaLock, TableLock}; use crate::metrics; use crate::rpc::ddl::AlterTableTask; -use crate::rpc::router::find_leaders; +use crate::rpc::router::{find_leaders, RegionRoute}; pub struct AlterLogicalTablesProcedure { pub context: DdlContext, @@ -125,14 +127,20 @@ impl AlterLogicalTablesProcedure { }); } - // Collects responses from datanodes. - let phy_raw_schemas = future::join_all(alter_region_tasks) + let mut results = future::join_all(alter_region_tasks) .await .into_iter() - .map(|res| res.map(|mut res| res.extensions.remove(ALTER_PHYSICAL_EXTENSION_KEY))) .collect::>>()?; + // Collects responses from datanodes. + let phy_raw_schemas = results + .iter_mut() + .map(|res| res.extensions.remove(ALTER_PHYSICAL_EXTENSION_KEY)) + .collect::>(); + if phy_raw_schemas.is_empty() { + self.submit_sync_region_requests(results, &physical_table_route.region_routes) + .await; self.data.state = AlterTablesState::UpdateMetadata; return Ok(Status::executing(true)); } @@ -155,10 +163,34 @@ impl AlterLogicalTablesProcedure { warn!("altering logical table result doesn't contains extension key `{ALTER_PHYSICAL_EXTENSION_KEY}`,leaving the physical table's schema unchanged"); } + self.submit_sync_region_requests(results, &physical_table_route.region_routes) + .await; self.data.state = AlterTablesState::UpdateMetadata; Ok(Status::executing(true)) } + async fn submit_sync_region_requests( + &self, + results: Vec, + region_routes: &[RegionRoute], + ) { + let table_info = &self.data.physical_table_info.as_ref().unwrap().table_info; + if let Err(err) = sync_follower_regions( + &self.context, + self.data.physical_table_id, + results, + region_routes, + table_info.meta.engine.as_str(), + ) + .await + { + error!(err; "Failed to sync regions for table {}, table_id: {}", + format_full_table_name(&table_info.catalog_name, &table_info.schema_name, &table_info.name), + self.data.physical_table_id + ); + } + } + pub(crate) async fn on_update_metadata(&mut self) -> Result { self.update_physical_table_metadata().await?; self.update_logical_tables_metadata().await?; diff --git a/src/common/meta/src/ddl/alter_table.rs b/src/common/meta/src/ddl/alter_table.rs index 89406e6a96..ee7b15509c 100644 --- a/src/common/meta/src/ddl/alter_table.rs +++ b/src/common/meta/src/ddl/alter_table.rs @@ -19,6 +19,7 @@ mod update_metadata; use std::vec; +use api::region::RegionResponse; use api::v1::alter_table_expr::Kind; use api::v1::RenameTable; use async_trait::async_trait; @@ -29,7 +30,7 @@ use common_procedure::{ PoisonKeys, Procedure, ProcedureId, Status, StringKey, }; use common_telemetry::{debug, error, info}; -use futures::future; +use futures::future::{self}; use serde::{Deserialize, Serialize}; use snafu::{ensure, ResultExt}; use store_api::storage::RegionId; @@ -38,7 +39,9 @@ use table::metadata::{RawTableInfo, TableId, TableInfo}; use table::table_reference::TableReference; use crate::cache_invalidator::Context; -use crate::ddl::utils::{add_peer_context_if_needed, handle_multiple_results, MultipleResults}; +use crate::ddl::utils::{ + add_peer_context_if_needed, handle_multiple_results, sync_follower_regions, MultipleResults, +}; use crate::ddl::DdlContext; use crate::error::{AbortProcedureSnafu, Error, NoLeaderSnafu, PutPoisonSnafu, Result}; use crate::instruction::CacheIdent; @@ -48,7 +51,7 @@ use crate::lock_key::{CatalogLock, SchemaLock, TableLock, TableNameLock}; use crate::metrics; use crate::poison_key::table_poison_key; use crate::rpc::ddl::AlterTableTask; -use crate::rpc::router::{find_leader_regions, find_leaders, region_distribution}; +use crate::rpc::router::{find_leader_regions, find_leaders, region_distribution, RegionRoute}; /// The alter table procedure pub struct AlterTableProcedure { @@ -194,7 +197,9 @@ impl AlterTableProcedure { // Just returns the error, and wait for the next try. Err(error) } - MultipleResults::Ok => { + MultipleResults::Ok(results) => { + self.submit_sync_region_requests(results, &physical_table_route.region_routes) + .await; self.data.state = AlterTableState::UpdateMetadata; Ok(Status::executing_with_clean_poisons(true)) } @@ -211,6 +216,26 @@ impl AlterTableProcedure { } } + async fn submit_sync_region_requests( + &mut self, + results: Vec, + region_routes: &[RegionRoute], + ) { + // Safety: filled in `prepare` step. + let table_info = self.data.table_info().unwrap(); + if let Err(err) = sync_follower_regions( + &self.context, + self.data.table_id(), + results, + region_routes, + table_info.meta.engine.as_str(), + ) + .await + { + error!(err; "Failed to sync regions for table {}, table_id: {}", self.data.table_ref(), self.data.table_id()); + } + } + /// Update table metadata. pub(crate) async fn on_update_metadata(&mut self) -> Result { let table_id = self.data.table_id(); diff --git a/src/common/meta/src/ddl/create_flow.rs b/src/common/meta/src/ddl/create_flow.rs index 4e7d661c1d..278a3a6c9e 100644 --- a/src/common/meta/src/ddl/create_flow.rs +++ b/src/common/meta/src/ddl/create_flow.rs @@ -38,7 +38,7 @@ use table::metadata::TableId; use crate::cache_invalidator::Context; use crate::ddl::utils::{add_peer_context_if_needed, handle_retry_error}; use crate::ddl::DdlContext; -use crate::error::{self, Result}; +use crate::error::{self, Result, UnexpectedSnafu}; use crate::instruction::{CacheIdent, CreateFlow}; use crate::key::flow::flow_info::FlowInfoValue; use crate::key::flow::flow_route::FlowRouteValue; @@ -171,7 +171,7 @@ impl CreateFlowProcedure { } self.data.state = CreateFlowState::CreateFlows; // determine flow type - self.data.flow_type = Some(determine_flow_type(&self.data.task)); + self.data.flow_type = Some(get_flow_type_from_options(&self.data.task)?); Ok(Status::executing(true)) } @@ -196,8 +196,8 @@ impl CreateFlowProcedure { }); } info!( - "Creating flow({:?}) on flownodes with peers={:?}", - self.data.flow_id, self.data.peers + "Creating flow({:?}, type={:?}) on flownodes with peers={:?}", + self.data.flow_id, self.data.flow_type, self.data.peers ); join_all(create_flow) .await @@ -306,8 +306,20 @@ impl Procedure for CreateFlowProcedure { } } -pub fn determine_flow_type(_flow_task: &CreateFlowTask) -> FlowType { - FlowType::Batching +pub fn get_flow_type_from_options(flow_task: &CreateFlowTask) -> Result { + let flow_type = flow_task + .flow_options + .get(FlowType::FLOW_TYPE_KEY) + .map(|s| s.as_str()); + match flow_type { + Some(FlowType::BATCHING) => Ok(FlowType::Batching), + Some(FlowType::STREAMING) => Ok(FlowType::Streaming), + Some(unknown) => UnexpectedSnafu { + err_msg: format!("Unknown flow type: {}", unknown), + } + .fail(), + None => Ok(FlowType::Batching), + } } /// The state of [CreateFlowProcedure]. @@ -324,7 +336,7 @@ pub enum CreateFlowState { } /// The type of flow. -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum FlowType { /// The flow is a batching task. Batching, @@ -437,6 +449,7 @@ impl From<&CreateFlowData> for (FlowInfoValue, Vec<(FlowPartitionId, FlowRouteVa sink_table_name, flownode_ids, catalog_name, + query_context: Some(value.query_context.clone()), flow_name, raw_sql: sql, expire_after, diff --git a/src/common/meta/src/ddl/create_logical_tables.rs b/src/common/meta/src/ddl/create_logical_tables.rs index 59882ec491..628f17d398 100644 --- a/src/common/meta/src/ddl/create_logical_tables.rs +++ b/src/common/meta/src/ddl/create_logical_tables.rs @@ -17,12 +17,14 @@ mod metadata; mod region_request; mod update_metadata; +use api::region::RegionResponse; use api::v1::CreateTableExpr; use async_trait::async_trait; +use common_catalog::consts::METRIC_ENGINE; use common_procedure::error::{FromJsonSnafu, Result as ProcedureResult, ToJsonSnafu}; use common_procedure::{Context as ProcedureContext, LockKey, Procedure, Status}; -use common_telemetry::{debug, warn}; -use futures_util::future::join_all; +use common_telemetry::{debug, error, warn}; +use futures::future; use serde::{Deserialize, Serialize}; use snafu::{ensure, ResultExt}; use store_api::metadata::ColumnMetadata; @@ -31,7 +33,7 @@ use store_api::storage::{RegionId, RegionNumber}; use strum::AsRefStr; use table::metadata::{RawTableInfo, TableId}; -use crate::ddl::utils::{add_peer_context_if_needed, handle_retry_error}; +use crate::ddl::utils::{add_peer_context_if_needed, handle_retry_error, sync_follower_regions}; use crate::ddl::DdlContext; use crate::error::{DecodeJsonSnafu, MetadataCorruptionSnafu, Result}; use crate::key::table_route::TableRouteValue; @@ -156,14 +158,20 @@ impl CreateLogicalTablesProcedure { }); } - // Collects response from datanodes. - let phy_raw_schemas = join_all(create_region_tasks) + let mut results = future::join_all(create_region_tasks) .await .into_iter() - .map(|res| res.map(|mut res| res.extensions.remove(ALTER_PHYSICAL_EXTENSION_KEY))) .collect::>>()?; + // Collects response from datanodes. + let phy_raw_schemas = results + .iter_mut() + .map(|res| res.extensions.remove(ALTER_PHYSICAL_EXTENSION_KEY)) + .collect::>(); + if phy_raw_schemas.is_empty() { + self.submit_sync_region_requests(results, region_routes) + .await; self.data.state = CreateTablesState::CreateMetadata; return Ok(Status::executing(false)); } @@ -186,10 +194,30 @@ impl CreateLogicalTablesProcedure { warn!("creating logical table result doesn't contains extension key `{ALTER_PHYSICAL_EXTENSION_KEY}`,leaving the physical table's schema unchanged"); } + self.submit_sync_region_requests(results, region_routes) + .await; self.data.state = CreateTablesState::CreateMetadata; Ok(Status::executing(true)) } + + async fn submit_sync_region_requests( + &self, + results: Vec, + region_routes: &[RegionRoute], + ) { + if let Err(err) = sync_follower_regions( + &self.context, + self.data.physical_table_id, + results, + region_routes, + METRIC_ENGINE, + ) + .await + { + error!(err; "Failed to sync regions for physical table_id: {}",self.data.physical_table_id); + } + } } #[async_trait] diff --git a/src/common/meta/src/ddl/drop_table/executor.rs b/src/common/meta/src/ddl/drop_table/executor.rs index 1204629a1e..7aae31b13a 100644 --- a/src/common/meta/src/ddl/drop_table/executor.rs +++ b/src/common/meta/src/ddl/drop_table/executor.rs @@ -15,12 +15,13 @@ use std::collections::HashMap; use api::v1::region::{ - region_request, DropRequest as PbDropRegionRequest, RegionRequest, RegionRequestHeader, + region_request, CloseRequest as PbCloseRegionRequest, DropRequest as PbDropRegionRequest, + RegionRequest, RegionRequestHeader, }; use common_error::ext::ErrorExt; use common_error::status_code::StatusCode; -use common_telemetry::debug; use common_telemetry::tracing_context::TracingContext; +use common_telemetry::{debug, error}; use common_wal::options::WalOptions; use futures::future::join_all; use snafu::ensure; @@ -36,7 +37,8 @@ use crate::instruction::CacheIdent; use crate::key::table_name::TableNameKey; use crate::key::table_route::TableRouteValue; use crate::rpc::router::{ - find_leader_regions, find_leaders, operating_leader_regions, RegionRoute, + find_follower_regions, find_followers, find_leader_regions, find_leaders, + operating_leader_regions, RegionRoute, }; /// [Control] indicated to the caller whether to go to the next step. @@ -210,10 +212,10 @@ impl DropTableExecutor { region_routes: &[RegionRoute], fast_path: bool, ) -> Result<()> { + // Drops leader regions on datanodes. let leaders = find_leaders(region_routes); let mut drop_region_tasks = Vec::with_capacity(leaders.len()); let table_id = self.table_id; - for datanode in leaders { let requester = ctx.node_manager.datanode(&datanode).await; let regions = find_leader_regions(region_routes, &datanode); @@ -252,6 +254,53 @@ impl DropTableExecutor { .into_iter() .collect::>>()?; + // Drops follower regions on datanodes. + let followers = find_followers(region_routes); + let mut close_region_tasks = Vec::with_capacity(followers.len()); + for datanode in followers { + let requester = ctx.node_manager.datanode(&datanode).await; + let regions = find_follower_regions(region_routes, &datanode); + let region_ids = regions + .iter() + .map(|region_number| RegionId::new(table_id, *region_number)) + .collect::>(); + + for region_id in region_ids { + debug!("Closing region {region_id} on Datanode {datanode:?}"); + let request = RegionRequest { + header: Some(RegionRequestHeader { + tracing_context: TracingContext::from_current_span().to_w3c(), + ..Default::default() + }), + body: Some(region_request::Body::Close(PbCloseRegionRequest { + region_id: region_id.as_u64(), + })), + }; + + let datanode = datanode.clone(); + let requester = requester.clone(); + close_region_tasks.push(async move { + if let Err(err) = requester.handle(request).await { + if err.status_code() != StatusCode::RegionNotFound { + return Err(add_peer_context_if_needed(datanode)(err)); + } + } + Ok(()) + }); + } + } + + // Failure to close follower regions is not critical. + // When a leader region is dropped, follower regions will be unable to renew their leases via metasrv. + // Eventually, these follower regions will be automatically closed by the region livekeeper. + if let Err(err) = join_all(close_region_tasks) + .await + .into_iter() + .collect::>>() + { + error!(err; "Failed to close follower regions on datanodes, table_id: {}", table_id); + } + // Deletes the leader region from registry. let region_ids = operating_leader_regions(region_routes); ctx.leader_region_registry diff --git a/src/common/meta/src/ddl/test_util/create_table.rs b/src/common/meta/src/ddl/test_util/create_table.rs index 12896fbf91..9d99bbf5c6 100644 --- a/src/common/meta/src/ddl/test_util/create_table.rs +++ b/src/common/meta/src/ddl/test_util/create_table.rs @@ -18,7 +18,9 @@ use api::v1::column_def::try_as_column_schema; use api::v1::meta::Partition; use api::v1::{ColumnDataType, ColumnDef, CreateTableExpr, SemanticType}; use chrono::DateTime; -use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, MITO2_ENGINE}; +use common_catalog::consts::{ + DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, MITO2_ENGINE, MITO_ENGINE, +}; use datatypes::schema::RawSchema; use derive_builder::Builder; use store_api::storage::TableId; @@ -164,6 +166,7 @@ pub fn test_create_table_task(name: &str, table_id: TableId) -> CreateTableTask .time_index("ts") .primary_keys(["host".into()]) .table_name(name) + .engine(MITO_ENGINE) .build() .unwrap() .into(); diff --git a/src/common/meta/src/ddl/test_util/datanode_handler.rs b/src/common/meta/src/ddl/test_util/datanode_handler.rs index 7f02d9cc5a..bed78724a5 100644 --- a/src/common/meta/src/ddl/test_util/datanode_handler.rs +++ b/src/common/meta/src/ddl/test_util/datanode_handler.rs @@ -45,14 +45,41 @@ impl MockDatanodeHandler for () { } #[derive(Clone)] -pub struct DatanodeWatcher(pub mpsc::Sender<(Peer, RegionRequest)>); +pub struct DatanodeWatcher { + sender: mpsc::Sender<(Peer, RegionRequest)>, + handler: Option Result>, +} + +impl DatanodeWatcher { + pub fn new(sender: mpsc::Sender<(Peer, RegionRequest)>) -> Self { + Self { + sender, + handler: None, + } + } + + pub fn with_handler( + mut self, + user_handler: fn(Peer, RegionRequest) -> Result, + ) -> Self { + self.handler = Some(user_handler); + self + } +} #[async_trait::async_trait] impl MockDatanodeHandler for DatanodeWatcher { async fn handle(&self, peer: &Peer, request: RegionRequest) -> Result { debug!("Returning Ok(0) for request: {request:?}, peer: {peer:?}"); - self.0.send((peer.clone(), request)).await.unwrap(); - Ok(RegionResponse::new(0)) + self.sender + .send((peer.clone(), request.clone())) + .await + .unwrap(); + if let Some(handler) = self.handler { + handler(peer.clone(), request) + } else { + Ok(RegionResponse::new(0)) + } } async fn handle_query( diff --git a/src/common/meta/src/ddl/tests/alter_logical_tables.rs b/src/common/meta/src/ddl/tests/alter_logical_tables.rs index 1c22cdf6f4..01ab8e513c 100644 --- a/src/common/meta/src/ddl/tests/alter_logical_tables.rs +++ b/src/common/meta/src/ddl/tests/alter_logical_tables.rs @@ -15,19 +15,33 @@ use std::assert_matches::assert_matches; use std::sync::Arc; +use api::region::RegionResponse; +use api::v1::meta::Peer; +use api::v1::region::sync_request::ManifestInfo; +use api::v1::region::{region_request, MetricManifestInfo, RegionRequest, SyncRequest}; use api::v1::{ColumnDataType, SemanticType}; use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; use common_procedure::{Procedure, ProcedureId, Status}; use common_procedure_test::MockContextProvider; +use store_api::metric_engine_consts::MANIFEST_INFO_EXTENSION_KEY; +use store_api::region_engine::RegionManifestInfo; +use store_api::storage::RegionId; +use tokio::sync::mpsc; use crate::ddl::alter_logical_tables::AlterLogicalTablesProcedure; use crate::ddl::test_util::alter_table::TestAlterTableExprBuilder; use crate::ddl::test_util::columns::TestColumnDefBuilder; -use crate::ddl::test_util::datanode_handler::NaiveDatanodeHandler; -use crate::ddl::test_util::{create_logical_table, create_physical_table}; +use crate::ddl::test_util::datanode_handler::{DatanodeWatcher, NaiveDatanodeHandler}; +use crate::ddl::test_util::{ + create_logical_table, create_physical_table, create_physical_table_metadata, + test_create_physical_table_task, +}; use crate::error::Error::{AlterLogicalTablesInvalidArguments, TableNotFound}; +use crate::error::Result; use crate::key::table_name::TableNameKey; +use crate::key::table_route::{PhysicalTableRouteValue, TableRouteValue}; use crate::rpc::ddl::AlterTableTask; +use crate::rpc::router::{Region, RegionRoute}; use crate::test_util::{new_ddl_context, MockDatanodeManager}; fn make_alter_logical_table_add_column_task( @@ -407,3 +421,78 @@ async fn test_on_part_duplicate_alter_request() { ] ); } + +fn alters_request_handler(_peer: Peer, request: RegionRequest) -> Result { + if let region_request::Body::Alters(_) = request.body.unwrap() { + let mut response = RegionResponse::new(0); + // Default region id for physical table. + let region_id = RegionId::new(1000, 1); + response.extensions.insert( + MANIFEST_INFO_EXTENSION_KEY.to_string(), + RegionManifestInfo::encode_list(&[(region_id, RegionManifestInfo::metric(1, 0, 2, 0))]) + .unwrap(), + ); + return Ok(response); + } + + Ok(RegionResponse::new(0)) +} + +#[tokio::test] +async fn test_on_submit_alter_region_request() { + common_telemetry::init_default_ut_logging(); + let (tx, mut rx) = mpsc::channel(8); + let handler = DatanodeWatcher::new(tx).with_handler(alters_request_handler); + let node_manager = Arc::new(MockDatanodeManager::new(handler)); + let ddl_context = new_ddl_context(node_manager); + + let mut create_physical_table_task = test_create_physical_table_task("phy"); + let phy_id = 1000u32; + let region_routes = vec![RegionRoute { + region: Region::new_test(RegionId::new(phy_id, 1)), + leader_peer: Some(Peer::empty(1)), + follower_peers: vec![Peer::empty(5)], + leader_state: None, + leader_down_since: None, + }]; + create_physical_table_task.set_table_id(phy_id); + create_physical_table_metadata( + &ddl_context, + create_physical_table_task.table_info.clone(), + TableRouteValue::Physical(PhysicalTableRouteValue::new(region_routes)), + ) + .await; + create_logical_table(ddl_context.clone(), phy_id, "table1").await; + create_logical_table(ddl_context.clone(), phy_id, "table2").await; + + let tasks = vec![ + make_alter_logical_table_add_column_task(None, "table1", vec!["new_col".to_string()]), + make_alter_logical_table_add_column_task(None, "table2", vec!["mew_col".to_string()]), + ]; + + let mut procedure = AlterLogicalTablesProcedure::new(tasks, phy_id, ddl_context); + procedure.on_prepare().await.unwrap(); + procedure.on_submit_alter_region_requests().await.unwrap(); + let mut results = Vec::new(); + for _ in 0..2 { + let result = rx.try_recv().unwrap(); + results.push(result); + } + rx.try_recv().unwrap_err(); + let (peer, request) = results.remove(0); + assert_eq!(peer.id, 1); + assert_matches!(request.body.unwrap(), region_request::Body::Alters(_)); + let (peer, request) = results.remove(0); + assert_eq!(peer.id, 5); + assert_matches!( + request.body.unwrap(), + region_request::Body::Sync(SyncRequest { + manifest_info: Some(ManifestInfo::MetricManifestInfo(MetricManifestInfo { + data_manifest_version: 1, + metadata_manifest_version: 2, + .. + })), + .. + }) + ); +} diff --git a/src/common/meta/src/ddl/tests/alter_table.rs b/src/common/meta/src/ddl/tests/alter_table.rs index c8d0450b90..6f24870e6e 100644 --- a/src/common/meta/src/ddl/tests/alter_table.rs +++ b/src/common/meta/src/ddl/tests/alter_table.rs @@ -16,7 +16,9 @@ use std::assert_matches::assert_matches; use std::collections::HashMap; use std::sync::Arc; +use api::region::RegionResponse; use api::v1::alter_table_expr::Kind; +use api::v1::region::sync_request::ManifestInfo; use api::v1::region::{region_request, RegionRequest}; use api::v1::{ AddColumn, AddColumns, AlterTableExpr, ColumnDataType, ColumnDef as PbColumnDef, DropColumn, @@ -28,6 +30,8 @@ use common_error::status_code::StatusCode; use common_procedure::store::poison_store::PoisonStore; use common_procedure::{ProcedureId, Status}; use common_procedure_test::MockContextProvider; +use store_api::metric_engine_consts::MANIFEST_INFO_EXTENSION_KEY; +use store_api::region_engine::RegionManifestInfo; use store_api::storage::RegionId; use table::requests::TTL_KEY; use tokio::sync::mpsc::{self}; @@ -39,7 +43,7 @@ use crate::ddl::test_util::datanode_handler::{ AllFailureDatanodeHandler, DatanodeWatcher, PartialSuccessDatanodeHandler, RequestOutdatedErrorDatanodeHandler, }; -use crate::error::Error; +use crate::error::{Error, Result}; use crate::key::datanode_table::DatanodeTableKey; use crate::key::table_name::TableNameKey; use crate::key::table_route::TableRouteValue; @@ -120,10 +124,71 @@ async fn test_on_prepare_table_not_exists_err() { assert_matches!(err.status_code(), StatusCode::TableNotFound); } +fn test_alter_table_task(table_name: &str) -> AlterTableTask { + AlterTableTask { + alter_table: AlterTableExpr { + catalog_name: DEFAULT_CATALOG_NAME.to_string(), + schema_name: DEFAULT_SCHEMA_NAME.to_string(), + table_name: table_name.to_string(), + kind: Some(Kind::DropColumns(DropColumns { + drop_columns: vec![DropColumn { + name: "cpu".to_string(), + }], + })), + }, + } +} + +fn assert_alter_request( + peer: Peer, + request: RegionRequest, + expected_peer_id: u64, + expected_region_id: RegionId, +) { + assert_eq!(peer.id, expected_peer_id); + let Some(region_request::Body::Alter(req)) = request.body else { + unreachable!(); + }; + assert_eq!(req.region_id, expected_region_id); +} + +fn assert_sync_request( + peer: Peer, + request: RegionRequest, + expected_peer_id: u64, + expected_region_id: RegionId, + expected_manifest_version: u64, +) { + assert_eq!(peer.id, expected_peer_id); + let Some(region_request::Body::Sync(req)) = request.body else { + unreachable!(); + }; + let Some(ManifestInfo::MitoManifestInfo(info)) = req.manifest_info else { + unreachable!(); + }; + assert_eq!(info.data_manifest_version, expected_manifest_version); + assert_eq!(req.region_id, expected_region_id); +} + +fn alter_request_handler(_peer: Peer, request: RegionRequest) -> Result { + if let region_request::Body::Alter(req) = request.body.unwrap() { + let mut response = RegionResponse::new(0); + let region_id = RegionId::from(req.region_id); + response.extensions.insert( + MANIFEST_INFO_EXTENSION_KEY.to_string(), + RegionManifestInfo::encode_list(&[(region_id, RegionManifestInfo::mito(1, 1))]) + .unwrap(), + ); + return Ok(response); + } + + Ok(RegionResponse::new(0)) +} + #[tokio::test] async fn test_on_submit_alter_request() { let (tx, mut rx) = mpsc::channel(8); - let datanode_handler = DatanodeWatcher(tx); + let datanode_handler = DatanodeWatcher::new(tx).with_handler(alter_request_handler); let node_manager = Arc::new(MockDatanodeManager::new(datanode_handler)); let ddl_context = new_ddl_context(node_manager); let table_id = 1024; @@ -140,18 +205,7 @@ async fn test_on_submit_alter_request() { .await .unwrap(); - let alter_table_task = AlterTableTask { - alter_table: AlterTableExpr { - catalog_name: DEFAULT_CATALOG_NAME.to_string(), - schema_name: DEFAULT_SCHEMA_NAME.to_string(), - table_name: table_name.to_string(), - kind: Some(Kind::DropColumns(DropColumns { - drop_columns: vec![DropColumn { - name: "cpu".to_string(), - }], - })), - }, - }; + let alter_table_task = test_alter_table_task(table_name); let procedure_id = ProcedureId::random(); let provider = Arc::new(MockContextProvider::default()); let mut procedure = @@ -162,30 +216,72 @@ async fn test_on_submit_alter_request() { .await .unwrap(); - let check = |peer: Peer, - request: RegionRequest, - expected_peer_id: u64, - expected_region_id: RegionId| { - assert_eq!(peer.id, expected_peer_id); - let Some(region_request::Body::Alter(req)) = request.body else { - unreachable!(); - }; - assert_eq!(req.region_id, expected_region_id); - }; + let mut results = Vec::new(); + for _ in 0..5 { + let result = rx.try_recv().unwrap(); + results.push(result); + } + rx.try_recv().unwrap_err(); + results.sort_unstable_by(|(a, _), (b, _)| a.id.cmp(&b.id)); + + let (peer, request) = results.remove(0); + assert_alter_request(peer, request, 1, RegionId::new(table_id, 1)); + let (peer, request) = results.remove(0); + assert_alter_request(peer, request, 2, RegionId::new(table_id, 2)); + let (peer, request) = results.remove(0); + assert_alter_request(peer, request, 3, RegionId::new(table_id, 3)); + let (peer, request) = results.remove(0); + assert_sync_request(peer, request, 4, RegionId::new(table_id, 2), 1); + let (peer, request) = results.remove(0); + assert_sync_request(peer, request, 5, RegionId::new(table_id, 1), 1); +} + +#[tokio::test] +async fn test_on_submit_alter_request_without_sync_request() { + let (tx, mut rx) = mpsc::channel(8); + // without use `alter_request_handler`, so no sync request will be sent. + let datanode_handler = DatanodeWatcher::new(tx); + let node_manager = Arc::new(MockDatanodeManager::new(datanode_handler)); + let ddl_context = new_ddl_context(node_manager); + let table_id = 1024; + let table_name = "foo"; + let task = test_create_table_task(table_name, table_id); + // Puts a value to table name key. + ddl_context + .table_metadata_manager + .create_table_metadata( + task.table_info.clone(), + prepare_table_route(table_id), + HashMap::new(), + ) + .await + .unwrap(); + + let alter_table_task = test_alter_table_task(table_name); + let procedure_id = ProcedureId::random(); + let provider = Arc::new(MockContextProvider::default()); + let mut procedure = + AlterTableProcedure::new(table_id, alter_table_task, ddl_context.clone()).unwrap(); + procedure.on_prepare().await.unwrap(); + procedure + .submit_alter_region_requests(procedure_id, provider.as_ref()) + .await + .unwrap(); let mut results = Vec::new(); for _ in 0..3 { let result = rx.try_recv().unwrap(); results.push(result); } + rx.try_recv().unwrap_err(); results.sort_unstable_by(|(a, _), (b, _)| a.id.cmp(&b.id)); let (peer, request) = results.remove(0); - check(peer, request, 1, RegionId::new(table_id, 1)); + assert_alter_request(peer, request, 1, RegionId::new(table_id, 1)); let (peer, request) = results.remove(0); - check(peer, request, 2, RegionId::new(table_id, 2)); + assert_alter_request(peer, request, 2, RegionId::new(table_id, 2)); let (peer, request) = results.remove(0); - check(peer, request, 3, RegionId::new(table_id, 3)); + assert_alter_request(peer, request, 3, RegionId::new(table_id, 3)); } #[tokio::test] diff --git a/src/common/meta/src/ddl/tests/create_flow.rs b/src/common/meta/src/ddl/tests/create_flow.rs index 4c9f86fe09..3b24e86400 100644 --- a/src/common/meta/src/ddl/tests/create_flow.rs +++ b/src/common/meta/src/ddl/tests/create_flow.rs @@ -46,7 +46,7 @@ pub(crate) fn test_create_flow_task( create_if_not_exists, expire_after: Some(300), comment: "".to_string(), - sql: "raw_sql".to_string(), + sql: "select 1".to_string(), flow_options: Default::default(), } } diff --git a/src/common/meta/src/ddl/tests/create_logical_tables.rs b/src/common/meta/src/ddl/tests/create_logical_tables.rs index fb5518d463..af0af4ccdb 100644 --- a/src/common/meta/src/ddl/tests/create_logical_tables.rs +++ b/src/common/meta/src/ddl/tests/create_logical_tables.rs @@ -15,20 +15,28 @@ use std::assert_matches::assert_matches; use std::sync::Arc; +use api::region::RegionResponse; +use api::v1::meta::Peer; +use api::v1::region::sync_request::ManifestInfo; +use api::v1::region::{region_request, MetricManifestInfo, RegionRequest, SyncRequest}; use common_error::ext::ErrorExt; use common_error::status_code::StatusCode; use common_procedure::{Context as ProcedureContext, Procedure, ProcedureId, Status}; use common_procedure_test::MockContextProvider; +use store_api::metric_engine_consts::MANIFEST_INFO_EXTENSION_KEY; +use store_api::region_engine::RegionManifestInfo; use store_api::storage::RegionId; +use tokio::sync::mpsc; use crate::ddl::create_logical_tables::CreateLogicalTablesProcedure; -use crate::ddl::test_util::datanode_handler::NaiveDatanodeHandler; +use crate::ddl::test_util::datanode_handler::{DatanodeWatcher, NaiveDatanodeHandler}; use crate::ddl::test_util::{ create_physical_table_metadata, test_create_logical_table_task, test_create_physical_table_task, }; use crate::ddl::TableMetadata; -use crate::error::Error; -use crate::key::table_route::TableRouteValue; +use crate::error::{Error, Result}; +use crate::key::table_route::{PhysicalTableRouteValue, TableRouteValue}; +use crate::rpc::router::{Region, RegionRoute}; use crate::test_util::{new_ddl_context, MockDatanodeManager}; #[tokio::test] @@ -390,3 +398,76 @@ async fn test_on_create_metadata_err() { let error = procedure.execute(&ctx).await.unwrap_err(); assert!(!error.is_retry_later()); } + +fn creates_request_handler(_peer: Peer, request: RegionRequest) -> Result { + if let region_request::Body::Creates(_) = request.body.unwrap() { + let mut response = RegionResponse::new(0); + // Default region id for physical table. + let region_id = RegionId::new(1024, 1); + response.extensions.insert( + MANIFEST_INFO_EXTENSION_KEY.to_string(), + RegionManifestInfo::encode_list(&[(region_id, RegionManifestInfo::metric(1, 0, 2, 0))]) + .unwrap(), + ); + return Ok(response); + } + + Ok(RegionResponse::new(0)) +} + +#[tokio::test] +async fn test_on_submit_create_request() { + common_telemetry::init_default_ut_logging(); + let (tx, mut rx) = mpsc::channel(8); + let handler = DatanodeWatcher::new(tx).with_handler(creates_request_handler); + let node_manager = Arc::new(MockDatanodeManager::new(handler)); + let ddl_context = new_ddl_context(node_manager); + let mut create_physical_table_task = test_create_physical_table_task("phy_table"); + let table_id = 1024u32; + let region_routes = vec![RegionRoute { + region: Region::new_test(RegionId::new(table_id, 1)), + leader_peer: Some(Peer::empty(1)), + follower_peers: vec![Peer::empty(5)], + leader_state: None, + leader_down_since: None, + }]; + create_physical_table_task.set_table_id(table_id); + create_physical_table_metadata( + &ddl_context, + create_physical_table_task.table_info.clone(), + TableRouteValue::Physical(PhysicalTableRouteValue::new(region_routes)), + ) + .await; + let physical_table_id = table_id; + let task = test_create_logical_table_task("foo"); + let yet_another_task = test_create_logical_table_task("bar"); + let mut procedure = CreateLogicalTablesProcedure::new( + vec![task, yet_another_task], + physical_table_id, + ddl_context, + ); + procedure.on_prepare().await.unwrap(); + procedure.on_datanode_create_regions().await.unwrap(); + let mut results = Vec::new(); + for _ in 0..2 { + let result = rx.try_recv().unwrap(); + results.push(result); + } + rx.try_recv().unwrap_err(); + let (peer, request) = results.remove(0); + assert_eq!(peer.id, 1); + assert_matches!(request.body.unwrap(), region_request::Body::Creates(_)); + let (peer, request) = results.remove(0); + assert_eq!(peer.id, 5); + assert_matches!( + request.body.unwrap(), + region_request::Body::Sync(SyncRequest { + manifest_info: Some(ManifestInfo::MetricManifestInfo(MetricManifestInfo { + data_manifest_version: 1, + metadata_manifest_version: 2, + .. + })), + .. + }) + ); +} diff --git a/src/common/meta/src/ddl/tests/drop_table.rs b/src/common/meta/src/ddl/tests/drop_table.rs index 3e09f65422..9983e19ec5 100644 --- a/src/common/meta/src/ddl/tests/drop_table.rs +++ b/src/common/meta/src/ddl/tests/drop_table.rs @@ -100,7 +100,7 @@ async fn test_on_prepare_table() { #[tokio::test] async fn test_on_datanode_drop_regions() { let (tx, mut rx) = mpsc::channel(8); - let datanode_handler = DatanodeWatcher(tx); + let datanode_handler = DatanodeWatcher::new(tx); let node_manager = Arc::new(MockDatanodeManager::new(datanode_handler)); let ddl_context = new_ddl_context(node_manager); let table_id = 1024; @@ -148,27 +148,39 @@ async fn test_on_datanode_drop_regions() { let check = |peer: Peer, request: RegionRequest, expected_peer_id: u64, - expected_region_id: RegionId| { + expected_region_id: RegionId, + follower: bool| { assert_eq!(peer.id, expected_peer_id); - let Some(region_request::Body::Drop(req)) = request.body else { - unreachable!(); + if follower { + let Some(region_request::Body::Close(req)) = request.body else { + unreachable!(); + }; + assert_eq!(req.region_id, expected_region_id); + } else { + let Some(region_request::Body::Drop(req)) = request.body else { + unreachable!(); + }; + assert_eq!(req.region_id, expected_region_id); }; - assert_eq!(req.region_id, expected_region_id); }; let mut results = Vec::new(); - for _ in 0..3 { + for _ in 0..5 { let result = rx.try_recv().unwrap(); results.push(result); } results.sort_unstable_by(|(a, _), (b, _)| a.id.cmp(&b.id)); let (peer, request) = results.remove(0); - check(peer, request, 1, RegionId::new(table_id, 1)); + check(peer, request, 1, RegionId::new(table_id, 1), false); let (peer, request) = results.remove(0); - check(peer, request, 2, RegionId::new(table_id, 2)); + check(peer, request, 2, RegionId::new(table_id, 2), false); let (peer, request) = results.remove(0); - check(peer, request, 3, RegionId::new(table_id, 3)); + check(peer, request, 3, RegionId::new(table_id, 3), false); + let (peer, request) = results.remove(0); + check(peer, request, 4, RegionId::new(table_id, 2), true); + let (peer, request) = results.remove(0); + check(peer, request, 5, RegionId::new(table_id, 1), true); } #[tokio::test] diff --git a/src/common/meta/src/ddl/utils.rs b/src/common/meta/src/ddl/utils.rs index 2e909946e0..8c48d9f419 100644 --- a/src/common/meta/src/ddl/utils.rs +++ b/src/common/meta/src/ddl/utils.rs @@ -15,27 +15,37 @@ use std::collections::HashMap; use std::fmt::Debug; -use common_catalog::consts::METRIC_ENGINE; +use api::region::RegionResponse; +use api::v1::region::sync_request::ManifestInfo; +use api::v1::region::{ + region_request, MetricManifestInfo, MitoManifestInfo, RegionRequest, RegionRequestHeader, + SyncRequest, +}; +use common_catalog::consts::{METRIC_ENGINE, MITO_ENGINE}; use common_error::ext::BoxedError; use common_procedure::error::Error as ProcedureError; -use common_telemetry::{error, warn}; +use common_telemetry::tracing_context::TracingContext; +use common_telemetry::{error, info, warn}; use common_wal::options::WalOptions; +use futures::future::join_all; use snafu::{ensure, OptionExt, ResultExt}; -use store_api::metric_engine_consts::LOGICAL_TABLE_METADATA_KEY; -use store_api::storage::RegionNumber; +use store_api::metric_engine_consts::{LOGICAL_TABLE_METADATA_KEY, MANIFEST_INFO_EXTENSION_KEY}; +use store_api::region_engine::RegionManifestInfo; +use store_api::storage::{RegionId, RegionNumber}; use table::metadata::TableId; use table::table_reference::TableReference; -use crate::ddl::DetectingRegion; +use crate::ddl::{DdlContext, DetectingRegion}; use crate::error::{ - Error, OperateDatanodeSnafu, ParseWalOptionsSnafu, Result, TableNotFoundSnafu, UnsupportedSnafu, + self, Error, OperateDatanodeSnafu, ParseWalOptionsSnafu, Result, TableNotFoundSnafu, + UnsupportedSnafu, }; use crate::key::datanode_table::DatanodeTableValue; use crate::key::table_name::TableNameKey; use crate::key::TableMetadataManagerRef; use crate::peer::Peer; use crate::rpc::ddl::CreateTableTask; -use crate::rpc::router::RegionRoute; +use crate::rpc::router::{find_follower_regions, find_followers, RegionRoute}; /// Adds [Peer] context if the error is unretryable. pub fn add_peer_context_if_needed(datanode: Peer) -> impl FnOnce(Error) -> Error { @@ -192,8 +202,8 @@ pub fn extract_region_wal_options( /// - PartialNonRetryable: if any operation is non retryable, the result is non retryable. /// - AllRetryable: all operations are retryable. /// - AllNonRetryable: all operations are not retryable. -pub enum MultipleResults { - Ok, +pub enum MultipleResults { + Ok(Vec), PartialRetryable(Error), PartialNonRetryable(Error), AllRetryable(Error), @@ -205,9 +215,9 @@ pub enum MultipleResults { /// For partial success, we need to check if the errors are retryable. /// If all the errors are retryable, we return a retryable error. /// Otherwise, we return the first error. -pub fn handle_multiple_results(results: Vec>) -> MultipleResults { +pub fn handle_multiple_results(results: Vec>) -> MultipleResults { if results.is_empty() { - return MultipleResults::Ok; + return MultipleResults::Ok(Vec::new()); } let num_results = results.len(); let mut retryable_results = Vec::new(); @@ -216,7 +226,7 @@ pub fn handle_multiple_results(results: Vec>) -> MultipleRes for result in results { match result { - Ok(_) => ok_results.push(result), + Ok(value) => ok_results.push(value), Err(err) => { if err.is_retry_later() { retryable_results.push(err); @@ -243,7 +253,7 @@ pub fn handle_multiple_results(results: Vec>) -> MultipleRes } return MultipleResults::AllNonRetryable(non_retryable_results.into_iter().next().unwrap()); } else if ok_results.len() == num_results { - return MultipleResults::Ok; + return MultipleResults::Ok(ok_results); } else if !retryable_results.is_empty() && !ok_results.is_empty() && non_retryable_results.is_empty() @@ -264,6 +274,125 @@ pub fn handle_multiple_results(results: Vec>) -> MultipleRes MultipleResults::PartialNonRetryable(non_retryable_results.into_iter().next().unwrap()) } +/// Parses manifest infos from extensions. +pub fn parse_manifest_infos_from_extensions( + extensions: &HashMap>, +) -> Result> { + let data_manifest_version = + extensions + .get(MANIFEST_INFO_EXTENSION_KEY) + .context(error::UnexpectedSnafu { + err_msg: "manifest info extension not found", + })?; + let data_manifest_version = + RegionManifestInfo::decode_list(data_manifest_version).context(error::SerdeJsonSnafu {})?; + Ok(data_manifest_version) +} + +/// Sync follower regions on datanodes. +pub async fn sync_follower_regions( + context: &DdlContext, + table_id: TableId, + results: Vec, + region_routes: &[RegionRoute], + engine: &str, +) -> Result<()> { + if engine != MITO_ENGINE && engine != METRIC_ENGINE { + info!( + "Skip submitting sync region requests for table_id: {}, engine: {}", + table_id, engine + ); + return Ok(()); + } + + let results = results + .into_iter() + .map(|response| parse_manifest_infos_from_extensions(&response.extensions)) + .collect::>>()? + .into_iter() + .flatten() + .collect::>(); + + let is_mito_engine = engine == MITO_ENGINE; + + let followers = find_followers(region_routes); + if followers.is_empty() { + return Ok(()); + } + let mut sync_region_tasks = Vec::with_capacity(followers.len()); + for datanode in followers { + let requester = context.node_manager.datanode(&datanode).await; + let regions = find_follower_regions(region_routes, &datanode); + for region in regions { + let region_id = RegionId::new(table_id, region); + let manifest_info = if is_mito_engine { + let region_manifest_info = + results.get(®ion_id).context(error::UnexpectedSnafu { + err_msg: format!("No manifest info found for region {}", region_id), + })?; + ensure!( + region_manifest_info.is_mito(), + error::UnexpectedSnafu { + err_msg: format!("Region {} is not a mito region", region_id) + } + ); + ManifestInfo::MitoManifestInfo(MitoManifestInfo { + data_manifest_version: region_manifest_info.data_manifest_version(), + }) + } else { + let region_manifest_info = + results.get(®ion_id).context(error::UnexpectedSnafu { + err_msg: format!("No manifest info found for region {}", region_id), + })?; + ensure!( + region_manifest_info.is_metric(), + error::UnexpectedSnafu { + err_msg: format!("Region {} is not a metric region", region_id) + } + ); + ManifestInfo::MetricManifestInfo(MetricManifestInfo { + data_manifest_version: region_manifest_info.data_manifest_version(), + metadata_manifest_version: region_manifest_info + .metadata_manifest_version() + .unwrap_or_default(), + }) + }; + let request = RegionRequest { + header: Some(RegionRequestHeader { + tracing_context: TracingContext::from_current_span().to_w3c(), + ..Default::default() + }), + body: Some(region_request::Body::Sync(SyncRequest { + region_id: region_id.as_u64(), + manifest_info: Some(manifest_info), + })), + }; + + let datanode = datanode.clone(); + let requester = requester.clone(); + sync_region_tasks.push(async move { + requester + .handle(request) + .await + .map_err(add_peer_context_if_needed(datanode)) + }); + } + } + + // Failure to sync region is not critical. + // We try our best to sync the regions. + if let Err(err) = join_all(sync_region_tasks) + .await + .into_iter() + .collect::>>() + { + error!(err; "Failed to sync follower regions on datanodes, table_id: {}", table_id); + } + info!("Sync follower regions on datanodes, table_id: {}", table_id); + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/common/meta/src/error.rs b/src/common/meta/src/error.rs index 1bdb3d0857..1bc85a898d 100644 --- a/src/common/meta/src/error.rs +++ b/src/common/meta/src/error.rs @@ -401,6 +401,13 @@ pub enum Error { location: Location, }, + #[snafu(display("Invalid flow request body: {:?}", body))] + InvalidFlowRequestBody { + body: Box>, + #[snafu(implicit)] + location: Location, + }, + #[snafu(display("Failed to get kv cache, err: {}", err_msg))] GetKvCache { err_msg: String }, @@ -783,6 +790,14 @@ pub enum Error { #[snafu(source)] source: common_procedure::error::Error, }, + + #[snafu(display("Failed to parse timezone"))] + InvalidTimeZone { + #[snafu(implicit)] + location: Location, + #[snafu(source)] + error: common_time::error::Error, + }, } pub type Result = std::result::Result; @@ -853,7 +868,9 @@ impl ErrorExt for Error { | TlsConfig { .. } | InvalidSetDatabaseOption { .. } | InvalidUnsetDatabaseOption { .. } - | InvalidTopicNamePrefix { .. } => StatusCode::InvalidArguments, + | InvalidTopicNamePrefix { .. } + | InvalidTimeZone { .. } => StatusCode::InvalidArguments, + InvalidFlowRequestBody { .. } => StatusCode::InvalidArguments, FlowNotFound { .. } => StatusCode::FlowNotFound, FlowRouteNotFound { .. } => StatusCode::Unexpected, diff --git a/src/common/meta/src/instruction.rs b/src/common/meta/src/instruction.rs index afdc14dff0..5e00437332 100644 --- a/src/common/meta/src/instruction.rs +++ b/src/common/meta/src/instruction.rs @@ -57,6 +57,8 @@ impl Display for RegionIdent { pub struct DowngradeRegionReply { /// Returns the `last_entry_id` if available. pub last_entry_id: Option, + /// Returns the `metadata_last_entry_id` if available (Only available for metric engine). + pub metadata_last_entry_id: Option, /// Indicates whether the region exists. pub exists: bool, /// Return error if any during the operation. @@ -136,16 +138,14 @@ pub struct DowngradeRegion { /// `None` stands for don't flush before downgrading the region. #[serde(default)] pub flush_timeout: Option, - /// Rejects all write requests after flushing. - pub reject_write: bool, } impl Display for DowngradeRegion { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, - "DowngradeRegion(region_id={}, flush_timeout={:?}, rejct_write={})", - self.region_id, self.flush_timeout, self.reject_write + "DowngradeRegion(region_id={}, flush_timeout={:?})", + self.region_id, self.flush_timeout, ) } } @@ -157,6 +157,8 @@ pub struct UpgradeRegion { pub region_id: RegionId, /// The `last_entry_id` of old leader region. pub last_entry_id: Option, + /// The `last_entry_id` of old leader metadata region (Only used for metric engine). + pub metadata_last_entry_id: Option, /// The timeout of waiting for a wal replay. /// /// `None` stands for no wait, diff --git a/src/common/meta/src/key/flow.rs b/src/common/meta/src/key/flow.rs index 6af910b2fc..bc66c08a9d 100644 --- a/src/common/meta/src/key/flow.rs +++ b/src/common/meta/src/key/flow.rs @@ -452,6 +452,7 @@ mod tests { }; FlowInfoValue { catalog_name: catalog_name.to_string(), + query_context: None, flow_name: flow_name.to_string(), source_table_ids, sink_table_name, @@ -625,6 +626,7 @@ mod tests { }; let flow_value = FlowInfoValue { catalog_name: "greptime".to_string(), + query_context: None, flow_name: "flow".to_string(), source_table_ids: vec![1024, 1025, 1026], sink_table_name: another_sink_table_name, @@ -864,6 +866,7 @@ mod tests { }; let flow_value = FlowInfoValue { catalog_name: "greptime".to_string(), + query_context: None, flow_name: "flow".to_string(), source_table_ids: vec![1024, 1025, 1026], sink_table_name: another_sink_table_name, diff --git a/src/common/meta/src/key/flow/flow_info.rs b/src/common/meta/src/key/flow/flow_info.rs index eeb37da81d..1ed3f1e6f4 100644 --- a/src/common/meta/src/key/flow/flow_info.rs +++ b/src/common/meta/src/key/flow/flow_info.rs @@ -121,6 +121,13 @@ pub struct FlowInfoValue { pub(crate) flownode_ids: BTreeMap, /// The catalog name. pub(crate) catalog_name: String, + /// The query context used when create flow. + /// Although flow doesn't belong to any schema, this query_context is needed to remember + /// the query context when `create_flow` is executed + /// for recovering flow using the same sql&query_context after db restart. + /// if none, should use default query context + #[serde(default)] + pub(crate) query_context: Option, /// The flow name. pub(crate) flow_name: String, /// The raw sql. @@ -155,6 +162,10 @@ impl FlowInfoValue { &self.catalog_name } + pub fn query_context(&self) -> &Option { + &self.query_context + } + pub fn flow_name(&self) -> &String { &self.flow_name } diff --git a/src/common/meta/src/lib.rs b/src/common/meta/src/lib.rs index b1cc18d5e4..7bfbd78f9c 100644 --- a/src/common/meta/src/lib.rs +++ b/src/common/meta/src/lib.rs @@ -15,8 +15,6 @@ #![feature(assert_matches)] #![feature(btree_extract_if)] #![feature(let_chains)] -#![feature(extract_if)] -#![feature(hash_extract_if)] pub mod cache; pub mod cache_invalidator; diff --git a/src/common/meta/src/region_registry.rs b/src/common/meta/src/region_registry.rs index 76fb271f52..4beb24c008 100644 --- a/src/common/meta/src/region_registry.rs +++ b/src/common/meta/src/region_registry.rs @@ -19,7 +19,7 @@ use std::sync::{Arc, RwLock}; use common_telemetry::warn; use store_api::storage::RegionId; -use crate::datanode::RegionManifestInfo; +use crate::datanode::{RegionManifestInfo, RegionStat}; /// Represents information about a leader region in the cluster. /// Contains the datanode id where the leader is located, @@ -35,25 +35,22 @@ pub enum LeaderRegionManifestInfo { Mito { manifest_version: u64, flushed_entry_id: u64, + topic_latest_entry_id: u64, }, Metric { data_manifest_version: u64, data_flushed_entry_id: u64, + data_topic_latest_entry_id: u64, metadata_manifest_version: u64, metadata_flushed_entry_id: u64, + metadata_topic_latest_entry_id: u64, }, } -impl From for LeaderRegionManifestInfo { - fn from(value: RegionManifestInfo) -> Self { - match value { - RegionManifestInfo::Mito { - manifest_version, - flushed_entry_id, - } => LeaderRegionManifestInfo::Mito { - manifest_version, - flushed_entry_id, - }, +impl LeaderRegionManifestInfo { + /// Generate a [LeaderRegionManifestInfo] from [RegionStat]. + pub fn from_region_stat(region_stat: &RegionStat) -> LeaderRegionManifestInfo { + match region_stat.region_manifest { RegionManifestInfo::Metric { data_manifest_version, data_flushed_entry_id, @@ -62,14 +59,22 @@ impl From for LeaderRegionManifestInfo { } => LeaderRegionManifestInfo::Metric { data_manifest_version, data_flushed_entry_id, + data_topic_latest_entry_id: region_stat.data_topic_latest_entry_id, metadata_manifest_version, metadata_flushed_entry_id, + metadata_topic_latest_entry_id: region_stat.metadata_topic_latest_entry_id, + }, + RegionManifestInfo::Mito { + manifest_version, + flushed_entry_id, + } => LeaderRegionManifestInfo::Mito { + manifest_version, + flushed_entry_id, + topic_latest_entry_id: region_stat.data_topic_latest_entry_id, }, } } -} -impl LeaderRegionManifestInfo { /// Returns the manifest version of the leader region. pub fn manifest_version(&self) -> u64 { match self { @@ -96,18 +101,35 @@ impl LeaderRegionManifestInfo { } } - /// Returns the minimum flushed entry id of the leader region. - /// It is used to determine the minimum flushed entry id that can be pruned in remote wal. - pub fn min_flushed_entry_id(&self) -> u64 { + /// Returns prunable entry id of the leader region. + /// It is used to determine the entry id that can be pruned in remote wal. + /// + /// For a mito region, the prunable entry id should max(flushed_entry_id, latest_entry_id_since_flush). + /// + /// For a metric region, the prunable entry id should min( + /// max(data_flushed_entry_id, data_latest_entry_id_since_flush), + /// max(metadata_flushed_entry_id, metadata_latest_entry_id_since_flush) + /// ). + pub fn prunable_entry_id(&self) -> u64 { match self { LeaderRegionManifestInfo::Mito { - flushed_entry_id, .. - } => *flushed_entry_id, + flushed_entry_id, + topic_latest_entry_id, + .. + } => (*flushed_entry_id).max(*topic_latest_entry_id), LeaderRegionManifestInfo::Metric { data_flushed_entry_id, + data_topic_latest_entry_id, metadata_flushed_entry_id, + metadata_topic_latest_entry_id, .. - } => (*data_flushed_entry_id).min(*metadata_flushed_entry_id), + } => { + let data_prunable_entry_id = + (*data_flushed_entry_id).max(*data_topic_latest_entry_id); + let metadata_prunable_entry_id = + (*metadata_flushed_entry_id).max(*metadata_topic_latest_entry_id); + data_prunable_entry_id.min(metadata_prunable_entry_id) + } } } } diff --git a/src/common/meta/src/rpc/ddl.rs b/src/common/meta/src/rpc/ddl.rs index ae7794d9bd..2797c6bee0 100644 --- a/src/common/meta/src/rpc/ddl.rs +++ b/src/common/meta/src/rpc/ddl.rs @@ -35,17 +35,20 @@ use api::v1::{ }; use base64::engine::general_purpose; use base64::Engine as _; -use common_time::DatabaseTimeToLive; +use common_time::{DatabaseTimeToLive, Timezone}; use prost::Message; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DefaultOnNull}; -use session::context::QueryContextRef; +use session::context::{QueryContextBuilder, QueryContextRef}; use snafu::{OptionExt, ResultExt}; use table::metadata::{RawTableInfo, TableId}; use table::table_name::TableName; use table::table_reference::TableReference; -use crate::error::{self, InvalidSetDatabaseOptionSnafu, InvalidUnsetDatabaseOptionSnafu, Result}; +use crate::error::{ + self, InvalidSetDatabaseOptionSnafu, InvalidTimeZoneSnafu, InvalidUnsetDatabaseOptionSnafu, + Result, +}; use crate::key::FlowId; /// DDL tasks @@ -1202,7 +1205,7 @@ impl From for PbDropFlowTask { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct QueryContext { current_catalog: String, current_schema: String, @@ -1223,6 +1226,19 @@ impl From for QueryContext { } } +impl TryFrom for session::context::QueryContext { + type Error = error::Error; + fn try_from(value: QueryContext) -> std::result::Result { + Ok(QueryContextBuilder::default() + .current_catalog(value.current_catalog) + .current_schema(value.current_schema) + .timezone(Timezone::from_tz_string(&value.timezone).context(InvalidTimeZoneSnafu)?) + .extensions(value.extensions) + .channel((value.channel as u32).into()) + .build()) + } +} + impl From for PbQueryContext { fn from( QueryContext { diff --git a/src/common/meta/src/rpc/router.rs b/src/common/meta/src/rpc/router.rs index 0e700cc6da..2386ca73a7 100644 --- a/src/common/meta/src/rpc/router.rs +++ b/src/common/meta/src/rpc/router.rs @@ -32,6 +32,10 @@ use crate::key::RegionDistribution; use crate::peer::Peer; use crate::DatanodeId; +/// Returns the distribution of regions to datanodes. +/// +/// The distribution is a map of datanode id to a list of region ids. +/// The list of region ids is sorted in ascending order. pub fn region_distribution(region_routes: &[RegionRoute]) -> RegionDistribution { let mut regions_id_map = RegionDistribution::new(); for route in region_routes.iter() { @@ -39,6 +43,10 @@ pub fn region_distribution(region_routes: &[RegionRoute]) -> RegionDistribution let region_id = route.region.id.region_number(); regions_id_map.entry(peer.id).or_default().push(region_id); } + for peer in route.follower_peers.iter() { + let region_id = route.region.id.region_number(); + regions_id_map.entry(peer.id).or_default().push(region_id); + } } for (_, regions) in regions_id_map.iter_mut() { // id asc @@ -54,6 +62,7 @@ pub struct TableRoute { region_leaders: HashMap>, } +/// Returns the leader peers of the table. pub fn find_leaders(region_routes: &[RegionRoute]) -> HashSet { region_routes .iter() @@ -62,6 +71,15 @@ pub fn find_leaders(region_routes: &[RegionRoute]) -> HashSet { .collect() } +/// Returns the followers of the table. +pub fn find_followers(region_routes: &[RegionRoute]) -> HashSet { + region_routes + .iter() + .flat_map(|x| &x.follower_peers) + .cloned() + .collect() +} + /// Returns the operating leader regions with corresponding [DatanodeId]. pub fn operating_leader_regions(region_routes: &[RegionRoute]) -> Vec<(RegionId, DatanodeId)> { region_routes @@ -100,6 +118,7 @@ pub fn find_region_leader( .cloned() } +/// Returns the region numbers of the leader regions on the target datanode. pub fn find_leader_regions(region_routes: &[RegionRoute], datanode: &Peer) -> Vec { region_routes .iter() @@ -114,6 +133,19 @@ pub fn find_leader_regions(region_routes: &[RegionRoute], datanode: &Peer) -> Ve .collect() } +/// Returns the region numbers of the follower regions on the target datanode. +pub fn find_follower_regions(region_routes: &[RegionRoute], datanode: &Peer) -> Vec { + region_routes + .iter() + .filter_map(|x| { + if x.follower_peers.contains(datanode) { + return Some(x.region.id.region_number()); + } + None + }) + .collect() +} + impl TableRoute { pub fn new(table: Table, region_routes: Vec) -> Self { let region_leaders = region_routes @@ -144,15 +176,12 @@ impl TableRoute { })? .into(); - let leader_peer = peers - .get(region_route.leader_peer_index as usize) - .cloned() - .map(Into::into); + let leader_peer = peers.get(region_route.leader_peer_index as usize).cloned(); let follower_peers = region_route .follower_peer_indexes .into_iter() - .filter_map(|x| peers.get(x as usize).cloned().map(Into::into)) + .filter_map(|x| peers.get(x as usize).cloned()) .collect::>(); region_routes.push(RegionRoute { @@ -550,4 +579,40 @@ mod tests { assert_eq!(got, p); } + + #[test] + fn test_region_distribution() { + let region_routes = vec![ + RegionRoute { + region: Region { + id: RegionId::new(1, 1), + name: "r1".to_string(), + partition: None, + attrs: BTreeMap::new(), + }, + leader_peer: Some(Peer::new(1, "a1")), + follower_peers: vec![Peer::new(2, "a2"), Peer::new(3, "a3")], + leader_state: None, + leader_down_since: None, + }, + RegionRoute { + region: Region { + id: RegionId::new(1, 2), + name: "r2".to_string(), + partition: None, + attrs: BTreeMap::new(), + }, + leader_peer: Some(Peer::new(2, "a2")), + follower_peers: vec![Peer::new(1, "a1"), Peer::new(3, "a3")], + leader_state: None, + leader_down_since: None, + }, + ]; + + let distribution = region_distribution(®ion_routes); + assert_eq!(distribution.len(), 3); + assert_eq!(distribution[&1], vec![1, 2]); + assert_eq!(distribution[&2], vec![1, 2]); + assert_eq!(distribution[&3], vec![1, 2]); + } } diff --git a/src/common/meta/src/wal_options_allocator.rs b/src/common/meta/src/wal_options_allocator.rs index 2aba2a5ee3..a6e1482f04 100644 --- a/src/common/meta/src/wal_options_allocator.rs +++ b/src/common/meta/src/wal_options_allocator.rs @@ -30,7 +30,9 @@ use crate::error::{EncodeWalOptionsSnafu, InvalidTopicNamePrefixSnafu, Result}; use crate::key::NAME_PATTERN_REGEX; use crate::kv_backend::KvBackendRef; use crate::leadership_notifier::LeadershipChangeListener; -pub use crate::wal_options_allocator::topic_creator::build_kafka_topic_creator; +pub use crate::wal_options_allocator::topic_creator::{ + build_kafka_client, build_kafka_topic_creator, +}; use crate::wal_options_allocator::topic_pool::KafkaTopicPool; /// Allocates wal options in region granularity. diff --git a/src/common/meta/src/wal_options_allocator/topic_creator.rs b/src/common/meta/src/wal_options_allocator/topic_creator.rs index f49d1bf1ca..1a023546d3 100644 --- a/src/common/meta/src/wal_options_allocator/topic_creator.rs +++ b/src/common/meta/src/wal_options_allocator/topic_creator.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::Arc; - use common_telemetry::{error, info}; use common_wal::config::kafka::common::DEFAULT_BACKOFF_CONFIG; use common_wal::config::kafka::MetasrvKafkaConfig; @@ -34,11 +32,9 @@ use crate::error::{ // The `DEFAULT_PARTITION` refers to the index of the partition. const DEFAULT_PARTITION: i32 = 0; -type KafkaClientRef = Arc; - /// Creates topics in kafka. pub struct KafkaTopicCreator { - client: KafkaClientRef, + client: Client, /// The number of partitions per topic. num_partitions: i32, /// The replication factor of each topic. @@ -48,7 +44,7 @@ pub struct KafkaTopicCreator { } impl KafkaTopicCreator { - pub fn client(&self) -> &KafkaClientRef { + pub fn client(&self) -> &Client { &self.client } @@ -133,7 +129,8 @@ impl KafkaTopicCreator { } } -pub async fn build_kafka_topic_creator(config: &MetasrvKafkaConfig) -> Result { +/// Builds a kafka [Client](rskafka::client::Client). +pub async fn build_kafka_client(config: &MetasrvKafkaConfig) -> Result { // Builds an kafka controller client for creating topics. let broker_endpoints = common_wal::resolve_to_ipv4(&config.connection.broker_endpoints) .await @@ -145,15 +142,19 @@ pub async fn build_kafka_topic_creator(config: &MetasrvKafkaConfig) -> Result Result { + let client = build_kafka_client(config).await?; Ok(KafkaTopicCreator { - client: Arc::new(client), + client, num_partitions: config.kafka_topic.num_partitions, replication_factor: config.kafka_topic.replication_factor, create_topic_timeout: config.kafka_topic.create_topic_timeout.as_millis() as i32, diff --git a/src/common/procedure/src/store/state_store.rs b/src/common/procedure/src/store/state_store.rs index a43d7da86f..d98ce06a1e 100644 --- a/src/common/procedure/src/store/state_store.rs +++ b/src/common/procedure/src/store/state_store.rs @@ -137,6 +137,7 @@ impl StateStore for ObjectStateStore { )) }) .context(PutStateSnafu { key }) + .map(|_| ()) } async fn walk_top_down(&self, path: &str) -> Result { diff --git a/src/common/query/src/logical_plan.rs b/src/common/query/src/logical_plan.rs index 974a30a15a..418e7a52ae 100644 --- a/src/common/query/src/logical_plan.rs +++ b/src/common/query/src/logical_plan.rs @@ -18,16 +18,19 @@ mod udaf; use std::sync::Arc; +use api::v1::TableName; use datafusion::catalog::CatalogProviderList; use datafusion::error::Result as DatafusionResult; use datafusion::logical_expr::{LogicalPlan, LogicalPlanBuilder}; -use datafusion_common::Column; -use datafusion_expr::col; +use datafusion_common::{Column, TableReference}; +use datafusion_expr::dml::InsertOp; +use datafusion_expr::{col, DmlStatement, WriteOp}; pub use expr::{build_filter_from_timestamp, build_same_type_ts_filter}; +use snafu::ResultExt; pub use self::accumulator::{Accumulator, AggregateFunctionCreator, AggregateFunctionCreatorRef}; pub use self::udaf::AggregateFunction; -use crate::error::Result; +use crate::error::{GeneralDataFusionSnafu, Result}; use crate::logical_plan::accumulator::*; use crate::signature::{Signature, Volatility}; @@ -79,6 +82,74 @@ pub fn rename_logical_plan_columns( LogicalPlanBuilder::from(plan).project(projection)?.build() } +/// Convert a insert into logical plan to an (table_name, logical_plan) +/// where table_name is the name of the table to insert into. +/// logical_plan is the plan to be executed. +/// +/// if input logical plan is not `insert into table_name `, return None +/// +/// Returned TableName will use provided catalog and schema if not specified in the logical plan, +/// if table scan in logical plan have full table name, will **NOT** override it. +pub fn breakup_insert_plan( + plan: &LogicalPlan, + default_catalog: &str, + default_schema: &str, +) -> Option<(TableName, Arc)> { + if let LogicalPlan::Dml(dml) = plan { + if dml.op != WriteOp::Insert(InsertOp::Append) { + return None; + } + let table_name = &dml.table_name; + let table_name = match table_name { + TableReference::Bare { table } => TableName { + catalog_name: default_catalog.to_string(), + schema_name: default_schema.to_string(), + table_name: table.to_string(), + }, + TableReference::Partial { schema, table } => TableName { + catalog_name: default_catalog.to_string(), + schema_name: schema.to_string(), + table_name: table.to_string(), + }, + TableReference::Full { + catalog, + schema, + table, + } => TableName { + catalog_name: catalog.to_string(), + schema_name: schema.to_string(), + table_name: table.to_string(), + }, + }; + let logical_plan = dml.input.clone(); + Some((table_name, logical_plan)) + } else { + None + } +} + +/// create a `insert into table_name ` logical plan +pub fn add_insert_to_logical_plan( + table_name: TableName, + table_schema: datafusion_common::DFSchemaRef, + input: LogicalPlan, +) -> Result { + let table_name = TableReference::Full { + catalog: table_name.catalog_name.into(), + schema: table_name.schema_name.into(), + table: table_name.table_name.into(), + }; + + let plan = LogicalPlan::Dml(DmlStatement::new( + table_name, + table_schema, + WriteOp::Insert(InsertOp::Append), + Arc::new(input), + )); + let plan = plan.recompute_schema().context(GeneralDataFusionSnafu)?; + Ok(plan) +} + /// The datafusion `[LogicalPlan]` decoder. #[async_trait::async_trait] pub trait SubstraitPlanDecoder { diff --git a/src/common/query/src/logical_plan/accumulator.rs b/src/common/query/src/logical_plan/accumulator.rs index 32f1b4587c..a9c499d323 100644 --- a/src/common/query/src/logical_plan/accumulator.rs +++ b/src/common/query/src/logical_plan/accumulator.rs @@ -24,7 +24,7 @@ use datatypes::prelude::*; use datatypes::vectors::{Helper as VectorHelper, VectorRef}; use snafu::ResultExt; -use crate::error::{self, Error, FromScalarValueSnafu, IntoVectorSnafu, Result}; +use crate::error::{self, FromScalarValueSnafu, IntoVectorSnafu, Result}; use crate::prelude::*; pub type AggregateFunctionCreatorRef = Arc; @@ -166,8 +166,7 @@ impl DfAccumulator for DfAccumulatorAdaptor { let output_type = self.creator.output_type()?; let scalar_value = value .try_to_scalar_value(&output_type) - .context(error::ToScalarValueSnafu) - .map_err(Error::from)?; + .context(error::ToScalarValueSnafu)?; Ok(scalar_value) } diff --git a/src/common/recordbatch/Cargo.toml b/src/common/recordbatch/Cargo.toml index 5c3d9fa550..bb8ba13907 100644 --- a/src/common/recordbatch/Cargo.toml +++ b/src/common/recordbatch/Cargo.toml @@ -17,6 +17,7 @@ datafusion-common.workspace = true datatypes.workspace = true futures.workspace = true pin-project.workspace = true +regex.workspace = true serde.workspace = true serde_json.workspace = true snafu.workspace = true diff --git a/src/common/recordbatch/src/adapter.rs b/src/common/recordbatch/src/adapter.rs index d342aa8129..3d27d7120f 100644 --- a/src/common/recordbatch/src/adapter.rs +++ b/src/common/recordbatch/src/adapter.rs @@ -298,7 +298,7 @@ impl Stream for RecordBatchStreamAdapter { match Pin::new(&mut self.stream).poll_next(cx) { Poll::Pending => Poll::Pending, Poll::Ready(Some(df_record_batch)) => { - let df_record_batch = df_record_batch.context(error::PollStreamSnafu)?; + let df_record_batch = df_record_batch?; Poll::Ready(Some(RecordBatch::try_from_df_record_batch( self.schema(), df_record_batch, diff --git a/src/common/recordbatch/src/error.rs b/src/common/recordbatch/src/error.rs index 6a1c61c0a0..e0b66eb903 100644 --- a/src/common/recordbatch/src/error.rs +++ b/src/common/recordbatch/src/error.rs @@ -23,7 +23,7 @@ use datatypes::prelude::ConcreteDataType; use datatypes::schema::SchemaRef; use snafu::{Location, Snafu}; -pub type Result = std::result::Result; +pub type Result = std::result::Result; #[derive(Snafu)] #[snafu(visibility(pub))] @@ -65,7 +65,7 @@ pub enum Error { location: Location, }, - #[snafu(display(""))] + #[snafu(transparent)] PollStream { #[snafu(source)] error: datafusion::error::DataFusionError, diff --git a/src/common/recordbatch/src/filter.rs b/src/common/recordbatch/src/filter.rs index b16ee401c0..2c509396ff 100644 --- a/src/common/recordbatch/src/filter.rs +++ b/src/common/recordbatch/src/filter.rs @@ -24,12 +24,16 @@ use datafusion_common::arrow::buffer::BooleanBuffer; use datafusion_common::arrow::compute::kernels::cmp; use datafusion_common::cast::{as_boolean_array, as_null_array, as_string_array}; use datafusion_common::{internal_err, DataFusionError, ScalarValue}; -use datatypes::arrow::array::{Array, BooleanArray, RecordBatch}; +use datatypes::arrow::array::{ + Array, ArrayAccessor, ArrayData, BooleanArray, BooleanBufferBuilder, RecordBatch, + StringArrayType, +}; use datatypes::arrow::compute::filter_record_batch; +use datatypes::arrow::datatypes::DataType; use datatypes::arrow::error::ArrowError; -use datatypes::compute::kernels::regexp; use datatypes::compute::or_kleene; use datatypes::vectors::VectorRef; +use regex::Regex; use snafu::ResultExt; use crate::error::{ArrowComputeSnafu, Result, ToArrowScalarSnafu, UnsupportedOperationSnafu}; @@ -53,6 +57,12 @@ pub struct SimpleFilterEvaluator { op: Operator, /// Only used when the operator is `Or`-chain. literal_list: Vec>, + /// Pre-compiled regex. + /// Only used when the operator is regex operators. + /// If the regex is empty, it is also `None`. + regex: Option, + /// Whether the regex is negative. + regex_negative: bool, } impl SimpleFilterEvaluator { @@ -76,6 +86,8 @@ impl SimpleFilterEvaluator { literal: val.to_scalar().ok()?, op, literal_list: vec![], + regex: None, + regex_negative: false, }) } @@ -121,6 +133,8 @@ impl SimpleFilterEvaluator { literal: placeholder_literal, op: Operator::Or, literal_list: list, + regex: None, + regex_negative: false, }); } _ => return None, @@ -138,12 +152,15 @@ impl SimpleFilterEvaluator { _ => return None, }; + let (regex, regex_negative) = Self::maybe_build_regex(op, rhs).ok()?; let literal = rhs.to_scalar().ok()?; Some(Self { column_name: lhs.name.clone(), literal, op, literal_list: vec![], + regex, + regex_negative, }) } _ => None, @@ -179,10 +196,10 @@ impl SimpleFilterEvaluator { Operator::LtEq => cmp::lt_eq(input, &self.literal), Operator::Gt => cmp::gt(input, &self.literal), Operator::GtEq => cmp::gt_eq(input, &self.literal), - Operator::RegexMatch => self.regex_match(input, false, false), - Operator::RegexIMatch => self.regex_match(input, true, false), - Operator::RegexNotMatch => self.regex_match(input, false, true), - Operator::RegexNotIMatch => self.regex_match(input, true, true), + Operator::RegexMatch => self.regex_match(input), + Operator::RegexIMatch => self.regex_match(input), + Operator::RegexNotMatch => self.regex_match(input), + Operator::RegexNotIMatch => self.regex_match(input), Operator::Or => { // OR operator stands for OR-chained EQs (or INLIST in other words) let mut result: BooleanArray = vec![false; input_len].into(); @@ -204,23 +221,54 @@ impl SimpleFilterEvaluator { .map(|array| array.values().clone()) } - fn regex_match( - &self, - input: &impl Datum, - ignore_case: bool, - negative: bool, - ) -> std::result::Result { + /// Builds a regex pattern from a scalar value and operator. + /// Returns the `(regex, negative)` and if successful. + /// + /// Returns `Err` if + /// - the value is not a string + /// - the regex pattern is invalid + /// + /// The regex is `None` if + /// - the operator is not a regex operator + /// - the pattern is empty + fn maybe_build_regex( + operator: Operator, + value: &ScalarValue, + ) -> Result<(Option, bool), ArrowError> { + let (ignore_case, negative) = match operator { + Operator::RegexMatch => (false, false), + Operator::RegexIMatch => (true, false), + Operator::RegexNotMatch => (false, true), + Operator::RegexNotIMatch => (true, true), + _ => return Ok((None, false)), + }; let flag = if ignore_case { Some("i") } else { None }; + let regex = value + .try_as_str() + .ok_or_else(|| ArrowError::CastError(format!("Cannot cast {:?} to str", value)))? + .ok_or_else(|| ArrowError::CastError("Regex should not be null".to_string()))?; + let pattern = match flag { + Some(flag) => format!("(?{flag}){regex}"), + None => regex.to_string(), + }; + if pattern.is_empty() { + Ok((None, negative)) + } else { + Regex::new(pattern.as_str()) + .map_err(|e| { + ArrowError::ComputeError(format!("Regular expression did not compile: {e:?}")) + }) + .map(|regex| (Some(regex), negative)) + } + } + + fn regex_match(&self, input: &impl Datum) -> std::result::Result { let array = input.get().0; let string_array = as_string_array(array).map_err(|_| { ArrowError::CastError(format!("Cannot cast {:?} to StringArray", array)) })?; - let literal_array = self.literal.clone().into_inner(); - let regex_array = as_string_array(&literal_array).map_err(|_| { - ArrowError::CastError(format!("Cannot cast {:?} to StringArray", literal_array)) - })?; - let mut result = regexp::regexp_is_match_scalar(string_array, regex_array.value(0), flag)?; - if negative { + let mut result = regexp_is_match_scalar(string_array, self.regex.as_ref())?; + if self.regex_negative { result = datatypes::compute::not(&result)?; } Ok(result) @@ -254,6 +302,44 @@ pub fn batch_filter( }) } +/// The same as arrow [regexp_is_match_scalar()](datatypes::compute::kernels::regexp::regexp_is_match_scalar()) +/// with pre-compiled regex. +/// See for the implementation details. +pub fn regexp_is_match_scalar<'a, S>( + array: &'a S, + regex: Option<&Regex>, +) -> Result +where + &'a S: StringArrayType<'a>, +{ + let null_bit_buffer = array.nulls().map(|x| x.inner().sliced()); + let mut result = BooleanBufferBuilder::new(array.len()); + + if let Some(re) = regex { + for i in 0..array.len() { + let value = array.value(i); + result.append(re.is_match(value)); + } + } else { + result.append_n(array.len(), true); + } + + let buffer = result.into(); + let data = unsafe { + ArrayData::new_unchecked( + DataType::Boolean, + array.len(), + None, + null_bit_buffer, + 0, + vec![buffer], + vec![], + ) + }; + + Ok(BooleanArray::from(data)) +} + #[cfg(test)] mod test { @@ -446,4 +532,84 @@ mod test { .unwrap(); assert_eq!(col_filtered, &expected); } + + #[test] + fn test_maybe_build_regex() { + // Test case for RegexMatch (case sensitive, non-negative) + let (regex, negative) = SimpleFilterEvaluator::maybe_build_regex( + Operator::RegexMatch, + &ScalarValue::Utf8(Some("a.*b".to_string())), + ) + .unwrap(); + assert!(regex.is_some()); + assert!(!negative); + assert!(regex.unwrap().is_match("axxb")); + + // Test case for RegexIMatch (case insensitive, non-negative) + let (regex, negative) = SimpleFilterEvaluator::maybe_build_regex( + Operator::RegexIMatch, + &ScalarValue::Utf8(Some("a.*b".to_string())), + ) + .unwrap(); + assert!(regex.is_some()); + assert!(!negative); + assert!(regex.unwrap().is_match("AxxB")); + + // Test case for RegexNotMatch (case sensitive, negative) + let (regex, negative) = SimpleFilterEvaluator::maybe_build_regex( + Operator::RegexNotMatch, + &ScalarValue::Utf8(Some("a.*b".to_string())), + ) + .unwrap(); + assert!(regex.is_some()); + assert!(negative); + + // Test case for RegexNotIMatch (case insensitive, negative) + let (regex, negative) = SimpleFilterEvaluator::maybe_build_regex( + Operator::RegexNotIMatch, + &ScalarValue::Utf8(Some("a.*b".to_string())), + ) + .unwrap(); + assert!(regex.is_some()); + assert!(negative); + + // Test with empty regex pattern + let (regex, negative) = SimpleFilterEvaluator::maybe_build_regex( + Operator::RegexMatch, + &ScalarValue::Utf8(Some("".to_string())), + ) + .unwrap(); + assert!(regex.is_none()); + assert!(!negative); + + // Test with non-regex operator + let (regex, negative) = SimpleFilterEvaluator::maybe_build_regex( + Operator::Eq, + &ScalarValue::Utf8(Some("a.*b".to_string())), + ) + .unwrap(); + assert!(regex.is_none()); + assert!(!negative); + + // Test with invalid regex pattern + let result = SimpleFilterEvaluator::maybe_build_regex( + Operator::RegexMatch, + &ScalarValue::Utf8(Some("a(b".to_string())), + ); + assert!(result.is_err()); + + // Test with non-string value + let result = SimpleFilterEvaluator::maybe_build_regex( + Operator::RegexMatch, + &ScalarValue::Int64(Some(123)), + ); + assert!(result.is_err()); + + // Test with null value + let result = SimpleFilterEvaluator::maybe_build_regex( + Operator::RegexMatch, + &ScalarValue::Utf8(None), + ); + assert!(result.is_err()); + } } diff --git a/src/common/wal/src/config.rs b/src/common/wal/src/config.rs index 831faef772..921c1faaa3 100644 --- a/src/common/wal/src/config.rs +++ b/src/common/wal/src/config.rs @@ -15,6 +15,8 @@ pub mod kafka; pub mod raft_engine; +use std::time::Duration; + use serde::{Deserialize, Serialize}; use crate::config::kafka::{DatanodeKafkaConfig, MetasrvKafkaConfig}; @@ -53,11 +55,32 @@ impl From for MetasrvWalConfig { connection: config.connection, kafka_topic: config.kafka_topic, auto_create_topics: config.auto_create_topics, + auto_prune_interval: config.auto_prune_interval, + trigger_flush_threshold: config.trigger_flush_threshold, + auto_prune_parallelism: config.auto_prune_parallelism, }), } } } +impl MetasrvWalConfig { + /// Returns if active wal pruning is enabled. + pub fn enable_active_wal_pruning(&self) -> bool { + match self { + MetasrvWalConfig::RaftEngine => false, + MetasrvWalConfig::Kafka(config) => config.auto_prune_interval > Duration::ZERO, + } + } + + /// Gets the kafka connection config. + pub fn remote_wal_options(&self) -> Option<&MetasrvKafkaConfig> { + match self { + MetasrvWalConfig::RaftEngine => None, + MetasrvWalConfig::Kafka(config) => Some(config), + } + } +} + impl From for DatanodeWalConfig { fn from(config: MetasrvWalConfig) -> Self { match config { @@ -181,6 +204,9 @@ mod tests { create_topic_timeout: Duration::from_secs(30), }, auto_create_topics: true, + auto_prune_interval: Duration::from_secs(0), + trigger_flush_threshold: 0, + auto_prune_parallelism: 10, }; assert_eq!(metasrv_wal_config, MetasrvWalConfig::Kafka(expected)); diff --git a/src/common/wal/src/config/kafka/common.rs b/src/common/wal/src/config/kafka/common.rs index ea58a3d49e..e4763687cd 100644 --- a/src/common/wal/src/config/kafka/common.rs +++ b/src/common/wal/src/config/kafka/common.rs @@ -30,6 +30,13 @@ pub const DEFAULT_BACKOFF_CONFIG: BackoffConfig = BackoffConfig { deadline: Some(Duration::from_secs(120)), }; +/// Default interval for auto WAL pruning. +pub const DEFAULT_AUTO_PRUNE_INTERVAL: Duration = Duration::ZERO; +/// Default limit for concurrent auto pruning tasks. +pub const DEFAULT_AUTO_PRUNE_PARALLELISM: usize = 10; +/// Default interval for sending flush request to regions when pruning remote WAL. +pub const DEFAULT_TRIGGER_FLUSH_THRESHOLD: u64 = 0; + use crate::error::{self, Result}; use crate::{TopicSelectorType, BROKER_ENDPOINT, TOPIC_NAME_PREFIX}; diff --git a/src/common/wal/src/config/kafka/datanode.rs b/src/common/wal/src/config/kafka/datanode.rs index 77cf05397d..278e3dd1a5 100644 --- a/src/common/wal/src/config/kafka/datanode.rs +++ b/src/common/wal/src/config/kafka/datanode.rs @@ -17,7 +17,10 @@ use std::time::Duration; use common_base::readable_size::ReadableSize; use serde::{Deserialize, Serialize}; -use crate::config::kafka::common::{KafkaConnectionConfig, KafkaTopicConfig}; +use crate::config::kafka::common::{ + KafkaConnectionConfig, KafkaTopicConfig, DEFAULT_AUTO_PRUNE_INTERVAL, + DEFAULT_AUTO_PRUNE_PARALLELISM, DEFAULT_TRIGGER_FLUSH_THRESHOLD, +}; /// Kafka wal configurations for datanode. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -44,6 +47,14 @@ pub struct DatanodeKafkaConfig { pub dump_index_interval: Duration, /// Ignore missing entries during read WAL. pub overwrite_entry_start_id: bool, + // Interval of WAL pruning. + #[serde(with = "humantime_serde")] + pub auto_prune_interval: Duration, + // Threshold for sending flush request when pruning remote WAL. + // `None` stands for never sending flush request. + pub trigger_flush_threshold: u64, + // Limit of concurrent active pruning procedures. + pub auto_prune_parallelism: usize, } impl Default for DatanodeKafkaConfig { @@ -58,6 +69,9 @@ impl Default for DatanodeKafkaConfig { create_index: true, dump_index_interval: Duration::from_secs(60), overwrite_entry_start_id: false, + auto_prune_interval: DEFAULT_AUTO_PRUNE_INTERVAL, + trigger_flush_threshold: DEFAULT_TRIGGER_FLUSH_THRESHOLD, + auto_prune_parallelism: DEFAULT_AUTO_PRUNE_PARALLELISM, } } } diff --git a/src/common/wal/src/config/kafka/metasrv.rs b/src/common/wal/src/config/kafka/metasrv.rs index 27df3569b8..d20100af89 100644 --- a/src/common/wal/src/config/kafka/metasrv.rs +++ b/src/common/wal/src/config/kafka/metasrv.rs @@ -12,9 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::time::Duration; + use serde::{Deserialize, Serialize}; -use crate::config::kafka::common::{KafkaConnectionConfig, KafkaTopicConfig}; +use crate::config::kafka::common::{ + KafkaConnectionConfig, KafkaTopicConfig, DEFAULT_AUTO_PRUNE_INTERVAL, + DEFAULT_AUTO_PRUNE_PARALLELISM, DEFAULT_TRIGGER_FLUSH_THRESHOLD, +}; /// Kafka wal configurations for metasrv. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -28,6 +33,14 @@ pub struct MetasrvKafkaConfig { pub kafka_topic: KafkaTopicConfig, // Automatically create topics for WAL. pub auto_create_topics: bool, + // Interval of WAL pruning. + #[serde(with = "humantime_serde")] + pub auto_prune_interval: Duration, + // Threshold for sending flush request when pruning remote WAL. + // `None` stands for never sending flush request. + pub trigger_flush_threshold: u64, + // Limit of concurrent active pruning procedures. + pub auto_prune_parallelism: usize, } impl Default for MetasrvKafkaConfig { @@ -36,6 +49,9 @@ impl Default for MetasrvKafkaConfig { connection: Default::default(), kafka_topic: Default::default(), auto_create_topics: true, + auto_prune_interval: DEFAULT_AUTO_PRUNE_INTERVAL, + trigger_flush_threshold: DEFAULT_TRIGGER_FLUSH_THRESHOLD, + auto_prune_parallelism: DEFAULT_AUTO_PRUNE_PARALLELISM, } } } diff --git a/src/datanode/src/config.rs b/src/datanode/src/config.rs index 322f337ba3..7d63057a72 100644 --- a/src/datanode/src/config.rs +++ b/src/datanode/src/config.rs @@ -26,6 +26,7 @@ use file_engine::config::EngineConfig as FileEngineConfig; use meta_client::MetaClientOptions; use metric_engine::config::EngineConfig as MetricEngineConfig; use mito2::config::MitoConfig; +use query::options::QueryOptions; use serde::{Deserialize, Serialize}; use servers::export_metrics::ExportMetricsOption; use servers::grpc::GrpcOptions; @@ -375,6 +376,7 @@ pub struct DatanodeOptions { pub enable_telemetry: bool, pub export_metrics: ExportMetricsOption, pub tracing: TracingOptions, + pub query: QueryOptions, /// Deprecated options, please use the new options instead. #[deprecated(note = "Please use `grpc.addr` instead.")] @@ -412,6 +414,7 @@ impl Default for DatanodeOptions { enable_telemetry: true, export_metrics: ExportMetricsOption::default(), tracing: TracingOptions::default(), + query: QueryOptions::default(), // Deprecated options rpc_addr: None, diff --git a/src/datanode/src/datanode.rs b/src/datanode/src/datanode.rs index b32a1668c6..25cb5a9d0c 100644 --- a/src/datanode/src/datanode.rs +++ b/src/datanode/src/datanode.rs @@ -57,9 +57,9 @@ use tokio::sync::Notify; use crate::config::{DatanodeOptions, RegionEngineConfig, StorageConfig}; use crate::error::{ - self, BuildMitoEngineSnafu, CreateDirSnafu, GetMetadataSnafu, MissingCacheSnafu, - MissingKvBackendSnafu, MissingNodeIdSnafu, OpenLogStoreSnafu, Result, ShutdownInstanceSnafu, - ShutdownServerSnafu, StartServerSnafu, + self, BuildMetricEngineSnafu, BuildMitoEngineSnafu, CreateDirSnafu, GetMetadataSnafu, + MissingCacheSnafu, MissingKvBackendSnafu, MissingNodeIdSnafu, OpenLogStoreSnafu, Result, + ShutdownInstanceSnafu, ShutdownServerSnafu, StartServerSnafu, }; use crate::event_listener::{ new_region_server_event_channel, NoopRegionServerEventListener, RegionServerEventListenerRef, @@ -359,6 +359,7 @@ impl DatanodeBuilder { None, false, self.plugins.clone(), + opts.query.clone(), ); let query_engine = query_engine_factory.query_engine(); @@ -415,10 +416,11 @@ impl DatanodeBuilder { ) .await?; - let metric_engine = MetricEngine::new( + let metric_engine = MetricEngine::try_new( mito_engine.clone(), metric_engine_config.take().unwrap_or_default(), - ); + ) + .context(BuildMetricEngineSnafu)?; engines.push(Arc::new(mito_engine) as _); engines.push(Arc::new(metric_engine) as _); } diff --git a/src/datanode/src/error.rs b/src/datanode/src/error.rs index 6c63e9115f..84e8168d91 100644 --- a/src/datanode/src/error.rs +++ b/src/datanode/src/error.rs @@ -336,6 +336,13 @@ pub enum Error { location: Location, }, + #[snafu(display("Failed to build metric engine"))] + BuildMetricEngine { + source: metric_engine::error::Error, + #[snafu(implicit)] + location: Location, + }, + #[snafu(display("Failed to serialize options to TOML"))] TomlFormat { #[snafu(implicit)] @@ -452,6 +459,7 @@ impl ErrorExt for Error { FindLogicalRegions { source, .. } => source.status_code(), BuildMitoEngine { source, .. } => source.status_code(), + BuildMetricEngine { source, .. } => source.status_code(), ConcurrentQueryLimiterClosed { .. } | ConcurrentQueryLimiterTimeout { .. } => { StatusCode::RegionBusy } diff --git a/src/datanode/src/heartbeat/handler.rs b/src/datanode/src/heartbeat/handler.rs index bf93d15128..17950847ed 100644 --- a/src/datanode/src/heartbeat/handler.rs +++ b/src/datanode/src/heartbeat/handler.rs @@ -220,7 +220,6 @@ mod tests { let instruction = Instruction::DowngradeRegion(DowngradeRegion { region_id: RegionId::new(2048, 1), flush_timeout: Some(Duration::from_secs(1)), - reject_write: false, }); assert!(heartbeat_handler .is_acceptable(&heartbeat_env.create_handler_ctx((meta.clone(), instruction)))); @@ -229,6 +228,7 @@ mod tests { let instruction = Instruction::UpgradeRegion(UpgradeRegion { region_id, last_entry_id: None, + metadata_last_entry_id: None, replay_timeout: None, location_id: None, }); @@ -419,7 +419,6 @@ mod tests { let instruction = Instruction::DowngradeRegion(DowngradeRegion { region_id, flush_timeout: Some(Duration::from_secs(1)), - reject_write: false, }); let mut ctx = heartbeat_env.create_handler_ctx((meta, instruction)); @@ -442,7 +441,6 @@ mod tests { let instruction = Instruction::DowngradeRegion(DowngradeRegion { region_id: RegionId::new(2048, 1), flush_timeout: Some(Duration::from_secs(1)), - reject_write: false, }); let mut ctx = heartbeat_env.create_handler_ctx((meta, instruction)); let control = heartbeat_handler.handle(&mut ctx).await.unwrap(); diff --git a/src/datanode/src/heartbeat/handler/downgrade_region.rs b/src/datanode/src/heartbeat/handler/downgrade_region.rs index 216a460921..d82e4e065b 100644 --- a/src/datanode/src/heartbeat/handler/downgrade_region.rs +++ b/src/datanode/src/heartbeat/handler/downgrade_region.rs @@ -14,7 +14,7 @@ use common_meta::instruction::{DowngradeRegion, DowngradeRegionReply, InstructionReply}; use common_telemetry::tracing::info; -use common_telemetry::warn; +use common_telemetry::{error, warn}; use futures_util::future::BoxFuture; use store_api::region_engine::{SetRegionRoleStateResponse, SettableRegionRoleState}; use store_api::region_request::{RegionFlushRequest, RegionRequest}; @@ -33,25 +33,32 @@ impl HandlerContext { .set_region_role_state_gracefully(region_id, SettableRegionRoleState::Follower) .await { - Ok(SetRegionRoleStateResponse::Success { last_entry_id }) => { + Ok(SetRegionRoleStateResponse::Success(success)) => { Some(InstructionReply::DowngradeRegion(DowngradeRegionReply { - last_entry_id, + last_entry_id: success.last_entry_id(), + metadata_last_entry_id: success.metadata_last_entry_id(), exists: true, error: None, })) } Ok(SetRegionRoleStateResponse::NotFound) => { + warn!("Region: {region_id} is not found"); Some(InstructionReply::DowngradeRegion(DowngradeRegionReply { last_entry_id: None, + metadata_last_entry_id: None, exists: false, error: None, })) } - Err(err) => Some(InstructionReply::DowngradeRegion(DowngradeRegionReply { - last_entry_id: None, - exists: true, - error: Some(format!("{err:?}")), - })), + Err(err) => { + error!(err; "Failed to convert region to {}", SettableRegionRoleState::Follower); + Some(InstructionReply::DowngradeRegion(DowngradeRegionReply { + last_entry_id: None, + metadata_last_entry_id: None, + exists: true, + error: Some(format!("{err:?}")), + })) + } } } @@ -60,7 +67,6 @@ impl HandlerContext { DowngradeRegion { region_id, flush_timeout, - reject_write, }: DowngradeRegion, ) -> BoxFuture<'static, Option> { Box::pin(async move { @@ -68,6 +74,7 @@ impl HandlerContext { warn!("Region: {region_id} is not found"); return Some(InstructionReply::DowngradeRegion(DowngradeRegionReply { last_entry_id: None, + metadata_last_entry_id: None, exists: false, error: None, })); @@ -89,33 +96,35 @@ impl HandlerContext { return self.downgrade_to_follower_gracefully(region_id).await; }; - if reject_write { - // Sets region to downgrading, the downgrading region will reject all write requests. - match self - .region_server - .set_region_role_state_gracefully( - region_id, - SettableRegionRoleState::DowngradingLeader, - ) - .await - { - Ok(SetRegionRoleStateResponse::Success { .. }) => {} - Ok(SetRegionRoleStateResponse::NotFound) => { - warn!("Region: {region_id} is not found"); - return Some(InstructionReply::DowngradeRegion(DowngradeRegionReply { - last_entry_id: None, - exists: false, - error: None, - })); - } - Err(err) => { - warn!(err; "Failed to convert region to downgrading leader"); - return Some(InstructionReply::DowngradeRegion(DowngradeRegionReply { - last_entry_id: None, - exists: true, - error: Some(format!("{err:?}")), - })); - } + // Sets region to downgrading, + // the downgrading region will reject all write requests. + // However, the downgrading region will still accept read, flush requests. + match self + .region_server + .set_region_role_state_gracefully( + region_id, + SettableRegionRoleState::DowngradingLeader, + ) + .await + { + Ok(SetRegionRoleStateResponse::Success { .. }) => {} + Ok(SetRegionRoleStateResponse::NotFound) => { + warn!("Region: {region_id} is not found"); + return Some(InstructionReply::DowngradeRegion(DowngradeRegionReply { + last_entry_id: None, + metadata_last_entry_id: None, + exists: false, + error: None, + })); + } + Err(err) => { + error!(err; "Failed to convert region to downgrading leader"); + return Some(InstructionReply::DowngradeRegion(DowngradeRegionReply { + last_entry_id: None, + metadata_last_entry_id: None, + exists: true, + error: Some(format!("{err:?}")), + })); } } @@ -144,20 +153,25 @@ impl HandlerContext { } let mut watcher = register_result.into_watcher(); - let result = self.catchup_tasks.wait(&mut watcher, flush_timeout).await; + let result = self.downgrade_tasks.wait(&mut watcher, flush_timeout).await; match result { WaitResult::Timeout => { Some(InstructionReply::DowngradeRegion(DowngradeRegionReply { last_entry_id: None, + metadata_last_entry_id: None, exists: true, - error: Some(format!("Flush region: {region_id} is timeout")), + error: Some(format!( + "Flush region timeout, region: {region_id}, timeout: {:?}", + flush_timeout + )), })) } WaitResult::Finish(Ok(_)) => self.downgrade_to_follower_gracefully(region_id).await, WaitResult::Finish(Err(err)) => { Some(InstructionReply::DowngradeRegion(DowngradeRegionReply { last_entry_id: None, + metadata_last_entry_id: None, exists: true, error: Some(format!("{err:?}")), })) @@ -174,7 +188,9 @@ mod tests { use common_meta::instruction::{DowngradeRegion, InstructionReply}; use mito2::engine::MITO_ENGINE_NAME; - use store_api::region_engine::{RegionRole, SetRegionRoleStateResponse}; + use store_api::region_engine::{ + RegionRole, SetRegionRoleStateResponse, SetRegionRoleStateSuccess, + }; use store_api::region_request::RegionRequest; use store_api::storage::RegionId; use tokio::time::Instant; @@ -198,7 +214,6 @@ mod tests { .handle_downgrade_region_instruction(DowngradeRegion { region_id, flush_timeout, - reject_write: false, }) .await; assert_matches!(reply, Some(InstructionReply::DowngradeRegion(_))); @@ -227,7 +242,9 @@ mod tests { Ok(0) })); region_engine.handle_set_readonly_gracefully_mock_fn = Some(Box::new(|_| { - Ok(SetRegionRoleStateResponse::success(Some(1024))) + Ok(SetRegionRoleStateResponse::success( + SetRegionRoleStateSuccess::mito(1024), + )) })) }); mock_region_server.register_test_region(region_id, mock_engine); @@ -240,7 +257,6 @@ mod tests { .handle_downgrade_region_instruction(DowngradeRegion { region_id, flush_timeout, - reject_write: false, }) .await; assert_matches!(reply, Some(InstructionReply::DowngradeRegion(_))); @@ -262,7 +278,9 @@ mod tests { region_engine.mock_role = Some(Some(RegionRole::Leader)); region_engine.handle_request_delay = Some(Duration::from_secs(100)); region_engine.handle_set_readonly_gracefully_mock_fn = Some(Box::new(|_| { - Ok(SetRegionRoleStateResponse::success(Some(1024))) + Ok(SetRegionRoleStateResponse::success( + SetRegionRoleStateSuccess::mito(1024), + )) })) }); mock_region_server.register_test_region(region_id, mock_engine); @@ -274,7 +292,6 @@ mod tests { .handle_downgrade_region_instruction(DowngradeRegion { region_id, flush_timeout: Some(flush_timeout), - reject_write: false, }) .await; assert_matches!(reply, Some(InstructionReply::DowngradeRegion(_))); @@ -295,7 +312,9 @@ mod tests { region_engine.mock_role = Some(Some(RegionRole::Leader)); region_engine.handle_request_delay = Some(Duration::from_millis(300)); region_engine.handle_set_readonly_gracefully_mock_fn = Some(Box::new(|_| { - Ok(SetRegionRoleStateResponse::success(Some(1024))) + Ok(SetRegionRoleStateResponse::success( + SetRegionRoleStateSuccess::mito(1024), + )) })) }); mock_region_server.register_test_region(region_id, mock_engine); @@ -312,7 +331,6 @@ mod tests { .handle_downgrade_region_instruction(DowngradeRegion { region_id, flush_timeout, - reject_write: false, }) .await; assert_matches!(reply, Some(InstructionReply::DowngradeRegion(_))); @@ -327,7 +345,6 @@ mod tests { .handle_downgrade_region_instruction(DowngradeRegion { region_id, flush_timeout: Some(Duration::from_millis(500)), - reject_write: false, }) .await; assert_matches!(reply, Some(InstructionReply::DowngradeRegion(_))); @@ -356,7 +373,9 @@ mod tests { .fail() })); region_engine.handle_set_readonly_gracefully_mock_fn = Some(Box::new(|_| { - Ok(SetRegionRoleStateResponse::success(Some(1024))) + Ok(SetRegionRoleStateResponse::success( + SetRegionRoleStateSuccess::mito(1024), + )) })) }); mock_region_server.register_test_region(region_id, mock_engine); @@ -373,7 +392,6 @@ mod tests { .handle_downgrade_region_instruction(DowngradeRegion { region_id, flush_timeout, - reject_write: false, }) .await; assert_matches!(reply, Some(InstructionReply::DowngradeRegion(_))); @@ -388,7 +406,6 @@ mod tests { .handle_downgrade_region_instruction(DowngradeRegion { region_id, flush_timeout: Some(Duration::from_millis(500)), - reject_write: false, }) .await; assert_matches!(reply, Some(InstructionReply::DowngradeRegion(_))); @@ -419,7 +436,6 @@ mod tests { .handle_downgrade_region_instruction(DowngradeRegion { region_id, flush_timeout: None, - reject_write: false, }) .await; assert_matches!(reply, Some(InstructionReply::DowngradeRegion(_))); @@ -451,7 +467,6 @@ mod tests { .handle_downgrade_region_instruction(DowngradeRegion { region_id, flush_timeout: None, - reject_write: false, }) .await; assert_matches!(reply, Some(InstructionReply::DowngradeRegion(_))); diff --git a/src/datanode/src/heartbeat/handler/upgrade_region.rs b/src/datanode/src/heartbeat/handler/upgrade_region.rs index a23ae71a3d..b9a324c197 100644 --- a/src/datanode/src/heartbeat/handler/upgrade_region.rs +++ b/src/datanode/src/heartbeat/handler/upgrade_region.rs @@ -26,6 +26,7 @@ impl HandlerContext { UpgradeRegion { region_id, last_entry_id, + metadata_last_entry_id, replay_timeout, location_id, }: UpgradeRegion, @@ -63,6 +64,7 @@ impl HandlerContext { RegionRequest::Catchup(RegionCatchupRequest { set_writable: true, entry_id: last_entry_id, + metadata_entry_id: metadata_last_entry_id, location_id, }), ) @@ -147,6 +149,7 @@ mod tests { .handle_upgrade_region_instruction(UpgradeRegion { region_id, last_entry_id: None, + metadata_last_entry_id: None, replay_timeout, location_id: None, }) @@ -185,6 +188,7 @@ mod tests { .handle_upgrade_region_instruction(UpgradeRegion { region_id, last_entry_id: None, + metadata_last_entry_id: None, replay_timeout, location_id: None, }) @@ -224,6 +228,7 @@ mod tests { .handle_upgrade_region_instruction(UpgradeRegion { region_id, last_entry_id: None, + metadata_last_entry_id: None, replay_timeout, location_id: None, }) @@ -267,6 +272,7 @@ mod tests { region_id, replay_timeout, last_entry_id: None, + metadata_last_entry_id: None, location_id: None, }) .await; @@ -284,6 +290,7 @@ mod tests { .handle_upgrade_region_instruction(UpgradeRegion { region_id, last_entry_id: None, + metadata_last_entry_id: None, replay_timeout: Some(Duration::from_millis(500)), location_id: None, }) @@ -326,6 +333,7 @@ mod tests { .handle_upgrade_region_instruction(UpgradeRegion { region_id, last_entry_id: None, + metadata_last_entry_id: None, replay_timeout: None, location_id: None, }) @@ -344,6 +352,7 @@ mod tests { .handle_upgrade_region_instruction(UpgradeRegion { region_id, last_entry_id: None, + metadata_last_entry_id: None, replay_timeout: Some(Duration::from_millis(200)), location_id: None, }) diff --git a/src/datanode/src/metrics.rs b/src/datanode/src/metrics.rs index d11e8af9fe..12ac482826 100644 --- a/src/datanode/src/metrics.rs +++ b/src/datanode/src/metrics.rs @@ -20,13 +20,21 @@ pub const REGION_REQUEST_TYPE: &str = "datanode_region_request_type"; pub const REGION_ROLE: &str = "region_role"; pub const REGION_ID: &str = "region_id"; +pub const RESULT_TYPE: &str = "result"; lazy_static! { /// The elapsed time of handling a request in the region_server. pub static ref HANDLE_REGION_REQUEST_ELAPSED: HistogramVec = register_histogram_vec!( "greptime_datanode_handle_region_request_elapsed", "datanode handle region request elapsed", - &[REGION_REQUEST_TYPE] + &[REGION_ID, REGION_REQUEST_TYPE] + ) + .unwrap(); + /// The number of rows in region request received by region server, labeled with request type. + pub static ref REGION_CHANGED_ROW_COUNT: IntCounterVec = register_int_counter_vec!( + "greptime_datanode_region_changed_row_count", + "datanode region changed row count", + &[REGION_ID, REGION_REQUEST_TYPE] ) .unwrap(); /// The elapsed time since the last received heartbeat. @@ -64,7 +72,7 @@ lazy_static! { pub static ref HEARTBEAT_RECV_COUNT: IntCounterVec = register_int_counter_vec!( "greptime_datanode_heartbeat_recv_count", "datanode heartbeat received", - &["result"] + &[RESULT_TYPE] ) .unwrap(); } diff --git a/src/datanode/src/region_server.rs b/src/datanode/src/region_server.rs index c05291050c..9bbb899bf1 100644 --- a/src/datanode/src/region_server.rs +++ b/src/datanode/src/region_server.rs @@ -19,7 +19,8 @@ use std::sync::{Arc, RwLock}; use std::time::Duration; use api::region::RegionResponse; -use api::v1::region::{region_request, RegionResponse as RegionResponseV1}; +use api::v1::region::sync_request::ManifestInfo; +use api::v1::region::{region_request, RegionResponse as RegionResponseV1, SyncRequest}; use api::v1::{ResponseHeader, Status}; use arrow_flight::{FlightData, Ticket}; use async_trait::async_trait; @@ -308,22 +309,6 @@ impl RegionServer { .with_context(|_| HandleRegionRequestSnafu { region_id }) } - pub async fn sync_region_manifest( - &self, - region_id: RegionId, - manifest_info: RegionManifestInfo, - ) -> Result<()> { - let engine = self - .inner - .region_map - .get(®ion_id) - .with_context(|| RegionNotFoundSnafu { region_id })?; - engine - .sync_region(region_id, manifest_info) - .await - .with_context(|_| HandleRegionRequestSnafu { region_id }) - } - /// Set region role state gracefully. /// /// For [SettableRegionRoleState::Follower]: @@ -452,6 +437,52 @@ impl RegionServer { extensions, }) } + + async fn handle_sync_region_request(&self, request: &SyncRequest) -> Result { + let region_id = RegionId::from_u64(request.region_id); + let manifest_info = request + .manifest_info + .context(error::MissingRequiredFieldSnafu { + name: "manifest_info", + })?; + + let manifest_info = match manifest_info { + ManifestInfo::MitoManifestInfo(info) => { + RegionManifestInfo::mito(info.data_manifest_version, 0) + } + ManifestInfo::MetricManifestInfo(info) => RegionManifestInfo::metric( + info.data_manifest_version, + 0, + info.metadata_manifest_version, + 0, + ), + }; + + let tracing_context = TracingContext::from_current_span(); + let span = tracing_context.attach(info_span!("RegionServer::handle_sync_region_request")); + + self.sync_region(region_id, manifest_info) + .trace(span) + .await + .map(|_| RegionResponse::new(AffectedRows::default())) + } + + /// Sync region manifest and registers new opened logical regions. + pub async fn sync_region( + &self, + region_id: RegionId, + manifest_info: RegionManifestInfo, + ) -> Result<()> { + let engine_with_status = self + .inner + .region_map + .get(®ion_id) + .with_context(|| RegionNotFoundSnafu { region_id })?; + + self.inner + .handle_sync_region(engine_with_status.engine(), region_id, manifest_info) + .await + } } #[async_trait] @@ -464,6 +495,9 @@ impl RegionServerHandler for RegionServer { region_request::Body::Inserts(_) | region_request::Body::Deletes(_) => { self.handle_requests_in_parallel(request).await } + region_request::Body::Sync(sync_request) => { + self.handle_sync_region_request(sync_request).await + } _ => self.handle_requests_in_serial(request).await, } .map_err(BoxedError::new) @@ -526,6 +560,15 @@ impl RegionEngineWithStatus { RegionEngineWithStatus::Ready(engine) => engine, } } + + /// Returns [RegionEngineRef] reference. + pub fn engine(&self) -> &RegionEngineRef { + match self { + RegionEngineWithStatus::Registering(engine) => engine, + RegionEngineWithStatus::Deregistering(engine) => engine, + RegionEngineWithStatus::Ready(engine) => engine, + } + } } impl Deref for RegionEngineWithStatus { @@ -665,18 +708,20 @@ impl RegionServerInner { }, None => return Ok(CurrentEngine::EarlyReturn(0)), }, - RegionChange::None | RegionChange::Catchup => match current_region_status { - Some(status) => match status.clone() { - RegionEngineWithStatus::Registering(_) => { - return error::RegionNotReadySnafu { region_id }.fail() - } - RegionEngineWithStatus::Deregistering(_) => { - return error::RegionNotFoundSnafu { region_id }.fail() - } - RegionEngineWithStatus::Ready(engine) => engine, - }, - None => return error::RegionNotFoundSnafu { region_id }.fail(), - }, + RegionChange::None | RegionChange::Catchup | RegionChange::Ingest => { + match current_region_status { + Some(status) => match status.clone() { + RegionEngineWithStatus::Registering(_) => { + return error::RegionNotReadySnafu { region_id }.fail() + } + RegionEngineWithStatus::Deregistering(_) => { + return error::RegionNotFoundSnafu { region_id }.fail() + } + RegionEngineWithStatus::Ready(engine) => engine, + }, + None => return error::RegionNotFoundSnafu { region_id }.fail(), + } + } }; Ok(CurrentEngine::Engine(engine)) @@ -834,8 +879,8 @@ impl RegionServerInner { match result { Ok(result) => { - for (region_id, region_change) in region_changes { - self.set_region_status_ready(region_id, engine.clone(), region_change) + for (region_id, region_change) in ®ion_changes { + self.set_region_status_ready(*region_id, engine.clone(), *region_change) .await?; } @@ -860,8 +905,9 @@ impl RegionServerInner { request: RegionRequest, ) -> Result { let request_type = request.request_type(); + let region_id_str = region_id.to_string(); let _timer = crate::metrics::HANDLE_REGION_REQUEST_ELAPSED - .with_label_values(&[request_type]) + .with_label_values(&[®ion_id_str, request_type]) .start_timer(); let region_change = match &request { @@ -874,9 +920,10 @@ impl RegionServerInner { RegionChange::Register(attribute) } RegionRequest::Close(_) | RegionRequest::Drop(_) => RegionChange::Deregisters, - RegionRequest::Put(_) - | RegionRequest::Delete(_) - | RegionRequest::Alter(_) + RegionRequest::Put(_) | RegionRequest::Delete(_) | RegionRequest::BulkInserts(_) => { + RegionChange::Ingest + } + RegionRequest::Alter(_) | RegionRequest::Flush(_) | RegionRequest::Compact(_) | RegionRequest::Truncate(_) => RegionChange::None, @@ -897,9 +944,16 @@ impl RegionServerInner { .with_context(|_| HandleRegionRequestSnafu { region_id }) { Ok(result) => { + // Update metrics + if matches!(region_change, RegionChange::Ingest) { + crate::metrics::REGION_CHANGED_ROW_COUNT + .with_label_values(&[®ion_id_str, request_type]) + .inc_by(result.affected_rows as u64); + } // Sets corresponding region status to ready. - self.set_region_status_ready(region_id, engine, region_change) + self.set_region_status_ready(region_id, engine.clone(), region_change) .await?; + Ok(RegionResponse { affected_rows: result.affected_rows, extensions: result.extensions, @@ -913,6 +967,32 @@ impl RegionServerInner { } } + /// Handles the sync region request. + pub async fn handle_sync_region( + &self, + engine: &RegionEngineRef, + region_id: RegionId, + manifest_info: RegionManifestInfo, + ) -> Result<()> { + let Some(new_opened_regions) = engine + .sync_region(region_id, manifest_info) + .await + .with_context(|_| HandleRegionRequestSnafu { region_id })? + .new_opened_logical_region_ids() + else { + warn!("No new opened logical regions"); + return Ok(()); + }; + + for region in new_opened_regions { + self.region_map + .insert(region, RegionEngineWithStatus::Ready(engine.clone())); + info!("Logical region {} is registered!", region); + } + + Ok(()) + } + fn set_region_status_not_ready( &self, region_id: RegionId, @@ -943,7 +1023,7 @@ impl RegionServerInner { region_change: RegionChange, ) { match region_change { - RegionChange::None => {} + RegionChange::None | RegionChange::Ingest => {} RegionChange::Register(_) => { self.region_map.remove(®ion_id); } @@ -963,7 +1043,7 @@ impl RegionServerInner { ) -> Result<()> { let engine_type = engine.name(); match region_change { - RegionChange::None => {} + RegionChange::None | RegionChange::Ingest => {} RegionChange::Register(attribute) => { info!( "Region {region_id} is registered to engine {}", @@ -1029,7 +1109,7 @@ impl RegionServerInner { for region in logical_regions { self.region_map .insert(region, RegionEngineWithStatus::Ready(engine.clone())); - debug!("Logical region {} is registered!", region); + info!("Logical region {} is registered!", region); } Ok(()) } @@ -1104,6 +1184,7 @@ enum RegionChange { Register(RegionAttribute), Deregisters, Catchup, + Ingest, } fn is_metric_engine(engine: &str) -> bool { diff --git a/src/datanode/src/tests.rs b/src/datanode/src/tests.rs index b349024cc9..f182e1c423 100644 --- a/src/datanode/src/tests.rs +++ b/src/datanode/src/tests.rs @@ -33,7 +33,7 @@ use session::context::QueryContextRef; use store_api::metadata::RegionMetadataRef; use store_api::region_engine::{ RegionEngine, RegionManifestInfo, RegionRole, RegionScannerRef, RegionStatistic, - SetRegionRoleStateResponse, SettableRegionRoleState, + SetRegionRoleStateResponse, SettableRegionRoleState, SyncManifestResponse, }; use store_api::region_request::{AffectedRows, RegionRequest}; use store_api::storage::{RegionId, ScanRequest, SequenceNumber}; @@ -250,7 +250,7 @@ impl RegionEngine for MockRegionEngine { &self, _region_id: RegionId, _manifest_info: RegionManifestInfo, - ) -> Result<(), BoxedError> { + ) -> Result { unimplemented!() } diff --git a/src/datatypes/src/schema/column_schema.rs b/src/datatypes/src/schema/column_schema.rs index 9a975c4008..376c9e6de0 100644 --- a/src/datatypes/src/schema/column_schema.rs +++ b/src/datatypes/src/schema/column_schema.rs @@ -537,8 +537,8 @@ impl fmt::Display for FulltextOptions { #[serde(rename_all = "kebab-case")] pub enum FulltextBackend { #[default] + Bloom, Tantivy, - Bloom, // TODO(zhongzc): when bloom is ready, use it as default } impl fmt::Display for FulltextBackend { diff --git a/src/datatypes/src/schema/constraint.rs b/src/datatypes/src/schema/constraint.rs index 1a2128c200..560500810f 100644 --- a/src/datatypes/src/schema/constraint.rs +++ b/src/datatypes/src/schema/constraint.rs @@ -253,9 +253,10 @@ fn create_current_timestamp_vector( data_type: &ConcreteDataType, num_rows: usize, ) -> Result { - let current_timestamp_vector = TimestampMillisecondVector::from_values( - std::iter::repeat(util::current_time_millis()).take(num_rows), - ); + let current_timestamp_vector = TimestampMillisecondVector::from_values(std::iter::repeat_n( + util::current_time_millis(), + num_rows, + )); if data_type.is_timestamp() { current_timestamp_vector.cast(data_type) } else { diff --git a/src/datatypes/src/vectors/constant.rs b/src/datatypes/src/vectors/constant.rs index 66587cf1d7..3ccade1392 100644 --- a/src/datatypes/src/vectors/constant.rs +++ b/src/datatypes/src/vectors/constant.rs @@ -198,8 +198,7 @@ impl fmt::Debug for ConstantVector { impl Serializable for ConstantVector { fn serialize_to_json(&self) -> Result> { - std::iter::repeat(self.get(0)) - .take(self.len()) + std::iter::repeat_n(self.get(0), self.len()) .map(serde_json::Value::try_from) .collect::>() .context(SerializeSnafu) diff --git a/src/datatypes/src/vectors/decimal.rs b/src/datatypes/src/vectors/decimal.rs index cce26e3e3e..e446b36de3 100644 --- a/src/datatypes/src/vectors/decimal.rs +++ b/src/datatypes/src/vectors/decimal.rs @@ -412,7 +412,7 @@ pub(crate) fn replicate_decimal128( // Safety: std::iter::Repeat and std::iter::Take implement TrustedLen. builder .mutable_array - .append_trusted_len_iter(std::iter::repeat(data).take(repeat_times)); + .append_trusted_len_iter(std::iter::repeat_n(data, repeat_times)); } } None => { diff --git a/src/datatypes/src/vectors/null.rs b/src/datatypes/src/vectors/null.rs index 292e2c5e33..e745ee13d6 100644 --- a/src/datatypes/src/vectors/null.rs +++ b/src/datatypes/src/vectors/null.rs @@ -120,9 +120,7 @@ impl fmt::Debug for NullVector { impl Serializable for NullVector { fn serialize_to_json(&self) -> Result> { - Ok(std::iter::repeat(serde_json::Value::Null) - .take(self.len()) - .collect()) + Ok(std::iter::repeat_n(serde_json::Value::Null, self.len()).collect()) } } diff --git a/src/datatypes/src/vectors/primitive.rs b/src/datatypes/src/vectors/primitive.rs index 7b059e0d07..f3e49183f5 100644 --- a/src/datatypes/src/vectors/primitive.rs +++ b/src/datatypes/src/vectors/primitive.rs @@ -388,7 +388,7 @@ pub(crate) fn replicate_primitive( // Safety: std::iter::Repeat and std::iter::Take implement TrustedLen. builder .mutable_array - .append_trusted_len_iter(std::iter::repeat(data).take(repeat_times)); + .append_trusted_len_iter(std::iter::repeat_n(data, repeat_times)); } } None => { diff --git a/src/file-engine/src/engine.rs b/src/file-engine/src/engine.rs index 9e9d1aa405..09a373caad 100644 --- a/src/file-engine/src/engine.rs +++ b/src/file-engine/src/engine.rs @@ -27,7 +27,8 @@ use snafu::{ensure, OptionExt}; use store_api::metadata::RegionMetadataRef; use store_api::region_engine::{ RegionEngine, RegionManifestInfo, RegionRole, RegionScannerRef, RegionStatistic, - SetRegionRoleStateResponse, SettableRegionRoleState, SinglePartitionScanner, + SetRegionRoleStateResponse, SetRegionRoleStateSuccess, SettableRegionRoleState, + SinglePartitionScanner, SyncManifestResponse, }; use store_api::region_request::{ AffectedRows, RegionCloseRequest, RegionCreateRequest, RegionDropRequest, RegionOpenRequest, @@ -132,7 +133,9 @@ impl RegionEngine for FileRegionEngine { let exists = self.inner.get_region(region_id).await.is_some(); if exists { - Ok(SetRegionRoleStateResponse::success(None)) + Ok(SetRegionRoleStateResponse::success( + SetRegionRoleStateSuccess::file(), + )) } else { Ok(SetRegionRoleStateResponse::NotFound) } @@ -142,9 +145,9 @@ impl RegionEngine for FileRegionEngine { &self, _region_id: RegionId, _manifest_info: RegionManifestInfo, - ) -> Result<(), BoxedError> { + ) -> Result { // File engine doesn't need to sync region manifest. - Ok(()) + Ok(SyncManifestResponse::NotSupported) } fn role(&self, region_id: RegionId) -> Option { diff --git a/src/flow/src/adapter.rs b/src/flow/src/adapter.rs index 1dd3e7e40e..a613e3f83a 100644 --- a/src/flow/src/adapter.rs +++ b/src/flow/src/adapter.rs @@ -16,7 +16,7 @@ //! and communicating with other parts of the database #![warn(unused_imports)] -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use std::sync::Arc; use std::time::{Duration, Instant, SystemTime}; @@ -32,6 +32,7 @@ use datatypes::value::Value; use greptime_proto::v1; use itertools::{EitherOrBoth, Itertools}; use meta_client::MetaClientOptions; +use query::options::QueryOptions; use query::QueryEngine; use serde::{Deserialize, Serialize}; use servers::grpc::GrpcOptions; @@ -55,8 +56,9 @@ use crate::error::{EvalSnafu, ExternalSnafu, InternalSnafu, InvalidQuerySnafu, U use crate::expr::Batch; use crate::metrics::{METRIC_FLOW_INSERT_ELAPSED, METRIC_FLOW_ROWS, METRIC_FLOW_RUN_INTERVAL_MS}; use crate::repr::{self, DiffRow, RelationDesc, Row, BATCH_SIZE}; +use crate::{CreateFlowArgs, FlowId, TableName}; -mod flownode_impl; +pub(crate) mod flownode_impl; mod parse_expr; pub(crate) mod refill; mod stat; @@ -77,11 +79,6 @@ pub const AUTO_CREATED_PLACEHOLDER_TS_COL: &str = "__ts_placeholder"; pub const AUTO_CREATED_UPDATE_AT_TS_COL: &str = "update_at"; -// TODO(discord9): refactor common types for flow to a separate module -/// FlowId is a unique identifier for a flow task -pub type FlowId = u64; -pub type TableName = [String; 3]; - /// Flow config that exists both in standalone&distributed mode #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(default)] @@ -109,6 +106,7 @@ pub struct FlownodeOptions { pub logging: LoggingOptions, pub tracing: TracingOptions, pub heartbeat: HeartbeatOptions, + pub query: QueryOptions, } impl Default for FlownodeOptions { @@ -122,6 +120,7 @@ impl Default for FlownodeOptions { logging: LoggingOptions::default(), tracing: TracingOptions::default(), heartbeat: HeartbeatOptions::default(), + query: QueryOptions::default(), } } } @@ -136,12 +135,13 @@ impl Configurable for FlownodeOptions { } /// Arc-ed FlowNodeManager, cheaper to clone -pub type FlowWorkerManagerRef = Arc; +pub type FlowStreamingEngineRef = Arc; /// FlowNodeManager manages the state of all tasks in the flow node, which should be run on the same thread /// /// The choice of timestamp is just using current system timestamp for now -pub struct FlowWorkerManager { +/// +pub struct StreamingEngine { /// The handler to the worker that will run the dataflow /// which is `!Send` so a handle is used pub worker_handles: Vec, @@ -159,7 +159,8 @@ pub struct FlowWorkerManager { flow_err_collectors: RwLock>, src_send_buf_lens: RwLock>>, tick_manager: FlowTickManager, - node_id: Option, + /// This node id is only available in distributed mode, on standalone mode this is guaranteed to be `None` + pub node_id: Option, /// Lock for flushing, will be `read` by `handle_inserts` and `write` by `flush_flow` /// /// So that a series of event like `inserts -> flush` can be handled correctly @@ -169,7 +170,7 @@ pub struct FlowWorkerManager { } /// Building FlownodeManager -impl FlowWorkerManager { +impl StreamingEngine { /// set frontend invoker pub async fn set_frontend_invoker(&self, frontend: FrontendInvoker) { *self.frontend_invoker.write().await = Some(frontend); @@ -188,7 +189,7 @@ impl FlowWorkerManager { let node_context = FlownodeContext::new(Box::new(srv_map.clone()) as _); let tick_manager = FlowTickManager::new(); let worker_handles = Vec::new(); - FlowWorkerManager { + StreamingEngine { worker_handles, worker_selector: Mutex::new(0), query_engine, @@ -264,7 +265,7 @@ pub fn batches_to_rows_req(batches: Vec) -> Result, Erro } /// This impl block contains methods to send writeback requests to frontend -impl FlowWorkerManager { +impl StreamingEngine { /// Return the number of requests it made pub async fn send_writeback_requests(&self) -> Result { let all_reqs = self.generate_writeback_request().await?; @@ -535,7 +536,7 @@ impl FlowWorkerManager { } /// Flow Runtime related methods -impl FlowWorkerManager { +impl StreamingEngine { /// Start state report handler, which will receive a sender from HeartbeatTask to send state size report back /// /// if heartbeat task is shutdown, this future will exit too @@ -660,7 +661,7 @@ impl FlowWorkerManager { } // flow is now shutdown, drop frontend_invoker early so a ref cycle(in standalone mode) can be prevent: // FlowWorkerManager.frontend_invoker -> FrontendInvoker.inserter - // -> Inserter.node_manager -> NodeManager.flownode -> Flownode.flow_worker_manager.frontend_invoker + // -> Inserter.node_manager -> NodeManager.flownode -> Flownode.flow_streaming_engine.frontend_invoker self.frontend_invoker.write().await.take(); } @@ -728,25 +729,10 @@ impl FlowWorkerManager { } } -/// The arguments to create a flow in [`FlowWorkerManager`]. -#[derive(Debug, Clone)] -pub struct CreateFlowArgs { - pub flow_id: FlowId, - pub sink_table_name: TableName, - pub source_table_ids: Vec, - pub create_if_not_exists: bool, - pub or_replace: bool, - pub expire_after: Option, - pub comment: Option, - pub sql: String, - pub flow_options: HashMap, - pub query_ctx: Option, -} - /// Create&Remove flow -impl FlowWorkerManager { +impl StreamingEngine { /// remove a flow by it's id - pub async fn remove_flow(&self, flow_id: FlowId) -> Result<(), Error> { + pub async fn remove_flow_inner(&self, flow_id: FlowId) -> Result<(), Error> { for handle in self.worker_handles.iter() { if handle.contains_flow(flow_id).await? { handle.remove_flow(flow_id).await?; @@ -762,8 +748,7 @@ impl FlowWorkerManager { /// steps to create task: /// 1. parse query into typed plan(and optional parse expire_after expr) /// 2. render source/sink with output table id and used input table id - #[allow(clippy::too_many_arguments)] - pub async fn create_flow(&self, args: CreateFlowArgs) -> Result, Error> { + pub async fn create_flow_inner(&self, args: CreateFlowArgs) -> Result, Error> { let CreateFlowArgs { flow_id, sink_table_name, @@ -902,6 +887,32 @@ impl FlowWorkerManager { info!("Successfully create flow with id={}", flow_id); Ok(Some(flow_id)) } + + pub async fn flush_flow_inner(&self, flow_id: FlowId) -> Result { + debug!("Starting to flush flow_id={:?}", flow_id); + // lock to make sure writes before flush are written to flow + // and immediately drop to prevent following writes to be blocked + drop(self.flush_lock.write().await); + let flushed_input_rows = self.node_context.read().await.flush_all_sender().await?; + let rows_send = self.run_available(true).await?; + let row = self.send_writeback_requests().await?; + debug!( + "Done to flush flow_id={:?} with {} input rows flushed, {} rows sended and {} output rows flushed", + flow_id, flushed_input_rows, rows_send, row + ); + Ok(row) + } + + pub async fn flow_exist_inner(&self, flow_id: FlowId) -> Result { + let mut exist = false; + for handle in self.worker_handles.iter() { + if handle.contains_flow(flow_id).await? { + exist = true; + break; + } + } + Ok(exist) + } } /// FlowTickManager is a manager for flow tick, which trakc flow execution progress diff --git a/src/flow/src/adapter/flownode_impl.rs b/src/flow/src/adapter/flownode_impl.rs index 1daec77fbd..c49cbb97ef 100644 --- a/src/flow/src/adapter/flownode_impl.rs +++ b/src/flow/src/adapter/flownode_impl.rs @@ -13,40 +13,603 @@ // limitations under the License. //! impl `FlowNode` trait for FlowNodeManager so standalone can call them -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; use api::v1::flow::{ flow_request, CreateRequest, DropRequest, FlowRequest, FlowResponse, FlushFlow, }; use api::v1::region::InsertRequests; +use catalog::CatalogManager; use common_error::ext::BoxedError; -use common_meta::error::{ExternalSnafu, Result, UnexpectedSnafu}; -use common_telemetry::{debug, trace}; +use common_meta::ddl::create_flow::FlowType; +use common_meta::error::Result as MetaResult; +use common_meta::key::flow::FlowMetadataManager; +use common_runtime::JoinHandle; +use common_telemetry::{error, info, trace, warn}; use datatypes::value::Value; +use futures::TryStreamExt; use itertools::Itertools; -use snafu::{IntoError, OptionExt, ResultExt}; -use store_api::storage::RegionId; +use session::context::QueryContextBuilder; +use snafu::{ensure, IntoError, OptionExt, ResultExt}; +use store_api::storage::{RegionId, TableId}; +use tokio::sync::{Mutex, RwLock}; -use crate::adapter::{CreateFlowArgs, FlowWorkerManager}; -use crate::error::{CreateFlowSnafu, InsertIntoFlowSnafu, InternalSnafu}; +use crate::adapter::{CreateFlowArgs, StreamingEngine}; +use crate::batching_mode::engine::BatchingEngine; +use crate::engine::FlowEngine; +use crate::error::{ + CreateFlowSnafu, ExternalSnafu, FlowNotFoundSnafu, IllegalCheckTaskStateSnafu, + InsertIntoFlowSnafu, InternalSnafu, JoinTaskSnafu, ListFlowsSnafu, SyncCheckTaskSnafu, + UnexpectedSnafu, +}; use crate::metrics::METRIC_FLOW_TASK_COUNT; use crate::repr::{self, DiffRow}; +use crate::{Error, FlowId}; -/// return a function to convert `crate::error::Error` to `common_meta::error::Error` -fn to_meta_err( - location: snafu::Location, -) -> impl FnOnce(crate::error::Error) -> common_meta::error::Error { - move |err: crate::error::Error| -> common_meta::error::Error { - common_meta::error::Error::External { - location, - source: BoxedError::new(err), +/// Ref to [`FlowDualEngine`] +pub type FlowDualEngineRef = Arc; + +/// Manage both streaming and batching mode engine +/// +/// including create/drop/flush flow +/// and redirect insert requests to the appropriate engine +pub struct FlowDualEngine { + streaming_engine: Arc, + batching_engine: Arc, + /// helper struct for faster query flow by table id or vice versa + src_table2flow: RwLock, + flow_metadata_manager: Arc, + catalog_manager: Arc, + check_task: tokio::sync::Mutex>, +} + +impl FlowDualEngine { + pub fn new( + streaming_engine: Arc, + batching_engine: Arc, + flow_metadata_manager: Arc, + catalog_manager: Arc, + ) -> Self { + Self { + streaming_engine, + batching_engine, + src_table2flow: RwLock::new(SrcTableToFlow::default()), + flow_metadata_manager, + catalog_manager, + check_task: Mutex::new(None), } } + + pub fn streaming_engine(&self) -> Arc { + self.streaming_engine.clone() + } + + pub fn batching_engine(&self) -> Arc { + self.batching_engine.clone() + } + + /// Try to sync with check task, this is only used in drop flow&flush flow, so a flow id is required + /// + /// the need to sync is to make sure flush flow actually get called + async fn try_sync_with_check_task( + &self, + flow_id: FlowId, + allow_drop: bool, + ) -> Result<(), Error> { + // this function rarely get called so adding some log is helpful + info!("Try to sync with check task for flow {}", flow_id); + let mut retry = 0; + let max_retry = 10; + // keep trying to trigger consistent check + while retry < max_retry { + if let Some(task) = self.check_task.lock().await.as_ref() { + task.trigger(false, allow_drop).await?; + break; + } + retry += 1; + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + } + + if retry == max_retry { + error!( + "Can't sync with check task for flow {} with allow_drop={}", + flow_id, allow_drop + ); + return SyncCheckTaskSnafu { + flow_id, + allow_drop, + } + .fail(); + } + info!("Successfully sync with check task for flow {}", flow_id); + + Ok(()) + } + + /// Spawn a task to consistently check if all flow tasks in metasrv is created on flownode, + /// so on startup, this will create all missing flow tasks, and constantly check at a interval + async fn check_flow_consistent( + &self, + allow_create: bool, + allow_drop: bool, + ) -> Result<(), Error> { + // use nodeid to determine if this is standalone/distributed mode, and retrieve all flows in this node(in distributed mode)/or all flows(in standalone mode) + let nodeid = self.streaming_engine.node_id; + let should_exists: Vec<_> = if let Some(nodeid) = nodeid { + // nodeid is available, so we only need to check flows on this node + // which also means we are in distributed mode + let to_be_recover = self + .flow_metadata_manager + .flownode_flow_manager() + .flows(nodeid.into()) + .try_collect::>() + .await + .context(ListFlowsSnafu { + id: Some(nodeid.into()), + })?; + to_be_recover.into_iter().map(|(id, _)| id).collect() + } else { + // nodeid is not available, so we need to check all flows + // which also means we are in standalone mode + let all_catalogs = self + .catalog_manager + .catalog_names() + .await + .map_err(BoxedError::new) + .context(ExternalSnafu)?; + let mut all_flow_ids = vec![]; + for catalog in all_catalogs { + let flows = self + .flow_metadata_manager + .flow_name_manager() + .flow_names(&catalog) + .await + .try_collect::>() + .await + .map_err(BoxedError::new) + .context(ExternalSnafu)?; + + all_flow_ids.extend(flows.into_iter().map(|(_, id)| id.flow_id())); + } + all_flow_ids + }; + let should_exists = should_exists + .into_iter() + .map(|i| i as FlowId) + .collect::>(); + let actual_exists = self.list_flows().await?.into_iter().collect::>(); + let to_be_created = should_exists + .iter() + .filter(|id| !actual_exists.contains(id)) + .collect::>(); + let to_be_dropped = actual_exists + .iter() + .filter(|id| !should_exists.contains(id)) + .collect::>(); + + if !to_be_created.is_empty() { + if allow_create { + info!( + "Recovering {} flows: {:?}", + to_be_created.len(), + to_be_created + ); + let mut errors = vec![]; + for flow_id in to_be_created { + let flow_id = *flow_id; + let info = self + .flow_metadata_manager + .flow_info_manager() + .get(flow_id as u32) + .await + .map_err(BoxedError::new) + .context(ExternalSnafu)? + .context(FlowNotFoundSnafu { id: flow_id })?; + + let sink_table_name = [ + info.sink_table_name().catalog_name.clone(), + info.sink_table_name().schema_name.clone(), + info.sink_table_name().table_name.clone(), + ]; + let args = CreateFlowArgs { + flow_id, + sink_table_name, + source_table_ids: info.source_table_ids().to_vec(), + // because recover should only happen on restart the `create_if_not_exists` and `or_replace` can be arbitrary value(since flow doesn't exist) + // but for the sake of consistency and to make sure recover of flow actually happen, we set both to true + // (which is also fine since checks for not allow both to be true is on metasrv and we already pass that) + create_if_not_exists: true, + or_replace: true, + expire_after: info.expire_after(), + comment: Some(info.comment().clone()), + sql: info.raw_sql().clone(), + flow_options: info.options().clone(), + query_ctx: info + .query_context() + .clone() + .map(|ctx| { + ctx.try_into() + .map_err(BoxedError::new) + .context(ExternalSnafu) + }) + .transpose()? + // or use default QueryContext with catalog_name from info + // to keep compatibility with old version + .or_else(|| { + Some( + QueryContextBuilder::default() + .current_catalog(info.catalog_name().to_string()) + .build(), + ) + }), + }; + if let Err(err) = self + .create_flow(args) + .await + .map_err(BoxedError::new) + .with_context(|_| CreateFlowSnafu { + sql: info.raw_sql().clone(), + }) + { + errors.push((flow_id, err)); + } + } + for (flow_id, err) in errors { + warn!("Failed to recreate flow {}, err={:#?}", flow_id, err); + } + } else { + warn!( + "Flownode {:?} found flows not exist in flownode, flow_ids={:?}", + nodeid, to_be_created + ); + } + } + if !to_be_dropped.is_empty() { + if allow_drop { + info!("Dropping flows: {:?}", to_be_dropped); + let mut errors = vec![]; + for flow_id in to_be_dropped { + let flow_id = *flow_id; + if let Err(err) = self.remove_flow(flow_id).await { + errors.push((flow_id, err)); + } + } + for (flow_id, err) in errors { + warn!("Failed to drop flow {}, err={:#?}", flow_id, err); + } + } else { + warn!( + "Flownode {:?} found flows not exist in flownode, flow_ids={:?}", + nodeid, to_be_dropped + ); + } + } + Ok(()) + } + + // TODO(discord9): consider sync this with heartbeat(might become necessary in the future) + pub async fn start_flow_consistent_check_task(self: &Arc) -> Result<(), Error> { + let mut check_task = self.check_task.lock().await; + ensure!( + check_task.is_none(), + IllegalCheckTaskStateSnafu { + reason: "Flow consistent check task already exists", + } + ); + let task = ConsistentCheckTask::start_check_task(self).await?; + *check_task = Some(task); + Ok(()) + } + + pub async fn stop_flow_consistent_check_task(&self) -> Result<(), Error> { + info!("Stopping flow consistent check task"); + let mut check_task = self.check_task.lock().await; + + ensure!( + check_task.is_some(), + IllegalCheckTaskStateSnafu { + reason: "Flow consistent check task does not exist", + } + ); + + check_task.take().unwrap().stop().await?; + info!("Stopped flow consistent check task"); + Ok(()) + } + + /// TODO(discord9): also add a `exists` api using flow metadata manager's `exists` method + async fn flow_exist_in_metadata(&self, flow_id: FlowId) -> Result { + self.flow_metadata_manager + .flow_info_manager() + .get(flow_id as u32) + .await + .map_err(BoxedError::new) + .context(ExternalSnafu) + .map(|info| info.is_some()) + } +} + +struct ConsistentCheckTask { + handle: JoinHandle<()>, + shutdown_tx: tokio::sync::mpsc::Sender<()>, + trigger_tx: tokio::sync::mpsc::Sender<(bool, bool, tokio::sync::oneshot::Sender<()>)>, +} + +impl ConsistentCheckTask { + async fn start_check_task(engine: &Arc) -> Result { + // first do recover flows + engine.check_flow_consistent(true, false).await?; + + let inner = engine.clone(); + let (tx, mut rx) = tokio::sync::mpsc::channel(1); + let (trigger_tx, mut trigger_rx) = + tokio::sync::mpsc::channel::<(bool, bool, tokio::sync::oneshot::Sender<()>)>(10); + let handle = common_runtime::spawn_global(async move { + let (mut allow_create, mut allow_drop) = (false, false); + let mut ret_signal: Option> = None; + loop { + if let Err(err) = inner.check_flow_consistent(allow_create, allow_drop).await { + error!(err; "Failed to check flow consistent"); + } + if let Some(done) = ret_signal.take() { + let _ = done.send(()); + } + tokio::select! { + _ = rx.recv() => break, + incoming = trigger_rx.recv() => if let Some(incoming) = incoming { + (allow_create, allow_drop) = (incoming.0, incoming.1); + ret_signal = Some(incoming.2); + }, + _ = tokio::time::sleep(std::time::Duration::from_secs(10)) => { + (allow_create, allow_drop) = (false, false); + }, + } + } + }); + Ok(ConsistentCheckTask { + handle, + shutdown_tx: tx, + trigger_tx, + }) + } + + async fn trigger(&self, allow_create: bool, allow_drop: bool) -> Result<(), Error> { + let (tx, rx) = tokio::sync::oneshot::channel(); + self.trigger_tx + .send((allow_create, allow_drop, tx)) + .await + .map_err(|_| { + IllegalCheckTaskStateSnafu { + reason: "Failed to send trigger signal", + } + .build() + })?; + rx.await.map_err(|_| { + IllegalCheckTaskStateSnafu { + reason: "Failed to receive trigger signal", + } + .build() + })?; + Ok(()) + } + + async fn stop(self) -> Result<(), Error> { + self.shutdown_tx.send(()).await.map_err(|_| { + IllegalCheckTaskStateSnafu { + reason: "Failed to send shutdown signal", + } + .build() + })?; + // abort so no need to wait + self.handle.abort(); + Ok(()) + } +} + +#[derive(Default)] +struct SrcTableToFlow { + /// mapping of table ids to flow ids for streaming mode + stream: HashMap>, + /// mapping of table ids to flow ids for batching mode + batch: HashMap>, + /// mapping of flow ids to (flow type, source table ids) + flow_infos: HashMap)>, +} + +impl SrcTableToFlow { + fn in_stream(&self, table_id: TableId) -> bool { + self.stream.contains_key(&table_id) + } + fn in_batch(&self, table_id: TableId) -> bool { + self.batch.contains_key(&table_id) + } + fn add_flow(&mut self, flow_id: FlowId, flow_type: FlowType, src_table_ids: Vec) { + let mapping = match flow_type { + FlowType::Streaming => &mut self.stream, + FlowType::Batching => &mut self.batch, + }; + + for src_table in src_table_ids.clone() { + mapping + .entry(src_table) + .and_modify(|flows| { + flows.insert(flow_id); + }) + .or_insert_with(|| { + let mut set = HashSet::new(); + set.insert(flow_id); + set + }); + } + self.flow_infos.insert(flow_id, (flow_type, src_table_ids)); + } + + fn remove_flow(&mut self, flow_id: FlowId) { + let mapping = match self.get_flow_type(flow_id) { + Some(FlowType::Streaming) => &mut self.stream, + Some(FlowType::Batching) => &mut self.batch, + None => return, + }; + if let Some((_, src_table_ids)) = self.flow_infos.remove(&flow_id) { + for src_table in src_table_ids { + if let Some(flows) = mapping.get_mut(&src_table) { + flows.remove(&flow_id); + } + } + } + } + + fn get_flow_type(&self, flow_id: FlowId) -> Option { + self.flow_infos + .get(&flow_id) + .map(|(flow_type, _)| flow_type) + .cloned() + } +} + +impl FlowEngine for FlowDualEngine { + async fn create_flow(&self, args: CreateFlowArgs) -> Result, Error> { + let flow_type = args + .flow_options + .get(FlowType::FLOW_TYPE_KEY) + .map(|s| s.as_str()); + + let flow_type = match flow_type { + Some(FlowType::BATCHING) => FlowType::Batching, + Some(FlowType::STREAMING) => FlowType::Streaming, + None => FlowType::Batching, + Some(flow_type) => { + return InternalSnafu { + reason: format!("Invalid flow type: {}", flow_type), + } + .fail() + } + }; + + let flow_id = args.flow_id; + let src_table_ids = args.source_table_ids.clone(); + + let res = match flow_type { + FlowType::Batching => self.batching_engine.create_flow(args).await, + FlowType::Streaming => self.streaming_engine.create_flow(args).await, + }?; + + self.src_table2flow + .write() + .await + .add_flow(flow_id, flow_type, src_table_ids); + + Ok(res) + } + + async fn remove_flow(&self, flow_id: FlowId) -> Result<(), Error> { + let flow_type = self.src_table2flow.read().await.get_flow_type(flow_id); + + match flow_type { + Some(FlowType::Batching) => self.batching_engine.remove_flow(flow_id).await, + Some(FlowType::Streaming) => self.streaming_engine.remove_flow(flow_id).await, + None => { + // this can happen if flownode just restart, and is stilling creating the flow + // since now that this flow should dropped, we need to trigger the consistent check and allow drop + // this rely on drop flow ddl delete metadata first, see src/common/meta/src/ddl/drop_flow.rs + warn!( + "Flow {} is not exist in the underlying engine, but exist in metadata", + flow_id + ); + self.try_sync_with_check_task(flow_id, true).await?; + + Ok(()) + } + }?; + // remove mapping + self.src_table2flow.write().await.remove_flow(flow_id); + Ok(()) + } + + async fn flush_flow(&self, flow_id: FlowId) -> Result { + // sync with check task + self.try_sync_with_check_task(flow_id, false).await?; + let flow_type = self.src_table2flow.read().await.get_flow_type(flow_id); + match flow_type { + Some(FlowType::Batching) => self.batching_engine.flush_flow(flow_id).await, + Some(FlowType::Streaming) => self.streaming_engine.flush_flow(flow_id).await, + None => Ok(0), + } + } + + async fn flow_exist(&self, flow_id: FlowId) -> Result { + let flow_type = self.src_table2flow.read().await.get_flow_type(flow_id); + // not using `flow_type.is_some()` to make sure the flow is actually exist in the underlying engine + match flow_type { + Some(FlowType::Batching) => self.batching_engine.flow_exist(flow_id).await, + Some(FlowType::Streaming) => self.streaming_engine.flow_exist(flow_id).await, + None => Ok(false), + } + } + + async fn list_flows(&self) -> Result, Error> { + let stream_flows = self.streaming_engine.list_flows().await?; + let batch_flows = self.batching_engine.list_flows().await?; + + Ok(stream_flows.into_iter().chain(batch_flows)) + } + + async fn handle_flow_inserts( + &self, + request: api::v1::region::InsertRequests, + ) -> Result<(), Error> { + // TODO(discord9): make as little clone as possible + let mut to_stream_engine = Vec::with_capacity(request.requests.len()); + let mut to_batch_engine = request.requests; + + { + let src_table2flow = self.src_table2flow.read().await; + to_batch_engine.retain(|req| { + let region_id = RegionId::from(req.region_id); + let table_id = region_id.table_id(); + let is_in_stream = src_table2flow.in_stream(table_id); + let is_in_batch = src_table2flow.in_batch(table_id); + if is_in_stream { + to_stream_engine.push(req.clone()); + } + if is_in_batch { + return true; + } + if !is_in_batch && !is_in_stream { + // TODO(discord9): also put to centralized logging for flow once it implemented + warn!("Table {} is not any flow's source table", table_id) + } + false + }); + // drop(src_table2flow); + // can't use drop due to https://github.com/rust-lang/rust/pull/128846 + } + + let streaming_engine = self.streaming_engine.clone(); + let stream_handler: JoinHandle> = + common_runtime::spawn_global(async move { + streaming_engine + .handle_flow_inserts(api::v1::region::InsertRequests { + requests: to_stream_engine, + }) + .await?; + Ok(()) + }); + self.batching_engine + .handle_flow_inserts(api::v1::region::InsertRequests { + requests: to_batch_engine, + }) + .await?; + stream_handler.await.context(JoinTaskSnafu)??; + + Ok(()) + } } #[async_trait::async_trait] -impl common_meta::node_manager::Flownode for FlowWorkerManager { - async fn handle(&self, request: FlowRequest) -> Result { +impl common_meta::node_manager::Flownode for FlowDualEngine { + async fn handle(&self, request: FlowRequest) -> MetaResult { let query_ctx = request .header .and_then(|h| h.query_context) @@ -109,49 +672,184 @@ impl common_meta::node_manager::Flownode for FlowWorkerManager { Some(flow_request::Body::Flush(FlushFlow { flow_id: Some(flow_id), })) => { - // TODO(discord9): impl individual flush - debug!("Starting to flush flow_id={:?}", flow_id); - // lock to make sure writes before flush are written to flow - // and immediately drop to prevent following writes to be blocked - drop(self.flush_lock.write().await); - let flushed_input_rows = self - .node_context - .read() - .await - .flush_all_sender() - .await - .map_err(to_meta_err(snafu::location!()))?; - let rows_send = self - .run_available(true) - .await - .map_err(to_meta_err(snafu::location!()))?; let row = self - .send_writeback_requests() + .flush_flow(flow_id.id as u64) .await .map_err(to_meta_err(snafu::location!()))?; - - debug!( - "Done to flush flow_id={:?} with {} input rows flushed, {} rows sended and {} output rows flushed", - flow_id, flushed_input_rows, rows_send, row - ); Ok(FlowResponse { affected_flows: vec![flow_id], affected_rows: row as u64, ..Default::default() }) } - None => UnexpectedSnafu { - err_msg: "Missing request body", - } - .fail(), - _ => UnexpectedSnafu { - err_msg: "Invalid request body.", - } - .fail(), + other => common_meta::error::InvalidFlowRequestBodySnafu { body: other }.fail(), } } - async fn handle_inserts(&self, request: InsertRequests) -> Result { + async fn handle_inserts(&self, request: InsertRequests) -> MetaResult { + FlowEngine::handle_flow_inserts(self, request) + .await + .map(|_| Default::default()) + .map_err(to_meta_err(snafu::location!())) + } +} + +/// return a function to convert `crate::error::Error` to `common_meta::error::Error` +fn to_meta_err( + location: snafu::Location, +) -> impl FnOnce(crate::error::Error) -> common_meta::error::Error { + move |err: crate::error::Error| -> common_meta::error::Error { + common_meta::error::Error::External { + location, + source: BoxedError::new(err), + } + } +} + +#[async_trait::async_trait] +impl common_meta::node_manager::Flownode for StreamingEngine { + async fn handle(&self, request: FlowRequest) -> MetaResult { + let query_ctx = request + .header + .and_then(|h| h.query_context) + .map(|ctx| ctx.into()); + match request.body { + Some(flow_request::Body::Create(CreateRequest { + flow_id: Some(task_id), + source_table_ids, + sink_table_name: Some(sink_table_name), + create_if_not_exists, + expire_after, + comment, + sql, + flow_options, + or_replace, + })) => { + let source_table_ids = source_table_ids.into_iter().map(|id| id.id).collect_vec(); + let sink_table_name = [ + sink_table_name.catalog_name, + sink_table_name.schema_name, + sink_table_name.table_name, + ]; + let expire_after = expire_after.map(|e| e.value); + let args = CreateFlowArgs { + flow_id: task_id.id as u64, + sink_table_name, + source_table_ids, + create_if_not_exists, + or_replace, + expire_after, + comment: Some(comment), + sql: sql.clone(), + flow_options, + query_ctx, + }; + let ret = self + .create_flow(args) + .await + .map_err(BoxedError::new) + .with_context(|_| CreateFlowSnafu { sql: sql.clone() }) + .map_err(to_meta_err(snafu::location!()))?; + METRIC_FLOW_TASK_COUNT.inc(); + Ok(FlowResponse { + affected_flows: ret + .map(|id| greptime_proto::v1::FlowId { id: id as u32 }) + .into_iter() + .collect_vec(), + ..Default::default() + }) + } + Some(flow_request::Body::Drop(DropRequest { + flow_id: Some(flow_id), + })) => { + self.remove_flow(flow_id.id as u64) + .await + .map_err(to_meta_err(snafu::location!()))?; + METRIC_FLOW_TASK_COUNT.dec(); + Ok(Default::default()) + } + Some(flow_request::Body::Flush(FlushFlow { + flow_id: Some(flow_id), + })) => { + let row = self + .flush_flow_inner(flow_id.id as u64) + .await + .map_err(to_meta_err(snafu::location!()))?; + Ok(FlowResponse { + affected_flows: vec![flow_id], + affected_rows: row as u64, + ..Default::default() + }) + } + other => common_meta::error::InvalidFlowRequestBodySnafu { body: other }.fail(), + } + } + + async fn handle_inserts(&self, request: InsertRequests) -> MetaResult { + self.handle_inserts_inner(request) + .await + .map(|_| Default::default()) + .map_err(to_meta_err(snafu::location!())) + } +} + +impl FlowEngine for StreamingEngine { + async fn create_flow(&self, args: CreateFlowArgs) -> Result, Error> { + self.create_flow_inner(args).await + } + + async fn remove_flow(&self, flow_id: FlowId) -> Result<(), Error> { + self.remove_flow_inner(flow_id).await + } + + async fn flush_flow(&self, flow_id: FlowId) -> Result { + self.flush_flow_inner(flow_id).await + } + + async fn flow_exist(&self, flow_id: FlowId) -> Result { + self.flow_exist_inner(flow_id).await + } + + async fn list_flows(&self) -> Result, Error> { + Ok(self + .flow_err_collectors + .read() + .await + .keys() + .cloned() + .collect::>()) + } + + async fn handle_flow_inserts( + &self, + request: api::v1::region::InsertRequests, + ) -> Result<(), Error> { + self.handle_inserts_inner(request).await + } +} + +/// Simple helper enum for fetching value from row with default value +#[derive(Debug, Clone)] +enum FetchFromRow { + Idx(usize), + Default(Value), +} + +impl FetchFromRow { + /// Panic if idx is out of bound + fn fetch(&self, row: &repr::Row) -> Value { + match self { + FetchFromRow::Idx(idx) => row.get(*idx).unwrap().clone(), + FetchFromRow::Default(v) => v.clone(), + } + } +} + +impl StreamingEngine { + async fn handle_inserts_inner( + &self, + request: InsertRequests, + ) -> std::result::Result<(), Error> { // using try_read to ensure two things: // 1. flush wouldn't happen until inserts before it is inserted // 2. inserts happening concurrently with flush wouldn't be block by flush @@ -172,11 +870,7 @@ impl common_meta::node_manager::Flownode for FlowWorkerManager { let ctx = self.node_context.read().await; // TODO(discord9): also check schema version so that altered table can be reported - let table_schema = ctx - .table_source - .table_from_id(&table_id) - .await - .map_err(to_meta_err(snafu::location!()))?; + let table_schema = ctx.table_source.table_from_id(&table_id).await?; let default_vals = table_schema .default_values .iter() @@ -210,9 +904,9 @@ impl common_meta::node_manager::Flownode for FlowWorkerManager { None => InternalSnafu { reason: format!("Expect column {idx} of table id={table_id} to have name in table schema, found None"), } - .fail().map_err(BoxedError::new).context(ExternalSnafu), + .fail(), }) - .collect::>>()?; + .collect::, _>>()?; let name_to_col = HashMap::<_, _>::from_iter( insert_schema .iter() @@ -230,7 +924,7 @@ impl common_meta::node_manager::Flownode for FlowWorkerManager { .map(FetchFromRow::Idx) .or_else(|| col_default_val.clone().map(FetchFromRow::Default)) .with_context(|| UnexpectedSnafu { - err_msg: format!( + reason: format!( "Column not found: {}, default_value: {:?}", col_name, col_default_val ), @@ -272,27 +966,9 @@ impl common_meta::node_manager::Flownode for FlowWorkerManager { } .into_error(err); common_telemetry::error!(err; "Failed to handle write request"); - let err = to_meta_err(snafu::location!())(err); return Err(err); } } - Ok(Default::default()) - } -} - -/// Simple helper enum for fetching value from row with default value -#[derive(Debug, Clone)] -enum FetchFromRow { - Idx(usize), - Default(Value), -} - -impl FetchFromRow { - /// Panic if idx is out of bound - fn fetch(&self, row: &repr::Row) -> Value { - match self { - FetchFromRow::Idx(idx) => row.get(*idx).unwrap().clone(), - FetchFromRow::Default(v) => v.clone(), - } + Ok(()) } } diff --git a/src/flow/src/adapter/refill.rs b/src/flow/src/adapter/refill.rs index 25287df62b..a29e120f0a 100644 --- a/src/flow/src/adapter/refill.rs +++ b/src/flow/src/adapter/refill.rs @@ -31,7 +31,7 @@ use snafu::{ensure, OptionExt, ResultExt}; use table::metadata::TableId; use crate::adapter::table_source::ManagedTableSource; -use crate::adapter::{FlowId, FlowWorkerManager, FlowWorkerManagerRef}; +use crate::adapter::{FlowId, FlowStreamingEngineRef, StreamingEngine}; use crate::error::{FlowNotFoundSnafu, JoinTaskSnafu, UnexpectedSnafu}; use crate::expr::error::ExternalSnafu; use crate::expr::utils::find_plan_time_window_expr_lower_bound; @@ -39,10 +39,10 @@ use crate::repr::RelationDesc; use crate::server::get_all_flow_ids; use crate::{Error, FrontendInvoker}; -impl FlowWorkerManager { +impl StreamingEngine { /// Create and start refill flow tasks in background pub async fn create_and_start_refill_flow_tasks( - self: &FlowWorkerManagerRef, + self: &FlowStreamingEngineRef, flow_metadata_manager: &FlowMetadataManagerRef, catalog_manager: &CatalogManagerRef, ) -> Result<(), Error> { @@ -130,7 +130,7 @@ impl FlowWorkerManager { /// Starting to refill flows, if any error occurs, will rebuild the flow and retry pub(crate) async fn starting_refill_flows( - self: &FlowWorkerManagerRef, + self: &FlowStreamingEngineRef, tasks: Vec, ) -> Result<(), Error> { // TODO(discord9): add a back pressure mechanism @@ -266,7 +266,7 @@ impl TaskState<()> { fn start_running( &mut self, task_data: &TaskData, - manager: FlowWorkerManagerRef, + manager: FlowStreamingEngineRef, mut output_stream: SendableRecordBatchStream, ) -> Result<(), Error> { let data = (*task_data).clone(); @@ -383,7 +383,7 @@ impl RefillTask { /// Start running the task in background, non-blocking pub async fn start_running( &mut self, - manager: FlowWorkerManagerRef, + manager: FlowStreamingEngineRef, invoker: &FrontendInvoker, ) -> Result<(), Error> { let TaskState::Prepared { sql } = &mut self.state else { diff --git a/src/flow/src/adapter/stat.rs b/src/flow/src/adapter/stat.rs index bf272cd9d0..fe1727a30e 100644 --- a/src/flow/src/adapter/stat.rs +++ b/src/flow/src/adapter/stat.rs @@ -16,9 +16,9 @@ use std::collections::BTreeMap; use common_meta::key::flow::flow_state::FlowStat; -use crate::FlowWorkerManager; +use crate::StreamingEngine; -impl FlowWorkerManager { +impl StreamingEngine { pub async fn gen_state_report(&self) -> FlowStat { let mut full_report = BTreeMap::new(); let mut last_exec_time_map = BTreeMap::new(); diff --git a/src/flow/src/adapter/util.rs b/src/flow/src/adapter/util.rs index 6811c29c96..3bb0031eee 100644 --- a/src/flow/src/adapter/util.rs +++ b/src/flow/src/adapter/util.rs @@ -33,8 +33,8 @@ use crate::adapter::table_source::TableDesc; use crate::adapter::{TableName, WorkerHandle, AUTO_CREATED_PLACEHOLDER_TS_COL}; use crate::error::{Error, ExternalSnafu, UnexpectedSnafu}; use crate::repr::{ColumnType, RelationDesc, RelationType}; -use crate::FlowWorkerManager; -impl FlowWorkerManager { +use crate::StreamingEngine; +impl StreamingEngine { /// Get a worker handle for creating flow, using round robin to select a worker pub(crate) async fn get_worker_handle_for_create_flow(&self) -> &WorkerHandle { let use_idx = { diff --git a/src/flow/src/batching_mode.rs b/src/flow/src/batching_mode.rs index 138a44b633..031c7aad4b 100644 --- a/src/flow/src/batching_mode.rs +++ b/src/flow/src/batching_mode.rs @@ -16,8 +16,8 @@ use std::time::Duration; -mod engine; -mod frontend_client; +pub(crate) mod engine; +pub(crate) mod frontend_client; mod state; mod task; mod time_window; @@ -32,3 +32,9 @@ pub const SLOW_QUERY_THRESHOLD: Duration = Duration::from_secs(60); /// The minimum duration between two queries execution by batching mode task const MIN_REFRESH_DURATION: Duration = Duration::new(5, 0); + +/// Grpc connection timeout +const GRPC_CONN_TIMEOUT: Duration = Duration::from_secs(5); + +/// Grpc max retry number +const GRPC_MAX_RETRIES: u32 = 3; diff --git a/src/flow/src/batching_mode/engine.rs b/src/flow/src/batching_mode/engine.rs index 72ab7042d2..6c667d56d5 100644 --- a/src/flow/src/batching_mode/engine.rs +++ b/src/flow/src/batching_mode/engine.rs @@ -12,31 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Batching mode engine + use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; -use api::v1::flow::FlowResponse; +use catalog::CatalogManagerRef; use common_error::ext::BoxedError; use common_meta::ddl::create_flow::FlowType; use common_meta::key::flow::FlowMetadataManagerRef; -use common_meta::key::table_info::TableInfoManager; +use common_meta::key::table_info::{TableInfoManager, TableInfoValue}; use common_meta::key::TableMetadataManagerRef; use common_runtime::JoinHandle; -use common_telemetry::info; use common_telemetry::tracing::warn; +use common_telemetry::{debug, info}; +use common_time::TimeToLive; use query::QueryEngineRef; use snafu::{ensure, OptionExt, ResultExt}; use store_api::storage::RegionId; use table::metadata::TableId; use tokio::sync::{oneshot, RwLock}; -use crate::adapter::{CreateFlowArgs, FlowId, TableName}; use crate::batching_mode::frontend_client::FrontendClient; use crate::batching_mode::task::BatchingTask; use crate::batching_mode::time_window::{find_time_window_expr, TimeWindowExpr}; use crate::batching_mode::utils::sql_to_df_plan; -use crate::error::{ExternalSnafu, FlowAlreadyExistSnafu, TableNotFoundMetaSnafu, UnexpectedSnafu}; -use crate::Error; +use crate::engine::FlowEngine; +use crate::error::{ + ExternalSnafu, FlowAlreadyExistSnafu, TableNotFoundMetaSnafu, UnexpectedSnafu, UnsupportedSnafu, +}; +use crate::{CreateFlowArgs, Error, FlowId, TableName}; /// Batching mode Engine, responsible for driving all the batching mode tasks /// @@ -47,6 +52,7 @@ pub struct BatchingEngine { frontend_client: Arc, flow_metadata_manager: FlowMetadataManagerRef, table_meta: TableMetadataManagerRef, + catalog_manager: CatalogManagerRef, query_engine: QueryEngineRef, } @@ -56,6 +62,7 @@ impl BatchingEngine { query_engine: QueryEngineRef, flow_metadata_manager: FlowMetadataManagerRef, table_meta: TableMetadataManagerRef, + catalog_manager: CatalogManagerRef, ) -> Self { Self { tasks: Default::default(), @@ -63,14 +70,15 @@ impl BatchingEngine { frontend_client, flow_metadata_manager, table_meta, + catalog_manager, query_engine, } } - pub async fn handle_inserts( + pub async fn handle_inserts_inner( &self, request: api::v1::region::InsertRequests, - ) -> Result { + ) -> Result<(), Error> { let table_info_mgr = self.table_meta.table_info_manager(); let mut group_by_table_id: HashMap> = HashMap::new(); @@ -170,7 +178,7 @@ impl BatchingEngine { } drop(tasks); - Ok(Default::default()) + Ok(()) } } @@ -178,6 +186,16 @@ async fn get_table_name( table_info: &TableInfoManager, table_id: &TableId, ) -> Result { + get_table_info(table_info, table_id).await.map(|info| { + let name = info.table_name(); + [name.catalog_name, name.schema_name, name.table_name] + }) +} + +async fn get_table_info( + table_info: &TableInfoManager, + table_id: &TableId, +) -> Result { table_info .get(*table_id) .await @@ -186,12 +204,11 @@ async fn get_table_name( .with_context(|| UnexpectedSnafu { reason: format!("Table id = {:?}, couldn't found table name", table_id), }) - .map(|name| name.table_name()) - .map(|name| [name.catalog_name, name.schema_name, name.table_name]) + .map(|info| info.into_inner()) } impl BatchingEngine { - pub async fn create_flow(&self, args: CreateFlowArgs) -> Result, Error> { + pub async fn create_flow_inner(&self, args: CreateFlowArgs) -> Result, Error> { let CreateFlowArgs { flow_id, sink_table_name, @@ -247,7 +264,20 @@ impl BatchingEngine { let query_ctx = Arc::new(query_ctx); let mut source_table_names = Vec::with_capacity(2); for src_id in source_table_ids { + // also check table option to see if ttl!=instant let table_name = get_table_name(self.table_meta.table_info_manager(), &src_id).await?; + let table_info = get_table_info(self.table_meta.table_info_manager(), &src_id).await?; + ensure!( + table_info.table_info.meta.options.ttl != Some(TimeToLive::Instant), + UnsupportedSnafu { + reason: format!( + "Source table `{}`(id={}) has instant TTL, Instant TTL is not supported under batching mode. Consider using a TTL longer than flush interval", + table_name.join("."), + src_id + ), + } + ); + source_table_names.push(table_name); } @@ -272,7 +302,14 @@ impl BatchingEngine { }) .transpose()?; - info!("Flow id={}, found time window expr={:?}", flow_id, phy_expr); + info!( + "Flow id={}, found time window expr={}", + flow_id, + phy_expr + .as_ref() + .map(|phy_expr| phy_expr.to_string()) + .unwrap_or("None".to_string()) + ); let task = BatchingTask::new( flow_id, @@ -283,7 +320,7 @@ impl BatchingEngine { sink_table_name, source_table_names, query_ctx, - self.table_meta.clone(), + self.catalog_manager.clone(), rx, ); @@ -294,10 +331,11 @@ impl BatchingEngine { // check execute once first to detect any error early task.check_execute(&engine, &frontend).await?; - // TODO(discord9): also save handle & use time wheel or what for better - let _handle = common_runtime::spawn_global(async move { + // TODO(discord9): use time wheel or what for better + let handle = common_runtime::spawn_global(async move { task_inner.start_executing_loop(engine, frontend).await; }); + task.state.write().unwrap().task_handle = Some(handle); // only replace here not earlier because we want the old one intact if something went wrong before this line let replaced_old_task_opt = self.tasks.write().await.insert(flow_id, task); @@ -308,7 +346,7 @@ impl BatchingEngine { Ok(Some(flow_id)) } - pub async fn remove_flow(&self, flow_id: FlowId) -> Result<(), Error> { + pub async fn remove_flow_inner(&self, flow_id: FlowId) -> Result<(), Error> { if self.tasks.write().await.remove(&flow_id).is_none() { warn!("Flow {flow_id} not found in tasks") } @@ -324,19 +362,53 @@ impl BatchingEngine { Ok(()) } - pub async fn flush_flow(&self, flow_id: FlowId) -> Result<(), Error> { + pub async fn flush_flow_inner(&self, flow_id: FlowId) -> Result { + debug!("Try flush flow {flow_id}"); let task = self.tasks.read().await.get(&flow_id).cloned(); let task = task.with_context(|| UnexpectedSnafu { reason: format!("Can't found task for flow {flow_id}"), })?; - task.gen_exec_once(&self.query_engine, &self.frontend_client) + task.mark_all_windows_as_dirty()?; + + let res = task + .gen_exec_once(&self.query_engine, &self.frontend_client) .await?; - Ok(()) + + let affected_rows = res.map(|(r, _)| r).unwrap_or_default() as usize; + debug!( + "Successfully flush flow {flow_id}, affected rows={}", + affected_rows + ); + Ok(affected_rows) } /// Determine if the batching mode flow task exists with given flow id - pub async fn flow_exist(&self, flow_id: FlowId) -> bool { + pub async fn flow_exist_inner(&self, flow_id: FlowId) -> bool { self.tasks.read().await.contains_key(&flow_id) } } + +impl FlowEngine for BatchingEngine { + async fn create_flow(&self, args: CreateFlowArgs) -> Result, Error> { + self.create_flow_inner(args).await + } + async fn remove_flow(&self, flow_id: FlowId) -> Result<(), Error> { + self.remove_flow_inner(flow_id).await + } + async fn flush_flow(&self, flow_id: FlowId) -> Result { + self.flush_flow_inner(flow_id).await + } + async fn flow_exist(&self, flow_id: FlowId) -> Result { + Ok(self.flow_exist_inner(flow_id).await) + } + async fn list_flows(&self) -> Result, Error> { + Ok(self.tasks.read().await.keys().cloned().collect::>()) + } + async fn handle_flow_inserts( + &self, + request: api::v1::region::InsertRequests, + ) -> Result<(), Error> { + self.handle_inserts_inner(request).await + } +} diff --git a/src/flow/src/batching_mode/frontend_client.rs b/src/flow/src/batching_mode/frontend_client.rs index 3b62986422..9f16ea07fa 100644 --- a/src/flow/src/batching_mode/frontend_client.rs +++ b/src/flow/src/batching_mode/frontend_client.rs @@ -14,44 +14,109 @@ //! Frontend client to run flow as batching task which is time-window-aware normal query triggered every tick set by user -use std::sync::Arc; +use std::sync::{Arc, Weak}; -use client::{Client, Database, DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; -use common_error::ext::BoxedError; +use api::v1::greptime_request::Request; +use api::v1::CreateTableExpr; +use client::{Client, Database}; +use common_error::ext::{BoxedError, ErrorExt}; use common_grpc::channel_manager::{ChannelConfig, ChannelManager}; use common_meta::cluster::{NodeInfo, NodeInfoKey, Role}; use common_meta::peer::Peer; use common_meta::rpc::store::RangeRequest; +use common_query::Output; +use common_telemetry::warn; use meta_client::client::MetaClient; -use snafu::ResultExt; +use servers::query_handler::grpc::GrpcQueryHandler; +use session::context::{QueryContextBuilder, QueryContextRef}; +use snafu::{OptionExt, ResultExt}; -use crate::batching_mode::DEFAULT_BATCHING_ENGINE_QUERY_TIMEOUT; -use crate::error::{ExternalSnafu, UnexpectedSnafu}; +use crate::batching_mode::{ + DEFAULT_BATCHING_ENGINE_QUERY_TIMEOUT, GRPC_CONN_TIMEOUT, GRPC_MAX_RETRIES, +}; +use crate::error::{ExternalSnafu, InvalidRequestSnafu, UnexpectedSnafu}; use crate::Error; -fn default_channel_mgr() -> ChannelManager { - let cfg = ChannelConfig::new().timeout(DEFAULT_BATCHING_ENGINE_QUERY_TIMEOUT); - ChannelManager::with_config(cfg) +/// Just like [`GrpcQueryHandler`] but use BoxedError +/// +/// basically just a specialized `GrpcQueryHandler` +/// +/// this is only useful for flownode to +/// invoke frontend Instance in standalone mode +#[async_trait::async_trait] +pub trait GrpcQueryHandlerWithBoxedError: Send + Sync + 'static { + async fn do_query( + &self, + query: Request, + ctx: QueryContextRef, + ) -> std::result::Result; } -fn client_from_urls(addrs: Vec) -> Client { - Client::with_manager_and_urls(default_channel_mgr(), addrs) +/// auto impl +#[async_trait::async_trait] +impl< + E: ErrorExt + Send + Sync + 'static, + T: GrpcQueryHandler + Send + Sync + 'static, + > GrpcQueryHandlerWithBoxedError for T +{ + async fn do_query( + &self, + query: Request, + ctx: QueryContextRef, + ) -> std::result::Result { + self.do_query(query, ctx).await.map_err(BoxedError::new) + } } +type HandlerMutable = Arc>>>; + /// A simple frontend client able to execute sql using grpc protocol -#[derive(Debug)] +/// +/// This is for computation-heavy query which need to offload computation to frontend, lifting the load from flownode +#[derive(Debug, Clone)] pub enum FrontendClient { Distributed { meta_client: Arc, + chnl_mgr: ChannelManager, }, Standalone { /// for the sake of simplicity still use grpc even in standalone mode /// notice the client here should all be lazy, so that can wait after frontend is booted then make conn - /// TODO(discord9): not use grpc under standalone mode - database_client: DatabaseWithPeer, + database_client: HandlerMutable, }, } +impl FrontendClient { + /// Create a new empty frontend client, with a `HandlerMutable` to set the grpc handler later + pub fn from_empty_grpc_handler() -> (Self, HandlerMutable) { + let handler = Arc::new(std::sync::Mutex::new(None)); + ( + Self::Standalone { + database_client: handler.clone(), + }, + handler, + ) + } + + pub fn from_meta_client(meta_client: Arc) -> Self { + Self::Distributed { + meta_client, + chnl_mgr: { + let cfg = ChannelConfig::new() + .connect_timeout(GRPC_CONN_TIMEOUT) + .timeout(DEFAULT_BATCHING_ENGINE_QUERY_TIMEOUT); + ChannelManager::with_config(cfg) + }, + } + } + + pub fn from_grpc_handler(grpc_handler: Weak) -> Self { + Self::Standalone { + database_client: Arc::new(std::sync::Mutex::new(Some(grpc_handler))), + } + } +} + #[derive(Debug, Clone)] pub struct DatabaseWithPeer { pub database: Database, @@ -64,25 +129,6 @@ impl DatabaseWithPeer { } } -impl FrontendClient { - pub fn from_meta_client(meta_client: Arc) -> Self { - Self::Distributed { meta_client } - } - - pub fn from_static_grpc_addr(addr: String) -> Self { - let peer = Peer { - id: 0, - addr: addr.clone(), - }; - - let client = client_from_urls(vec![addr]); - let database = Database::new(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, client); - Self::Standalone { - database_client: DatabaseWithPeer::new(database, peer), - } - } -} - impl FrontendClient { async fn scan_for_frontend(&self) -> Result, Error> { let Self::Distributed { meta_client, .. } = self else { @@ -115,10 +161,21 @@ impl FrontendClient { } /// Get the database with max `last_activity_ts` - async fn get_last_active_frontend(&self) -> Result { - if let Self::Standalone { database_client } = self { - return Ok(database_client.clone()); - } + async fn get_last_active_frontend( + &self, + catalog: &str, + schema: &str, + ) -> Result { + let Self::Distributed { + meta_client: _, + chnl_mgr, + } = self + else { + return UnexpectedSnafu { + reason: "Expect distributed mode", + } + .fail(); + }; let frontends = self.scan_for_frontend().await?; let mut peer = None; @@ -133,16 +190,139 @@ impl FrontendClient { } .fail()? }; - let client = client_from_urls(vec![peer.addr.clone()]); - let database = Database::new(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, client); + let client = Client::with_manager_and_urls(chnl_mgr.clone(), vec![peer.addr.clone()]); + let database = Database::new(catalog, schema, client); Ok(DatabaseWithPeer::new(database, peer)) } - /// Get a database client, and possibly update it before returning. - pub async fn get_database_client(&self) -> Result { + pub async fn create( + &self, + create: CreateTableExpr, + catalog: &str, + schema: &str, + ) -> Result { + self.handle( + Request::Ddl(api::v1::DdlRequest { + expr: Some(api::v1::ddl_request::Expr::CreateTable(create)), + }), + catalog, + schema, + &mut None, + ) + .await + } + + /// Handle a request to frontend + pub(crate) async fn handle( + &self, + req: api::v1::greptime_request::Request, + catalog: &str, + schema: &str, + peer_desc: &mut Option, + ) -> Result { match self { - Self::Standalone { database_client } => Ok(database_client.clone()), - Self::Distributed { meta_client: _ } => self.get_last_active_frontend().await, + FrontendClient::Distributed { .. } => { + let db = self.get_last_active_frontend(catalog, schema).await?; + + *peer_desc = Some(PeerDesc::Dist { + peer: db.peer.clone(), + }); + + let mut retry = 0; + + loop { + let ret = db.database.handle(req.clone()).await.with_context(|_| { + InvalidRequestSnafu { + context: format!("Failed to handle request: {:?}", req), + } + }); + if let Err(err) = ret { + if retry < GRPC_MAX_RETRIES { + retry += 1; + warn!( + "Failed to send request to grpc handle at Peer={:?}, retry = {}, error = {:?}", + db.peer, retry, err + ); + continue; + } else { + common_telemetry::error!( + "Failed to send request to grpc handle at Peer={:?} after {} retries, error = {:?}", + db.peer, retry, err + ); + return Err(err); + } + } + return ret; + } + } + FrontendClient::Standalone { database_client } => { + let ctx = QueryContextBuilder::default() + .current_catalog(catalog.to_string()) + .current_schema(schema.to_string()) + .build(); + let ctx = Arc::new(ctx); + { + let database_client = { + database_client + .lock() + .map_err(|e| { + UnexpectedSnafu { + reason: format!("Failed to lock database client: {e}"), + } + .build() + })? + .as_ref() + .context(UnexpectedSnafu { + reason: "Standalone's frontend instance is not set", + })? + .upgrade() + .context(UnexpectedSnafu { + reason: "Failed to upgrade database client", + })? + }; + let resp: common_query::Output = database_client + .do_query(req.clone(), ctx) + .await + .map_err(BoxedError::new) + .context(ExternalSnafu)?; + match resp.data { + common_query::OutputData::AffectedRows(rows) => { + Ok(rows.try_into().map_err(|_| { + UnexpectedSnafu { + reason: format!("Failed to convert rows to u32: {}", rows), + } + .build() + })?) + } + _ => UnexpectedSnafu { + reason: "Unexpected output data", + } + .fail(), + } + } + } + } + } +} + +/// Describe a peer of frontend +#[derive(Debug, Default)] +pub(crate) enum PeerDesc { + /// Distributed mode's frontend peer address + Dist { + /// frontend peer address + peer: Peer, + }, + /// Standalone mode + #[default] + Standalone, +} + +impl std::fmt::Display for PeerDesc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PeerDesc::Dist { peer } => write!(f, "{}", peer.addr), + PeerDesc::Standalone => write!(f, "standalone"), } } } diff --git a/src/flow/src/batching_mode/state.rs b/src/flow/src/batching_mode/state.rs index a406dae798..4c6f608af9 100644 --- a/src/flow/src/batching_mode/state.rs +++ b/src/flow/src/batching_mode/state.rs @@ -22,15 +22,15 @@ use common_telemetry::tracing::warn; use common_time::Timestamp; use datatypes::value::Value; use session::context::QueryContextRef; -use snafu::ResultExt; +use snafu::{OptionExt, ResultExt}; use tokio::sync::oneshot; use tokio::time::Instant; -use crate::adapter::FlowId; use crate::batching_mode::task::BatchingTask; +use crate::batching_mode::time_window::TimeWindowExpr; use crate::batching_mode::MIN_REFRESH_DURATION; -use crate::error::{DatatypesSnafu, InternalSnafu, TimeSnafu}; -use crate::Error; +use crate::error::{DatatypesSnafu, InternalSnafu, TimeSnafu, UnexpectedSnafu}; +use crate::{Error, FlowId}; /// The state of the [`BatchingTask`]. #[derive(Debug)] @@ -47,6 +47,8 @@ pub struct TaskState { exec_state: ExecState, /// Shutdown receiver pub(crate) shutdown_rx: oneshot::Receiver<()>, + /// Task handle + pub(crate) task_handle: Option>, } impl TaskState { pub fn new(query_ctx: QueryContextRef, shutdown_rx: oneshot::Receiver<()>) -> Self { @@ -57,6 +59,7 @@ impl TaskState { dirty_time_windows: Default::default(), exec_state: ExecState::Idle, shutdown_rx, + task_handle: None, } } @@ -71,7 +74,11 @@ impl TaskState { /// wait for at least `last_query_duration`, at most `max_timeout` to start next query /// /// if have more dirty time window, exec next query immediately - pub fn get_next_start_query_time(&self, max_timeout: Option) -> Instant { + pub fn get_next_start_query_time( + &self, + flow_id: FlowId, + max_timeout: Option, + ) -> Instant { let next_duration = max_timeout .unwrap_or(self.last_query_duration) .min(self.last_query_duration); @@ -81,6 +88,12 @@ impl TaskState { if self.dirty_time_windows.windows.is_empty() { self.last_update_time + next_duration } else { + debug!( + "Flow id = {}, still have {} dirty time window({:?}), execute immediately", + flow_id, + self.dirty_time_windows.windows.len(), + self.dirty_time_windows.windows + ); Instant::now() } } @@ -116,6 +129,15 @@ impl DirtyTimeWindows { } } + pub fn add_window(&mut self, start: Timestamp, end: Option) { + self.windows.insert(start, end); + } + + /// Clean all dirty time windows, useful when can't found time window expr + pub fn clean(&mut self) { + self.windows.clear(); + } + /// Generate all filter expressions consuming all time windows pub fn gen_filter_exprs( &mut self, @@ -178,6 +200,18 @@ impl DirtyTimeWindows { let mut expr_lst = vec![]; for (start, end) in first_nth.into_iter() { + // align using time window exprs + let (start, end) = if let Some(ctx) = task_ctx { + let Some(time_window_expr) = &ctx.config.time_window_expr else { + UnexpectedSnafu { + reason: "time_window_expr is not set", + } + .fail()? + }; + self.align_time_window(start, end, time_window_expr)? + } else { + (start, end) + }; debug!( "Time window start: {:?}, end: {:?}", start.to_iso8601_string(), @@ -200,6 +234,30 @@ impl DirtyTimeWindows { Ok(expr) } + fn align_time_window( + &self, + start: Timestamp, + end: Option, + time_window_expr: &TimeWindowExpr, + ) -> Result<(Timestamp, Option), Error> { + let align_start = time_window_expr.eval(start)?.0.context(UnexpectedSnafu { + reason: format!( + "Failed to align start time {:?} with time window expr {:?}", + start, time_window_expr + ), + })?; + let align_end = end + .and_then(|end| { + time_window_expr + .eval(end) + // if after aligned, end is the same, then use end(because it's already aligned) else use aligned end + .map(|r| if r.0 == Some(end) { r.0 } else { r.1 }) + .transpose() + }) + .transpose()?; + Ok((align_start, align_end)) + } + /// Merge time windows that overlaps or get too close pub fn merge_dirty_time_windows( &mut self, @@ -288,8 +346,12 @@ enum ExecState { #[cfg(test)] mod test { use pretty_assertions::assert_eq; + use session::context::QueryContext; use super::*; + use crate::batching_mode::time_window::find_time_window_expr; + use crate::batching_mode::utils::sql_to_df_plan; + use crate::test_utils::create_test_query_engine; #[test] fn test_merge_dirty_time_windows() { @@ -405,4 +467,59 @@ mod test { assert_eq!(expected_filter_expr, to_sql.as_deref()); } } + + #[tokio::test] + async fn test_align_time_window() { + type TimeWindow = (Timestamp, Option); + struct TestCase { + sql: String, + aligns: Vec<(TimeWindow, TimeWindow)>, + } + let testcases: Vec = vec![TestCase{ + sql: "SELECT date_bin(INTERVAL '5 second', ts) AS time_window FROM numbers_with_ts GROUP BY time_window;".to_string(), + aligns: vec![ + ((Timestamp::new_second(3), None), (Timestamp::new_second(0), None)), + ((Timestamp::new_second(8), None), (Timestamp::new_second(5), None)), + ((Timestamp::new_second(8), Some(Timestamp::new_second(10))), (Timestamp::new_second(5), Some(Timestamp::new_second(10)))), + ((Timestamp::new_second(8), Some(Timestamp::new_second(9))), (Timestamp::new_second(5), Some(Timestamp::new_second(10)))), + ], + }]; + + let query_engine = create_test_query_engine(); + let ctx = QueryContext::arc(); + for TestCase { sql, aligns } in testcases { + let plan = sql_to_df_plan(ctx.clone(), query_engine.clone(), &sql, true) + .await + .unwrap(); + + let (column_name, time_window_expr, _, df_schema) = find_time_window_expr( + &plan, + query_engine.engine_state().catalog_manager().clone(), + ctx.clone(), + ) + .await + .unwrap(); + + let time_window_expr = time_window_expr + .map(|expr| { + TimeWindowExpr::from_expr( + &expr, + &column_name, + &df_schema, + &query_engine.engine_state().session_state(), + ) + }) + .transpose() + .unwrap() + .unwrap(); + + let dirty = DirtyTimeWindows::default(); + for (before_align, expected_after_align) in aligns { + let after_align = dirty + .align_time_window(before_align.0, before_align.1, &time_window_expr) + .unwrap(); + assert_eq!(expected_after_align, after_align); + } + } + } } diff --git a/src/flow/src/batching_mode/task.rs b/src/flow/src/batching_mode/task.rs index 44312509d4..bb1f296c90 100644 --- a/src/flow/src/batching_mode/task.rs +++ b/src/flow/src/batching_mode/task.rs @@ -12,55 +12,56 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashSet; +use std::collections::{BTreeSet, HashSet}; use std::ops::Deref; use std::sync::{Arc, RwLock}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use api::v1::CreateTableExpr; use arrow_schema::Fields; +use catalog::CatalogManagerRef; use common_error::ext::BoxedError; -use common_meta::key::table_name::TableNameKey; -use common_meta::key::TableMetadataManagerRef; +use common_query::logical_plan::breakup_insert_plan; use common_telemetry::tracing::warn; use common_telemetry::{debug, info}; use common_time::Timestamp; +use datafusion::optimizer::analyzer::count_wildcard_rule::CountWildcardRule; +use datafusion::optimizer::AnalyzerRule; use datafusion::sql::unparser::expr_to_sql; -use datafusion_common::tree_node::TreeNode; +use datafusion_common::tree_node::{Transformed, TreeNode}; use datafusion_expr::{DmlStatement, LogicalPlan, WriteOp}; use datatypes::prelude::ConcreteDataType; -use datatypes::schema::constraint::NOW_FN; -use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema}; -use datatypes::value::Value; +use datatypes::schema::{ColumnSchema, Schema}; use operator::expr_helper::column_schemas_to_defs; use query::query_engine::DefaultSerializer; use query::QueryEngineRef; use session::context::QueryContextRef; -use snafu::{OptionExt, ResultExt}; +use snafu::{ensure, OptionExt, ResultExt}; use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan}; -use table::metadata::RawTableMeta; use tokio::sync::oneshot; use tokio::sync::oneshot::error::TryRecvError; use tokio::time::Instant; -use crate::adapter::{FlowId, AUTO_CREATED_PLACEHOLDER_TS_COL, AUTO_CREATED_UPDATE_AT_TS_COL}; +use crate::adapter::{AUTO_CREATED_PLACEHOLDER_TS_COL, AUTO_CREATED_UPDATE_AT_TS_COL}; use crate::batching_mode::frontend_client::FrontendClient; use crate::batching_mode::state::TaskState; use crate::batching_mode::time_window::TimeWindowExpr; use crate::batching_mode::utils::{ - sql_to_df_plan, AddAutoColumnRewriter, AddFilterRewriter, FindGroupByFinalName, + get_table_info_df_schema, sql_to_df_plan, AddAutoColumnRewriter, AddFilterRewriter, + FindGroupByFinalName, }; use crate::batching_mode::{ DEFAULT_BATCHING_ENGINE_QUERY_TIMEOUT, MIN_REFRESH_DURATION, SLOW_QUERY_THRESHOLD, }; +use crate::df_optimizer::apply_df_optimizer; use crate::error::{ - ConvertColumnSchemaSnafu, DatafusionSnafu, DatatypesSnafu, ExternalSnafu, InvalidRequestSnafu, - SubstraitEncodeLogicalPlanSnafu, TableNotFoundMetaSnafu, TableNotFoundSnafu, UnexpectedSnafu, + ConvertColumnSchemaSnafu, DatafusionSnafu, ExternalSnafu, InvalidQuerySnafu, + SubstraitEncodeLogicalPlanSnafu, UnexpectedSnafu, }; use crate::metrics::{ METRIC_FLOW_BATCHING_ENGINE_QUERY_TIME, METRIC_FLOW_BATCHING_ENGINE_SLOW_QUERY, }; -use crate::Error; +use crate::{Error, FlowId}; /// The task's config, immutable once created #[derive(Clone)] @@ -73,7 +74,7 @@ pub struct TaskConfig { pub expire_after: Option, sink_table_name: [String; 3], pub source_table_names: HashSet<[String; 3]>, - table_meta: TableMetadataManagerRef, + catalog_manager: CatalogManagerRef, } #[derive(Clone)] @@ -93,7 +94,7 @@ impl BatchingTask { sink_table_name: [String; 3], source_table_names: Vec<[String; 3]>, query_ctx: QueryContextRef, - table_meta: TableMetadataManagerRef, + catalog_manager: CatalogManagerRef, shutdown_rx: oneshot::Receiver<()>, ) -> Self { Self { @@ -105,12 +106,42 @@ impl BatchingTask { expire_after, sink_table_name, source_table_names: source_table_names.into_iter().collect(), - table_meta, + catalog_manager, }), state: Arc::new(RwLock::new(TaskState::new(query_ctx, shutdown_rx))), } } + /// mark time window range (now - expire_after, now) as dirty (or (0, now) if expire_after not set) + /// + /// useful for flush_flow to flush dirty time windows range + pub fn mark_all_windows_as_dirty(&self) -> Result<(), Error> { + let now = SystemTime::now(); + let now = Timestamp::new_second( + now.duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() as _, + ); + let lower_bound = self + .config + .expire_after + .map(|e| now.sub_duration(Duration::from_secs(e as _))) + .transpose() + .map_err(BoxedError::new) + .context(ExternalSnafu)? + .unwrap_or(Timestamp::new_second(0)); + debug!( + "Flow {} mark range ({:?}, {:?}) as dirty", + self.config.flow_id, lower_bound, now + ); + self.state + .write() + .unwrap() + .dirty_time_windows + .add_window(lower_bound, Some(now)); + Ok(()) + } + /// Test execute, for check syntax or such pub async fn check_execute( &self, @@ -148,13 +179,8 @@ impl BatchingTask { async fn is_table_exist(&self, table_name: &[String; 3]) -> Result { self.config - .table_meta - .table_name_manager() - .exists(TableNameKey { - catalog: &table_name[0], - schema: &table_name[1], - table: &table_name[2], - }) + .catalog_manager + .table_exists(&table_name[0], &table_name[1], &table_name[2], None) .await .map_err(BoxedError::new) .context(ExternalSnafu) @@ -166,8 +192,10 @@ impl BatchingTask { frontend_client: &Arc, ) -> Result, Error> { if let Some(new_query) = self.gen_insert_plan(engine).await? { + debug!("Generate new query: {:#?}", new_query); self.execute_logical_plan(frontend_client, &new_query).await } else { + debug!("Generate no query"); Ok(None) } } @@ -176,67 +204,35 @@ impl BatchingTask { &self, engine: &QueryEngineRef, ) -> Result, Error> { - let full_table_name = self.config.sink_table_name.clone().join("."); - - let table_id = self - .config - .table_meta - .table_name_manager() - .get(common_meta::key::table_name::TableNameKey::new( - &self.config.sink_table_name[0], - &self.config.sink_table_name[1], - &self.config.sink_table_name[2], - )) - .await - .with_context(|_| TableNotFoundMetaSnafu { - msg: full_table_name.clone(), - })? - .map(|t| t.table_id()) - .with_context(|| TableNotFoundSnafu { - name: full_table_name.clone(), - })?; - - let table = self - .config - .table_meta - .table_info_manager() - .get(table_id) - .await - .with_context(|_| TableNotFoundMetaSnafu { - msg: full_table_name.clone(), - })? - .with_context(|| TableNotFoundSnafu { - name: full_table_name.clone(), - })? - .into_inner(); - - let schema: datatypes::schema::Schema = table - .table_info - .meta - .schema - .clone() - .try_into() - .with_context(|_| DatatypesSnafu { - extra: format!( - "Failed to convert schema from raw schema, raw_schema={:?}", - table.table_info.meta.schema - ), - })?; - - let df_schema = Arc::new(schema.arrow_schema().clone().try_into().with_context(|_| { - DatafusionSnafu { - context: format!( - "Failed to convert arrow schema to datafusion schema, arrow_schema={:?}", - schema.arrow_schema() - ), - } - })?); + let (table, df_schema) = get_table_info_df_schema( + self.config.catalog_manager.clone(), + self.config.sink_table_name.clone(), + ) + .await?; let new_query = self - .gen_query_with_time_window(engine.clone(), &table.table_info.meta) + .gen_query_with_time_window(engine.clone(), &table.meta.schema) .await?; let insert_into = if let Some((new_query, _column_cnt)) = new_query { + // first check if all columns in input query exists in sink table + // since insert into ref to names in record batch generate by given query + let table_columns = df_schema + .columns() + .into_iter() + .map(|c| c.name) + .collect::>(); + for column in new_query.schema().columns() { + ensure!( + table_columns.contains(column.name()), + InvalidQuerySnafu { + reason: format!( + "Column {} not found in sink table with columns {:?}", + column, table_columns + ), + } + ); + } // update_at& time index placeholder (if exists) should have default value LogicalPlan::Dml(DmlStatement::new( datafusion_common::TableReference::Full { @@ -251,6 +247,9 @@ impl BatchingTask { } else { return Ok(None); }; + let insert_into = insert_into.recompute_schema().context(DatafusionSnafu { + context: "Failed to recompute schema", + })?; Ok(Some(insert_into)) } @@ -259,14 +258,11 @@ impl BatchingTask { frontend_client: &Arc, expr: CreateTableExpr, ) -> Result<(), Error> { - let db_client = frontend_client.get_database_client().await?; - db_client - .database - .create(expr.clone()) - .await - .with_context(|_| InvalidRequestSnafu { - context: format!("Failed to create table with expr: {:?}", expr), - })?; + let catalog = &self.config.sink_table_name[0]; + let schema = &self.config.sink_table_name[1]; + frontend_client + .create(expr.clone(), catalog, schema) + .await?; Ok(()) } @@ -277,27 +273,78 @@ impl BatchingTask { ) -> Result, Error> { let instant = Instant::now(); let flow_id = self.config.flow_id; - let db_client = frontend_client.get_database_client().await?; - let peer_addr = db_client.peer.addr; + debug!( - "Executing flow {flow_id}(expire_after={:?} secs) on {:?} with query {}", - self.config.expire_after, peer_addr, &plan + "Executing flow {flow_id}(expire_after={:?} secs) with query {}", + self.config.expire_after, &plan ); - let timer = METRIC_FLOW_BATCHING_ENGINE_QUERY_TIME - .with_label_values(&[flow_id.to_string().as_str()]) - .start_timer(); + let catalog = &self.config.sink_table_name[0]; + let schema = &self.config.sink_table_name[1]; - let message = DFLogicalSubstraitConvertor {} - .encode(plan, DefaultSerializer) - .context(SubstraitEncodeLogicalPlanSnafu)?; + // fix all table ref by make it fully qualified, i.e. "table_name" => "catalog_name.schema_name.table_name" + let fixed_plan = plan + .clone() + .transform_down_with_subqueries(|p| { + if let LogicalPlan::TableScan(mut table_scan) = p { + let resolved = table_scan.table_name.resolve(catalog, schema); + table_scan.table_name = resolved.into(); + Ok(Transformed::yes(LogicalPlan::TableScan(table_scan))) + } else { + Ok(Transformed::no(p)) + } + }) + .with_context(|_| DatafusionSnafu { + context: format!("Failed to fix table ref in logical plan, plan={:?}", plan), + })? + .data; - let req = api::v1::greptime_request::Request::Query(api::v1::QueryRequest { - query: Some(api::v1::query_request::Query::LogicalPlan(message.to_vec())), - }); + let expanded_plan = CountWildcardRule::new() + .analyze(fixed_plan.clone(), &Default::default()) + .with_context(|_| DatafusionSnafu { + context: format!( + "Failed to expand wildcard in logical plan, plan={:?}", + fixed_plan + ), + })?; - let res = db_client.database.handle(req).await; - drop(timer); + let plan = expanded_plan; + let mut peer_desc = None; + + let res = { + let _timer = METRIC_FLOW_BATCHING_ENGINE_QUERY_TIME + .with_label_values(&[flow_id.to_string().as_str()]) + .start_timer(); + + // hack and special handling the insert logical plan + let req = if let Some((insert_to, insert_plan)) = + breakup_insert_plan(&plan, catalog, schema) + { + let message = DFLogicalSubstraitConvertor {} + .encode(&insert_plan, DefaultSerializer) + .context(SubstraitEncodeLogicalPlanSnafu)?; + api::v1::greptime_request::Request::Query(api::v1::QueryRequest { + query: Some(api::v1::query_request::Query::InsertIntoPlan( + api::v1::InsertIntoPlan { + table_name: Some(insert_to), + logical_plan: message.to_vec(), + }, + )), + }) + } else { + let message = DFLogicalSubstraitConvertor {} + .encode(&plan, DefaultSerializer) + .context(SubstraitEncodeLogicalPlanSnafu)?; + + api::v1::greptime_request::Request::Query(api::v1::QueryRequest { + query: Some(api::v1::query_request::Query::LogicalPlan(message.to_vec())), + }) + }; + + frontend_client + .handle(req, catalog, schema, &mut peer_desc) + .await + }; let elapsed = instant.elapsed(); if let Ok(affected_rows) = &res { @@ -307,19 +354,23 @@ impl BatchingTask { ); } else if let Err(err) = &res { warn!( - "Failed to execute Flow {flow_id} on frontend {}, result: {err:?}, elapsed: {:?} with query: {}", - peer_addr, elapsed, &plan + "Failed to execute Flow {flow_id} on frontend {:?}, result: {err:?}, elapsed: {:?} with query: {}", + peer_desc, elapsed, &plan ); } // record slow query if elapsed >= SLOW_QUERY_THRESHOLD { warn!( - "Flow {flow_id} on frontend {} executed for {:?} before complete, query: {}", - peer_addr, elapsed, &plan + "Flow {flow_id} on frontend {:?} executed for {:?} before complete, query: {}", + peer_desc, elapsed, &plan ); METRIC_FLOW_BATCHING_ENGINE_SLOW_QUERY - .with_label_values(&[flow_id.to_string().as_str(), &plan.to_string(), &peer_addr]) + .with_label_values(&[ + flow_id.to_string().as_str(), + &plan.to_string(), + &peer_desc.unwrap_or_default().to_string(), + ]) .observe(elapsed.as_secs_f64()); } @@ -328,12 +379,7 @@ impl BatchingTask { .unwrap() .after_query_exec(elapsed, res.is_ok()); - let res = res.context(InvalidRequestSnafu { - context: format!( - "Failed to execute query for flow={}: \'{}\'", - self.config.flow_id, &plan - ), - })?; + let res = res?; Ok(Some((res, elapsed))) } @@ -372,7 +418,10 @@ impl BatchingTask { } Err(TryRecvError::Empty) => (), } - state.get_next_start_query_time(Some(DEFAULT_BATCHING_ENGINE_QUERY_TIMEOUT)) + state.get_next_start_query_time( + self.config.flow_id, + Some(DEFAULT_BATCHING_ENGINE_QUERY_TIMEOUT), + ) }; tokio::time::sleep_until(sleep_until).await; } @@ -386,14 +435,18 @@ impl BatchingTask { continue; } // TODO(discord9): this error should have better place to go, but for now just print error, also more context is needed - Err(err) => match new_query { - Some(query) => { - common_telemetry::error!(err; "Failed to execute query for flow={} with query: {query}", self.config.flow_id) + Err(err) => { + match new_query { + Some(query) => { + common_telemetry::error!(err; "Failed to execute query for flow={} with query: {query}", self.config.flow_id) + } + None => { + common_telemetry::error!(err; "Failed to generate query for flow={}", self.config.flow_id) + } } - None => { - common_telemetry::error!(err; "Failed to generate query for flow={}", self.config.flow_id) - } - }, + // also sleep for a little while before try again to prevent flooding logs + tokio::time::sleep(MIN_REFRESH_DURATION).await; + } } } } @@ -418,7 +471,7 @@ impl BatchingTask { async fn gen_query_with_time_window( &self, engine: QueryEngineRef, - sink_table_meta: &RawTableMeta, + sink_table_schema: &Arc, ) -> Result, Error> { let query_ctx = self.state.read().unwrap().query_ctx.clone(); let start = SystemTime::now(); @@ -477,9 +530,11 @@ impl BatchingTask { debug!( "Flow id = {:?}, can't get window size: precise_lower_bound={expire_time_window_bound:?}, using the same query", self.config.flow_id ); + // clean dirty time window too, this could be from create flow's check_execute + self.state.write().unwrap().dirty_time_windows.clean(); let mut add_auto_column = - AddAutoColumnRewriter::new(sink_table_meta.schema.clone()); + AddAutoColumnRewriter::new(sink_table_schema.clone()); let plan = self .config .plan @@ -487,7 +542,10 @@ impl BatchingTask { .clone() .rewrite(&mut add_auto_column) .with_context(|_| DatafusionSnafu { - context: format!("Failed to rewrite plan {:?}", self.config.plan), + context: format!( + "Failed to rewrite plan:\n {}\n", + self.config.plan + ), })? .data; let schema_len = plan.schema().fields().len(); @@ -515,18 +573,23 @@ impl BatchingTask { return Ok(None); }; + // TODO(discord9): add auto column or not? This might break compatibility for auto created sink table before this, but that's ok right? + let mut add_filter = AddFilterRewriter::new(expr); - let mut add_auto_column = AddAutoColumnRewriter::new(sink_table_meta.schema.clone()); - // make a not optimized plan for clearer unparse + let mut add_auto_column = AddAutoColumnRewriter::new(sink_table_schema.clone()); + let plan = sql_to_df_plan(query_ctx.clone(), engine.clone(), &self.config.query, false) .await?; - plan.clone() + let rewrite = plan + .clone() .rewrite(&mut add_filter) .and_then(|p| p.data.rewrite(&mut add_auto_column)) .with_context(|_| DatafusionSnafu { - context: format!("Failed to rewrite plan {plan:?}"), + context: format!("Failed to rewrite plan:\n {}\n", plan), })? - .data + .data; + // only apply optimize after complex rewrite is done + apply_df_optimizer(rewrite).await? }; Ok(Some((new_plan, schema_len))) @@ -534,7 +597,7 @@ impl BatchingTask { } // auto created table have a auto added column `update_at`, and optional have a `AUTO_CREATED_PLACEHOLDER_TS_COL` column for time index placeholder if no timestamp column is specified -// TODO(discord9): unit test +// TODO(discord9): for now no default value is set for auto added column for compatibility reason with streaming mode, but this might change in favor of simpler code? fn create_table_with_expr( plan: &LogicalPlan, sink_table_name: &[String; 3], @@ -558,11 +621,7 @@ fn create_table_with_expr( AUTO_CREATED_UPDATE_AT_TS_COL, ConcreteDataType::timestamp_millisecond_datatype(), true, - ) - .with_default_constraint(Some(ColumnDefaultConstraint::Function(NOW_FN.to_string()))) - .context(DatatypesSnafu { - extra: "Failed to build column `update_at TimestampMillisecond default now()`", - })?; + ); column_schemas.push(update_at_schema); let time_index = if let Some(time_index) = first_time_stamp { @@ -574,16 +633,7 @@ fn create_table_with_expr( ConcreteDataType::timestamp_millisecond_datatype(), false, ) - .with_time_index(true) - .with_default_constraint(Some(ColumnDefaultConstraint::Value(Value::Timestamp( - Timestamp::new_millisecond(0), - )))) - .context(DatatypesSnafu { - extra: format!( - "Failed to build column `{} TimestampMillisecond TIME INDEX default 0`", - AUTO_CREATED_PLACEHOLDER_TS_COL - ), - })?, + .with_time_index(true), ); AUTO_CREATED_PLACEHOLDER_TS_COL.to_string() }; @@ -675,20 +725,14 @@ mod test { AUTO_CREATED_UPDATE_AT_TS_COL, ConcreteDataType::timestamp_millisecond_datatype(), true, - ) - .with_default_constraint(Some(ColumnDefaultConstraint::Function(NOW_FN.to_string()))) - .unwrap(); + ); let ts_placeholder_schema = ColumnSchema::new( AUTO_CREATED_PLACEHOLDER_TS_COL, ConcreteDataType::timestamp_millisecond_datatype(), false, ) - .with_time_index(true) - .with_default_constraint(Some(ColumnDefaultConstraint::Value(Value::Timestamp( - Timestamp::new_millisecond(0), - )))) - .unwrap(); + .with_time_index(true); let testcases = vec![ TestCase { diff --git a/src/flow/src/batching_mode/time_window.rs b/src/flow/src/batching_mode/time_window.rs index f154847499..398250fc8b 100644 --- a/src/flow/src/batching_mode/time_window.rs +++ b/src/flow/src/batching_mode/time_window.rs @@ -72,6 +72,17 @@ pub struct TimeWindowExpr { df_schema: DFSchema, } +impl std::fmt::Display for TimeWindowExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TimeWindowExpr") + .field("phy_expr", &self.phy_expr.to_string()) + .field("column_name", &self.column_name) + .field("logical_expr", &self.logical_expr.to_string()) + .field("df_schema", &self.df_schema) + .finish() + } +} + impl TimeWindowExpr { pub fn from_expr( expr: &Expr, @@ -256,7 +267,7 @@ fn columnar_to_ts_vector(columnar: &ColumnarValue) -> Result= CAST('2025-02-24 10:48:00' AS TIMESTAMP)) AND (ts <= CAST('2025-02-24 10:49:00' AS TIMESTAMP))) GROUP BY arrow_cast(date_bin(INTERVAL '1 MINS', numbers_with_ts.ts), 'Timestamp(Second, None)')" ), + // complex time window index with where + ( + "SELECT arrow_cast(date_bin(INTERVAL '1 MINS', numbers_with_ts.ts), 'Timestamp(Second, None)') AS time_window FROM numbers_with_ts WHERE number in (2, 3, 4) GROUP BY time_window;", + Timestamp::new(1740394109, TimeUnit::Second), + ( + "ts".to_string(), + Some(Timestamp::new(1740394080, TimeUnit::Second)), + Some(Timestamp::new(1740394140, TimeUnit::Second)), + ), + "SELECT arrow_cast(date_bin(INTERVAL '1 MINS', numbers_with_ts.ts), 'Timestamp(Second, None)') AS time_window FROM numbers_with_ts WHERE numbers_with_ts.number IN (2, 3, 4) AND ((ts >= CAST('2025-02-24 10:48:00' AS TIMESTAMP)) AND (ts <= CAST('2025-02-24 10:49:00' AS TIMESTAMP))) GROUP BY arrow_cast(date_bin(INTERVAL '1 MINS', numbers_with_ts.ts), 'Timestamp(Second, None)')" + ), + // complex time window index with between and + ( + "SELECT arrow_cast(date_bin(INTERVAL '1 MINS', numbers_with_ts.ts), 'Timestamp(Second, None)') AS time_window FROM numbers_with_ts WHERE number BETWEEN 2 AND 4 GROUP BY time_window;", + Timestamp::new(1740394109, TimeUnit::Second), + ( + "ts".to_string(), + Some(Timestamp::new(1740394080, TimeUnit::Second)), + Some(Timestamp::new(1740394140, TimeUnit::Second)), + ), + "SELECT arrow_cast(date_bin(INTERVAL '1 MINS', numbers_with_ts.ts), 'Timestamp(Second, None)') AS time_window FROM numbers_with_ts WHERE (numbers_with_ts.number BETWEEN 2 AND 4) AND ((ts >= CAST('2025-02-24 10:48:00' AS TIMESTAMP)) AND (ts <= CAST('2025-02-24 10:49:00' AS TIMESTAMP))) GROUP BY arrow_cast(date_bin(INTERVAL '1 MINS', numbers_with_ts.ts), 'Timestamp(Second, None)')" + ), // no time index ( "SELECT date_bin('5 minutes', ts) FROM numbers_with_ts;", diff --git a/src/flow/src/batching_mode/utils.rs b/src/flow/src/batching_mode/utils.rs index 3560f878d1..117db03665 100644 --- a/src/flow/src/batching_mode/utils.rs +++ b/src/flow/src/batching_mode/utils.rs @@ -14,29 +14,63 @@ //! some utils for helping with batching mode -use std::collections::HashSet; +use std::collections::{BTreeSet, HashSet}; use std::sync::Arc; +use catalog::CatalogManagerRef; use common_error::ext::BoxedError; -use common_telemetry::{debug, info}; +use common_telemetry::debug; use datafusion::error::Result as DfResult; use datafusion::logical_expr::Expr; use datafusion::sql::unparser::Unparser; use datafusion_common::tree_node::{ Transformed, TreeNodeRecursion, TreeNodeRewriter, TreeNodeVisitor, }; -use datafusion_common::DataFusionError; -use datafusion_expr::{Distinct, LogicalPlan}; -use datatypes::schema::RawSchema; +use datafusion_common::{DFSchema, DataFusionError, ScalarValue}; +use datafusion_expr::{Distinct, LogicalPlan, Projection}; +use datatypes::schema::SchemaRef; use query::parser::QueryLanguageParser; use query::QueryEngineRef; use session::context::QueryContextRef; -use snafu::ResultExt; +use snafu::{OptionExt, ResultExt}; +use table::metadata::TableInfo; use crate::adapter::AUTO_CREATED_PLACEHOLDER_TS_COL; use crate::df_optimizer::apply_df_optimizer; -use crate::error::{DatafusionSnafu, ExternalSnafu}; -use crate::Error; +use crate::error::{DatafusionSnafu, ExternalSnafu, TableNotFoundSnafu}; +use crate::{Error, TableName}; + +pub async fn get_table_info_df_schema( + catalog_mr: CatalogManagerRef, + table_name: TableName, +) -> Result<(Arc, Arc), Error> { + let full_table_name = table_name.clone().join("."); + let table = catalog_mr + .table(&table_name[0], &table_name[1], &table_name[2], None) + .await + .map_err(BoxedError::new) + .context(ExternalSnafu)? + .context(TableNotFoundSnafu { + name: &full_table_name, + })?; + let table_info = table.table_info().clone(); + + let schema = table_info.meta.schema.clone(); + + let df_schema: Arc = Arc::new( + schema + .arrow_schema() + .clone() + .try_into() + .with_context(|_| DatafusionSnafu { + context: format!( + "Failed to convert arrow schema to datafusion schema, arrow_schema={:?}", + schema.arrow_schema() + ), + })?, + ); + Ok((table_info, df_schema)) +} /// Convert sql to datafusion logical plan pub async fn sql_to_df_plan( @@ -164,14 +198,16 @@ impl TreeNodeVisitor<'_> for FindGroupByFinalName { /// (which doesn't necessary need to have exact name just need to be a extra timestamp column) /// and `__ts_placeholder`(this column need to have exact this name and be a timestamp) /// with values like `now()` and `0` +/// +/// it also give existing columns alias to column in sink table if needed #[derive(Debug)] pub struct AddAutoColumnRewriter { - pub schema: RawSchema, + pub schema: SchemaRef, pub is_rewritten: bool, } impl AddAutoColumnRewriter { - pub fn new(schema: RawSchema) -> Self { + pub fn new(schema: SchemaRef) -> Self { Self { schema, is_rewritten: false, @@ -181,37 +217,97 @@ impl AddAutoColumnRewriter { impl TreeNodeRewriter for AddAutoColumnRewriter { type Node = LogicalPlan; - fn f_down(&mut self, node: Self::Node) -> DfResult> { + fn f_down(&mut self, mut node: Self::Node) -> DfResult> { if self.is_rewritten { return Ok(Transformed::no(node)); } - // if is distinct all, go one level down - if let LogicalPlan::Distinct(Distinct::All(_)) = node { - return Ok(Transformed::no(node)); + // if is distinct all, wrap it in a projection + if let LogicalPlan::Distinct(Distinct::All(_)) = &node { + let mut exprs = vec![]; + + for field in node.schema().fields().iter() { + exprs.push(Expr::Column(datafusion::common::Column::new_unqualified( + field.name(), + ))); + } + + let projection = + LogicalPlan::Projection(Projection::try_new(exprs, Arc::new(node.clone()))?); + + node = projection; + } + // handle table_scan by wrap it in a projection + else if let LogicalPlan::TableScan(table_scan) = node { + let mut exprs = vec![]; + + for field in table_scan.projected_schema.fields().iter() { + exprs.push(Expr::Column(datafusion::common::Column::new( + Some(table_scan.table_name.clone()), + field.name(), + ))); + } + + let projection = LogicalPlan::Projection(Projection::try_new( + exprs, + Arc::new(LogicalPlan::TableScan(table_scan)), + )?); + + node = projection; } - // FIXME(discord9): just read plan.expr and do stuffs - let mut exprs = node.expressions(); + // only do rewrite if found the outermost projection + let mut exprs = if let LogicalPlan::Projection(project) = &node { + project.expr.clone() + } else { + return Ok(Transformed::no(node)); + }; + + let all_names = self + .schema + .column_schemas() + .iter() + .map(|c| c.name.clone()) + .collect::>(); + // first match by position + for (idx, expr) in exprs.iter_mut().enumerate() { + if !all_names.contains(&expr.qualified_name().1) { + if let Some(col_name) = self + .schema + .column_schemas() + .get(idx) + .map(|c| c.name.clone()) + { + // if the data type mismatched, later check_execute will error out + // hence no need to check it here, beside, optimize pass might be able to cast it + // so checking here is not necessary + *expr = expr.clone().alias(col_name); + } + } + } // add columns if have different column count let query_col_cnt = exprs.len(); - let table_col_cnt = self.schema.column_schemas.len(); - info!("query_col_cnt={query_col_cnt}, table_col_cnt={table_col_cnt}"); + let table_col_cnt = self.schema.column_schemas().len(); + debug!("query_col_cnt={query_col_cnt}, table_col_cnt={table_col_cnt}"); + + let placeholder_ts_expr = + datafusion::logical_expr::lit(ScalarValue::TimestampMillisecond(Some(0), None)) + .alias(AUTO_CREATED_PLACEHOLDER_TS_COL); + if query_col_cnt == table_col_cnt { - self.is_rewritten = true; - return Ok(Transformed::no(node)); + // still need to add alias, see below } else if query_col_cnt + 1 == table_col_cnt { - let last_col_schema = self.schema.column_schemas.last().unwrap(); + let last_col_schema = self.schema.column_schemas().last().unwrap(); // if time index column is auto created add it if last_col_schema.name == AUTO_CREATED_PLACEHOLDER_TS_COL - && self.schema.timestamp_index == Some(table_col_cnt - 1) + && self.schema.timestamp_index() == Some(table_col_cnt - 1) { - exprs.push(datafusion::logical_expr::lit(0)); + exprs.push(placeholder_ts_expr); } else if last_col_schema.data_type.is_timestamp() { // is the update at column - exprs.push(datafusion::prelude::now()); + exprs.push(datafusion::prelude::now().alias(&last_col_schema.name)); } else { // helpful error message return Err(DataFusionError::Plan(format!( @@ -221,11 +317,11 @@ impl TreeNodeRewriter for AddAutoColumnRewriter { ))); } } else if query_col_cnt + 2 == table_col_cnt { - let mut col_iter = self.schema.column_schemas.iter().rev(); + let mut col_iter = self.schema.column_schemas().iter().rev(); let last_col_schema = col_iter.next().unwrap(); let second_last_col_schema = col_iter.next().unwrap(); if second_last_col_schema.data_type.is_timestamp() { - exprs.push(datafusion::prelude::now()); + exprs.push(datafusion::prelude::now().alias(&second_last_col_schema.name)); } else { return Err(DataFusionError::Plan(format!( "Expect the second last column in the table to be timestamp column, found column {} with type {:?}", @@ -235,9 +331,9 @@ impl TreeNodeRewriter for AddAutoColumnRewriter { } if last_col_schema.name == AUTO_CREATED_PLACEHOLDER_TS_COL - && self.schema.timestamp_index == Some(table_col_cnt - 1) + && self.schema.timestamp_index() == Some(table_col_cnt - 1) { - exprs.push(datafusion::logical_expr::lit(0)); + exprs.push(placeholder_ts_expr); } else { return Err(DataFusionError::Plan(format!( "Expect timestamp column {}, found {:?}", @@ -247,7 +343,7 @@ impl TreeNodeRewriter for AddAutoColumnRewriter { } else { return Err(DataFusionError::Plan(format!( "Expect table have 0,1 or 2 columns more than query columns, found {} query columns {:?}, {} table columns {:?}", - query_col_cnt, node.expressions(), table_col_cnt, self.schema.column_schemas + query_col_cnt, exprs, table_col_cnt, self.schema.column_schemas() ))); } @@ -255,9 +351,12 @@ impl TreeNodeRewriter for AddAutoColumnRewriter { let new_plan = node.with_new_exprs(exprs, node.inputs().into_iter().cloned().collect())?; Ok(Transformed::yes(new_plan)) } -} -// TODO(discord9): a method to found out the precise time window + /// We might add new columns, so we need to recompute the schema + fn f_up(&mut self, node: Self::Node) -> DfResult> { + node.recompute_schema().map(Transformed::yes) + } +} /// Find out the `Filter` Node corresponding to innermost(deepest) `WHERE` and add a new filter expr to it #[derive(Debug)] @@ -301,11 +400,15 @@ impl TreeNodeRewriter for AddFilterRewriter { #[cfg(test)] mod test { + use std::sync::Arc; + use datafusion_common::tree_node::TreeNode as _; use datatypes::prelude::ConcreteDataType; - use datatypes::schema::ColumnSchema; + use datatypes::schema::{ColumnSchema, Schema}; use pretty_assertions::assert_eq; + use query::query_engine::DefaultSerializer; use session::context::QueryContext; + use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan}; use super::*; use crate::test_utils::create_test_query_engine; @@ -386,7 +489,7 @@ mod test { // add update_at ( "SELECT number FROM numbers_with_ts", - Ok("SELECT numbers_with_ts.number, now() FROM numbers_with_ts"), + Ok("SELECT numbers_with_ts.number, now() AS ts FROM numbers_with_ts"), vec![ ColumnSchema::new("number", ConcreteDataType::int32_datatype(), true), ColumnSchema::new( @@ -400,7 +503,7 @@ mod test { // add ts placeholder ( "SELECT number FROM numbers_with_ts", - Ok("SELECT numbers_with_ts.number, 0 FROM numbers_with_ts"), + Ok("SELECT numbers_with_ts.number, CAST('1970-01-01 00:00:00' AS TIMESTAMP) AS __ts_placeholder FROM numbers_with_ts"), vec![ ColumnSchema::new("number", ConcreteDataType::int32_datatype(), true), ColumnSchema::new( @@ -428,7 +531,7 @@ mod test { // add update_at and ts placeholder ( "SELECT number FROM numbers_with_ts", - Ok("SELECT numbers_with_ts.number, now(), 0 FROM numbers_with_ts"), + Ok("SELECT numbers_with_ts.number, now() AS update_at, CAST('1970-01-01 00:00:00' AS TIMESTAMP) AS __ts_placeholder FROM numbers_with_ts"), vec![ ColumnSchema::new("number", ConcreteDataType::int32_datatype(), true), ColumnSchema::new( @@ -447,7 +550,7 @@ mod test { // add ts placeholder ( "SELECT number, ts FROM numbers_with_ts", - Ok("SELECT numbers_with_ts.number, numbers_with_ts.ts, 0 FROM numbers_with_ts"), + Ok("SELECT numbers_with_ts.number, numbers_with_ts.ts AS update_at, CAST('1970-01-01 00:00:00' AS TIMESTAMP) AS __ts_placeholder FROM numbers_with_ts"), vec![ ColumnSchema::new("number", ConcreteDataType::int32_datatype(), true), ColumnSchema::new( @@ -466,7 +569,7 @@ mod test { // add update_at after time index column ( "SELECT number, ts FROM numbers_with_ts", - Ok("SELECT numbers_with_ts.number, numbers_with_ts.ts, now() FROM numbers_with_ts"), + Ok("SELECT numbers_with_ts.number, numbers_with_ts.ts, now() AS update_atat FROM numbers_with_ts"), vec![ ColumnSchema::new("number", ConcreteDataType::int32_datatype(), true), ColumnSchema::new( @@ -528,8 +631,8 @@ mod test { let query_engine = create_test_query_engine(); let ctx = QueryContext::arc(); for (before, after, column_schemas) in testcases { - let raw_schema = RawSchema::new(column_schemas); - let mut add_auto_column_rewriter = AddAutoColumnRewriter::new(raw_schema); + let schema = Arc::new(Schema::new(column_schemas)); + let mut add_auto_column_rewriter = AddAutoColumnRewriter::new(schema); let plan = sql_to_df_plan(ctx.clone(), query_engine.clone(), before, false) .await @@ -600,4 +703,18 @@ mod test { ); } } + + #[tokio::test] + async fn test_null_cast() { + let query_engine = create_test_query_engine(); + let ctx = QueryContext::arc(); + let sql = "SELECT NULL::DOUBLE FROM numbers_with_ts"; + let plan = sql_to_df_plan(ctx, query_engine.clone(), sql, false) + .await + .unwrap(); + + let _sub_plan = DFLogicalSubstraitConvertor {} + .encode(&plan, DefaultSerializer) + .unwrap(); + } } diff --git a/src/flow/src/df_optimizer.rs b/src/flow/src/df_optimizer.rs index d83bb77718..bef5b3ed79 100644 --- a/src/flow/src/df_optimizer.rs +++ b/src/flow/src/df_optimizer.rs @@ -25,7 +25,6 @@ use datafusion::config::ConfigOptions; use datafusion::error::DataFusionError; use datafusion::functions_aggregate::count::count_udaf; use datafusion::functions_aggregate::sum::sum_udaf; -use datafusion::optimizer::analyzer::count_wildcard_rule::CountWildcardRule; use datafusion::optimizer::analyzer::type_coercion::TypeCoercion; use datafusion::optimizer::common_subexpr_eliminate::CommonSubexprEliminate; use datafusion::optimizer::optimize_projections::OptimizeProjections; @@ -42,6 +41,7 @@ use datafusion_expr::{ BinaryExpr, ColumnarValue, Expr, Operator, Projection, ScalarFunctionArgs, ScalarUDFImpl, Signature, TypeSignature, Volatility, }; +use query::optimizer::count_wildcard::CountWildcardToTimeIndexRule; use query::parser::QueryLanguageParser; use query::query_engine::DefaultSerializer; use query::QueryEngine; @@ -61,9 +61,9 @@ pub async fn apply_df_optimizer( ) -> Result { let cfg = ConfigOptions::new(); let analyzer = Analyzer::with_rules(vec![ - Arc::new(CountWildcardRule::new()), - Arc::new(AvgExpandRule::new()), - Arc::new(TumbleExpandRule::new()), + Arc::new(CountWildcardToTimeIndexRule), + Arc::new(AvgExpandRule), + Arc::new(TumbleExpandRule), Arc::new(CheckGroupByRule::new()), Arc::new(TypeCoercion::new()), ]); @@ -128,13 +128,7 @@ pub async fn sql_to_flow_plan( } #[derive(Debug)] -struct AvgExpandRule {} - -impl AvgExpandRule { - pub fn new() -> Self { - Self {} - } -} +struct AvgExpandRule; impl AnalyzerRule for AvgExpandRule { fn analyze( @@ -331,13 +325,7 @@ impl TreeNodeRewriter for ExpandAvgRewriter<'_> { /// expand tumble in aggr expr to tumble_start and tumble_end with column name like `window_start` #[derive(Debug)] -struct TumbleExpandRule {} - -impl TumbleExpandRule { - pub fn new() -> Self { - Self {} - } -} +struct TumbleExpandRule; impl AnalyzerRule for TumbleExpandRule { fn analyze( diff --git a/src/flow/src/engine.rs b/src/flow/src/engine.rs new file mode 100644 index 0000000000..1338e893f3 --- /dev/null +++ b/src/flow/src/engine.rs @@ -0,0 +1,59 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Define a trait for flow engine, which is used by both streaming engine and batch engine + +use std::collections::HashMap; + +use session::context::QueryContext; +use table::metadata::TableId; + +use crate::Error; +// TODO(discord9): refactor common types for flow to a separate module +/// FlowId is a unique identifier for a flow task +pub type FlowId = u64; +pub type TableName = [String; 3]; + +/// The arguments to create a flow +#[derive(Debug, Clone)] +pub struct CreateFlowArgs { + pub flow_id: FlowId, + pub sink_table_name: TableName, + pub source_table_ids: Vec, + pub create_if_not_exists: bool, + pub or_replace: bool, + pub expire_after: Option, + pub comment: Option, + pub sql: String, + pub flow_options: HashMap, + pub query_ctx: Option, +} + +pub trait FlowEngine { + /// Create a flow using the provided arguments, return previous flow id if exists and is replaced + async fn create_flow(&self, args: CreateFlowArgs) -> Result, Error>; + /// Remove a flow by its ID + async fn remove_flow(&self, flow_id: FlowId) -> Result<(), Error>; + /// Flush the flow, return the number of rows flushed + async fn flush_flow(&self, flow_id: FlowId) -> Result; + /// Check if the flow exists + async fn flow_exist(&self, flow_id: FlowId) -> Result; + /// List all flows + async fn list_flows(&self) -> Result, Error>; + /// Handle the insert requests for the flow + async fn handle_flow_inserts( + &self, + request: api::v1::region::InsertRequests, + ) -> Result<(), Error>; +} diff --git a/src/flow/src/error.rs b/src/flow/src/error.rs index 2488b0a677..904a0a8fa7 100644 --- a/src/flow/src/error.rs +++ b/src/flow/src/error.rs @@ -25,8 +25,8 @@ use common_telemetry::common_error::status_code::StatusCode; use snafu::{Location, ResultExt, Snafu}; use tonic::metadata::MetadataMap; -use crate::adapter::FlowId; use crate::expr::EvalError; +use crate::FlowId; /// This error is used to represent all possible errors that can occur in the flow module. #[derive(Snafu)] @@ -149,6 +149,13 @@ pub enum Error { location: Location, }, + #[snafu(display("Unsupported: {reason}"))] + Unsupported { + reason: String, + #[snafu(implicit)] + location: Location, + }, + #[snafu(display("Unsupported temporal filter: {reason}"))] UnsupportedTemporalFilter { reason: String, @@ -189,6 +196,25 @@ pub enum Error { location: Location, }, + #[snafu(display("Illegal check task state: {reason}"))] + IllegalCheckTaskState { + reason: String, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display( + "Failed to sync with check task for flow {} with allow_drop={}", + flow_id, + allow_drop + ))] + SyncCheckTask { + flow_id: FlowId, + allow_drop: bool, + #[snafu(implicit)] + location: Location, + }, + #[snafu(display("Failed to start server"))] StartServer { #[snafu(implicit)] @@ -280,10 +306,12 @@ impl ErrorExt for Error { Self::CreateFlow { .. } | Self::Arrow { .. } | Self::Time { .. } => { StatusCode::EngineExecuteQuery } - Self::Unexpected { .. } => StatusCode::Unexpected, - Self::NotImplemented { .. } | Self::UnsupportedTemporalFilter { .. } => { - StatusCode::Unsupported - } + Self::Unexpected { .. } + | Self::SyncCheckTask { .. } + | Self::IllegalCheckTaskState { .. } => StatusCode::Unexpected, + Self::NotImplemented { .. } + | Self::UnsupportedTemporalFilter { .. } + | Self::Unsupported { .. } => StatusCode::Unsupported, Self::External { source, .. } => source.status_code(), Self::Internal { .. } | Self::CacheRequired { .. } => StatusCode::Internal, Self::StartServer { source, .. } | Self::ShutdownServer { source, .. } => { diff --git a/src/flow/src/lib.rs b/src/flow/src/lib.rs index 8ec464730b..8caa659f07 100644 --- a/src/flow/src/lib.rs +++ b/src/flow/src/lib.rs @@ -26,9 +26,10 @@ // allow unused for now because it should be use later mod adapter; -mod batching_mode; +pub(crate) mod batching_mode; mod compute; mod df_optimizer; +pub(crate) mod engine; pub mod error; mod expr; pub mod heartbeat; @@ -42,7 +43,9 @@ mod utils; #[cfg(test)] mod test_utils; -pub use adapter::{FlowConfig, FlowWorkerManager, FlowWorkerManagerRef, FlownodeOptions}; +pub use adapter::{FlowConfig, FlowStreamingEngineRef, FlownodeOptions, StreamingEngine}; +pub use batching_mode::frontend_client::{FrontendClient, GrpcQueryHandlerWithBoxedError}; +pub(crate) use engine::{CreateFlowArgs, FlowId, TableName}; pub use error::{Error, Result}; pub use server::{ FlownodeBuilder, FlownodeInstance, FlownodeServer, FlownodeServiceBuilder, FrontendInvoker, diff --git a/src/flow/src/server.rs b/src/flow/src/server.rs index f347ac369e..46c0d93301 100644 --- a/src/flow/src/server.rs +++ b/src/flow/src/server.rs @@ -29,6 +29,7 @@ use common_meta::key::TableMetadataManagerRef; use common_meta::kv_backend::KvBackendRef; use common_meta::node_manager::{Flownode, NodeManagerRef}; use common_query::Output; +use common_runtime::JoinHandle; use common_telemetry::tracing::info; use futures::{FutureExt, TryStreamExt}; use greptime_proto::v1::flow::{flow_server, FlowRequest, FlowResponse, InsertRequests}; @@ -50,7 +51,10 @@ use tonic::codec::CompressionEncoding; use tonic::transport::server::TcpIncoming; use tonic::{Request, Response, Status}; -use crate::adapter::{create_worker, CreateFlowArgs, FlowWorkerManagerRef}; +use crate::adapter::flownode_impl::{FlowDualEngine, FlowDualEngineRef}; +use crate::adapter::{create_worker, FlowStreamingEngineRef}; +use crate::batching_mode::engine::BatchingEngine; +use crate::engine::FlowEngine; use crate::error::{ to_status_with_last_err, CacheRequiredSnafu, CreateFlowSnafu, ExternalSnafu, FlowNotFoundSnafu, ListFlowsSnafu, ParseAddrSnafu, ShutdownServerSnafu, StartServerSnafu, UnexpectedSnafu, @@ -59,18 +63,20 @@ use crate::heartbeat::HeartbeatTask; use crate::metrics::{METRIC_FLOW_PROCESSING_TIME, METRIC_FLOW_ROWS}; use crate::transform::register_function_to_query_engine; use crate::utils::{SizeReportSender, StateReportHandler}; -use crate::{Error, FlowWorkerManager, FlownodeOptions}; +use crate::{CreateFlowArgs, Error, FlownodeOptions, FrontendClient, StreamingEngine}; pub const FLOW_NODE_SERVER_NAME: &str = "FLOW_NODE_SERVER"; /// wrapping flow node manager to avoid orphan rule with Arc<...> #[derive(Clone)] pub struct FlowService { - pub manager: FlowWorkerManagerRef, + pub dual_engine: FlowDualEngineRef, } impl FlowService { - pub fn new(manager: FlowWorkerManagerRef) -> Self { - Self { manager } + pub fn new(manager: FlowDualEngineRef) -> Self { + Self { + dual_engine: manager, + } } } @@ -85,7 +91,7 @@ impl flow_server::Flow for FlowService { .start_timer(); let request = request.into_inner(); - self.manager + self.dual_engine .handle(request) .await .map_err(|err| { @@ -125,7 +131,7 @@ impl flow_server::Flow for FlowService { .with_label_values(&["in"]) .inc_by(row_count as u64); - self.manager + self.dual_engine .handle_inserts(request) .await .map(Response::new) @@ -138,11 +144,16 @@ pub struct FlownodeServer { inner: Arc, } +/// FlownodeServerInner is the inner state of FlownodeServer, +/// this struct mostly useful for construct/start and stop the +/// flow node server struct FlownodeServerInner { /// worker shutdown signal, not to be confused with server_shutdown_tx worker_shutdown_tx: Mutex>, /// server shutdown signal for shutdown grpc server server_shutdown_tx: Mutex>, + /// streaming task handler + streaming_task_handler: Mutex>>, flow_service: FlowService, } @@ -155,16 +166,28 @@ impl FlownodeServer { flow_service, worker_shutdown_tx: Mutex::new(tx), server_shutdown_tx: Mutex::new(server_tx), + streaming_task_handler: Mutex::new(None), }), } } /// Start the background task for streaming computation. async fn start_workers(&self) -> Result<(), Error> { - let manager_ref = self.inner.flow_service.manager.clone(); - let _handle = manager_ref - .clone() + let manager_ref = self.inner.flow_service.dual_engine.clone(); + let handle = manager_ref + .streaming_engine() .run_background(Some(self.inner.worker_shutdown_tx.lock().await.subscribe())); + self.inner + .streaming_task_handler + .lock() + .await + .replace(handle); + + self.inner + .flow_service + .dual_engine + .start_flow_consistent_check_task() + .await?; Ok(()) } @@ -175,6 +198,11 @@ impl FlownodeServer { if tx.send(()).is_err() { info!("Receiver dropped, the flow node server has already shutdown"); } + self.inner + .flow_service + .dual_engine + .stop_flow_consistent_check_task() + .await?; Ok(()) } } @@ -271,8 +299,8 @@ impl FlownodeInstance { &self.flownode_server } - pub fn flow_worker_manager(&self) -> FlowWorkerManagerRef { - self.flownode_server.inner.flow_service.manager.clone() + pub fn flow_engine(&self) -> FlowDualEngineRef { + self.flownode_server.inner.flow_service.dual_engine.clone() } pub fn setup_services(&mut self, services: ServerHandlers) { @@ -290,6 +318,7 @@ pub struct FlownodeBuilder { heartbeat_task: Option, /// receive a oneshot sender to send state size report state_report_handler: Option, + frontend_client: Arc, } impl FlownodeBuilder { @@ -300,6 +329,7 @@ impl FlownodeBuilder { table_meta: TableMetadataManagerRef, catalog_manager: CatalogManagerRef, flow_metadata_manager: FlowMetadataManagerRef, + frontend_client: Arc, ) -> Self { Self { opts, @@ -309,6 +339,7 @@ impl FlownodeBuilder { flow_metadata_manager, heartbeat_task: None, state_report_handler: None, + frontend_client, } } @@ -332,17 +363,27 @@ impl FlownodeBuilder { None, false, Default::default(), + self.opts.query.clone(), ); let manager = Arc::new( self.build_manager(query_engine_factory.query_engine()) .await?, ); + let batching = Arc::new(BatchingEngine::new( + self.frontend_client.clone(), + query_engine_factory.query_engine(), + self.flow_metadata_manager.clone(), + self.table_meta.clone(), + self.catalog_manager.clone(), + )); + let dual = FlowDualEngine::new( + manager.clone(), + batching, + self.flow_metadata_manager.clone(), + self.catalog_manager.clone(), + ); - if let Err(err) = self.recover_flows(&manager).await { - common_telemetry::error!(err; "Failed to recover flows"); - } - - let server = FlownodeServer::new(FlowService::new(manager.clone())); + let server = FlownodeServer::new(FlowService::new(Arc::new(dual))); let heartbeat_task = self.heartbeat_task; @@ -359,7 +400,7 @@ impl FlownodeBuilder { /// or recover all existing flow tasks if in standalone mode(nodeid is None) /// /// TODO(discord9): persistent flow tasks with internal state - async fn recover_flows(&self, manager: &FlowWorkerManagerRef) -> Result { + async fn recover_flows(&self, manager: &FlowDualEngine) -> Result { let nodeid = self.opts.node_id; let to_be_recovered: Vec<_> = if let Some(nodeid) = nodeid { let to_be_recover = self @@ -396,6 +437,7 @@ impl FlownodeBuilder { let cnt = to_be_recovered.len(); // TODO(discord9): recover in parallel + info!("Recovering {} flows: {:?}", cnt, to_be_recovered); for flow_id in to_be_recovered { let info = self .flow_metadata_manager @@ -411,6 +453,7 @@ impl FlownodeBuilder { info.sink_table_name().schema_name.clone(), info.sink_table_name().table_name.clone(), ]; + let args = CreateFlowArgs { flow_id: flow_id as _, sink_table_name, @@ -424,11 +467,24 @@ impl FlownodeBuilder { comment: Some(info.comment().clone()), sql: info.raw_sql().clone(), flow_options: info.options().clone(), - query_ctx: Some( - QueryContextBuilder::default() - .current_catalog(info.catalog_name().clone()) - .build(), - ), + query_ctx: info + .query_context() + .clone() + .map(|ctx| { + ctx.try_into() + .map_err(BoxedError::new) + .context(ExternalSnafu) + }) + .transpose()? + // or use default QueryContext with catalog_name from info + // to keep compatibility with old version + .or_else(|| { + Some( + QueryContextBuilder::default() + .current_catalog(info.catalog_name().to_string()) + .build(), + ) + }), }; manager .create_flow(args) @@ -447,7 +503,7 @@ impl FlownodeBuilder { async fn build_manager( &mut self, query_engine: Arc, - ) -> Result { + ) -> Result { let table_meta = self.table_meta.clone(); register_function_to_query_engine(&query_engine); @@ -456,7 +512,7 @@ impl FlownodeBuilder { let node_id = self.opts.node_id.map(|id| id as u32); - let mut man = FlowWorkerManager::new(node_id, query_engine, table_meta); + let mut man = StreamingEngine::new(node_id, query_engine, table_meta); for worker_id in 0..num_workers { let (tx, rx) = oneshot::channel(); @@ -538,6 +594,10 @@ impl<'a> FlownodeServiceBuilder<'a> { } } +/// Basically a tiny frontend that communicates with datanode, different from [`FrontendClient`] which +/// connect to a real frontend instead, this is used for flow's streaming engine. And is for simple query. +/// +/// For heavy query use [`FrontendClient`] which offload computation to frontend, lifting the load from flownode #[derive(Clone)] pub struct FrontendInvoker { inserter: Arc, @@ -559,7 +619,7 @@ impl FrontendInvoker { } pub async fn build_from( - flow_worker_manager: FlowWorkerManagerRef, + flow_streaming_engine: FlowStreamingEngineRef, catalog_manager: CatalogManagerRef, kv_backend: KvBackendRef, layered_cache_registry: LayeredCacheRegistryRef, @@ -594,7 +654,7 @@ impl FrontendInvoker { node_manager.clone(), )); - let query_engine = flow_worker_manager.query_engine.clone(); + let query_engine = flow_streaming_engine.query_engine.clone(); let statement_executor = Arc::new(StatementExecutor::new( catalog_manager.clone(), diff --git a/src/flow/src/test_utils.rs b/src/flow/src/test_utils.rs index 4d269a80c0..ecaabae32d 100644 --- a/src/flow/src/test_utils.rs +++ b/src/flow/src/test_utils.rs @@ -23,6 +23,7 @@ use datatypes::timestamp::TimestampMillisecond; use datatypes::vectors::{TimestampMillisecondVectorBuilder, VectorRef}; use itertools::Itertools; use prost::Message; +use query::options::QueryOptions; use query::parser::QueryLanguageParser; use query::query_engine::DefaultSerializer; use query::QueryEngine; @@ -146,7 +147,15 @@ pub fn create_test_query_engine() -> Arc { }; catalog_list.register_table_sync(req_with_ts).unwrap(); - let factory = query::QueryEngineFactory::new(catalog_list, None, None, None, None, false); + let factory = query::QueryEngineFactory::new( + catalog_list, + None, + None, + None, + None, + false, + QueryOptions::default(), + ); let engine = factory.query_engine(); register_function_to_query_engine(&engine); diff --git a/src/flow/src/transform.rs b/src/flow/src/transform.rs index 15da89b21f..04c7f40e68 100644 --- a/src/flow/src/transform.rs +++ b/src/flow/src/transform.rs @@ -171,6 +171,7 @@ mod test { use datatypes::vectors::{TimestampMillisecondVectorBuilder, VectorRef}; use itertools::Itertools; use prost::Message; + use query::options::QueryOptions; use query::parser::QueryLanguageParser; use query::query_engine::DefaultSerializer; use query::QueryEngine; @@ -263,7 +264,15 @@ mod test { }; catalog_list.register_table_sync(req_with_ts).unwrap(); - let factory = query::QueryEngineFactory::new(catalog_list, None, None, None, None, false); + let factory = query::QueryEngineFactory::new( + catalog_list, + None, + None, + None, + None, + false, + QueryOptions::default(), + ); let engine = factory.query_engine(); register_function_to_query_engine(&engine); diff --git a/src/frontend/Cargo.toml b/src/frontend/Cargo.toml index e6c6cf940d..153fbcdd83 100644 --- a/src/frontend/Cargo.toml +++ b/src/frontend/Cargo.toml @@ -15,6 +15,7 @@ api.workspace = true arc-swap = "1.0" async-trait.workspace = true auth.workspace = true +bytes.workspace = true cache.workspace = true catalog.workspace = true client.workspace = true @@ -39,6 +40,7 @@ datafusion.workspace = true datafusion-expr.workspace = true datanode.workspace = true datatypes.workspace = true +futures.workspace = true humantime-serde.workspace = true lazy_static.workspace = true log-query.workspace = true @@ -47,6 +49,7 @@ meta-client.workspace = true num_cpus.workspace = true opentelemetry-proto.workspace = true operator.workspace = true +otel-arrow-rust.workspace = true partition.workspace = true pipeline.workspace = true prometheus.workspace = true diff --git a/src/frontend/src/error.rs b/src/frontend/src/error.rs index 7d599cb0ce..7880656f51 100644 --- a/src/frontend/src/error.rs +++ b/src/frontend/src/error.rs @@ -19,6 +19,8 @@ use common_error::define_into_tonic_status; use common_error::ext::{BoxedError, ErrorExt}; use common_error::status_code::StatusCode; use common_macro::stack_trace_debug; +use common_query::error::datafusion_status_code; +use datafusion::error::DataFusionError; use session::ReadPreference; use snafu::{Location, Snafu}; use store_api::storage::RegionId; @@ -128,13 +130,6 @@ pub enum Error { source: catalog::error::Error, }, - #[snafu(display("Failed to start Meta client"))] - StartMetaClient { - #[snafu(implicit)] - location: Location, - source: meta_client::error::Error, - }, - #[snafu(display("Failed to create heartbeat stream to Metasrv"))] CreateMetaHeartbeatStream { source: meta_client::error::Error, @@ -352,7 +347,15 @@ pub enum Error { SubstraitDecodeLogicalPlan { #[snafu(implicit)] location: Location, - source: substrait::error::Error, + source: common_query::error::Error, + }, + + #[snafu(display("DataFusionError"))] + DataFusion { + #[snafu(source)] + error: DataFusionError, + #[snafu(implicit)] + location: Location, }, } @@ -415,8 +418,7 @@ impl ErrorExt for Error { Error::Catalog { source, .. } => source.status_code(), - Error::StartMetaClient { source, .. } - | Error::CreateMetaHeartbeatStream { source, .. } => source.status_code(), + Error::CreateMetaHeartbeatStream { source, .. } => source.status_code(), Error::PlanStatement { source, .. } | Error::ReadTable { source, .. } @@ -431,6 +433,8 @@ impl ErrorExt for Error { Error::TableOperation { source, .. } => source.status_code(), Error::InFlightWriteBytesExceeded { .. } => StatusCode::RateLimited, + + Error::DataFusion { error, .. } => datafusion_status_code::(error, None), } } diff --git a/src/frontend/src/frontend.rs b/src/frontend/src/frontend.rs index 983550d0e7..ba795730c4 100644 --- a/src/frontend/src/frontend.rs +++ b/src/frontend/src/frontend.rs @@ -19,6 +19,7 @@ use common_config::config::Configurable; use common_options::datanode::DatanodeClientOptions; use common_telemetry::logging::{LoggingOptions, TracingOptions}; use meta_client::MetaClientOptions; +use query::options::QueryOptions; use serde::{Deserialize, Serialize}; use servers::export_metrics::{ExportMetricsOption, ExportMetricsTask}; use servers::grpc::GrpcOptions; @@ -58,6 +59,7 @@ pub struct FrontendOptions { pub user_provider: Option, pub export_metrics: ExportMetricsOption, pub tracing: TracingOptions, + pub query: QueryOptions, pub max_in_flight_write_bytes: Option, } @@ -82,6 +84,7 @@ impl Default for FrontendOptions { user_provider: None, export_metrics: ExportMetricsOption::default(), tracing: TracingOptions::default(), + query: QueryOptions::default(), max_in_flight_write_bytes: None, } } diff --git a/src/frontend/src/instance.rs b/src/frontend/src/instance.rs index 9c94ec326a..1477a2b133 100644 --- a/src/frontend/src/instance.rs +++ b/src/frontend/src/instance.rs @@ -278,7 +278,7 @@ impl SqlQueryHandler for Instance { // plan should be prepared before exec // we'll do check there self.query_engine - .execute(plan, query_ctx) + .execute(plan.clone(), query_ctx) .await .context(ExecLogicalPlanSnafu) } diff --git a/src/frontend/src/instance/builder.rs b/src/frontend/src/instance/builder.rs index 52b2463503..ffbfeabca1 100644 --- a/src/frontend/src/instance/builder.rs +++ b/src/frontend/src/instance/builder.rs @@ -152,6 +152,7 @@ impl FrontendBuilder { let procedure_service_handler = Arc::new(ProcedureServiceOperator::new( self.procedure_executor.clone(), + self.catalog_manager.clone(), )); let flow_metadata_manager = Arc::new(FlowMetadataManager::new(kv_backend.clone())); @@ -165,6 +166,7 @@ impl FrontendBuilder { Some(Arc::new(flow_service)), true, plugins.clone(), + self.options.query.clone(), ) .query_engine(); diff --git a/src/frontend/src/instance/grpc.rs b/src/frontend/src/instance/grpc.rs index decd713555..4a83a99d12 100644 --- a/src/frontend/src/instance/grpc.rs +++ b/src/frontend/src/instance/grpc.rs @@ -12,27 +12,33 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::Arc; + use api::v1::ddl_request::{Expr as DdlExpr, Expr}; use api::v1::greptime_request::Request; use api::v1::query_request::Query; -use api::v1::{DeleteRequests, DropFlowExpr, InsertRequests, RowDeleteRequests, RowInsertRequests}; +use api::v1::{ + DeleteRequests, DropFlowExpr, InsertIntoPlan, InsertRequests, RowDeleteRequests, + RowInsertRequests, +}; use async_trait::async_trait; use auth::{PermissionChecker, PermissionCheckerRef, PermissionReq}; +use common_base::AffectedRows; +use common_query::logical_plan::add_insert_to_logical_plan; use common_query::Output; use common_telemetry::tracing::{self}; -use datafusion::execution::SessionStateBuilder; use query::parser::PromQuery; use servers::interceptor::{GrpcQueryInterceptor, GrpcQueryInterceptorRef}; -use servers::query_handler::grpc::GrpcQueryHandler; +use servers::query_handler::grpc::{GrpcQueryHandler, RawRecordBatch}; use servers::query_handler::sql::SqlQueryHandler; use session::context::QueryContextRef; use snafu::{ensure, OptionExt, ResultExt}; -use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan}; use table::table_name::TableName; use crate::error::{ - Error, InFlightWriteBytesExceededSnafu, IncompleteGrpcRequestSnafu, NotSupportedSnafu, - PermissionSnafu, Result, SubstraitDecodeLogicalPlanSnafu, TableOperationSnafu, + CatalogSnafu, DataFusionSnafu, Error, InFlightWriteBytesExceededSnafu, + IncompleteGrpcRequestSnafu, NotSupportedSnafu, PermissionSnafu, PlanStatementSnafu, Result, + SubstraitDecodeLogicalPlanSnafu, TableNotFoundSnafu, TableOperationSnafu, }; use crate::instance::{attach_timer, Instance}; use crate::metrics::{ @@ -89,14 +95,31 @@ impl GrpcQueryHandler for Instance { Query::LogicalPlan(plan) => { // this path is useful internally when flownode needs to execute a logical plan through gRPC interface let timer = GRPC_HANDLE_PLAN_ELAPSED.start_timer(); - let plan = DFLogicalSubstraitConvertor {} - .decode(&*plan, SessionStateBuilder::default().build()) + + // use dummy catalog to provide table + let plan_decoder = self + .query_engine() + .engine_context(ctx.clone()) + .new_plan_decoder() + .context(PlanStatementSnafu)?; + + let dummy_catalog_list = + Arc::new(catalog::table_source::dummy_catalog::DummyCatalogList::new( + self.catalog_manager().clone(), + )); + + let logical_plan = plan_decoder + .decode(bytes::Bytes::from(plan), dummy_catalog_list, true) .await .context(SubstraitDecodeLogicalPlanSnafu)?; - let output = SqlQueryHandler::do_exec_plan(self, plan, ctx.clone()).await?; + let output = + SqlQueryHandler::do_exec_plan(self, logical_plan, ctx.clone()).await?; attach_timer(output, timer) } + Query::InsertIntoPlan(insert) => { + self.handle_insert_plan(insert, ctx.clone()).await? + } Query::PromRangeQuery(promql) => { let timer = GRPC_HANDLE_PROMQL_ELAPSED.start_timer(); let prom_query = PromQuery { @@ -203,6 +226,34 @@ impl GrpcQueryHandler for Instance { let output = interceptor.post_execute(output, ctx)?; Ok(output) } + + async fn put_record_batch( + &self, + table: &TableName, + record_batch: RawRecordBatch, + ) -> Result { + let _table = self + .catalog_manager() + .table( + &table.catalog_name, + &table.schema_name, + &table.table_name, + None, + ) + .await + .context(CatalogSnafu)? + .with_context(|| TableNotFoundSnafu { + table_name: table.to_string(), + })?; + + // TODO(LFC): Implement it. + common_telemetry::debug!( + "calling put_record_batch with table: {:?} and record_batch size: {}", + table, + record_batch.len() + ); + Ok(record_batch.len()) + } } fn fill_catalog_and_schema_from_context(ddl_expr: &mut DdlExpr, ctx: &QueryContextRef) { @@ -254,6 +305,91 @@ fn fill_catalog_and_schema_from_context(ddl_expr: &mut DdlExpr, ctx: &QueryConte } impl Instance { + async fn handle_insert_plan( + &self, + insert: InsertIntoPlan, + ctx: QueryContextRef, + ) -> Result { + let timer = GRPC_HANDLE_PLAN_ELAPSED.start_timer(); + let table_name = insert.table_name.context(IncompleteGrpcRequestSnafu { + err_msg: "'table_name' is absent in InsertIntoPlan", + })?; + + // use dummy catalog to provide table + let plan_decoder = self + .query_engine() + .engine_context(ctx.clone()) + .new_plan_decoder() + .context(PlanStatementSnafu)?; + + let dummy_catalog_list = + Arc::new(catalog::table_source::dummy_catalog::DummyCatalogList::new( + self.catalog_manager().clone(), + )); + + // no optimize yet since we still need to add stuff + let logical_plan = plan_decoder + .decode( + bytes::Bytes::from(insert.logical_plan), + dummy_catalog_list, + false, + ) + .await + .context(SubstraitDecodeLogicalPlanSnafu)?; + + let table = self + .catalog_manager() + .table( + &table_name.catalog_name, + &table_name.schema_name, + &table_name.table_name, + None, + ) + .await + .context(CatalogSnafu)? + .with_context(|| TableNotFoundSnafu { + table_name: [ + table_name.catalog_name.clone(), + table_name.schema_name.clone(), + table_name.table_name.clone(), + ] + .join("."), + })?; + + let table_info = table.table_info(); + + let df_schema = Arc::new( + table_info + .meta + .schema + .arrow_schema() + .clone() + .try_into() + .context(DataFusionSnafu)?, + ); + + let insert_into = add_insert_to_logical_plan(table_name, df_schema, logical_plan) + .context(SubstraitDecodeLogicalPlanSnafu)?; + + let engine_ctx = self.query_engine().engine_context(ctx.clone()); + let state = engine_ctx.state(); + // Analyze the plan + let analyzed_plan = state + .analyzer() + .execute_and_check(insert_into, state.config_options(), |_, _| {}) + .context(common_query::error::GeneralDataFusionSnafu) + .context(SubstraitDecodeLogicalPlanSnafu)?; + + // Optimize the plan + let optimized_plan = state + .optimize(&analyzed_plan) + .context(common_query::error::GeneralDataFusionSnafu) + .context(SubstraitDecodeLogicalPlanSnafu)?; + + let output = SqlQueryHandler::do_exec_plan(self, optimized_plan, ctx.clone()).await?; + + Ok(attach_timer(output, timer)) + } #[tracing::instrument(skip_all)] pub async fn handle_inserts( &self, diff --git a/src/frontend/src/instance/log_handler.rs b/src/frontend/src/instance/log_handler.rs index 029a40e980..179d5e098f 100644 --- a/src/frontend/src/instance/log_handler.rs +++ b/src/frontend/src/instance/log_handler.rs @@ -19,6 +19,7 @@ use async_trait::async_trait; use auth::{PermissionChecker, PermissionCheckerRef, PermissionReq}; use client::Output; use common_error::ext::BoxedError; +use datatypes::timestamp::TimestampNanosecond; use pipeline::pipeline_operator::PipelineOperator; use pipeline::{Pipeline, PipelineInfo, PipelineVersion}; use servers::error::{ @@ -103,6 +104,18 @@ impl PipelineHandler for Instance { fn build_pipeline(&self, pipeline: &str) -> ServerResult { PipelineOperator::build_pipeline(pipeline).context(PipelineSnafu) } + + async fn get_pipeline_str( + &self, + name: &str, + version: PipelineVersion, + query_ctx: QueryContextRef, + ) -> ServerResult<(String, TimestampNanosecond)> { + self.pipeline_operator + .get_pipeline_str(name, version, query_ctx) + .await + .context(PipelineSnafu) + } } impl Instance { diff --git a/src/frontend/src/instance/promql.rs b/src/frontend/src/instance/promql.rs index 1dca064982..f3f276bdd9 100644 --- a/src/frontend/src/instance/promql.rs +++ b/src/frontend/src/instance/promql.rs @@ -161,15 +161,11 @@ impl Instance { let mut results = Vec::with_capacity(batches.iter().map(|b| b.num_rows()).sum()); for batch in batches { - // Only one column the results, ensured by `prometheus::label_values_matchers_to_plan`. + // Only one column in results, ensured by `prometheus::label_values_matchers_to_plan`. let names = batch.column(0); for i in 0..names.len() { - let Value::String(name) = names.get(i) else { - unreachable!(); - }; - - results.push(name.into_string()); + results.push(names.get(i).to_string()); } } diff --git a/src/frontend/src/server.rs b/src/frontend/src/server.rs index fe1e15e7d9..d64fa4200f 100644 --- a/src/frontend/src/server.rs +++ b/src/frontend/src/server.rs @@ -27,6 +27,7 @@ use servers::http::{HttpServer, HttpServerBuilder}; use servers::interceptor::LogIngestInterceptorRef; use servers::metrics_handler::MetricsHandler; use servers::mysql::server::{MysqlServer, MysqlSpawnConfig, MysqlSpawnRef}; +use servers::otel_arrow::OtelArrowServiceHandler; use servers::postgres::PostgresServer; use servers::query_handler::grpc::ServerGrpcQueryHandlerAdapter; use servers::query_handler::sql::ServerSqlQueryHandlerAdapter; @@ -162,6 +163,7 @@ where let grpc_server = builder .database_handler(greptime_request_handler.clone()) .prometheus_handler(self.instance.clone(), user_provider.clone()) + .otel_arrow_handler(OtelArrowServiceHandler(self.instance.clone())) .flight_handler(Arc::new(greptime_request_handler)) .build(); Ok(grpc_server) diff --git a/src/index/Cargo.toml b/src/index/Cargo.toml index dc6e394ef4..c4b7057895 100644 --- a/src/index/Cargo.toml +++ b/src/index/Cargo.toml @@ -23,6 +23,7 @@ futures.workspace = true greptime-proto.workspace = true itertools.workspace = true jieba-rs = "0.7" +lazy_static.workspace = true mockall.workspace = true pin-project.workspace = true prost.workspace = true diff --git a/src/index/src/fulltext_index.rs b/src/index/src/fulltext_index.rs index 3a7f58c8ab..4cbbbdf477 100644 --- a/src/index/src/fulltext_index.rs +++ b/src/index/src/fulltext_index.rs @@ -12,18 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. +use puffin::blob_metadata::BlobMetadata; use serde::{Deserialize, Serialize}; - +use snafu::ResultExt; +use tantivy::tokenizer::{LowerCaser, SimpleTokenizer, TextAnalyzer, TokenizerManager}; +use tantivy_jieba::JiebaTokenizer; pub mod create; pub mod error; pub mod search; pub mod tokenizer; +pub const KEY_FULLTEXT_CONFIG: &str = "fulltext_config"; + +use crate::fulltext_index::error::{DeserializeFromJsonSnafu, Result}; + #[cfg(test)] mod tests; /// Configuration for fulltext index. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Config { /// Analyzer to use for tokenization. pub analyzer: Analyzer, @@ -33,10 +40,38 @@ pub struct Config { } /// Analyzer to use for tokenization. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum Analyzer { #[default] English, Chinese, } + +impl Config { + fn build_tantivy_tokenizer(&self) -> TokenizerManager { + let mut builder = match self.analyzer { + Analyzer::English => TextAnalyzer::builder(SimpleTokenizer::default()).dynamic(), + Analyzer::Chinese => TextAnalyzer::builder(JiebaTokenizer {}).dynamic(), + }; + + if !self.case_sensitive { + builder = builder.filter_dynamic(LowerCaser); + } + + let tokenizer = builder.build(); + let tokenizer_manager = TokenizerManager::new(); + tokenizer_manager.register("default", tokenizer); + tokenizer_manager + } + + /// Extracts the fulltext index configuration from the blob metadata. + pub fn from_blob_metadata(metadata: &BlobMetadata) -> Result { + if let Some(config) = metadata.properties.get(KEY_FULLTEXT_CONFIG) { + let config = serde_json::from_str(config).context(DeserializeFromJsonSnafu)?; + return Ok(config); + } + + Ok(Self::default()) + } +} diff --git a/src/index/src/fulltext_index/create/bloom_filter.rs b/src/index/src/fulltext_index/create/bloom_filter.rs index 970f89d65d..127464db71 100644 --- a/src/index/src/fulltext_index/create/bloom_filter.rs +++ b/src/index/src/fulltext_index/create/bloom_filter.rs @@ -30,12 +30,10 @@ use crate::fulltext_index::error::{ SerializeToJsonSnafu, }; use crate::fulltext_index::tokenizer::{Analyzer, ChineseTokenizer, EnglishTokenizer}; -use crate::fulltext_index::Config; +use crate::fulltext_index::{Config, KEY_FULLTEXT_CONFIG}; const PIPE_BUFFER_SIZE_FOR_SENDING_BLOB: usize = 8192; -pub const KEY_FULLTEXT_CONFIG: &str = "fulltext_config"; - /// `BloomFilterFulltextIndexCreator` is for creating a fulltext index using a bloom filter. pub struct BloomFilterFulltextIndexCreator { inner: Option, diff --git a/src/index/src/fulltext_index/create/tantivy.rs b/src/index/src/fulltext_index/create/tantivy.rs index 6b09c1f0fb..274fea596e 100644 --- a/src/index/src/fulltext_index/create/tantivy.rs +++ b/src/index/src/fulltext_index/create/tantivy.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashMap; use std::path::{Path, PathBuf}; use async_trait::async_trait; @@ -21,15 +22,13 @@ use snafu::{OptionExt, ResultExt}; use tantivy::indexer::NoMergePolicy; use tantivy::schema::{Schema, STORED, TEXT}; use tantivy::store::{Compressor, ZstdCompressor}; -use tantivy::tokenizer::{LowerCaser, SimpleTokenizer, TextAnalyzer, TokenizerManager}; use tantivy::{doc, Index, IndexWriter}; -use tantivy_jieba::JiebaTokenizer; use crate::fulltext_index::create::FulltextIndexCreator; use crate::fulltext_index::error::{ - ExternalSnafu, FinishedSnafu, IoSnafu, JoinSnafu, Result, TantivySnafu, + ExternalSnafu, FinishedSnafu, IoSnafu, JoinSnafu, Result, SerializeToJsonSnafu, TantivySnafu, }; -use crate::fulltext_index::{Analyzer, Config}; +use crate::fulltext_index::{Config, KEY_FULLTEXT_CONFIG}; pub const TEXT_FIELD_NAME: &str = "greptime_fulltext_text"; pub const ROWID_FIELD_NAME: &str = "greptime_fulltext_rowid"; @@ -50,6 +49,9 @@ pub struct TantivyFulltextIndexCreator { /// The directory path in filesystem to store the index. path: PathBuf, + + /// The configuration of the fulltext index. + config: Config, } impl TantivyFulltextIndexCreator { @@ -68,7 +70,7 @@ impl TantivyFulltextIndexCreator { let mut index = Index::create_in_dir(&path, schema).context(TantivySnafu)?; index.settings_mut().docstore_compression = Compressor::Zstd(ZstdCompressor::default()); - index.set_tokenizers(Self::build_tokenizer(&config)); + index.set_tokenizers(config.build_tantivy_tokenizer()); let memory_limit = Self::sanitize_memory_limit(memory_limit); @@ -84,25 +86,10 @@ impl TantivyFulltextIndexCreator { rowid_field, max_rowid: 0, path: path.as_ref().to_path_buf(), + config, }) } - fn build_tokenizer(config: &Config) -> TokenizerManager { - let mut builder = match config.analyzer { - Analyzer::English => TextAnalyzer::builder(SimpleTokenizer::default()).dynamic(), - Analyzer::Chinese => TextAnalyzer::builder(JiebaTokenizer {}).dynamic(), - }; - - if !config.case_sensitive { - builder = builder.filter_dynamic(LowerCaser); - } - - let tokenizer = builder.build(); - let tokenizer_manager = TokenizerManager::new(); - tokenizer_manager.register("default", tokenizer); - tokenizer_manager - } - fn sanitize_memory_limit(memory_limit: usize) -> usize { // Port from tantivy::indexer::index_writer::{MEMORY_BUDGET_NUM_BYTES_MIN, MEMORY_BUDGET_NUM_BYTES_MAX} const MARGIN_IN_BYTES: usize = 1_000_000; @@ -137,8 +124,16 @@ impl FulltextIndexCreator for TantivyFulltextIndexCreator { .await .context(JoinSnafu)??; + let property_key = KEY_FULLTEXT_CONFIG.to_string(); + let property_value = serde_json::to_string(&self.config).context(SerializeToJsonSnafu)?; + puffin_writer - .put_dir(blob_key, self.path.clone(), put_options) + .put_dir( + blob_key, + self.path.clone(), + put_options, + HashMap::from([(property_key, property_value)]), + ) .await .map_err(BoxedError::new) .context(ExternalSnafu) @@ -174,6 +169,7 @@ mod tests { use tantivy::TantivyDocument; use super::*; + use crate::fulltext_index::Analyzer; struct MockPuffinWriter; @@ -197,6 +193,7 @@ mod tests { _key: &str, _dir: PathBuf, _options: PutOptions, + _properties: HashMap, ) -> puffin::error::Result { Ok(0) } @@ -226,7 +223,7 @@ mod tests { ("foo", vec![3]), ("bar", vec![4]), ]; - query_and_check(temp_dir.path(), &cases).await; + query_and_check(temp_dir.path(), config, &cases).await; } } @@ -248,9 +245,13 @@ mod tests { ("hello", vec![0u32, 2]), ("world", vec![1, 2]), ("foo", vec![3]), + ("Foo", vec![]), + ("FOO", vec![]), ("bar", vec![]), + ("Bar", vec![4]), + ("BAR", vec![]), ]; - query_and_check(temp_dir.path(), &cases).await; + query_and_check(temp_dir.path(), config, &cases).await; } } @@ -274,7 +275,7 @@ mod tests { ("foo", vec![4]), ("bar", vec![5]), ]; - query_and_check(temp_dir.path(), &cases).await; + query_and_check(temp_dir.path(), config, &cases).await; } } @@ -297,8 +298,12 @@ mod tests { ("世界", vec![1, 2, 3]), ("foo", vec![4]), ("bar", vec![]), + ("Foo", vec![]), + ("FOO", vec![]), + ("Bar", vec![5]), + ("BAR", vec![]), ]; - query_and_check(temp_dir.path(), &cases).await; + query_and_check(temp_dir.path(), config, &cases).await; } } @@ -315,8 +320,9 @@ mod tests { .unwrap(); } - async fn query_and_check(path: &Path, cases: &[(&str, Vec)]) { - let index = Index::open_in_dir(path).unwrap(); + async fn query_and_check(path: &Path, config: Config, cases: &[(&str, Vec)]) { + let mut index = Index::open_in_dir(path).unwrap(); + index.set_tokenizers(config.build_tantivy_tokenizer()); let reader = index.reader().unwrap(); let searcher = reader.searcher(); for (query, expected) in cases { diff --git a/src/index/src/fulltext_index/search/tantivy.rs b/src/index/src/fulltext_index/search/tantivy.rs index 61c87e863f..a55b599d21 100644 --- a/src/index/src/fulltext_index/search/tantivy.rs +++ b/src/index/src/fulltext_index/search/tantivy.rs @@ -29,6 +29,7 @@ use crate::fulltext_index::error::{ Result, TantivyDocNotFoundSnafu, TantivyParserSnafu, TantivySnafu, }; use crate::fulltext_index::search::{FulltextIndexSearcher, RowId}; +use crate::fulltext_index::Config; /// `TantivyFulltextIndexSearcher` is a searcher using Tantivy. pub struct TantivyFulltextIndexSearcher { @@ -42,10 +43,11 @@ pub struct TantivyFulltextIndexSearcher { impl TantivyFulltextIndexSearcher { /// Creates a new `TantivyFulltextIndexSearcher`. - pub fn new(path: impl AsRef) -> Result { + pub fn new(path: impl AsRef, config: Config) -> Result { let now = Instant::now(); - let index = Index::open_in_dir(path.as_ref()).context(TantivySnafu)?; + let mut index = Index::open_in_dir(path.as_ref()).context(TantivySnafu)?; + index.set_tokenizers(config.build_tantivy_tokenizer()); let reader = index .reader_builder() .reload_policy(ReloadPolicy::Manual) diff --git a/src/index/src/fulltext_index/tests.rs b/src/index/src/fulltext_index/tests.rs index d3491a7e9d..a2a87a645a 100644 --- a/src/index/src/fulltext_index/tests.rs +++ b/src/index/src/fulltext_index/tests.rs @@ -19,7 +19,7 @@ use common_test_util::temp_dir::{create_temp_dir, TempDir}; use puffin::puffin_manager::file_accessor::MockFileAccessor; use puffin::puffin_manager::fs_puffin_manager::FsPuffinManager; use puffin::puffin_manager::stager::BoundedStager; -use puffin::puffin_manager::{DirGuard, PuffinManager, PuffinReader, PuffinWriter, PutOptions}; +use puffin::puffin_manager::{PuffinManager, PuffinReader, PuffinWriter, PutOptions}; use crate::fulltext_index::create::{FulltextIndexCreator, TantivyFulltextIndexCreator}; use crate::fulltext_index::search::{FulltextIndexSearcher, RowId, TantivyFulltextIndexSearcher}; @@ -61,8 +61,7 @@ async fn test_search( prefix: &str, config: Config, texts: Vec<&str>, - query: &str, - expected: impl IntoIterator, + query_expected: Vec<(&str, impl IntoIterator)>, ) { let (_staging_dir, stager) = new_bounded_stager(prefix).await; let file_accessor = Arc::new(MockFileAccessor::new(prefix)); @@ -72,14 +71,16 @@ async fn test_search( let blob_key = "fulltext_index".to_string(); let mut writer = puffin_manager.writer(&file_name).await.unwrap(); create_index(prefix, &mut writer, &blob_key, texts, config).await; + writer.finish().await.unwrap(); let reader = puffin_manager.reader(&file_name).await.unwrap(); let index_dir = reader.dir(&blob_key).await.unwrap(); - let searcher = TantivyFulltextIndexSearcher::new(index_dir.path()).unwrap(); - let results = searcher.search(query).await.unwrap(); - - let expected = expected.into_iter().collect::>(); - assert_eq!(results, expected); + let searcher = TantivyFulltextIndexSearcher::new(index_dir.path(), config).unwrap(); + for (query, expected) in query_expected { + let results = searcher.search(query).await.unwrap(); + let expected = expected.into_iter().collect::>(); + assert_eq!(results, expected); + } } #[tokio::test] @@ -91,8 +92,7 @@ async fn test_simple_term() { "This is a sample text containing Barack Obama", "Another document mentioning Barack", ], - "Barack Obama", - [0, 1], + vec![("Barack Obama", [0, 1])], ) .await; } @@ -103,8 +103,7 @@ async fn test_negative_term() { "test_negative_term_", Config::default(), vec!["apple is a fruit", "I like apple", "fruit is healthy"], - "apple -fruit", - [1], + vec![("apple -fruit", [1])], ) .await; } @@ -119,8 +118,7 @@ async fn test_must_term() { "I love apples and fruits", "apple and fruit are good", ], - "+apple +fruit", - [2], + vec![("+apple +fruit", [2])], ) .await; } @@ -131,8 +129,7 @@ async fn test_boolean_operators() { "test_boolean_operators_", Config::default(), vec!["a b c", "a b", "b c", "c"], - "a AND b OR c", - [0, 1, 2, 3], + vec![("a AND b OR c", [0, 1, 2, 3])], ) .await; } @@ -146,8 +143,7 @@ async fn test_phrase_term() { "This is a sample text containing Barack Obama", "Another document mentioning Barack", ], - "\"Barack Obama\"", - [0], + vec![("\"Barack Obama\"", [0])], ) .await; } @@ -161,8 +157,7 @@ async fn test_config_english_analyzer_case_insensitive() { ..Config::default() }, vec!["Banana is a fruit", "I like apple", "Fruit is healthy"], - "banana", - [0], + vec![("banana", [0]), ("Banana", [0]), ("BANANA", [0])], ) .await; } @@ -175,9 +170,8 @@ async fn test_config_english_analyzer_case_sensitive() { case_sensitive: true, ..Config::default() }, - vec!["Banana is a fruit", "I like apple", "Fruit is healthy"], - "banana", - [], + vec!["Banana is a fruit", "I like banana", "Fruit is healthy"], + vec![("banana", [1]), ("Banana", [0])], ) .await; } @@ -191,8 +185,7 @@ async fn test_config_chinese_analyzer() { ..Default::default() }, vec!["苹果是一种水果", "我喜欢苹果", "水果很健康"], - "苹果", - [0, 1], + vec![("苹果", [0, 1])], ) .await; } diff --git a/src/index/src/fulltext_index/tokenizer.rs b/src/index/src/fulltext_index/tokenizer.rs index 721ffdd3b9..54aa33edc8 100644 --- a/src/index/src/fulltext_index/tokenizer.rs +++ b/src/index/src/fulltext_index/tokenizer.rs @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use jieba_rs::Jieba; - use crate::fulltext_index::error::Result; use crate::Bytes; +lazy_static::lazy_static! { + static ref JIEBA: jieba_rs::Jieba = jieba_rs::Jieba::new(); +} + /// `Tokenizer` tokenizes a text into a list of tokens. pub trait Tokenizer: Send { fn tokenize<'a>(&self, text: &'a str) -> Vec<&'a str>; @@ -44,8 +46,11 @@ pub struct ChineseTokenizer; impl Tokenizer for ChineseTokenizer { fn tokenize<'a>(&self, text: &'a str) -> Vec<&'a str> { - let jieba = Jieba::new(); - jieba.cut(text, false) + if text.is_ascii() { + EnglishTokenizer {}.tokenize(text) + } else { + JIEBA.cut(text, false) + } } } diff --git a/src/index/src/inverted_index/create/sort/external_sort.rs b/src/index/src/inverted_index/create/sort/external_sort.rs index e8f67b7b7b..3b4eaebc5c 100644 --- a/src/index/src/inverted_index/create/sort/external_sort.rs +++ b/src/index/src/inverted_index/create/sort/external_sort.rs @@ -481,7 +481,7 @@ mod tests { let mock_values = dic_values .iter() - .flat_map(|(value, size)| iter::repeat(value.clone()).take(*size)) + .flat_map(|(value, size)| std::iter::repeat_n(value.clone(), *size)) .collect::>(); let sorted_result = sorted_result(&mock_values, segment_row_count); diff --git a/src/log-store/Cargo.toml b/src/log-store/Cargo.toml index 31571afe3e..354dee6102 100644 --- a/src/log-store/Cargo.toml +++ b/src/log-store/Cargo.toml @@ -26,6 +26,7 @@ common-runtime.workspace = true common-telemetry.workspace = true common-time.workspace = true common-wal.workspace = true +dashmap.workspace = true delta-encoding = "0.4" derive_builder.workspace = true futures.workspace = true diff --git a/src/log-store/src/kafka.rs b/src/log-store/src/kafka.rs index 1d765cf90b..452c88164a 100644 --- a/src/log-store/src/kafka.rs +++ b/src/log-store/src/kafka.rs @@ -14,9 +14,12 @@ pub(crate) mod client_manager; pub(crate) mod consumer; +mod high_watermark_manager; pub(crate) mod index; pub mod log_store; pub(crate) mod producer; +#[cfg(test)] +pub(crate) mod test_util; pub(crate) mod util; pub(crate) mod worker; diff --git a/src/log-store/src/kafka/client_manager.rs b/src/log-store/src/kafka/client_manager.rs index 9317cc938b..65599cb09e 100644 --- a/src/log-store/src/kafka/client_manager.rs +++ b/src/log-store/src/kafka/client_manager.rs @@ -17,6 +17,7 @@ use std::sync::Arc; use common_wal::config::kafka::common::DEFAULT_BACKOFF_CONFIG; use common_wal::config::kafka::DatanodeKafkaConfig; +use dashmap::DashMap; use rskafka::client::partition::{Compression, PartitionClient, UnknownTopicHandling}; use rskafka::client::ClientBuilder; use snafu::ResultExt; @@ -64,6 +65,9 @@ pub(crate) struct ClientManager { flush_batch_size: usize, compression: Compression, + + /// High watermark for each topic. + high_watermark: Arc, u64>>, } impl ClientManager { @@ -71,6 +75,7 @@ impl ClientManager { pub(crate) async fn try_new( config: &DatanodeKafkaConfig, global_index_collector: Option, + high_watermark: Arc, u64>>, ) -> Result { // Sets backoff config for the top-level kafka client and all clients constructed by it. let broker_endpoints = common_wal::resolve_to_ipv4(&config.connection.broker_endpoints) @@ -96,6 +101,7 @@ impl ClientManager { flush_batch_size: config.max_batch_bytes.as_bytes() as usize, compression: Compression::Lz4, global_index_collector, + high_watermark, }) } @@ -111,6 +117,7 @@ impl ClientManager { .write() .await .insert(provider.clone(), client.clone()); + self.high_watermark.insert(provider.clone(), 0); Ok(client) } } @@ -159,6 +166,7 @@ impl ClientManager { self.compression, self.flush_batch_size, index_collector, + self.high_watermark.clone(), )); Ok(Client { client, producer }) @@ -167,66 +175,20 @@ impl ClientManager { pub(crate) fn global_index_collector(&self) -> Option<&GlobalIndexCollector> { self.global_index_collector.as_ref() } + + #[cfg(test)] + pub(crate) fn high_watermark(&self) -> &Arc, u64>> { + &self.high_watermark + } } #[cfg(test)] mod tests { - use common_wal::config::kafka::common::KafkaConnectionConfig; use common_wal::test_util::run_test_with_kafka_wal; use tokio::sync::Barrier; use super::*; - - /// Creates `num_topics` number of topics each will be decorated by the given decorator. - pub async fn create_topics( - num_topics: usize, - decorator: F, - broker_endpoints: &[String], - ) -> Vec - where - F: Fn(usize) -> String, - { - assert!(!broker_endpoints.is_empty()); - let client = ClientBuilder::new(broker_endpoints.to_vec()) - .build() - .await - .unwrap(); - let ctrl_client = client.controller_client().unwrap(); - let (topics, tasks): (Vec<_>, Vec<_>) = (0..num_topics) - .map(|i| { - let topic = decorator(i); - let task = ctrl_client.create_topic(topic.clone(), 1, 1, 500); - (topic, task) - }) - .unzip(); - futures::future::try_join_all(tasks).await.unwrap(); - topics - } - - /// Prepares for a test in that a collection of topics and a client manager are created. - async fn prepare( - test_name: &str, - num_topics: usize, - broker_endpoints: Vec, - ) -> (ClientManager, Vec) { - let topics = create_topics( - num_topics, - |i| format!("{test_name}_{}_{}", i, uuid::Uuid::new_v4()), - &broker_endpoints, - ) - .await; - - let config = DatanodeKafkaConfig { - connection: KafkaConnectionConfig { - broker_endpoints, - ..Default::default() - }, - ..Default::default() - }; - let manager = ClientManager::try_new(&config, None).await.unwrap(); - - (manager, topics) - } + use crate::kafka::test_util::prepare; /// Sends `get_or_insert` requests sequentially to the client manager, and checks if it could handle them correctly. #[tokio::test] diff --git a/src/log-store/src/kafka/high_watermark_manager.rs b/src/log-store/src/kafka/high_watermark_manager.rs new file mode 100644 index 0000000000..8a4c2a1252 --- /dev/null +++ b/src/log-store/src/kafka/high_watermark_manager.rs @@ -0,0 +1,131 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::Arc; +use std::time::Duration; + +use common_telemetry::error; +use dashmap::DashMap; +use store_api::logstore::provider::KafkaProvider; +use tokio::time::{interval, MissedTickBehavior}; + +use crate::error::Result; +use crate::kafka::client_manager::ClientManagerRef; + +/// HighWatermarkManager is responsible for periodically updating the high watermark +/// (latest existing record offset) for each Kafka topic. +pub(crate) struct HighWatermarkManager { + /// Interval to update high watermark. + update_interval: Duration, + /// The high watermark for each topic. + high_watermark: Arc, u64>>, + /// Client manager to send requests. + client_manager: ClientManagerRef, +} + +impl HighWatermarkManager { + pub(crate) fn new( + update_interval: Duration, + high_watermark: Arc, u64>>, + client_manager: ClientManagerRef, + ) -> Self { + Self { + update_interval, + high_watermark, + client_manager, + } + } + + /// Starts the high watermark manager as a background task + /// + /// This spawns a task that periodically queries Kafka for the latest + /// high watermark values for all registered topics and updates the shared map. + pub(crate) async fn run(self) { + common_runtime::spawn_global(async move { + let mut interval = interval(self.update_interval); + interval.set_missed_tick_behavior(MissedTickBehavior::Skip); + loop { + interval.tick().await; + if let Err(e) = self.try_update().await { + error!(e; "Failed to update high watermark"); + } + } + }); + } + + /// Attempts to update the high watermark for all registered topics + /// + /// Iterates through all topics in the high watermark map, obtains a producer + /// for each topic, and requests an update of the high watermark value. + pub(crate) async fn try_update(&self) -> Result<()> { + for iterator_element in self.high_watermark.iter() { + let producer = self + .client_manager + .get_or_insert(iterator_element.key()) + .await? + .producer() + .clone(); + producer.update_high_watermark().await?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use common_wal::test_util::run_test_with_kafka_wal; + use store_api::storage::RegionId; + + use super::*; + use crate::kafka::test_util::{prepare, record}; + + #[tokio::test] + async fn test_try_update_high_watermark() { + run_test_with_kafka_wal(|broker_endpoints| { + Box::pin(async { + let (manager, topics) = + prepare("test_try_update_high_watermark", 1, broker_endpoints).await; + let manager = Arc::new(manager); + let high_watermark_manager = HighWatermarkManager::new( + Duration::from_millis(100), + manager.high_watermark().clone(), + manager.clone(), + ); + let high_watermark = high_watermark_manager.high_watermark.clone(); + high_watermark_manager.run().await; + + let topic = topics[0].clone(); + let provider = Arc::new(KafkaProvider::new(topic.to_string())); + let producer = manager + .get_or_insert(&provider) + .await + .unwrap() + .producer() + .clone(); + + tokio::time::sleep(Duration::from_millis(150)).await; + let current_high_watermark = *high_watermark.get(&provider).unwrap(); + assert_eq!(current_high_watermark, 0); + + let record = vec![record()]; + let region = RegionId::new(1, 1); + producer.produce(region, record.clone()).await.unwrap(); + tokio::time::sleep(Duration::from_millis(150)).await; + let current_high_watermark = *high_watermark.get(&provider).unwrap(); + assert_eq!(current_high_watermark, record.len() as u64); + }) + }) + .await + } +} diff --git a/src/log-store/src/kafka/log_store.rs b/src/log-store/src/kafka/log_store.rs index 3cc728a998..66ad613bbc 100644 --- a/src/log-store/src/kafka/log_store.rs +++ b/src/log-store/src/kafka/log_store.rs @@ -18,6 +18,7 @@ use std::time::Duration; use common_telemetry::{debug, warn}; use common_wal::config::kafka::DatanodeKafkaConfig; +use dashmap::DashMap; use futures::future::try_join_all; use futures_util::StreamExt; use rskafka::client::partition::OffsetAt; @@ -32,6 +33,7 @@ use store_api::storage::RegionId; use crate::error::{self, ConsumeRecordSnafu, Error, GetOffsetSnafu, InvalidProviderSnafu, Result}; use crate::kafka::client_manager::{ClientManager, ClientManagerRef}; use crate::kafka::consumer::{ConsumerBuilder, RecordsBuffer}; +use crate::kafka::high_watermark_manager::HighWatermarkManager; use crate::kafka::index::{ build_region_wal_index_iterator, GlobalIndexCollector, MIN_BATCH_WINDOW_SIZE, }; @@ -41,6 +43,8 @@ use crate::kafka::util::record::{ }; use crate::metrics; +const DEFAULT_HIGH_WATERMARK_UPDATE_INTERVAL: Duration = Duration::from_secs(60); + /// A log store backed by Kafka. #[derive(Debug)] pub struct KafkaLogStore { @@ -52,6 +56,18 @@ pub struct KafkaLogStore { consumer_wait_timeout: Duration, /// Ignore missing entries during read WAL. overwrite_entry_start_id: bool, + /// High watermark for all topics. + /// + /// Represents the offset of the last record in each topic. This is used to track + /// the latest available data in Kafka topics. + /// + /// The high watermark is updated in two ways: + /// - Automatically when the producer successfully commits data to Kafka + /// - Periodically by the [HighWatermarkManager](crate::kafka::high_watermark_manager::HighWatermarkManager). + /// + /// This shared map allows multiple components to access the latest high watermark + /// information without needing to query Kafka directly. + high_watermark: Arc, u64>>, } impl KafkaLogStore { @@ -60,14 +76,23 @@ impl KafkaLogStore { config: &DatanodeKafkaConfig, global_index_collector: Option, ) -> Result { - let client_manager = - Arc::new(ClientManager::try_new(config, global_index_collector).await?); + let high_watermark = Arc::new(DashMap::new()); + let client_manager = Arc::new( + ClientManager::try_new(config, global_index_collector, high_watermark.clone()).await?, + ); + let high_watermark_manager = HighWatermarkManager::new( + DEFAULT_HIGH_WATERMARK_UPDATE_INTERVAL, + high_watermark.clone(), + client_manager.clone(), + ); + high_watermark_manager.run().await; Ok(Self { client_manager, max_batch_bytes: config.max_batch_bytes.as_bytes() as usize, consumer_wait_timeout: config.consumer_wait_timeout, overwrite_entry_start_id: config.overwrite_entry_start_id, + high_watermark, }) } } @@ -158,6 +183,7 @@ impl LogStore for KafkaLogStore { .collect::>(); let mut region_grouped_records: HashMap)> = HashMap::with_capacity(region_ids.len()); + let mut region_to_provider = HashMap::with_capacity(region_ids.len()); for entry in entries { let provider = entry.provider().as_kafka_provider().with_context(|| { error::InvalidProviderSnafu { @@ -165,6 +191,7 @@ impl LogStore for KafkaLogStore { actual: entry.provider().type_name(), } })?; + region_to_provider.insert(entry.region_id(), provider.clone()); let region_id = entry.region_id(); match region_grouped_records.entry(region_id) { std::collections::hash_map::Entry::Occupied(mut slot) => { @@ -199,6 +226,13 @@ impl LogStore for KafkaLogStore { )) .await?; + // Updates the high watermark offset of the last record in the topic. + for (region_id, offset) in ®ion_grouped_max_offset { + // Safety: `region_id` is always valid. + let provider = region_to_provider.get(region_id).unwrap(); + self.high_watermark.insert(provider.clone(), *offset); + } + Ok(AppendBatchResponse { last_entry_ids: region_grouped_max_offset.into_iter().collect(), }) @@ -383,6 +417,25 @@ impl LogStore for KafkaLogStore { Ok(()) } + /// Returns the highest entry id of the specified topic in remote WAL. + fn high_watermark(&self, provider: &Provider) -> Result { + let provider = provider + .as_kafka_provider() + .with_context(|| InvalidProviderSnafu { + expected: KafkaProvider::type_name(), + actual: provider.type_name(), + })?; + + let high_watermark = self + .high_watermark + .get(provider) + .as_deref() + .copied() + .unwrap_or(0); + + Ok(high_watermark) + } + /// Stops components of the logstore. async fn stop(&self) -> Result<()> { Ok(()) @@ -567,6 +620,8 @@ mod tests { .for_each(|entry| entry.set_entry_id(0)); assert_eq!(expected_entries, actual_entries); } + let high_wathermark = logstore.high_watermark(&provider).unwrap(); + assert_eq!(high_wathermark, 99); } #[tokio::test] @@ -640,5 +695,7 @@ mod tests { .for_each(|entry| entry.set_entry_id(0)); assert_eq!(expected_entries, actual_entries); } + let high_wathermark = logstore.high_watermark(&provider).unwrap(); + assert_eq!(high_wathermark, (data_size_kb as u64 / 8 + 1) * 20 * 5 - 1); } } diff --git a/src/log-store/src/kafka/producer.rs b/src/log-store/src/kafka/producer.rs index 910465ba61..6f89c75e7a 100644 --- a/src/log-store/src/kafka/producer.rs +++ b/src/log-store/src/kafka/producer.rs @@ -15,6 +15,7 @@ use std::sync::Arc; use common_telemetry::warn; +use dashmap::DashMap; use rskafka::client::partition::{Compression, OffsetAt, PartitionClient}; use rskafka::record::Record; use store_api::logstore::provider::KafkaProvider; @@ -56,6 +57,7 @@ impl OrderedBatchProducer { compression: Compression, max_batch_bytes: usize, index_collector: Box, + high_watermark: Arc, u64>>, ) -> Self { let mut worker = BackgroundProducerWorker { provider, @@ -65,6 +67,7 @@ impl OrderedBatchProducer { request_batch_size: REQUEST_BATCH_SIZE, max_batch_bytes, index_collector, + high_watermark, }; tokio::spawn(async move { worker.run().await }); Self { sender: tx } @@ -90,6 +93,21 @@ impl OrderedBatchProducer { Ok(handle) } + + /// Sends an [WorkerRequest::UpdateHighWatermark] request to the producer. + /// This is used to update the high watermark for the topic. + pub(crate) async fn update_high_watermark(&self) -> Result<()> { + if self + .sender + .send(WorkerRequest::UpdateHighWatermark) + .await + .is_err() + { + warn!("OrderedBatchProducer is already exited"); + return error::OrderedBatchProducerStoppedSnafu {}.fail(); + } + Ok(()) + } } #[async_trait::async_trait] @@ -135,7 +153,6 @@ mod tests { use std::sync::{Arc, Mutex}; use std::time::Duration; - use chrono::{TimeZone, Utc}; use common_base::readable_size::ReadableSize; use common_telemetry::debug; use futures::stream::FuturesUnordered; @@ -149,6 +166,7 @@ mod tests { use super::*; use crate::kafka::index::NoopCollector; use crate::kafka::producer::OrderedBatchProducer; + use crate::kafka::test_util::record; #[derive(Debug)] struct MockClient { @@ -196,15 +214,6 @@ mod tests { } } - fn record() -> Record { - Record { - key: Some(vec![0; 4]), - value: Some(vec![0; 6]), - headers: Default::default(), - timestamp: Utc.timestamp_millis_opt(320).unwrap(), - } - } - #[tokio::test] async fn test_producer() { common_telemetry::init_default_ut_logging(); @@ -224,6 +233,7 @@ mod tests { Compression::NoCompression, ReadableSize((record.approximate_size() * 2) as u64).as_bytes() as usize, Box::new(NoopCollector), + Arc::new(DashMap::new()), ); let region_id = RegionId::new(1, 1); @@ -272,6 +282,7 @@ mod tests { Compression::NoCompression, ReadableSize((record.approximate_size() * 2) as u64).as_bytes() as usize, Box::new(NoopCollector), + Arc::new(DashMap::new()), ); let region_id = RegionId::new(1, 1); @@ -324,6 +335,7 @@ mod tests { Compression::NoCompression, ReadableSize((record.approximate_size() * 2) as u64).as_bytes() as usize, Box::new(NoopCollector), + Arc::new(DashMap::new()), ); let region_id = RegionId::new(1, 1); diff --git a/src/log-store/src/kafka/test_util.rs b/src/log-store/src/kafka/test_util.rs new file mode 100644 index 0000000000..c83e0ef00e --- /dev/null +++ b/src/log-store/src/kafka/test_util.rs @@ -0,0 +1,88 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::Arc; + +use chrono::{TimeZone, Utc}; +use common_wal::config::kafka::common::KafkaConnectionConfig; +use common_wal::config::kafka::DatanodeKafkaConfig; +use dashmap::DashMap; +use rskafka::client::ClientBuilder; +use rskafka::record::Record; + +use crate::kafka::client_manager::ClientManager; + +/// Creates `num_topics` number of topics each will be decorated by the given decorator. +pub(crate) async fn create_topics( + num_topics: usize, + decorator: F, + broker_endpoints: &[String], +) -> Vec +where + F: Fn(usize) -> String, +{ + assert!(!broker_endpoints.is_empty()); + let client = ClientBuilder::new(broker_endpoints.to_vec()) + .build() + .await + .unwrap(); + let ctrl_client = client.controller_client().unwrap(); + let (topics, tasks): (Vec<_>, Vec<_>) = (0..num_topics) + .map(|i| { + let topic = decorator(i); + let task = ctrl_client.create_topic(topic.clone(), 1, 1, 500); + (topic, task) + }) + .unzip(); + futures::future::try_join_all(tasks).await.unwrap(); + topics +} + +/// Prepares for a test in that a collection of topics and a client manager are created. +pub(crate) async fn prepare( + test_name: &str, + num_topics: usize, + broker_endpoints: Vec, +) -> (ClientManager, Vec) { + let topics = create_topics( + num_topics, + |i| format!("{test_name}_{}_{}", i, uuid::Uuid::new_v4()), + &broker_endpoints, + ) + .await; + + let config = DatanodeKafkaConfig { + connection: KafkaConnectionConfig { + broker_endpoints, + ..Default::default() + }, + ..Default::default() + }; + let high_watermark = Arc::new(DashMap::new()); + let manager = ClientManager::try_new(&config, None, high_watermark) + .await + .unwrap(); + + (manager, topics) +} + +/// Generate a record to produce. +pub fn record() -> Record { + Record { + key: Some(vec![0; 4]), + value: Some(vec![0; 6]), + headers: Default::default(), + timestamp: Utc.timestamp_millis_opt(320).unwrap(), + } +} diff --git a/src/log-store/src/kafka/worker.rs b/src/log-store/src/kafka/worker.rs index b05351d172..372d8c5567 100644 --- a/src/log-store/src/kafka/worker.rs +++ b/src/log-store/src/kafka/worker.rs @@ -15,10 +15,12 @@ pub(crate) mod dump_index; pub(crate) mod flush; pub(crate) mod produce; +pub(crate) mod update_high_watermark; use std::sync::Arc; use common_telemetry::debug; +use dashmap::DashMap; use futures::future::try_join_all; use rskafka::client::partition::Compression; use rskafka::record::Record; @@ -37,6 +39,7 @@ pub(crate) enum WorkerRequest { Produce(ProduceRequest), TruncateIndex(TruncateIndexRequest), DumpIndex(DumpIndexRequest), + UpdateHighWatermark, } impl WorkerRequest { @@ -157,6 +160,8 @@ pub(crate) struct BackgroundProducerWorker { pub(crate) max_batch_bytes: usize, /// Collecting ids of WAL entries. pub(crate) index_collector: Box, + /// High watermark for all topics. + pub(crate) high_watermark: Arc, u64>>, } impl BackgroundProducerWorker { @@ -194,6 +199,9 @@ impl BackgroundProducerWorker { entry_id, }) => self.index_collector.truncate(region_id, entry_id), WorkerRequest::DumpIndex(req) => self.dump_index(req).await, + WorkerRequest::UpdateHighWatermark => { + self.update_high_watermark().await; + } } } diff --git a/src/log-store/src/kafka/worker/update_high_watermark.rs b/src/log-store/src/kafka/worker/update_high_watermark.rs new file mode 100644 index 0000000000..8404086418 --- /dev/null +++ b/src/log-store/src/kafka/worker/update_high_watermark.rs @@ -0,0 +1,59 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use common_telemetry::{debug, error}; +use rskafka::client::partition::OffsetAt; +use snafu::ResultExt; + +use crate::error; +use crate::kafka::worker::BackgroundProducerWorker; + +impl BackgroundProducerWorker { + /// Updates the high watermark for the topic. + /// + /// This function retrieves the latest offset from Kafka and updates the high watermark + /// in the shared map. + pub async fn update_high_watermark(&mut self) { + match self + .client + .get_offset(OffsetAt::Latest) + .await + .context(error::GetOffsetSnafu { + topic: &self.provider.topic, + }) { + Ok(offset) => match self.high_watermark.entry(self.provider.clone()) { + dashmap::Entry::Occupied(mut occupied_entry) => { + let offset = offset as u64; + if *occupied_entry.get() != offset { + occupied_entry.insert(offset); + debug!( + "Updated high watermark for topic {} to {}", + self.provider.topic, offset + ); + } + } + dashmap::Entry::Vacant(vacant_entry) => { + vacant_entry.insert(offset as u64); + debug!( + "Inserted high watermark for topic {} to {}", + self.provider.topic, offset + ); + } + }, + Err(err) => { + error!(err; "Failed to get offset for topic {}", self.provider.topic); + } + } + } +} diff --git a/src/log-store/src/raft_engine/backend.rs b/src/log-store/src/raft_engine/backend.rs index 3d41e5298d..8d27994f8b 100644 --- a/src/log-store/src/raft_engine/backend.rs +++ b/src/log-store/src/raft_engine/backend.rs @@ -114,7 +114,13 @@ impl TxnService for RaftEngineBackend { } = txn.into(); let mut succeeded = true; + + // Here we are using the write lock to guard against parallel access inside "txn", and + // outside "get" or "put" etc. It doesn't serve the purpose of mutating some Rust data, so + // the variable is not "mut". Suppress the clippy warning because of this. + #[allow(clippy::readonly_write_lock)] let engine = self.engine.write().unwrap(); + for cmp in compare { let existing_value = engine_get(&engine, &cmp.key)?.map(|kv| kv.value); if !cmp.compare_value(existing_value.as_ref()) { diff --git a/src/log-store/src/raft_engine/log_store.rs b/src/log-store/src/raft_engine/log_store.rs index c7df8be66c..a060c9a976 100644 --- a/src/log-store/src/raft_engine/log_store.rs +++ b/src/log-store/src/raft_engine/log_store.rs @@ -483,6 +483,18 @@ impl LogStore for RaftEngineLogStore { ); Ok(()) } + + fn high_watermark(&self, provider: &Provider) -> Result { + let ns = provider + .as_raft_engine_provider() + .with_context(|| InvalidProviderSnafu { + expected: RaftEngineProvider::type_name(), + actual: provider.type_name(), + })?; + let namespace_id = ns.id; + let last_index = self.engine.last_index(namespace_id).unwrap_or(0); + Ok(last_index) + } } #[derive(Debug, Clone)] diff --git a/src/meta-client/src/client.rs b/src/meta-client/src/client.rs index 3829ae2273..e022717ea1 100644 --- a/src/meta-client/src/client.rs +++ b/src/meta-client/src/client.rs @@ -117,6 +117,7 @@ impl MetaClientBuilder { .enable_store() .enable_heartbeat() .enable_procedure() + .enable_access_cluster_info() } pub fn enable_heartbeat(self) -> Self { diff --git a/src/meta-srv/Cargo.toml b/src/meta-srv/Cargo.toml index 15830a4f05..0e0f3e5cde 100644 --- a/src/meta-srv/Cargo.toml +++ b/src/meta-srv/Cargo.toml @@ -83,6 +83,7 @@ chrono.workspace = true client = { workspace = true, features = ["testing"] } common-meta = { workspace = true, features = ["testing"] } common-procedure-test.workspace = true +common-wal = { workspace = true, features = ["testing"] } session.workspace = true tracing = "0.1" tracing-subscriber.workspace = true diff --git a/src/meta-srv/src/bootstrap.rs b/src/meta-srv/src/bootstrap.rs index 33c32443e2..40e41bb815 100644 --- a/src/meta-srv/src/bootstrap.rs +++ b/src/meta-srv/src/bootstrap.rs @@ -66,10 +66,12 @@ use crate::election::postgres::PgElection; #[cfg(any(feature = "pg_kvbackend", feature = "mysql_kvbackend"))] use crate::election::CANDIDATE_LEASE_SECS; use crate::metasrv::builder::MetasrvBuilder; -use crate::metasrv::{BackendImpl, Metasrv, MetasrvOptions, SelectorRef}; +use crate::metasrv::{BackendImpl, Metasrv, MetasrvOptions, SelectTarget, SelectorRef}; +use crate::node_excluder::NodeExcluderRef; use crate::selector::lease_based::LeaseBasedSelector; use crate::selector::load_based::LoadBasedSelector; use crate::selector::round_robin::RoundRobinSelector; +use crate::selector::weight_compute::RegionNumsBasedWeightCompute; use crate::selector::SelectorType; use crate::service::admin; use crate::{error, Result}; @@ -294,10 +296,31 @@ pub async fn metasrv_builder( let in_memory = Arc::new(MemoryKvBackend::new()) as ResettableKvBackendRef; - let selector = match opts.selector { - SelectorType::LoadBased => Arc::new(LoadBasedSelector::default()) as SelectorRef, - SelectorType::LeaseBased => Arc::new(LeaseBasedSelector) as SelectorRef, - SelectorType::RoundRobin => Arc::new(RoundRobinSelector::default()) as SelectorRef, + let node_excluder = plugins + .get::() + .unwrap_or_else(|| Arc::new(Vec::new()) as NodeExcluderRef); + let selector = if let Some(selector) = plugins.get::() { + info!("Using selector from plugins"); + selector + } else { + let selector = match opts.selector { + SelectorType::LoadBased => Arc::new(LoadBasedSelector::new( + RegionNumsBasedWeightCompute, + node_excluder, + )) as SelectorRef, + SelectorType::LeaseBased => { + Arc::new(LeaseBasedSelector::new(node_excluder)) as SelectorRef + } + SelectorType::RoundRobin => Arc::new(RoundRobinSelector::new( + SelectTarget::Datanode, + node_excluder, + )) as SelectorRef, + }; + info!( + "Using selector from options, selector type: {}", + opts.selector.as_ref() + ); + selector }; Ok(MetasrvBuilder::new() diff --git a/src/meta-srv/src/error.rs b/src/meta-srv/src/error.rs index 7c45fa408f..d6c4ef4208 100644 --- a/src/meta-srv/src/error.rs +++ b/src/meta-srv/src/error.rs @@ -336,6 +336,13 @@ pub enum Error { location: Location, }, + #[snafu(display("Region's leader peer changed: {}", msg))] + LeaderPeerChanged { + msg: String, + #[snafu(implicit)] + location: Location, + }, + #[snafu(display("Invalid arguments: {}", err_msg))] InvalidArguments { err_msg: String, @@ -518,6 +525,13 @@ pub enum Error { source: common_procedure::Error, }, + #[snafu(display("A prune task for topic {} is already running", topic))] + PruneTaskAlreadyRunning { + topic: String, + #[snafu(implicit)] + location: Location, + }, + #[snafu(display("Schema already exists, name: {schema_name}"))] SchemaAlreadyExists { schema_name: String, @@ -788,6 +802,14 @@ pub enum Error { source: common_meta::error::Error, }, + #[snafu(display("Failed to build kafka client."))] + BuildKafkaClient { + #[snafu(implicit)] + location: Location, + #[snafu(source)] + error: common_meta::error::Error, + }, + #[snafu(display( "Failed to build a Kafka partition client, topic: {}, partition: {}", topic, @@ -875,7 +897,9 @@ impl ErrorExt for Error { | Error::FlowStateHandler { .. } | Error::BuildWalOptionsAllocator { .. } | Error::BuildPartitionClient { .. } - | Error::DeleteRecords { .. } => StatusCode::Internal, + | Error::BuildKafkaClient { .. } + | Error::DeleteRecords { .. } + | Error::PruneTaskAlreadyRunning { .. } => StatusCode::Internal, Error::Unsupported { .. } => StatusCode::Unsupported, @@ -897,7 +921,8 @@ impl ErrorExt for Error { | Error::ProcedureNotFound { .. } | Error::TooManyPartitions { .. } | Error::TomlFormat { .. } - | Error::HandlerNotFound { .. } => StatusCode::InvalidArguments, + | Error::HandlerNotFound { .. } + | Error::LeaderPeerChanged { .. } => StatusCode::InvalidArguments, Error::LeaseKeyFromUtf8 { .. } | Error::LeaseValueFromUtf8 { .. } | Error::InvalidRegionKeyFromUtf8 { .. } diff --git a/src/meta-srv/src/flow_meta_alloc.rs b/src/meta-srv/src/flow_meta_alloc.rs index bdfac158aa..5c9dd82301 100644 --- a/src/meta-srv/src/flow_meta_alloc.rs +++ b/src/meta-srv/src/flow_meta_alloc.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashSet; + use common_error::ext::BoxedError; use common_meta::ddl::flow_meta::PartitionPeerAllocator; use common_meta::peer::Peer; @@ -40,6 +42,7 @@ impl PartitionPeerAllocator for FlowPeerAllocator { SelectorOptions { min_required_items: partitions, allow_duplication: true, + exclude_peer_ids: HashSet::new(), }, ) .await diff --git a/src/meta-srv/src/handler/collect_leader_region_handler.rs b/src/meta-srv/src/handler/collect_leader_region_handler.rs index fd5fab3639..13aee5d234 100644 --- a/src/meta-srv/src/handler/collect_leader_region_handler.rs +++ b/src/meta-srv/src/handler/collect_leader_region_handler.rs @@ -13,7 +13,7 @@ // limitations under the License. use api::v1::meta::{HeartbeatRequest, Role}; -use common_meta::region_registry::LeaderRegion; +use common_meta::region_registry::{LeaderRegion, LeaderRegionManifestInfo}; use store_api::region_engine::RegionRole; use crate::error::Result; @@ -44,7 +44,7 @@ impl HeartbeatHandler for CollectLeaderRegionHandler { continue; } - let manifest = stat.region_manifest.into(); + let manifest = LeaderRegionManifestInfo::from_region_stat(stat); let value = LeaderRegion { datanode_id: current_stat.id, manifest, @@ -122,6 +122,8 @@ mod tests { manifest_size: 0, sst_size: 0, index_size: 0, + data_topic_latest_entry_id: 0, + metadata_topic_latest_entry_id: 0, } } @@ -161,6 +163,7 @@ mod tests { manifest: LeaderRegionManifestInfo::Mito { manifest_version: 1, flushed_entry_id: 0, + topic_latest_entry_id: 0, }, }) ); @@ -192,6 +195,7 @@ mod tests { manifest: LeaderRegionManifestInfo::Mito { manifest_version: 2, flushed_entry_id: 0, + topic_latest_entry_id: 0, }, }) ); @@ -224,6 +228,7 @@ mod tests { manifest: LeaderRegionManifestInfo::Mito { manifest_version: 2, flushed_entry_id: 0, + topic_latest_entry_id: 0, }, }) ); diff --git a/src/meta-srv/src/handler/extract_stat_handler.rs b/src/meta-srv/src/handler/extract_stat_handler.rs index b31f41e4ef..ab8334ac3d 100644 --- a/src/meta-srv/src/handler/extract_stat_handler.rs +++ b/src/meta-srv/src/handler/extract_stat_handler.rs @@ -19,6 +19,7 @@ use common_telemetry::{info, warn}; use crate::error::Result; use crate::handler::{HandleControl, HeartbeatAccumulator, HeartbeatHandler}; use crate::metasrv::Context; +use crate::metrics::{METRIC_META_HEARTBEAT_RATE, METRIC_META_HEARTBEAT_STAT_MEMORY_SIZE}; pub struct ExtractStatHandler; @@ -42,6 +43,8 @@ impl HeartbeatHandler for ExtractStatHandler { match Stat::try_from(req) { Ok(stat) => { + METRIC_META_HEARTBEAT_RATE.inc(); + METRIC_META_HEARTBEAT_STAT_MEMORY_SIZE.observe(stat.memory_size() as f64); let _ = acc.stat.insert(stat); } Err(Some(header)) => { diff --git a/src/meta-srv/src/handler/failure_handler.rs b/src/meta-srv/src/handler/failure_handler.rs index 81e9bfaeb9..cdcd9d3228 100644 --- a/src/meta-srv/src/handler/failure_handler.rs +++ b/src/meta-srv/src/handler/failure_handler.rs @@ -102,6 +102,8 @@ mod tests { manifest_version: 0, flushed_entry_id: 0, }, + data_topic_latest_entry_id: 0, + metadata_topic_latest_entry_id: 0, } } acc.stat = Some(Stat { diff --git a/src/meta-srv/src/handler/region_lease_handler.rs b/src/meta-srv/src/handler/region_lease_handler.rs index b89a570b80..d900078897 100644 --- a/src/meta-srv/src/handler/region_lease_handler.rs +++ b/src/meta-srv/src/handler/region_lease_handler.rs @@ -160,6 +160,8 @@ mod test { manifest_version: 0, flushed_entry_id: 0, }, + data_topic_latest_entry_id: 0, + metadata_topic_latest_entry_id: 0, } } diff --git a/src/meta-srv/src/lib.rs b/src/meta-srv/src/lib.rs index 9a9f0861a8..20b9285723 100644 --- a/src/meta-srv/src/lib.rs +++ b/src/meta-srv/src/lib.rs @@ -14,7 +14,7 @@ #![feature(result_flattening)] #![feature(assert_matches)] -#![feature(extract_if)] +#![feature(hash_set_entry)] pub mod bootstrap; pub mod cache_invalidator; @@ -30,6 +30,7 @@ pub mod metasrv; pub mod metrics; #[cfg(feature = "mock")] pub mod mocks; +pub mod node_excluder; pub mod procedure; pub mod pubsub; pub mod region; diff --git a/src/meta-srv/src/metasrv.rs b/src/meta-srv/src/metasrv.rs index 00ac628b61..6c9111dd9c 100644 --- a/src/meta-srv/src/metasrv.rs +++ b/src/meta-srv/src/metasrv.rs @@ -61,6 +61,7 @@ use crate::failure_detector::PhiAccrualFailureDetectorOptions; use crate::handler::{HeartbeatHandlerGroupBuilder, HeartbeatHandlerGroupRef}; use crate::lease::lookup_datanode_peer; use crate::procedure::region_migration::manager::RegionMigrationManagerRef; +use crate::procedure::wal_prune::manager::WalPruneTickerRef; use crate::procedure::ProcedureManagerListenerAdapter; use crate::pubsub::{PublisherRef, SubscriptionManagerRef}; use crate::region::supervisor::RegionSupervisorTickerRef; @@ -110,6 +111,11 @@ pub struct MetasrvOptions { pub use_memory_store: bool, /// Whether to enable region failover. pub enable_region_failover: bool, + /// Whether to allow region failover on local WAL. + /// + /// If it's true, the region failover will be allowed even if the local WAL is used. + /// Note that this option is not recommended to be set to true, because it may lead to data loss during failover. + pub allow_region_failover_on_local_wal: bool, /// The HTTP server options. pub http: HttpOptions, /// The logging options. @@ -172,6 +178,7 @@ impl Default for MetasrvOptions { selector: SelectorType::default(), use_memory_store: false, enable_region_failover: false, + allow_region_failover_on_local_wal: false, http: HttpOptions::default(), logging: LoggingOptions { dir: format!("{METASRV_HOME}/logs"), @@ -407,6 +414,7 @@ pub struct Metasrv { region_supervisor_ticker: Option, cache_invalidator: CacheInvalidatorRef, leader_region_registry: LeaderRegionRegistryRef, + wal_prune_ticker: Option, plugins: Plugins, } @@ -461,6 +469,9 @@ impl Metasrv { if let Some(region_supervisor_ticker) = &self.region_supervisor_ticker { leadership_change_notifier.add_listener(region_supervisor_ticker.clone() as _); } + if let Some(wal_prune_ticker) = &self.wal_prune_ticker { + leadership_change_notifier.add_listener(wal_prune_ticker.clone() as _); + } if let Some(customizer) = self.plugins.get::() { customizer.customize(&mut leadership_change_notifier); } diff --git a/src/meta-srv/src/metasrv/builder.rs b/src/meta-srv/src/metasrv/builder.rs index 0f1a04e47b..0c93e4e4c7 100644 --- a/src/meta-srv/src/metasrv/builder.rs +++ b/src/meta-srv/src/metasrv/builder.rs @@ -37,10 +37,11 @@ use common_meta::region_keeper::MemoryRegionKeeper; use common_meta::region_registry::LeaderRegionRegistry; use common_meta::sequence::SequenceBuilder; use common_meta::state_store::KvStateStore; -use common_meta::wal_options_allocator::build_wal_options_allocator; +use common_meta::wal_options_allocator::{build_kafka_client, build_wal_options_allocator}; use common_procedure::local::{LocalManager, ManagerConfig}; use common_procedure::ProcedureManagerRef; -use snafu::ResultExt; +use common_telemetry::warn; +use snafu::{ensure, ResultExt}; use crate::cache_invalidator::MetasrvCacheInvalidator; use crate::cluster::{MetaPeerClientBuilder, MetaPeerClientRef}; @@ -58,6 +59,8 @@ use crate::metasrv::{ }; use crate::procedure::region_migration::manager::RegionMigrationManager; use crate::procedure::region_migration::DefaultContextFactory; +use crate::procedure::wal_prune::manager::{WalPruneManager, WalPruneTicker}; +use crate::procedure::wal_prune::Context as WalPruneContext; use crate::region::supervisor::{ HeartbeatAcceptor, RegionFailureDetectorControl, RegionSupervisor, RegionSupervisorTicker, DEFAULT_TICK_INTERVAL, @@ -188,7 +191,7 @@ impl MetasrvBuilder { let meta_peer_client = meta_peer_client .unwrap_or_else(|| build_default_meta_peer_client(&election, &in_memory)); - let selector = selector.unwrap_or_else(|| Arc::new(LeaseBasedSelector)); + let selector = selector.unwrap_or_else(|| Arc::new(LeaseBasedSelector::default())); let pushers = Pushers::default(); let mailbox = build_mailbox(&kv_backend, &pushers); let procedure_manager = build_procedure_manager(&options, &kv_backend); @@ -232,13 +235,17 @@ impl MetasrvBuilder { )) }); + let flow_selector = Arc::new(RoundRobinSelector::new( + SelectTarget::Flownode, + Arc::new(Vec::new()), + )) as SelectorRef; + let flow_metadata_allocator = { // for now flownode just use round-robin selector - let flow_selector = RoundRobinSelector::new(SelectTarget::Flownode); let flow_selector_ctx = selector_ctx.clone(); let peer_allocator = Arc::new(FlowPeerAllocator::new( flow_selector_ctx, - Arc::new(flow_selector), + flow_selector.clone(), )); let seq = Arc::new( SequenceBuilder::new(FLOW_ID_SEQ, kv_backend.clone()) @@ -270,18 +277,25 @@ impl MetasrvBuilder { }, )); let peer_lookup_service = Arc::new(MetaPeerLookupService::new(meta_peer_client.clone())); + if !is_remote_wal && options.enable_region_failover { - return error::UnexpectedSnafu { - violated: "Region failover is not supported in the local WAL implementation!", + ensure!( + options.allow_region_failover_on_local_wal, + error::UnexpectedSnafu { + violated: "Region failover is not supported in the local WAL implementation! + If you want to enable region failover for local WAL, please set `allow_region_failover_on_local_wal` to true.", + } + ); + if options.allow_region_failover_on_local_wal { + warn!("Region failover is force enabled in the local WAL implementation! This may lead to data loss during failover!"); } - .fail(); } let (tx, rx) = RegionSupervisor::channel(); let (region_failure_detector_controller, region_supervisor_ticker): ( RegionFailureDetectorControllerRef, Option>, - ) = if options.enable_region_failover && is_remote_wal { + ) = if options.enable_region_failover { ( Arc::new(RegionFailureDetectorControl::new(tx.clone())) as _, Some(Arc::new(RegionSupervisorTicker::new( @@ -307,7 +321,7 @@ impl MetasrvBuilder { )); region_migration_manager.try_start()?; - let region_failover_handler = if options.enable_region_failover && is_remote_wal { + let region_failover_handler = if options.enable_region_failover { let region_supervisor = RegionSupervisor::new( rx, options.failure_detector, @@ -346,6 +360,40 @@ impl MetasrvBuilder { .context(error::InitDdlManagerSnafu)?, ); + // remote WAL prune ticker and manager + let wal_prune_ticker = if is_remote_wal && options.wal.enable_active_wal_pruning() { + let (tx, rx) = WalPruneManager::channel(); + // Safety: Must be remote WAL. + let remote_wal_options = options.wal.remote_wal_options().unwrap(); + let kafka_client = build_kafka_client(remote_wal_options) + .await + .context(error::BuildKafkaClientSnafu)?; + let wal_prune_context = WalPruneContext { + client: Arc::new(kafka_client), + table_metadata_manager: table_metadata_manager.clone(), + leader_region_registry: leader_region_registry.clone(), + server_addr: options.server_addr.clone(), + mailbox: mailbox.clone(), + }; + let wal_prune_manager = WalPruneManager::new( + table_metadata_manager.clone(), + remote_wal_options.auto_prune_parallelism, + rx, + procedure_manager.clone(), + wal_prune_context, + remote_wal_options.trigger_flush_threshold, + ); + // Start manager in background. Ticker will be started in the main thread to send ticks. + wal_prune_manager.try_start().await?; + let wal_prune_ticker = Arc::new(WalPruneTicker::new( + remote_wal_options.auto_prune_interval, + tx.clone(), + )); + Some(wal_prune_ticker) + } else { + None + }; + let customized_region_lease_renewer = plugins .as_ref() .and_then(|plugins| plugins.get::()); @@ -384,7 +432,7 @@ impl MetasrvBuilder { meta_peer_client: meta_peer_client.clone(), selector, // TODO(jeremy): We do not allow configuring the flow selector. - flow_selector: Arc::new(RoundRobinSelector::new(SelectTarget::Flownode)), + flow_selector, handler_group: RwLock::new(None), handler_group_builder: Mutex::new(Some(handler_group_builder)), election, @@ -406,6 +454,7 @@ impl MetasrvBuilder { region_supervisor_ticker, cache_invalidator, leader_region_registry, + wal_prune_ticker, }) } } diff --git a/src/meta-srv/src/metrics.rs b/src/meta-srv/src/metrics.rs index 9160aa1e1d..2984a91a1c 100644 --- a/src/meta-srv/src/metrics.rs +++ b/src/meta-srv/src/metrics.rs @@ -60,10 +60,24 @@ lazy_static! { /// The migration fail counter. pub static ref METRIC_META_REGION_MIGRATION_FAIL: IntCounter = register_int_counter!("greptime_meta_region_migration_fail", "meta region migration fail").unwrap(); - /// The add region follower execute histogram. - pub static ref METRIC_META_ADD_REGION_FOLLOWER_EXECUTE: HistogramVec = - register_histogram_vec!("greptime_meta_add_region_follower_execute", "meta add region follower execute", &["state"]).unwrap(); - /// The remove region follower execute histogram. - pub static ref METRIC_META_REMOVE_REGION_FOLLOWER_EXECUTE: HistogramVec = - register_histogram_vec!("greptime_meta_remove_region_follower_execute", "meta remove region follower execute", &["state"]).unwrap(); + // The heartbeat stat memory size histogram. + pub static ref METRIC_META_HEARTBEAT_STAT_MEMORY_SIZE: Histogram = + register_histogram!("greptime_meta_heartbeat_stat_memory_size", "meta heartbeat stat memory size", vec![ + 100.0, 500.0, 1000.0, 1500.0, 2000.0, 3000.0, 5000.0, 10000.0, 20000.0 + ]).unwrap(); + // The heartbeat rate counter. + pub static ref METRIC_META_HEARTBEAT_RATE: IntCounter = + register_int_counter!("greptime_meta_heartbeat_rate", "meta heartbeat arrival rate").unwrap(); + /// The remote WAL prune execute counter. + pub static ref METRIC_META_REMOTE_WAL_PRUNE_EXECUTE: IntCounterVec = + register_int_counter_vec!("greptime_meta_remote_wal_prune_execute", "meta remote wal prune execute", &["topic_name"]).unwrap(); + /// The migration stage elapsed histogram. + pub static ref METRIC_META_REGION_MIGRATION_STAGE_ELAPSED: HistogramVec = register_histogram_vec!( + "greptime_meta_region_migration_stage_elapsed", + "meta region migration stage elapsed", + &["stage"], + // 0.01 ~ 1000 + exponential_buckets(0.01, 10.0, 7).unwrap(), + ) + .unwrap(); } diff --git a/src/meta-srv/src/mocks.rs b/src/meta-srv/src/mocks.rs index 656ceeb3d9..29ed3a5ae8 100644 --- a/src/meta-srv/src/mocks.rs +++ b/src/meta-srv/src/mocks.rs @@ -141,10 +141,7 @@ pub async fn mock( if let Some(client) = client { Ok(TokioIo::new(client)) } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Client already taken", - )) + Err(std::io::Error::other("Client already taken")) } } }), diff --git a/src/meta-srv/src/node_excluder.rs b/src/meta-srv/src/node_excluder.rs new file mode 100644 index 0000000000..a7bc6e0f69 --- /dev/null +++ b/src/meta-srv/src/node_excluder.rs @@ -0,0 +1,32 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::Arc; + +use common_meta::DatanodeId; + +pub type NodeExcluderRef = Arc; + +/// [NodeExcluder] is used to help decide whether some nodes should be excluded (out of consideration) +/// in certain situations. For example, in some node selectors. +pub trait NodeExcluder: Send + Sync { + /// Returns the excluded datanode ids. + fn excluded_datanode_ids(&self) -> &Vec; +} + +impl NodeExcluder for Vec { + fn excluded_datanode_ids(&self) -> &Vec { + self + } +} diff --git a/src/meta-srv/src/procedure/region_migration.rs b/src/meta-srv/src/procedure/region_migration.rs index f13939195b..43b444d3b1 100644 --- a/src/meta-srv/src/procedure/region_migration.rs +++ b/src/meta-srv/src/procedure/region_migration.rs @@ -25,7 +25,7 @@ pub(crate) mod update_metadata; pub(crate) mod upgrade_candidate_region; use std::any::Any; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::time::Duration; use common_error::ext::BoxedError; @@ -43,7 +43,7 @@ use common_procedure::error::{ Error as ProcedureError, FromJsonSnafu, Result as ProcedureResult, ToJsonSnafu, }; use common_procedure::{Context as ProcedureContext, LockKey, Procedure, Status, StringKey}; -use common_telemetry::info; +use common_telemetry::{error, info}; use manager::RegionMigrationProcedureGuard; pub use manager::{ RegionMigrationManagerRef, RegionMigrationProcedureTask, RegionMigrationProcedureTracker, @@ -55,9 +55,15 @@ use tokio::time::Instant; use self::migration_start::RegionMigrationStart; use crate::error::{self, Result}; -use crate::metrics::{METRIC_META_REGION_MIGRATION_ERROR, METRIC_META_REGION_MIGRATION_EXECUTE}; +use crate::metrics::{ + METRIC_META_REGION_MIGRATION_ERROR, METRIC_META_REGION_MIGRATION_EXECUTE, + METRIC_META_REGION_MIGRATION_STAGE_ELAPSED, +}; use crate::service::mailbox::MailboxRef; +/// The default timeout for region migration. +pub const DEFAULT_REGION_MIGRATION_TIMEOUT: Duration = Duration::from_secs(120); + /// It's shared in each step and available even after recovering. /// /// It will only be updated/stored after the Red node has succeeded. @@ -100,6 +106,82 @@ impl PersistentContext { } } +/// Metrics of region migration. +#[derive(Debug, Clone, Default)] +pub struct Metrics { + /// Elapsed time of downgrading region and upgrading region. + operations_elapsed: Duration, + /// Elapsed time of downgrading leader region. + downgrade_leader_region_elapsed: Duration, + /// Elapsed time of open candidate region. + open_candidate_region_elapsed: Duration, + /// Elapsed time of upgrade candidate region. + upgrade_candidate_region_elapsed: Duration, +} + +impl Display for Metrics { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "operations_elapsed: {:?}, downgrade_leader_region_elapsed: {:?}, open_candidate_region_elapsed: {:?}, upgrade_candidate_region_elapsed: {:?}", + self.operations_elapsed, + self.downgrade_leader_region_elapsed, + self.open_candidate_region_elapsed, + self.upgrade_candidate_region_elapsed + ) + } +} + +impl Metrics { + /// Updates the elapsed time of downgrading region and upgrading region. + pub fn update_operations_elapsed(&mut self, elapsed: Duration) { + self.operations_elapsed += elapsed; + } + + /// Updates the elapsed time of downgrading leader region. + pub fn update_downgrade_leader_region_elapsed(&mut self, elapsed: Duration) { + self.downgrade_leader_region_elapsed += elapsed; + } + + /// Updates the elapsed time of open candidate region. + pub fn update_open_candidate_region_elapsed(&mut self, elapsed: Duration) { + self.open_candidate_region_elapsed += elapsed; + } + + /// Updates the elapsed time of upgrade candidate region. + pub fn update_upgrade_candidate_region_elapsed(&mut self, elapsed: Duration) { + self.upgrade_candidate_region_elapsed += elapsed; + } +} + +impl Drop for Metrics { + fn drop(&mut self) { + if !self.operations_elapsed.is_zero() { + METRIC_META_REGION_MIGRATION_STAGE_ELAPSED + .with_label_values(&["operations"]) + .observe(self.operations_elapsed.as_secs_f64()); + } + + if !self.downgrade_leader_region_elapsed.is_zero() { + METRIC_META_REGION_MIGRATION_STAGE_ELAPSED + .with_label_values(&["downgrade_leader_region"]) + .observe(self.downgrade_leader_region_elapsed.as_secs_f64()); + } + + if !self.open_candidate_region_elapsed.is_zero() { + METRIC_META_REGION_MIGRATION_STAGE_ELAPSED + .with_label_values(&["open_candidate_region"]) + .observe(self.open_candidate_region_elapsed.as_secs_f64()); + } + + if !self.upgrade_candidate_region_elapsed.is_zero() { + METRIC_META_REGION_MIGRATION_STAGE_ELAPSED + .with_label_values(&["upgrade_candidate_region"]) + .observe(self.upgrade_candidate_region_elapsed.as_secs_f64()); + } + } +} + /// It's shared in each step and available in executing (including retrying). /// /// It will be dropped if the procedure runner crashes. @@ -127,8 +209,10 @@ pub struct VolatileContext { leader_region_lease_deadline: Option, /// The last_entry_id of leader region. leader_region_last_entry_id: Option, - /// Elapsed time of downgrading region and upgrading region. - operations_elapsed: Duration, + /// The last_entry_id of leader metadata region (Only used for metric engine). + leader_region_metadata_last_entry_id: Option, + /// Metrics of region migration. + metrics: Metrics, } impl VolatileContext { @@ -148,6 +232,11 @@ impl VolatileContext { pub fn set_last_entry_id(&mut self, last_entry_id: u64) { self.leader_region_last_entry_id = Some(last_entry_id) } + + /// Sets the `leader_region_metadata_last_entry_id`. + pub fn set_metadata_last_entry_id(&mut self, last_entry_id: u64) { + self.leader_region_metadata_last_entry_id = Some(last_entry_id); + } } /// Used to generate new [Context]. @@ -221,12 +310,35 @@ impl Context { pub fn next_operation_timeout(&self) -> Option { self.persistent_ctx .timeout - .checked_sub(self.volatile_ctx.operations_elapsed) + .checked_sub(self.volatile_ctx.metrics.operations_elapsed) } /// Updates operations elapsed. pub fn update_operations_elapsed(&mut self, instant: Instant) { - self.volatile_ctx.operations_elapsed += instant.elapsed(); + self.volatile_ctx + .metrics + .update_operations_elapsed(instant.elapsed()); + } + + /// Updates the elapsed time of downgrading leader region. + pub fn update_downgrade_leader_region_elapsed(&mut self, instant: Instant) { + self.volatile_ctx + .metrics + .update_downgrade_leader_region_elapsed(instant.elapsed()); + } + + /// Updates the elapsed time of open candidate region. + pub fn update_open_candidate_region_elapsed(&mut self, instant: Instant) { + self.volatile_ctx + .metrics + .update_open_candidate_region_elapsed(instant.elapsed()); + } + + /// Updates the elapsed time of upgrade candidate region. + pub fn update_upgrade_candidate_region_elapsed(&mut self, instant: Instant) { + self.volatile_ctx + .metrics + .update_upgrade_candidate_region_elapsed(instant.elapsed()); } /// Returns address of meta server. @@ -540,6 +652,14 @@ impl Procedure for RegionMigrationProcedure { .inc(); ProcedureError::retry_later(e) } else { + error!( + e; + "Region migration procedure failed, region_id: {}, from_peer: {}, to_peer: {}, {}", + self.context.region_id(), + self.context.persistent_ctx.from_peer, + self.context.persistent_ctx.to_peer, + self.context.volatile_ctx.metrics, + ); METRIC_META_REGION_MIGRATION_ERROR .with_label_values(&[name, "external"]) .inc(); diff --git a/src/meta-srv/src/procedure/region_migration/close_downgraded_region.rs b/src/meta-srv/src/procedure/region_migration/close_downgraded_region.rs index 4e09e421d0..ba13f7cdea 100644 --- a/src/meta-srv/src/procedure/region_migration/close_downgraded_region.rs +++ b/src/meta-srv/src/procedure/region_migration/close_downgraded_region.rs @@ -16,7 +16,7 @@ use std::any::Any; use std::time::Duration; use api::v1::meta::MailboxMessage; -use common_meta::distributed_time_constants::MAILBOX_RTT_SECS; +use common_meta::distributed_time_constants::REGION_LEASE_SECS; use common_meta::instruction::{Instruction, InstructionReply, SimpleReply}; use common_meta::key::datanode_table::RegionInfo; use common_meta::RegionIdent; @@ -31,7 +31,8 @@ use crate::procedure::region_migration::migration_end::RegionMigrationEnd; use crate::procedure::region_migration::{Context, State}; use crate::service::mailbox::Channel; -const CLOSE_DOWNGRADED_REGION_TIMEOUT: Duration = Duration::from_secs(MAILBOX_RTT_SECS); +/// Uses lease time of a region as the timeout of closing a downgraded region. +const CLOSE_DOWNGRADED_REGION_TIMEOUT: Duration = Duration::from_secs(REGION_LEASE_SECS); #[derive(Debug, Serialize, Deserialize)] pub struct CloseDowngradedRegion; @@ -45,7 +46,13 @@ impl State for CloseDowngradedRegion { let region_id = ctx.region_id(); warn!(err; "Failed to close downgraded leader region: {region_id} on datanode {:?}", downgrade_leader_datanode); } - + info!( + "Region migration is finished: region_id: {}, from_peer: {}, to_peer: {}, {}", + ctx.region_id(), + ctx.persistent_ctx.from_peer, + ctx.persistent_ctx.to_peer, + ctx.volatile_ctx.metrics, + ); Ok((Box::new(RegionMigrationEnd), Status::done())) } diff --git a/src/meta-srv/src/procedure/region_migration/downgrade_leader_region.rs b/src/meta-srv/src/procedure/region_migration/downgrade_leader_region.rs index 41d437d466..93481adc54 100644 --- a/src/meta-srv/src/procedure/region_migration/downgrade_leader_region.rs +++ b/src/meta-srv/src/procedure/region_migration/downgrade_leader_region.rs @@ -22,10 +22,8 @@ use common_meta::instruction::{ }; use common_procedure::Status; use common_telemetry::{error, info, warn}; -use common_wal::options::WalOptions; use serde::{Deserialize, Serialize}; use snafu::{OptionExt, ResultExt}; -use store_api::storage::RegionId; use tokio::time::{sleep, Instant}; use crate::error::{self, Result}; @@ -56,6 +54,7 @@ impl Default for DowngradeLeaderRegion { #[typetag::serde] impl State for DowngradeLeaderRegion { async fn next(&mut self, ctx: &mut Context) -> Result<(Box, Status)> { + let now = Instant::now(); // Ensures the `leader_region_lease_deadline` must exist after recovering. ctx.volatile_ctx .set_leader_region_lease_deadline(Duration::from_secs(REGION_LEASE_SECS)); @@ -79,6 +78,7 @@ impl State for DowngradeLeaderRegion { } } } + ctx.update_downgrade_leader_region_elapsed(now); Ok(( Box::new(UpgradeCandidateRegion::default()), @@ -97,32 +97,15 @@ impl DowngradeLeaderRegion { &self, ctx: &Context, flush_timeout: Duration, - reject_write: bool, ) -> Instruction { let pc = &ctx.persistent_ctx; let region_id = pc.region_id; Instruction::DowngradeRegion(DowngradeRegion { region_id, flush_timeout: Some(flush_timeout), - reject_write, }) } - async fn should_reject_write(ctx: &mut Context, region_id: RegionId) -> Result { - let datanode_table_value = ctx.get_from_peer_datanode_table_value().await?; - if let Some(wal_option) = datanode_table_value - .region_info - .region_wal_options - .get(®ion_id.region_number()) - { - let options: WalOptions = serde_json::from_str(wal_option) - .with_context(|_| error::DeserializeFromJsonSnafu { input: wal_option })?; - return Ok(matches!(options, WalOptions::RaftEngine)); - } - - Ok(true) - } - /// Tries to downgrade a leader region. /// /// Retry: @@ -143,9 +126,7 @@ impl DowngradeLeaderRegion { .context(error::ExceededDeadlineSnafu { operation: "Downgrade region", })?; - let reject_write = Self::should_reject_write(ctx, region_id).await?; - let downgrade_instruction = - self.build_downgrade_region_instruction(ctx, operation_timeout, reject_write); + let downgrade_instruction = self.build_downgrade_region_instruction(ctx, operation_timeout); let leader = &ctx.persistent_ctx.from_peer; let msg = MailboxMessage::json_message( @@ -174,6 +155,7 @@ impl DowngradeLeaderRegion { ); let InstructionReply::DowngradeRegion(DowngradeRegionReply { last_entry_id, + metadata_last_entry_id, exists, error, }) = reply @@ -202,9 +184,10 @@ impl DowngradeLeaderRegion { ); } else { info!( - "Region {} leader is downgraded, last_entry_id: {:?}, elapsed: {:?}", + "Region {} leader is downgraded, last_entry_id: {:?}, metadata_last_entry_id: {:?}, elapsed: {:?}", region_id, last_entry_id, + metadata_last_entry_id, now.elapsed() ); } @@ -213,6 +196,11 @@ impl DowngradeLeaderRegion { ctx.volatile_ctx.set_last_entry_id(last_entry_id); } + if let Some(metadata_last_entry_id) = metadata_last_entry_id { + ctx.volatile_ctx + .set_metadata_last_entry_id(metadata_last_entry_id); + } + Ok(()) } Err(error::Error::MailboxTimeout { .. }) => { @@ -276,7 +264,6 @@ mod tests { use common_meta::key::test_utils::new_test_table_info; use common_meta::peer::Peer; use common_meta::rpc::router::{Region, RegionRoute}; - use common_wal::options::KafkaWalOptions; use store_api::storage::RegionId; use tokio::time::Instant; @@ -331,41 +318,6 @@ mod tests { assert!(!err.is_retryable()); } - #[tokio::test] - async fn test_should_reject_writes() { - let persistent_context = new_persistent_context(); - let region_id = persistent_context.region_id; - let env = TestingEnv::new(); - let mut ctx = env.context_factory().new_context(persistent_context); - let wal_options = - HashMap::from([(1, serde_json::to_string(&WalOptions::RaftEngine).unwrap())]); - prepare_table_metadata(&ctx, wal_options).await; - - let reject_write = DowngradeLeaderRegion::should_reject_write(&mut ctx, region_id) - .await - .unwrap(); - assert!(reject_write); - - // Remote WAL - let persistent_context = new_persistent_context(); - let region_id = persistent_context.region_id; - let env = TestingEnv::new(); - let mut ctx = env.context_factory().new_context(persistent_context); - let wal_options = HashMap::from([( - 1, - serde_json::to_string(&WalOptions::Kafka(KafkaWalOptions { - topic: "my_topic".to_string(), - })) - .unwrap(), - )]); - prepare_table_metadata(&ctx, wal_options).await; - - let reject_write = DowngradeLeaderRegion::should_reject_write(&mut ctx, region_id) - .await - .unwrap(); - assert!(!reject_write); - } - #[tokio::test] async fn test_pusher_dropped() { let state = DowngradeLeaderRegion::default(); @@ -398,7 +350,8 @@ mod tests { let env = TestingEnv::new(); let mut ctx = env.context_factory().new_context(persistent_context); prepare_table_metadata(&ctx, HashMap::default()).await; - ctx.volatile_ctx.operations_elapsed = ctx.persistent_ctx.timeout + Duration::from_secs(1); + ctx.volatile_ctx.metrics.operations_elapsed = + ctx.persistent_ctx.timeout + Duration::from_secs(1); let err = state.downgrade_region(&mut ctx).await.unwrap_err(); @@ -641,7 +594,8 @@ mod tests { let mut ctx = env.context_factory().new_context(persistent_context); let mailbox_ctx = env.mailbox_context(); let mailbox = mailbox_ctx.mailbox().clone(); - ctx.volatile_ctx.operations_elapsed = ctx.persistent_ctx.timeout + Duration::from_secs(1); + ctx.volatile_ctx.metrics.operations_elapsed = + ctx.persistent_ctx.timeout + Duration::from_secs(1); let (tx, rx) = tokio::sync::mpsc::channel(1); mailbox_ctx diff --git a/src/meta-srv/src/procedure/region_migration/manager.rs b/src/meta-srv/src/procedure/region_migration/manager.rs index e2345559d0..9286bb8778 100644 --- a/src/meta-srv/src/procedure/region_migration/manager.rs +++ b/src/meta-srv/src/procedure/region_migration/manager.rs @@ -267,14 +267,36 @@ impl RegionMigrationManager { ensure!( leader_peer.id == task.from_peer.id, - error::InvalidArgumentsSnafu { - err_msg: "Invalid region migration `from_peer` argument" + error::LeaderPeerChangedSnafu { + msg: format!( + "Region's leader peer({}) is not the `from_peer`({}), region: {}", + leader_peer.id, task.from_peer.id, task.region_id + ), } ); Ok(()) } + /// Throws an error if `to_peer` is already has a region follower. + fn verify_region_follower_peers( + &self, + region_route: &RegionRoute, + task: &RegionMigrationProcedureTask, + ) -> Result<()> { + ensure!( + !region_route.follower_peers.contains(&task.to_peer), + error::InvalidArgumentsSnafu { + err_msg: format!( + "The `to_peer`({}) is already has a region follower, region: {}", + task.to_peer.id, task.region_id + ), + }, + ); + + Ok(()) + } + /// Submits a new region migration procedure. pub async fn submit_procedure( &self, @@ -308,7 +330,7 @@ impl RegionMigrationManager { } self.verify_region_leader_peer(®ion_route, &task)?; - + self.verify_region_follower_peers(®ion_route, &task)?; let table_info = self.retrieve_table_info(region_id).await?; let TableName { catalog_name, @@ -481,14 +503,44 @@ mod test { ..Default::default() }]; + env.create_physical_table_metadata(table_info, region_routes) + .await; + + let err = manager.submit_procedure(task).await.unwrap_err(); + assert_matches!(err, error::Error::LeaderPeerChanged { .. }); + assert_eq!(err.to_string(), "Region's leader peer changed: Region's leader peer(3) is not the `from_peer`(1), region: 4398046511105(1024, 1)"); + } + + #[tokio::test] + async fn test_submit_procedure_region_follower_on_to_peer() { + let env = TestingEnv::new(); + let context_factory = env.context_factory(); + let manager = RegionMigrationManager::new(env.procedure_manager().clone(), context_factory); + let region_id = RegionId::new(1024, 1); + let task = RegionMigrationProcedureTask { + region_id, + from_peer: Peer::empty(3), + to_peer: Peer::empty(2), + timeout: Duration::from_millis(1000), + }; + + let table_info = new_test_table_info(1024, vec![1]).into(); + let region_routes = vec![RegionRoute { + region: Region::new_test(region_id), + leader_peer: Some(Peer::empty(3)), + follower_peers: vec![Peer::empty(2)], + ..Default::default() + }]; + env.create_physical_table_metadata(table_info, region_routes) .await; let err = manager.submit_procedure(task).await.unwrap_err(); assert_matches!(err, error::Error::InvalidArguments { .. }); - assert!(err - .to_string() - .contains("Invalid region migration `from_peer` argument")); + assert_eq!( + err.to_string(), + "Invalid arguments: The `to_peer`(2) is already has a region follower, region: 4398046511105(1024, 1)" + ); } #[tokio::test] diff --git a/src/meta-srv/src/procedure/region_migration/migration_abort.rs b/src/meta-srv/src/procedure/region_migration/migration_abort.rs index af56843045..d364f0c8b9 100644 --- a/src/meta-srv/src/procedure/region_migration/migration_abort.rs +++ b/src/meta-srv/src/procedure/region_migration/migration_abort.rs @@ -15,6 +15,7 @@ use std::any::Any; use common_procedure::Status; +use common_telemetry::warn; use serde::{Deserialize, Serialize}; use crate::error::{self, Result}; @@ -37,7 +38,15 @@ impl RegionMigrationAbort { #[async_trait::async_trait] #[typetag::serde] impl State for RegionMigrationAbort { - async fn next(&mut self, _: &mut Context) -> Result<(Box, Status)> { + async fn next(&mut self, ctx: &mut Context) -> Result<(Box, Status)> { + warn!( + "Region migration is aborted: {}, region_id: {}, from_peer: {}, to_peer: {}, {}", + self.reason, + ctx.region_id(), + ctx.persistent_ctx.from_peer, + ctx.persistent_ctx.to_peer, + ctx.volatile_ctx.metrics, + ); error::MigrationAbortSnafu { reason: &self.reason, } diff --git a/src/meta-srv/src/procedure/region_migration/open_candidate_region.rs b/src/meta-srv/src/procedure/region_migration/open_candidate_region.rs index 6cacf75063..6d1c81d3ed 100644 --- a/src/meta-srv/src/procedure/region_migration/open_candidate_region.rs +++ b/src/meta-srv/src/procedure/region_migration/open_candidate_region.rs @@ -13,7 +13,7 @@ // limitations under the License. use std::any::Any; -use std::time::{Duration, Instant}; +use std::time::Duration; use api::v1::meta::MailboxMessage; use common_meta::distributed_time_constants::REGION_LEASE_SECS; @@ -24,6 +24,7 @@ use common_procedure::Status; use common_telemetry::info; use serde::{Deserialize, Serialize}; use snafu::{OptionExt, ResultExt}; +use tokio::time::Instant; use crate::error::{self, Result}; use crate::handler::HeartbeatMailbox; @@ -42,7 +43,9 @@ pub struct OpenCandidateRegion; impl State for OpenCandidateRegion { async fn next(&mut self, ctx: &mut Context) -> Result<(Box, Status)> { let instruction = self.build_open_region_instruction(ctx).await?; + let now = Instant::now(); self.open_candidate_region(ctx, instruction).await?; + ctx.update_open_candidate_region_elapsed(now); Ok(( Box::new(UpdateMetadata::Downgrade), diff --git a/src/meta-srv/src/procedure/region_migration/upgrade_candidate_region.rs b/src/meta-srv/src/procedure/region_migration/upgrade_candidate_region.rs index 6f42c670dd..8f3741dbac 100644 --- a/src/meta-srv/src/procedure/region_migration/upgrade_candidate_region.rs +++ b/src/meta-srv/src/procedure/region_migration/upgrade_candidate_region.rs @@ -54,9 +54,12 @@ impl Default for UpgradeCandidateRegion { #[typetag::serde] impl State for UpgradeCandidateRegion { async fn next(&mut self, ctx: &mut Context) -> Result<(Box, Status)> { + let now = Instant::now(); if self.upgrade_region_with_retry(ctx).await { + ctx.update_upgrade_candidate_region_elapsed(now); Ok((Box::new(UpdateMetadata::Upgrade), Status::executing(false))) } else { + ctx.update_upgrade_candidate_region_elapsed(now); Ok((Box::new(UpdateMetadata::Rollback), Status::executing(false))) } } @@ -76,10 +79,12 @@ impl UpgradeCandidateRegion { let pc = &ctx.persistent_ctx; let region_id = pc.region_id; let last_entry_id = ctx.volatile_ctx.leader_region_last_entry_id; + let metadata_last_entry_id = ctx.volatile_ctx.leader_region_metadata_last_entry_id; Instruction::UpgradeRegion(UpgradeRegion { region_id, last_entry_id, + metadata_last_entry_id, replay_timeout: Some(replay_timeout), location_id: Some(ctx.persistent_ctx.from_peer.id), }) @@ -286,7 +291,8 @@ mod tests { let persistent_context = new_persistent_context(); let env = TestingEnv::new(); let mut ctx = env.context_factory().new_context(persistent_context); - ctx.volatile_ctx.operations_elapsed = ctx.persistent_ctx.timeout + Duration::from_secs(1); + ctx.volatile_ctx.metrics.operations_elapsed = + ctx.persistent_ctx.timeout + Duration::from_secs(1); let err = state.upgrade_region(&ctx).await.unwrap_err(); @@ -556,7 +562,8 @@ mod tests { let mut ctx = env.context_factory().new_context(persistent_context); let mailbox_ctx = env.mailbox_context(); let mailbox = mailbox_ctx.mailbox().clone(); - ctx.volatile_ctx.operations_elapsed = ctx.persistent_ctx.timeout + Duration::from_secs(1); + ctx.volatile_ctx.metrics.operations_elapsed = + ctx.persistent_ctx.timeout + Duration::from_secs(1); let (tx, rx) = tokio::sync::mpsc::channel(1); mailbox_ctx diff --git a/src/meta-srv/src/procedure/test_util.rs b/src/meta-srv/src/procedure/test_util.rs index 61254b133b..ca6da59f2a 100644 --- a/src/meta-srv/src/procedure/test_util.rs +++ b/src/meta-srv/src/procedure/test_util.rs @@ -135,6 +135,7 @@ pub fn new_downgrade_region_reply( payload: Some(Payload::Json( serde_json::to_string(&InstructionReply::DowngradeRegion(DowngradeRegionReply { last_entry_id, + metadata_last_entry_id: None, exists: exist, error, })) @@ -178,8 +179,8 @@ pub async fn new_wal_prune_metadata( ) -> (EntryId, Vec) { let datanode_id = 1; let from_peer = Peer::empty(datanode_id); - let mut min_flushed_entry_id = u64::MAX; - let mut max_flushed_entry_id = 0; + let mut min_prunable_entry_id = u64::MAX; + let mut max_prunable_entry_id = 0; let mut region_entry_ids = HashMap::with_capacity(n_table as usize * n_region as usize); for table_id in 0..n_table { let region_ids = (0..n_region) @@ -220,10 +221,10 @@ pub async fn new_wal_prune_metadata( .iter() .map(|region_id| { let rand_n = rand::random::() as usize; - let current_flushed_entry_id = offsets[rand_n % offsets.len()] as u64; - min_flushed_entry_id = min_flushed_entry_id.min(current_flushed_entry_id); - max_flushed_entry_id = max_flushed_entry_id.max(current_flushed_entry_id); - (*region_id, current_flushed_entry_id) + let current_prunable_entry_id = offsets[rand_n % offsets.len()] as u64; + min_prunable_entry_id = min_prunable_entry_id.min(current_prunable_entry_id); + max_prunable_entry_id = max_prunable_entry_id.max(current_prunable_entry_id); + (*region_id, current_prunable_entry_id) }) .collect::>(); region_entry_ids.extend(current_region_entry_ids.clone()); @@ -234,15 +235,15 @@ pub async fn new_wal_prune_metadata( let regions_to_flush = region_entry_ids .iter() - .filter_map(|(region_id, flushed_entry_id)| { - if max_flushed_entry_id - flushed_entry_id > threshold { + .filter_map(|(region_id, prunable_entry_id)| { + if max_prunable_entry_id - prunable_entry_id > threshold { Some(*region_id) } else { None } }) .collect::>(); - (min_flushed_entry_id, regions_to_flush) + (min_prunable_entry_id, regions_to_flush) } pub async fn update_in_memory_region_flushed_entry_id( @@ -256,6 +257,7 @@ pub async fn update_in_memory_region_flushed_entry_id( manifest: LeaderRegionManifestInfo::Mito { manifest_version: 0, flushed_entry_id, + topic_latest_entry_id: 0, }, }; key_values.push((region_id, value)); diff --git a/src/meta-srv/src/procedure/wal_prune.rs b/src/meta-srv/src/procedure/wal_prune.rs index b7f145fffc..e9b6403942 100644 --- a/src/meta-srv/src/procedure/wal_prune.rs +++ b/src/meta-srv/src/procedure/wal_prune.rs @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub(crate) mod manager; +#[cfg(test)] +mod test_util; + use std::collections::{HashMap, HashSet}; use std::sync::Arc; use std::time::Duration; @@ -28,9 +32,10 @@ use common_procedure::{ Context as ProcedureContext, Error as ProcedureError, LockKey, Procedure, Result as ProcedureResult, Status, StringKey, }; -use common_telemetry::warn; +use common_telemetry::{info, warn}; use itertools::{Itertools, MinMaxResult}; use log_store::kafka::DEFAULT_PARTITION; +use manager::{WalPruneProcedureGuard, WalPruneProcedureTracker}; use rskafka::client::partition::UnknownTopicHandling; use rskafka::client::Client; use serde::{Deserialize, Serialize}; @@ -45,9 +50,8 @@ use crate::error::{ use crate::service::mailbox::{Channel, MailboxRef}; use crate::Result; -type KafkaClientRef = Arc; +pub type KafkaClientRef = Arc; -/// No timeout for flush request. const DELETE_RECORDS_TIMEOUT: Duration = Duration::from_secs(1); /// The state of WAL pruning. @@ -58,17 +62,18 @@ pub enum WalPruneState { Prune, } +#[derive(Clone)] pub struct Context { /// The Kafka client. - client: KafkaClientRef, + pub client: KafkaClientRef, /// The table metadata manager. - table_metadata_manager: TableMetadataManagerRef, + pub table_metadata_manager: TableMetadataManagerRef, /// The leader region registry. - leader_region_registry: LeaderRegionRegistryRef, + pub leader_region_registry: LeaderRegionRegistryRef, /// Server address of metasrv. - server_addr: String, + pub server_addr: String, /// The mailbox to send messages. - mailbox: MailboxRef, + pub mailbox: MailboxRef, } /// The data of WAL pruning. @@ -77,10 +82,11 @@ pub struct WalPruneData { /// The topic name to prune. pub topic: String, /// The minimum flush entry id for topic, which is used to prune the WAL. - pub min_flushed_entry_id: EntryId, + pub prunable_entry_id: EntryId, pub regions_to_flush: Vec, - /// If `flushed_entry_id` + `trigger_flush_threshold` < `max_flushed_entry_id`, send a flush request to the region. - pub trigger_flush_threshold: Option, + /// If `prunable_entry_id` + `trigger_flush_threshold` < `max_prunable_entry_id`, send a flush request to the region. + /// If `None`, never send flush requests. + pub trigger_flush_threshold: u64, /// The state. pub state: WalPruneState, } @@ -89,27 +95,43 @@ pub struct WalPruneData { pub struct WalPruneProcedure { pub data: WalPruneData, pub context: Context, + pub _guard: Option, } impl WalPruneProcedure { const TYPE_NAME: &'static str = "metasrv-procedure::WalPrune"; - pub fn new(topic: String, context: Context, trigger_flush_threshold: Option) -> Self { + pub fn new( + topic: String, + context: Context, + trigger_flush_threshold: u64, + guard: Option, + ) -> Self { Self { data: WalPruneData { topic, - min_flushed_entry_id: 0, + prunable_entry_id: 0, trigger_flush_threshold, regions_to_flush: vec![], state: WalPruneState::Prepare, }, context, + _guard: guard, } } - pub fn from_json(json: &str, context: Context) -> ProcedureResult { + pub fn from_json( + json: &str, + context: &Context, + tracker: WalPruneProcedureTracker, + ) -> ProcedureResult { let data: WalPruneData = serde_json::from_str(json).context(ToJsonSnafu)?; - Ok(Self { data, context }) + let guard = tracker.insert_running_procedure(data.topic.clone()); + Ok(Self { + data, + context: context.clone(), + _guard: guard, + }) } async fn build_peer_to_region_ids_map( @@ -182,20 +204,20 @@ impl WalPruneProcedure { .with_context(|_| error::RetryLaterWithSourceSnafu { reason: "Failed to get topic-region map", })?; - let flush_entry_ids_map: HashMap<_, _> = self + let prunable_entry_ids_map: HashMap<_, _> = self .context .leader_region_registry .batch_get(region_ids.iter().cloned()) .into_iter() .map(|(region_id, region)| { - let flushed_entry_id = region.manifest.min_flushed_entry_id(); - (region_id, flushed_entry_id) + let prunable_entry_id = region.manifest.prunable_entry_id(); + (region_id, prunable_entry_id) }) .collect(); - // Check if the `flush_entry_ids_map` contains all region ids. + // Check if the `prunable_entry_ids_map` contains all region ids. let non_collected_region_ids = - check_heartbeat_collected_region_ids(®ion_ids, &flush_entry_ids_map); + check_heartbeat_collected_region_ids(®ion_ids, &prunable_entry_ids_map); if !non_collected_region_ids.is_empty() { // The heartbeat collected region ids do not contain all region ids in the topic-region map. // In this case, we should not prune the WAL. @@ -204,23 +226,23 @@ impl WalPruneProcedure { return Ok(Status::done()); } - let min_max_result = flush_entry_ids_map.values().minmax(); - let max_flushed_entry_id = match min_max_result { + let min_max_result = prunable_entry_ids_map.values().minmax(); + let max_prunable_entry_id = match min_max_result { MinMaxResult::NoElements => { return Ok(Status::done()); } - MinMaxResult::OneElement(flushed_entry_id) => { - self.data.min_flushed_entry_id = *flushed_entry_id; - *flushed_entry_id + MinMaxResult::OneElement(prunable_entry_id) => { + self.data.prunable_entry_id = *prunable_entry_id; + *prunable_entry_id } - MinMaxResult::MinMax(min_flushed_entry_id, max_flushed_entry_id) => { - self.data.min_flushed_entry_id = *min_flushed_entry_id; - *max_flushed_entry_id + MinMaxResult::MinMax(min_prunable_entry_id, max_prunable_entry_id) => { + self.data.prunable_entry_id = *min_prunable_entry_id; + *max_prunable_entry_id } }; - if let Some(threshold) = self.data.trigger_flush_threshold { - for (region_id, flushed_entry_id) in flush_entry_ids_map { - if flushed_entry_id + threshold < max_flushed_entry_id { + if self.data.trigger_flush_threshold != 0 { + for (region_id, prunable_entry_id) in prunable_entry_ids_map { + if prunable_entry_id + self.data.trigger_flush_threshold < max_prunable_entry_id { self.data.regions_to_flush.push(region_id); } } @@ -232,10 +254,17 @@ impl WalPruneProcedure { } /// Send the flush request to regions with low flush entry id. + /// + /// Retry: + /// - Failed to build peer to region ids map. It means failure in retrieving metadata. pub async fn on_sending_flush_request(&mut self) -> Result { let peer_to_region_ids_map = self .build_peer_to_region_ids_map(&self.context, &self.data.regions_to_flush) - .await?; + .await + .map_err(BoxedError::new) + .with_context(|_| error::RetryLaterWithSourceSnafu { + reason: "Failed to build peer to region ids map", + })?; let flush_instructions = self.build_flush_region_instruction(peer_to_region_ids_map)?; for (peer, flush_instruction) in flush_instructions.into_iter() { let msg = MailboxMessage::json_message( @@ -255,13 +284,13 @@ impl WalPruneProcedure { Ok(Status::executing(true)) } - /// Prune the WAL and persist the minimum flushed entry id. + /// Prune the WAL and persist the minimum prunable entry id. /// /// Retry: - /// - Failed to update the minimum flushed entry id in kvbackend. + /// - Failed to update the minimum prunable entry id in kvbackend. /// - Failed to delete records. pub async fn on_prune(&mut self) -> Result { - // Safety: flushed_entry_ids are loaded in on_prepare. + // Safety: `prunable_entry_id`` are loaded in on_prepare. let partition_client = self .context .client @@ -276,7 +305,7 @@ impl WalPruneProcedure { partition: DEFAULT_PARTITION, })?; - // Should update the min flushed entry id in the kv backend before deleting records. + // Should update the min prunable entry id in the kv backend before deleting records. // Otherwise, when a datanode restarts, it will not be able to find the wal entries. let prev = self .context @@ -292,7 +321,7 @@ impl WalPruneProcedure { self.context .table_metadata_manager .topic_name_manager() - .update(&self.data.topic, self.data.min_flushed_entry_id, prev) + .update(&self.data.topic, self.data.prunable_entry_id, prev) .await .context(UpdateTopicNameValueSnafu { topic: &self.data.topic, @@ -306,24 +335,27 @@ impl WalPruneProcedure { })?; partition_client .delete_records( - (self.data.min_flushed_entry_id + 1) as i64, + // notice here no "+1" is needed because the offset arg is exclusive, and it's defensive programming just in case somewhere else have a off by one error, see https://kafka.apache.org/36/javadoc/org/apache/kafka/clients/consumer/KafkaConsumer.html#endOffsets(java.util.Collection) which we use to get the end offset from high watermark + self.data.prunable_entry_id as i64, DELETE_RECORDS_TIMEOUT.as_millis() as i32, ) .await .context(DeleteRecordsSnafu { topic: &self.data.topic, partition: DEFAULT_PARTITION, - offset: (self.data.min_flushed_entry_id + 1), + offset: self.data.prunable_entry_id, }) .map_err(BoxedError::new) .with_context(|_| error::RetryLaterWithSourceSnafu { reason: format!( "Failed to delete records for topic: {}, partition: {}, offset: {}", - self.data.topic, - DEFAULT_PARTITION, - self.data.min_flushed_entry_id + 1 + self.data.topic, DEFAULT_PARTITION, self.data.prunable_entry_id ), })?; + info!( + "Successfully pruned WAL for topic: {}, entry id: {}", + self.data.topic, self.data.prunable_entry_id + ); Ok(Status::done()) } } @@ -388,123 +420,41 @@ mod tests { use std::assert_matches::assert_matches; use api::v1::meta::HeartbeatResponse; - use common_meta::key::TableMetadataManager; - use common_meta::kv_backend::memory::MemoryKvBackend; - use common_meta::region_registry::LeaderRegionRegistry; - use common_meta::sequence::SequenceBuilder; - use common_meta::wal_options_allocator::build_kafka_topic_creator; - use common_wal::config::kafka::common::{KafkaConnectionConfig, KafkaTopicConfig}; - use common_wal::config::kafka::MetasrvKafkaConfig; use common_wal::test_util::run_test_with_kafka_wal; use rskafka::record::Record; use tokio::sync::mpsc::Receiver; use super::*; use crate::handler::HeartbeatMailbox; - use crate::procedure::test_util::{new_wal_prune_metadata, MailboxContext}; - - struct TestEnv { - table_metadata_manager: TableMetadataManagerRef, - leader_region_registry: LeaderRegionRegistryRef, - mailbox: MailboxContext, - server_addr: String, - } - - impl TestEnv { - fn new() -> Self { - let kv_backend = Arc::new(MemoryKvBackend::new()); - let table_metadata_manager = Arc::new(TableMetadataManager::new(kv_backend.clone())); - let leader_region_registry = Arc::new(LeaderRegionRegistry::new()); - let mailbox_sequence = - SequenceBuilder::new("test_heartbeat_mailbox", kv_backend.clone()).build(); - - let mailbox_ctx = MailboxContext::new(mailbox_sequence); - - Self { - table_metadata_manager, - leader_region_registry, - mailbox: mailbox_ctx, - server_addr: "localhost".to_string(), - } - } - - fn table_metadata_manager(&self) -> &TableMetadataManagerRef { - &self.table_metadata_manager - } - - fn leader_region_registry(&self) -> &LeaderRegionRegistryRef { - &self.leader_region_registry - } - - fn mailbox_context(&self) -> &MailboxContext { - &self.mailbox - } - - fn server_addr(&self) -> &str { - &self.server_addr - } - } + use crate::procedure::test_util::new_wal_prune_metadata; + // Fix this import to correctly point to the test_util module + use crate::procedure::wal_prune::test_util::TestEnv; /// Mock a test env for testing. /// Including: /// 1. Prepare some data in the table metadata manager and in-memory kv backend. - /// 2. Generate a `WalPruneProcedure` with the test env. - /// 3. Return the procedure, the minimum last entry id to prune and the regions to flush. - async fn mock_test_env( - topic: String, - broker_endpoints: Vec, - env: &TestEnv, - ) -> (WalPruneProcedure, u64, Vec) { - // Creates a topic manager. - let kafka_topic = KafkaTopicConfig { - replication_factor: broker_endpoints.len() as i16, - ..Default::default() - }; - let config = MetasrvKafkaConfig { - connection: KafkaConnectionConfig { - broker_endpoints, - ..Default::default() - }, - kafka_topic, - ..Default::default() - }; - let topic_creator = build_kafka_topic_creator(&config).await.unwrap(); - let table_metadata_manager = env.table_metadata_manager().clone(); - let leader_region_registry = env.leader_region_registry().clone(); - let mailbox = env.mailbox_context().mailbox().clone(); - + /// 2. Return the procedure, the minimum last entry id to prune and the regions to flush. + async fn mock_test_data(procedure: &WalPruneProcedure) -> (u64, Vec) { let n_region = 10; let n_table = 5; - let threshold = 10; // 5 entries per region. let offsets = mock_wal_entries( - topic_creator.client().clone(), - &topic, + procedure.context.client.clone(), + &procedure.data.topic, (n_region * n_table * 5) as usize, ) .await; - - let (min_flushed_entry_id, regions_to_flush) = new_wal_prune_metadata( - table_metadata_manager.clone(), - leader_region_registry.clone(), + let (prunable_entry_id, regions_to_flush) = new_wal_prune_metadata( + procedure.context.table_metadata_manager.clone(), + procedure.context.leader_region_registry.clone(), n_region, n_table, &offsets, - threshold, - topic.clone(), + procedure.data.trigger_flush_threshold, + procedure.data.topic.clone(), ) .await; - - let context = Context { - client: topic_creator.client().clone(), - table_metadata_manager, - leader_region_registry, - mailbox, - server_addr: env.server_addr().to_string(), - }; - - let wal_prune_procedure = WalPruneProcedure::new(topic, context, Some(threshold)); - (wal_prune_procedure, min_flushed_entry_id, regions_to_flush) + (prunable_entry_id, regions_to_flush) } fn record(i: usize) -> Record { @@ -603,10 +553,18 @@ mod tests { run_test_with_kafka_wal(|broker_endpoints| { Box::pin(async { common_telemetry::init_default_ut_logging(); - let topic_name = "greptime_test_topic".to_string(); + let mut topic_name = uuid::Uuid::new_v4().to_string(); + // Topic should start with a letter. + topic_name = format!("test_procedure_execution-{}", topic_name); let mut env = TestEnv::new(); - let (mut procedure, min_flushed_entry_id, regions_to_flush) = - mock_test_env(topic_name.clone(), broker_endpoints, &env).await; + let context = env.build_wal_prune_context(broker_endpoints).await; + let mut procedure = WalPruneProcedure::new(topic_name.clone(), context, 10, None); + + // Before any data in kvbackend is mocked, should return a retryable error. + let result = procedure.on_prune().await; + assert_matches!(result, Err(e) if e.is_retryable()); + + let (prunable_entry_id, regions_to_flush) = mock_test_data(&procedure).await; // Step 1: Test `on_prepare`. let status = procedure.on_prepare().await.unwrap(); @@ -618,7 +576,7 @@ mod tests { } ); assert_matches!(procedure.data.state, WalPruneState::FlushRegion); - assert_eq!(procedure.data.min_flushed_entry_id, min_flushed_entry_id); + assert_eq!(procedure.data.prunable_entry_id, prunable_entry_id); assert_eq!( procedure.data.regions_to_flush.len(), regions_to_flush.len() @@ -646,34 +604,31 @@ mod tests { // Step 3: Test `on_prune`. let status = procedure.on_prune().await.unwrap(); assert_matches!(status, Status::Done { output: None }); - // Check if the entry ids after `min_flushed_entry_id` still exist. + // Check if the entry ids after(include) `prunable_entry_id` still exist. check_entry_id_existence( procedure.context.client.clone(), &topic_name, - procedure.data.min_flushed_entry_id as i64 + 1, + procedure.data.prunable_entry_id as i64, true, ) .await; - // Check if the entry s before `min_flushed_entry_id` are deleted. + // Check if the entry ids before `prunable_entry_id` are deleted. check_entry_id_existence( procedure.context.client.clone(), &topic_name, - procedure.data.min_flushed_entry_id as i64, + procedure.data.prunable_entry_id as i64 - 1, false, ) .await; - let min_entry_id = env - .table_metadata_manager() + let value = env + .table_metadata_manager .topic_name_manager() .get(&topic_name) .await .unwrap() .unwrap(); - assert_eq!( - min_entry_id.pruned_entry_id, - procedure.data.min_flushed_entry_id - ); + assert_eq!(value.pruned_entry_id, procedure.data.prunable_entry_id); // Step 4: Test `on_prepare`, `check_heartbeat_collected_region_ids` fails. // Should log a warning and return `Status::Done`. @@ -682,13 +637,10 @@ mod tests { assert_matches!(status, Status::Done { output: None }); // Step 5: Test `on_prepare`, don't flush regions. - procedure.data.trigger_flush_threshold = None; + procedure.data.trigger_flush_threshold = 0; procedure.on_prepare().await.unwrap(); assert_matches!(procedure.data.state, WalPruneState::Prune); - assert_eq!( - min_entry_id.pruned_entry_id, - procedure.data.min_flushed_entry_id - ); + assert_eq!(value.pruned_entry_id, procedure.data.prunable_entry_id); // Clean up the topic. delete_topic(procedure.context.client, &topic_name).await; diff --git a/src/meta-srv/src/procedure/wal_prune/manager.rs b/src/meta-srv/src/procedure/wal_prune/manager.rs new file mode 100644 index 0000000000..8e5072ad11 --- /dev/null +++ b/src/meta-srv/src/procedure/wal_prune/manager.rs @@ -0,0 +1,438 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::hash_set::Entry; +use std::collections::HashSet; +use std::fmt::{Debug, Formatter}; +use std::sync::{Arc, Mutex, RwLock}; +use std::time::Duration; + +use common_meta::key::TableMetadataManagerRef; +use common_meta::leadership_notifier::LeadershipChangeListener; +use common_procedure::{watcher, ProcedureId, ProcedureManagerRef, ProcedureWithId}; +use common_runtime::JoinHandle; +use common_telemetry::{error, info, warn}; +use futures::future::join_all; +use snafu::{OptionExt, ResultExt}; +use tokio::sync::mpsc::{Receiver, Sender}; +use tokio::sync::Semaphore; +use tokio::time::{interval_at, Instant, MissedTickBehavior}; + +use crate::error::{self, Result}; +use crate::metrics::METRIC_META_REMOTE_WAL_PRUNE_EXECUTE; +use crate::procedure::wal_prune::{Context as WalPruneContext, WalPruneProcedure}; + +pub type WalPruneTickerRef = Arc; + +/// Tracks running [WalPruneProcedure]s and the resources they hold. +/// A [WalPruneProcedure] is holding a semaphore permit to limit the number of concurrent procedures. +/// +/// TODO(CookiePie): Similar to [RegionMigrationProcedureTracker], maybe can refactor to a unified framework. +#[derive(Clone)] +pub struct WalPruneProcedureTracker { + running_procedures: Arc>>, +} + +impl WalPruneProcedureTracker { + /// Insert a running [WalPruneProcedure] for the given topic name and + /// consume acquire a semaphore permit for the given topic name. + pub fn insert_running_procedure(&self, topic_name: String) -> Option { + let mut running_procedures = self.running_procedures.write().unwrap(); + match running_procedures.entry(topic_name.clone()) { + Entry::Occupied(_) => None, + Entry::Vacant(entry) => { + entry.insert(); + Some(WalPruneProcedureGuard { + topic_name, + running_procedures: self.running_procedures.clone(), + }) + } + } + } + + /// Number of running [WalPruneProcedure]s. + pub fn len(&self) -> usize { + self.running_procedures.read().unwrap().len() + } +} + +/// [WalPruneProcedureGuard] is a guard for [WalPruneProcedure]. +/// It is used to track the running [WalPruneProcedure]s. +/// When the guard is dropped, it will remove the topic name from the running procedures and release the semaphore. +pub struct WalPruneProcedureGuard { + topic_name: String, + running_procedures: Arc>>, +} + +impl Drop for WalPruneProcedureGuard { + fn drop(&mut self) { + let mut running_procedures = self.running_procedures.write().unwrap(); + running_procedures.remove(&self.topic_name); + } +} + +/// Event is used to notify the [WalPruneManager] to do some work. +/// +/// - `Tick`: Trigger a submission of [WalPruneProcedure] to prune remote WAL. +pub enum Event { + Tick, +} + +impl Debug for Event { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Event::Tick => write!(f, "Tick"), + } + } +} + +/// [WalPruneTicker] is a ticker that periodically sends [Event]s to the [WalPruneManager]. +/// It is used to trigger the [WalPruneManager] to submit [WalPruneProcedure]s. +pub(crate) struct WalPruneTicker { + /// Handle of ticker thread. + pub(crate) tick_handle: Mutex>>, + /// The interval of tick. + pub(crate) tick_interval: Duration, + /// Sends [Event]s. + pub(crate) sender: Sender, +} + +#[async_trait::async_trait] +impl LeadershipChangeListener for WalPruneTicker { + fn name(&self) -> &'static str { + "WalPruneTicker" + } + + async fn on_leader_start(&self) -> common_meta::error::Result<()> { + self.start(); + Ok(()) + } + + async fn on_leader_stop(&self) -> common_meta::error::Result<()> { + self.stop(); + Ok(()) + } +} + +/// TODO(CookiePie): Similar to [RegionSupervisorTicker], maybe can refactor to a unified framework. +impl WalPruneTicker { + pub(crate) fn new(tick_interval: Duration, sender: Sender) -> Self { + Self { + tick_handle: Mutex::new(None), + tick_interval, + sender, + } + } + + /// Starts the ticker. + pub fn start(&self) { + let mut handle = self.tick_handle.lock().unwrap(); + if handle.is_none() { + let sender = self.sender.clone(); + let tick_interval = self.tick_interval; + let ticker_loop = tokio::spawn(async move { + let mut interval = interval_at(Instant::now() + tick_interval, tick_interval); + interval.set_missed_tick_behavior(MissedTickBehavior::Skip); + loop { + interval.tick().await; + if sender.send(Event::Tick).await.is_err() { + info!("EventReceiver is dropped, tick loop is stopped"); + break; + } + } + }); + *handle = Some(ticker_loop); + } + info!("WalPruneTicker started."); + } + + /// Stops the ticker. + pub fn stop(&self) { + let mut handle = self.tick_handle.lock().unwrap(); + if let Some(handle) = handle.take() { + handle.abort(); + } + info!("WalPruneTicker stopped."); + } +} + +impl Drop for WalPruneTicker { + fn drop(&mut self) { + self.stop(); + } +} + +/// [WalPruneManager] manages all remote WAL related tasks in metasrv. +/// +/// [WalPruneManager] is responsible for: +/// 1. Registering [WalPruneProcedure] loader in the procedure manager. +/// 2. Periodically receive [Event::Tick] to submit [WalPruneProcedure] to prune remote WAL. +/// 3. Use a semaphore to limit the number of concurrent [WalPruneProcedure]s. +pub(crate) struct WalPruneManager { + /// Table metadata manager to restore topics from kvbackend. + table_metadata_manager: TableMetadataManagerRef, + /// Receives [Event]s. + receiver: Receiver, + /// Procedure manager. + procedure_manager: ProcedureManagerRef, + /// Tracker for running [WalPruneProcedure]s. + tracker: WalPruneProcedureTracker, + /// Semaphore to limit the number of concurrent [WalPruneProcedure]s. + semaphore: Arc, + + /// Context for [WalPruneProcedure]. + wal_prune_context: WalPruneContext, + /// Trigger flush threshold for [WalPruneProcedure]. + /// If `None`, never send flush requests. + trigger_flush_threshold: u64, +} + +impl WalPruneManager { + /// Returns a new empty [WalPruneManager]. + pub fn new( + table_metadata_manager: TableMetadataManagerRef, + limit: usize, + receiver: Receiver, + procedure_manager: ProcedureManagerRef, + wal_prune_context: WalPruneContext, + trigger_flush_threshold: u64, + ) -> Self { + Self { + table_metadata_manager, + receiver, + procedure_manager, + wal_prune_context, + tracker: WalPruneProcedureTracker { + running_procedures: Arc::new(RwLock::new(HashSet::new())), + }, + semaphore: Arc::new(Semaphore::new(limit)), + trigger_flush_threshold, + } + } + + /// Start the [WalPruneManager]. It will register [WalPruneProcedure] loader in the procedure manager. + pub async fn try_start(mut self) -> Result<()> { + let context = self.wal_prune_context.clone(); + let tracker = self.tracker.clone(); + self.procedure_manager + .register_loader( + WalPruneProcedure::TYPE_NAME, + Box::new(move |json| { + let tracker = tracker.clone(); + WalPruneProcedure::from_json(json, &context, tracker).map(|p| Box::new(p) as _) + }), + ) + .context(error::RegisterProcedureLoaderSnafu { + type_name: WalPruneProcedure::TYPE_NAME, + })?; + common_runtime::spawn_global(async move { + self.run().await; + }); + info!("WalPruneProcedureManager Started."); + Ok(()) + } + + /// Returns a mpsc channel with a buffer capacity of 1024 for sending and receiving `Event` messages. + pub(crate) fn channel() -> (Sender, Receiver) { + tokio::sync::mpsc::channel(1024) + } + + /// Runs the main loop. Performs actions on received events. + /// + /// - `Tick`: Submit `limit` [WalPruneProcedure]s to prune remote WAL. + pub(crate) async fn run(&mut self) { + while let Some(event) = self.receiver.recv().await { + match event { + Event::Tick => self.handle_tick_request().await.unwrap_or_else(|e| { + error!(e; "Failed to handle tick request"); + }), + } + } + } + + /// Submits a [WalPruneProcedure] for the given topic name. + pub async fn submit_procedure(&self, topic_name: &str) -> Result { + let guard = self + .tracker + .insert_running_procedure(topic_name.to_string()) + .with_context(|| error::PruneTaskAlreadyRunningSnafu { topic: topic_name })?; + + let procedure = WalPruneProcedure::new( + topic_name.to_string(), + self.wal_prune_context.clone(), + self.trigger_flush_threshold, + Some(guard), + ); + let procedure_with_id = ProcedureWithId::with_random_id(Box::new(procedure)); + let procedure_id = procedure_with_id.id; + METRIC_META_REMOTE_WAL_PRUNE_EXECUTE + .with_label_values(&[topic_name]) + .inc(); + let procedure_manager = self.procedure_manager.clone(); + let mut watcher = procedure_manager + .submit(procedure_with_id) + .await + .context(error::SubmitProcedureSnafu)?; + watcher::wait(&mut watcher) + .await + .context(error::WaitProcedureSnafu)?; + + Ok(procedure_id) + } + + async fn handle_tick_request(&self) -> Result<()> { + let topics = self.retrieve_sorted_topics().await?; + let mut tasks = Vec::with_capacity(topics.len()); + for topic_name in topics.iter() { + tasks.push(async { + let _permit = self.semaphore.acquire().await.unwrap(); + match self.submit_procedure(topic_name).await { + Ok(_) => {} + Err(error::Error::PruneTaskAlreadyRunning { topic, .. }) => { + warn!("Prune task for topic {} is already running", topic); + } + Err(e) => { + error!( + "Failed to submit prune task for topic {}: {}", + topic_name.clone(), + e + ); + } + } + }); + } + + join_all(tasks).await; + Ok(()) + } + + /// Retrieve topics from the table metadata manager. + /// Since [WalPruneManager] submits procedures depending on the order of the topics, we should sort the topics. + /// TODO(CookiePie): Can register topics in memory instead of retrieving from the table metadata manager every time. + async fn retrieve_sorted_topics(&self) -> Result> { + self.table_metadata_manager + .topic_name_manager() + .range() + .await + .context(error::TableMetadataManagerSnafu) + } +} + +#[cfg(test)] +mod test { + use std::assert_matches::assert_matches; + + use common_meta::key::topic_name::TopicNameKey; + use common_wal::test_util::run_test_with_kafka_wal; + use tokio::time::{sleep, timeout}; + + use super::*; + use crate::procedure::wal_prune::test_util::TestEnv; + + #[tokio::test] + async fn test_wal_prune_ticker() { + let (tx, mut rx) = WalPruneManager::channel(); + let interval = Duration::from_millis(10); + let ticker = WalPruneTicker::new(interval, tx); + assert_eq!(ticker.name(), "WalPruneTicker"); + + for _ in 0..2 { + ticker.start(); + sleep(2 * interval).await; + assert!(!rx.is_empty()); + while let Ok(event) = rx.try_recv() { + assert_matches!(event, Event::Tick); + } + } + ticker.stop(); + } + + #[tokio::test] + async fn test_wal_prune_tracker_and_guard() { + let tracker = WalPruneProcedureTracker { + running_procedures: Arc::new(RwLock::new(HashSet::new())), + }; + let topic_name = uuid::Uuid::new_v4().to_string(); + { + let guard = tracker + .insert_running_procedure(topic_name.clone()) + .unwrap(); + assert_eq!(guard.topic_name, topic_name); + assert_eq!(guard.running_procedures.read().unwrap().len(), 1); + + let result = tracker.insert_running_procedure(topic_name.clone()); + assert!(result.is_none()); + } + assert_eq!(tracker.running_procedures.read().unwrap().len(), 0); + } + + async fn mock_wal_prune_manager( + broker_endpoints: Vec, + limit: usize, + ) -> (Sender, WalPruneManager) { + let test_env = TestEnv::new(); + let (tx, rx) = WalPruneManager::channel(); + let wal_prune_context = test_env.build_wal_prune_context(broker_endpoints).await; + ( + tx, + WalPruneManager::new( + test_env.table_metadata_manager.clone(), + limit, + rx, + test_env.procedure_manager.clone(), + wal_prune_context, + 0, + ), + ) + } + + async fn mock_topics(manager: &WalPruneManager, topics: &[String]) { + let topic_name_keys = topics + .iter() + .map(|topic| TopicNameKey::new(topic)) + .collect::>(); + manager + .table_metadata_manager + .topic_name_manager() + .batch_put(topic_name_keys) + .await + .unwrap(); + } + + #[tokio::test] + async fn test_wal_prune_manager() { + run_test_with_kafka_wal(|broker_endpoints| { + Box::pin(async { + let limit = 6; + let (tx, manager) = mock_wal_prune_manager(broker_endpoints, limit).await; + let topics = (0..limit * 2) + .map(|_| uuid::Uuid::new_v4().to_string()) + .collect::>(); + mock_topics(&manager, &topics).await; + + let tracker = manager.tracker.clone(); + let handler = + common_runtime::spawn_global(async move { manager.try_start().await.unwrap() }); + handler.await.unwrap(); + + tx.send(Event::Tick).await.unwrap(); + // Wait for at least one procedure to be submitted. + timeout(Duration::from_millis(100), async move { tracker.len() > 0 }) + .await + .unwrap(); + }) + }) + .await; + } +} diff --git a/src/meta-srv/src/procedure/wal_prune/test_util.rs b/src/meta-srv/src/procedure/wal_prune/test_util.rs new file mode 100644 index 0000000000..b7cdbad286 --- /dev/null +++ b/src/meta-srv/src/procedure/wal_prune/test_util.rs @@ -0,0 +1,94 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::Arc; + +use common_meta::key::{TableMetadataManager, TableMetadataManagerRef}; +use common_meta::kv_backend::memory::MemoryKvBackend; +use common_meta::region_registry::{LeaderRegionRegistry, LeaderRegionRegistryRef}; +use common_meta::sequence::SequenceBuilder; +use common_meta::state_store::KvStateStore; +use common_meta::wal_options_allocator::build_kafka_client; +use common_procedure::local::{LocalManager, ManagerConfig}; +use common_procedure::test_util::InMemoryPoisonStore; +use common_procedure::ProcedureManagerRef; +use common_wal::config::kafka::common::{KafkaConnectionConfig, KafkaTopicConfig}; +use common_wal::config::kafka::MetasrvKafkaConfig; +use rskafka::client::Client; + +use crate::procedure::test_util::MailboxContext; +use crate::procedure::wal_prune::Context as WalPruneContext; + +pub struct TestEnv { + pub table_metadata_manager: TableMetadataManagerRef, + pub leader_region_registry: LeaderRegionRegistryRef, + pub procedure_manager: ProcedureManagerRef, + pub mailbox: MailboxContext, + pub server_addr: String, +} + +impl TestEnv { + pub fn new() -> Self { + let kv_backend = Arc::new(MemoryKvBackend::new()); + let table_metadata_manager = Arc::new(TableMetadataManager::new(kv_backend.clone())); + let leader_region_registry = Arc::new(LeaderRegionRegistry::new()); + let mailbox_sequence = + SequenceBuilder::new("test_heartbeat_mailbox", kv_backend.clone()).build(); + + let state_store = Arc::new(KvStateStore::new(kv_backend.clone())); + let poison_manager = Arc::new(InMemoryPoisonStore::default()); + let procedure_manager = Arc::new(LocalManager::new( + ManagerConfig::default(), + state_store, + poison_manager, + )); + + let mailbox_ctx = MailboxContext::new(mailbox_sequence); + + Self { + table_metadata_manager, + leader_region_registry, + procedure_manager, + mailbox: mailbox_ctx, + server_addr: "localhost".to_string(), + } + } + + async fn build_kafka_client(broker_endpoints: Vec) -> Arc { + let kafka_topic = KafkaTopicConfig { + replication_factor: broker_endpoints.len() as i16, + ..Default::default() + }; + let config = MetasrvKafkaConfig { + connection: KafkaConnectionConfig { + broker_endpoints, + ..Default::default() + }, + kafka_topic, + ..Default::default() + }; + Arc::new(build_kafka_client(&config).await.unwrap()) + } + + pub async fn build_wal_prune_context(&self, broker_endpoints: Vec) -> WalPruneContext { + let client = Self::build_kafka_client(broker_endpoints).await; + WalPruneContext { + client, + table_metadata_manager: self.table_metadata_manager.clone(), + leader_region_registry: self.leader_region_registry.clone(), + server_addr: self.server_addr.to_string(), + mailbox: self.mailbox.mailbox().clone(), + } + } +} diff --git a/src/meta-srv/src/region/supervisor.rs b/src/meta-srv/src/region/supervisor.rs index 9393474f16..42963fb1fd 100644 --- a/src/meta-srv/src/region/supervisor.rs +++ b/src/meta-srv/src/region/supervisor.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -24,9 +25,9 @@ use common_meta::leadership_notifier::LeadershipChangeListener; use common_meta::peer::PeerLookupServiceRef; use common_meta::DatanodeId; use common_runtime::JoinHandle; -use common_telemetry::{error, info, warn}; +use common_telemetry::{debug, error, info, warn}; use common_time::util::current_time_millis; -use error::Error::{MigrationRunning, TableRouteNotFound}; +use error::Error::{LeaderPeerChanged, MigrationRunning, TableRouteNotFound}; use snafu::{OptionExt, ResultExt}; use store_api::storage::RegionId; use tokio::sync::mpsc::{Receiver, Sender}; @@ -36,7 +37,9 @@ use crate::error::{self, Result}; use crate::failure_detector::PhiAccrualFailureDetectorOptions; use crate::metasrv::{SelectorContext, SelectorRef}; use crate::procedure::region_migration::manager::RegionMigrationManagerRef; -use crate::procedure::region_migration::RegionMigrationProcedureTask; +use crate::procedure::region_migration::{ + RegionMigrationProcedureTask, DEFAULT_REGION_MIGRATION_TIMEOUT, +}; use crate::region::failure_detector::RegionFailureDetector; use crate::selector::SelectorOptions; @@ -205,6 +208,8 @@ pub const DEFAULT_TICK_INTERVAL: Duration = Duration::from_secs(1); pub struct RegionSupervisor { /// Used to detect the failure of regions. failure_detector: RegionFailureDetector, + /// Tracks the number of failovers for each region. + failover_counts: HashMap, /// Receives [Event]s. receiver: Receiver, /// The context of [`SelectorRef`] @@ -290,6 +295,7 @@ impl RegionSupervisor { ) -> Self { Self { failure_detector: RegionFailureDetector::new(options), + failover_counts: HashMap::new(), receiver: event_receiver, selector_context, selector, @@ -333,13 +339,14 @@ impl RegionSupervisor { } } - async fn deregister_failure_detectors(&self, detecting_regions: Vec) { + async fn deregister_failure_detectors(&mut self, detecting_regions: Vec) { for region in detecting_regions { - self.failure_detector.remove(®ion) + self.failure_detector.remove(®ion); + self.failover_counts.remove(®ion); } } - async fn handle_region_failures(&self, mut regions: Vec<(DatanodeId, RegionId)>) { + async fn handle_region_failures(&mut self, mut regions: Vec<(DatanodeId, RegionId)>) { if regions.is_empty() { return; } @@ -362,16 +369,15 @@ impl RegionSupervisor { .collect::>(); for (datanode_id, region_id) in migrating_regions { - self.failure_detector.remove(&(datanode_id, region_id)); + debug!( + "Removed region failover for region: {region_id}, datanode: {datanode_id} because it's migrating" + ); } warn!("Detects region failures: {:?}", regions); for (datanode_id, region_id) in regions { - match self.do_failover(datanode_id, region_id).await { - Ok(_) => self.failure_detector.remove(&(datanode_id, region_id)), - Err(err) => { - error!(err; "Failed to execute region failover for region: {region_id}, datanode: {datanode_id}"); - } + if let Err(err) = self.do_failover(datanode_id, region_id).await { + error!(err; "Failed to execute region failover for region: {region_id}, datanode: {datanode_id}"); } } } @@ -383,7 +389,12 @@ impl RegionSupervisor { .context(error::MaintenanceModeManagerSnafu) } - async fn do_failover(&self, datanode_id: DatanodeId, region_id: RegionId) -> Result<()> { + async fn do_failover(&mut self, datanode_id: DatanodeId, region_id: RegionId) -> Result<()> { + let count = *self + .failover_counts + .entry((datanode_id, region_id)) + .and_modify(|count| *count += 1) + .or_insert(1); let from_peer = self .peer_lookup .datanode(datanode_id) @@ -401,6 +412,7 @@ impl RegionSupervisor { SelectorOptions { min_required_items: 1, allow_duplication: false, + exclude_peer_ids: HashSet::from([from_peer.id]), }, ) .await?; @@ -411,17 +423,44 @@ impl RegionSupervisor { ); return Ok(()); } + info!( + "Failover for region: {region_id}, from_peer: {from_peer}, to_peer: {to_peer}, tries: {count}" + ); let task = RegionMigrationProcedureTask { region_id, from_peer, to_peer, - timeout: Duration::from_secs(60), + timeout: DEFAULT_REGION_MIGRATION_TIMEOUT * count, }; if let Err(err) = self.region_migration_manager.submit_procedure(task).await { return match err { // Returns Ok if it's running or table is dropped. - MigrationRunning { .. } | TableRouteNotFound { .. } => Ok(()), + MigrationRunning { .. } => { + info!( + "Another region migration is running, skip failover for region: {}, datanode: {}", + region_id, datanode_id + ); + Ok(()) + } + TableRouteNotFound { .. } => { + self.deregister_failure_detectors(vec![(datanode_id, region_id)]) + .await; + info!( + "Table route is not found, the table is dropped, removed failover detector for region: {}, datanode: {}", + region_id, datanode_id + ); + Ok(()) + } + LeaderPeerChanged { .. } => { + self.deregister_failure_detectors(vec![(datanode_id, region_id)]) + .await; + info!( + "Region's leader peer changed, removed failover detector for region: {}, datanode: {}", + region_id, datanode_id + ); + Ok(()) + } err => Err(err), }; }; diff --git a/src/meta-srv/src/selector.rs b/src/meta-srv/src/selector.rs index c197f04e59..96fbda241d 100644 --- a/src/meta-srv/src/selector.rs +++ b/src/meta-srv/src/selector.rs @@ -12,15 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod common; +pub mod common; pub mod lease_based; pub mod load_based; pub mod round_robin; #[cfg(test)] pub(crate) mod test_utils; -mod weight_compute; -mod weighted_choose; +pub mod weight_compute; +pub mod weighted_choose; +use std::collections::HashSet; + use serde::{Deserialize, Serialize}; +use strum::AsRefStr; use crate::error; use crate::error::Result; @@ -39,6 +42,8 @@ pub struct SelectorOptions { pub min_required_items: usize, /// Whether duplicates are allowed in the selected result, default false. pub allow_duplication: bool, + /// The peers to exclude from the selection. + pub exclude_peer_ids: HashSet, } impl Default for SelectorOptions { @@ -46,12 +51,13 @@ impl Default for SelectorOptions { Self { min_required_items: 1, allow_duplication: false, + exclude_peer_ids: HashSet::new(), } } } /// [`SelectorType`] refers to the load balancer used when creating tables. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, AsRefStr)] #[serde(try_from = "String")] pub enum SelectorType { /// The current load balancing is based on the number of regions on each datanode node; diff --git a/src/meta-srv/src/selector/common.rs b/src/meta-srv/src/selector/common.rs index 24352c14c2..24c3597c26 100644 --- a/src/meta-srv/src/selector/common.rs +++ b/src/meta-srv/src/selector/common.rs @@ -12,15 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashSet; + use common_meta::peer::Peer; use snafu::ensure; use crate::error; use crate::error::Result; use crate::metasrv::SelectTarget; -use crate::selector::weighted_choose::WeightedChoose; +use crate::selector::weighted_choose::{WeightedChoose, WeightedItem}; use crate::selector::SelectorOptions; +/// Filter out the excluded peers from the `weight_array`. +pub fn filter_out_excluded_peers( + weight_array: &mut Vec>, + exclude_peer_ids: &HashSet, +) { + weight_array.retain(|peer| !exclude_peer_ids.contains(&peer.item.id)); +} + /// According to the `opts`, choose peers from the `weight_array` through `weighted_choose`. pub fn choose_items(opts: &SelectorOptions, weighted_choose: &mut W) -> Result> where @@ -80,7 +90,7 @@ mod tests { use common_meta::peer::Peer; - use crate::selector::common::choose_items; + use crate::selector::common::{choose_items, filter_out_excluded_peers}; use crate::selector::weighted_choose::{RandomWeightedChoose, WeightedItem}; use crate::selector::SelectorOptions; @@ -92,35 +102,35 @@ mod tests { id: 1, addr: "127.0.0.1:3001".to_string(), }, - weight: 1, + weight: 1.0, }, WeightedItem { item: Peer { id: 2, addr: "127.0.0.1:3001".to_string(), }, - weight: 1, + weight: 1.0, }, WeightedItem { item: Peer { id: 3, addr: "127.0.0.1:3001".to_string(), }, - weight: 1, + weight: 1.0, }, WeightedItem { item: Peer { id: 4, addr: "127.0.0.1:3001".to_string(), }, - weight: 1, + weight: 1.0, }, WeightedItem { item: Peer { id: 5, addr: "127.0.0.1:3001".to_string(), }, - weight: 1, + weight: 1.0, }, ]; @@ -128,6 +138,7 @@ mod tests { let opts = SelectorOptions { min_required_items: i, allow_duplication: false, + exclude_peer_ids: HashSet::new(), }; let selected_peers: HashSet<_> = @@ -142,6 +153,7 @@ mod tests { let opts = SelectorOptions { min_required_items: 6, allow_duplication: false, + exclude_peer_ids: HashSet::new(), }; let selected_result = @@ -152,6 +164,7 @@ mod tests { let opts = SelectorOptions { min_required_items: i, allow_duplication: true, + exclude_peer_ids: HashSet::new(), }; let selected_peers = @@ -160,4 +173,30 @@ mod tests { assert_eq!(i, selected_peers.len()); } } + + #[test] + fn test_filter_out_excluded_peers() { + let mut weight_array = vec![ + WeightedItem { + item: Peer { + id: 1, + addr: "127.0.0.1:3001".to_string(), + }, + weight: 1.0, + }, + WeightedItem { + item: Peer { + id: 2, + addr: "127.0.0.1:3002".to_string(), + }, + weight: 1.0, + }, + ]; + + let exclude_peer_ids = HashSet::from([1]); + filter_out_excluded_peers(&mut weight_array, &exclude_peer_ids); + + assert_eq!(weight_array.len(), 1); + assert_eq!(weight_array[0].item.id, 2); + } } diff --git a/src/meta-srv/src/selector/lease_based.rs b/src/meta-srv/src/selector/lease_based.rs index a7ce7c7321..448c26b08e 100644 --- a/src/meta-srv/src/selector/lease_based.rs +++ b/src/meta-srv/src/selector/lease_based.rs @@ -12,17 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashSet; +use std::sync::Arc; + use common_meta::peer::Peer; use crate::error::Result; use crate::lease; use crate::metasrv::SelectorContext; -use crate::selector::common::choose_items; +use crate::node_excluder::NodeExcluderRef; +use crate::selector::common::{choose_items, filter_out_excluded_peers}; use crate::selector::weighted_choose::{RandomWeightedChoose, WeightedItem}; use crate::selector::{Selector, SelectorOptions}; /// Select all alive datanodes based using a random weighted choose. -pub struct LeaseBasedSelector; +pub struct LeaseBasedSelector { + node_excluder: NodeExcluderRef, +} + +impl LeaseBasedSelector { + pub fn new(node_excluder: NodeExcluderRef) -> Self { + Self { node_excluder } + } +} + +impl Default for LeaseBasedSelector { + fn default() -> Self { + Self { + node_excluder: Arc::new(Vec::new()), + } + } +} #[async_trait::async_trait] impl Selector for LeaseBasedSelector { @@ -35,18 +55,26 @@ impl Selector for LeaseBasedSelector { lease::alive_datanodes(&ctx.meta_peer_client, ctx.datanode_lease_secs).await?; // 2. compute weight array, but the weight of each item is the same. - let weight_array = lease_kvs + let mut weight_array = lease_kvs .into_iter() .map(|(k, v)| WeightedItem { item: Peer { id: k.node_id, addr: v.node_addr.clone(), }, - weight: 1, + weight: 1.0, }) .collect(); // 3. choose peers by weight_array. + let mut exclude_peer_ids = self + .node_excluder + .excluded_datanode_ids() + .iter() + .cloned() + .collect::>(); + exclude_peer_ids.extend(opts.exclude_peer_ids.iter()); + filter_out_excluded_peers(&mut weight_array, &exclude_peer_ids); let mut weighted_choose = RandomWeightedChoose::new(weight_array); let selected = choose_items(&opts, &mut weighted_choose)?; diff --git a/src/meta-srv/src/selector/load_based.rs b/src/meta-srv/src/selector/load_based.rs index 2628990bf4..4f33245a28 100644 --- a/src/meta-srv/src/selector/load_based.rs +++ b/src/meta-srv/src/selector/load_based.rs @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; use common_meta::datanode::{DatanodeStatKey, DatanodeStatValue}; use common_meta::key::TableMetadataManager; @@ -26,18 +27,23 @@ use crate::error::{self, Result}; use crate::key::{DatanodeLeaseKey, LeaseValue}; use crate::lease; use crate::metasrv::SelectorContext; -use crate::selector::common::choose_items; +use crate::node_excluder::NodeExcluderRef; +use crate::selector::common::{choose_items, filter_out_excluded_peers}; use crate::selector::weight_compute::{RegionNumsBasedWeightCompute, WeightCompute}; use crate::selector::weighted_choose::RandomWeightedChoose; use crate::selector::{Selector, SelectorOptions}; pub struct LoadBasedSelector { weight_compute: C, + node_excluder: NodeExcluderRef, } impl LoadBasedSelector { - pub fn new(weight_compute: C) -> Self { - Self { weight_compute } + pub fn new(weight_compute: C, node_excluder: NodeExcluderRef) -> Self { + Self { + weight_compute, + node_excluder, + } } } @@ -45,6 +51,7 @@ impl Default for LoadBasedSelector { fn default() -> Self { Self { weight_compute: RegionNumsBasedWeightCompute, + node_excluder: Arc::new(Vec::new()), } } } @@ -85,9 +92,17 @@ where }; // 4. compute weight array. - let weight_array = self.weight_compute.compute(&stat_kvs); + let mut weight_array = self.weight_compute.compute(&stat_kvs); // 5. choose peers by weight_array. + let mut exclude_peer_ids = self + .node_excluder + .excluded_datanode_ids() + .iter() + .cloned() + .collect::>(); + exclude_peer_ids.extend(opts.exclude_peer_ids.iter()); + filter_out_excluded_peers(&mut weight_array, &exclude_peer_ids); let mut weighted_choose = RandomWeightedChoose::new(weight_array); let selected = choose_items(&opts, &mut weighted_choose)?; diff --git a/src/meta-srv/src/selector/round_robin.rs b/src/meta-srv/src/selector/round_robin.rs index f11a36555f..d930ca06cb 100644 --- a/src/meta-srv/src/selector/round_robin.rs +++ b/src/meta-srv/src/selector/round_robin.rs @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashSet; use std::sync::atomic::AtomicUsize; +use std::sync::Arc; use common_meta::peer::Peer; use snafu::ensure; @@ -20,6 +22,7 @@ use snafu::ensure; use crate::error::{NoEnoughAvailableNodeSnafu, Result}; use crate::lease; use crate::metasrv::{SelectTarget, SelectorContext}; +use crate::node_excluder::NodeExcluderRef; use crate::selector::{Selector, SelectorOptions}; /// Round-robin selector that returns the next peer in the list in sequence. @@ -32,6 +35,7 @@ use crate::selector::{Selector, SelectorOptions}; pub struct RoundRobinSelector { select_target: SelectTarget, counter: AtomicUsize, + node_excluder: NodeExcluderRef, } impl Default for RoundRobinSelector { @@ -39,32 +43,38 @@ impl Default for RoundRobinSelector { Self { select_target: SelectTarget::Datanode, counter: AtomicUsize::new(0), + node_excluder: Arc::new(Vec::new()), } } } impl RoundRobinSelector { - pub fn new(select_target: SelectTarget) -> Self { + pub fn new(select_target: SelectTarget, node_excluder: NodeExcluderRef) -> Self { Self { select_target, + node_excluder, ..Default::default() } } - async fn get_peers( - &self, - min_required_items: usize, - ctx: &SelectorContext, - ) -> Result> { + async fn get_peers(&self, opts: &SelectorOptions, ctx: &SelectorContext) -> Result> { let mut peers = match self.select_target { SelectTarget::Datanode => { // 1. get alive datanodes. let lease_kvs = lease::alive_datanodes(&ctx.meta_peer_client, ctx.datanode_lease_secs).await?; + let mut exclude_peer_ids = self + .node_excluder + .excluded_datanode_ids() + .iter() + .cloned() + .collect::>(); + exclude_peer_ids.extend(opts.exclude_peer_ids.iter()); // 2. map into peers lease_kvs .into_iter() + .filter(|(k, _)| !exclude_peer_ids.contains(&k.node_id)) .map(|(k, v)| Peer::new(k.node_id, v.node_addr)) .collect::>() } @@ -84,8 +94,8 @@ impl RoundRobinSelector { ensure!( !peers.is_empty(), NoEnoughAvailableNodeSnafu { - required: min_required_items, - available: 0usize, + required: opts.min_required_items, + available: peers.len(), select_target: self.select_target } ); @@ -103,7 +113,7 @@ impl Selector for RoundRobinSelector { type Output = Vec; async fn select(&self, ctx: &Self::Context, opts: SelectorOptions) -> Result> { - let peers = self.get_peers(opts.min_required_items, ctx).await?; + let peers = self.get_peers(&opts, ctx).await?; // choose peers let mut selected = Vec::with_capacity(opts.min_required_items); for _ in 0..opts.min_required_items { @@ -120,6 +130,8 @@ impl Selector for RoundRobinSelector { #[cfg(test)] mod test { + use std::collections::HashSet; + use super::*; use crate::test_util::{create_selector_context, put_datanodes}; @@ -149,6 +161,7 @@ mod test { SelectorOptions { min_required_items: 4, allow_duplication: true, + exclude_peer_ids: HashSet::new(), }, ) .await @@ -165,6 +178,7 @@ mod test { SelectorOptions { min_required_items: 2, allow_duplication: true, + exclude_peer_ids: HashSet::new(), }, ) .await @@ -172,4 +186,42 @@ mod test { assert_eq!(peers.len(), 2); assert_eq!(peers, vec![peer2.clone(), peer3.clone()]); } + + #[tokio::test] + async fn test_round_robin_selector_with_exclude_peer_ids() { + let selector = RoundRobinSelector::new(SelectTarget::Datanode, Arc::new(vec![5])); + let ctx = create_selector_context(); + // add three nodes + let peer1 = Peer { + id: 2, + addr: "node1".to_string(), + }; + let peer2 = Peer { + id: 5, + addr: "node2".to_string(), + }; + let peer3 = Peer { + id: 8, + addr: "node3".to_string(), + }; + put_datanodes( + &ctx.meta_peer_client, + vec![peer1.clone(), peer2.clone(), peer3.clone()], + ) + .await; + + let peers = selector + .select( + &ctx, + SelectorOptions { + min_required_items: 1, + allow_duplication: true, + exclude_peer_ids: HashSet::from([2]), + }, + ) + .await + .unwrap(); + assert_eq!(peers.len(), 1); + assert_eq!(peers, vec![peer3.clone()]); + } } diff --git a/src/meta-srv/src/selector/weight_compute.rs b/src/meta-srv/src/selector/weight_compute.rs index eb35f43f19..d69e2f56d3 100644 --- a/src/meta-srv/src/selector/weight_compute.rs +++ b/src/meta-srv/src/selector/weight_compute.rs @@ -84,7 +84,7 @@ impl WeightCompute for RegionNumsBasedWeightCompute { .zip(region_nums) .map(|(peer, region_num)| WeightedItem { item: peer, - weight: (max_weight - region_num + base_weight) as usize, + weight: (max_weight - region_num + base_weight) as f64, }) .collect() } @@ -148,7 +148,7 @@ mod tests { 2, ); for weight in weight_array.iter() { - assert_eq!(*expected.get(&weight.item).unwrap(), weight.weight,); + assert_eq!(*expected.get(&weight.item).unwrap(), weight.weight as usize); } let mut expected = HashMap::new(); @@ -195,6 +195,8 @@ mod tests { manifest_version: 0, flushed_entry_id: 0, }, + data_topic_latest_entry_id: 0, + metadata_topic_latest_entry_id: 0, }], ..Default::default() } @@ -220,6 +222,8 @@ mod tests { manifest_version: 0, flushed_entry_id: 0, }, + data_topic_latest_entry_id: 0, + metadata_topic_latest_entry_id: 0, }], ..Default::default() } @@ -245,6 +249,8 @@ mod tests { manifest_version: 0, flushed_entry_id: 0, }, + data_topic_latest_entry_id: 0, + metadata_topic_latest_entry_id: 0, }], ..Default::default() } diff --git a/src/meta-srv/src/selector/weighted_choose.rs b/src/meta-srv/src/selector/weighted_choose.rs index 749df57edf..0890be9137 100644 --- a/src/meta-srv/src/selector/weighted_choose.rs +++ b/src/meta-srv/src/selector/weighted_choose.rs @@ -42,10 +42,10 @@ pub trait WeightedChoose: Send + Sync { } /// The struct represents a weighted item. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub struct WeightedItem { pub item: Item, - pub weight: usize, + pub weight: f64, } /// A implementation of weighted balance: random weighted choose. @@ -87,7 +87,7 @@ where // unwrap safety: whether weighted_index is none has been checked before. let item = self .items - .choose_weighted(&mut rng(), |item| item.weight as f64) + .choose_weighted(&mut rng(), |item| item.weight) .context(error::ChooseItemsSnafu)? .item .clone(); @@ -95,11 +95,11 @@ where } fn choose_multiple(&mut self, amount: usize) -> Result> { - let amount = amount.min(self.items.iter().filter(|item| item.weight > 0).count()); + let amount = amount.min(self.items.iter().filter(|item| item.weight > 0.0).count()); Ok(self .items - .choose_multiple_weighted(&mut rng(), amount, |item| item.weight as f64) + .choose_multiple_weighted(&mut rng(), amount, |item| item.weight) .context(error::ChooseItemsSnafu)? .cloned() .map(|item| item.item) @@ -120,9 +120,12 @@ mod tests { let mut choose = RandomWeightedChoose::new(vec![ WeightedItem { item: 1, - weight: 100, + weight: 100.0, + }, + WeightedItem { + item: 2, + weight: 0.0, }, - WeightedItem { item: 2, weight: 0 }, ]); for _ in 0..100 { diff --git a/src/meta-srv/src/service/store/cached_kv.rs b/src/meta-srv/src/service/store/cached_kv.rs index b26c2a558f..f86b42a9e2 100644 --- a/src/meta-srv/src/service/store/cached_kv.rs +++ b/src/meta-srv/src/service/store/cached_kv.rs @@ -278,7 +278,7 @@ impl KvBackend for LeaderCachedKvBackend { let remote_res = self.store.batch_get(remote_req).await?; let put_req = BatchPutRequest { - kvs: remote_res.kvs.clone().into_iter().map(Into::into).collect(), + kvs: remote_res.kvs.clone().into_iter().collect(), ..Default::default() }; let _ = self.cache.batch_put(put_req).await?; diff --git a/src/meta-srv/src/table_meta_alloc.rs b/src/meta-srv/src/table_meta_alloc.rs index 8578e6cd19..54dd34ea86 100644 --- a/src/meta-srv/src/table_meta_alloc.rs +++ b/src/meta-srv/src/table_meta_alloc.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashSet; + use async_trait::async_trait; use common_error::ext::BoxedError; use common_meta::ddl::table_meta::PeerAllocator; @@ -51,6 +53,7 @@ impl MetasrvPeerAllocator { SelectorOptions { min_required_items: regions, allow_duplication: true, + exclude_peer_ids: HashSet::new(), }, ) .await?; diff --git a/src/metric-engine/Cargo.toml b/src/metric-engine/Cargo.toml index 5fe5ed3cb5..9e7a5b8545 100644 --- a/src/metric-engine/Cargo.toml +++ b/src/metric-engine/Cargo.toml @@ -18,11 +18,13 @@ common-error.workspace = true common-macro.workspace = true common-query.workspace = true common-recordbatch.workspace = true +common-runtime.workspace = true common-telemetry.workspace = true common-time.workspace = true datafusion.workspace = true datatypes.workspace = true futures-util.workspace = true +humantime-serde.workspace = true itertools.workspace = true lazy_static = "1.4" mito2.workspace = true diff --git a/src/metric-engine/src/config.rs b/src/metric-engine/src/config.rs index b35b412188..20df8fa739 100644 --- a/src/metric-engine/src/config.rs +++ b/src/metric-engine/src/config.rs @@ -12,9 +12,49 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::time::Duration; + +use common_telemetry::warn; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] +/// The default flush interval of the metadata region. +pub(crate) const DEFAULT_FLUSH_METADATA_REGION_INTERVAL: Duration = Duration::from_secs(30); + +/// Configuration for the metric engine. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct EngineConfig { + /// Experimental feature to use sparse primary key encoding. pub experimental_sparse_primary_key_encoding: bool, + /// The flush interval of the metadata region. + #[serde( + with = "humantime_serde", + default = "EngineConfig::default_flush_metadata_region_interval" + )] + pub flush_metadata_region_interval: Duration, +} + +impl Default for EngineConfig { + fn default() -> Self { + Self { + flush_metadata_region_interval: DEFAULT_FLUSH_METADATA_REGION_INTERVAL, + experimental_sparse_primary_key_encoding: false, + } + } +} + +impl EngineConfig { + fn default_flush_metadata_region_interval() -> Duration { + DEFAULT_FLUSH_METADATA_REGION_INTERVAL + } + + /// Sanitizes the configuration. + pub fn sanitize(&mut self) { + if self.flush_metadata_region_interval.is_zero() { + warn!( + "Flush metadata region interval is zero, override with default value: {:?}. Disable metadata region flush is forbidden.", + DEFAULT_FLUSH_METADATA_REGION_INTERVAL + ); + self.flush_metadata_region_interval = DEFAULT_FLUSH_METADATA_REGION_INTERVAL; + } + } } diff --git a/src/metric-engine/src/engine.rs b/src/metric-engine/src/engine.rs index d1e837d078..15caa2c456 100644 --- a/src/metric-engine/src/engine.rs +++ b/src/metric-engine/src/engine.rs @@ -24,6 +24,7 @@ mod put; mod read; mod region_metadata; mod state; +mod sync; use std::any::Any; use std::collections::HashMap; @@ -33,25 +34,28 @@ use api::region::RegionResponse; use async_trait::async_trait; use common_error::ext::{BoxedError, ErrorExt}; use common_error::status_code::StatusCode; +use common_runtime::RepeatedTask; use mito2::engine::MitoEngine; pub(crate) use options::IndexOptions; use snafu::ResultExt; +pub(crate) use state::MetricEngineState; use store_api::metadata::RegionMetadataRef; use store_api::metric_engine_consts::METRIC_ENGINE_NAME; use store_api::region_engine::{ RegionEngine, RegionManifestInfo, RegionRole, RegionScannerRef, RegionStatistic, - SetRegionRoleStateResponse, SettableRegionRoleState, + SetRegionRoleStateResponse, SetRegionRoleStateSuccess, SettableRegionRoleState, + SyncManifestResponse, }; use store_api::region_request::{BatchRegionDdlRequest, RegionRequest}; use store_api::storage::{RegionId, ScanRequest, SequenceNumber}; -use self::state::MetricEngineState; use crate::config::EngineConfig; use crate::data_region::DataRegion; -use crate::error::{self, MetricManifestInfoSnafu, Result, UnsupportedRegionRequestSnafu}; +use crate::error::{self, Error, Result, StartRepeatedTaskSnafu, UnsupportedRegionRequestSnafu}; use crate::metadata_region::MetadataRegion; +use crate::repeated_task::FlushMetadataRegionTask; use crate::row_modifier::RowModifier; -use crate::utils; +use crate::utils::{self, get_region_statistic}; #[cfg_attr(doc, aquamarine::aquamarine)] /// # Metric Engine @@ -219,6 +223,10 @@ impl RegionEngine for MetricEngine { } } RegionRequest::Catchup(req) => self.inner.catchup_region(region_id, req).await, + RegionRequest::BulkInserts(_) => { + // todo(hl): find a way to support bulk inserts in metric engine. + UnsupportedRegionRequestSnafu { request }.fail() + } }; result.map_err(BoxedError::new).map(|rows| RegionResponse { @@ -258,29 +266,7 @@ impl RegionEngine for MetricEngine { /// Note: Returns `None` if it's a logical region. fn region_statistic(&self, region_id: RegionId) -> Option { if self.inner.is_physical_region(region_id) { - let metadata_region_id = utils::to_metadata_region_id(region_id); - let data_region_id = utils::to_data_region_id(region_id); - - let metadata_stat = self.inner.mito.region_statistic(metadata_region_id); - let data_stat = self.inner.mito.region_statistic(data_region_id); - - match (metadata_stat, data_stat) { - (Some(metadata_stat), Some(data_stat)) => Some(RegionStatistic { - num_rows: metadata_stat.num_rows + data_stat.num_rows, - memtable_size: metadata_stat.memtable_size + data_stat.memtable_size, - wal_size: metadata_stat.wal_size + data_stat.wal_size, - manifest_size: metadata_stat.manifest_size + data_stat.manifest_size, - sst_size: metadata_stat.sst_size + data_stat.sst_size, - index_size: metadata_stat.index_size + data_stat.index_size, - manifest: RegionManifestInfo::Metric { - data_flushed_entry_id: data_stat.manifest.data_flushed_entry_id(), - data_manifest_version: data_stat.manifest.data_manifest_version(), - metadata_flushed_entry_id: metadata_stat.manifest.data_flushed_entry_id(), - metadata_manifest_version: metadata_stat.manifest.data_manifest_version(), - }, - }), - _ => None, - } + get_region_statistic(&self.inner.mito, region_id) } else { None } @@ -311,40 +297,11 @@ impl RegionEngine for MetricEngine { &self, region_id: RegionId, manifest_info: RegionManifestInfo, - ) -> Result<(), BoxedError> { - if !manifest_info.is_metric() { - return Err(BoxedError::new( - MetricManifestInfoSnafu { region_id }.build(), - )); - } - - let metadata_region_id = utils::to_metadata_region_id(region_id); - // checked by ensure above - let metadata_manifest_version = manifest_info - .metadata_manifest_version() - .unwrap_or_default(); - let metadata_flushed_entry_id = manifest_info - .metadata_flushed_entry_id() - .unwrap_or_default(); - let metadata_region_manifest = - RegionManifestInfo::mito(metadata_manifest_version, metadata_flushed_entry_id); + ) -> Result { self.inner - .mito - .sync_region(metadata_region_id, metadata_region_manifest) - .await?; - - let data_region_id = utils::to_data_region_id(region_id); - let data_manifest_version = manifest_info.data_manifest_version(); - let data_flushed_entry_id = manifest_info.data_flushed_entry_id(); - let data_region_manifest = - RegionManifestInfo::mito(data_manifest_version, data_flushed_entry_id); - - self.inner - .mito - .sync_region(data_region_id, data_region_manifest) - .await?; - - Ok(()) + .sync_region(region_id, manifest_info) + .await + .map_err(BoxedError::new) } async fn set_region_role_state_gracefully( @@ -352,17 +309,39 @@ impl RegionEngine for MetricEngine { region_id: RegionId, region_role_state: SettableRegionRoleState, ) -> std::result::Result { - self.inner + let metadata_result = match self + .inner .mito .set_region_role_state_gracefully( utils::to_metadata_region_id(region_id), region_role_state, ) - .await?; - self.inner + .await? + { + SetRegionRoleStateResponse::Success(success) => success, + SetRegionRoleStateResponse::NotFound => { + return Ok(SetRegionRoleStateResponse::NotFound) + } + }; + + let data_result = match self + .inner .mito .set_region_role_state_gracefully(region_id, region_role_state) - .await + .await? + { + SetRegionRoleStateResponse::Success(success) => success, + SetRegionRoleStateResponse::NotFound => { + return Ok(SetRegionRoleStateResponse::NotFound) + } + }; + + Ok(SetRegionRoleStateResponse::success( + SetRegionRoleStateSuccess::metric( + data_result.last_entry_id().unwrap_or_default(), + metadata_result.last_entry_id().unwrap_or_default(), + ), + )) } /// Returns the physical region role. @@ -382,25 +361,39 @@ impl RegionEngine for MetricEngine { } impl MetricEngine { - pub fn new(mito: MitoEngine, config: EngineConfig) -> Self { + pub fn try_new(mito: MitoEngine, mut config: EngineConfig) -> Result { let metadata_region = MetadataRegion::new(mito.clone()); let data_region = DataRegion::new(mito.clone()); - Self { - inner: Arc::new(MetricEngineInner { - mito, - metadata_region, - data_region, - state: RwLock::default(), - config, - row_modifier: RowModifier::new(), - }), - } + let state = Arc::new(RwLock::default()); + config.sanitize(); + let flush_interval = config.flush_metadata_region_interval; + let inner = Arc::new(MetricEngineInner { + mito: mito.clone(), + metadata_region, + data_region, + state: state.clone(), + config, + row_modifier: RowModifier::new(), + flush_task: RepeatedTask::new( + flush_interval, + Box::new(FlushMetadataRegionTask { + state: state.clone(), + mito: mito.clone(), + }), + ), + }); + inner + .flush_task + .start(common_runtime::global_runtime()) + .context(StartRepeatedTaskSnafu { name: "flush_task" })?; + Ok(Self { inner }) } pub fn mito(&self) -> MitoEngine { self.inner.mito.clone() } + /// Returns all logical regions associated with the physical region. pub async fn logical_regions(&self, physical_region_id: RegionId) -> Result> { self.inner .metadata_region @@ -448,15 +441,21 @@ impl MetricEngine { ) -> Result { self.inner.scan_to_stream(region_id, request).await } + + /// Returns the configuration of the engine. + pub fn config(&self) -> &EngineConfig { + &self.inner.config + } } struct MetricEngineInner { mito: MitoEngine, metadata_region: MetadataRegion, data_region: DataRegion, - state: RwLock, + state: Arc>, config: EngineConfig, row_modifier: RowModifier, + flush_task: RepeatedTask, } #[cfg(test)] diff --git a/src/metric-engine/src/engine/alter.rs b/src/metric-engine/src/engine/alter.rs index 0b23a80bfd..22ef54cab8 100644 --- a/src/metric-engine/src/engine/alter.rs +++ b/src/metric-engine/src/engine/alter.rs @@ -30,7 +30,7 @@ use crate::error::{ LogicalRegionNotFoundSnafu, PhysicalRegionNotFoundSnafu, Result, SerializeColumnMetadataSnafu, UnexpectedRequestSnafu, }; -use crate::utils::to_data_region_id; +use crate::utils::{append_manifest_info, encode_manifest_info_to_extensions, to_data_region_id}; impl MetricEngineInner { pub async fn alter_regions( @@ -63,11 +63,15 @@ impl MetricEngineInner { .unwrap() .get_physical_region_id(region_id) .with_context(|| LogicalRegionNotFoundSnafu { region_id })?; + let mut manifest_infos = Vec::with_capacity(1); self.alter_logical_regions(physical_region_id, requests, extension_return_value) .await?; + append_manifest_info(&self.mito, region_id, &mut manifest_infos); + encode_manifest_info_to_extensions(&manifest_infos, extension_return_value)?; } else { let grouped_requests = self.group_logical_region_requests_by_physical_region_id(requests)?; + let mut manifest_infos = Vec::with_capacity(grouped_requests.len()); for (physical_region_id, requests) in grouped_requests { self.alter_logical_regions( physical_region_id, @@ -75,7 +79,9 @@ impl MetricEngineInner { extension_return_value, ) .await?; + append_manifest_info(&self.mito, physical_region_id, &mut manifest_infos); } + encode_manifest_info_to_extensions(&manifest_infos, extension_return_value)?; } } Ok(0) @@ -145,7 +151,7 @@ impl MetricEngineInner { let _write_guard = self .metadata_region .write_lock_logical_region(*region_id) - .await; + .await?; write_guards.insert(*region_id, _write_guard); } diff --git a/src/metric-engine/src/engine/catchup.rs b/src/metric-engine/src/engine/catchup.rs index 44713f0bc4..d2e92f6e0e 100644 --- a/src/metric-engine/src/engine/catchup.rs +++ b/src/metric-engine/src/engine/catchup.rs @@ -56,7 +56,8 @@ impl MetricEngineInner { metadata_region_id, RegionRequest::Catchup(RegionCatchupRequest { set_writable: req.set_writable, - entry_id: None, + entry_id: req.metadata_entry_id, + metadata_entry_id: None, location_id: req.location_id, }), ) @@ -70,6 +71,7 @@ impl MetricEngineInner { RegionRequest::Catchup(RegionCatchupRequest { set_writable: req.set_writable, entry_id: req.entry_id, + metadata_entry_id: None, location_id: req.location_id, }), ) diff --git a/src/metric-engine/src/engine/create.rs b/src/metric-engine/src/engine/create.rs index 856bdb7b72..596054623d 100644 --- a/src/metric-engine/src/engine/create.rs +++ b/src/metric-engine/src/engine/create.rs @@ -34,7 +34,7 @@ use store_api::metric_engine_consts::{ METADATA_SCHEMA_VALUE_COLUMN_INDEX, METADATA_SCHEMA_VALUE_COLUMN_NAME, }; use store_api::mito_engine_options::{ - APPEND_MODE_KEY, MEMTABLE_PARTITION_TREE_PRIMARY_KEY_ENCODING, TTL_KEY, + APPEND_MODE_KEY, MEMTABLE_PARTITION_TREE_PRIMARY_KEY_ENCODING, SKIP_WAL_KEY, TTL_KEY, }; use store_api::region_engine::RegionEngine; use store_api::region_request::{AffectedRows, RegionCreateRequest, RegionRequest}; @@ -51,7 +51,10 @@ use crate::error::{ Result, SerializeColumnMetadataSnafu, UnexpectedRequestSnafu, }; use crate::metrics::PHYSICAL_REGION_COUNT; -use crate::utils::{self, to_data_region_id, to_metadata_region_id}; +use crate::utils::{ + self, append_manifest_info, encode_manifest_info_to_extensions, to_data_region_id, + to_metadata_region_id, +}; const DEFAULT_TABLE_ID_SKIPPING_INDEX_GRANULARITY: u32 = 1024; @@ -88,11 +91,15 @@ impl MetricEngineInner { if requests.len() == 1 { let request = &requests.first().unwrap().1; let physical_region_id = parse_physical_region_id(request)?; + let mut manifest_infos = Vec::with_capacity(1); self.create_logical_regions(physical_region_id, requests, extension_return_value) .await?; + append_manifest_info(&self.mito, physical_region_id, &mut manifest_infos); + encode_manifest_info_to_extensions(&manifest_infos, extension_return_value)?; } else { let grouped_requests = group_create_logical_region_requests_by_physical_region_id(requests)?; + let mut manifest_infos = Vec::with_capacity(grouped_requests.len()); for (physical_region_id, requests) in grouped_requests { self.create_logical_regions( physical_region_id, @@ -100,7 +107,9 @@ impl MetricEngineInner { extension_return_value, ) .await?; + append_manifest_info(&self.mito, physical_region_id, &mut manifest_infos); } + encode_manifest_info_to_extensions(&manifest_infos, extension_return_value)?; } } else { return MissingRegionOptionSnafu {}.fail(); @@ -279,9 +288,16 @@ impl MetricEngineInner { .add_logical_regions(physical_region_id, true, logical_region_columns) .await?; - let mut state = self.state.write().unwrap(); - state.add_physical_columns(data_region_id, new_add_columns); - state.add_logical_regions(physical_region_id, logical_regions); + { + let mut state = self.state.write().unwrap(); + state.add_physical_columns(data_region_id, new_add_columns); + state.add_logical_regions(physical_region_id, logical_regions.clone()); + } + for logical_region_id in logical_regions { + self.metadata_region + .open_logical_region(logical_region_id) + .await; + } Ok(()) } @@ -549,6 +565,7 @@ pub(crate) fn region_options_for_metadata_region( // Don't allow to set primary key encoding for metadata region. original.remove(MEMTABLE_PARTITION_TREE_PRIMARY_KEY_ENCODING); original.insert(TTL_KEY.to_string(), FOREVER.to_string()); + original.remove(SKIP_WAL_KEY); original } @@ -685,8 +702,12 @@ mod test { #[tokio::test] async fn test_create_request_for_physical_regions() { // original request - let mut ttl_options = HashMap::new(); - ttl_options.insert("ttl".to_string(), "60m".to_string()); + let options: HashMap<_, _> = [ + ("ttl".to_string(), "60m".to_string()), + ("skip_wal".to_string(), "true".to_string()), + ] + .into_iter() + .collect(); let request = RegionCreateRequest { engine: METRIC_ENGINE_NAME.to_string(), column_metadatas: vec![ @@ -710,13 +731,13 @@ mod test { }, ], primary_key: vec![0], - options: ttl_options, + options, region_dir: "/test_dir".to_string(), }; // set up let env = TestEnv::new().await; - let engine = MetricEngine::new(env.mito(), EngineConfig::default()); + let engine = MetricEngine::try_new(env.mito(), EngineConfig::default()).unwrap(); let engine_inner = engine.inner; // check create data region request @@ -742,5 +763,6 @@ mod test { metadata_region_request.options.get("ttl").unwrap(), "forever" ); + assert!(!metadata_region_request.options.contains_key("skip_wal")); } } diff --git a/src/metric-engine/src/engine/open.rs b/src/metric-engine/src/engine/open.rs index eb9f266be2..4b25cf38f2 100644 --- a/src/metric-engine/src/engine/open.rs +++ b/src/metric-engine/src/engine/open.rs @@ -132,12 +132,14 @@ impl MetricEngineInner { /// Includes: /// - Record physical region's column names /// - Record the mapping between logical region id and physical region id + /// + /// Returns new opened logical region ids. pub(crate) async fn recover_states( &self, physical_region_id: RegionId, primary_key_encoding: PrimaryKeyEncoding, physical_region_options: PhysicalRegionOptions, - ) -> Result<()> { + ) -> Result> { // load logical regions and physical column names let logical_regions = self .metadata_region @@ -147,7 +149,6 @@ impl MetricEngineInner { .data_region .physical_columns(physical_region_id) .await?; - let logical_region_num = logical_regions.len(); { let mut state = self.state.write().unwrap(); @@ -168,15 +169,22 @@ impl MetricEngineInner { } } + let mut opened_logical_region_ids = Vec::new(); + // The `recover_states` may be called multiple times, we only count the logical regions + // that are opened for the first time. for logical_region_id in logical_regions { - self.metadata_region + if self + .metadata_region .open_logical_region(logical_region_id) - .await; + .await + { + opened_logical_region_ids.push(logical_region_id); + } } - LOGICAL_REGION_COUNT.add(logical_region_num as i64); + LOGICAL_REGION_COUNT.add(opened_logical_region_ids.len() as i64); - Ok(()) + Ok(opened_logical_region_ids) } } diff --git a/src/metric-engine/src/engine/region_metadata.rs b/src/metric-engine/src/engine/region_metadata.rs index 9f00235e96..f8e0dd8dc3 100644 --- a/src/metric-engine/src/engine/region_metadata.rs +++ b/src/metric-engine/src/engine/region_metadata.rs @@ -46,7 +46,7 @@ impl MetricEngineInner { let _read_guard = self .metadata_region .read_lock_logical_region(logical_region_id) - .await; + .await?; // Load logical and physical columns, and intersect them to get logical column metadata. let logical_column_metadata = self .metadata_region diff --git a/src/metric-engine/src/engine/sync.rs b/src/metric-engine/src/engine/sync.rs new file mode 100644 index 0000000000..fe0d8ef6d0 --- /dev/null +++ b/src/metric-engine/src/engine/sync.rs @@ -0,0 +1,261 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::time::Instant; + +use common_telemetry::info; +use snafu::{ensure, OptionExt, ResultExt}; +use store_api::region_engine::{RegionEngine, RegionManifestInfo, SyncManifestResponse}; +use store_api::storage::RegionId; + +use crate::engine::MetricEngineInner; +use crate::error::{ + MetricManifestInfoSnafu, MitoSyncOperationSnafu, PhysicalRegionNotFoundSnafu, Result, +}; +use crate::utils; + +impl MetricEngineInner { + pub async fn sync_region( + &self, + region_id: RegionId, + manifest_info: RegionManifestInfo, + ) -> Result { + ensure!( + manifest_info.is_metric(), + MetricManifestInfoSnafu { region_id } + ); + + let metadata_region_id = utils::to_metadata_region_id(region_id); + // checked by ensure above + let metadata_manifest_version = manifest_info + .metadata_manifest_version() + .unwrap_or_default(); + let metadata_flushed_entry_id = manifest_info + .metadata_flushed_entry_id() + .unwrap_or_default(); + let metadata_region_manifest = + RegionManifestInfo::mito(metadata_manifest_version, metadata_flushed_entry_id); + let metadata_synced = self + .mito + .sync_region(metadata_region_id, metadata_region_manifest) + .await + .context(MitoSyncOperationSnafu)? + .is_data_synced(); + + let data_region_id = utils::to_data_region_id(region_id); + let data_manifest_version = manifest_info.data_manifest_version(); + let data_flushed_entry_id = manifest_info.data_flushed_entry_id(); + let data_region_manifest = + RegionManifestInfo::mito(data_manifest_version, data_flushed_entry_id); + + let data_synced = self + .mito + .sync_region(data_region_id, data_region_manifest) + .await + .context(MitoSyncOperationSnafu)? + .is_data_synced(); + + if !metadata_synced { + return Ok(SyncManifestResponse::Metric { + metadata_synced, + data_synced, + new_opened_logical_region_ids: vec![], + }); + } + + let now = Instant::now(); + // Recovers the states from the metadata region + // if the metadata manifest version is updated. + let physical_region_options = *self + .state + .read() + .unwrap() + .physical_region_states() + .get(&data_region_id) + .context(PhysicalRegionNotFoundSnafu { + region_id: data_region_id, + })? + .options(); + let primary_key_encoding = self.mito.get_primary_key_encoding(data_region_id).context( + PhysicalRegionNotFoundSnafu { + region_id: data_region_id, + }, + )?; + let new_opened_logical_region_ids = self + .recover_states( + data_region_id, + primary_key_encoding, + physical_region_options, + ) + .await?; + info!( + "Sync metadata region for physical region {}, cost: {:?}, new opened logical region ids: {:?}", + data_region_id, + now.elapsed(), + new_opened_logical_region_ids + ); + + Ok(SyncManifestResponse::Metric { + metadata_synced, + data_synced, + new_opened_logical_region_ids, + }) + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use api::v1::SemanticType; + use common_telemetry::info; + use datatypes::data_type::ConcreteDataType; + use datatypes::schema::ColumnSchema; + use store_api::metadata::ColumnMetadata; + use store_api::region_engine::{RegionEngine, RegionManifestInfo}; + use store_api::region_request::{ + AddColumn, AlterKind, RegionAlterRequest, RegionFlushRequest, RegionRequest, + }; + use store_api::storage::RegionId; + + use crate::metadata_region::MetadataRegion; + use crate::test_util::TestEnv; + + #[tokio::test] + async fn test_sync_region_with_new_created_logical_regions() { + common_telemetry::init_default_ut_logging(); + let mut env = TestEnv::with_prefix("sync_with_new_created_logical_regions").await; + env.init_metric_region().await; + + info!("creating follower engine"); + // Create a follower engine. + let (_follower_mito, follower_metric) = env.create_follower_engine().await; + + let physical_region_id = env.default_physical_region_id(); + + // Flushes the physical region + let metric_engine = env.metric(); + metric_engine + .handle_request( + env.default_physical_region_id(), + RegionRequest::Flush(RegionFlushRequest::default()), + ) + .await + .unwrap(); + + let response = follower_metric + .sync_region(physical_region_id, RegionManifestInfo::metric(1, 0, 1, 0)) + .await + .unwrap(); + assert!(response.is_metric()); + let new_opened_logical_region_ids = response.new_opened_logical_region_ids().unwrap(); + assert_eq!(new_opened_logical_region_ids, vec![RegionId::new(3, 2)]); + + // Sync again, no new logical region should be opened + let response = follower_metric + .sync_region(physical_region_id, RegionManifestInfo::metric(1, 0, 1, 0)) + .await + .unwrap(); + assert!(response.is_metric()); + let new_opened_logical_region_ids = response.new_opened_logical_region_ids().unwrap(); + assert!(new_opened_logical_region_ids.is_empty()); + } + + fn test_alter_logical_region_request() -> RegionAlterRequest { + RegionAlterRequest { + kind: AlterKind::AddColumns { + columns: vec![AddColumn { + column_metadata: ColumnMetadata { + column_id: 0, + semantic_type: SemanticType::Tag, + column_schema: ColumnSchema::new( + "tag1", + ConcreteDataType::string_datatype(), + false, + ), + }, + location: None, + }], + }, + } + } + + #[tokio::test] + async fn test_sync_region_alter_alter_logical_region() { + common_telemetry::init_default_ut_logging(); + let mut env = TestEnv::with_prefix("sync_region_alter_alter_logical_region").await; + env.init_metric_region().await; + + info!("creating follower engine"); + let physical_region_id = env.default_physical_region_id(); + // Flushes the physical region + let metric_engine = env.metric(); + metric_engine + .handle_request( + env.default_physical_region_id(), + RegionRequest::Flush(RegionFlushRequest::default()), + ) + .await + .unwrap(); + + // Create a follower engine. + let (follower_mito, follower_metric) = env.create_follower_engine().await; + let metric_engine = env.metric(); + let engine_inner = env.metric().inner; + let region_id = env.default_logical_region_id(); + let request = test_alter_logical_region_request(); + + engine_inner + .alter_logical_regions( + physical_region_id, + vec![(region_id, request)], + &mut HashMap::new(), + ) + .await + .unwrap(); + + // Flushes the physical region + metric_engine + .handle_request( + env.default_physical_region_id(), + RegionRequest::Flush(RegionFlushRequest::default()), + ) + .await + .unwrap(); + + // Sync the follower engine + let response = follower_metric + .sync_region(physical_region_id, RegionManifestInfo::metric(2, 0, 2, 0)) + .await + .unwrap(); + assert!(response.is_metric()); + let new_opened_logical_region_ids = response.new_opened_logical_region_ids().unwrap(); + assert!(new_opened_logical_region_ids.is_empty()); + + let logical_region_id = env.default_logical_region_id(); + let metadata_region = MetadataRegion::new(follower_mito.clone()); + let semantic_type = metadata_region + .column_semantic_type(physical_region_id, logical_region_id, "tag1") + .await + .unwrap() + .unwrap(); + assert_eq!(semantic_type, SemanticType::Tag); + let timestamp_index = metadata_region + .column_semantic_type(physical_region_id, logical_region_id, "greptime_timestamp") + .await + .unwrap() + .unwrap(); + assert_eq!(timestamp_index, SemanticType::Timestamp); + } +} diff --git a/src/metric-engine/src/error.rs b/src/metric-engine/src/error.rs index 8be535ec9f..c0ae55e402 100644 --- a/src/metric-engine/src/error.rs +++ b/src/metric-engine/src/error.rs @@ -67,6 +67,14 @@ pub enum Error { location: Location, }, + #[snafu(display("Failed to serialize region manifest info"))] + SerializeRegionManifestInfo { + #[snafu(source)] + error: serde_json::Error, + #[snafu(implicit)] + location: Location, + }, + #[snafu(display("Failed to decode base64 column value"))] DecodeColumnValue { #[snafu(source)] @@ -118,6 +126,7 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + #[snafu(display("Mito delete operation fails"))] MitoDeleteOperation { source: BoxedError, @@ -132,6 +141,13 @@ pub enum Error { location: Location, }, + #[snafu(display("Mito sync operation fails"))] + MitoSyncOperation { + source: BoxedError, + #[snafu(implicit)] + location: Location, + }, + #[snafu(display("Failed to collect record batch stream"))] CollectRecordBatchStream { source: common_recordbatch::error::Error, @@ -266,6 +282,14 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display("Failed to start repeated task: {}", name))] + StartRepeatedTask { + name: String, + source: common_runtime::error::Error, + #[snafu(implicit)] + location: Location, + }, } pub type Result = std::result::Result; @@ -296,7 +320,8 @@ impl ErrorExt for Error { | DecodeColumnValue { .. } | ParseRegionId { .. } | InvalidMetadata { .. } - | SetSkippingIndexOption { .. } => StatusCode::Unexpected, + | SetSkippingIndexOption { .. } + | SerializeRegionManifestInfo { .. } => StatusCode::Unexpected, PhysicalRegionNotFound { .. } | LogicalRegionNotFound { .. } => { StatusCode::RegionNotFound @@ -311,12 +336,15 @@ impl ErrorExt for Error { | MitoWriteOperation { source, .. } | MitoCatchupOperation { source, .. } | MitoFlushOperation { source, .. } - | MitoDeleteOperation { source, .. } => source.status_code(), + | MitoDeleteOperation { source, .. } + | MitoSyncOperation { source, .. } => source.status_code(), EncodePrimaryKey { source, .. } => source.status_code(), CollectRecordBatchStream { source, .. } => source.status_code(), + StartRepeatedTask { source, .. } => source.status_code(), + MetricManifestInfo { .. } => StatusCode::Internal, } } diff --git a/src/metric-engine/src/lib.rs b/src/metric-engine/src/lib.rs index 597e8f5897..7722f3acd1 100644 --- a/src/metric-engine/src/lib.rs +++ b/src/metric-engine/src/lib.rs @@ -59,6 +59,7 @@ pub mod engine; pub mod error; mod metadata_region; mod metrics; +mod repeated_task; pub mod row_modifier; #[cfg(test)] mod test_util; diff --git a/src/metric-engine/src/metadata_region.rs b/src/metric-engine/src/metadata_region.rs index 2b066a0bde..7e7bae095f 100644 --- a/src/metric-engine/src/metadata_region.rs +++ b/src/metric-engine/src/metadata_region.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::hash_map::Entry; use std::collections::HashMap; use std::sync::Arc; @@ -76,11 +77,22 @@ impl MetadataRegion { } } - pub async fn open_logical_region(&self, logical_region_id: RegionId) { - self.logical_region_lock + /// Open a logical region. + /// + /// Returns true if the logical region is opened for the first time. + pub async fn open_logical_region(&self, logical_region_id: RegionId) -> bool { + match self + .logical_region_lock .write() .await - .insert(logical_region_id, Arc::new(RwLock::new(()))); + .entry(logical_region_id) + { + Entry::Occupied(_) => false, + Entry::Vacant(vacant_entry) => { + vacant_entry.insert(Arc::new(RwLock::new(()))); + true + } + } } /// Retrieve a read lock guard of given logical region id. @@ -178,6 +190,7 @@ impl MetadataRegion { Ok(columns) } + /// Return all logical regions associated with the physical region. pub async fn logical_regions(&self, physical_region_id: RegionId) -> Result> { let metadata_region_id = utils::to_metadata_region_id(physical_region_id); diff --git a/src/metric-engine/src/repeated_task.rs b/src/metric-engine/src/repeated_task.rs new file mode 100644 index 0000000000..e5e7f7025f --- /dev/null +++ b/src/metric-engine/src/repeated_task.rs @@ -0,0 +1,167 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::{Arc, RwLock}; +use std::time::Instant; + +use common_runtime::TaskFunction; +use common_telemetry::{debug, error}; +use mito2::engine::MitoEngine; +use store_api::region_engine::{RegionEngine, RegionRole}; +use store_api::region_request::{RegionFlushRequest, RegionRequest}; + +use crate::engine::MetricEngineState; +use crate::error::{Error, Result}; +use crate::utils; + +/// Task to flush metadata regions. +/// +/// This task is used to send flush requests to the metadata regions +/// periodically. +pub(crate) struct FlushMetadataRegionTask { + pub(crate) state: Arc>, + pub(crate) mito: MitoEngine, +} + +#[async_trait::async_trait] +impl TaskFunction for FlushMetadataRegionTask { + fn name(&self) -> &str { + "FlushMetadataRegionTask" + } + + async fn call(&mut self) -> Result<()> { + let region_ids = { + let state = self.state.read().unwrap(); + state + .physical_region_states() + .keys() + .cloned() + .collect::>() + }; + + let num_region = region_ids.len(); + let now = Instant::now(); + for region_id in region_ids { + let Some(role) = self.mito.role(region_id) else { + continue; + }; + if role == RegionRole::Follower { + continue; + } + let metadata_region_id = utils::to_metadata_region_id(region_id); + if let Err(e) = self + .mito + .handle_request( + metadata_region_id, + RegionRequest::Flush(RegionFlushRequest { + row_group_size: None, + }), + ) + .await + { + error!(e; "Failed to flush metadata region {}", metadata_region_id); + } + } + debug!( + "Flushed {} metadata regions, elapsed: {:?}", + num_region, + now.elapsed() + ); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::assert_matches::assert_matches; + use std::time::Duration; + + use store_api::region_engine::{RegionEngine, RegionManifestInfo}; + + use crate::config::{EngineConfig, DEFAULT_FLUSH_METADATA_REGION_INTERVAL}; + use crate::test_util::TestEnv; + + #[tokio::test] + async fn test_flush_metadata_region_task() { + let env = TestEnv::with_prefix_and_config( + "test_flush_metadata_region_task", + EngineConfig { + flush_metadata_region_interval: Duration::from_millis(100), + ..Default::default() + }, + ) + .await; + env.init_metric_region().await; + let engine = env.metric(); + // Wait for flush task run + tokio::time::sleep(Duration::from_millis(200)).await; + let physical_region_id = env.default_physical_region_id(); + let stat = engine.region_statistic(physical_region_id).unwrap(); + + assert_matches!( + stat.manifest, + RegionManifestInfo::Metric { + metadata_manifest_version: 1, + metadata_flushed_entry_id: 1, + .. + } + ) + } + + #[tokio::test] + async fn test_flush_metadata_region_task_with_long_interval() { + let env = TestEnv::with_prefix_and_config( + "test_flush_metadata_region_task_with_long_interval", + EngineConfig { + flush_metadata_region_interval: Duration::from_secs(60), + ..Default::default() + }, + ) + .await; + env.init_metric_region().await; + let engine = env.metric(); + // Wait for flush task run, should not flush metadata region + tokio::time::sleep(Duration::from_millis(200)).await; + let physical_region_id = env.default_physical_region_id(); + let stat = engine.region_statistic(physical_region_id).unwrap(); + + assert_matches!( + stat.manifest, + RegionManifestInfo::Metric { + metadata_manifest_version: 0, + metadata_flushed_entry_id: 0, + .. + } + ) + } + + #[tokio::test] + async fn test_flush_metadata_region_sanitize() { + let env = TestEnv::with_prefix_and_config( + "test_flush_metadata_region_sanitize", + EngineConfig { + flush_metadata_region_interval: Duration::from_secs(0), + ..Default::default() + }, + ) + .await; + let metric = env.metric(); + let config = metric.config(); + assert_eq!( + config.flush_metadata_region_interval, + DEFAULT_FLUSH_METADATA_REGION_INTERVAL + ); + } +} diff --git a/src/metric-engine/src/test_util.rs b/src/metric-engine/src/test_util.rs index 284834a029..e750b516f1 100644 --- a/src/metric-engine/src/test_util.rs +++ b/src/metric-engine/src/test_util.rs @@ -16,6 +16,7 @@ use api::v1::value::ValueData; use api::v1::{ColumnDataType, ColumnSchema as PbColumnSchema, Row, SemanticType, Value}; +use common_telemetry::debug; use datatypes::prelude::ConcreteDataType; use datatypes::schema::ColumnSchema; use mito2::config::MitoConfig; @@ -28,7 +29,7 @@ use store_api::metric_engine_consts::{ }; use store_api::region_engine::RegionEngine; use store_api::region_request::{ - AddColumn, AlterKind, RegionAlterRequest, RegionCreateRequest, RegionRequest, + AddColumn, AlterKind, RegionAlterRequest, RegionCreateRequest, RegionOpenRequest, RegionRequest, }; use store_api::storage::{ColumnId, RegionId}; @@ -53,9 +54,14 @@ impl TestEnv { /// Returns a new env with specific `prefix` for test. pub async fn with_prefix(prefix: &str) -> Self { + Self::with_prefix_and_config(prefix, EngineConfig::default()).await + } + + /// Returns a new env with specific `prefix` and `config` for test. + pub async fn with_prefix_and_config(prefix: &str, config: EngineConfig) -> Self { let mut mito_env = MitoTestEnv::with_prefix(prefix); let mito = mito_env.create_engine(MitoConfig::default()).await; - let metric = MetricEngine::new(mito.clone(), EngineConfig::default()); + let metric = MetricEngine::try_new(mito.clone(), config).unwrap(); Self { mito_env, mito, @@ -77,6 +83,34 @@ impl TestEnv { self.metric.clone() } + /// Creates a new follower engine with the same config as the leader engine. + pub async fn create_follower_engine(&mut self) -> (MitoEngine, MetricEngine) { + let mito = self + .mito_env + .create_follower_engine(MitoConfig::default()) + .await; + let metric = MetricEngine::try_new(mito.clone(), EngineConfig::default()).unwrap(); + + let region_id = self.default_physical_region_id(); + debug!("opening default physical region: {region_id}"); + let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())] + .into_iter() + .collect(); + metric + .handle_request( + region_id, + RegionRequest::Open(RegionOpenRequest { + engine: METRIC_ENGINE_NAME.to_string(), + region_dir: self.default_region_dir(), + options: physical_region_option, + skip_wal_replay: true, + }), + ) + .await + .unwrap(); + (mito, metric) + } + /// Create regions in [MetricEngine] under [`default_region_id`] /// and region dir `"test_metric_region"`. /// diff --git a/src/metric-engine/src/utils.rs b/src/metric-engine/src/utils.rs index 183ba2eaa3..0f28a6365e 100644 --- a/src/metric-engine/src/utils.rs +++ b/src/metric-engine/src/utils.rs @@ -12,9 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use store_api::metric_engine_consts::{METRIC_DATA_REGION_GROUP, METRIC_METADATA_REGION_GROUP}; +use std::collections::HashMap; + +use common_telemetry::{info, warn}; +use mito2::engine::MitoEngine; +use snafu::ResultExt; +use store_api::metric_engine_consts::{ + MANIFEST_INFO_EXTENSION_KEY, METRIC_DATA_REGION_GROUP, METRIC_METADATA_REGION_GROUP, +}; +use store_api::region_engine::{RegionEngine, RegionManifestInfo, RegionStatistic}; use store_api::storage::RegionId; +use crate::error::{Result, SerializeRegionManifestInfoSnafu}; + /// Change the given [RegionId]'s region group to [METRIC_METADATA_REGION_GROUP]. pub fn to_metadata_region_id(region_id: RegionId) -> RegionId { let table_id = region_id.table_id(); @@ -29,6 +39,71 @@ pub fn to_data_region_id(region_id: RegionId) -> RegionId { RegionId::with_group_and_seq(table_id, METRIC_DATA_REGION_GROUP, region_sequence) } +/// Get the region statistic of the given [RegionId]. +pub fn get_region_statistic(mito: &MitoEngine, region_id: RegionId) -> Option { + let metadata_region_id = to_metadata_region_id(region_id); + let data_region_id = to_data_region_id(region_id); + + let metadata_stat = mito.region_statistic(metadata_region_id); + let data_stat = mito.region_statistic(data_region_id); + + match (&metadata_stat, &data_stat) { + (Some(metadata_stat), Some(data_stat)) => Some(RegionStatistic { + num_rows: metadata_stat.num_rows + data_stat.num_rows, + memtable_size: metadata_stat.memtable_size + data_stat.memtable_size, + wal_size: metadata_stat.wal_size + data_stat.wal_size, + manifest_size: metadata_stat.manifest_size + data_stat.manifest_size, + sst_size: metadata_stat.sst_size + data_stat.sst_size, + index_size: metadata_stat.index_size + data_stat.index_size, + manifest: RegionManifestInfo::Metric { + data_flushed_entry_id: data_stat.manifest.data_flushed_entry_id(), + data_manifest_version: data_stat.manifest.data_manifest_version(), + metadata_flushed_entry_id: metadata_stat.manifest.data_flushed_entry_id(), + metadata_manifest_version: metadata_stat.manifest.data_manifest_version(), + }, + data_topic_latest_entry_id: data_stat.data_topic_latest_entry_id, + metadata_topic_latest_entry_id: metadata_stat.metadata_topic_latest_entry_id, + }), + _ => { + warn!( + "Failed to get region statistic for region {}, metadata_stat: {:?}, data_stat: {:?}", + region_id, metadata_stat, data_stat + ); + None + } + } +} + +/// Appends the given [RegionId]'s manifest info to the given list. +pub(crate) fn append_manifest_info( + mito: &MitoEngine, + region_id: RegionId, + manifest_infos: &mut Vec<(RegionId, RegionManifestInfo)>, +) { + if let Some(statistic) = get_region_statistic(mito, region_id) { + manifest_infos.push((region_id, statistic.manifest)); + } +} + +/// Encodes the given list of ([RegionId], [RegionManifestInfo]) to extensions(key: MANIFEST_INFO_EXTENSION_KEY). +pub(crate) fn encode_manifest_info_to_extensions( + manifest_infos: &[(RegionId, RegionManifestInfo)], + extensions: &mut HashMap>, +) -> Result<()> { + extensions.insert( + MANIFEST_INFO_EXTENSION_KEY.to_string(), + RegionManifestInfo::encode_list(manifest_infos) + .context(SerializeRegionManifestInfoSnafu)?, + ); + for (region_id, manifest_info) in manifest_infos { + info!( + "Added manifest info: {:?} to extensions, region_id: {:?}", + manifest_info, region_id + ); + } + Ok(()) +} + #[cfg(test)] mod tests { diff --git a/src/mito2/src/cache/index/bloom_filter_index.rs b/src/mito2/src/cache/index/bloom_filter_index.rs index 097acd6367..61df853573 100644 --- a/src/mito2/src/cache/index/bloom_filter_index.rs +++ b/src/mito2/src/cache/index/bloom_filter_index.rs @@ -29,8 +29,15 @@ use crate::sst::file::FileId; const INDEX_TYPE_BLOOM_FILTER_INDEX: &str = "bloom_filter_index"; +/// Tag for bloom filter index cache. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Tag { + Skipping, + Fulltext, +} + /// Cache for bloom filter index. -pub type BloomFilterIndexCache = IndexCache<(FileId, ColumnId), BloomFilterMeta>; +pub type BloomFilterIndexCache = IndexCache<(FileId, ColumnId, Tag), BloomFilterMeta>; pub type BloomFilterIndexCacheRef = Arc; impl BloomFilterIndexCache { @@ -48,14 +55,20 @@ impl BloomFilterIndexCache { } /// Calculates weight for bloom filter index metadata. -fn bloom_filter_index_metadata_weight(k: &(FileId, ColumnId), _: &Arc) -> u32 { +fn bloom_filter_index_metadata_weight( + k: &(FileId, ColumnId, Tag), + _: &Arc, +) -> u32 { (k.0.as_bytes().len() + std::mem::size_of::() + std::mem::size_of::()) as u32 } /// Calculates weight for bloom filter index content. -fn bloom_filter_index_content_weight((k, _): &((FileId, ColumnId), PageKey), v: &Bytes) -> u32 { +fn bloom_filter_index_content_weight( + (k, _): &((FileId, ColumnId, Tag), PageKey), + v: &Bytes, +) -> u32 { (k.0.as_bytes().len() + std::mem::size_of::() + v.len()) as u32 } @@ -63,6 +76,7 @@ fn bloom_filter_index_content_weight((k, _): &((FileId, ColumnId), PageKey), v: pub struct CachedBloomFilterIndexBlobReader { file_id: FileId, column_id: ColumnId, + tag: Tag, blob_size: u64, inner: R, cache: BloomFilterIndexCacheRef, @@ -73,6 +87,7 @@ impl CachedBloomFilterIndexBlobReader { pub fn new( file_id: FileId, column_id: ColumnId, + tag: Tag, blob_size: u64, inner: R, cache: BloomFilterIndexCacheRef, @@ -80,6 +95,7 @@ impl CachedBloomFilterIndexBlobReader { Self { file_id, column_id, + tag, blob_size, inner, cache, @@ -93,7 +109,7 @@ impl BloomFilterReader for CachedBloomFilterIndexBl let inner = &self.inner; self.cache .get_or_load( - (self.file_id, self.column_id), + (self.file_id, self.column_id, self.tag), self.blob_size, offset, size, @@ -107,7 +123,7 @@ impl BloomFilterReader for CachedBloomFilterIndexBl let fetch = ranges.iter().map(|range| { let inner = &self.inner; self.cache.get_or_load( - (self.file_id, self.column_id), + (self.file_id, self.column_id, self.tag), self.blob_size, range.start, (range.end - range.start) as u32, @@ -123,13 +139,18 @@ impl BloomFilterReader for CachedBloomFilterIndexBl /// Reads the meta information of the bloom filter. async fn metadata(&self) -> Result { - if let Some(cached) = self.cache.get_metadata((self.file_id, self.column_id)) { + if let Some(cached) = self + .cache + .get_metadata((self.file_id, self.column_id, self.tag)) + { CACHE_HIT.with_label_values(&[INDEX_METADATA_TYPE]).inc(); Ok((*cached).clone()) } else { let meta = self.inner.metadata().await?; - self.cache - .put_metadata((self.file_id, self.column_id), Arc::new(meta.clone())); + self.cache.put_metadata( + (self.file_id, self.column_id, self.tag), + Arc::new(meta.clone()), + ); CACHE_MISS.with_label_values(&[INDEX_METADATA_TYPE]).inc(); Ok(meta) } diff --git a/src/mito2/src/engine.rs b/src/mito2/src/engine.rs index 110f79b875..fbedef3c35 100644 --- a/src/mito2/src/engine.rs +++ b/src/mito2/src/engine.rs @@ -70,7 +70,7 @@ use common_base::Plugins; use common_error::ext::BoxedError; use common_meta::key::SchemaMetadataManagerRef; use common_recordbatch::SendableRecordBatchStream; -use common_telemetry::tracing; +use common_telemetry::{info, tracing}; use common_wal::options::{WalOptions, WAL_OPTIONS_KEY}; use futures::future::{join_all, try_join_all}; use object_store::manager::ObjectStoreManagerRef; @@ -80,9 +80,10 @@ use store_api::logstore::provider::Provider; use store_api::logstore::LogStore; use store_api::manifest::ManifestVersion; use store_api::metadata::RegionMetadataRef; +use store_api::metric_engine_consts::MANIFEST_INFO_EXTENSION_KEY; use store_api::region_engine::{ BatchResponses, RegionEngine, RegionManifestInfo, RegionRole, RegionScannerRef, - RegionStatistic, SetRegionRoleStateResponse, SettableRegionRoleState, + RegionStatistic, SetRegionRoleStateResponse, SettableRegionRoleState, SyncManifestResponse, }; use store_api::region_request::{AffectedRows, RegionOpenRequest, RegionRequest}; use store_api::storage::{RegionId, ScanRequest, SequenceNumber}; @@ -224,6 +225,24 @@ impl MitoEngine { pub(crate) fn get_region(&self, id: RegionId) -> Option { self.inner.workers.get_region(id) } + + fn encode_manifest_info_to_extensions( + region_id: &RegionId, + manifest_info: RegionManifestInfo, + extensions: &mut HashMap>, + ) -> Result<()> { + let region_manifest_info = vec![(*region_id, manifest_info)]; + + extensions.insert( + MANIFEST_INFO_EXTENSION_KEY.to_string(), + RegionManifestInfo::encode_list(®ion_manifest_info).context(SerdeJsonSnafu)?, + ); + info!( + "Added manifest info: {:?} to extensions, region_id: {:?}", + region_manifest_info, region_id + ); + Ok(()) + } } /// Check whether the region edit is valid. Only adding files to region is considered valid now. @@ -496,7 +515,7 @@ impl EngineInner { &self, region_id: RegionId, manifest_info: RegionManifestInfo, - ) -> Result { + ) -> Result<(ManifestVersion, bool)> { ensure!(manifest_info.is_mito(), MitoManifestInfoSnafu); let manifest_version = manifest_info.data_manifest_version(); let (request, receiver) = @@ -557,11 +576,26 @@ impl RegionEngine for MitoEngine { .with_label_values(&[request.request_type()]) .start_timer(); - self.inner + let is_alter = matches!(request, RegionRequest::Alter(_)); + let mut response = self + .inner .handle_request(region_id, request) .await .map(RegionResponse::new) - .map_err(BoxedError::new) + .map_err(BoxedError::new)?; + + if is_alter { + if let Some(statistic) = self.region_statistic(region_id) { + Self::encode_manifest_info_to_extensions( + ®ion_id, + statistic.manifest, + &mut response.extensions, + ) + .map_err(BoxedError::new)?; + } + } + + Ok(response) } #[tracing::instrument(skip_all)] @@ -631,12 +665,14 @@ impl RegionEngine for MitoEngine { &self, region_id: RegionId, manifest_info: RegionManifestInfo, - ) -> Result<(), BoxedError> { - self.inner + ) -> Result { + let (_, synced) = self + .inner .sync_region(region_id, manifest_info) .await - .map_err(BoxedError::new) - .map(|_| ()) + .map_err(BoxedError::new)?; + + Ok(SyncManifestResponse::Mito { synced }) } fn role(&self, region_id: RegionId) -> Option { diff --git a/src/mito2/src/engine/catchup_test.rs b/src/mito2/src/engine/catchup_test.rs index a9de0d6008..8a24fecf3a 100644 --- a/src/mito2/src/engine/catchup_test.rs +++ b/src/mito2/src/engine/catchup_test.rs @@ -35,8 +35,8 @@ use crate::test_util::{ use crate::wal::EntryId; fn get_last_entry_id(resp: SetRegionRoleStateResponse) -> Option { - if let SetRegionRoleStateResponse::Success { last_entry_id } = resp { - last_entry_id + if let SetRegionRoleStateResponse::Success(success) = resp { + success.last_entry_id() } else { unreachable!(); } @@ -118,6 +118,7 @@ async fn test_catchup_with_last_entry_id(factory: Option) { RegionRequest::Catchup(RegionCatchupRequest { set_writable: false, entry_id: last_entry_id, + metadata_entry_id: None, location_id: None, }), ) @@ -150,6 +151,7 @@ async fn test_catchup_with_last_entry_id(factory: Option) { RegionRequest::Catchup(RegionCatchupRequest { set_writable: true, entry_id: last_entry_id, + metadata_entry_id: None, location_id: None, }), ) @@ -237,6 +239,7 @@ async fn test_catchup_with_incorrect_last_entry_id(factory: Option) { RegionRequest::Catchup(RegionCatchupRequest { set_writable: false, entry_id: None, + metadata_entry_id: None, location_id: None, }), ) @@ -353,6 +358,7 @@ async fn test_catchup_without_last_entry_id(factory: Option) { RegionRequest::Catchup(RegionCatchupRequest { set_writable: true, entry_id: None, + metadata_entry_id: None, location_id: None, }), ) @@ -442,6 +448,7 @@ async fn test_catchup_with_manifest_update(factory: Option) { RegionRequest::Catchup(RegionCatchupRequest { set_writable: false, entry_id: None, + metadata_entry_id: None, location_id: None, }), ) @@ -479,6 +486,7 @@ async fn test_catchup_with_manifest_update(factory: Option) { RegionRequest::Catchup(RegionCatchupRequest { set_writable: true, entry_id: None, + metadata_entry_id: None, location_id: None, }), ) @@ -501,6 +509,7 @@ async fn test_catchup_not_exist() { RegionRequest::Catchup(RegionCatchupRequest { set_writable: true, entry_id: None, + metadata_entry_id: None, location_id: None, }), ) diff --git a/src/mito2/src/engine/flush_test.rs b/src/mito2/src/engine/flush_test.rs index 25bba7e085..1d836da733 100644 --- a/src/mito2/src/engine/flush_test.rs +++ b/src/mito2/src/engine/flush_test.rs @@ -25,7 +25,7 @@ use common_wal::options::WAL_OPTIONS_KEY; use rstest::rstest; use rstest_reuse::{self, apply}; use store_api::region_engine::RegionEngine; -use store_api::region_request::RegionRequest; +use store_api::region_request::{RegionFlushRequest, RegionRequest}; use store_api::storage::{RegionId, ScanRequest}; use crate::config::MitoConfig; @@ -33,8 +33,8 @@ use crate::engine::listener::{FlushListener, StallListener}; use crate::test_util::{ build_rows, build_rows_for_key, flush_region, kafka_log_store_factory, multiple_log_store_factories, prepare_test_for_kafka_log_store, put_rows, - raft_engine_log_store_factory, reopen_region, rows_schema, CreateRequestBuilder, - LogStoreFactory, MockWriteBufferManager, TestEnv, + raft_engine_log_store_factory, reopen_region, rows_schema, single_kafka_log_store_factory, + CreateRequestBuilder, LogStoreFactory, MockWriteBufferManager, TestEnv, }; use crate::time_provider::TimeProvider; use crate::worker::MAX_INITIAL_CHECK_DELAY_SECS; @@ -544,3 +544,67 @@ async fn test_flush_workers() { +-------+---------+---------------------+"; assert_eq!(expected, batches.pretty_print().unwrap()); } + +#[apply(single_kafka_log_store_factory)] +async fn test_update_topic_latest_entry_id(factory: Option) { + common_telemetry::init_default_ut_logging(); + let Some(factory) = factory else { + return; + }; + let write_buffer_manager = Arc::new(MockWriteBufferManager::default()); + let listener = Arc::new(FlushListener::default()); + + let mut env = TestEnv::new().with_log_store_factory(factory.clone()); + let engine = env + .create_engine_with( + MitoConfig::default(), + Some(write_buffer_manager.clone()), + Some(listener.clone()), + ) + .await; + let region_id = RegionId::new(1, 1); + env.get_schema_metadata_manager() + .register_region_table_info( + region_id.table_id(), + "test_table", + "test_catalog", + "test_schema", + None, + env.get_kv_backend(), + ) + .await; + + let topic = prepare_test_for_kafka_log_store(&factory).await; + let request = CreateRequestBuilder::new() + .kafka_topic(topic.clone()) + .build(); + let column_schemas = rows_schema(&request); + engine + .handle_request(region_id, RegionRequest::Create(request.clone())) + .await + .unwrap(); + + let region = engine.get_region(region_id).unwrap(); + assert_eq!(region.topic_latest_entry_id.load(Ordering::Relaxed), 0); + + let rows = Rows { + schema: column_schemas.clone(), + rows: build_rows_for_key("a", 0, 2, 0), + }; + put_rows(&engine, region_id, rows.clone()).await; + + let request = RegionFlushRequest::default(); + engine + .handle_request(region_id, RegionRequest::Flush(request.clone())) + .await + .unwrap(); + // Wait until flush is finished. + listener.wait().await; + assert_eq!(region.topic_latest_entry_id.load(Ordering::Relaxed), 0); + + engine + .handle_request(region_id, RegionRequest::Flush(request.clone())) + .await + .unwrap(); + assert_eq!(region.topic_latest_entry_id.load(Ordering::Relaxed), 1); +} diff --git a/src/mito2/src/engine/set_role_state_test.rs b/src/mito2/src/engine/set_role_state_test.rs index 2a4cb9f9ca..93dbc69407 100644 --- a/src/mito2/src/engine/set_role_state_test.rs +++ b/src/mito2/src/engine/set_role_state_test.rs @@ -16,7 +16,8 @@ use api::v1::Rows; use common_error::ext::ErrorExt; use common_error::status_code::StatusCode; use store_api::region_engine::{ - RegionEngine, RegionRole, SetRegionRoleStateResponse, SettableRegionRoleState, + RegionEngine, RegionRole, SetRegionRoleStateResponse, SetRegionRoleStateSuccess, + SettableRegionRoleState, }; use store_api::region_request::{RegionPutRequest, RegionRequest}; use store_api::storage::RegionId; @@ -48,9 +49,7 @@ async fn test_set_role_state_gracefully() { .await .unwrap(); assert_eq!( - SetRegionRoleStateResponse::Success { - last_entry_id: Some(0) - }, + SetRegionRoleStateResponse::success(SetRegionRoleStateSuccess::mito(0)), result ); @@ -60,9 +59,7 @@ async fn test_set_role_state_gracefully() { .await .unwrap(); assert_eq!( - SetRegionRoleStateResponse::Success { - last_entry_id: Some(0) - }, + SetRegionRoleStateResponse::success(SetRegionRoleStateSuccess::mito(0)), result ); @@ -96,9 +93,7 @@ async fn test_set_role_state_gracefully() { .unwrap(); assert_eq!( - SetRegionRoleStateResponse::Success { - last_entry_id: Some(1) - }, + SetRegionRoleStateResponse::success(SetRegionRoleStateSuccess::mito(1)), result ); } @@ -144,9 +139,7 @@ async fn test_write_downgrading_region() { .await .unwrap(); assert_eq!( - SetRegionRoleStateResponse::Success { - last_entry_id: Some(1) - }, + SetRegionRoleStateResponse::success(SetRegionRoleStateSuccess::mito(1)), result ); diff --git a/src/mito2/src/error.rs b/src/mito2/src/error.rs index 9c9c78f07e..0ac04e8b3e 100644 --- a/src/mito2/src/error.rs +++ b/src/mito2/src/error.rs @@ -1021,6 +1021,17 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display( + "Failed to convert ConcreteDataType to ColumnDataType: {:?}", + data_type + ))] + ConvertDataType { + data_type: ConcreteDataType, + source: api::error::Error, + #[snafu(implicit)] + location: Location, + }, } pub type Result = std::result::Result; @@ -1172,6 +1183,7 @@ impl ErrorExt for Error { ManualCompactionOverride {} => StatusCode::Cancelled, IncompatibleWalProviderChange { .. } => StatusCode::InvalidArguments, + ConvertDataType { .. } => StatusCode::Internal, } } diff --git a/src/mito2/src/manifest/manager.rs b/src/mito2/src/manifest/manager.rs index d60f018b4e..2590d7ae6c 100644 --- a/src/mito2/src/manifest/manager.rs +++ b/src/mito2/src/manifest/manager.rs @@ -313,11 +313,12 @@ impl RegionManifestManager { } ); + let region_id = self.manifest.metadata.region_id; // Fetches manifests from the last version strictly. let mut manifests = self .store // Invariant: last_version < target_version. - .fetch_manifests_strict_from(last_version + 1, target_version + 1) + .fetch_manifests_strict_from(last_version + 1, target_version + 1, region_id) .await?; // Case 2: No manifests in range: [current_version+1, target_version+1) @@ -327,7 +328,7 @@ impl RegionManifestManager { // [Current Version]......[Target Version] // [Follower region] if manifests.is_empty() { - debug!( + info!( "Manifests are not strict from {}, region: {}, tries to install the last checkpoint", last_version, self.manifest.metadata.region_id ); @@ -341,7 +342,7 @@ impl RegionManifestManager { manifests = self .store // Invariant: last_version < target_version. - .fetch_manifests_strict_from(last_version + 1, target_version + 1) + .fetch_manifests_strict_from(last_version + 1, target_version + 1, region_id) .await?; } diff --git a/src/mito2/src/manifest/storage.rs b/src/mito2/src/manifest/storage.rs index c0ee01ba60..89e23e2cd4 100644 --- a/src/mito2/src/manifest/storage.rs +++ b/src/mito2/src/manifest/storage.rs @@ -29,6 +29,7 @@ use regex::Regex; use serde::{Deserialize, Serialize}; use snafu::{ensure, ResultExt}; use store_api::manifest::ManifestVersion; +use store_api::storage::RegionId; use tokio::sync::Semaphore; use crate::error::{ @@ -243,12 +244,17 @@ impl ManifestObjectStore { &self, start_version: ManifestVersion, end_version: ManifestVersion, + region_id: RegionId, ) -> Result)>> { let mut manifests = self.fetch_manifests(start_version, end_version).await?; let start_index = manifests.iter().position(|(v, _)| *v == start_version); debug!( - "fetches manifests in range [{},{}), start_index: {:?}", - start_version, end_version, start_index + "Fetches manifests in range [{},{}), start_index: {:?}, region_id: {}, manifests: {:?}", + start_version, + end_version, + start_index, + region_id, + manifests.iter().map(|(v, _)| *v).collect::>() ); if let Some(start_index) = start_index { Ok(manifests.split_off(start_index)) diff --git a/src/mito2/src/memtable/time_series.rs b/src/mito2/src/memtable/time_series.rs index 82758a542b..44bce1ec74 100644 --- a/src/mito2/src/memtable/time_series.rs +++ b/src/mito2/src/memtable/time_series.rs @@ -161,18 +161,15 @@ impl TimeSeriesMemtable { let primary_key_encoded = self.row_codec.encode(kv.primary_keys())?; - let (series, series_allocated) = self.series_set.get_or_add_series(primary_key_encoded); - stats.key_bytes += series_allocated; + let (key_allocated, value_allocated) = + self.series_set.push_to_series(primary_key_encoded, &kv); + stats.key_bytes += key_allocated; + stats.value_bytes += value_allocated; // safety: timestamp of kv must be both present and a valid timestamp value. let ts = kv.timestamp().as_timestamp().unwrap().unwrap().value(); stats.min_ts = stats.min_ts.min(ts); stats.max_ts = stats.max_ts.max(ts); - - let mut guard = series.write().unwrap(); - let size = guard.push(kv.timestamp(), kv.sequence(), kv.op_type(), kv.fields()); - stats.value_bytes += size; - Ok(()) } } @@ -368,25 +365,46 @@ impl SeriesSet { } impl SeriesSet { - /// Returns the series for given primary key, or create a new series if not already exist, - /// along with the allocated memory footprint for primary keys. - fn get_or_add_series(&self, primary_key: Vec) -> (Arc>, usize) { + /// Push [KeyValue] to SeriesSet with given primary key and return key/value allocated memory size. + fn push_to_series(&self, primary_key: Vec, kv: &KeyValue) -> (usize, usize) { if let Some(series) = self.series.read().unwrap().get(&primary_key) { - return (series.clone(), 0); + let value_allocated = series.write().unwrap().push( + kv.timestamp(), + kv.sequence(), + kv.op_type(), + kv.fields(), + ); + return (0, value_allocated); }; - let s = Arc::new(RwLock::new(Series::new(&self.region_metadata))); + let mut indices = self.series.write().unwrap(); match indices.entry(primary_key) { Entry::Vacant(v) => { let key_len = v.key().len(); - v.insert(s.clone()); - (s, key_len) + let mut series = Series::new(&self.region_metadata); + let value_allocated = + series.push(kv.timestamp(), kv.sequence(), kv.op_type(), kv.fields()); + v.insert(Arc::new(RwLock::new(series))); + (key_len, value_allocated) } // safety: series must exist at given index. - Entry::Occupied(v) => (v.get().clone(), 0), + Entry::Occupied(v) => { + let value_allocated = v.get().write().unwrap().push( + kv.timestamp(), + kv.sequence(), + kv.op_type(), + kv.fields(), + ); + (0, value_allocated) + } } } + #[cfg(test)] + fn get_series(&self, primary_key: &[u8]) -> Option>> { + self.series.read().unwrap().get(primary_key).cloned() + } + /// Iterates all series in [SeriesSet]. fn iter_series( &self, @@ -948,7 +966,7 @@ mod tests { use api::helper::ColumnDataTypeWrapper; use api::v1::value::ValueData; - use api::v1::{Row, Rows, SemanticType}; + use api::v1::{Mutation, Row, Rows, SemanticType}; use common_time::Timestamp; use datatypes::prelude::{ConcreteDataType, ScalarVector}; use datatypes::schema::ColumnSchema; @@ -959,6 +977,7 @@ mod tests { use super::*; use crate::row_converter::SortField; + use crate::test_util::column_metadata_to_column_schema; fn schema_for_test() -> RegionMetadataRef { let mut builder = RegionMetadataBuilder::new(RegionId::new(123, 456)); @@ -1242,18 +1261,54 @@ mod tests { let mut handles = Vec::with_capacity(concurrency); for i in 0..concurrency { let set = set.clone(); + let schema = schema.clone(); + let column_schemas = schema + .column_metadatas + .iter() + .map(column_metadata_to_column_schema) + .collect::>(); let handle = std::thread::spawn(move || { for j in i * 100..(i + 1) * 100 { let pk = j % pk_num; let primary_key = format!("pk-{}", pk).as_bytes().to_vec(); - let (series, _) = set.get_or_add_series(primary_key); - let mut guard = series.write().unwrap(); - guard.push( - ts_value_ref(j as i64), - j as u64, - OpType::Put, - field_value_ref(j as i64, j as f64), - ); + + let kvs = KeyValues::new( + &schema, + Mutation { + op_type: OpType::Put as i32, + sequence: j as u64, + rows: Some(Rows { + schema: column_schemas.clone(), + rows: vec![Row { + values: vec![ + api::v1::Value { + value_data: Some(ValueData::StringValue(format!( + "{}", + j + ))), + }, + api::v1::Value { + value_data: Some(ValueData::I64Value(j as i64)), + }, + api::v1::Value { + value_data: Some(ValueData::TimestampMillisecondValue( + j as i64, + )), + }, + api::v1::Value { + value_data: Some(ValueData::I64Value(j as i64)), + }, + api::v1::Value { + value_data: Some(ValueData::F64Value(j as f64)), + }, + ], + }], + }), + write_hint: None, + }, + ) + .unwrap(); + set.push_to_series(primary_key, &kvs.iter().next().unwrap()); } }); handles.push(handle); @@ -1269,7 +1324,7 @@ mod tests { for i in 0..pk_num { let pk = format!("pk-{}", i).as_bytes().to_vec(); - let (series, _) = set.get_or_add_series(pk); + let series = set.get_series(&pk).unwrap(); let mut guard = series.write().unwrap(); let values = guard.compact(&schema).unwrap(); timestamps.extend(values.sequence.iter_data().map(|v| v.unwrap() as i64)); @@ -1385,4 +1440,95 @@ mod tests { } assert_eq!((0..100i64).collect::>(), v0_all); } + + #[test] + fn test_memtable_concurrent_write_read() { + common_telemetry::init_default_ut_logging(); + let schema = schema_for_test(); + let memtable = Arc::new(TimeSeriesMemtable::new( + schema.clone(), + 42, + None, + true, + MergeMode::LastRow, + )); + + // Number of writer threads + let num_writers = 10; + // Number of reader threads + let num_readers = 5; + // Number of series per writer + let series_per_writer = 100; + // Number of rows per series + let rows_per_series = 10; + // Total number of series + let total_series = num_writers * series_per_writer; + + // Create a barrier to synchronize the start of all threads + let barrier = Arc::new(std::sync::Barrier::new(num_writers + num_readers + 1)); + + // Spawn writer threads + let mut writer_handles = Vec::with_capacity(num_writers); + for writer_id in 0..num_writers { + let memtable = memtable.clone(); + let schema = schema.clone(); + let barrier = barrier.clone(); + + let handle = std::thread::spawn(move || { + // Wait for all threads to be ready + barrier.wait(); + + // Create and write series + for series_id in 0..series_per_writer { + let series_key = format!("writer-{}-series-{}", writer_id, series_id); + let kvs = + build_key_values(&schema, series_key, series_id as i64, rows_per_series); + memtable.write(&kvs).unwrap(); + } + }); + + writer_handles.push(handle); + } + + // Spawn reader threads + let mut reader_handles = Vec::with_capacity(num_readers); + for _ in 0..num_readers { + let memtable = memtable.clone(); + let barrier = barrier.clone(); + + let handle = std::thread::spawn(move || { + barrier.wait(); + + for _ in 0..10 { + let iter = memtable.iter(None, None, None).unwrap(); + for batch_result in iter { + let _ = batch_result.unwrap(); + } + } + }); + + reader_handles.push(handle); + } + + barrier.wait(); + + for handle in writer_handles { + handle.join().unwrap(); + } + for handle in reader_handles { + handle.join().unwrap(); + } + + let iter = memtable.iter(None, None, None).unwrap(); + let mut series_count = 0; + let mut row_count = 0; + + for batch_result in iter { + let batch = batch_result.unwrap(); + series_count += 1; + row_count += batch.num_rows(); + } + assert_eq!(total_series, series_count); + assert_eq!(total_series * rows_per_series, row_count); + } } diff --git a/src/mito2/src/read/projection.rs b/src/mito2/src/read/projection.rs index 883f554066..6d6de78a74 100644 --- a/src/mito2/src/read/projection.rs +++ b/src/mito2/src/read/projection.rs @@ -363,9 +363,9 @@ mod tests { builder .push_field_array( *column_id, - Arc::new(Int64Array::from_iter_values( - std::iter::repeat(*field).take(num_rows), - )), + Arc::new(Int64Array::from_iter_values(std::iter::repeat_n( + *field, num_rows, + ))), ) .unwrap(); } diff --git a/src/mito2/src/read/scan_region.rs b/src/mito2/src/read/scan_region.rs index 855455453f..8b9efb9ae8 100644 --- a/src/mito2/src/read/scan_region.rs +++ b/src/mito2/src/read/scan_region.rs @@ -502,7 +502,7 @@ impl ScanRegion { let file_cache = self.cache_strategy.write_cache().map(|w| w.file_cache()); let puffin_metadata_cache = self.cache_strategy.puffin_metadata_cache().cloned(); - + let bloom_filter_index_cache = self.cache_strategy.bloom_filter_index_cache().cloned(); FulltextIndexApplierBuilder::new( self.access_layer.region_dir().to_string(), self.version.metadata.region_id, @@ -512,6 +512,7 @@ impl ScanRegion { ) .with_file_cache(file_cache) .with_puffin_metadata_cache(puffin_metadata_cache) + .with_bloom_filter_cache(bloom_filter_index_cache) .build(&self.request.filters) .inspect_err(|err| warn!(err; "Failed to build fulltext index applier")) .ok() diff --git a/src/mito2/src/read/scan_util.rs b/src/mito2/src/read/scan_util.rs index 4a211f7117..36899796b0 100644 --- a/src/mito2/src/read/scan_util.rs +++ b/src/mito2/src/read/scan_util.rs @@ -505,6 +505,7 @@ pub(crate) fn scan_file_ranges( // Reports metrics. reader_metrics.observe_rows(read_type); + reader_metrics.filter_metrics.observe(); part_metrics.merge_reader_metrics(&reader_metrics); } } diff --git a/src/mito2/src/region.rs b/src/mito2/src/region.rs index 191300a076..4ad3ea8e2d 100644 --- a/src/mito2/src/region.rs +++ b/src/mito2/src/region.rs @@ -119,6 +119,16 @@ pub(crate) struct MitoRegion { last_compaction_millis: AtomicI64, /// Provider to get current time. time_provider: TimeProviderRef, + /// The topic's latest entry id since the region's last flushing. + /// **Only used for remote WAL pruning.** + /// + /// The value will be updated to the high watermark of the topic + /// if region receives a flush request or schedules a periodic flush task + /// and the region's memtable is empty. + /// + /// There are no WAL entries in range [flushed_entry_id, topic_latest_entry_id] for current region, + /// which means these WAL entries maybe able to be pruned up to `topic_latest_entry_id`. + pub(crate) topic_latest_entry_id: AtomicU64, /// Memtable builder for the region. pub(crate) memtable_builder: MemtableBuilderRef, /// manifest stats @@ -287,12 +297,14 @@ impl MitoRegion { let sst_usage = version.ssts.sst_usage(); let index_usage = version.ssts.index_usage(); + let flushed_entry_id = version.flushed_entry_id; let wal_usage = self.estimated_wal_usage(memtable_usage); let manifest_usage = self.stats.total_manifest_size(); let num_rows = version.ssts.num_rows() + version.memtables.num_rows(); let manifest_version = self.stats.manifest_version(); - let flushed_entry_id = version.flushed_entry_id; + + let topic_latest_entry_id = self.topic_latest_entry_id.load(Ordering::Relaxed); RegionStatistic { num_rows, @@ -305,6 +317,8 @@ impl MitoRegion { manifest_version, flushed_entry_id, }, + data_topic_latest_entry_id: topic_latest_entry_id, + metadata_topic_latest_entry_id: topic_latest_entry_id, } } diff --git a/src/mito2/src/region/opener.rs b/src/mito2/src/region/opener.rs index b7c4803b54..5a1bf86c18 100644 --- a/src/mito2/src/region/opener.rs +++ b/src/mito2/src/region/opener.rs @@ -274,6 +274,7 @@ impl RegionOpener { last_flush_millis: AtomicI64::new(now), last_compaction_millis: AtomicI64::new(now), time_provider: self.time_provider.clone(), + topic_latest_entry_id: AtomicU64::new(0), memtable_builder, stats: self.stats, }) @@ -452,6 +453,7 @@ impl RegionOpener { last_flush_millis: AtomicI64::new(now), last_compaction_millis: AtomicI64::new(now), time_provider: self.time_provider.clone(), + topic_latest_entry_id: AtomicU64::new(0), memtable_builder, stats: self.stats.clone(), }; diff --git a/src/mito2/src/request.rs b/src/mito2/src/request.rs index 18ef260abe..5331ba6fdc 100644 --- a/src/mito2/src/request.rs +++ b/src/mito2/src/request.rs @@ -35,9 +35,9 @@ use store_api::manifest::ManifestVersion; use store_api::metadata::{ColumnMetadata, RegionMetadata, RegionMetadataRef}; use store_api::region_engine::{SetRegionRoleStateResponse, SettableRegionRoleState}; use store_api::region_request::{ - AffectedRows, RegionAlterRequest, RegionCatchupRequest, RegionCloseRequest, - RegionCompactRequest, RegionCreateRequest, RegionFlushRequest, RegionOpenRequest, - RegionRequest, RegionTruncateRequest, + AffectedRows, RegionAlterRequest, RegionBulkInsertsRequest, RegionCatchupRequest, + RegionCloseRequest, RegionCompactRequest, RegionCreateRequest, RegionFlushRequest, + RegionOpenRequest, RegionRequest, RegionTruncateRequest, }; use store_api::storage::{RegionId, SequenceNumber}; use tokio::sync::oneshot::{self, Receiver, Sender}; @@ -569,6 +569,13 @@ pub(crate) enum WorkerRequest { /// Keep the manifest of a region up to date. SyncRegion(RegionSyncRequest), + + /// Bulk inserts request and region metadata. + BulkInserts { + metadata: Option, + request: RegionBulkInsertsRequest, + sender: OptionOutputTx, + }, } impl WorkerRequest { @@ -668,6 +675,11 @@ impl WorkerRequest { sender: sender.into(), request: DdlRequest::Catchup(v), }), + RegionRequest::BulkInserts(region_bulk_inserts_request) => WorkerRequest::BulkInserts { + metadata: region_metadata, + sender: sender.into(), + request: region_bulk_inserts_request, + }, }; Ok((worker_request, receiver)) @@ -692,7 +704,7 @@ impl WorkerRequest { pub(crate) fn new_sync_region_request( region_id: RegionId, manifest_version: ManifestVersion, - ) -> (WorkerRequest, Receiver>) { + ) -> (WorkerRequest, Receiver>) { let (sender, receiver) = oneshot::channel(); ( WorkerRequest::SyncRegion(RegionSyncRequest { @@ -892,7 +904,8 @@ pub(crate) struct RegionEditResult { pub(crate) struct RegionSyncRequest { pub(crate) region_id: RegionId, pub(crate) manifest_version: ManifestVersion, - pub(crate) sender: Sender>, + /// Returns the latest manifest version and a boolean indicating whether new maniefst is installed. + pub(crate) sender: Sender>, } #[cfg(test)] diff --git a/src/mito2/src/sst/index/bloom_filter/applier.rs b/src/mito2/src/sst/index/bloom_filter/applier.rs index afd5cc16cd..fac5db5405 100644 --- a/src/mito2/src/sst/index/bloom_filter/applier.rs +++ b/src/mito2/src/sst/index/bloom_filter/applier.rs @@ -31,7 +31,7 @@ use store_api::storage::{ColumnId, RegionId}; use crate::access_layer::{RegionFilePathFactory, WriteCachePathProvider}; use crate::cache::file_cache::{FileCacheRef, FileType, IndexKey}; use crate::cache::index::bloom_filter_index::{ - BloomFilterIndexCacheRef, CachedBloomFilterIndexBlobReader, + BloomFilterIndexCacheRef, CachedBloomFilterIndexBlobReader, Tag, }; use crate::error::{ ApplyBloomFilterIndexSnafu, Error, MetadataSnafu, PuffinBuildReaderSnafu, PuffinReadBlobSnafu, @@ -165,6 +165,7 @@ impl BloomFilterIndexApplier { let reader = CachedBloomFilterIndexBlobReader::new( file_id, *column_id, + Tag::Skipping, blob_size, BloomFilterReaderImpl::new(blob), bloom_filter_cache.clone(), @@ -308,13 +309,13 @@ impl BloomFilterIndexApplier { ) -> std::result::Result<(), index::bloom_filter::error::Error> { let mut applier = BloomFilterApplier::new(Box::new(reader)).await?; - for (_, output) in output.iter_mut() { + for (_, row_group_output) in output.iter_mut() { // All rows are filtered out, skip the search - if output.is_empty() { + if row_group_output.is_empty() { continue; } - *output = applier.search(predicates, output).await?; + *row_group_output = applier.search(predicates, row_group_output).await?; } Ok(()) diff --git a/src/mito2/src/sst/index/bloom_filter/creator.rs b/src/mito2/src/sst/index/bloom_filter/creator.rs index 5c00b1a19c..53821c7cf2 100644 --- a/src/mito2/src/sst/index/bloom_filter/creator.rs +++ b/src/mito2/src/sst/index/bloom_filter/creator.rs @@ -346,7 +346,6 @@ impl BloomFilterIndexer { #[cfg(test)] pub(crate) mod tests { - use std::iter; use api::v1::SemanticType; use datatypes::data_type::ConcreteDataType; @@ -461,15 +460,15 @@ pub(crate) mod tests { Batch::new( primary_key, - Arc::new(UInt64Vector::from_iter_values( - iter::repeat(0).take(num_rows), - )), - Arc::new(UInt64Vector::from_iter_values( - iter::repeat(0).take(num_rows), - )), - Arc::new(UInt8Vector::from_iter_values( - iter::repeat(1).take(num_rows), - )), + Arc::new(UInt64Vector::from_iter_values(std::iter::repeat_n( + 0, num_rows, + ))), + Arc::new(UInt64Vector::from_iter_values(std::iter::repeat_n( + 0, num_rows, + ))), + Arc::new(UInt8Vector::from_iter_values(std::iter::repeat_n( + 1, num_rows, + ))), vec![u64_field], ) .unwrap() diff --git a/src/mito2/src/sst/index/fulltext_index/applier.rs b/src/mito2/src/sst/index/fulltext_index/applier.rs index e463bd0ee8..063227a89f 100644 --- a/src/mito2/src/sst/index/fulltext_index/applier.rs +++ b/src/mito2/src/sst/index/fulltext_index/applier.rs @@ -12,24 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeSet, HashMap}; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::iter; +use std::ops::Range; use std::sync::Arc; +use common_base::range_read::RangeReader; use common_telemetry::warn; +use index::bloom_filter::applier::{BloomFilterApplier, InListPredicate}; +use index::bloom_filter::reader::BloomFilterReaderImpl; use index::fulltext_index::search::{FulltextIndexSearcher, RowId, TantivyFulltextIndexSearcher}; +use index::fulltext_index::Config; use object_store::ObjectStore; use puffin::puffin_manager::cache::PuffinMetadataCacheRef; -use puffin::puffin_manager::{BlobWithMetadata, DirGuard, PuffinManager, PuffinReader}; +use puffin::puffin_manager::{GuardWithMetadata, PuffinManager, PuffinReader}; use snafu::ResultExt; use store_api::storage::{ColumnId, RegionId}; use crate::access_layer::{RegionFilePathFactory, WriteCachePathProvider}; use crate::cache::file_cache::{FileCacheRef, FileType, IndexKey}; -use crate::error::{ApplyFulltextIndexSnafu, PuffinBuildReaderSnafu, PuffinReadBlobSnafu, Result}; +use crate::cache::index::bloom_filter_index::{ + BloomFilterIndexCacheRef, CachedBloomFilterIndexBlobReader, Tag, +}; +use crate::error::{ + ApplyBloomFilterIndexSnafu, ApplyFulltextIndexSnafu, MetadataSnafu, PuffinBuildReaderSnafu, + PuffinReadBlobSnafu, Result, +}; use crate::metrics::INDEX_APPLY_ELAPSED; use crate::sst::file::FileId; -use crate::sst::index::fulltext_index::applier::builder::FulltextRequest; -use crate::sst::index::fulltext_index::INDEX_BLOB_TYPE_TANTIVY; +use crate::sst::index::fulltext_index::applier::builder::{FulltextRequest, FulltextTerm}; +use crate::sst::index::fulltext_index::{INDEX_BLOB_TYPE_BLOOM, INDEX_BLOB_TYPE_TANTIVY}; use crate::sst::index::puffin_manager::{ PuffinManagerFactory, SstPuffinBlob, SstPuffinDir, SstPuffinReader, }; @@ -44,6 +56,9 @@ pub struct FulltextIndexApplier { /// The source of the index. index_source: IndexSource, + + /// Cache for bloom filter index. + bloom_filter_index_cache: Option, } pub type FulltextIndexApplierRef = Arc; @@ -62,6 +77,7 @@ impl FulltextIndexApplier { Self { requests, index_source, + bloom_filter_index_cache: None, } } @@ -81,24 +97,36 @@ impl FulltextIndexApplier { self } - /// Applies the queries to the fulltext index of the specified SST file. - pub async fn apply( + /// Sets the bloom filter cache. + pub fn with_bloom_filter_cache( + mut self, + bloom_filter_index_cache: Option, + ) -> Self { + self.bloom_filter_index_cache = bloom_filter_index_cache; + self + } +} + +impl FulltextIndexApplier { + /// Applies fine-grained fulltext index to the specified SST file. + /// Returns the row ids that match the queries. + pub async fn apply_fine( &self, file_id: FileId, file_size_hint: Option, ) -> Result>> { - let _timer = INDEX_APPLY_ELAPSED + let timer = INDEX_APPLY_ELAPSED .with_label_values(&[TYPE_FULLTEXT_INDEX]) .start_timer(); let mut row_ids: Option> = None; for (column_id, request) in &self.requests { - if request.queries.is_empty() { + if request.queries.is_empty() && request.terms.is_empty() { continue; } let Some(result) = self - .apply_one_column(file_size_hint, file_id, *column_id, request) + .apply_fine_one_column(file_size_hint, file_id, *column_id, request) .await? else { continue; @@ -117,10 +145,13 @@ impl FulltextIndexApplier { } } + if row_ids.is_none() { + timer.stop_and_discard(); + } Ok(row_ids) } - async fn apply_one_column( + async fn apply_fine_one_column( &self, file_size_hint: Option, file_id: FileId, @@ -133,15 +164,21 @@ impl FulltextIndexApplier { .dir(file_id, &blob_key, file_size_hint) .await?; - let path = match &dir { - Some(dir) => dir.path(), + let dir = match &dir { + Some(dir) => dir, None => { return Ok(None); } }; - let searcher = TantivyFulltextIndexSearcher::new(path).context(ApplyFulltextIndexSnafu)?; + let config = Config::from_blob_metadata(dir.metadata()).context(ApplyFulltextIndexSnafu)?; + let path = dir.path(); + + let searcher = + TantivyFulltextIndexSearcher::new(path, config).context(ApplyFulltextIndexSnafu)?; let mut row_ids: Option> = None; + + // 1. Apply queries for query in &request.queries { let result = searcher .search(&query.0) @@ -161,10 +198,214 @@ impl FulltextIndexApplier { } } + // 2. Apply terms + let query = request.terms_as_query(config.case_sensitive); + if !query.0.is_empty() { + let result = searcher + .search(&query.0) + .await + .context(ApplyFulltextIndexSnafu)?; + + if let Some(ids) = row_ids.as_mut() { + ids.retain(|id| result.contains(id)); + } else { + row_ids = Some(result); + } + } + Ok(row_ids) } } +impl FulltextIndexApplier { + /// Applies coarse-grained fulltext index to the specified SST file. + /// Returns (row group id -> ranges) that match the queries. + pub async fn apply_coarse( + &self, + file_id: FileId, + file_size_hint: Option, + row_groups: impl Iterator, + ) -> Result>)>>> { + let timer = INDEX_APPLY_ELAPSED + .with_label_values(&[TYPE_FULLTEXT_INDEX]) + .start_timer(); + + let (input, mut output) = Self::init_coarse_output(row_groups); + let mut applied = false; + + for (column_id, request) in &self.requests { + if request.terms.is_empty() { + // only apply terms + continue; + } + + applied |= self + .apply_coarse_one_column( + file_id, + file_size_hint, + *column_id, + &request.terms, + &mut output, + ) + .await?; + } + + if !applied { + timer.stop_and_discard(); + return Ok(None); + } + + Self::adjust_coarse_output(input, &mut output); + Ok(Some(output)) + } + + async fn apply_coarse_one_column( + &self, + file_id: FileId, + file_size_hint: Option, + column_id: ColumnId, + terms: &[FulltextTerm], + output: &mut [(usize, Vec>)], + ) -> Result { + let blob_key = format!("{INDEX_BLOB_TYPE_BLOOM}-{column_id}"); + let Some(reader) = self + .index_source + .blob(file_id, &blob_key, file_size_hint) + .await? + else { + return Ok(false); + }; + let config = + Config::from_blob_metadata(reader.metadata()).context(ApplyFulltextIndexSnafu)?; + + let predicates = Self::terms_to_predicates(terms, &config); + if predicates.is_empty() { + return Ok(false); + } + + let range_reader = reader.reader().await.context(PuffinBuildReaderSnafu)?; + let reader = if let Some(bloom_filter_cache) = &self.bloom_filter_index_cache { + let blob_size = range_reader + .metadata() + .await + .context(MetadataSnafu)? + .content_length; + let reader = CachedBloomFilterIndexBlobReader::new( + file_id, + column_id, + Tag::Fulltext, + blob_size, + BloomFilterReaderImpl::new(range_reader), + bloom_filter_cache.clone(), + ); + Box::new(reader) as _ + } else { + Box::new(BloomFilterReaderImpl::new(range_reader)) as _ + }; + + let mut applier = BloomFilterApplier::new(reader) + .await + .context(ApplyBloomFilterIndexSnafu)?; + for (_, row_group_output) in output.iter_mut() { + // All rows are filtered out, skip the search + if row_group_output.is_empty() { + continue; + } + + *row_group_output = applier + .search(&predicates, row_group_output) + .await + .context(ApplyBloomFilterIndexSnafu)?; + } + + Ok(true) + } + + /// Initializes the coarse output. Must call `adjust_coarse_output` after applying bloom filters. + /// + /// `row_groups` is a list of (row group length, whether to search). + /// + /// Returns (`input`, `output`): + /// * `input` is a list of (row group index to search, row group range based on start of the file). + /// * `output` is a list of (row group index to search, row group ranges based on start of the file). + #[allow(clippy::type_complexity)] + fn init_coarse_output( + row_groups: impl Iterator, + ) -> (Vec<(usize, Range)>, Vec<(usize, Vec>)>) { + // Calculates row groups' ranges based on start of the file. + let mut input = Vec::with_capacity(row_groups.size_hint().0); + let mut start = 0; + for (i, (len, to_search)) in row_groups.enumerate() { + let end = start + len; + if to_search { + input.push((i, start..end)); + } + start = end; + } + + // Initializes output with input ranges, but ranges are based on start of the file not the row group, + // so we need to adjust them later. + let output = input + .iter() + .map(|(i, range)| (*i, vec![range.clone()])) + .collect::>(); + + (input, output) + } + + /// Adjusts the coarse output. Makes the output ranges based on row group start. + fn adjust_coarse_output( + input: Vec<(usize, Range)>, + output: &mut Vec<(usize, Vec>)>, + ) { + // adjust ranges to be based on row group + for ((_, output), (_, input)) in output.iter_mut().zip(input) { + let start = input.start; + for range in output.iter_mut() { + range.start -= start; + range.end -= start; + } + } + output.retain(|(_, ranges)| !ranges.is_empty()); + } + + /// Converts terms to predicates. + /// + /// Split terms by non-alphanumeric characters and convert them to lowercase if case-insensitive. + /// Multiple terms are combined with AND semantics. + fn terms_to_predicates(terms: &[FulltextTerm], config: &Config) -> Vec { + let mut probes = HashSet::new(); + for term in terms { + if config.case_sensitive && term.col_lowered { + // lowercased terms are not indexed + continue; + } + + let ts = term + .term + .split(|c: char| !c.is_alphanumeric()) + .filter(|&t| !t.is_empty()) + .map(|t| { + if !config.case_sensitive { + t.to_lowercase() + } else { + t.to_string() + } + .into_bytes() + }); + + probes.extend(ts); + } + + probes + .into_iter() + .map(|p| InListPredicate { + list: iter::once(p).collect(), + }) + .collect::>() + } +} + /// The source of the index. struct IndexSource { region_dir: String, @@ -217,7 +458,7 @@ impl IndexSource { file_id: FileId, key: &str, file_size_hint: Option, - ) -> Result>> { + ) -> Result>> { let (reader, fallbacked) = self.ensure_reader(file_id, file_size_hint).await?; let res = reader.blob(key).await; match res { @@ -248,7 +489,7 @@ impl IndexSource { file_id: FileId, key: &str, file_size_hint: Option, - ) -> Result> { + ) -> Result>> { let (reader, fallbacked) = self.ensure_reader(file_id, file_size_hint).await?; let res = reader.dir(key).await; match res { diff --git a/src/mito2/src/sst/index/fulltext_index/applier/builder.rs b/src/mito2/src/sst/index/fulltext_index/applier/builder.rs index e5cb6cf765..3297275f26 100644 --- a/src/mito2/src/sst/index/fulltext_index/applier/builder.rs +++ b/src/mito2/src/sst/index/fulltext_index/applier/builder.rs @@ -23,6 +23,7 @@ use store_api::metadata::RegionMetadata; use store_api::storage::{ColumnId, ConcreteDataType, RegionId}; use crate::cache::file_cache::FileCacheRef; +use crate::cache::index::bloom_filter_index::BloomFilterIndexCacheRef; use crate::error::Result; use crate::sst::index::fulltext_index::applier::FulltextIndexApplier; use crate::sst::index::puffin_manager::PuffinManagerFactory; @@ -30,12 +31,37 @@ use crate::sst::index::puffin_manager::PuffinManagerFactory; /// A request for fulltext index. /// /// It contains all the queries and terms for a column. -#[derive(Default)] +#[derive(Default, Debug)] pub struct FulltextRequest { pub queries: Vec, pub terms: Vec, } +impl FulltextRequest { + /// Convert terms to a query string. + /// + /// For example, if the terms are ["foo", "bar"], the query string will be `r#"+"foo" +"bar""#`. + /// Need to escape the `"` in the term. + /// + /// `skip_lowercased` is used for the situation that lowercased terms are not indexed. + pub fn terms_as_query(&self, skip_lowercased: bool) -> FulltextQuery { + let mut query = String::new(); + for term in &self.terms { + if skip_lowercased && term.col_lowered { + continue; + } + // Escape the `"` in the term. + let escaped_term = term.term.replace("\"", "\\\""); + if query.is_empty() { + query = format!("+\"{escaped_term}\""); + } else { + query.push_str(&format!(" +\"{escaped_term}\"")); + } + } + FulltextQuery(query) + } +} + /// A query to be matched in fulltext index. /// /// `query` is the query to be matched, e.g. "+foo -bar" in `SELECT * FROM t WHERE matches(text, "+foo -bar")`. @@ -61,6 +87,7 @@ pub struct FulltextIndexApplierBuilder<'a> { metadata: &'a RegionMetadata, file_cache: Option, puffin_metadata_cache: Option, + bloom_filter_cache: Option, } impl<'a> FulltextIndexApplierBuilder<'a> { @@ -80,6 +107,7 @@ impl<'a> FulltextIndexApplierBuilder<'a> { metadata, file_cache: None, puffin_metadata_cache: None, + bloom_filter_cache: None, } } @@ -98,6 +126,15 @@ impl<'a> FulltextIndexApplierBuilder<'a> { self } + /// Sets the bloom filter cache to be used by the `FulltextIndexApplier`. + pub fn with_bloom_filter_cache( + mut self, + bloom_filter_cache: Option, + ) -> Self { + self.bloom_filter_cache = bloom_filter_cache; + self + } + /// Builds `SstIndexApplier` from the given expressions. pub fn build(self, exprs: &[Expr]) -> Result> { let mut requests = HashMap::new(); @@ -120,6 +157,7 @@ impl<'a> FulltextIndexApplierBuilder<'a> { ) .with_file_cache(self.file_cache) .with_puffin_metadata_cache(self.puffin_metadata_cache) + .with_bloom_filter_cache(self.bloom_filter_cache) })) } @@ -543,4 +581,92 @@ mod tests { } ); } + + #[test] + fn test_terms_as_query() { + // Test with empty terms + let request = FulltextRequest::default(); + assert_eq!(request.terms_as_query(false), FulltextQuery(String::new())); + assert_eq!(request.terms_as_query(true), FulltextQuery(String::new())); + + // Test with a single term (not lowercased) + let mut request = FulltextRequest::default(); + request.terms.push(FulltextTerm { + col_lowered: false, + term: "foo".to_string(), + }); + assert_eq!( + request.terms_as_query(false), + FulltextQuery("+\"foo\"".to_string()) + ); + assert_eq!( + request.terms_as_query(true), + FulltextQuery("+\"foo\"".to_string()) + ); + + // Test with a single lowercased term and skip_lowercased=true + let mut request = FulltextRequest::default(); + request.terms.push(FulltextTerm { + col_lowered: true, + term: "foo".to_string(), + }); + assert_eq!( + request.terms_as_query(false), + FulltextQuery("+\"foo\"".to_string()) + ); + assert_eq!(request.terms_as_query(true), FulltextQuery(String::new())); // Should skip lowercased term + + // Test with multiple terms, mix of lowercased and not + let mut request = FulltextRequest::default(); + request.terms.push(FulltextTerm { + col_lowered: false, + term: "foo".to_string(), + }); + request.terms.push(FulltextTerm { + col_lowered: true, + term: "bar".to_string(), + }); + assert_eq!( + request.terms_as_query(false), + FulltextQuery("+\"foo\" +\"bar\"".to_string()) + ); + assert_eq!( + request.terms_as_query(true), + FulltextQuery("+\"foo\"".to_string()) // Only the non-lowercased term + ); + + // Test with term containing quotes that need escaping + let mut request = FulltextRequest::default(); + request.terms.push(FulltextTerm { + col_lowered: false, + term: "foo\"bar".to_string(), + }); + assert_eq!( + request.terms_as_query(false), + FulltextQuery("+\"foo\\\"bar\"".to_string()) + ); + + // Test with a complex mix of terms + let mut request = FulltextRequest::default(); + request.terms.push(FulltextTerm { + col_lowered: false, + term: "foo".to_string(), + }); + request.terms.push(FulltextTerm { + col_lowered: true, + term: "bar\"quoted\"".to_string(), + }); + request.terms.push(FulltextTerm { + col_lowered: false, + term: "baz\\escape".to_string(), + }); + assert_eq!( + request.terms_as_query(false), + FulltextQuery("+\"foo\" +\"bar\\\"quoted\\\"\" +\"baz\\escape\"".to_string()) + ); + assert_eq!( + request.terms_as_query(true), + FulltextQuery("+\"foo\" +\"baz\\escape\"".to_string()) // Skips the lowercased term + ); + } } diff --git a/src/mito2/src/sst/index/fulltext_index/creator.rs b/src/mito2/src/sst/index/fulltext_index/creator.rs index b6eab05bfa..fc9aae9f42 100644 --- a/src/mito2/src/sst/index/fulltext_index/creator.rs +++ b/src/mito2/src/sst/index/fulltext_index/creator.rs @@ -360,6 +360,7 @@ mod tests { use std::sync::Arc; use api::v1::SemanticType; + use common_base::BitVec; use datatypes::data_type::DataType; use datatypes::schema::{ColumnSchema, FulltextAnalyzer, FulltextOptions}; use datatypes::vectors::{UInt64Vector, UInt8Vector}; @@ -376,7 +377,9 @@ mod tests { use crate::access_layer::RegionFilePathFactory; use crate::read::{Batch, BatchColumn}; use crate::sst::file::FileId; - use crate::sst::index::fulltext_index::applier::builder::{FulltextQuery, FulltextRequest}; + use crate::sst::index::fulltext_index::applier::builder::{ + FulltextQuery, FulltextRequest, FulltextTerm, + }; use crate::sst::index::fulltext_index::applier::FulltextIndexApplier; use crate::sst::index::puffin_manager::PuffinManagerFactory; @@ -388,7 +391,7 @@ mod tests { IntermediateManager::init_fs(path).await.unwrap() } - fn mock_region_metadata() -> RegionMetadataRef { + fn mock_region_metadata(backend: FulltextBackend) -> RegionMetadataRef { let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 2)); builder .push_column_metadata(ColumnMetadata { @@ -401,7 +404,7 @@ mod tests { enable: true, analyzer: FulltextAnalyzer::English, case_sensitive: true, - backend: FulltextBackend::Tantivy, + backend: backend.clone(), }) .unwrap(), semantic_type: SemanticType::Field, @@ -417,7 +420,7 @@ mod tests { enable: true, analyzer: FulltextAnalyzer::English, case_sensitive: false, - backend: FulltextBackend::Tantivy, + backend: backend.clone(), }) .unwrap(), semantic_type: SemanticType::Field, @@ -433,7 +436,7 @@ mod tests { enable: true, analyzer: FulltextAnalyzer::Chinese, case_sensitive: false, - backend: FulltextBackend::Tantivy, + backend: backend.clone(), }) .unwrap(), semantic_type: SemanticType::Field, @@ -486,12 +489,12 @@ mod tests { Arc::new(UInt64Vector::from_iter_values( (0..num_rows).map(|n| n as u64), )), - Arc::new(UInt64Vector::from_iter_values( - std::iter::repeat(0).take(num_rows), - )), - Arc::new(UInt8Vector::from_iter_values( - std::iter::repeat(1).take(num_rows), - )), + Arc::new(UInt64Vector::from_iter_values(std::iter::repeat_n( + 0, num_rows, + ))), + Arc::new(UInt8Vector::from_iter_values(std::iter::repeat_n( + 1, num_rows, + ))), vec![ BatchColumn { column_id: 1, @@ -510,19 +513,32 @@ mod tests { .unwrap() } - async fn build_applier_factory( + /// Applier factory that can handle both queries and terms. + /// + /// It builds a fulltext index with the given data rows, and returns a function + /// that can handle both queries and terms in a single request. + /// + /// The function takes two parameters: + /// - `queries`: A list of (ColumnId, query_string) pairs for fulltext queries + /// - `terms`: A list of (ColumnId, [(bool, String)]) for fulltext terms, where bool indicates if term is lowercased + async fn build_fulltext_applier_factory( prefix: &str, + backend: FulltextBackend, rows: &[( Option<&str>, // text_english_case_sensitive Option<&str>, // text_english_case_insensitive Option<&str>, // text_chinese )], - ) -> impl Fn(Vec<(ColumnId, &str)>) -> BoxFuture<'static, BTreeSet> { + ) -> impl Fn( + Vec<(ColumnId, &str)>, + Vec<(ColumnId, Vec<(bool, &str)>)>, + Option, + ) -> BoxFuture<'static, Option>> { let (d, factory) = PuffinManagerFactory::new_for_test_async(prefix).await; let region_dir = "region0".to_string(); let sst_file_id = FileId::random(); let object_store = mock_object_store(); - let region_metadata = mock_region_metadata(); + let region_metadata = mock_region_metadata(backend.clone()); let intm_mgr = new_intm_mgr(d.path().to_string_lossy()).await; let mut indexer = FulltextIndexer::new( @@ -531,7 +547,7 @@ mod tests { &intm_mgr, ®ion_metadata, true, - 8096, + 1, 1024, ) .await @@ -549,74 +565,722 @@ mod tests { let _ = indexer.finish(&mut writer).await.unwrap(); writer.finish().await.unwrap(); - move |queries| { + move |queries: Vec<(ColumnId, &str)>, + terms_requests: Vec<(ColumnId, Vec<(bool, &str)>)>, + coarse_mask: Option| { let _d = &d; - let applier = FulltextIndexApplier::new( - region_dir.clone(), - region_metadata.region_id, - object_store.clone(), - queries + let region_dir = region_dir.clone(); + let object_store = object_store.clone(); + let factory = factory.clone(); + + let mut requests: HashMap = HashMap::new(); + + // Add queries + for (column_id, query) in queries { + requests + .entry(column_id) + .or_default() + .queries + .push(FulltextQuery(query.to_string())); + } + + // Add terms + for (column_id, terms) in terms_requests { + let fulltext_terms = terms .into_iter() - .map(|(a, b)| { - ( - a, - FulltextRequest { - queries: vec![FulltextQuery(b.to_string())], - terms: vec![], - }, - ) + .map(|(col_lowered, term)| FulltextTerm { + col_lowered, + term: term.to_string(), }) - .collect(), - factory.clone(), + .collect::>(); + + requests + .entry(column_id) + .or_default() + .terms + .extend(fulltext_terms); + } + + let applier = FulltextIndexApplier::new( + region_dir, + region_metadata.region_id, + object_store, + requests, + factory, ); - async move { applier.apply(sst_file_id, None).await.unwrap().unwrap() }.boxed() + let backend = backend.clone(); + async move { + match backend { + FulltextBackend::Tantivy => { + applier.apply_fine(sst_file_id, None).await.unwrap() + } + FulltextBackend::Bloom => { + let coarse_mask = coarse_mask.unwrap_or_default(); + let row_groups = (0..coarse_mask.len()).map(|i| (1, coarse_mask[i])); + // row group id == row id + let resp = applier + .apply_coarse(sst_file_id, None, row_groups) + .await + .unwrap(); + resp.map(|r| { + r.into_iter() + .map(|(row_group_id, _)| row_group_id as RowId) + .collect() + }) + } + } + } + .boxed() } } - #[tokio::test] - async fn test_fulltext_index_basic() { - let applier_factory = build_applier_factory( - "test_fulltext_index_basic_", - &[ - (Some("hello"), None, Some("你好")), - (Some("world"), Some("world"), None), - (None, Some("World"), Some("世界")), - ( - Some("Hello, World"), - Some("Hello, World"), - Some("你好,世界"), - ), - ], - ) - .await; - - let row_ids = applier_factory(vec![(1, "hello")]).await; - assert_eq!(row_ids, vec![0].into_iter().collect()); - - let row_ids = applier_factory(vec![(1, "world")]).await; - assert_eq!(row_ids, vec![1].into_iter().collect()); - - let row_ids = applier_factory(vec![(2, "hello")]).await; - assert_eq!(row_ids, vec![3].into_iter().collect()); - - let row_ids = applier_factory(vec![(2, "world")]).await; - assert_eq!(row_ids, vec![1, 2, 3].into_iter().collect()); - - let row_ids = applier_factory(vec![(3, "你好")]).await; - assert_eq!(row_ids, vec![0, 3].into_iter().collect()); - - let row_ids = applier_factory(vec![(3, "世界")]).await; - assert_eq!(row_ids, vec![2, 3].into_iter().collect()); + fn rows(row_ids: impl IntoIterator) -> BTreeSet { + row_ids.into_iter().collect() } #[tokio::test] - async fn test_fulltext_index_multi_columns() { - let applier_factory = build_applier_factory( - "test_fulltext_index_multi_columns_", + async fn test_fulltext_index_basic_case_sensitive_tantivy() { + let applier_factory = build_fulltext_applier_factory( + "test_fulltext_index_basic_case_sensitive_tantivy_", + FulltextBackend::Tantivy, &[ - (Some("hello"), None, Some("你好")), - (Some("world"), Some("world"), None), + (Some("hello"), None, None), + (Some("world"), None, None), + (None, None, None), + (Some("Hello, World"), None, None), + ], + ) + .await; + + let row_ids = applier_factory(vec![(1, "hello")], vec![], None).await; + assert_eq!(row_ids, Some(rows([0]))); + + let row_ids = applier_factory(vec![(1, "world")], vec![], None).await; + assert_eq!(row_ids, Some(rows([1]))); + + let row_ids = applier_factory(vec![(1, "Hello")], vec![], None).await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory(vec![(1, "World")], vec![], None).await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory(vec![], vec![(1, vec![(false, "hello")])], None).await; + assert_eq!(row_ids, Some(rows([0]))); + + let row_ids = applier_factory(vec![], vec![(1, vec![(true, "hello")])], None).await; + assert_eq!(row_ids, None); + + let row_ids = applier_factory(vec![], vec![(1, vec![(false, "world")])], None).await; + assert_eq!(row_ids, Some(rows([1]))); + + let row_ids = applier_factory(vec![], vec![(1, vec![(true, "world")])], None).await; + assert_eq!(row_ids, None); + + let row_ids = applier_factory(vec![], vec![(1, vec![(false, "Hello")])], None).await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory(vec![], vec![(1, vec![(true, "Hello")])], None).await; + assert_eq!(row_ids, None); + + let row_ids = applier_factory(vec![], vec![(1, vec![(false, "Hello, World")])], None).await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory(vec![], vec![(1, vec![(true, "Hello, World")])], None).await; + assert_eq!(row_ids, None); + } + + #[tokio::test] + async fn test_fulltext_index_basic_case_sensitive_bloom() { + let applier_factory = build_fulltext_applier_factory( + "test_fulltext_index_basic_case_sensitive_bloom_", + FulltextBackend::Bloom, + &[ + (Some("hello"), None, None), + (Some("world"), None, None), + (None, None, None), + (Some("Hello, World"), None, None), + ], + ) + .await; + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "hello")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([0]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "hello")])], + Some(BitVec::from_slice(&[0b1110])), // row 0 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(true, "hello")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, None); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "world")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([1]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "world")])], + Some(BitVec::from_slice(&[0b1101])), // row 1 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(true, "world")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, None); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "Hello")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "Hello")])], + Some(BitVec::from_slice(&[0b0111])), // row 3 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(true, "Hello")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, None); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "Hello, World")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "Hello, World")])], + Some(BitVec::from_slice(&[0b0111])), // row 3 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(true, "Hello, World")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, None); + } + + #[tokio::test] + async fn test_fulltext_index_basic_case_insensitive_tantivy() { + let applier_factory = build_fulltext_applier_factory( + "test_fulltext_index_basic_case_insensitive_tantivy_", + FulltextBackend::Tantivy, + &[ + (None, Some("hello"), None), + (None, None, None), + (None, Some("world"), None), + (None, Some("Hello, World"), None), + ], + ) + .await; + + let row_ids = applier_factory(vec![(2, "hello")], vec![], None).await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory(vec![(2, "world")], vec![], None).await; + assert_eq!(row_ids, Some(rows([2, 3]))); + + let row_ids = applier_factory(vec![(2, "Hello")], vec![], None).await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory(vec![(2, "World")], vec![], None).await; + assert_eq!(row_ids, Some(rows([2, 3]))); + + let row_ids = applier_factory(vec![], vec![(2, vec![(false, "hello")])], None).await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory(vec![], vec![(2, vec![(true, "hello")])], None).await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory(vec![], vec![(2, vec![(false, "world")])], None).await; + assert_eq!(row_ids, Some(rows([2, 3]))); + + let row_ids = applier_factory(vec![], vec![(2, vec![(true, "world")])], None).await; + assert_eq!(row_ids, Some(rows([2, 3]))); + + let row_ids = applier_factory(vec![], vec![(2, vec![(false, "Hello")])], None).await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory(vec![], vec![(2, vec![(true, "Hello")])], None).await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory(vec![], vec![(2, vec![(false, "World")])], None).await; + assert_eq!(row_ids, Some(rows([2, 3]))); + + let row_ids = applier_factory(vec![], vec![(2, vec![(true, "World")])], None).await; + assert_eq!(row_ids, Some(rows([2, 3]))); + } + + #[tokio::test] + async fn test_fulltext_index_basic_case_insensitive_bloom() { + let applier_factory = build_fulltext_applier_factory( + "test_fulltext_index_basic_case_insensitive_bloom_", + FulltextBackend::Bloom, + &[ + (None, Some("hello"), None), + (None, None, None), + (None, Some("world"), None), + (None, Some("Hello, World"), None), + ], + ) + .await; + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(false, "hello")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(false, "hello")])], + Some(BitVec::from_slice(&[0b1110])), // row 0 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(true, "hello")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(true, "hello")])], + Some(BitVec::from_slice(&[0b1110])), // row 0 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(false, "world")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([2, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(false, "world")])], + Some(BitVec::from_slice(&[0b1011])), // row 2 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(true, "world")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([2, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(true, "world")])], + Some(BitVec::from_slice(&[0b1011])), // row 2 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(false, "Hello")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(false, "Hello")])], + Some(BitVec::from_slice(&[0b0111])), // row 3 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([0]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(true, "Hello")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(true, "Hello")])], + Some(BitVec::from_slice(&[0b1110])), // row 0 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(false, "World")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([2, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(false, "World")])], + Some(BitVec::from_slice(&[0b0111])), // row 3 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([2]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(true, "World")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([2, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(true, "World")])], + Some(BitVec::from_slice(&[0b1011])), // row 2 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + } + + #[tokio::test] + async fn test_fulltext_index_basic_chinese_tantivy() { + let applier_factory = build_fulltext_applier_factory( + "test_fulltext_index_basic_chinese_tantivy_", + FulltextBackend::Tantivy, + &[ + (None, None, Some("你好")), + (None, None, None), + (None, None, Some("世界")), + (None, None, Some("你好,世界")), + ], + ) + .await; + + let row_ids = applier_factory(vec![(3, "你好")], vec![], None).await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory(vec![(3, "世界")], vec![], None).await; + assert_eq!(row_ids, Some(rows([2, 3]))); + + let row_ids = applier_factory(vec![], vec![(3, vec![(false, "你好")])], None).await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory(vec![], vec![(3, vec![(false, "世界")])], None).await; + assert_eq!(row_ids, Some(rows([2, 3]))); + } + + #[tokio::test] + async fn test_fulltext_index_basic_chinese_bloom() { + let applier_factory = build_fulltext_applier_factory( + "test_fulltext_index_basic_chinese_bloom_", + FulltextBackend::Bloom, + &[ + (None, None, Some("你好")), + (None, None, None), + (None, None, Some("世界")), + (None, None, Some("你好,世界")), + ], + ) + .await; + + let row_ids = applier_factory( + vec![], + vec![(3, vec![(false, "你好")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(3, vec![(false, "你好")])], + Some(BitVec::from_slice(&[0b1110])), // row 0 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(3, vec![(false, "世界")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([2, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(3, vec![(false, "世界")])], + Some(BitVec::from_slice(&[0b1011])), // row 2 is filtered out + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + } + + #[tokio::test] + async fn test_fulltext_index_multi_terms_case_sensitive_tantivy() { + let applier_factory = build_fulltext_applier_factory( + "test_fulltext_index_multi_terms_case_sensitive_tantivy_", + FulltextBackend::Tantivy, + &[ + (Some("Hello"), None, None), + (Some("World"), None, None), + (None, None, None), + (Some("Hello, World"), None, None), + ], + ) + .await; + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "hello"), (false, "world")])], + None, + ) + .await; + assert_eq!(row_ids, Some(rows([]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "Hello"), (false, "World")])], + None, + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(true, "Hello"), (false, "World")])], + None, + ) + .await; + assert_eq!(row_ids, Some(rows([1, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "Hello"), (true, "World")])], + None, + ) + .await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(true, "Hello"), (true, "World")])], + None, + ) + .await; + assert_eq!(row_ids, None); + } + + #[tokio::test] + async fn test_fulltext_index_multi_terms_case_sensitive_bloom() { + let applier_factory = build_fulltext_applier_factory( + "test_fulltext_index_multi_terms_case_sensitive_bloom_", + FulltextBackend::Bloom, + &[ + (Some("Hello"), None, None), + (Some("World"), None, None), + (None, None, None), + (Some("Hello, World"), None, None), + ], + ) + .await; + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "hello"), (false, "world")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "Hello"), (false, "World")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(true, "Hello"), (false, "World")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([1, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "Hello"), (true, "World")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([0, 3]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(true, "Hello"), (true, "World")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, None); + } + + #[tokio::test] + async fn test_fulltext_index_multi_terms_case_insensitive_tantivy() { + let applier_factory = build_fulltext_applier_factory( + "test_fulltext_index_multi_terms_case_insensitive_tantivy_", + FulltextBackend::Tantivy, + &[ + (None, Some("hello"), None), + (None, None, None), + (None, Some("world"), None), + (None, Some("Hello, World"), None), + ], + ) + .await; + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(false, "hello"), (false, "world")])], + None, + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(true, "hello"), (false, "world")])], + None, + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(false, "hello"), (true, "world")])], + None, + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(true, "hello"), (true, "world")])], + None, + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + } + + #[tokio::test] + async fn test_fulltext_index_multi_terms_case_insensitive_bloom() { + let applier_factory = build_fulltext_applier_factory( + "test_fulltext_index_multi_terms_case_insensitive_bloom_", + FulltextBackend::Bloom, + &[ + (None, Some("hello"), None), + (None, None, None), + (None, Some("world"), None), + (None, Some("Hello, World"), None), + ], + ) + .await; + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(false, "hello"), (false, "world")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(true, "hello"), (false, "world")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(false, "hello"), (true, "world")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(2, vec![(true, "hello"), (true, "world")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + } + + #[tokio::test] + async fn test_fulltext_index_multi_columns_tantivy() { + let applier_factory = build_fulltext_applier_factory( + "test_fulltext_index_multi_columns_tantivy_", + FulltextBackend::Tantivy, + &[ + (Some("Hello"), None, Some("你好")), + (Some("World"), Some("world"), None), (None, Some("World"), Some("世界")), ( Some("Hello, World"), @@ -627,13 +1291,55 @@ mod tests { ) .await; - let row_ids = applier_factory(vec![(1, "hello"), (3, "你好")]).await; - assert_eq!(row_ids, vec![0].into_iter().collect()); + let row_ids = applier_factory( + vec![(1, "Hello"), (3, "你好")], + vec![(2, vec![(false, "world")])], + None, + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); - let row_ids = applier_factory(vec![(1, "world"), (3, "世界")]).await; - assert_eq!(row_ids, vec![].into_iter().collect()); + let row_ids = + applier_factory(vec![(2, "World")], vec![(1, vec![(false, "World")])], None).await; + assert_eq!(row_ids, Some(rows([1, 3]))); + } - let row_ids = applier_factory(vec![(2, "world"), (3, "世界")]).await; - assert_eq!(row_ids, vec![2, 3].into_iter().collect()); + #[tokio::test] + async fn test_fulltext_index_multi_columns_bloom() { + let applier_factory = build_fulltext_applier_factory( + "test_fulltext_index_multi_columns_bloom_", + FulltextBackend::Bloom, + &[ + (Some("Hello"), None, Some("你好")), + (Some("World"), Some("world"), None), + (None, Some("World"), Some("世界")), + ( + Some("Hello, World"), + Some("Hello, World"), + Some("你好,世界"), + ), + ], + ) + .await; + + let row_ids = applier_factory( + vec![], + vec![ + (1, vec![(false, "Hello")]), + (2, vec![(false, "world")]), + (3, vec![(false, "你好")]), + ], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([3]))); + + let row_ids = applier_factory( + vec![], + vec![(1, vec![(false, "World")]), (2, vec![(false, "World")])], + Some(BitVec::from_slice(&[0b1111])), + ) + .await; + assert_eq!(row_ids, Some(rows([1, 3]))); } } diff --git a/src/mito2/src/sst/index/inverted_index/creator.rs b/src/mito2/src/sst/index/inverted_index/creator.rs index 8991b72aec..6f44979c78 100644 --- a/src/mito2/src/sst/index/inverted_index/creator.rs +++ b/src/mito2/src/sst/index/inverted_index/creator.rs @@ -326,7 +326,6 @@ impl InvertedIndexer { #[cfg(test)] mod tests { use std::collections::BTreeSet; - use std::iter; use api::v1::SemanticType; use datafusion_expr::{binary_expr, col, lit, Expr as DfExpr, Operator}; @@ -424,15 +423,15 @@ mod tests { Batch::new( primary_key, - Arc::new(UInt64Vector::from_iter_values( - iter::repeat(0).take(num_rows), - )), - Arc::new(UInt64Vector::from_iter_values( - iter::repeat(0).take(num_rows), - )), - Arc::new(UInt8Vector::from_iter_values( - iter::repeat(1).take(num_rows), - )), + Arc::new(UInt64Vector::from_iter_values(std::iter::repeat_n( + 0, num_rows, + ))), + Arc::new(UInt64Vector::from_iter_values(std::iter::repeat_n( + 0, num_rows, + ))), + Arc::new(UInt8Vector::from_iter_values(std::iter::repeat_n( + 1, num_rows, + ))), vec![u64_field], ) .unwrap() diff --git a/src/mito2/src/sst/index/puffin_manager.rs b/src/mito2/src/sst/index/puffin_manager.rs index 9f288ecd16..cf4bb185a8 100644 --- a/src/mito2/src/sst/index/puffin_manager.rs +++ b/src/mito2/src/sst/index/puffin_manager.rs @@ -174,12 +174,13 @@ impl PuffinFileAccessor for ObjectStorePuffinFileAccessor { #[cfg(test)] mod tests { + use common_base::range_read::RangeReader; use common_test_util::temp_dir::create_temp_dir; use futures::io::Cursor; use object_store::services::Memory; use puffin::blob_metadata::CompressionCodec; - use puffin::puffin_manager::{DirGuard, PuffinManager, PuffinReader, PuffinWriter, PutOptions}; + use puffin::puffin_manager::{PuffinManager, PuffinReader, PuffinWriter, PutOptions}; use super::*; @@ -229,6 +230,7 @@ mod tests { PutOptions { compression: Some(CompressionCodec::Zstd), }, + Default::default(), ) .await .unwrap(); diff --git a/src/mito2/src/sst/parquet/file_range.rs b/src/mito2/src/sst/parquet/file_range.rs index e8241a453f..e8dbca04fb 100644 --- a/src/mito2/src/sst/parquet/file_range.rs +++ b/src/mito2/src/sst/parquet/file_range.rs @@ -36,7 +36,9 @@ use crate::read::Batch; use crate::row_converter::{CompositeValues, PrimaryKeyCodec}; use crate::sst::file::FileHandle; use crate::sst::parquet::format::ReadFormat; -use crate::sst::parquet::reader::{RowGroupReader, RowGroupReaderBuilder, SimpleFilterContext}; +use crate::sst::parquet::reader::{ + MaybeFilter, RowGroupReader, RowGroupReaderBuilder, SimpleFilterContext, +}; /// A range of a parquet SST. Now it is a row group. /// We can read different file ranges in parallel. @@ -255,8 +257,15 @@ impl RangeBase { // Run filter one by one and combine them result // TODO(ruihang): run primary key filter first. It may short circuit other filters - for filter in &self.filters { - let result = match filter.semantic_type() { + for filter_ctx in &self.filters { + let filter = match filter_ctx.filter() { + MaybeFilter::Filter(f) => f, + // Column matches. + MaybeFilter::Matched => continue, + // Column doesn't match, filter the entire batch. + MaybeFilter::Pruned => return Ok(None), + }; + let result = match filter_ctx.semantic_type() { SemanticType::Tag => { let pk_values = if let Some(pk_values) = input.pk_values() { pk_values @@ -270,21 +279,20 @@ impl RangeBase { let pk_index = self .read_format .metadata() - .primary_key_index(filter.column_id()) + .primary_key_index(filter_ctx.column_id()) .unwrap(); v[pk_index] .1 - .try_to_scalar_value(filter.data_type()) + .try_to_scalar_value(filter_ctx.data_type()) .context(FieldTypeMismatchSnafu)? } CompositeValues::Sparse(v) => { - let v = v.get_or_null(filter.column_id()); - v.try_to_scalar_value(filter.data_type()) + let v = v.get_or_null(filter_ctx.column_id()); + v.try_to_scalar_value(filter_ctx.data_type()) .context(FieldTypeMismatchSnafu)? } }; if filter - .filter() .evaluate_scalar(&pk_value) .context(FilterRecordBatchSnafu)? { @@ -295,18 +303,17 @@ impl RangeBase { } } SemanticType::Field => { - let Some(field_index) = self.read_format.field_index_by_id(filter.column_id()) + let Some(field_index) = + self.read_format.field_index_by_id(filter_ctx.column_id()) else { continue; }; let field_col = &input.fields()[field_index].data; filter - .filter() .evaluate_vector(field_col) .context(FilterRecordBatchSnafu)? } SemanticType::Timestamp => filter - .filter() .evaluate_vector(input.timestamps()) .context(FilterRecordBatchSnafu)?, }; diff --git a/src/mito2/src/sst/parquet/format.rs b/src/mito2/src/sst/parquet/format.rs index c90907f0eb..005e276bbd 100644 --- a/src/mito2/src/sst/parquet/format.rs +++ b/src/mito2/src/sst/parquet/format.rs @@ -755,7 +755,7 @@ mod tests { )); let mut keys = vec![]; for (index, num_rows) in pk_row_nums.iter().map(|v| v.1).enumerate() { - keys.extend(std::iter::repeat(index as u32).take(num_rows)); + keys.extend(std::iter::repeat_n(index as u32, num_rows)); } let keys = UInt32Array::from(keys); Arc::new(DictionaryArray::new(keys, values)) diff --git a/src/mito2/src/sst/parquet/reader.rs b/src/mito2/src/sst/parquet/reader.rs index 069e10344c..ffa5c5d003 100644 --- a/src/mito2/src/sst/parquet/reader.rs +++ b/src/mito2/src/sst/parquet/reader.rs @@ -34,7 +34,7 @@ use parquet::arrow::{parquet_to_arrow_field_levels, FieldLevels, ProjectionMask} use parquet::file::metadata::ParquetMetaData; use parquet::format::KeyValue; use snafu::{OptionExt, ResultExt}; -use store_api::metadata::{RegionMetadata, RegionMetadataRef}; +use store_api::metadata::{ColumnMetadata, RegionMetadata, RegionMetadataRef}; use store_api::storage::ColumnId; use table::predicate::Predicate; @@ -191,6 +191,7 @@ impl ParquetReaderBuilder { let file_path = self.file_handle.file_path(&self.file_dir); let file_size = self.file_handle.meta_ref().file_size; + // Loads parquet metadata of the file. let parquet_meta = self.read_parquet_metadata(&file_path, file_size).await?; // Decodes region metadata. @@ -369,6 +370,9 @@ impl ParquetReaderBuilder { self.prune_row_groups_by_bloom_filter(parquet_meta, &mut output, metrics) .await; + self.prune_row_groups_by_fulltext_bloom(parquet_meta, &mut output, metrics) + .await; + output } @@ -389,7 +393,7 @@ impl ParquetReaderBuilder { let file_size_hint = self.file_handle.meta_ref().index_file_size(); let apply_res = match index_applier - .apply(self.file_handle.file_id(), Some(file_size_hint)) + .apply_fine(self.file_handle.file_id(), Some(file_size_hint)) .await { Ok(Some(res)) => res, @@ -547,11 +551,17 @@ impl ParquetReaderBuilder { let row_groups = parquet_meta.row_groups(); let stats = RowGroupPruningStats::new(row_groups, read_format, self.expected_metadata.clone()); + let prune_schema = self + .expected_metadata + .as_ref() + .map(|meta| meta.schema.arrow_schema()) + .unwrap_or_else(|| region_meta.schema.arrow_schema()); + // Here we use the schema of the SST to build the physical expression. If the column // in the SST doesn't have the same column id as the column in the expected metadata, // we will get a None statistics for that column. let res = predicate - .prune_with_stats(&stats, region_meta.schema.arrow_schema()) + .prune_with_stats(&stats, prune_schema) .iter() .zip(0..parquet_meta.num_row_groups()) .filter_map(|(mask, row_group)| { @@ -631,6 +641,67 @@ impl ParquetReaderBuilder { true } + async fn prune_row_groups_by_fulltext_bloom( + &self, + parquet_meta: &ParquetMetaData, + output: &mut BTreeMap>, + metrics: &mut ReaderFilterMetrics, + ) -> bool { + let Some(index_applier) = &self.fulltext_index_applier else { + return false; + }; + + if !self.file_handle.meta_ref().fulltext_index_available() { + return false; + } + + let file_size_hint = self.file_handle.meta_ref().index_file_size(); + let apply_output = match index_applier + .apply_coarse( + self.file_handle.file_id(), + Some(file_size_hint), + parquet_meta + .row_groups() + .iter() + .enumerate() + .map(|(i, rg)| (rg.num_rows() as usize, output.contains_key(&i))), + ) + .await + { + Ok(Some(apply_output)) => apply_output, + Ok(None) => return false, + Err(err) => { + if cfg!(any(test, feature = "test")) { + panic!( + "Failed to apply fulltext index, region_id: {}, file_id: {}, err: {:?}", + self.file_handle.region_id(), + self.file_handle.file_id(), + err + ); + } else { + warn!( + err; "Failed to apply fulltext index, region_id: {}, file_id: {}", + self.file_handle.region_id(), self.file_handle.file_id() + ); + } + + return false; + } + }; + + Self::prune_row_groups_by_ranges( + parquet_meta, + apply_output + .into_iter() + .map(|(rg, ranges)| (rg, ranges.into_iter())), + output, + &mut metrics.rg_fulltext_filtered, + &mut metrics.rows_fulltext_filtered, + ); + + true + } + /// Prunes row groups by rows. The `rows_in_row_groups` is like a map from row group to /// a list of row ids to keep. fn prune_row_groups_by_rows( @@ -945,10 +1016,20 @@ impl ReaderState { } } -/// Context to evaluate the column filter. +/// The filter to evaluate or the prune result of the default value. +pub(crate) enum MaybeFilter { + /// The filter to evaluate. + Filter(SimpleFilterEvaluator), + /// The filter matches the default value. + Matched, + /// The filter is pruned. + Pruned, +} + +/// Context to evaluate the column filter for a parquet file. pub(crate) struct SimpleFilterContext { /// Filter to evaluate. - filter: SimpleFilterEvaluator, + filter: MaybeFilter, /// Id of the column to evaluate. column_id: ColumnId, /// Semantic type of the column. @@ -968,22 +1049,38 @@ impl SimpleFilterContext { expr: &Expr, ) -> Option { let filter = SimpleFilterEvaluator::try_new(expr)?; - let column_metadata = match expected_meta { + let (column_metadata, maybe_filter) = match expected_meta { Some(meta) => { // Gets the column metadata from the expected metadata. let column = meta.column_by_name(filter.column_name())?; // Checks if the column is present in the SST metadata. We still uses the // column from the expected metadata. - let sst_column = sst_meta.column_by_id(column.column_id)?; - debug_assert_eq!(column.semantic_type, sst_column.semantic_type); + match sst_meta.column_by_id(column.column_id) { + Some(sst_column) => { + debug_assert_eq!(column.semantic_type, sst_column.semantic_type); - column + (column, MaybeFilter::Filter(filter)) + } + None => { + // If the column is not present in the SST metadata, we evaluate the filter + // against the default value of the column. + // If we can't evaluate the filter, we return None. + if pruned_by_default(&filter, column)? { + (column, MaybeFilter::Pruned) + } else { + (column, MaybeFilter::Matched) + } + } + } + } + None => { + let column = sst_meta.column_by_name(filter.column_name())?; + (column, MaybeFilter::Filter(filter)) } - None => sst_meta.column_by_name(filter.column_name())?, }; Some(Self { - filter, + filter: maybe_filter, column_id: column_metadata.column_id, semantic_type: column_metadata.semantic_type, data_type: column_metadata.column_schema.data_type.clone(), @@ -991,7 +1088,7 @@ impl SimpleFilterContext { } /// Returns the filter to evaluate. - pub(crate) fn filter(&self) -> &SimpleFilterEvaluator { + pub(crate) fn filter(&self) -> &MaybeFilter { &self.filter } @@ -1011,6 +1108,17 @@ impl SimpleFilterContext { } } +/// Prune a column by its default value. +/// Returns false if we can't create the default value or evaluate the filter. +fn pruned_by_default(filter: &SimpleFilterEvaluator, column: &ColumnMetadata) -> Option { + let value = column.column_schema.create_default().ok().flatten()?; + let scalar_value = value + .try_to_scalar_value(&column.column_schema.data_type) + .ok()?; + let matches = filter.evaluate_scalar(&scalar_value).ok()?; + Some(!matches) +} + type RowGroupMap = BTreeMap>; /// Parquet batch reader to read our SST format. diff --git a/src/mito2/src/sst/parquet/stats.rs b/src/mito2/src/sst/parquet/stats.rs index 09b837698c..ead3679397 100644 --- a/src/mito2/src/sst/parquet/stats.rs +++ b/src/mito2/src/sst/parquet/stats.rs @@ -16,10 +16,11 @@ use std::borrow::Borrow; use std::collections::HashSet; +use std::sync::Arc; use datafusion::physical_optimizer::pruning::PruningStatistics; use datafusion_common::{Column, ScalarValue}; -use datatypes::arrow::array::{ArrayRef, BooleanArray}; +use datatypes::arrow::array::{ArrayRef, BooleanArray, UInt64Array}; use parquet::file::metadata::RowGroupMetaData; use store_api::metadata::RegionMetadataRef; use store_api::storage::ColumnId; @@ -54,25 +55,62 @@ impl<'a, T> RowGroupPruningStats<'a, T> { } /// Returns the column id of specific column name if we need to read it. + /// Prefers the column id in the expected metadata if it exists. fn column_id_to_prune(&self, name: &str) -> Option { let metadata = self .expected_metadata .as_ref() .unwrap_or_else(|| self.read_format.metadata()); - // Only use stats when the column to read has the same id as the column in the SST. metadata.column_by_name(name).map(|col| col.column_id) } + + /// Returns the default value of all row groups for `column` according to the metadata. + fn compat_default_value(&self, column: &str) -> Option { + let metadata = self.expected_metadata.as_ref()?; + let col_metadata = metadata.column_by_name(column)?; + col_metadata + .column_schema + .create_default_vector(self.row_groups.len()) + .unwrap_or(None) + .map(|vector| vector.to_arrow_array()) + } +} + +impl> RowGroupPruningStats<'_, T> { + /// Returns the null count of all row groups for `column` according to the metadata. + fn compat_null_count(&self, column: &str) -> Option { + let metadata = self.expected_metadata.as_ref()?; + let col_metadata = metadata.column_by_name(column)?; + let value = col_metadata + .column_schema + .create_default() + .unwrap_or(None)?; + let values = self.row_groups.iter().map(|meta| { + if value.is_null() { + u64::try_from(meta.borrow().num_rows()).ok() + } else { + Some(0) + } + }); + Some(Arc::new(UInt64Array::from_iter(values))) + } } impl> PruningStatistics for RowGroupPruningStats<'_, T> { fn min_values(&self, column: &Column) -> Option { let column_id = self.column_id_to_prune(&column.name)?; - self.read_format.min_values(self.row_groups, column_id) + match self.read_format.min_values(self.row_groups, column_id) { + Some(values) => Some(values), + None => self.compat_default_value(&column.name), + } } fn max_values(&self, column: &Column) -> Option { let column_id = self.column_id_to_prune(&column.name)?; - self.read_format.max_values(self.row_groups, column_id) + match self.read_format.max_values(self.row_groups, column_id) { + Some(values) => Some(values), + None => self.compat_default_value(&column.name), + } } fn num_containers(&self) -> usize { @@ -80,7 +118,9 @@ impl> PruningStatistics for RowGroupPruningStats<'_, } fn null_counts(&self, column: &Column) -> Option { - let column_id = self.column_id_to_prune(&column.name)?; + let Some(column_id) = self.column_id_to_prune(&column.name) else { + return self.compat_null_count(&column.name); + }; self.read_format.null_counts(self.row_groups, column_id) } diff --git a/src/mito2/src/wal/raw_entry_reader.rs b/src/mito2/src/wal/raw_entry_reader.rs index 85a0c945b9..9a86c9ebfc 100644 --- a/src/mito2/src/wal/raw_entry_reader.rs +++ b/src/mito2/src/wal/raw_entry_reader.rs @@ -196,6 +196,10 @@ mod tests { ) -> Result { unreachable!() } + + fn high_watermark(&self, _provider: &Provider) -> Result { + unreachable!() + } } #[tokio::test] diff --git a/src/mito2/src/worker.rs b/src/mito2/src/worker.rs index 0eb5abff41..33edb186d4 100644 --- a/src/mito2/src/worker.rs +++ b/src/mito2/src/worker.rs @@ -15,6 +15,7 @@ //! Structs and utilities for writing regions. mod handle_alter; +mod handle_bulk_insert; mod handle_catchup; mod handle_close; mod handle_compaction; @@ -25,6 +26,7 @@ mod handle_manifest; mod handle_open; mod handle_truncate; mod handle_write; + use std::collections::HashMap; use std::path::Path; use std::sync::atomic::{AtomicBool, Ordering}; @@ -41,7 +43,9 @@ use prometheus::IntGauge; use rand::{rng, Rng}; use snafu::{ensure, ResultExt}; use store_api::logstore::LogStore; -use store_api::region_engine::{SetRegionRoleStateResponse, SettableRegionRoleState}; +use store_api::region_engine::{ + SetRegionRoleStateResponse, SetRegionRoleStateSuccess, SettableRegionRoleState, +}; use store_api::storage::RegionId; use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::{mpsc, oneshot, watch, Mutex}; @@ -50,6 +54,7 @@ use crate::cache::write_cache::{WriteCache, WriteCacheRef}; use crate::cache::{CacheManager, CacheManagerRef}; use crate::compaction::CompactionScheduler; use crate::config::MitoConfig; +use crate::error; use crate::error::{CreateDirSnafu, JoinSnafu, Result, WorkerStoppedSnafu}; use crate::flush::{FlushScheduler, WriteBufferManagerImpl, WriteBufferManagerRef}; use crate::memtable::MemtableBuilderProvider; @@ -818,16 +823,30 @@ impl RegionWorkerLoop { WorkerRequest::EditRegion(request) => { self.handle_region_edit(request).await; } - // We receive a stop signal, but we still want to process remaining - // requests. The worker thread will then check the running flag and - // then exit. WorkerRequest::Stop => { debug_assert!(!self.running.load(Ordering::Relaxed)); } - WorkerRequest::SyncRegion(req) => { self.handle_region_sync(req).await; } + WorkerRequest::BulkInserts { + metadata, + request, + sender, + } => { + if let Some(region_metadata) = metadata { + self.handle_bulk_inserts(request, region_metadata, write_requests, sender) + .await; + } else { + error!("Cannot find region metadata for {}", request.region_id); + sender.send( + error::RegionNotFoundSnafu { + region_id: request.region_id, + } + .fail(), + ); + } + } } } @@ -931,7 +950,9 @@ impl RegionWorkerLoop { region.set_role_state_gracefully(region_role_state).await; let last_entry_id = region.version_control.current().last_entry_id; - let _ = sender.send(SetRegionRoleStateResponse::success(Some(last_entry_id))); + let _ = sender.send(SetRegionRoleStateResponse::success( + SetRegionRoleStateSuccess::mito(last_entry_id), + )); }); } else { let _ = sender.send(SetRegionRoleStateResponse::NotFound); diff --git a/src/mito2/src/worker/handle_bulk_insert.rs b/src/mito2/src/worker/handle_bulk_insert.rs new file mode 100644 index 0000000000..9f2a745835 --- /dev/null +++ b/src/mito2/src/worker/handle_bulk_insert.rs @@ -0,0 +1,247 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Handles bulk insert requests. + +use api::helper::{value_to_grpc_value, ColumnDataTypeWrapper}; +use api::v1::{ColumnSchema, OpType, Row, Rows}; +use common_recordbatch::DfRecordBatch; +use datatypes::prelude::VectorRef; +use datatypes::vectors::Helper; +use snafu::ResultExt; +use store_api::logstore::LogStore; +use store_api::metadata::RegionMetadataRef; +use store_api::region_request::{BulkInsertPayload, RegionBulkInsertsRequest}; + +use crate::error; +use crate::request::{OptionOutputTx, SenderWriteRequest, WriteRequest}; +use crate::worker::RegionWorkerLoop; + +impl RegionWorkerLoop { + pub(crate) async fn handle_bulk_inserts( + &mut self, + request: RegionBulkInsertsRequest, + region_metadata: RegionMetadataRef, + pending_write_requests: &mut Vec, + sender: OptionOutputTx, + ) { + let schema = match region_metadata_to_column_schema(®ion_metadata) { + Ok(schema) => schema, + Err(e) => { + sender.send(Err(e)); + return; + } + }; + let mut pending_tasks = Vec::with_capacity(request.payloads.len()); + for req in request.payloads { + match req { + BulkInsertPayload::ArrowIpc(df_record_batch) => { + let rows = match record_batch_to_rows(®ion_metadata, &df_record_batch) { + Ok(rows) => rows, + Err(e) => { + sender.send(Err(e)); + return; + } + }; + + let write_request = match WriteRequest::new( + region_metadata.region_id, + OpType::Put, + Rows { + schema: schema.clone(), + rows, + }, + Some(region_metadata.clone()), + ) { + Ok(write_request) => write_request, + Err(e) => { + sender.send(Err(e)); + return; + } + }; + + let (tx, rx) = tokio::sync::oneshot::channel(); + let sender = OptionOutputTx::from(tx); + let req = SenderWriteRequest { + sender, + request: write_request, + }; + pending_tasks.push(rx); + pending_write_requests.push(req); + } + } + } + + common_runtime::spawn_global(async move { + let results = match futures::future::try_join_all(pending_tasks).await { + Ok(results) => results, + Err(e) => { + sender.send(Err(e).context(error::RecvSnafu)); + return; + } + }; + let result1 = match results.into_iter().collect::>>() { + Ok(results) => Ok(results.into_iter().sum()), + Err(e) => Err(e), + }; + sender.send(result1); + }); + } +} + +fn region_metadata_to_column_schema( + region_meta: &RegionMetadataRef, +) -> error::Result> { + region_meta + .column_metadatas + .iter() + .map(|c| { + let wrapper = ColumnDataTypeWrapper::try_from(c.column_schema.data_type.clone()) + .with_context(|_| error::ConvertDataTypeSnafu { + data_type: c.column_schema.data_type.clone(), + })?; + + Ok(ColumnSchema { + column_name: c.column_schema.name.clone(), + datatype: wrapper.datatype() as i32, + semantic_type: c.semantic_type as i32, + ..Default::default() + }) + }) + .collect::>() +} + +/// Convert [DfRecordBatch] to gRPC rows. +fn record_batch_to_rows( + region_metadata: &RegionMetadataRef, + rb: &DfRecordBatch, +) -> error::Result> { + let num_rows = rb.num_rows(); + let mut rows = Vec::with_capacity(num_rows); + if num_rows == 0 { + return Ok(rows); + } + let vectors: Vec> = region_metadata + .column_metadatas + .iter() + .map(|c| { + rb.column_by_name(&c.column_schema.name) + .map(|column| Helper::try_into_vector(column).context(error::ConvertVectorSnafu)) + .transpose() + }) + .collect::>()?; + + for row_idx in 0..num_rows { + let row = Row { + values: row_at(&vectors, row_idx), + }; + rows.push(row); + } + Ok(rows) +} + +fn row_at(vectors: &[Option], row_idx: usize) -> Vec { + let mut row = Vec::with_capacity(vectors.len()); + for a in vectors { + let value = if let Some(a) = a { + value_to_grpc_value(a.get(row_idx)) + } else { + api::v1::Value { value_data: None } + }; + row.push(value) + } + row +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use api::v1::SemanticType; + use datatypes::arrow::array::{Int64Array, TimestampMillisecondArray}; + + use super::*; + use crate::test_util::meta_util::TestRegionMetadataBuilder; + + fn build_record_batch(num_rows: usize) -> DfRecordBatch { + let region_metadata = Arc::new(TestRegionMetadataBuilder::default().build()); + let schema = region_metadata.schema.arrow_schema().clone(); + let values = (0..num_rows).map(|v| v as i64).collect::>(); + let ts_array = Arc::new(TimestampMillisecondArray::from_iter_values(values.clone())); + let k0_array = Arc::new(Int64Array::from_iter_values(values.clone())); + let v0_array = Arc::new(Int64Array::from_iter_values(values)); + DfRecordBatch::try_new(schema, vec![ts_array, k0_array, v0_array]).unwrap() + } + + #[test] + fn test_region_metadata_to_column_schema() { + let region_metadata = Arc::new(TestRegionMetadataBuilder::default().build()); + let result = region_metadata_to_column_schema(®ion_metadata).unwrap(); + assert_eq!(result.len(), 3); + + assert_eq!(result[0].column_name, "ts"); + assert_eq!(result[0].semantic_type, SemanticType::Timestamp as i32); + + assert_eq!(result[1].column_name, "k0"); + assert_eq!(result[1].semantic_type, SemanticType::Tag as i32); + + assert_eq!(result[2].column_name, "v0"); + assert_eq!(result[2].semantic_type, SemanticType::Field as i32); + } + + #[test] + fn test_record_batch_to_rows() { + // Create record batch + let region_metadata = Arc::new(TestRegionMetadataBuilder::default().build()); + let record_batch = build_record_batch(10); + let rows = record_batch_to_rows(®ion_metadata, &record_batch).unwrap(); + + assert_eq!(rows.len(), 10); + assert_eq!(rows[0].values.len(), 3); + + for (row_idx, row) in rows.iter().enumerate().take(10) { + assert_eq!( + row.values[0].value_data.as_ref().unwrap(), + &api::v1::value::ValueData::TimestampMillisecondValue(row_idx as i64) + ); + } + } + + #[test] + fn test_record_batch_to_rows_schema_mismatch() { + let region_metadata = Arc::new(TestRegionMetadataBuilder::default().num_fields(2).build()); + let record_batch = build_record_batch(1); + + let rows = record_batch_to_rows(®ion_metadata, &record_batch).unwrap(); + assert_eq!(rows.len(), 1); + + // Check first row + let row1 = &rows[0]; + assert_eq!(row1.values.len(), 4); + assert_eq!( + row1.values[0].value_data.as_ref().unwrap(), + &api::v1::value::ValueData::TimestampMillisecondValue(0) + ); + assert_eq!( + row1.values[1].value_data.as_ref().unwrap(), + &api::v1::value::ValueData::I64Value(0) + ); + assert_eq!( + row1.values[2].value_data.as_ref().unwrap(), + &api::v1::value::ValueData::I64Value(0) + ); + + assert!(row1.values[3].value_data.is_none()); + } +} diff --git a/src/mito2/src/worker/handle_flush.rs b/src/mito2/src/worker/handle_flush.rs index e846809a5d..ca7499f326 100644 --- a/src/mito2/src/worker/handle_flush.rs +++ b/src/mito2/src/worker/handle_flush.rs @@ -14,6 +14,7 @@ //! Handling flush related requests. +use std::sync::atomic::Ordering; use std::sync::Arc; use common_telemetry::{error, info}; @@ -29,34 +30,6 @@ use crate::request::{FlushFailed, FlushFinished, OnFailure, OptionOutputTx}; use crate::worker::RegionWorkerLoop; impl RegionWorkerLoop { - /// Handles manual flush request. - pub(crate) async fn handle_flush_request( - &mut self, - region_id: RegionId, - request: RegionFlushRequest, - mut sender: OptionOutputTx, - ) { - let Some(region) = self.regions.flushable_region_or(region_id, &mut sender) else { - return; - }; - - let reason = if region.is_downgrading() { - FlushReason::Downgrading - } else { - FlushReason::Manual - }; - - let mut task = - self.new_flush_task(®ion, reason, request.row_group_size, self.config.clone()); - task.push_sender(sender); - if let Err(e) = - self.flush_scheduler - .schedule_flush(region.region_id, ®ion.version_control, task) - { - error!(e; "Failed to schedule flush task for region {}", region.region_id); - } - } - /// On region flush job failed. pub(crate) async fn handle_flush_failed(&mut self, region_id: RegionId, request: FlushFailed) { self.flush_scheduler.on_flush_failed(region_id, request.err); @@ -129,37 +102,6 @@ impl RegionWorkerLoop { Ok(()) } - /// Flushes regions periodically. - pub(crate) fn flush_periodically(&mut self) -> Result<()> { - let regions = self.regions.list_regions(); - let now = self.time_provider.current_time_millis(); - let min_last_flush_time = now - self.config.auto_flush_interval.as_millis() as i64; - - for region in ®ions { - if self.flush_scheduler.is_flush_requested(region.region_id) || !region.is_writable() { - // Already flushing or not writable. - continue; - } - - if region.last_flush_millis() < min_last_flush_time { - // If flush time of this region is earlier than `min_last_flush_time`, we can flush this region. - let task = self.new_flush_task( - region, - FlushReason::Periodically, - None, - self.config.clone(), - ); - self.flush_scheduler.schedule_flush( - region.region_id, - ®ion.version_control, - task, - )?; - } - } - - Ok(()) - } - /// Creates a flush task with specific `reason` for the `region`. pub(crate) fn new_flush_task( &self, @@ -185,6 +127,70 @@ impl RegionWorkerLoop { } impl RegionWorkerLoop { + /// Handles manual flush request. + pub(crate) async fn handle_flush_request( + &mut self, + region_id: RegionId, + request: RegionFlushRequest, + mut sender: OptionOutputTx, + ) { + let Some(region) = self.regions.flushable_region_or(region_id, &mut sender) else { + return; + }; + // `update_topic_latest_entry_id` updates `topic_latest_entry_id` when memtables are empty. + // But the flush is skipped if memtables are empty. Thus should update the `topic_latest_entry_id` + // when handling flush request instead of in `schedule_flush` or `flush_finished`. + self.update_topic_latest_entry_id(®ion); + + let reason = if region.is_downgrading() { + FlushReason::Downgrading + } else { + FlushReason::Manual + }; + + let mut task = + self.new_flush_task(®ion, reason, request.row_group_size, self.config.clone()); + task.push_sender(sender); + if let Err(e) = + self.flush_scheduler + .schedule_flush(region.region_id, ®ion.version_control, task) + { + error!(e; "Failed to schedule flush task for region {}", region.region_id); + } + } + + /// Flushes regions periodically. + pub(crate) fn flush_periodically(&mut self) -> Result<()> { + let regions = self.regions.list_regions(); + let now = self.time_provider.current_time_millis(); + let min_last_flush_time = now - self.config.auto_flush_interval.as_millis() as i64; + + for region in ®ions { + if self.flush_scheduler.is_flush_requested(region.region_id) || !region.is_writable() { + // Already flushing or not writable. + continue; + } + self.update_topic_latest_entry_id(region); + + if region.last_flush_millis() < min_last_flush_time { + // If flush time of this region is earlier than `min_last_flush_time`, we can flush this region. + let task = self.new_flush_task( + region, + FlushReason::Periodically, + None, + self.config.clone(), + ); + self.flush_scheduler.schedule_flush( + region.region_id, + ®ion.version_control, + task, + )?; + } + } + + Ok(()) + } + /// On region flush job finished. pub(crate) async fn handle_flush_finished( &mut self, @@ -247,4 +253,27 @@ impl RegionWorkerLoop { self.listener.on_flush_success(region_id); } + + /// Updates the latest entry id since flush of the region. + /// **This is only used for remote WAL pruning.** + pub(crate) fn update_topic_latest_entry_id(&mut self, region: &MitoRegionRef) { + if region.provider.is_remote_wal() && region.version().memtables.is_empty() { + let high_watermark = self + .wal + .store() + .high_watermark(®ion.provider) + .unwrap_or(0); + let topic_last_entry_id = region.topic_latest_entry_id.load(Ordering::Relaxed); + + if high_watermark != 0 && high_watermark > topic_last_entry_id { + region + .topic_latest_entry_id + .store(high_watermark, Ordering::Relaxed); + info!( + "Region {} high watermark updated to {}", + region.region_id, high_watermark + ); + } + } + } } diff --git a/src/mito2/src/worker/handle_manifest.rs b/src/mito2/src/worker/handle_manifest.rs index f1bec95514..4fd0de0d7b 100644 --- a/src/mito2/src/worker/handle_manifest.rs +++ b/src/mito2/src/worker/handle_manifest.rs @@ -136,6 +136,7 @@ impl RegionWorkerLoop { } }; + let original_manifest_version = region.manifest_ctx.manifest_version().await; let manifest = match region .manifest_ctx .install_manifest_to(request.manifest_version) @@ -173,7 +174,8 @@ impl RegionWorkerLoop { .build(); region.version_control.overwrite_current(Arc::new(version)); - let _ = sender.send(Ok(manifest.manifest_version)); + let updated = manifest.manifest_version > original_manifest_version; + let _ = sender.send(Ok((manifest.manifest_version, updated))); } } diff --git a/src/mito2/src/worker/handle_write.rs b/src/mito2/src/worker/handle_write.rs index a5dafcdd3a..b6e8783e1e 100644 --- a/src/mito2/src/worker/handle_write.rs +++ b/src/mito2/src/worker/handle_write.rs @@ -299,7 +299,7 @@ impl RegionWorkerLoop { } /// Returns true if the engine needs to reject some write requests. - fn should_reject_write(&self) -> bool { + pub(crate) fn should_reject_write(&self) -> bool { // If memory usage reaches high threshold (we should also consider stalled requests) returns true. self.write_buffer_manager.memory_usage() + self.stalled_requests.estimated_size >= self.config.global_write_buffer_reject_size.as_bytes() as usize diff --git a/src/object-store/Cargo.toml b/src/object-store/Cargo.toml index 8aab3af382..f90ea42d61 100644 --- a/src/object-store/Cargo.toml +++ b/src/object-store/Cargo.toml @@ -17,7 +17,7 @@ futures.workspace = true lazy_static.workspace = true md5 = "0.7" moka = { workspace = true, features = ["future"] } -opendal = { version = "0.51.1", features = [ +opendal = { version = "0.52", features = [ "layers-tracing", "layers-prometheus", "services-azblob", diff --git a/src/operator/src/error.rs b/src/operator/src/error.rs index c0c102ceda..900a3b4310 100644 --- a/src/operator/src/error.rs +++ b/src/operator/src/error.rs @@ -799,6 +799,14 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display("Failed to create partition rules"))] + CreatePartitionRules { + #[snafu(source)] + source: sql::error::Error, + #[snafu(implicit)] + location: Location, + }, } pub type Result = std::result::Result; @@ -840,7 +848,8 @@ impl ErrorExt for Error { | Error::PhysicalExpr { .. } | Error::InvalidJsonFormat { .. } | Error::CursorNotFound { .. } - | Error::CursorExists { .. } => StatusCode::InvalidArguments, + | Error::CursorExists { .. } + | Error::CreatePartitionRules { .. } => StatusCode::InvalidArguments, Error::TableAlreadyExists { .. } | Error::ViewAlreadyExists { .. } => { StatusCode::TableAlreadyExists diff --git a/src/operator/src/insert.rs b/src/operator/src/insert.rs index b2742685fb..2f7c30ccd0 100644 --- a/src/operator/src/insert.rs +++ b/src/operator/src/insert.rs @@ -63,8 +63,8 @@ use table::table_reference::TableReference; use table::TableRef; use crate::error::{ - CatalogSnafu, ColumnOptionsSnafu, FindRegionLeaderSnafu, InvalidInsertRequestSnafu, - JoinTaskSnafu, RequestInsertsSnafu, Result, TableNotFoundSnafu, + CatalogSnafu, ColumnOptionsSnafu, CreatePartitionRulesSnafu, FindRegionLeaderSnafu, + InvalidInsertRequestSnafu, JoinTaskSnafu, RequestInsertsSnafu, Result, TableNotFoundSnafu, }; use crate::expr_helper; use crate::region_req_factory::RegionRequestFactory; @@ -591,7 +591,8 @@ impl Inserter { } else { // prebuilt partition rules for uuid data: see the function // for more information - let partitions = partition_rule_for_hexstring(TRACE_ID_COLUMN); + let partitions = partition_rule_for_hexstring(TRACE_ID_COLUMN) + .context(CreatePartitionRulesSnafu)?; // add skip index to // - trace_id: when searching by trace id // - parent_span_id: when searching root span diff --git a/src/operator/src/procedure.rs b/src/operator/src/procedure.rs index e2c27c024f..87f805acb1 100644 --- a/src/operator/src/procedure.rs +++ b/src/operator/src/procedure.rs @@ -13,6 +13,7 @@ // limitations under the License. use async_trait::async_trait; +use catalog::CatalogManagerRef; use common_error::ext::BoxedError; use common_function::handlers::ProcedureServiceHandler; use common_meta::ddl::{ExecutorContext, ProcedureExecutorRef}; @@ -28,11 +29,18 @@ use snafu::ResultExt; #[derive(Clone)] pub struct ProcedureServiceOperator { procedure_executor: ProcedureExecutorRef, + catalog_manager: CatalogManagerRef, } impl ProcedureServiceOperator { - pub fn new(procedure_executor: ProcedureExecutorRef) -> Self { - Self { procedure_executor } + pub fn new( + procedure_executor: ProcedureExecutorRef, + catalog_manager: CatalogManagerRef, + ) -> Self { + Self { + procedure_executor, + catalog_manager, + } } } @@ -75,4 +83,8 @@ impl ProcedureServiceHandler for ProcedureServiceOperator { .map_err(BoxedError::new) .context(query_error::ProcedureServiceSnafu) } + + fn catalog_manager(&self) -> &CatalogManagerRef { + &self.catalog_manager + } } diff --git a/src/operator/src/req_convert/insert/fill_impure_default.rs b/src/operator/src/req_convert/insert/fill_impure_default.rs index a60138c6e5..cf1e1565a8 100644 --- a/src/operator/src/req_convert/insert/fill_impure_default.rs +++ b/src/operator/src/req_convert/insert/fill_impure_default.rs @@ -85,11 +85,9 @@ impl ImpureDefaultFiller { .schema .iter() .filter_map(|schema| { - if self.impure_columns.contains_key(&schema.column_name) { - Some(&schema.column_name) - } else { - None - } + self.impure_columns + .contains_key(&schema.column_name) + .then_some(&schema.column_name) }) .collect(); diff --git a/src/operator/src/statement/copy_table_from.rs b/src/operator/src/statement/copy_table_from.rs index 2d9fbef6b9..505d74c3d7 100644 --- a/src/operator/src/statement/copy_table_from.rs +++ b/src/operator/src/statement/copy_table_from.rs @@ -237,7 +237,7 @@ impl StatementExecutor { path, schema, } => { - let projected_schema = Arc::new( + let output_schema = Arc::new( compat_schema .project(&projection) .context(error::ProjectSchemaSnafu)?, @@ -255,17 +255,23 @@ impl StatementExecutor { )), None, )); - + let projected_file_schema = Arc::new( + schema + .project(&projection) + .context(error::ProjectSchemaSnafu)?, + ); let stream = self .build_file_stream( CsvOpener::new(csv_config, format.compression_type.into()), path, - schema.clone(), + projected_file_schema, ) .await?; Ok(Box::pin( - RecordBatchStreamTypeAdapter::new(projected_schema, stream, Some(projection)) + // The projection is already applied in the CSV reader when we created the stream, + // so we pass None here to avoid double projection which would cause schema mismatch errors. + RecordBatchStreamTypeAdapter::new(output_schema, stream, None) .with_filter(filters) .context(error::PhysicalExprSnafu)?, )) @@ -280,7 +286,7 @@ impl StatementExecutor { .project(&projection) .context(error::ProjectSchemaSnafu)?, ); - let projected_schema = Arc::new( + let output_schema = Arc::new( compat_schema .project(&projection) .context(error::ProjectSchemaSnafu)?, @@ -290,17 +296,19 @@ impl StatementExecutor { .build_file_stream( JsonOpener::new( DEFAULT_BATCH_SIZE, - projected_file_schema, + projected_file_schema.clone(), format.compression_type.into(), Arc::new(store), ), path, - schema.clone(), + projected_file_schema, ) .await?; Ok(Box::pin( - RecordBatchStreamTypeAdapter::new(projected_schema, stream, Some(projection)) + // The projection is already applied in the JSON reader when we created the stream, + // so we pass None here to avoid double projection which would cause schema mismatch errors. + RecordBatchStreamTypeAdapter::new(output_schema, stream, None) .with_filter(filters) .context(error::PhysicalExprSnafu)?, )) @@ -325,13 +333,13 @@ impl StatementExecutor { .build() .context(error::BuildParquetRecordBatchStreamSnafu)?; - let projected_schema = Arc::new( + let output_schema = Arc::new( compat_schema .project(&projection) .context(error::ProjectSchemaSnafu)?, ); Ok(Box::pin( - RecordBatchStreamTypeAdapter::new(projected_schema, stream, Some(projection)) + RecordBatchStreamTypeAdapter::new(output_schema, stream, Some(projection)) .with_filter(filters) .context(error::PhysicalExprSnafu)?, )) @@ -352,14 +360,14 @@ impl StatementExecutor { .await .context(error::ReadOrcSnafu)?; - let projected_schema = Arc::new( + let output_schema = Arc::new( compat_schema .project(&projection) .context(error::ProjectSchemaSnafu)?, ); Ok(Box::pin( - RecordBatchStreamTypeAdapter::new(projected_schema, stream, Some(projection)) + RecordBatchStreamTypeAdapter::new(output_schema, stream, Some(projection)) .with_filter(filters) .context(error::PhysicalExprSnafu)?, )) diff --git a/src/operator/src/statement/ddl.rs b/src/operator/src/statement/ddl.rs index 8450c73c4d..afae466dc0 100644 --- a/src/operator/src/statement/ddl.rs +++ b/src/operator/src/statement/ddl.rs @@ -26,6 +26,7 @@ use common_catalog::consts::{is_readonly_schema, DEFAULT_CATALOG_NAME, DEFAULT_S use common_catalog::{format_full_flow_name, format_full_table_name}; use common_error::ext::BoxedError; use common_meta::cache_invalidator::Context; +use common_meta::ddl::create_flow::FlowType; use common_meta::ddl::ExecutorContext; use common_meta::instruction::CacheIdent; use common_meta::key::schema_name::{SchemaName, SchemaNameKey}; @@ -38,6 +39,8 @@ use common_meta::rpc::router::{Partition, Partition as MetaPartition}; use common_query::Output; use common_telemetry::{debug, info, tracing}; use common_time::Timezone; +use datafusion_common::tree_node::TreeNodeVisitor; +use datafusion_expr::LogicalPlan; use datatypes::prelude::ConcreteDataType; use datatypes::schema::{RawSchema, Schema}; use datatypes::value::Value; @@ -45,7 +48,7 @@ use lazy_static::lazy_static; use partition::expr::{Operand, PartitionExpr, RestrictedOp}; use partition::multi_dim::MultiDimPartitionRule; use partition::partition::{PartitionBound, PartitionDef}; -use query::parser::QueryStatement; +use query::parser::{QueryLanguageParser, QueryStatement}; use query::plan::extract_and_rewrite_full_table_names; use query::query_engine::DefaultSerializer; use query::sql::create_table_stmt; @@ -69,13 +72,14 @@ use table::table_name::TableName; use table::TableRef; use crate::error::{ - self, AlterExprToRequestSnafu, CatalogSnafu, ColumnDataTypeSnafu, ColumnNotFoundSnafu, - ConvertSchemaSnafu, CreateLogicalTablesSnafu, CreateTableInfoSnafu, DeserializePartitionSnafu, - EmptyDdlExprSnafu, ExtractTableNamesSnafu, FlowNotFoundSnafu, InvalidPartitionRuleSnafu, - InvalidPartitionSnafu, InvalidSqlSnafu, InvalidTableNameSnafu, InvalidViewNameSnafu, - InvalidViewStmtSnafu, ParseSqlValueSnafu, Result, SchemaInUseSnafu, SchemaNotFoundSnafu, - SchemaReadOnlySnafu, SubstraitCodecSnafu, TableAlreadyExistsSnafu, TableMetadataManagerSnafu, - TableNotFoundSnafu, UnrecognizedTableOptionSnafu, ViewAlreadyExistsSnafu, + self, AlterExprToRequestSnafu, BuildDfLogicalPlanSnafu, CatalogSnafu, ColumnDataTypeSnafu, + ColumnNotFoundSnafu, ConvertSchemaSnafu, CreateLogicalTablesSnafu, CreateTableInfoSnafu, + DeserializePartitionSnafu, EmptyDdlExprSnafu, ExternalSnafu, ExtractTableNamesSnafu, + FlowNotFoundSnafu, InvalidPartitionRuleSnafu, InvalidPartitionSnafu, InvalidSqlSnafu, + InvalidTableNameSnafu, InvalidViewNameSnafu, InvalidViewStmtSnafu, ParseSqlValueSnafu, Result, + SchemaInUseSnafu, SchemaNotFoundSnafu, SchemaReadOnlySnafu, SubstraitCodecSnafu, + TableAlreadyExistsSnafu, TableMetadataManagerSnafu, TableNotFoundSnafu, + UnrecognizedTableOptionSnafu, ViewAlreadyExistsSnafu, }; use crate::expr_helper; use crate::statement::show::create_partitions_stmt; @@ -364,6 +368,18 @@ impl StatementExecutor { expr: CreateFlowExpr, query_context: QueryContextRef, ) -> Result { + let flow_type = self + .determine_flow_type(&expr.sql, query_context.clone()) + .await?; + info!("determined flow={} type: {:#?}", expr.flow_name, flow_type); + + let expr = { + let mut expr = expr; + expr.flow_options + .insert(FlowType::FLOW_TYPE_KEY.to_string(), flow_type.to_string()); + expr + }; + let task = CreateFlowTask::try_from(PbCreateFlowTask { create_flow: Some(expr), }) @@ -379,6 +395,55 @@ impl StatementExecutor { .context(error::ExecuteDdlSnafu) } + /// Determine the flow type based on the SQL query + /// + /// If it contains aggregation or distinct, then it is a batch flow, otherwise it is a streaming flow + async fn determine_flow_type(&self, sql: &str, query_ctx: QueryContextRef) -> Result { + let engine = &self.query_engine; + let stmt = QueryLanguageParser::parse_sql(sql, &query_ctx) + .map_err(BoxedError::new) + .context(ExternalSnafu)?; + let plan = engine + .planner() + .plan(&stmt, query_ctx) + .await + .map_err(BoxedError::new) + .context(ExternalSnafu)?; + + /// Visitor to find aggregation or distinct + struct FindAggr { + is_aggr: bool, + } + + impl TreeNodeVisitor<'_> for FindAggr { + type Node = LogicalPlan; + fn f_down( + &mut self, + node: &Self::Node, + ) -> datafusion_common::Result + { + match node { + LogicalPlan::Aggregate(_) | LogicalPlan::Distinct(_) => { + self.is_aggr = true; + return Ok(datafusion_common::tree_node::TreeNodeRecursion::Stop); + } + _ => (), + } + Ok(datafusion_common::tree_node::TreeNodeRecursion::Continue) + } + } + + let mut find_aggr = FindAggr { is_aggr: false }; + + plan.visit_with_subqueries(&mut find_aggr) + .context(BuildDfLogicalPlanSnafu)?; + if find_aggr.is_aggr { + Ok(FlowType::Batching) + } else { + Ok(FlowType::Streaming) + } + } + #[tracing::instrument(skip_all)] pub async fn create_view( &self, diff --git a/src/partition/Cargo.toml b/src/partition/Cargo.toml index ebb7d68f8d..6a0904f8f2 100644 --- a/src/partition/Cargo.toml +++ b/src/partition/Cargo.toml @@ -16,6 +16,7 @@ common-meta.workspace = true common-query.workspace = true datafusion-common.workspace = true datafusion-expr.workspace = true +datafusion-physical-expr.workspace = true datatypes.workspace = true itertools.workspace = true serde.workspace = true @@ -26,3 +27,11 @@ sql.workspace = true sqlparser.workspace = true store-api.workspace = true table.workspace = true + +[dev-dependencies] +criterion = "0.5" +rand = "0.8" + +[[bench]] +name = "bench_split_record_batch" +harness = false diff --git a/src/partition/benches/bench_split_record_batch.rs b/src/partition/benches/bench_split_record_batch.rs new file mode 100644 index 0000000000..f6c1bd69d4 --- /dev/null +++ b/src/partition/benches/bench_split_record_batch.rs @@ -0,0 +1,226 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::Arc; +use std::vec; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use datatypes::arrow::array::{ArrayRef, Int32Array, StringArray, TimestampMillisecondArray}; +use datatypes::arrow::datatypes::{DataType, Field, Schema, TimeUnit}; +use datatypes::arrow::record_batch::RecordBatch; +use datatypes::value::Value; +use partition::expr::{col, Operand}; +use partition::multi_dim::MultiDimPartitionRule; +use partition::PartitionRule; +use rand::Rng; +use store_api::storage::RegionNumber; + +fn table_schema() -> Arc { + Arc::new(Schema::new(vec![ + Field::new("a0", DataType::Int32, false), + Field::new("a1", DataType::Utf8, false), + Field::new("a2", DataType::Int32, false), + Field::new( + "ts", + DataType::Timestamp(TimeUnit::Millisecond, None), + false, + ), + ])) +} + +fn create_test_rule(num_columns: usize) -> MultiDimPartitionRule { + let (columns, exprs) = match num_columns { + 1 => { + let exprs = vec![ + col("a0").lt(Value::Int32(50)), + col("a0").gt_eq(Value::Int32(50)), + ]; + (vec!["a0".to_string()], exprs) + } + 2 => { + let exprs = vec![ + col("a0") + .lt(Value::Int32(50)) + .and(col("a1").lt(Value::String("server50".into()))), + col("a0") + .lt(Value::Int32(50)) + .and(col("a1").gt_eq(Value::String("server50".into()))), + col("a0") + .gt_eq(Value::Int32(50)) + .and(col("a1").lt(Value::String("server50".into()))), + col("a0") + .gt_eq(Value::Int32(50)) + .and(col("a1").gt_eq(Value::String("server50".into()))), + ]; + (vec!["a0".to_string(), "a1".to_string()], exprs) + } + 3 => { + let expr = vec![ + col("a0") + .lt(Value::Int32(50)) + .and(col("a1").lt(Value::String("server50".into()))) + .and(col("a2").lt(Value::Int32(50))), + col("a0") + .lt(Operand::Value(Value::Int32(50))) + .and(col("a1").lt(Value::String("server50".into()))) + .and(col("a2").gt_eq(Value::Int32(50))), + col("a0") + .lt(Value::Int32(50)) + .and(col("a1").gt_eq(Value::String("server50".into()))) + .and(col("a2").lt(Value::Int32(50))), + col("a0") + .lt(Value::Int32(50)) + .and(col("a1").gt_eq(Value::String("server50".into()))) + .and(col("a2").gt_eq(Value::Int32(50))), + col("a0") + .gt_eq(Value::Int32(50)) + .and(col("a1").lt(Value::String("server50".into()))) + .and(col("a2").lt(Value::Int32(50))), + col("a0") + .gt_eq(Operand::Value(Value::Int32(50))) + .and(col("a1").lt(Value::String("server50".into()))) + .and(col("a2").gt_eq(Value::Int32(50))), + col("a0") + .gt_eq(Value::Int32(50)) + .and(col("a1").gt_eq(Value::String("server50".into()))) + .and(col("a2").lt(Value::Int32(50))), + col("a0") + .gt_eq(Value::Int32(50)) + .and(col("a1").gt_eq(Value::String("server50".into()))) + .and(col("a2").gt_eq(Value::Int32(50))), + ]; + + ( + vec!["a0".to_string(), "a1".to_string(), "a2".to_string()], + expr, + ) + } + _ => { + panic!("invalid number of columns, only 1-3 are supported"); + } + }; + + let regions = (0..exprs.len()).map(|v| v as u32).collect(); + MultiDimPartitionRule::try_new(columns, regions, exprs).unwrap() +} + +fn create_test_batch(size: usize) -> RecordBatch { + let mut rng = rand::thread_rng(); + + let schema = table_schema(); + let arrays: Vec = (0..3) + .map(|col_idx| { + if col_idx % 2 == 0 { + // Integer columns (a0, a2) + Arc::new(Int32Array::from_iter_values( + (0..size).map(|_| rng.gen_range(0..100)), + )) as ArrayRef + } else { + // String columns (a1) + let values: Vec = (0..size) + .map(|_| { + let server_id: i32 = rng.gen_range(0..100); + format!("server{}", server_id) + }) + .collect(); + Arc::new(StringArray::from(values)) as ArrayRef + } + }) + .chain(std::iter::once({ + // Timestamp column (ts) + Arc::new(TimestampMillisecondArray::from_iter_values( + (0..size).map(|idx| idx as i64), + )) as ArrayRef + })) + .collect(); + RecordBatch::try_new(schema, arrays).unwrap() +} + +fn bench_split_record_batch_naive_vs_optimized(c: &mut Criterion) { + let mut group = c.benchmark_group("split_record_batch"); + + for num_columns in [1, 2, 3].iter() { + for num_rows in [100, 1000, 10000, 100000].iter() { + let rule = create_test_rule(*num_columns); + let batch = create_test_batch(*num_rows); + + group.bench_function(format!("naive_{}_{}", num_columns, num_rows), |b| { + b.iter(|| { + black_box(rule.split_record_batch_naive(black_box(&batch))).unwrap(); + }); + }); + group.bench_function(format!("optimized_{}_{}", num_columns, num_rows), |b| { + b.iter(|| { + black_box(rule.split_record_batch(black_box(&batch))).unwrap(); + }); + }); + } + } + + group.finish(); +} + +fn record_batch_to_rows( + rule: &MultiDimPartitionRule, + record_batch: &RecordBatch, +) -> Vec> { + let num_rows = record_batch.num_rows(); + let vectors = rule.record_batch_to_cols(record_batch).unwrap(); + let mut res = Vec::with_capacity(num_rows); + let mut current_row = vec![Value::Null; vectors.len()]; + + for row in 0..num_rows { + rule.row_at(&vectors, row, &mut current_row).unwrap(); + res.push(current_row.clone()); + } + res +} + +fn find_all_regions(rule: &MultiDimPartitionRule, rows: &[Vec]) -> Vec { + rows.iter() + .map(|row| rule.find_region(row).unwrap()) + .collect() +} + +fn bench_split_record_batch_vs_row(c: &mut Criterion) { + let mut group = c.benchmark_group("bench_split_record_batch_vs_row"); + + for num_columns in [1, 2, 3].iter() { + for num_rows in [100, 1000, 10000, 100000].iter() { + let rule = create_test_rule(*num_columns); + let batch = create_test_batch(*num_rows); + let rows = record_batch_to_rows(&rule, &batch); + + group.bench_function(format!("split_by_row_{}_{}", num_columns, num_rows), |b| { + b.iter(|| { + black_box(find_all_regions(&rule, &rows)); + }); + }); + group.bench_function(format!("split_by_col_{}_{}", num_columns, num_rows), |b| { + b.iter(|| { + black_box(rule.split_record_batch(black_box(&batch))).unwrap(); + }); + }); + } + } + + group.finish(); +} + +criterion_group!( + benches, + bench_split_record_batch_naive_vs_optimized, + bench_split_record_batch_vs_row +); +criterion_main!(benches); diff --git a/src/partition/src/error.rs b/src/partition/src/error.rs index 2487fa0974..2194583f40 100644 --- a/src/partition/src/error.rs +++ b/src/partition/src/error.rs @@ -18,6 +18,8 @@ use common_error::ext::ErrorExt; use common_error::status_code::StatusCode; use common_macro::stack_trace_debug; use datafusion_common::ScalarValue; +use datatypes::arrow; +use datatypes::prelude::Value; use snafu::{Location, Snafu}; use store_api::storage::RegionId; use table::metadata::TableId; @@ -173,6 +175,59 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display("Failed to convert to vector"))] + ConvertToVector { + source: datatypes::error::Error, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Failed to evaluate record batch"))] + EvaluateRecordBatch { + #[snafu(source)] + error: datafusion_common::error::DataFusionError, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Failed to compute arrow kernel"))] + ComputeArrowKernel { + #[snafu(source)] + error: arrow::error::ArrowError, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Unexpected evaluation result column type: {}", data_type))] + UnexpectedColumnType { + data_type: arrow::datatypes::DataType, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Failed to convert to DataFusion's Schema"))] + ToDFSchema { + #[snafu(source)] + error: datafusion_common::error::DataFusionError, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Failed to create physical expression"))] + CreatePhysicalExpr { + #[snafu(source)] + error: datafusion_common::error::DataFusionError, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Partition expr value is not supported: {:?}", value))] + UnsupportedPartitionExprValue { + value: Value, + #[snafu(implicit)] + location: Location, + }, } impl ErrorExt for Error { @@ -201,6 +256,13 @@ impl ErrorExt for Error { Error::TableRouteNotFound { .. } => StatusCode::TableNotFound, Error::TableRouteManager { source, .. } => source.status_code(), Error::UnexpectedLogicalRouteTable { source, .. } => source.status_code(), + Error::ConvertToVector { source, .. } => source.status_code(), + Error::EvaluateRecordBatch { .. } => StatusCode::Internal, + Error::ComputeArrowKernel { .. } => StatusCode::Internal, + Error::UnexpectedColumnType { .. } => StatusCode::Internal, + Error::ToDFSchema { .. } => StatusCode::Internal, + Error::CreatePhysicalExpr { .. } => StatusCode::Internal, + Error::UnsupportedPartitionExprValue { .. } => StatusCode::InvalidArguments, } } diff --git a/src/partition/src/expr.rs b/src/partition/src/expr.rs index bec9543e72..b758d6dcba 100644 --- a/src/partition/src/expr.rs +++ b/src/partition/src/expr.rs @@ -13,12 +13,23 @@ // limitations under the License. use std::fmt::{Debug, Display, Formatter}; +use std::sync::Arc; -use datatypes::value::Value; +use datafusion_common::{ScalarValue, ToDFSchema}; +use datafusion_expr::execution_props::ExecutionProps; +use datafusion_expr::Expr; +use datafusion_physical_expr::{create_physical_expr, PhysicalExpr}; +use datatypes::arrow; +use datatypes::value::{ + duration_to_scalar_value, time_to_scalar_value, timestamp_to_scalar_value, Value, +}; use serde::{Deserialize, Serialize}; +use snafu::ResultExt; use sql::statements::value_to_sql_value; use sqlparser::ast::{BinaryOperator as ParserBinaryOperator, Expr as ParserExpr, Ident}; +use crate::error; + /// Struct for partition expression. This can be converted back to sqlparser's [Expr]. /// by [`Self::to_parser_expr`]. /// @@ -37,6 +48,75 @@ pub enum Operand { Expr(PartitionExpr), } +pub fn col(column_name: impl Into) -> Operand { + Operand::Column(column_name.into()) +} + +impl From for Operand { + fn from(value: Value) -> Self { + Operand::Value(value) + } +} + +impl Operand { + pub fn try_as_logical_expr(&self) -> error::Result { + match self { + Self::Column(c) => Ok(datafusion_expr::col(c)), + Self::Value(v) => { + let scalar_value = match v { + Value::Boolean(v) => ScalarValue::Boolean(Some(*v)), + Value::UInt8(v) => ScalarValue::UInt8(Some(*v)), + Value::UInt16(v) => ScalarValue::UInt16(Some(*v)), + Value::UInt32(v) => ScalarValue::UInt32(Some(*v)), + Value::UInt64(v) => ScalarValue::UInt64(Some(*v)), + Value::Int8(v) => ScalarValue::Int8(Some(*v)), + Value::Int16(v) => ScalarValue::Int16(Some(*v)), + Value::Int32(v) => ScalarValue::Int32(Some(*v)), + Value::Int64(v) => ScalarValue::Int64(Some(*v)), + Value::Float32(v) => ScalarValue::Float32(Some(v.0)), + Value::Float64(v) => ScalarValue::Float64(Some(v.0)), + Value::String(v) => ScalarValue::Utf8(Some(v.as_utf8().to_string())), + Value::Binary(v) => ScalarValue::Binary(Some(v.to_vec())), + Value::Date(v) => ScalarValue::Date32(Some(v.val())), + Value::Null => ScalarValue::Null, + Value::Timestamp(t) => timestamp_to_scalar_value(t.unit(), Some(t.value())), + Value::Time(t) => time_to_scalar_value(*t.unit(), Some(t.value())).unwrap(), + Value::IntervalYearMonth(v) => ScalarValue::IntervalYearMonth(Some(v.to_i32())), + Value::IntervalDayTime(v) => ScalarValue::IntervalDayTime(Some((*v).into())), + Value::IntervalMonthDayNano(v) => { + ScalarValue::IntervalMonthDayNano(Some((*v).into())) + } + Value::Duration(d) => duration_to_scalar_value(d.unit(), Some(d.value())), + Value::Decimal128(d) => { + let (v, p, s) = d.to_scalar_value(); + ScalarValue::Decimal128(v, p, s) + } + other => { + return error::UnsupportedPartitionExprValueSnafu { + value: other.clone(), + } + .fail() + } + }; + Ok(datafusion_expr::lit(scalar_value)) + } + Self::Expr(e) => e.try_as_logical_expr(), + } + } + + pub fn lt(self, rhs: impl Into) -> PartitionExpr { + PartitionExpr::new(self, RestrictedOp::Lt, rhs.into()) + } + + pub fn gt_eq(self, rhs: impl Into) -> PartitionExpr { + PartitionExpr::new(self, RestrictedOp::GtEq, rhs.into()) + } + + pub fn eq(self, rhs: impl Into) -> PartitionExpr { + PartitionExpr::new(self, RestrictedOp::Eq, rhs.into()) + } +} + impl Display for Operand { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { @@ -140,6 +220,41 @@ impl PartitionExpr { right: Box::new(rhs), } } + + pub fn try_as_logical_expr(&self) -> error::Result { + let lhs = self.lhs.try_as_logical_expr()?; + let rhs = self.rhs.try_as_logical_expr()?; + + let expr = match &self.op { + RestrictedOp::And => datafusion_expr::and(lhs, rhs), + RestrictedOp::Or => datafusion_expr::or(lhs, rhs), + RestrictedOp::Gt => lhs.gt(rhs), + RestrictedOp::GtEq => lhs.gt_eq(rhs), + RestrictedOp::Lt => lhs.lt(rhs), + RestrictedOp::LtEq => lhs.lt_eq(rhs), + RestrictedOp::Eq => lhs.eq(rhs), + RestrictedOp::NotEq => lhs.not_eq(rhs), + }; + Ok(expr) + } + + pub fn try_as_physical_expr( + &self, + schema: &arrow::datatypes::SchemaRef, + ) -> error::Result> { + let df_schema = schema + .clone() + .to_dfschema_ref() + .context(error::ToDFSchemaSnafu)?; + let execution_props = &ExecutionProps::default(); + let expr = self.try_as_logical_expr()?; + create_physical_expr(&expr, &df_schema, execution_props) + .context(error::CreatePhysicalExprSnafu) + } + + pub fn and(self, rhs: PartitionExpr) -> PartitionExpr { + PartitionExpr::new(Operand::Expr(self), RestrictedOp::And, Operand::Expr(rhs)) + } } impl Display for PartitionExpr { diff --git a/src/partition/src/lib.rs b/src/partition/src/lib.rs index b1843a1093..bc56edc584 100644 --- a/src/partition/src/lib.rs +++ b/src/partition/src/lib.rs @@ -13,7 +13,7 @@ // limitations under the License. #![feature(assert_matches)] - +#![feature(let_chains)] //! Structs and traits for partitioning rule. pub mod error; diff --git a/src/partition/src/multi_dim.rs b/src/partition/src/multi_dim.rs index f47d71f98b..551fb6a8de 100644 --- a/src/partition/src/multi_dim.rs +++ b/src/partition/src/multi_dim.rs @@ -15,10 +15,18 @@ use std::any::Any; use std::cmp::Ordering; use std::collections::HashMap; +use std::sync::{Arc, RwLock}; +use datafusion_expr::ColumnarValue; +use datafusion_physical_expr::PhysicalExpr; +use datatypes::arrow; +use datatypes::arrow::array::{BooleanArray, BooleanBufferBuilder, RecordBatch}; +use datatypes::arrow::buffer::BooleanBuffer; +use datatypes::arrow::datatypes::Schema; use datatypes::prelude::Value; +use datatypes::vectors::{Helper, VectorRef}; use serde::{Deserialize, Serialize}; -use snafu::{ensure, OptionExt}; +use snafu::{ensure, OptionExt, ResultExt}; use store_api::storage::RegionNumber; use crate::error::{ @@ -28,6 +36,11 @@ use crate::error::{ use crate::expr::{Operand, PartitionExpr, RestrictedOp}; use crate::PartitionRule; +/// The default region number when no partition exprs are matched. +const DEFAULT_REGION: RegionNumber = 0; + +type PhysicalExprCache = Option<(Vec>, Arc)>; + /// Multi-Dimiension partition rule. RFC [here](https://github.com/GreptimeTeam/greptimedb/blob/main/docs/rfcs/2024-02-21-multi-dimension-partition-rule/rfc.md) /// /// This partition rule is defined by a set of simple expressions on the partition @@ -44,6 +57,9 @@ pub struct MultiDimPartitionRule { regions: Vec, /// Partition expressions. exprs: Vec, + /// Cache of physical expressions. + #[serde(skip)] + physical_expr_cache: RwLock, } impl MultiDimPartitionRule { @@ -63,6 +79,7 @@ impl MultiDimPartitionRule { name_to_index, regions, exprs, + physical_expr_cache: RwLock::new(None), }; let mut checker = RuleChecker::new(&rule); @@ -87,7 +104,7 @@ impl MultiDimPartitionRule { } // return the default region number - Ok(0) + Ok(DEFAULT_REGION) } fn evaluate_expr(&self, expr: &PartitionExpr, values: &[Value]) -> Result { @@ -134,6 +151,133 @@ impl MultiDimPartitionRule { Ok(result) } + + pub fn row_at(&self, cols: &[VectorRef], index: usize, row: &mut [Value]) -> Result<()> { + for (col_idx, col) in cols.iter().enumerate() { + row[col_idx] = col.get(index); + } + Ok(()) + } + + pub fn record_batch_to_cols(&self, record_batch: &RecordBatch) -> Result> { + self.partition_columns + .iter() + .map(|col_name| { + record_batch + .column_by_name(col_name) + .context(error::UndefinedColumnSnafu { column: col_name }) + .and_then(|array| { + Helper::try_into_vector(array).context(error::ConvertToVectorSnafu) + }) + }) + .collect::>>() + } + + pub fn split_record_batch_naive( + &self, + record_batch: &RecordBatch, + ) -> Result> { + let num_rows = record_batch.num_rows(); + + let mut result = self + .regions + .iter() + .map(|region| { + let mut builder = BooleanBufferBuilder::new(num_rows); + builder.append_n(num_rows, false); + (*region, builder) + }) + .collect::>(); + + let cols = self.record_batch_to_cols(record_batch)?; + let mut current_row = vec![Value::Null; self.partition_columns.len()]; + for row_idx in 0..num_rows { + self.row_at(&cols, row_idx, &mut current_row)?; + let current_region = self.find_region(¤t_row)?; + let region_mask = result + .get_mut(¤t_region) + .unwrap_or_else(|| panic!("Region {} must be initialized", current_region)); + region_mask.set_bit(row_idx, true); + } + + Ok(result + .into_iter() + .map(|(region, mut mask)| (region, BooleanArray::new(mask.finish(), None))) + .collect()) + } + + pub fn split_record_batch( + &self, + record_batch: &RecordBatch, + ) -> Result> { + let num_rows = record_batch.num_rows(); + let physical_exprs = { + let cache_read_guard = self.physical_expr_cache.read().unwrap(); + if let Some((cached_exprs, schema)) = cache_read_guard.as_ref() + && schema == record_batch.schema_ref() + { + cached_exprs.clone() + } else { + drop(cache_read_guard); // Release the read lock before acquiring write lock + + let schema = record_batch.schema(); + let new_cache = self + .exprs + .iter() + .map(|e| e.try_as_physical_expr(&schema)) + .collect::>>()?; + + let mut cache_write_guard = self.physical_expr_cache.write().unwrap(); + cache_write_guard.replace((new_cache.clone(), schema)); + new_cache + } + }; + + let mut result: HashMap = physical_exprs + .iter() + .zip(self.regions.iter()) + .map(|(expr, region_num)| { + let ColumnarValue::Array(column) = expr + .evaluate(record_batch) + .context(error::EvaluateRecordBatchSnafu)? + else { + unreachable!("Expected an array") + }; + Ok(( + *region_num, + column + .as_any() + .downcast_ref::() + .with_context(|| error::UnexpectedColumnTypeSnafu { + data_type: column.data_type().clone(), + })? + .clone(), + )) + }) + .collect::>()?; + + let mut selected = BooleanArray::new(BooleanBuffer::new_unset(num_rows), None); + for region_selection in result.values() { + selected = arrow::compute::kernels::boolean::or(&selected, region_selection) + .context(error::ComputeArrowKernelSnafu)?; + } + + // fast path: all rows are selected + if selected.true_count() == num_rows { + return Ok(result); + } + + // find unselected rows and assign to default region + let unselected = arrow::compute::kernels::boolean::not(&selected) + .context(error::ComputeArrowKernelSnafu)?; + let default_region_selection = result + .entry(DEFAULT_REGION) + .or_insert_with(|| unselected.clone()); + *default_region_selection = + arrow::compute::kernels::boolean::or(default_region_selection, &unselected) + .context(error::ComputeArrowKernelSnafu)?; + Ok(result) + } } impl PartitionRule for MultiDimPartitionRule { @@ -148,6 +292,13 @@ impl PartitionRule for MultiDimPartitionRule { fn find_region(&self, values: &[Value]) -> Result { self.find_region(values) } + + fn split_record_batch( + &self, + record_batch: &RecordBatch, + ) -> Result> { + self.split_record_batch(record_batch) + } } /// Helper for [RuleChecker] @@ -633,3 +784,155 @@ mod tests { assert!(rule.is_err()); } } + +#[cfg(test)] +mod test_split_record_batch { + use std::sync::Arc; + + use datatypes::arrow::array::{Int64Array, StringArray}; + use datatypes::arrow::datatypes::{DataType, Field, Schema}; + use datatypes::arrow::record_batch::RecordBatch; + use rand::Rng; + + use super::*; + use crate::expr::col; + + fn test_schema() -> Arc { + Arc::new(Schema::new(vec![ + Field::new("host", DataType::Utf8, false), + Field::new("value", DataType::Int64, false), + ])) + } + + fn generate_random_record_batch(num_rows: usize) -> RecordBatch { + let schema = test_schema(); + let mut rng = rand::thread_rng(); + let mut host_array = Vec::with_capacity(num_rows); + let mut value_array = Vec::with_capacity(num_rows); + for _ in 0..num_rows { + host_array.push(format!("server{}", rng.gen_range(0..20))); + value_array.push(rng.gen_range(0..20)); + } + let host_array = StringArray::from(host_array); + let value_array = Int64Array::from(value_array); + RecordBatch::try_new(schema, vec![Arc::new(host_array), Arc::new(value_array)]).unwrap() + } + + #[test] + fn test_split_record_batch_by_one_column() { + // Create a simple MultiDimPartitionRule + let rule = MultiDimPartitionRule::try_new( + vec!["host".to_string(), "value".to_string()], + vec![0, 1], + vec![ + col("host").lt(Value::String("server1".into())), + col("host").gt_eq(Value::String("server1".into())), + ], + ) + .unwrap(); + + let batch = generate_random_record_batch(1000); + // Split the batch + let result = rule.split_record_batch(&batch).unwrap(); + let expected = rule.split_record_batch_naive(&batch).unwrap(); + assert_eq!(result.len(), expected.len()); + for (region, value) in &result { + assert_eq!( + value, + expected.get(region).unwrap(), + "failed on region: {}", + region + ); + } + } + + #[test] + fn test_split_record_batch_empty() { + // Create a simple MultiDimPartitionRule + let rule = MultiDimPartitionRule::try_new( + vec!["host".to_string()], + vec![1], + vec![PartitionExpr::new( + Operand::Column("host".to_string()), + RestrictedOp::Eq, + Operand::Value(Value::String("server1".into())), + )], + ) + .unwrap(); + + let schema = test_schema(); + let host_array = StringArray::from(Vec::<&str>::new()); + let value_array = Int64Array::from(Vec::::new()); + let batch = RecordBatch::try_new(schema, vec![Arc::new(host_array), Arc::new(value_array)]) + .unwrap(); + + let result = rule.split_record_batch(&batch).unwrap(); + assert_eq!(result.len(), 1); + } + + #[test] + fn test_split_record_batch_by_two_columns() { + let rule = MultiDimPartitionRule::try_new( + vec!["host".to_string(), "value".to_string()], + vec![0, 1, 2, 3], + vec![ + col("host") + .lt(Value::String("server10".into())) + .and(col("value").lt(Value::Int64(10))), + col("host") + .lt(Value::String("server10".into())) + .and(col("value").gt_eq(Value::Int64(10))), + col("host") + .gt_eq(Value::String("server10".into())) + .and(col("value").lt(Value::Int64(10))), + col("host") + .gt_eq(Value::String("server10".into())) + .and(col("value").gt_eq(Value::Int64(10))), + ], + ) + .unwrap(); + + let batch = generate_random_record_batch(1000); + let result = rule.split_record_batch(&batch).unwrap(); + let expected = rule.split_record_batch_naive(&batch).unwrap(); + assert_eq!(result.len(), expected.len()); + for (region, value) in &result { + assert_eq!(value, expected.get(region).unwrap()); + } + } + + #[test] + fn test_default_region() { + let rule = MultiDimPartitionRule::try_new( + vec!["host".to_string(), "value".to_string()], + vec![0, 1, 2, 3], + vec![ + col("host") + .lt(Value::String("server10".into())) + .and(col("value").eq(Value::Int64(10))), + col("host") + .lt(Value::String("server10".into())) + .and(col("value").eq(Value::Int64(20))), + col("host") + .gt_eq(Value::String("server10".into())) + .and(col("value").eq(Value::Int64(10))), + col("host") + .gt_eq(Value::String("server10".into())) + .and(col("value").eq(Value::Int64(20))), + ], + ) + .unwrap(); + + let schema = test_schema(); + let host_array = StringArray::from(vec!["server1", "server1", "server1", "server100"]); + let value_array = Int64Array::from(vec![10, 20, 30, 10]); + let batch = RecordBatch::try_new(schema, vec![Arc::new(host_array), Arc::new(value_array)]) + .unwrap(); + let result = rule.split_record_batch(&batch).unwrap(); + let expected = rule.split_record_batch_naive(&batch).unwrap(); + assert_eq!(result.len(), expected.len()); + for (region, value) in &result { + assert_eq!(value, expected.get(region).unwrap()); + } + } +} diff --git a/src/partition/src/partition.rs b/src/partition/src/partition.rs index ac965034c6..a190d33eca 100644 --- a/src/partition/src/partition.rs +++ b/src/partition/src/partition.rs @@ -13,11 +13,13 @@ // limitations under the License. use std::any::Any; +use std::collections::HashMap; use std::fmt::{Debug, Display, Formatter}; use std::sync::Arc; use common_meta::rpc::router::Partition as MetaPartition; use datafusion_expr::Operator; +use datatypes::arrow::array::{BooleanArray, RecordBatch}; use datatypes::prelude::Value; use itertools::Itertools; use serde::{Deserialize, Serialize}; @@ -37,6 +39,13 @@ pub trait PartitionRule: Sync + Send { /// /// Note that the `values` should have the same length as the `partition_columns`. fn find_region(&self, values: &[Value]) -> Result; + + /// Split the record batch into multiple regions by the partition values. + /// The result is a map from region number to a boolean array, where the boolean array is true for the rows that match the partition values. + fn split_record_batch( + &self, + record_batch: &RecordBatch, + ) -> Result>; } /// The right bound(exclusive) of partition range. diff --git a/src/partition/src/splitter.rs b/src/partition/src/splitter.rs index f62210a6b5..87c04a4942 100644 --- a/src/partition/src/splitter.rs +++ b/src/partition/src/splitter.rs @@ -136,6 +136,7 @@ mod tests { use api::v1::value::ValueData; use api::v1::{ColumnDataType, SemanticType}; + use datatypes::arrow::array::BooleanArray; use serde::{Deserialize, Serialize}; use super::*; @@ -209,6 +210,13 @@ mod tests { Ok(val.parse::().unwrap() % 2) } + + fn split_record_batch( + &self, + _record_batch: &datatypes::arrow::array::RecordBatch, + ) -> Result> { + unimplemented!() + } } #[derive(Debug, Serialize, Deserialize)] @@ -232,6 +240,13 @@ mod tests { Ok(val) } + + fn split_record_batch( + &self, + _record_batch: &datatypes::arrow::array::RecordBatch, + ) -> Result> { + unimplemented!() + } } #[derive(Debug, Serialize, Deserialize)] @@ -249,8 +264,14 @@ mod tests { fn find_region(&self, _values: &[Value]) -> Result { Ok(0) } - } + fn split_record_batch( + &self, + _record_batch: &datatypes::arrow::array::RecordBatch, + ) -> Result> { + unimplemented!() + } + } #[test] fn test_writer_splitter() { let rows = mock_rows(); diff --git a/src/pipeline/src/error.rs b/src/pipeline/src/error.rs index b590e6847d..099d2b5100 100644 --- a/src/pipeline/src/error.rs +++ b/src/pipeline/src/error.rs @@ -517,12 +517,7 @@ pub enum Error { #[snafu(implicit)] location: Location, }, - #[snafu(display("Unsupported number type: {value}"))] - ValueUnsupportedNumberType { - value: serde_json::Number, - #[snafu(implicit)] - location: Location, - }, + #[snafu(display("Unsupported yaml type: {value:?}"))] ValueUnsupportedYamlType { value: yaml_rust::Yaml, @@ -574,6 +569,13 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + #[snafu(display("Failed to parse json"))] + JsonParse { + #[snafu(source)] + error: serde_json::Error, + #[snafu(implicit)] + location: Location, + }, #[snafu(display("Column datatype mismatch. For column: {column}, expected datatype: {expected}, actual datatype: {actual}"))] IdentifyPipelineColumnTypeMismatch { column: String, @@ -808,7 +810,6 @@ impl ErrorExt for Error { | ValueParseFloat { .. } | ValueParseBoolean { .. } | ValueDefaultValueUnsupported { .. } - | ValueUnsupportedNumberType { .. } | ValueUnsupportedYamlType { .. } | ValueYamlKeyMustBeString { .. } | YamlLoad { .. } @@ -818,6 +819,7 @@ impl ErrorExt for Error { | UnsupportedIndexType { .. } | UnsupportedNumberType { .. } | IdentifyPipelineColumnTypeMismatch { .. } + | JsonParse { .. } | JsonPathParse { .. } | JsonPathParseResultIndex { .. } | FieldRequiredForDispatcher diff --git a/src/pipeline/src/etl/processor.rs b/src/pipeline/src/etl/processor.rs index 240b477efd..aa10fd9e78 100644 --- a/src/pipeline/src/etl/processor.rs +++ b/src/pipeline/src/etl/processor.rs @@ -21,6 +21,7 @@ pub mod dissect; pub mod epoch; pub mod gsub; pub mod join; +pub mod json_parse; pub mod json_path; pub mod letter; pub mod regex; @@ -50,6 +51,7 @@ use crate::error::{ ProcessorMustBeMapSnafu, ProcessorMustHaveStringKeySnafu, Result, UnsupportedProcessorSnafu, }; use crate::etl::field::{Field, Fields}; +use crate::etl::processor::json_parse::JsonParseProcessor; use crate::etl::processor::simple_extract::SimpleExtractProcessor; use crate::etl::PipelineMap; @@ -98,6 +100,7 @@ pub enum ProcessorKind { Epoch(EpochProcessor), Date(DateProcessor), JsonPath(JsonPathProcessor), + JsonParse(JsonParseProcessor), SimpleJsonPath(SimpleExtractProcessor), Decolorize(DecolorizeProcessor), Digest(DigestProcessor), @@ -179,6 +182,9 @@ fn parse_processor(doc: &yaml_rust::Yaml) -> Result { simple_extract::PROCESSOR_SIMPLE_EXTRACT => { ProcessorKind::SimpleJsonPath(SimpleExtractProcessor::try_from(value)?) } + json_parse::PROCESSOR_JSON_PARSE => { + ProcessorKind::JsonParse(JsonParseProcessor::try_from(value)?) + } _ => return UnsupportedProcessorSnafu { processor: str_key }.fail(), }; diff --git a/src/pipeline/src/etl/processor/dissect.rs b/src/pipeline/src/etl/processor/dissect.rs index 8c31f42ace..8034d984d2 100644 --- a/src/pipeline/src/etl/processor/dissect.rs +++ b/src/pipeline/src/etl/processor/dissect.rs @@ -325,7 +325,7 @@ impl std::str::FromStr for Pattern { impl Pattern { fn check(&self) -> Result<()> { - if self.len() == 0 { + if self.is_empty() { return DissectEmptyPatternSnafu.fail(); } diff --git a/src/pipeline/src/etl/processor/json_parse.rs b/src/pipeline/src/etl/processor/json_parse.rs new file mode 100644 index 0000000000..7361ee9151 --- /dev/null +++ b/src/pipeline/src/etl/processor/json_parse.rs @@ -0,0 +1,147 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use snafu::{OptionExt as _, ResultExt}; + +use crate::error::{ + Error, FieldMustBeTypeSnafu, JsonParseSnafu, KeyMustBeStringSnafu, ProcessorMissingFieldSnafu, + ProcessorUnsupportedValueSnafu, Result, +}; +use crate::etl::field::Fields; +use crate::etl::processor::{ + yaml_bool, yaml_new_field, yaml_new_fields, FIELDS_NAME, FIELD_NAME, IGNORE_MISSING_NAME, +}; +use crate::{json_to_map, PipelineMap, Processor, Value}; + +pub(crate) const PROCESSOR_JSON_PARSE: &str = "json_parse"; + +#[derive(Debug, Default)] +pub struct JsonParseProcessor { + fields: Fields, + ignore_missing: bool, +} + +impl TryFrom<&yaml_rust::yaml::Hash> for JsonParseProcessor { + type Error = Error; + + fn try_from(value: &yaml_rust::yaml::Hash) -> std::result::Result { + let mut fields = Fields::default(); + let mut ignore_missing = false; + + for (k, v) in value.iter() { + let key = k + .as_str() + .with_context(|| KeyMustBeStringSnafu { k: k.clone() })?; + match key { + FIELD_NAME => { + fields = Fields::one(yaml_new_field(v, FIELD_NAME)?); + } + FIELDS_NAME => { + fields = yaml_new_fields(v, FIELDS_NAME)?; + } + IGNORE_MISSING_NAME => { + ignore_missing = yaml_bool(v, IGNORE_MISSING_NAME)?; + } + _ => {} + } + } + + let processor = JsonParseProcessor { + fields, + ignore_missing, + }; + + Ok(processor) + } +} + +impl JsonParseProcessor { + fn process_field(&self, val: &Value) -> Result { + let Some(json_str) = val.as_str() else { + return FieldMustBeTypeSnafu { + field: val.to_str_type(), + ty: "string", + } + .fail(); + }; + let parsed: serde_json::Value = serde_json::from_str(json_str).context(JsonParseSnafu)?; + match parsed { + serde_json::Value::Object(_) => Ok(Value::Map(json_to_map(parsed)?.into())), + serde_json::Value::Array(arr) => Ok(Value::Array(arr.try_into()?)), + _ => ProcessorUnsupportedValueSnafu { + processor: self.kind(), + val: val.to_str_type(), + } + .fail(), + } + } +} + +impl Processor for JsonParseProcessor { + fn kind(&self) -> &str { + PROCESSOR_JSON_PARSE + } + + fn ignore_missing(&self) -> bool { + self.ignore_missing + } + + fn exec_mut(&self, val: &mut PipelineMap) -> Result<()> { + for field in self.fields.iter() { + let index = field.input_field(); + match val.get(index) { + Some(v) => { + let processed = self.process_field(v)?; + let output_index = field.target_or_input_field(); + val.insert(output_index.to_string(), processed); + } + None => { + if !self.ignore_missing { + return ProcessorMissingFieldSnafu { + processor: self.kind(), + field: field.input_field(), + } + .fail(); + } + } + } + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + + #[test] + fn test_json_parse() { + use super::*; + use crate::Value; + + let processor = JsonParseProcessor { + ..Default::default() + }; + + let result = processor + .process_field(&Value::String(r#"{"hello": "world"}"#.to_string())) + .unwrap(); + + let expected = Value::Map(crate::Map::one( + "hello".to_string(), + Value::String("world".to_string()), + )); + + assert_eq!(result, expected); + } +} diff --git a/src/pipeline/src/etl/processor/simple_extract.rs b/src/pipeline/src/etl/processor/simple_extract.rs index ebac88f937..6015fc4591 100644 --- a/src/pipeline/src/etl/processor/simple_extract.rs +++ b/src/pipeline/src/etl/processor/simple_extract.rs @@ -126,7 +126,7 @@ impl Processor for SimpleExtractProcessor { mod test { #[test] - fn test_json_path() { + fn test_simple_extract() { use super::*; use crate::{Map, Value}; diff --git a/src/pipeline/src/etl/transform/transformer/greptime.rs b/src/pipeline/src/etl/transform/transformer/greptime.rs index fa0f9c3b49..033feda0c5 100644 --- a/src/pipeline/src/etl/transform/transformer/greptime.rs +++ b/src/pipeline/src/etl/transform/transformer/greptime.rs @@ -25,6 +25,7 @@ use api::v1::{ColumnDataType, ColumnDataTypeExtension, JsonTypeExtension, Semant use coerce::{coerce_columns, coerce_value}; use greptime_proto::v1::{ColumnSchema, Row, Rows, Value as GreptimeValue}; use itertools::Itertools; +use once_cell::sync::OnceCell; use serde_json::Number; use crate::error::{ @@ -54,8 +55,12 @@ pub struct GreptimeTransformer { /// Parameters that can be used to configure the greptime pipelines. #[derive(Debug, Clone, Default)] pub struct GreptimePipelineParams { - /// The options for configuring the greptime pipelines. - pub options: HashMap, + /// The original options for configuring the greptime pipelines. + /// This should not be used directly, instead, use the parsed shortcut option values. + options: HashMap, + + /// Parsed shortcut option values + pub flatten_json_object: OnceCell, } impl GreptimePipelineParams { @@ -70,15 +75,20 @@ impl GreptimePipelineParams { .map(|(k, v)| (k.to_string(), v.to_string())) .collect::>(); - Self { options } + Self { + options, + flatten_json_object: OnceCell::new(), + } } /// Whether to flatten the JSON object. pub fn flatten_json_object(&self) -> bool { - self.options - .get("flatten_json_object") - .map(|v| v == "true") - .unwrap_or(false) + *self.flatten_json_object.get_or_init(|| { + self.options + .get("flatten_json_object") + .map(|v| v == "true") + .unwrap_or(false) + }) } } diff --git a/src/pipeline/src/etl/transform/transformer/greptime/coerce.rs b/src/pipeline/src/etl/transform/transformer/greptime/coerce.rs index 71c83dc477..41172a2876 100644 --- a/src/pipeline/src/etl/transform/transformer/greptime/coerce.rs +++ b/src/pipeline/src/etl/transform/transformer/greptime/coerce.rs @@ -436,7 +436,8 @@ fn coerce_string_value(s: &String, transform: &Transform) -> Result CoerceUnsupportedEpochTypeSnafu { ty: "String" }.fail(), }, - Value::Array(_) | Value::Map(_) => CoerceJsonTypeToSnafu { + Value::Array(_) | Value::Map(_) => CoerceStringToTypeSnafu { + s, ty: transform.type_.to_str_type(), } .fail(), diff --git a/src/pipeline/src/etl/value.rs b/src/pipeline/src/etl/value.rs index 5f44b264b3..51ac76c4a5 100644 --- a/src/pipeline/src/etl/value.rs +++ b/src/pipeline/src/etl/value.rs @@ -29,9 +29,9 @@ use snafu::{OptionExt, ResultExt}; pub use time::Timestamp; use crate::error::{ - Error, Result, ValueDefaultValueUnsupportedSnafu, ValueInvalidResolutionSnafu, - ValueParseBooleanSnafu, ValueParseFloatSnafu, ValueParseIntSnafu, ValueParseTypeSnafu, - ValueUnsupportedNumberTypeSnafu, ValueUnsupportedYamlTypeSnafu, ValueYamlKeyMustBeStringSnafu, + Error, Result, UnsupportedNumberTypeSnafu, ValueDefaultValueUnsupportedSnafu, + ValueInvalidResolutionSnafu, ValueParseBooleanSnafu, ValueParseFloatSnafu, ValueParseIntSnafu, + ValueParseTypeSnafu, ValueUnsupportedYamlTypeSnafu, ValueYamlKeyMustBeStringSnafu, }; use crate::etl::PipelineMap; @@ -413,7 +413,7 @@ impl TryFrom for Value { } else if let Some(v) = v.as_f64() { Ok(Value::Float64(v)) } else { - ValueUnsupportedNumberTypeSnafu { value: v }.fail() + UnsupportedNumberTypeSnafu { value: v }.fail() } } serde_json::Value::String(v) => Ok(Value::String(v)), diff --git a/src/pipeline/src/etl/value/array.rs b/src/pipeline/src/etl/value/array.rs index 4cba167215..0658d502df 100644 --- a/src/pipeline/src/etl/value/array.rs +++ b/src/pipeline/src/etl/value/array.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::error::{Error, Result}; use crate::etl::value::Value; #[derive(Debug, Clone, PartialEq, Default)] @@ -66,3 +67,15 @@ impl From> for Array { Array { values } } } + +impl TryFrom> for Array { + type Error = Error; + + fn try_from(value: Vec) -> Result { + let values = value + .into_iter() + .map(|v| v.try_into()) + .collect::>>()?; + Ok(Array { values }) + } +} diff --git a/src/pipeline/src/lib.rs b/src/pipeline/src/lib.rs index 857c321d33..d656094c2e 100644 --- a/src/pipeline/src/lib.rs +++ b/src/pipeline/src/lib.rs @@ -29,7 +29,7 @@ pub use etl::{ DispatchedTo, Pipeline, PipelineExecOutput, PipelineMap, }; pub use manager::{ - pipeline_operator, table, util, IdentityTimeIndex, PipelineDefinition, PipelineInfo, - PipelineRef, PipelineTableRef, PipelineVersion, PipelineWay, SelectInfo, + pipeline_operator, table, util, IdentityTimeIndex, PipelineContext, PipelineDefinition, + PipelineInfo, PipelineRef, PipelineTableRef, PipelineVersion, PipelineWay, SelectInfo, GREPTIME_INTERNAL_IDENTITY_PIPELINE_NAME, GREPTIME_INTERNAL_TRACE_PIPELINE_V1_NAME, }; diff --git a/src/pipeline/src/manager.rs b/src/pipeline/src/manager.rs index b929cf3c23..3928e00b35 100644 --- a/src/pipeline/src/manager.rs +++ b/src/pipeline/src/manager.rs @@ -26,7 +26,7 @@ use util::to_pipeline_version; use crate::error::{CastTypeSnafu, InvalidCustomTimeIndexSnafu, PipelineMissingSnafu, Result}; use crate::etl::value::time::{MS_RESOLUTION, NS_RESOLUTION, S_RESOLUTION, US_RESOLUTION}; use crate::table::PipelineTable; -use crate::{Pipeline, Value}; +use crate::{GreptimePipelineParams, Pipeline, Value}; pub mod pipeline_operator; pub mod table; @@ -104,6 +104,22 @@ impl PipelineDefinition { } } +pub struct PipelineContext<'a> { + pub pipeline_definition: &'a PipelineDefinition, + pub pipeline_param: &'a GreptimePipelineParams, +} + +impl<'a> PipelineContext<'a> { + pub fn new( + pipeline_definition: &'a PipelineDefinition, + pipeline_param: &'a GreptimePipelineParams, + ) -> Self { + Self { + pipeline_definition, + pipeline_param, + } + } +} pub enum PipelineWay { OtlpLogDirect(Box), Pipeline(PipelineDefinition), diff --git a/src/pipeline/src/manager/pipeline_operator.rs b/src/pipeline/src/manager/pipeline_operator.rs index f98bab6b6c..c5d11574b7 100644 --- a/src/pipeline/src/manager/pipeline_operator.rs +++ b/src/pipeline/src/manager/pipeline_operator.rs @@ -20,6 +20,7 @@ use api::v1::CreateTableExpr; use catalog::{CatalogManagerRef, RegisterSystemTableRequest}; use common_catalog::consts::{default_engine, DEFAULT_PRIVATE_SCHEMA_NAME}; use common_telemetry::info; +use datatypes::timestamp::TimestampNanosecond; use futures::FutureExt; use operator::insert::InserterRef; use operator::statement::StatementExecutorRef; @@ -198,6 +199,29 @@ impl PipelineOperator { .await } + /// Get a original pipeline by name. + pub async fn get_pipeline_str( + &self, + name: &str, + version: PipelineVersion, + query_ctx: QueryContextRef, + ) -> Result<(String, TimestampNanosecond)> { + let schema = query_ctx.current_schema(); + self.create_pipeline_table_if_not_exists(query_ctx.clone()) + .await?; + + let timer = Instant::now(); + self.get_pipeline_table_from_cache(query_ctx.current_catalog()) + .context(PipelineTableNotFoundSnafu)? + .get_pipeline_str(&schema, name, version) + .inspect(|re| { + METRIC_PIPELINE_RETRIEVE_HISTOGRAM + .with_label_values(&[&re.is_ok().to_string()]) + .observe(timer.elapsed().as_secs_f64()) + }) + .await + } + /// Insert a pipeline into the pipeline table. pub async fn insert_pipeline( &self, diff --git a/src/pipeline/src/manager/table.rs b/src/pipeline/src/manager/table.rs index f01742d7c2..649bab9f6e 100644 --- a/src/pipeline/src/manager/table.rs +++ b/src/pipeline/src/manager/table.rs @@ -69,6 +69,7 @@ pub struct PipelineTable { table: TableRef, query_engine: QueryEngineRef, pipelines: Cache>, + original_pipelines: Cache, } impl PipelineTable { @@ -88,6 +89,10 @@ impl PipelineTable { .max_capacity(PIPELINES_CACHE_SIZE) .time_to_live(PIPELINES_CACHE_TTL) .build(), + original_pipelines: Cache::builder() + .max_capacity(PIPELINES_CACHE_SIZE) + .time_to_live(PIPELINES_CACHE_TTL) + .build(), } } @@ -273,10 +278,7 @@ impl PipelineTable { return Ok(pipeline); } - let pipeline = self - .find_pipeline(schema, name, version) - .await? - .context(PipelineNotFoundSnafu { name, version })?; + let pipeline = self.get_pipeline_str(schema, name, version).await?; let compiled_pipeline = Arc::new(Self::compile_pipeline(&pipeline.0)?); self.pipelines.insert( @@ -286,6 +288,31 @@ impl PipelineTable { Ok(compiled_pipeline) } + /// Get a original pipeline by name. + /// If the pipeline is not in the cache, it will be get from table and compiled and inserted into the cache. + pub async fn get_pipeline_str( + &self, + schema: &str, + name: &str, + version: PipelineVersion, + ) -> Result<(String, TimestampNanosecond)> { + if let Some(pipeline) = self + .original_pipelines + .get(&generate_pipeline_cache_key(schema, name, version)) + { + return Ok(pipeline); + } + let pipeline = self + .find_pipeline(schema, name, version) + .await? + .context(PipelineNotFoundSnafu { name, version })?; + self.original_pipelines.insert( + generate_pipeline_cache_key(schema, name, version), + pipeline.clone(), + ); + Ok(pipeline) + } + /// Insert a pipeline into the pipeline table and compile it. /// The compiled pipeline will be inserted into the cache. pub async fn insert_and_compile( @@ -310,6 +337,15 @@ impl PipelineTable { generate_pipeline_cache_key(schema, name, Some(TimestampNanosecond(version))), compiled_pipeline.clone(), ); + + self.original_pipelines.insert( + generate_pipeline_cache_key(schema, name, None), + (pipeline.to_owned(), TimestampNanosecond(version)), + ); + self.original_pipelines.insert( + generate_pipeline_cache_key(schema, name, Some(TimestampNanosecond(version))), + (pipeline.to_owned(), TimestampNanosecond(version)), + ); } Ok((version, compiled_pipeline)) @@ -392,6 +428,10 @@ impl PipelineTable { .remove(&generate_pipeline_cache_key(schema, name, version)); self.pipelines .remove(&generate_pipeline_cache_key(schema, name, None)); + self.original_pipelines + .remove(&generate_pipeline_cache_key(schema, name, version)); + self.original_pipelines + .remove(&generate_pipeline_cache_key(schema, name, None)); Ok(Some(())) } diff --git a/src/pipeline/tests/common.rs b/src/pipeline/tests/common.rs index 0361f4e41c..781f70f0d5 100644 --- a/src/pipeline/tests/common.rs +++ b/src/pipeline/tests/common.rs @@ -56,6 +56,7 @@ pub fn parse_and_exec(input_str: &str, pipeline_yaml: &str) -> Rows { } /// test util function to create column schema +#[allow(dead_code)] pub fn make_column_schema( column_name: String, datatype: ColumnDataType, diff --git a/src/pipeline/tests/json_parse.rs b/src/pipeline/tests/json_parse.rs new file mode 100644 index 0000000000..bd43d9754c --- /dev/null +++ b/src/pipeline/tests/json_parse.rs @@ -0,0 +1,178 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod common; + +use std::borrow::Cow; + +use api::v1::value::ValueData; +use api::v1::ColumnDataType; + +const INPUT_VALUE_OBJ: &str = r#" +[ + { + "commit": "{\"commitTime\": \"1573840000.000\", \"commitAuthor\": \"test\"}" + } +] +"#; + +const INPUT_VALUE_ARR: &str = r#" +[ + { + "commit": "[\"test1\", \"test2\"]" + } +] +"#; + +#[test] +fn test_json_parse_inplace() { + let pipeline_yaml = r#" +--- +processors: + - json_parse: + field: commit + +transform: + - field: commit + type: json +"#; + + let output = common::parse_and_exec(INPUT_VALUE_OBJ, pipeline_yaml); + + // check schema + assert_eq!(output.schema[0].column_name, "commit"); + let type_id: i32 = ColumnDataType::Binary.into(); + assert_eq!(output.schema[0].datatype, type_id); + + // check value + let ValueData::BinaryValue(json_value) = output.rows[0].values[0].value_data.clone().unwrap() + else { + panic!("expect binary value"); + }; + let v = jsonb::from_slice(&json_value).unwrap(); + + let mut expected = jsonb::Object::new(); + expected.insert( + "commitTime".to_string(), + jsonb::Value::String(Cow::Borrowed("1573840000.000")), + ); + expected.insert( + "commitAuthor".to_string(), + jsonb::Value::String(Cow::Borrowed("test")), + ); + assert_eq!(v, jsonb::Value::Object(expected)); +} + +#[test] +fn test_json_parse_new_var() { + let pipeline_yaml = r#" +--- +processors: + - json_parse: + field: commit, commit_json + +transform: + - field: commit_json + type: json +"#; + + let output = common::parse_and_exec(INPUT_VALUE_OBJ, pipeline_yaml); + + // check schema + assert_eq!(output.schema[0].column_name, "commit_json"); + let type_id: i32 = ColumnDataType::Binary.into(); + assert_eq!(output.schema[0].datatype, type_id); + + // check value + let ValueData::BinaryValue(json_value) = output.rows[0].values[0].value_data.clone().unwrap() + else { + panic!("expect binary value"); + }; + let v = jsonb::from_slice(&json_value).unwrap(); + + let mut expected = jsonb::Object::new(); + expected.insert( + "commitTime".to_string(), + jsonb::Value::String(Cow::Borrowed("1573840000.000")), + ); + expected.insert( + "commitAuthor".to_string(), + jsonb::Value::String(Cow::Borrowed("test")), + ); + assert_eq!(v, jsonb::Value::Object(expected)); +} + +#[test] +fn test_json_parse_with_simple_extractor() { + let pipeline_yaml = r#" +--- +processors: + - json_parse: + field: commit, commit_json + - simple_extract: + field: commit_json, commit_author + key: "commitAuthor" + + +transform: + - field: commit_author + type: string +"#; + + let output = common::parse_and_exec(INPUT_VALUE_OBJ, pipeline_yaml); + + // check schema + assert_eq!(output.schema[0].column_name, "commit_author"); + let type_id: i32 = ColumnDataType::String.into(); + assert_eq!(output.schema[0].datatype, type_id); + + assert_eq!( + output.rows[0].values[0].value_data, + Some(ValueData::StringValue("test".to_string())) + ); +} + +#[test] +fn test_json_parse_array() { + let pipeline_yaml = r#" +--- +processors: + - json_parse: + field: commit + +transform: + - field: commit + type: json +"#; + + let output = common::parse_and_exec(INPUT_VALUE_ARR, pipeline_yaml); + + // check schema + assert_eq!(output.schema[0].column_name, "commit"); + let type_id: i32 = ColumnDataType::Binary.into(); + assert_eq!(output.schema[0].datatype, type_id); + + // check value + let ValueData::BinaryValue(json_value) = output.rows[0].values[0].value_data.clone().unwrap() + else { + panic!("expect binary value"); + }; + let v = jsonb::from_slice(&json_value).unwrap(); + + let expected = jsonb::Value::Array(vec![ + jsonb::Value::String(Cow::Borrowed("test1")), + jsonb::Value::String(Cow::Borrowed("test2")), + ]); + assert_eq!(v, expected); +} diff --git a/src/pipeline/tests/simple_extract.rs b/src/pipeline/tests/simple_extract.rs index 5e989f0365..ee2fbcbcae 100644 --- a/src/pipeline/tests/simple_extract.rs +++ b/src/pipeline/tests/simple_extract.rs @@ -34,7 +34,7 @@ lazy_static! { } #[test] -fn test_gsub() { +fn test_simple_extract() { let input_value_str = r#" [ { diff --git a/src/promql/src/functions.rs b/src/promql/src/functions.rs index 0294214842..fee6387d20 100644 --- a/src/promql/src/functions.rs +++ b/src/promql/src/functions.rs @@ -44,13 +44,13 @@ pub use quantile_aggr::{quantile_udaf, QUANTILE_NAME}; pub use resets::Resets; pub use round::Round; +/// Extracts an array from a `ColumnarValue`. +/// +/// If the `ColumnarValue` is a scalar, it converts it to an array of size 1. pub(crate) fn extract_array(columnar_value: &ColumnarValue) -> Result { - if let ColumnarValue::Array(array) = columnar_value { - Ok(array.clone()) - } else { - Err(DataFusionError::Execution( - "expect array as input, found scalar value".to_string(), - )) + match columnar_value { + ColumnarValue::Array(array) => Ok(array.clone()), + ColumnarValue::Scalar(scalar) => Ok(scalar.to_array_of_size(1)?), } } diff --git a/src/promql/src/functions/aggr_over_time.rs b/src/promql/src/functions/aggr_over_time.rs index 298959ef35..841f28e0df 100644 --- a/src/promql/src/functions/aggr_over_time.rs +++ b/src/promql/src/functions/aggr_over_time.rs @@ -231,6 +231,7 @@ mod test { AvgOverTime::scalar_udf(), ts_array, value_array, + vec![], vec![ Some(49.9999995), Some(45.8618844), @@ -253,6 +254,7 @@ mod test { MinOverTime::scalar_udf(), ts_array, value_array, + vec![], vec![ Some(12.345678), Some(12.345678), @@ -275,6 +277,7 @@ mod test { MaxOverTime::scalar_udf(), ts_array, value_array, + vec![], vec![ Some(87.654321), Some(87.654321), @@ -297,6 +300,7 @@ mod test { SumOverTime::scalar_udf(), ts_array, value_array, + vec![], vec![ Some(99.999999), Some(229.309422), @@ -319,6 +323,7 @@ mod test { CountOverTime::scalar_udf(), ts_array, value_array, + vec![], vec![ Some(2.0), Some(5.0), @@ -341,6 +346,7 @@ mod test { LastOverTime::scalar_udf(), ts_array, value_array, + vec![], vec![ Some(87.654321), Some(70.710678), @@ -363,6 +369,7 @@ mod test { AbsentOverTime::scalar_udf(), ts_array, value_array, + vec![], vec![ None, None, @@ -385,6 +392,7 @@ mod test { PresentOverTime::scalar_udf(), ts_array, value_array, + vec![], vec![ Some(1.0), Some(1.0), @@ -407,6 +415,7 @@ mod test { StdvarOverTime::scalar_udf(), ts_array, value_array, + vec![], vec![ Some(1417.8479276253622), Some(808.999919713209), @@ -442,6 +451,7 @@ mod test { StdvarOverTime::scalar_udf(), RangeArray::from_ranges(ts_array, ranges).unwrap(), RangeArray::from_ranges(values_array, ranges).unwrap(), + vec![], vec![Some(0.0), Some(10.559999999999999)], ); } @@ -453,6 +463,7 @@ mod test { StddevOverTime::scalar_udf(), ts_array, value_array, + vec![], vec![ Some(37.6543215), Some(28.442923895289123), @@ -488,6 +499,7 @@ mod test { StddevOverTime::scalar_udf(), RangeArray::from_ranges(ts_array, ranges).unwrap(), RangeArray::from_ranges(values_array, ranges).unwrap(), + vec![], vec![Some(0.0), Some(3.249615361854384)], ); } diff --git a/src/promql/src/functions/changes.rs b/src/promql/src/functions/changes.rs index 743f941652..21819436e6 100644 --- a/src/promql/src/functions/changes.rs +++ b/src/promql/src/functions/changes.rs @@ -90,6 +90,7 @@ mod test { Changes::scalar_udf(), ts_array_1, value_array_1, + vec![], vec![Some(0.0), Some(3.0), Some(5.0), Some(8.0), None], ); @@ -101,6 +102,7 @@ mod test { Changes::scalar_udf(), ts_array_2, value_array_2, + vec![], vec![Some(0.0), Some(3.0), Some(5.0), Some(9.0), None], ); @@ -111,6 +113,7 @@ mod test { Changes::scalar_udf(), ts_array_3, value_array_3, + vec![], vec![Some(0.0), Some(0.0), Some(1.0), Some(1.0), None], ); } diff --git a/src/promql/src/functions/deriv.rs b/src/promql/src/functions/deriv.rs index 90b09f0d40..49e6718911 100644 --- a/src/promql/src/functions/deriv.rs +++ b/src/promql/src/functions/deriv.rs @@ -74,6 +74,7 @@ mod test { Deriv::scalar_udf(), ts_array, value_array, + vec![], vec![Some(10.606060606060607), None], ); } @@ -99,6 +100,7 @@ mod test { Deriv::scalar_udf(), ts_range_array, value_range_array, + vec![], vec![Some(0.0)], ); } diff --git a/src/promql/src/functions/extrapolate_rate.rs b/src/promql/src/functions/extrapolate_rate.rs index 56a8029958..aadaab904c 100644 --- a/src/promql/src/functions/extrapolate_rate.rs +++ b/src/promql/src/functions/extrapolate_rate.rs @@ -34,11 +34,11 @@ use std::sync::Arc; use datafusion::arrow::array::{Float64Array, TimestampMillisecondArray}; use datafusion::arrow::datatypes::TimeUnit; -use datafusion::common::DataFusionError; +use datafusion::common::{DataFusionError, Result as DfResult}; use datafusion::logical_expr::{ScalarUDF, Volatility}; use datafusion::physical_plan::ColumnarValue; use datafusion_expr::create_udf; -use datatypes::arrow::array::Array; +use datatypes::arrow::array::{Array, Int64Array}; use datatypes::arrow::datatypes::DataType; use crate::extension_plan::Millisecond; @@ -53,7 +53,7 @@ pub type Increase = ExtrapolatedRate; /// from #[derive(Debug)] pub struct ExtrapolatedRate { - /// Range duration in millisecond + /// Range length in milliseconds. range_length: i64, } @@ -63,7 +63,7 @@ impl ExtrapolatedRate ScalarUDF { + fn scalar_udf_with_name(name: &str) -> ScalarUDF { let input_types = vec![ // timestamp range vector RangeArray::convert_data_type(DataType::Timestamp(TimeUnit::Millisecond, None)), @@ -71,6 +71,8 @@ impl ExtrapolatedRate ExtrapolatedRate Result { - assert_eq!(input.len(), 3); + fn create_function(inputs: &[ColumnarValue]) -> DfResult { + if inputs.len() != 4 { + return Err(DataFusionError::Plan( + "ExtrapolatedRate function should have 4 inputs".to_string(), + )); + } + + let range_length_array = extract_array(&inputs[3])?; + let range_length = range_length_array + .as_any() + .downcast_ref::() + .unwrap() + .value(0) as i64; + + Ok(Self::new(range_length)) + } + + /// Input parameters: + /// * 0: timestamp range vector + /// * 1: value range vector + /// * 2: timestamp vector + /// * 3: range length. Range duration in millisecond. Not used here + fn calc(&self, input: &[ColumnarValue]) -> DfResult { + assert_eq!(input.len(), 4); // construct matrix from input let ts_array = extract_array(&input[0])?; @@ -208,8 +232,8 @@ impl ExtrapolatedRate { "prom_delta" } - pub fn scalar_udf(range_length: i64) -> ScalarUDF { - Self::scalar_udf_with_name(Self::name(), range_length) + pub fn scalar_udf() -> ScalarUDF { + Self::scalar_udf_with_name(Self::name()) } } @@ -219,8 +243,8 @@ impl ExtrapolatedRate { "prom_rate" } - pub fn scalar_udf(range_length: i64) -> ScalarUDF { - Self::scalar_udf_with_name(Self::name(), range_length) + pub fn scalar_udf() -> ScalarUDF { + Self::scalar_udf_with_name(Self::name()) } } @@ -230,8 +254,8 @@ impl ExtrapolatedRate { "prom_increase" } - pub fn scalar_udf(range_length: i64) -> ScalarUDF { - Self::scalar_udf_with_name(Self::name(), range_length) + pub fn scalar_udf() -> ScalarUDF { + Self::scalar_udf_with_name(Self::name()) } } @@ -271,6 +295,7 @@ mod test { ColumnarValue::Array(Arc::new(ts_range.into_dict())), ColumnarValue::Array(Arc::new(value_range.into_dict())), ColumnarValue::Array(timestamps), + ColumnarValue::Array(Arc::new(Int64Array::from(vec![5]))), ]; let output = extract_array( &ExtrapolatedRate::::new(5) diff --git a/src/promql/src/functions/holt_winters.rs b/src/promql/src/functions/holt_winters.rs index 3f26abffb1..8e722c8651 100644 --- a/src/promql/src/functions/holt_winters.rs +++ b/src/promql/src/functions/holt_winters.rs @@ -22,6 +22,7 @@ use datafusion::arrow::datatypes::TimeUnit; use datafusion::common::DataFusionError; use datafusion::logical_expr::{ScalarUDF, Volatility}; use datafusion::physical_plan::ColumnarValue; +use datafusion_common::ScalarValue; use datafusion_expr::create_udf; use datatypes::arrow::array::Array; use datatypes::arrow::datatypes::DataType; @@ -62,6 +63,10 @@ impl HoltWinters { vec![ RangeArray::convert_data_type(DataType::Timestamp(TimeUnit::Millisecond, None)), RangeArray::convert_data_type(DataType::Float64), + // sf + DataType::Float64, + // tf + DataType::Float64, ] } @@ -69,20 +74,39 @@ impl HoltWinters { DataType::Float64 } - pub fn scalar_udf(level: f64, trend: f64) -> ScalarUDF { + pub fn scalar_udf() -> ScalarUDF { create_udf( Self::name(), Self::input_type(), Self::return_type(), Volatility::Volatile, - Arc::new(move |input: &_| Self::new(level, trend).calc(input)) as _, + Arc::new(move |input: &_| Self::create_function(input)?.calc(input)) as _, ) } + fn create_function(inputs: &[ColumnarValue]) -> Result { + if inputs.len() != 4 { + return Err(DataFusionError::Plan( + "HoltWinters function should have 4 inputs".to_string(), + )); + } + let ColumnarValue::Scalar(ScalarValue::Float64(Some(sf))) = inputs[2] else { + return Err(DataFusionError::Plan( + "HoltWinters function's third input should be a scalar float64".to_string(), + )); + }; + let ColumnarValue::Scalar(ScalarValue::Float64(Some(tf))) = inputs[3] else { + return Err(DataFusionError::Plan( + "HoltWinters function's fourth input should be a scalar float64".to_string(), + )); + }; + Ok(Self::new(sf, tf)) + } + fn calc(&self, input: &[ColumnarValue]) -> Result { // construct matrix from input. // The third one is level param, the fourth - trend param which are included in fields. - assert_eq!(input.len(), 2); + assert_eq!(input.len(), 4); let ts_array = extract_array(&input[0])?; let value_array = extract_array(&input[1])?; @@ -264,9 +288,13 @@ mod tests { let ts_range_array = RangeArray::from_ranges(ts_array, ranges).unwrap(); let value_range_array = RangeArray::from_ranges(values_array, ranges).unwrap(); simple_range_udf_runner( - HoltWinters::scalar_udf(0.5, 0.1), + HoltWinters::scalar_udf(), ts_range_array, value_range_array, + vec![ + ScalarValue::Float64(Some(0.5)), + ScalarValue::Float64(Some(0.1)), + ], vec![Some(5.0)], ); } @@ -287,9 +315,13 @@ mod tests { let ts_range_array = RangeArray::from_ranges(ts_array, ranges).unwrap(); let value_range_array = RangeArray::from_ranges(values_array, ranges).unwrap(); simple_range_udf_runner( - HoltWinters::scalar_udf(0.5, 0.1), + HoltWinters::scalar_udf(), ts_range_array, value_range_array, + vec![ + ScalarValue::Float64(Some(0.5)), + ScalarValue::Float64(Some(0.1)), + ], vec![Some(38.18119566835938)], ); } @@ -315,9 +347,13 @@ mod tests { let (ts_range_array, value_range_array) = create_ts_and_value_range_arrays(query, ranges.clone()); simple_range_udf_runner( - HoltWinters::scalar_udf(0.01, 0.1), + HoltWinters::scalar_udf(), ts_range_array, value_range_array, + vec![ + ScalarValue::Float64(Some(0.01)), + ScalarValue::Float64(Some(0.1)), + ], vec![Some(expected)], ); } diff --git a/src/promql/src/functions/idelta.rs b/src/promql/src/functions/idelta.rs index 0e581d2d2c..a70a1dee3c 100644 --- a/src/promql/src/functions/idelta.rs +++ b/src/promql/src/functions/idelta.rs @@ -138,9 +138,7 @@ impl IDelta { } // else is rate - // TODO(ruihang): "divide 1000" converts the timestamp from millisecond to second. - // it should consider other percisions. - let sampled_interval = (timestamps[len - 1] - timestamps[len - 2]) / 1000; + let sampled_interval = (timestamps[len - 1] - timestamps[len - 2]) as f64 / 1000.0; let last_value = values[len - 1]; let prev_value = values[len - 2]; let result_value = if last_value < prev_value { @@ -192,6 +190,7 @@ mod test { IDelta::::scalar_udf(), ts_range_array, value_range_array, + vec![], vec![Some(1.0), Some(-5.0), None, Some(6.0), None, None], ); @@ -202,6 +201,7 @@ mod test { IDelta::::scalar_udf(), ts_range_array, value_range_array, + vec![], // the second point represent counter reset vec![Some(0.5), Some(0.0), None, Some(3.0), None, None], ); diff --git a/src/promql/src/functions/predict_linear.rs b/src/promql/src/functions/predict_linear.rs index 4b945cabbb..d3c1e8214c 100644 --- a/src/promql/src/functions/predict_linear.rs +++ b/src/promql/src/functions/predict_linear.rs @@ -22,6 +22,7 @@ use datafusion::arrow::datatypes::TimeUnit; use datafusion::common::DataFusionError; use datafusion::logical_expr::{ScalarUDF, Volatility}; use datafusion::physical_plan::ColumnarValue; +use datafusion_common::ScalarValue; use datafusion_expr::create_udf; use datatypes::arrow::array::Array; use datatypes::arrow::datatypes::DataType; @@ -44,25 +45,41 @@ impl PredictLinear { "prom_predict_linear" } - pub fn scalar_udf(t: i64) -> ScalarUDF { + pub fn scalar_udf() -> ScalarUDF { let input_types = vec![ // time index column RangeArray::convert_data_type(DataType::Timestamp(TimeUnit::Millisecond, None)), // value column RangeArray::convert_data_type(DataType::Float64), + // t + DataType::Int64, ]; create_udf( Self::name(), input_types, DataType::Float64, Volatility::Volatile, - Arc::new(move |input: &_| Self::new(t).predict_linear(input)) as _, + Arc::new(move |input: &_| Self::create_function(input)?.predict_linear(input)) as _, ) } + fn create_function(inputs: &[ColumnarValue]) -> Result { + if inputs.len() != 3 { + return Err(DataFusionError::Plan( + "PredictLinear function should have 3 inputs".to_string(), + )); + } + let ColumnarValue::Scalar(ScalarValue::Int64(Some(t))) = inputs[2] else { + return Err(DataFusionError::Plan( + "PredictLinear function's third input should be a scalar int64".to_string(), + )); + }; + Ok(Self::new(t)) + } + fn predict_linear(&self, input: &[ColumnarValue]) -> Result { // construct matrix from input. - assert_eq!(input.len(), 2); + assert_eq!(input.len(), 3); let ts_array = extract_array(&input[0])?; let value_array = extract_array(&input[1])?; @@ -190,9 +207,10 @@ mod test { let ts_array = RangeArray::from_ranges(ts_array, ranges).unwrap(); let value_array = RangeArray::from_ranges(values_array, ranges).unwrap(); simple_range_udf_runner( - PredictLinear::scalar_udf(0), + PredictLinear::scalar_udf(), ts_array, value_array, + vec![ScalarValue::Int64(Some(0))], vec![None, None], ); } @@ -201,9 +219,10 @@ mod test { fn calculate_predict_linear_test1() { let (ts_array, value_array) = build_test_range_arrays(); simple_range_udf_runner( - PredictLinear::scalar_udf(0), + PredictLinear::scalar_udf(), ts_array, value_array, + vec![ScalarValue::Int64(Some(0))], // value at t = 0 vec![Some(38.63636363636364)], ); @@ -213,9 +232,10 @@ mod test { fn calculate_predict_linear_test2() { let (ts_array, value_array) = build_test_range_arrays(); simple_range_udf_runner( - PredictLinear::scalar_udf(3000), + PredictLinear::scalar_udf(), ts_array, value_array, + vec![ScalarValue::Int64(Some(3000))], // value at t = 3000 vec![Some(31856.818181818187)], ); @@ -225,9 +245,10 @@ mod test { fn calculate_predict_linear_test3() { let (ts_array, value_array) = build_test_range_arrays(); simple_range_udf_runner( - PredictLinear::scalar_udf(4200), + PredictLinear::scalar_udf(), ts_array, value_array, + vec![ScalarValue::Int64(Some(4200))], // value at t = 4200 vec![Some(44584.09090909091)], ); @@ -237,9 +258,10 @@ mod test { fn calculate_predict_linear_test4() { let (ts_array, value_array) = build_test_range_arrays(); simple_range_udf_runner( - PredictLinear::scalar_udf(6600), + PredictLinear::scalar_udf(), ts_array, value_array, + vec![ScalarValue::Int64(Some(6600))], // value at t = 6600 vec![Some(70038.63636363638)], ); @@ -249,9 +271,10 @@ mod test { fn calculate_predict_linear_test5() { let (ts_array, value_array) = build_test_range_arrays(); simple_range_udf_runner( - PredictLinear::scalar_udf(7800), + PredictLinear::scalar_udf(), ts_array, value_array, + vec![ScalarValue::Int64(Some(7800))], // value at t = 7800 vec![Some(82765.9090909091)], ); diff --git a/src/promql/src/functions/quantile.rs b/src/promql/src/functions/quantile.rs index f975a76cf4..7fd553287d 100644 --- a/src/promql/src/functions/quantile.rs +++ b/src/promql/src/functions/quantile.rs @@ -19,6 +19,7 @@ use datafusion::arrow::datatypes::TimeUnit; use datafusion::common::DataFusionError; use datafusion::logical_expr::{ScalarUDF, Volatility}; use datafusion::physical_plan::ColumnarValue; +use datafusion_common::ScalarValue; use datafusion_expr::create_udf; use datatypes::arrow::array::Array; use datatypes::arrow::datatypes::DataType; @@ -40,22 +41,38 @@ impl QuantileOverTime { "prom_quantile_over_time" } - pub fn scalar_udf(quantile: f64) -> ScalarUDF { + pub fn scalar_udf() -> ScalarUDF { let input_types = vec![ // time index column RangeArray::convert_data_type(DataType::Timestamp(TimeUnit::Millisecond, None)), // value column RangeArray::convert_data_type(DataType::Float64), + // quantile + DataType::Float64, ]; create_udf( Self::name(), input_types, DataType::Float64, Volatility::Volatile, - Arc::new(move |input: &_| Self::new(quantile).quantile_over_time(input)) as _, + Arc::new(move |input: &_| Self::create_function(input)?.quantile_over_time(input)) as _, ) } + fn create_function(inputs: &[ColumnarValue]) -> Result { + if inputs.len() != 3 { + return Err(DataFusionError::Plan( + "QuantileOverTime function should have 3 inputs".to_string(), + )); + } + let ColumnarValue::Scalar(ScalarValue::Float64(Some(quantile))) = inputs[2] else { + return Err(DataFusionError::Plan( + "QuantileOverTime function's third input should be a scalar float64".to_string(), + )); + }; + Ok(Self::new(quantile)) + } + fn quantile_over_time( &self, input: &[ColumnarValue], diff --git a/src/promql/src/functions/quantile_aggr.rs b/src/promql/src/functions/quantile_aggr.rs index 6af43eda52..5652f57342 100644 --- a/src/promql/src/functions/quantile_aggr.rs +++ b/src/promql/src/functions/quantile_aggr.rs @@ -16,10 +16,12 @@ use std::sync::Arc; use datafusion::arrow::array::{ArrayRef, AsArray}; use datafusion::common::cast::{as_list_array, as_primitive_array, as_struct_array}; -use datafusion::error::Result as DfResult; +use datafusion::error::{DataFusionError, Result as DfResult}; use datafusion::logical_expr::{Accumulator as DfAccumulator, AggregateUDF, Volatility}; +use datafusion::physical_plan::expressions::Literal; use datafusion::prelude::create_udaf; use datafusion_common::ScalarValue; +use datafusion_expr::function::AccumulatorArgs; use datatypes::arrow::array::{ListArray, StructArray}; use datatypes::arrow::datatypes::{DataType, Field, Float64Type}; @@ -38,16 +40,16 @@ pub struct QuantileAccumulator { /// Create a quantile `AggregateUDF` for PromQL quantile operator, /// which calculates φ-quantile (0 ≤ φ ≤ 1) over dimensions -pub fn quantile_udaf(q: f64) -> Arc { +pub fn quantile_udaf() -> Arc { Arc::new(create_udaf( QUANTILE_NAME, - // Input type: (values) - vec![DataType::Float64], + // Input type: (φ, values) + vec![DataType::Float64, DataType::Float64], // Output type: the φ-quantile Arc::new(DataType::Float64), Volatility::Volatile, // Create the accumulator - Arc::new(move |_| Ok(Box::new(QuantileAccumulator::new(q)))), + Arc::new(QuantileAccumulator::from_args), // Intermediate state types Arc::new(vec![DataType::Struct( vec![Field::new( @@ -65,17 +67,40 @@ pub fn quantile_udaf(q: f64) -> Arc { } impl QuantileAccumulator { - pub fn new(q: f64) -> Self { + fn new(q: f64) -> Self { Self { q, ..Default::default() } } + + pub fn from_args(args: AccumulatorArgs) -> DfResult> { + if args.exprs.len() != 2 { + return Err(DataFusionError::Plan( + "Quantile function should have 2 inputs".to_string(), + )); + } + + let q = match &args.exprs[0] + .as_any() + .downcast_ref::() + .map(|lit| lit.value()) + { + Some(ScalarValue::Float64(Some(q))) => *q, + _ => { + return Err(DataFusionError::Internal( + "Invalid quantile value".to_string(), + )) + } + }; + + Ok(Box::new(Self::new(q))) + } } impl DfAccumulator for QuantileAccumulator { fn update_batch(&mut self, values: &[ArrayRef]) -> DfResult<()> { - let f64_array = values[0].as_primitive::(); + let f64_array = values[1].as_primitive::(); self.values.extend(f64_array); @@ -162,9 +187,10 @@ mod tests { #[test] fn test_quantile_accumulator_single_value() { let mut accumulator = QuantileAccumulator::new(0.5); + let q = create_f64_array(vec![Some(0.5)]); let input = create_f64_array(vec![Some(10.0)]); - accumulator.update_batch(&[input]).unwrap(); + accumulator.update_batch(&[q, input]).unwrap(); let result = accumulator.evaluate().unwrap(); assert_eq!(result, ScalarValue::Float64(Some(10.0))); @@ -173,9 +199,10 @@ mod tests { #[test] fn test_quantile_accumulator_multiple_values() { let mut accumulator = QuantileAccumulator::new(0.5); + let q = create_f64_array(vec![Some(0.5)]); let input = create_f64_array(vec![Some(1.0), Some(2.0), Some(3.0), Some(4.0), Some(5.0)]); - accumulator.update_batch(&[input]).unwrap(); + accumulator.update_batch(&[q, input]).unwrap(); let result = accumulator.evaluate().unwrap(); assert_eq!(result, ScalarValue::Float64(Some(3.0))); @@ -184,9 +211,10 @@ mod tests { #[test] fn test_quantile_accumulator_with_nulls() { let mut accumulator = QuantileAccumulator::new(0.5); + let q = create_f64_array(vec![Some(0.5)]); let input = create_f64_array(vec![Some(1.0), None, Some(3.0), Some(4.0), Some(5.0)]); - accumulator.update_batch(&[input]).unwrap(); + accumulator.update_batch(&[q, input]).unwrap(); let result = accumulator.evaluate().unwrap(); assert_eq!(result, ScalarValue::Float64(Some(3.0))); @@ -195,11 +223,12 @@ mod tests { #[test] fn test_quantile_accumulator_multiple_batches() { let mut accumulator = QuantileAccumulator::new(0.5); + let q = create_f64_array(vec![Some(0.5)]); let input1 = create_f64_array(vec![Some(1.0), Some(2.0)]); let input2 = create_f64_array(vec![Some(3.0), Some(4.0), Some(5.0)]); - accumulator.update_batch(&[input1]).unwrap(); - accumulator.update_batch(&[input2]).unwrap(); + accumulator.update_batch(&[q.clone(), input1]).unwrap(); + accumulator.update_batch(&[q, input2]).unwrap(); let result = accumulator.evaluate().unwrap(); assert_eq!(result, ScalarValue::Float64(Some(3.0))); @@ -208,29 +237,33 @@ mod tests { #[test] fn test_quantile_accumulator_different_quantiles() { let mut min_accumulator = QuantileAccumulator::new(0.0); + let q = create_f64_array(vec![Some(0.0)]); let input = create_f64_array(vec![Some(1.0), Some(2.0), Some(3.0), Some(4.0), Some(5.0)]); - min_accumulator.update_batch(&[input.clone()]).unwrap(); + min_accumulator.update_batch(&[q, input.clone()]).unwrap(); assert_eq!( min_accumulator.evaluate().unwrap(), ScalarValue::Float64(Some(1.0)) ); let mut q1_accumulator = QuantileAccumulator::new(0.25); - q1_accumulator.update_batch(&[input.clone()]).unwrap(); + let q = create_f64_array(vec![Some(0.25)]); + q1_accumulator.update_batch(&[q, input.clone()]).unwrap(); assert_eq!( q1_accumulator.evaluate().unwrap(), ScalarValue::Float64(Some(2.0)) ); let mut q3_accumulator = QuantileAccumulator::new(0.75); - q3_accumulator.update_batch(&[input.clone()]).unwrap(); + let q = create_f64_array(vec![Some(0.75)]); + q3_accumulator.update_batch(&[q, input.clone()]).unwrap(); assert_eq!( q3_accumulator.evaluate().unwrap(), ScalarValue::Float64(Some(4.0)) ); let mut max_accumulator = QuantileAccumulator::new(1.0); - max_accumulator.update_batch(&[input]).unwrap(); + let q = create_f64_array(vec![Some(1.0)]); + max_accumulator.update_batch(&[q, input]).unwrap(); assert_eq!( max_accumulator.evaluate().unwrap(), ScalarValue::Float64(Some(5.0)) @@ -240,10 +273,11 @@ mod tests { #[test] fn test_quantile_accumulator_size() { let mut accumulator = QuantileAccumulator::new(0.5); + let q = create_f64_array(vec![Some(0.5)]); let input = create_f64_array(vec![Some(1.0), Some(2.0), Some(3.0)]); let initial_size = accumulator.size(); - accumulator.update_batch(&[input]).unwrap(); + accumulator.update_batch(&[q, input]).unwrap(); let after_update_size = accumulator.size(); assert!(after_update_size >= initial_size); @@ -252,14 +286,16 @@ mod tests { #[test] fn test_quantile_accumulator_state_and_merge() -> DfResult<()> { let mut acc1 = QuantileAccumulator::new(0.5); + let q = create_f64_array(vec![Some(0.5)]); let input1 = create_f64_array(vec![Some(1.0), Some(2.0)]); - acc1.update_batch(&[input1])?; + acc1.update_batch(&[q, input1])?; let state1 = acc1.state()?; let mut acc2 = QuantileAccumulator::new(0.5); + let q = create_f64_array(vec![Some(0.5)]); let input2 = create_f64_array(vec![Some(3.0), Some(4.0), Some(5.0)]); - acc2.update_batch(&[input2])?; + acc2.update_batch(&[q, input2])?; let mut struct_builders = vec![]; for scalar in &state1 { @@ -280,16 +316,16 @@ mod tests { #[test] fn test_quantile_accumulator_with_extreme_values() { let mut accumulator = QuantileAccumulator::new(0.5); + let q = create_f64_array(vec![Some(0.5)]); let input = create_f64_array(vec![Some(f64::MAX), Some(f64::MIN), Some(0.0)]); - accumulator.update_batch(&[input]).unwrap(); + accumulator.update_batch(&[q, input]).unwrap(); let _result = accumulator.evaluate().unwrap(); } #[test] fn test_quantile_udaf_creation() { - let q = 0.5; - let udaf = quantile_udaf(q); + let udaf = quantile_udaf(); assert_eq!(udaf.name(), QUANTILE_NAME); assert_eq!(udaf.return_type(&[]).unwrap(), DataType::Float64); diff --git a/src/promql/src/functions/resets.rs b/src/promql/src/functions/resets.rs index 7df44b5e76..05d091db0d 100644 --- a/src/promql/src/functions/resets.rs +++ b/src/promql/src/functions/resets.rs @@ -90,6 +90,7 @@ mod test { Resets::scalar_udf(), ts_array_1, value_array_1, + vec![], vec![Some(0.0), Some(1.0), Some(2.0), Some(3.0), None], ); @@ -101,6 +102,7 @@ mod test { Resets::scalar_udf(), ts_array_2, value_array_2, + vec![], vec![Some(0.0), Some(0.0), Some(1.0), Some(1.0), None], ); @@ -111,6 +113,7 @@ mod test { Resets::scalar_udf(), ts_array_3, value_array_3, + vec![], vec![Some(0.0), Some(0.0), Some(0.0), Some(0.0), None], ); } diff --git a/src/promql/src/functions/round.rs b/src/promql/src/functions/round.rs index d1c9d318d8..c3931c5424 100644 --- a/src/promql/src/functions/round.rs +++ b/src/promql/src/functions/round.rs @@ -15,6 +15,7 @@ use std::sync::Arc; use datafusion::error::DataFusionError; +use datafusion_common::ScalarValue; use datafusion_expr::{create_udf, ColumnarValue, ScalarUDF, Volatility}; use datatypes::arrow::array::AsArray; use datatypes::arrow::datatypes::{DataType, Float64Type}; @@ -36,25 +37,39 @@ impl Round { } fn input_type() -> Vec { - vec![DataType::Float64] + vec![DataType::Float64, DataType::Float64] } pub fn return_type() -> DataType { DataType::Float64 } - pub fn scalar_udf(nearest: f64) -> ScalarUDF { + pub fn scalar_udf() -> ScalarUDF { create_udf( Self::name(), Self::input_type(), Self::return_type(), Volatility::Volatile, - Arc::new(move |input: &_| Self::new(nearest).calc(input)) as _, + Arc::new(move |input: &_| Self::create_function(input)?.calc(input)) as _, ) } + fn create_function(inputs: &[ColumnarValue]) -> Result { + if inputs.len() != 2 { + return Err(DataFusionError::Plan( + "Round function should have 2 inputs".to_string(), + )); + } + let ColumnarValue::Scalar(ScalarValue::Float64(Some(nearest))) = inputs[1] else { + return Err(DataFusionError::Plan( + "Round function's second input should be a scalar float64".to_string(), + )); + }; + Ok(Self::new(nearest)) + } + fn calc(&self, input: &[ColumnarValue]) -> Result { - assert_eq!(input.len(), 1); + assert_eq!(input.len(), 2); let value_array = extract_array(&input[0])?; @@ -80,8 +95,11 @@ mod tests { use super::*; fn test_round_f64(value: Vec, nearest: f64, expected: Vec) { - let round_udf = Round::scalar_udf(nearest); - let input = vec![ColumnarValue::Array(Arc::new(Float64Array::from(value)))]; + let round_udf = Round::scalar_udf(); + let input = vec![ + ColumnarValue::Array(Arc::new(Float64Array::from(value))), + ColumnarValue::Scalar(ScalarValue::Float64(Some(nearest))), + ]; let args = ScalarFunctionArgs { args: input, number_rows: 1, diff --git a/src/promql/src/functions/test_util.rs b/src/promql/src/functions/test_util.rs index 46ad6ec1a8..fb76ca52b5 100644 --- a/src/promql/src/functions/test_util.rs +++ b/src/promql/src/functions/test_util.rs @@ -17,6 +17,7 @@ use std::sync::Arc; use datafusion::arrow::array::Float64Array; use datafusion::logical_expr::ScalarUDF; use datafusion::physical_plan::ColumnarValue; +use datafusion_common::ScalarValue; use datafusion_expr::ScalarFunctionArgs; use datatypes::arrow::datatypes::DataType; @@ -28,13 +29,17 @@ pub fn simple_range_udf_runner( range_fn: ScalarUDF, input_ts: RangeArray, input_value: RangeArray, + other_args: Vec, expected: Vec>, ) { let num_rows = input_ts.len(); - let input = vec![ + let input = [ ColumnarValue::Array(Arc::new(input_ts.into_dict())), ColumnarValue::Array(Arc::new(input_value.into_dict())), - ]; + ] + .into_iter() + .chain(other_args.into_iter().map(ColumnarValue::Scalar)) + .collect::>(); let args = ScalarFunctionArgs { args: input, number_rows: num_rows, diff --git a/src/puffin/src/puffin_manager.rs b/src/puffin/src/puffin_manager.rs index 1dfec58f5b..9f287128c1 100644 --- a/src/puffin/src/puffin_manager.rs +++ b/src/puffin/src/puffin_manager.rs @@ -65,7 +65,13 @@ pub trait PuffinWriter { /// Returns the number of bytes written. /// /// The specified `dir` should be accessible from the filesystem. - async fn put_dir(&mut self, key: &str, dir: PathBuf, options: PutOptions) -> Result; + async fn put_dir( + &mut self, + key: &str, + dir: PathBuf, + options: PutOptions, + properties: HashMap, + ) -> Result; /// Sets whether the footer should be LZ4 compressed. fn set_footer_lz4_compressed(&mut self, lz4_compressed: bool); @@ -94,15 +100,15 @@ pub trait PuffinReader { /// Reads a blob from the Puffin file. /// - /// The returned `BlobWithMetadata` is used to access the blob data and its metadata. - /// Users should hold the `BlobWithMetadata` until they are done with the blob data. - async fn blob(&self, key: &str) -> Result>; + /// The returned `GuardWithMetadata` is used to access the blob data and its metadata. + /// Users should hold the `GuardWithMetadata` until they are done with the blob data. + async fn blob(&self, key: &str) -> Result>; /// Reads a directory from the Puffin file. /// - /// The returned `DirGuard` is used to access the directory in the filesystem. - /// The caller is responsible for holding the `DirGuard` until they are done with the directory. - async fn dir(&self, key: &str) -> Result; + /// The returned `GuardWithMetadata` is used to access the directory data and its metadata. + /// Users should hold the `GuardWithMetadata` until they are done with the directory data. + async fn dir(&self, key: &str) -> Result>; } /// `BlobGuard` is provided by the `PuffinReader` to access the blob data. @@ -114,32 +120,41 @@ pub trait BlobGuard { async fn reader(&self) -> Result; } -/// `BlobWithMetadata` provides access to the blob data and its metadata. -pub struct BlobWithMetadata { - blob: B, - metadata: BlobMetadata, -} - -impl BlobWithMetadata { - /// Creates a new `BlobWithMetadata` instance. - pub fn new(blob: B, metadata: BlobMetadata) -> Self { - Self { blob, metadata } - } - - /// Returns the reader for the blob data. - pub async fn reader(&self) -> Result { - self.blob.reader().await - } - - /// Returns the metadata of the blob. - pub fn metadata(&self) -> &BlobMetadata { - &self.metadata - } -} - /// `DirGuard` is provided by the `PuffinReader` to access the directory in the filesystem. /// Users should hold the `DirGuard` until they are done with the directory. #[auto_impl::auto_impl(Arc)] pub trait DirGuard { fn path(&self) -> &PathBuf; } + +/// `GuardWithMetadata` provides access to the blob or directory data and its metadata. +pub struct GuardWithMetadata { + guard: G, + metadata: BlobMetadata, +} + +impl GuardWithMetadata { + /// Creates a new `GuardWithMetadata` instance. + pub fn new(guard: G, metadata: BlobMetadata) -> Self { + Self { guard, metadata } + } + + /// Returns the metadata of the directory. + pub fn metadata(&self) -> &BlobMetadata { + &self.metadata + } +} + +impl GuardWithMetadata { + /// Returns the reader for the blob data. + pub async fn reader(&self) -> Result { + self.guard.reader().await + } +} + +impl GuardWithMetadata { + /// Returns the path of the directory. + pub fn path(&self) -> &PathBuf { + self.guard.path() + } +} diff --git a/src/puffin/src/puffin_manager/fs_puffin_manager/reader.rs b/src/puffin/src/puffin_manager/fs_puffin_manager/reader.rs index 5d2033e2e9..2c616578f6 100644 --- a/src/puffin/src/puffin_manager/fs_puffin_manager/reader.rs +++ b/src/puffin/src/puffin_manager/fs_puffin_manager/reader.rs @@ -36,7 +36,7 @@ use crate::puffin_manager::file_accessor::PuffinFileAccessor; use crate::puffin_manager::fs_puffin_manager::dir_meta::DirMetadata; use crate::puffin_manager::fs_puffin_manager::PuffinMetadataCacheRef; use crate::puffin_manager::stager::{BoxWriter, DirWriterProviderRef, Stager}; -use crate::puffin_manager::{BlobGuard, BlobWithMetadata, PuffinReader}; +use crate::puffin_manager::{BlobGuard, GuardWithMetadata, PuffinReader}; /// `FsPuffinReader` is a `PuffinReader` that provides fs readers for puffin files. pub struct FsPuffinReader @@ -96,26 +96,13 @@ where } async fn metadata(&self) -> Result> { - let reader = self.puffin_file_accessor.reader(&self.handle).await?; - let mut file = PuffinFileReader::new(reader); + let mut file = self.puffin_reader().await?; self.get_puffin_file_metadata(&mut file).await } - async fn blob(&self, key: &str) -> Result> { - let mut reader = self.puffin_file_accessor.reader(&self.handle).await?; - if let Some(file_size_hint) = self.file_size_hint { - reader.with_file_size_hint(file_size_hint); - } - let mut file = PuffinFileReader::new(reader); - - let metadata = self.get_puffin_file_metadata(&mut file).await?; - let blob_metadata = metadata - .blobs - .iter() - .find(|m| m.blob_type == key) - .context(BlobNotFoundSnafu { blob: key })? - .clone(); - + async fn blob(&self, key: &str) -> Result> { + let mut file = self.puffin_reader().await?; + let blob_metadata = self.get_blob_metadata(key, &mut file).await?; let blob = if blob_metadata.compression_codec.is_none() { // If the blob is not compressed, we can directly read it from the puffin file. Either::L(RandomReadBlob { @@ -140,28 +127,33 @@ where Either::R(staged_blob) }; - Ok(BlobWithMetadata::new(blob, blob_metadata)) + Ok(GuardWithMetadata::new(blob, blob_metadata)) } - async fn dir(&self, key: &str) -> Result { - self.stager + async fn dir(&self, key: &str) -> Result> { + let mut file = self.puffin_reader().await?; + let blob_metadata = self.get_blob_metadata(key, &mut file).await?; + let dir = self + .stager .get_dir( &self.handle, key, Box::new(|writer_provider| { let accessor = self.puffin_file_accessor.clone(); let handle = self.handle.clone(); - let key = key.to_string(); + let blob_metadata = blob_metadata.clone(); Box::pin(Self::init_dir_to_stager( + file, + blob_metadata, handle, - key, writer_provider, accessor, - self.file_size_hint, )) }), ) - .await + .await?; + + Ok(GuardWithMetadata::new(dir, blob_metadata)) } } @@ -188,6 +180,30 @@ where Ok(metadata) } + async fn get_blob_metadata( + &self, + key: &str, + file: &mut PuffinFileReader, + ) -> Result { + let metadata = self.get_puffin_file_metadata(file).await?; + let blob_metadata = metadata + .blobs + .iter() + .find(|m| m.blob_type == key) + .context(BlobNotFoundSnafu { blob: key })? + .clone(); + + Ok(blob_metadata) + } + + async fn puffin_reader(&self) -> Result> { + let mut reader = self.puffin_file_accessor.reader(&self.handle).await?; + if let Some(file_size_hint) = self.file_size_hint { + reader.with_file_size_hint(file_size_hint); + } + Ok(PuffinFileReader::new(reader)) + } + async fn init_blob_to_stager( reader: PuffinFileReader, blob_metadata: BlobMetadata, @@ -201,26 +217,14 @@ where } async fn init_dir_to_stager( + mut file: PuffinFileReader, + blob_metadata: BlobMetadata, handle: F::FileHandle, - key: String, writer_provider: DirWriterProviderRef, accessor: F, - file_size_hint: Option, ) -> Result { - let mut reader = accessor.reader(&handle).await?; - if let Some(file_size_hint) = file_size_hint { - reader.with_file_size_hint(file_size_hint); - } - let mut file = PuffinFileReader::new(reader); - let puffin_metadata = file.metadata().await?; - let blob_metadata = puffin_metadata - .blobs - .iter() - .find(|m| m.blob_type == key.as_str()) - .context(BlobNotFoundSnafu { blob: key })?; - - let reader = file.blob_reader(blob_metadata)?; + let reader = file.blob_reader(&blob_metadata)?; let meta = reader.metadata().await.context(MetadataSnafu)?; let buf = reader .read(0..meta.content_length) diff --git a/src/puffin/src/puffin_manager/fs_puffin_manager/writer.rs b/src/puffin/src/puffin_manager/fs_puffin_manager/writer.rs index 61d9df52f0..feb7678756 100644 --- a/src/puffin/src/puffin_manager/fs_puffin_manager/writer.rs +++ b/src/puffin/src/puffin_manager/fs_puffin_manager/writer.rs @@ -88,7 +88,13 @@ where Ok(written_bytes) } - async fn put_dir(&mut self, key: &str, dir_path: PathBuf, options: PutOptions) -> Result { + async fn put_dir( + &mut self, + key: &str, + dir_path: PathBuf, + options: PutOptions, + properties: HashMap, + ) -> Result { ensure!( !self.blob_keys.contains(key), DuplicateBlobSnafu { blob: key } @@ -150,7 +156,7 @@ where blob_type: key.to_string(), compressed_data: encoded.as_slice(), compression_codec: None, - properties: Default::default(), + properties, }; written_bytes += self.puffin_file_writer.add_blob(dir_meta_blob).await?; diff --git a/src/puffin/src/puffin_manager/tests.rs b/src/puffin/src/puffin_manager/tests.rs index e2f32e9498..bd3ec9d5a5 100644 --- a/src/puffin/src/puffin_manager/tests.rs +++ b/src/puffin/src/puffin_manager/tests.rs @@ -23,7 +23,7 @@ use crate::blob_metadata::CompressionCodec; use crate::puffin_manager::file_accessor::MockFileAccessor; use crate::puffin_manager::fs_puffin_manager::FsPuffinManager; use crate::puffin_manager::stager::BoundedStager; -use crate::puffin_manager::{DirGuard, PuffinManager, PuffinReader, PuffinWriter, PutOptions}; +use crate::puffin_manager::{PuffinManager, PuffinReader, PuffinWriter, PutOptions}; async fn new_bounded_stager(prefix: &str, capacity: u64) -> (TempDir, Arc>) { let staging_dir = create_temp_dir(prefix); @@ -343,6 +343,7 @@ async fn put_dir( PutOptions { compression: compression_codec, }, + HashMap::from_iter([("test_key".to_string(), "test_value".to_string())]), ) .await .unwrap(); @@ -356,6 +357,11 @@ async fn check_dir( puffin_reader: &impl PuffinReader, ) { let res_dir = puffin_reader.dir(key).await.unwrap(); + let metadata = res_dir.metadata(); + assert_eq!( + metadata.properties, + HashMap::from_iter([("test_key".to_string(), "test_value".to_string())]) + ); for (file_name, raw_data) in files_in_dir { let file_path = if cfg!(windows) { res_dir.path().join(file_name.replace('/', "\\")) diff --git a/src/query/src/datafusion.rs b/src/query/src/datafusion.rs index e0f020cd3a..db4207fd8a 100644 --- a/src/query/src/datafusion.rs +++ b/src/query/src/datafusion.rs @@ -50,9 +50,9 @@ use crate::dataframe::DataFrame; pub use crate::datafusion::planner::DfContextProviderAdapter; use crate::dist_plan::MergeScanLogicalPlan; use crate::error::{ - CatalogSnafu, ConvertSchemaSnafu, CreateRecordBatchSnafu, DataFusionSnafu, - MissingTableMutationHandlerSnafu, MissingTimestampColumnSnafu, QueryExecutionSnafu, Result, - TableMutationSnafu, TableNotFoundSnafu, TableReadOnlySnafu, UnsupportedExprSnafu, + CatalogSnafu, ConvertSchemaSnafu, CreateRecordBatchSnafu, MissingTableMutationHandlerSnafu, + MissingTimestampColumnSnafu, QueryExecutionSnafu, Result, TableMutationSnafu, + TableNotFoundSnafu, TableReadOnlySnafu, UnsupportedExprSnafu, }; use crate::executor::QueryExecutor; use crate::metrics::{OnDone, QUERY_STAGE_ELAPSED}; @@ -308,8 +308,7 @@ impl DatafusionQueryEngine { let physical_plan = state .query_planner() .create_physical_plan(&optimized_plan, state) - .await - .context(DataFusionSnafu)?; + .await?; Ok(physical_plan) } @@ -568,6 +567,7 @@ mod tests { use table::table::numbers::{NumbersTable, NUMBERS_TABLE_NAME}; use super::*; + use crate::options::QueryOptions; use crate::parser::QueryLanguageParser; use crate::query_engine::{QueryEngineFactory, QueryEngineRef}; @@ -582,7 +582,16 @@ mod tests { }; catalog_manager.register_table_sync(req).unwrap(); - QueryEngineFactory::new(catalog_manager, None, None, None, None, false).query_engine() + QueryEngineFactory::new( + catalog_manager, + None, + None, + None, + None, + false, + QueryOptions::default(), + ) + .query_engine() } #[tokio::test] diff --git a/src/query/src/datafusion/planner.rs b/src/query/src/datafusion/planner.rs index 912393690d..0ad531541f 100644 --- a/src/query/src/datafusion/planner.rs +++ b/src/query/src/datafusion/planner.rs @@ -43,7 +43,7 @@ use datafusion_sql::parser::Statement as DfStatement; use session::context::QueryContextRef; use snafu::{Location, ResultExt}; -use crate::error::{CatalogSnafu, DataFusionSnafu, Result}; +use crate::error::{CatalogSnafu, Result}; use crate::query_engine::{DefaultPlanDecoder, QueryEngineState}; pub struct DfContextProviderAdapter { @@ -70,9 +70,7 @@ impl DfContextProviderAdapter { query_ctx: QueryContextRef, ) -> Result { let table_names = if let Some(df_stmt) = df_stmt { - session_state - .resolve_table_references(df_stmt) - .context(DataFusionSnafu)? + session_state.resolve_table_references(df_stmt)? } else { vec![] }; diff --git a/src/query/src/error.rs b/src/query/src/error.rs index 1ebba8de5d..c2a2e960b0 100644 --- a/src/query/src/error.rs +++ b/src/query/src/error.rs @@ -126,7 +126,7 @@ pub enum Error { location: Location, }, - #[snafu(display(""))] + #[snafu(transparent)] DataFusion { #[snafu(source)] error: DataFusionError, diff --git a/src/query/src/lib.rs b/src/query/src/lib.rs index 6e1fbfae0a..428aa5cb45 100644 --- a/src/query/src/lib.rs +++ b/src/query/src/lib.rs @@ -14,7 +14,6 @@ #![feature(let_chains)] #![feature(int_roundings)] -#![feature(trait_upcasting)] #![feature(try_blocks)] #![feature(stmt_expr_attributes)] #![feature(iterator_try_collect)] @@ -28,7 +27,8 @@ pub mod error; pub mod executor; pub mod log_query; pub mod metrics; -mod optimizer; +pub mod optimizer; +pub mod options; pub mod parser; mod part_sort; pub mod physical_wrapper; diff --git a/src/query/src/optimizer.rs b/src/query/src/optimizer.rs index c98ad0c634..52a33029e2 100644 --- a/src/query/src/optimizer.rs +++ b/src/query/src/optimizer.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod constant_term; pub mod count_wildcard; pub mod parallelize_scan; pub mod pass_distribution; @@ -20,6 +21,7 @@ pub mod scan_hint; pub mod string_normalization; #[cfg(test)] pub(crate) mod test_util; +pub mod transcribe_atat; pub mod type_conversion; pub mod windowed_sort; diff --git a/src/query/src/optimizer/constant_term.rs b/src/query/src/optimizer/constant_term.rs new file mode 100644 index 0000000000..60e5b76d9d --- /dev/null +++ b/src/query/src/optimizer/constant_term.rs @@ -0,0 +1,454 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::sync::Arc; + +use arrow::array::{AsArray, BooleanArray}; +use common_function::scalars::matches_term::MatchesTermFinder; +use datafusion::config::ConfigOptions; +use datafusion::error::Result as DfResult; +use datafusion::physical_optimizer::PhysicalOptimizerRule; +use datafusion::physical_plan::filter::FilterExec; +use datafusion::physical_plan::ExecutionPlan; +use datafusion_common::tree_node::{Transformed, TreeNode}; +use datafusion_common::ScalarValue; +use datafusion_expr::ColumnarValue; +use datafusion_physical_expr::expressions::Literal; +use datafusion_physical_expr::{PhysicalExpr, ScalarFunctionExpr}; + +/// A physical expression that uses a pre-compiled term finder for the `matches_term` function. +/// +/// This expression optimizes the `matches_term` function by pre-compiling the term +/// when the term is a constant value. This avoids recompiling the term for each row +/// during execution. +#[derive(Debug)] +pub struct PreCompiledMatchesTermExpr { + /// The text column expression to search in + text: Arc, + /// The constant term to search for + term: String, + /// The pre-compiled term finder + finder: MatchesTermFinder, +} + +impl fmt::Display for PreCompiledMatchesTermExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "MatchesConstTerm({}, \"{}\")", self.text, self.term) + } +} + +impl Hash for PreCompiledMatchesTermExpr { + fn hash(&self, state: &mut H) { + self.text.hash(state); + self.term.hash(state); + } +} + +impl PartialEq for PreCompiledMatchesTermExpr { + fn eq(&self, other: &Self) -> bool { + self.text.eq(&other.text) && self.term.eq(&other.term) + } +} + +impl Eq for PreCompiledMatchesTermExpr {} + +impl PhysicalExpr for PreCompiledMatchesTermExpr { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn data_type( + &self, + _input_schema: &arrow_schema::Schema, + ) -> datafusion::error::Result { + Ok(arrow_schema::DataType::Boolean) + } + + fn nullable(&self, input_schema: &arrow_schema::Schema) -> datafusion::error::Result { + self.text.nullable(input_schema) + } + + fn evaluate( + &self, + batch: &common_recordbatch::DfRecordBatch, + ) -> datafusion::error::Result { + let num_rows = batch.num_rows(); + + let text_value = self.text.evaluate(batch)?; + let array = text_value.into_array(num_rows)?; + let str_array = array.as_string::(); + + let mut result = BooleanArray::builder(num_rows); + for text in str_array { + match text { + Some(text) => { + result.append_value(self.finder.find(text)); + } + None => { + result.append_null(); + } + } + } + + Ok(ColumnarValue::Array(Arc::new(result.finish()))) + } + + fn children(&self) -> Vec<&Arc> { + vec![&self.text] + } + + fn with_new_children( + self: Arc, + children: Vec>, + ) -> datafusion::error::Result> { + Ok(Arc::new(PreCompiledMatchesTermExpr { + text: children[0].clone(), + term: self.term.clone(), + finder: self.finder.clone(), + })) + } +} + +/// Optimizer rule that pre-compiles constant term in `matches_term` function. +/// +/// This optimizer looks for `matches_term` function calls where the second argument +/// (the term to match) is a constant value. When found, it replaces the function +/// call with a specialized `PreCompiledMatchesTermExpr` that uses a pre-compiled +/// term finder. +/// +/// Example: +/// ```sql +/// -- Before optimization: +/// matches_term(text_column, 'constant_term') +/// +/// -- After optimization: +/// PreCompiledMatchesTermExpr(text_column, 'constant_term') +/// ``` +/// +/// This optimization improves performance by: +/// 1. Pre-compiling the term once instead of for each row +/// 2. Using a specialized expression that avoids function call overhead +#[derive(Debug)] +pub struct MatchesConstantTermOptimizer; + +impl PhysicalOptimizerRule for MatchesConstantTermOptimizer { + fn optimize( + &self, + plan: Arc, + _config: &ConfigOptions, + ) -> DfResult> { + let res = plan + .transform_down(&|plan: Arc| { + if let Some(filter) = plan.as_any().downcast_ref::() { + let pred = filter.predicate().clone(); + let new_pred = pred.transform_down(&|expr: Arc| { + if let Some(func) = expr.as_any().downcast_ref::() { + if !func.name().eq_ignore_ascii_case("matches_term") { + return Ok(Transformed::no(expr)); + } + let args = func.args(); + if args.len() != 2 { + return Ok(Transformed::no(expr)); + } + + if let Some(lit) = args[1].as_any().downcast_ref::() { + if let ScalarValue::Utf8(Some(term)) = lit.value() { + let finder = MatchesTermFinder::new(term); + let expr = PreCompiledMatchesTermExpr { + text: args[0].clone(), + term: term.to_string(), + finder, + }; + + return Ok(Transformed::yes(Arc::new(expr))); + } + } + } + + Ok(Transformed::no(expr)) + })?; + + if new_pred.transformed { + let exec = FilterExec::try_new(new_pred.data, filter.input().clone())? + .with_default_selectivity(filter.default_selectivity())? + .with_projection(filter.projection().cloned())?; + return Ok(Transformed::yes(Arc::new(exec) as _)); + } + } + + Ok(Transformed::no(plan)) + })? + .data; + + Ok(res) + } + + fn name(&self) -> &str { + "MatchesConstantTerm" + } + + fn schema_check(&self) -> bool { + false + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use arrow::array::{ArrayRef, StringArray}; + use arrow::datatypes::{DataType, Field, Schema}; + use arrow::record_batch::RecordBatch; + use catalog::memory::MemoryCatalogManager; + use catalog::RegisterTableRequest; + use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; + use common_function::scalars::matches_term::MatchesTermFunction; + use common_function::scalars::udf::create_udf; + use common_function::state::FunctionState; + use datafusion::physical_optimizer::PhysicalOptimizerRule; + use datafusion::physical_plan::filter::FilterExec; + use datafusion::physical_plan::get_plan_string; + use datafusion::physical_plan::memory::MemoryExec; + use datafusion_common::{Column, DFSchema, ScalarValue}; + use datafusion_expr::expr::ScalarFunction; + use datafusion_expr::{Expr, ScalarUDF}; + use datafusion_physical_expr::{create_physical_expr, ScalarFunctionExpr}; + use datatypes::prelude::ConcreteDataType; + use datatypes::schema::ColumnSchema; + use session::context::QueryContext; + use table::metadata::{TableInfoBuilder, TableMetaBuilder}; + use table::test_util::EmptyTable; + + use super::*; + use crate::parser::QueryLanguageParser; + use crate::{QueryEngineFactory, QueryEngineRef}; + + fn create_test_batch() -> RecordBatch { + let schema = Schema::new(vec![Field::new("text", DataType::Utf8, true)]); + + let text_array = StringArray::from(vec![ + Some("hello world"), + Some("greeting"), + Some("hello there"), + None, + ]); + + RecordBatch::try_new(Arc::new(schema), vec![Arc::new(text_array) as ArrayRef]).unwrap() + } + + fn create_test_engine() -> QueryEngineRef { + let table_name = "test".to_string(); + let columns = vec![ + ColumnSchema::new( + "text".to_string(), + ConcreteDataType::string_datatype(), + false, + ), + ColumnSchema::new( + "timestamp".to_string(), + ConcreteDataType::timestamp_millisecond_datatype(), + false, + ) + .with_time_index(true), + ]; + + let schema = Arc::new(datatypes::schema::Schema::new(columns)); + let table_meta = TableMetaBuilder::empty() + .schema(schema) + .primary_key_indices(vec![]) + .value_indices(vec![0]) + .next_column_id(2) + .build() + .unwrap(); + let table_info = TableInfoBuilder::default() + .name(&table_name) + .meta(table_meta) + .build() + .unwrap(); + let table = EmptyTable::from_table_info(&table_info); + let catalog_list = MemoryCatalogManager::with_default_setup(); + assert!(catalog_list + .register_table_sync(RegisterTableRequest { + catalog: DEFAULT_CATALOG_NAME.to_string(), + schema: DEFAULT_SCHEMA_NAME.to_string(), + table_name, + table_id: 1024, + table, + }) + .is_ok()); + QueryEngineFactory::new( + catalog_list, + None, + None, + None, + None, + false, + Default::default(), + ) + .query_engine() + } + + fn matches_term_udf() -> Arc { + Arc::new(create_udf( + Arc::new(MatchesTermFunction), + QueryContext::arc(), + Arc::new(FunctionState::default()), + )) + } + + #[test] + fn test_matches_term_optimization() { + let batch = create_test_batch(); + + // Create a predicate with a constant pattern + let predicate = create_physical_expr( + &Expr::ScalarFunction(ScalarFunction::new_udf( + matches_term_udf(), + vec![ + Expr::Column(Column::from_name("text")), + Expr::Literal(ScalarValue::Utf8(Some("hello".to_string()))), + ], + )), + &DFSchema::try_from(batch.schema().clone()).unwrap(), + &Default::default(), + ) + .unwrap(); + + let input = + Arc::new(MemoryExec::try_new(&[vec![batch.clone()]], batch.schema(), None).unwrap()); + let filter = FilterExec::try_new(predicate, input).unwrap(); + + // Apply the optimizer + let optimizer = MatchesConstantTermOptimizer; + let optimized_plan = optimizer + .optimize(Arc::new(filter), &Default::default()) + .unwrap(); + + let optimized_filter = optimized_plan + .as_any() + .downcast_ref::() + .unwrap(); + let predicate = optimized_filter.predicate(); + + // The predicate should be a PreCompiledMatchesTermExpr + assert!( + std::any::TypeId::of::() == predicate.as_any().type_id() + ); + } + + #[test] + fn test_matches_term_no_optimization() { + let batch = create_test_batch(); + + // Create a predicate with a non-constant pattern + let predicate = create_physical_expr( + &Expr::ScalarFunction(ScalarFunction::new_udf( + matches_term_udf(), + vec![ + Expr::Column(Column::from_name("text")), + Expr::Column(Column::from_name("text")), + ], + )), + &DFSchema::try_from(batch.schema().clone()).unwrap(), + &Default::default(), + ) + .unwrap(); + + let input = + Arc::new(MemoryExec::try_new(&[vec![batch.clone()]], batch.schema(), None).unwrap()); + let filter = FilterExec::try_new(predicate, input).unwrap(); + + let optimizer = MatchesConstantTermOptimizer; + let optimized_plan = optimizer + .optimize(Arc::new(filter), &Default::default()) + .unwrap(); + + let optimized_filter = optimized_plan + .as_any() + .downcast_ref::() + .unwrap(); + let predicate = optimized_filter.predicate(); + + // The predicate should still be a ScalarFunctionExpr + assert!(std::any::TypeId::of::() == predicate.as_any().type_id()); + } + + #[tokio::test] + async fn test_matches_term_optimization_from_sql() { + let sql = "WITH base AS ( + SELECT text, timestamp FROM test + WHERE MATCHES_TERM(text, 'hello') + AND timestamp > '2025-01-01 00:00:00' + ), + subquery1 AS ( + SELECT * FROM base + WHERE MATCHES_TERM(text, 'world') + ), + subquery2 AS ( + SELECT * FROM test + WHERE MATCHES_TERM(text, 'greeting') + AND timestamp < '2025-01-02 00:00:00' + ), + union_result AS ( + SELECT * FROM subquery1 + UNION ALL + SELECT * FROM subquery2 + ), + joined_data AS ( + SELECT a.text, a.timestamp, b.text as other_text + FROM union_result a + JOIN test b ON a.timestamp = b.timestamp + WHERE MATCHES_TERM(a.text, 'there') + ) + SELECT text, other_text + FROM joined_data + WHERE MATCHES_TERM(text, '42') + AND MATCHES_TERM(other_text, 'foo')"; + + let query_ctx = QueryContext::arc(); + + let stmt = QueryLanguageParser::parse_sql(sql, &query_ctx).unwrap(); + let engine = create_test_engine(); + let logical_plan = engine + .planner() + .plan(&stmt, query_ctx.clone()) + .await + .unwrap(); + + let engine_ctx = engine.engine_context(query_ctx); + let state = engine_ctx.state(); + + let analyzed_plan = state + .analyzer() + .execute_and_check(logical_plan.clone(), state.config_options(), |_, _| {}) + .unwrap(); + + let optimized_plan = state + .optimizer() + .optimize(analyzed_plan, state, |_, _| {}) + .unwrap(); + + let physical_plan = state + .query_planner() + .create_physical_plan(&optimized_plan, state) + .await + .unwrap(); + + let plan_str = get_plan_string(&physical_plan).join("\n"); + assert!(plan_str.contains("MatchesConstTerm")); + assert!(!plan_str.contains("matches_term")) + } +} diff --git a/src/query/src/optimizer/test_util.rs b/src/query/src/optimizer/test_util.rs index 72e4ad093a..2b3b473770 100644 --- a/src/query/src/optimizer/test_util.rs +++ b/src/query/src/optimizer/test_util.rs @@ -29,7 +29,7 @@ use store_api::metadata::{ }; use store_api::region_engine::{ RegionEngine, RegionManifestInfo, RegionRole, RegionScannerRef, RegionStatistic, - SetRegionRoleStateResponse, SettableRegionRoleState, + SetRegionRoleStateResponse, SettableRegionRoleState, SyncManifestResponse, }; use store_api::region_request::RegionRequest; use store_api::storage::{ConcreteDataType, RegionId, ScanRequest, SequenceNumber}; @@ -113,7 +113,7 @@ impl RegionEngine for MetaRegionEngine { &self, _region_id: RegionId, _manifest_info: RegionManifestInfo, - ) -> Result<(), BoxedError> { + ) -> Result { unimplemented!() } diff --git a/src/query/src/optimizer/transcribe_atat.rs b/src/query/src/optimizer/transcribe_atat.rs new file mode 100644 index 0000000000..3292f19f08 --- /dev/null +++ b/src/query/src/optimizer/transcribe_atat.rs @@ -0,0 +1,230 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::Arc; + +use common_function::scalars::matches_term::MatchesTermFunction; +use common_function::scalars::udf::create_udf; +use common_function::state::FunctionState; +use datafusion::config::ConfigOptions; +use datafusion_common::tree_node::{Transformed, TreeNode, TreeNodeRewriter}; +use datafusion_common::Result; +use datafusion_expr::expr::ScalarFunction; +use datafusion_expr::{Expr, LogicalPlan}; +use datafusion_optimizer::analyzer::AnalyzerRule; +use session::context::QueryContext; + +use crate::plan::ExtractExpr; + +/// TranscribeAtatRule is an analyzer rule that transcribes `@@` operator +/// to `matches_term` function. +/// +/// Example: +/// ```sql +/// SELECT matches_term('cat!', 'cat') as result; +/// +/// SELECT matches_term(`log_message`, '/start') as `matches_start` FROM t; +/// ``` +/// +/// to +/// +/// ```sql +/// SELECT 'cat!' @@ 'cat' as result; +/// +/// SELECT `log_message` @@ '/start' as `matches_start` FROM t; +/// ``` +#[derive(Debug)] +pub struct TranscribeAtatRule; + +impl AnalyzerRule for TranscribeAtatRule { + fn analyze(&self, plan: LogicalPlan, _config: &ConfigOptions) -> Result { + plan.transform(Self::do_analyze).map(|x| x.data) + } + + fn name(&self) -> &str { + "TranscribeAtatRule" + } +} + +impl TranscribeAtatRule { + fn do_analyze(plan: LogicalPlan) -> Result> { + let mut rewriter = TranscribeAtatRewriter::default(); + let new_expr = plan + .expressions_consider_join() + .into_iter() + .map(|e| e.rewrite(&mut rewriter).map(|x| x.data)) + .collect::>>()?; + + if rewriter.transcribed { + let inputs = plan.inputs().into_iter().cloned().collect::>(); + plan.with_new_exprs(new_expr, inputs).map(Transformed::yes) + } else { + Ok(Transformed::no(plan)) + } + } +} + +#[derive(Default)] +struct TranscribeAtatRewriter { + transcribed: bool, +} + +impl TreeNodeRewriter for TranscribeAtatRewriter { + type Node = Expr; + + fn f_up(&mut self, expr: Expr) -> Result> { + if let Expr::BinaryExpr(binary_expr) = &expr + && matches!(binary_expr.op, datafusion_expr::Operator::AtAt) + { + self.transcribed = true; + let scalar_udf = create_udf( + Arc::new(MatchesTermFunction), + QueryContext::arc(), + Arc::new(FunctionState::default()), + ); + let exprs = vec![ + binary_expr.left.as_ref().clone(), + binary_expr.right.as_ref().clone(), + ]; + Ok(Transformed::yes(Expr::ScalarFunction( + ScalarFunction::new_udf(Arc::new(scalar_udf), exprs), + ))) + } else { + Ok(Transformed::no(expr)) + } + } +} +#[cfg(test)] +mod tests { + + use arrow_schema::SchemaRef; + use datafusion::datasource::{provider_as_source, MemTable}; + use datafusion::logical_expr::{col, lit, LogicalPlan, LogicalPlanBuilder}; + use datafusion_expr::{BinaryExpr, Operator}; + use datatypes::arrow::datatypes::{DataType, Field, Schema}; + + use super::*; + + fn optimize(plan: LogicalPlan) -> Result { + TranscribeAtatRule.analyze(plan, &ConfigOptions::default()) + } + + fn prepare_test_plan_builder() -> LogicalPlanBuilder { + let schema = Schema::new(vec![ + Field::new("a", DataType::Utf8, false), + Field::new("b", DataType::Utf8, false), + ]); + let table = MemTable::try_new(SchemaRef::from(schema), vec![]).unwrap(); + LogicalPlanBuilder::scan("t", provider_as_source(Arc::new(table)), None).unwrap() + } + + #[test] + fn test_multiple_atat() { + let plan = prepare_test_plan_builder() + .filter( + Expr::BinaryExpr(BinaryExpr { + left: Box::new(col("a")), + op: Operator::AtAt, + right: Box::new(lit("foo")), + }) + .and(Expr::BinaryExpr(BinaryExpr { + left: Box::new(col("b")), + op: Operator::AtAt, + right: Box::new(lit("bar")), + })), + ) + .unwrap() + .project(vec![ + Expr::BinaryExpr(BinaryExpr { + left: Box::new(col("a")), + op: Operator::AtAt, + right: Box::new(col("b")), + }), + col("b"), + ]) + .unwrap() + .build() + .unwrap(); + + let expected = r#"Projection: matches_term(t.a, t.b), t.b + Filter: matches_term(t.a, Utf8("foo")) AND matches_term(t.b, Utf8("bar")) + TableScan: t"#; + + let optimized_plan = optimize(plan).unwrap(); + let formatted = optimized_plan.to_string(); + + assert_eq!(formatted, expected); + } + + #[test] + fn test_nested_atat() { + let plan = prepare_test_plan_builder() + .filter( + Expr::BinaryExpr(BinaryExpr { + left: Box::new(col("a")), + op: Operator::AtAt, + right: Box::new(lit("foo")), + }) + .and( + Expr::BinaryExpr(BinaryExpr { + left: Box::new(col("b")), + op: Operator::AtAt, + right: Box::new(lit("bar")), + }) + .or(Expr::BinaryExpr(BinaryExpr { + left: Box::new( + // Nested case in function argument + Expr::BinaryExpr(BinaryExpr { + left: Box::new(col("a")), + op: Operator::AtAt, + right: Box::new(lit("nested")), + }), + ), + op: Operator::Eq, + right: Box::new(lit(true)), + })), + ), + ) + .unwrap() + .project(vec![ + col("a"), + // Complex nested expression with multiple @@ operators + Expr::BinaryExpr(BinaryExpr { + left: Box::new(Expr::BinaryExpr(BinaryExpr { + left: Box::new(col("a")), + op: Operator::AtAt, + right: Box::new(lit("foo")), + })), + op: Operator::And, + right: Box::new(Expr::BinaryExpr(BinaryExpr { + left: Box::new(col("b")), + op: Operator::AtAt, + right: Box::new(lit("bar")), + })), + }), + ]) + .unwrap() + .build() + .unwrap(); + + let expected = r#"Projection: t.a, matches_term(t.a, Utf8("foo")) AND matches_term(t.b, Utf8("bar")) + Filter: matches_term(t.a, Utf8("foo")) AND (matches_term(t.b, Utf8("bar")) OR matches_term(t.a, Utf8("nested")) = Boolean(true)) + TableScan: t"#; + + let optimized_plan = optimize(plan).unwrap(); + let formatted = optimized_plan.to_string(); + + assert_eq!(formatted, expected); + } +} diff --git a/src/query/src/options.rs b/src/query/src/options.rs new file mode 100644 index 0000000000..441e9f161f --- /dev/null +++ b/src/query/src/options.rs @@ -0,0 +1,30 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use serde::{Deserialize, Serialize}; + +/// Query engine config +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(default)] +pub struct QueryOptions { + /// Parallelism of query engine. Default to 0, which implies the number of logical CPUs. + pub parallelism: usize, +} + +#[allow(clippy::derivable_impls)] +impl Default for QueryOptions { + fn default() -> Self { + Self { parallelism: 0 } + } +} diff --git a/src/query/src/part_sort.rs b/src/query/src/part_sort.rs index 1c784c8b33..cd35fb66fb 100644 --- a/src/query/src/part_sort.rs +++ b/src/query/src/part_sort.rs @@ -348,7 +348,7 @@ impl PartSortStream { &self, sort_column: &ArrayRef, ) -> datafusion_common::Result> { - if sort_column.len() == 0 { + if sort_column.is_empty() { return Ok(Some(0)); } diff --git a/src/query/src/plan.rs b/src/query/src/plan.rs index e94c073c70..8d5586607e 100644 --- a/src/query/src/plan.rs +++ b/src/query/src/plan.rs @@ -19,12 +19,11 @@ use datafusion_common::tree_node::{Transformed, TreeNode, TreeNodeRewriter}; use datafusion_common::TableReference; use datafusion_expr::{BinaryExpr, Expr, Join, LogicalPlan, Operator}; use session::context::QueryContextRef; -use snafu::ResultExt; pub use table::metadata::TableType; use table::table::adapter::DfTableProviderAdapter; use table::table_name::TableName; -use crate::error::{DataFusionSnafu, Result}; +use crate::error::Result; struct TableNamesExtractAndRewriter { pub(crate) table_names: HashSet, @@ -119,7 +118,7 @@ pub fn extract_and_rewrite_full_table_names( query_ctx: QueryContextRef, ) -> Result<(HashSet, LogicalPlan)> { let mut extractor = TableNamesExtractAndRewriter::new(query_ctx); - let plan = plan.rewrite(&mut extractor).context(DataFusionSnafu)?; + let plan = plan.rewrite(&mut extractor)?; Ok((extractor.table_names, plan.data)) } diff --git a/src/query/src/planner.rs b/src/query/src/planner.rs index b0d0063d70..e3ee3904b4 100644 --- a/src/query/src/planner.rs +++ b/src/query/src/planner.rs @@ -31,7 +31,7 @@ use snafu::ResultExt; use sql::ast::Expr as SqlExpr; use sql::statements::statement::Statement; -use crate::error::{DataFusionSnafu, PlanSqlSnafu, QueryPlanSnafu, Result, SqlSnafu}; +use crate::error::{PlanSqlSnafu, QueryPlanSnafu, Result, SqlSnafu}; use crate::log_query::planner::LogQueryPlanner; use crate::parser::QueryStatement; use crate::promql::planner::PromPlanner; @@ -118,8 +118,7 @@ impl DfLogicalPlanner { let context = QueryEngineContext::new(self.session_state.clone(), query_ctx); let plan = self .engine_state - .optimize_by_extension_rules(plan, &context) - .context(DataFusionSnafu)?; + .optimize_by_extension_rules(plan, &context)?; common_telemetry::debug!("Logical planner, optimize result: {plan}"); Ok(plan) @@ -154,9 +153,7 @@ impl DfLogicalPlanner { let sql_to_rel = SqlToRel::new_with_options(&context_provider, parser_options); - sql_to_rel - .sql_to_expr(sql.into(), schema, &mut PlannerContext::new()) - .context(DataFusionSnafu) + Ok(sql_to_rel.sql_to_expr(sql.into(), schema, &mut PlannerContext::new())?) } #[tracing::instrument(skip_all)] @@ -183,10 +180,7 @@ impl DfLogicalPlanner { #[tracing::instrument(skip_all)] fn optimize_logical_plan(&self, plan: LogicalPlan) -> Result { - self.engine_state - .optimize_logical_plan(plan) - .context(DataFusionSnafu) - .map(Into::into) + Ok(self.engine_state.optimize_logical_plan(plan)?) } } diff --git a/src/query/src/promql/planner.rs b/src/query/src/promql/planner.rs index c0b7bfb098..9f5d67a578 100644 --- a/src/query/src/promql/planner.rs +++ b/src/query/src/promql/planner.rs @@ -31,7 +31,7 @@ use datafusion::functions_aggregate::stddev::stddev_pop_udaf; use datafusion::functions_aggregate::sum::sum_udaf; use datafusion::functions_aggregate::variance::var_pop_udaf; use datafusion::functions_window::row_number::RowNumber; -use datafusion::logical_expr::expr::{AggregateFunction, Alias, ScalarFunction, WindowFunction}; +use datafusion::logical_expr::expr::{Alias, ScalarFunction, WindowFunction}; use datafusion::logical_expr::expr_rewriter::normalize_cols; use datafusion::logical_expr::{ BinaryExpr, Cast, Extension, LogicalPlan, LogicalPlanBuilder, Operator, @@ -1165,13 +1165,17 @@ impl PromPlanner { DfExpr::BinaryExpr(BinaryExpr { left: Box::new(col), op: Operator::RegexMatch, - right: Box::new(lit), + right: Box::new(DfExpr::Literal(ScalarValue::Utf8(Some( + re.as_str().to_string(), + )))), }) } - MatchOp::NotRe(_) => DfExpr::BinaryExpr(BinaryExpr { + MatchOp::NotRe(re) => DfExpr::BinaryExpr(BinaryExpr { left: Box::new(col), op: Operator::RegexNotMatch, - right: Box::new(lit), + right: Box::new(DfExpr::Literal(ScalarValue::Utf8(Some( + re.as_str().to_string(), + )))), }), }; exprs.push(expr); @@ -1421,15 +1425,18 @@ impl PromPlanner { let field_column_pos = 0; let mut exprs = Vec::with_capacity(self.ctx.field_columns.len()); let scalar_func = match func.name { - "increase" => ScalarFunc::ExtrapolateUdf(Arc::new(Increase::scalar_udf( + "increase" => ScalarFunc::ExtrapolateUdf( + Arc::new(Increase::scalar_udf()), self.ctx.range.context(ExpectRangeSelectorSnafu)?, - ))), - "rate" => ScalarFunc::ExtrapolateUdf(Arc::new(Rate::scalar_udf( + ), + "rate" => ScalarFunc::ExtrapolateUdf( + Arc::new(Rate::scalar_udf()), self.ctx.range.context(ExpectRangeSelectorSnafu)?, - ))), - "delta" => ScalarFunc::ExtrapolateUdf(Arc::new(Delta::scalar_udf( + ), + "delta" => ScalarFunc::ExtrapolateUdf( + Arc::new(Delta::scalar_udf()), self.ctx.range.context(ExpectRangeSelectorSnafu)?, - ))), + ), "idelta" => ScalarFunc::Udf(Arc::new(IDelta::::scalar_udf())), "irate" => ScalarFunc::Udf(Arc::new(IDelta::::scalar_udf())), "resets" => ScalarFunc::Udf(Arc::new(Resets::scalar_udf())), @@ -1445,50 +1452,9 @@ impl PromPlanner { "present_over_time" => ScalarFunc::Udf(Arc::new(PresentOverTime::scalar_udf())), "stddev_over_time" => ScalarFunc::Udf(Arc::new(StddevOverTime::scalar_udf())), "stdvar_over_time" => ScalarFunc::Udf(Arc::new(StdvarOverTime::scalar_udf())), - "quantile_over_time" => { - let quantile_expr = match other_input_exprs.pop_front() { - Some(DfExpr::Literal(ScalarValue::Float64(Some(quantile)))) => quantile, - other => UnexpectedPlanExprSnafu { - desc: format!("expected f64 literal as quantile, but found {:?}", other), - } - .fail()?, - }; - ScalarFunc::Udf(Arc::new(QuantileOverTime::scalar_udf(quantile_expr))) - } - "predict_linear" => { - let t_expr = match other_input_exprs.pop_front() { - Some(DfExpr::Literal(ScalarValue::Float64(Some(t)))) => t as i64, - Some(DfExpr::Literal(ScalarValue::Int64(Some(t)))) => t, - other => UnexpectedPlanExprSnafu { - desc: format!("expected i64 literal as t, but found {:?}", other), - } - .fail()?, - }; - ScalarFunc::Udf(Arc::new(PredictLinear::scalar_udf(t_expr))) - } - "holt_winters" => { - let sf_exp = match other_input_exprs.pop_front() { - Some(DfExpr::Literal(ScalarValue::Float64(Some(sf)))) => sf, - other => UnexpectedPlanExprSnafu { - desc: format!( - "expected f64 literal as smoothing factor, but found {:?}", - other - ), - } - .fail()?, - }; - let tf_exp = match other_input_exprs.pop_front() { - Some(DfExpr::Literal(ScalarValue::Float64(Some(tf)))) => tf, - other => UnexpectedPlanExprSnafu { - desc: format!( - "expected f64 literal as trend factor, but found {:?}", - other - ), - } - .fail()?, - }; - ScalarFunc::Udf(Arc::new(HoltWinters::scalar_udf(sf_exp, tf_exp))) - } + "quantile_over_time" => ScalarFunc::Udf(Arc::new(QuantileOverTime::scalar_udf())), + "predict_linear" => ScalarFunc::Udf(Arc::new(PredictLinear::scalar_udf())), + "holt_winters" => ScalarFunc::Udf(Arc::new(HoltWinters::scalar_udf())), "time" => { exprs.push(build_special_time_expr( self.ctx.time_index_column.as_ref().unwrap(), @@ -1623,17 +1589,10 @@ impl PromPlanner { ScalarFunc::GeneratedExpr } "round" => { - let nearest = match other_input_exprs.pop_front() { - Some(DfExpr::Literal(ScalarValue::Float64(Some(t)))) => t, - Some(DfExpr::Literal(ScalarValue::Int64(Some(t)))) => t as f64, - None => 0.0, - other => UnexpectedPlanExprSnafu { - desc: format!("expected f64 literal as t, but found {:?}", other), - } - .fail()?, - }; - - ScalarFunc::DataFusionUdf(Arc::new(Round::scalar_udf(nearest))) + if other_input_exprs.is_empty() { + other_input_exprs.push_front(DfExpr::Literal(ScalarValue::Float64(Some(0.0)))); + } + ScalarFunc::DataFusionUdf(Arc::new(Round::scalar_udf())) } _ => { @@ -1691,7 +1650,7 @@ impl PromPlanner { let _ = other_input_exprs.remove(field_column_pos + 1); let _ = other_input_exprs.remove(field_column_pos); } - ScalarFunc::ExtrapolateUdf(func) => { + ScalarFunc::ExtrapolateUdf(func, range_length) => { let ts_range_expr = DfExpr::Column(Column::from_name( RangeManipulate::build_timestamp_range_name( self.ctx.time_index_column.as_ref().unwrap(), @@ -1701,11 +1660,13 @@ impl PromPlanner { other_input_exprs.insert(field_column_pos + 1, col_expr); other_input_exprs .insert(field_column_pos + 2, self.create_time_index_column_expr()?); + other_input_exprs.push_back(lit(range_length)); let fn_expr = DfExpr::ScalarFunction(ScalarFunction { func, args: other_input_exprs.clone().into(), }); exprs.push(fn_expr); + let _ = other_input_exprs.pop_back(); let _ = other_input_exprs.remove(field_column_pos + 2); let _ = other_input_exprs.remove(field_column_pos + 1); let _ = other_input_exprs.remove(field_column_pos); @@ -1968,11 +1929,13 @@ impl PromPlanner { param: &Option>, input_plan: &LogicalPlan, ) -> Result<(Vec, Vec)> { + let mut non_col_args = Vec::new(); let aggr = match op.id() { token::T_SUM => sum_udaf(), token::T_QUANTILE => { let q = Self::get_param_value_as_f64(op, param)?; - quantile_udaf(q) + non_col_args.push(lit(q)); + quantile_udaf() } token::T_AVG => avg_udaf(), token::T_COUNT_VALUES | token::T_COUNT => count_udaf(), @@ -1994,16 +1957,12 @@ impl PromPlanner { .field_columns .iter() .map(|col| { - Ok(DfExpr::AggregateFunction(AggregateFunction { - func: aggr.clone(), - args: vec![DfExpr::Column(Column::from_name(col))], - distinct: false, - filter: None, - order_by: None, - null_treatment: None, - })) + non_col_args.push(DfExpr::Column(Column::from_name(col))); + let expr = aggr.call(non_col_args.clone()); + non_col_args.pop(); + expr }) - .collect::>>()?; + .collect::>(); // if the aggregator is `count_values`, it must be grouped by current fields. let prev_field_exprs = if op.id() == token::T_COUNT_VALUES { @@ -2937,7 +2896,8 @@ enum ScalarFunc { Udf(Arc), // todo(ruihang): maybe merge with Udf later /// UDF that require extra information like range length to be evaluated. - ExtrapolateUdf(Arc), + /// The second argument is range length. + ExtrapolateUdf(Arc, i64), /// Func that doesn't require input, like `time()`. GeneratedExpr, } @@ -3591,8 +3551,8 @@ mod test { async fn increase_aggr() { let query = "increase(some_metric[5m])"; let expected = String::from( - "Filter: prom_increase(timestamp_range,field_0,timestamp) IS NOT NULL [timestamp:Timestamp(Millisecond, None), prom_increase(timestamp_range,field_0,timestamp):Float64;N, tag_0:Utf8]\ - \n Projection: some_metric.timestamp, prom_increase(timestamp_range, field_0, some_metric.timestamp) AS prom_increase(timestamp_range,field_0,timestamp), some_metric.tag_0 [timestamp:Timestamp(Millisecond, None), prom_increase(timestamp_range,field_0,timestamp):Float64;N, tag_0:Utf8]\ + "Filter: prom_increase(timestamp_range,field_0,timestamp,Int64(300000)) IS NOT NULL [timestamp:Timestamp(Millisecond, None), prom_increase(timestamp_range,field_0,timestamp,Int64(300000)):Float64;N, tag_0:Utf8]\ + \n Projection: some_metric.timestamp, prom_increase(timestamp_range, field_0, some_metric.timestamp, Int64(300000)) AS prom_increase(timestamp_range,field_0,timestamp,Int64(300000)), some_metric.tag_0 [timestamp:Timestamp(Millisecond, None), prom_increase(timestamp_range,field_0,timestamp,Int64(300000)):Float64;N, tag_0:Utf8]\ \n PromRangeManipulate: req range=[0..100000000], interval=[5000], eval range=[300000], time index=[timestamp], values=[\"field_0\"] [tag_0:Utf8, timestamp:Timestamp(Millisecond, None), field_0:Dictionary(Int64, Float64);N, timestamp_range:Dictionary(Int64, Timestamp(Millisecond, None))]\ \n PromSeriesNormalize: offset=[0], time index=[timestamp], filter NaN: [true] [tag_0:Utf8, timestamp:Timestamp(Millisecond, None), field_0:Float64;N]\ \n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(Millisecond, None), field_0:Float64;N]\ @@ -4237,7 +4197,8 @@ mod test { interval: Duration::from_secs(5), lookback_delta: Duration::from_secs(1), }; - let case = r#"sum(prometheus_tsdb_head_series{tag_1=~"(10\\.0\\.160\\.237:8080|10\\.0\\.160\\.237:9090)"})"#; + let case = + r#"sum(prometheus_tsdb_head_series{tag_1=~"(10.0.160.237:8080|10.0.160.237:9090)"})"#; let prom_expr = parser::parse(case).unwrap(); eval_stmt.expr = prom_expr; @@ -4258,7 +4219,7 @@ mod test { \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, timestamp:Timestamp(Millisecond, None), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N]\ \n PromSeriesDivide: tags=[\"tag_0\", \"tag_1\", \"tag_2\"] [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, timestamp:Timestamp(Millisecond, None), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N]\ \n Sort: prometheus_tsdb_head_series.tag_0 ASC NULLS FIRST, prometheus_tsdb_head_series.tag_1 ASC NULLS FIRST, prometheus_tsdb_head_series.tag_2 ASC NULLS FIRST, prometheus_tsdb_head_series.timestamp ASC NULLS FIRST [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, timestamp:Timestamp(Millisecond, None), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N]\ - \n Filter: prometheus_tsdb_head_series.tag_1 ~ Utf8(\"(10\\.0\\.160\\.237:8080|10\\.0\\.160\\.237:9090)\") AND prometheus_tsdb_head_series.timestamp >= TimestampMillisecond(-1000, None) AND prometheus_tsdb_head_series.timestamp <= TimestampMillisecond(100001000, None) [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, timestamp:Timestamp(Millisecond, None), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N]\ + \n Filter: prometheus_tsdb_head_series.tag_1 ~ Utf8(\"^(10.0.160.237:8080|10.0.160.237:9090)$\") AND prometheus_tsdb_head_series.timestamp >= TimestampMillisecond(-1000, None) AND prometheus_tsdb_head_series.timestamp <= TimestampMillisecond(100001000, None) [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, timestamp:Timestamp(Millisecond, None), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N]\ \n TableScan: prometheus_tsdb_head_series [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, timestamp:Timestamp(Millisecond, None), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N]"; assert_eq!(plan.display_indent_schema().to_string(), expected); } @@ -4274,7 +4235,7 @@ mod test { interval: Duration::from_secs(5), lookback_delta: Duration::from_secs(1), }; - let case = r#"topk(10, sum(prometheus_tsdb_head_series{ip=~"(10\\.0\\.160\\.237:8080|10\\.0\\.160\\.237:9090)"}) by (ip))"#; + let case = r#"topk(10, sum(prometheus_tsdb_head_series{ip=~"(10.0.160.237:8080|10.0.160.237:9090)"}) by (ip))"#; let prom_expr = parser::parse(case).unwrap(); eval_stmt.expr = prom_expr; @@ -4305,7 +4266,7 @@ mod test { \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[greptime_timestamp] [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ \n PromSeriesDivide: tags=[\"ip\"] [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ \n Sort: prometheus_tsdb_head_series.ip ASC NULLS FIRST, prometheus_tsdb_head_series.greptime_timestamp ASC NULLS FIRST [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ - \n Filter: prometheus_tsdb_head_series.ip ~ Utf8(\"(10\\.0\\.160\\.237:8080|10\\.0\\.160\\.237:9090)\") AND prometheus_tsdb_head_series.greptime_timestamp >= TimestampMillisecond(-1000, None) AND prometheus_tsdb_head_series.greptime_timestamp <= TimestampMillisecond(100001000, None) [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ + \n Filter: prometheus_tsdb_head_series.ip ~ Utf8(\"^(10.0.160.237:8080|10.0.160.237:9090)$\") AND prometheus_tsdb_head_series.greptime_timestamp >= TimestampMillisecond(-1000, None) AND prometheus_tsdb_head_series.greptime_timestamp <= TimestampMillisecond(100001000, None) [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ \n TableScan: prometheus_tsdb_head_series [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]"; assert_eq!(plan.display_indent_schema().to_string(), expected); @@ -4322,7 +4283,7 @@ mod test { interval: Duration::from_secs(5), lookback_delta: Duration::from_secs(1), }; - let case = r#"count_values('series', prometheus_tsdb_head_series{ip=~"(10\\.0\\.160\\.237:8080|10\\.0\\.160\\.237:9090)"}) by (ip)"#; + let case = r#"count_values('series', prometheus_tsdb_head_series{ip=~"(10.0.160.237:8080|10.0.160.237:9090)"}) by (ip)"#; let prom_expr = parser::parse(case).unwrap(); eval_stmt.expr = prom_expr; @@ -4351,7 +4312,7 @@ mod test { \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[greptime_timestamp] [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ \n PromSeriesDivide: tags=[\"ip\"] [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ \n Sort: prometheus_tsdb_head_series.ip ASC NULLS FIRST, prometheus_tsdb_head_series.greptime_timestamp ASC NULLS FIRST [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ - \n Filter: prometheus_tsdb_head_series.ip ~ Utf8(\"(10\\.0\\.160\\.237:8080|10\\.0\\.160\\.237:9090)\") AND prometheus_tsdb_head_series.greptime_timestamp >= TimestampMillisecond(-1000, None) AND prometheus_tsdb_head_series.greptime_timestamp <= TimestampMillisecond(100001000, None) [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ + \n Filter: prometheus_tsdb_head_series.ip ~ Utf8(\"^(10.0.160.237:8080|10.0.160.237:9090)$\") AND prometheus_tsdb_head_series.greptime_timestamp >= TimestampMillisecond(-1000, None) AND prometheus_tsdb_head_series.greptime_timestamp <= TimestampMillisecond(100001000, None) [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ \n TableScan: prometheus_tsdb_head_series [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]"; assert_eq!(plan.display_indent_schema().to_string(), expected); @@ -4368,7 +4329,7 @@ mod test { interval: Duration::from_secs(5), lookback_delta: Duration::from_secs(1), }; - let case = r#"quantile(0.3, sum(prometheus_tsdb_head_series{ip=~"(10\\.0\\.160\\.237:8080|10\\.0\\.160\\.237:9090)"}) by (ip))"#; + let case = r#"quantile(0.3, sum(prometheus_tsdb_head_series{ip=~"(10.0.160.237:8080|10.0.160.237:9090)"}) by (ip))"#; let prom_expr = parser::parse(case).unwrap(); eval_stmt.expr = prom_expr; @@ -4390,14 +4351,14 @@ mod test { let plan = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_session_state()) .await .unwrap(); - let expected = "Sort: prometheus_tsdb_head_series.greptime_timestamp ASC NULLS LAST [greptime_timestamp:Timestamp(Millisecond, None), quantile(sum(prometheus_tsdb_head_series.greptime_value)):Float64;N]\ - \n Aggregate: groupBy=[[prometheus_tsdb_head_series.greptime_timestamp]], aggr=[[quantile(sum(prometheus_tsdb_head_series.greptime_value))]] [greptime_timestamp:Timestamp(Millisecond, None), quantile(sum(prometheus_tsdb_head_series.greptime_value)):Float64;N]\ + let expected = "Sort: prometheus_tsdb_head_series.greptime_timestamp ASC NULLS LAST [greptime_timestamp:Timestamp(Millisecond, None), quantile(Float64(0.3),sum(prometheus_tsdb_head_series.greptime_value)):Float64;N]\ + \n Aggregate: groupBy=[[prometheus_tsdb_head_series.greptime_timestamp]], aggr=[[quantile(Float64(0.3), sum(prometheus_tsdb_head_series.greptime_value))]] [greptime_timestamp:Timestamp(Millisecond, None), quantile(Float64(0.3),sum(prometheus_tsdb_head_series.greptime_value)):Float64;N]\ \n Sort: prometheus_tsdb_head_series.ip ASC NULLS LAST, prometheus_tsdb_head_series.greptime_timestamp ASC NULLS LAST [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), sum(prometheus_tsdb_head_series.greptime_value):Float64;N]\ \n Aggregate: groupBy=[[prometheus_tsdb_head_series.ip, prometheus_tsdb_head_series.greptime_timestamp]], aggr=[[sum(prometheus_tsdb_head_series.greptime_value)]] [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), sum(prometheus_tsdb_head_series.greptime_value):Float64;N]\ \n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[greptime_timestamp] [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ \n PromSeriesDivide: tags=[\"ip\"] [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ \n Sort: prometheus_tsdb_head_series.ip ASC NULLS FIRST, prometheus_tsdb_head_series.greptime_timestamp ASC NULLS FIRST [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ - \n Filter: prometheus_tsdb_head_series.ip ~ Utf8(\"(10\\.0\\.160\\.237:8080|10\\.0\\.160\\.237:9090)\") AND prometheus_tsdb_head_series.greptime_timestamp >= TimestampMillisecond(-1000, None) AND prometheus_tsdb_head_series.greptime_timestamp <= TimestampMillisecond(100001000, None) [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ + \n Filter: prometheus_tsdb_head_series.ip ~ Utf8(\"^(10.0.160.237:8080|10.0.160.237:9090)$\") AND prometheus_tsdb_head_series.greptime_timestamp >= TimestampMillisecond(-1000, None) AND prometheus_tsdb_head_series.greptime_timestamp <= TimestampMillisecond(100001000, None) [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]\ \n TableScan: prometheus_tsdb_head_series [ip:Utf8, greptime_timestamp:Timestamp(Millisecond, None), greptime_value:Float64;N]"; assert_eq!(plan.display_indent_schema().to_string(), expected); diff --git a/src/query/src/query_engine.rs b/src/query/src/query_engine.rs index c4e8aee7d1..8b0c091054 100644 --- a/src/query/src/query_engine.rs +++ b/src/query/src/query_engine.rs @@ -38,6 +38,7 @@ use table::TableRef; use crate::dataframe::DataFrame; use crate::datafusion::DatafusionQueryEngine; use crate::error::Result; +use crate::options::QueryOptions; use crate::planner::LogicalPlanner; pub use crate::query_engine::context::QueryEngineContext; pub use crate::query_engine::state::QueryEngineState; @@ -106,6 +107,7 @@ impl QueryEngineFactory { procedure_service_handler: Option, flow_service_handler: Option, with_dist_planner: bool, + options: QueryOptions, ) -> Self { Self::new_with_plugins( catalog_manager, @@ -115,9 +117,11 @@ impl QueryEngineFactory { flow_service_handler, with_dist_planner, Default::default(), + options, ) } + #[allow(clippy::too_many_arguments)] pub fn new_with_plugins( catalog_manager: CatalogManagerRef, region_query_handler: Option, @@ -126,6 +130,7 @@ impl QueryEngineFactory { flow_service_handler: Option, with_dist_planner: bool, plugins: Plugins, + options: QueryOptions, ) -> Self { let state = Arc::new(QueryEngineState::new( catalog_manager, @@ -135,6 +140,7 @@ impl QueryEngineFactory { flow_service_handler, with_dist_planner, plugins.clone(), + options, )); let query_engine = Arc::new(DatafusionQueryEngine::new(state, plugins)); register_functions(&query_engine); @@ -166,7 +172,15 @@ mod tests { #[test] fn test_query_engine_factory() { let catalog_list = catalog::memory::new_memory_catalog_manager().unwrap(); - let factory = QueryEngineFactory::new(catalog_list, None, None, None, None, false); + let factory = QueryEngineFactory::new( + catalog_list, + None, + None, + None, + None, + false, + QueryOptions::default(), + ); let engine = factory.query_engine(); diff --git a/src/query/src/query_engine/context.rs b/src/query/src/query_engine/context.rs index d8c110d2f2..df20a70a42 100644 --- a/src/query/src/query_engine/context.rs +++ b/src/query/src/query_engine/context.rs @@ -75,6 +75,7 @@ impl QueryEngineContext { use common_base::Plugins; use session::context::QueryContext; + use crate::options::QueryOptions; use crate::query_engine::QueryEngineState; let state = Arc::new(QueryEngineState::new( @@ -85,6 +86,7 @@ impl QueryEngineContext { None, false, Plugins::default(), + QueryOptions::default(), )); QueryEngineContext::new(state.session_state(), QueryContext::arc()) diff --git a/src/query/src/query_engine/default_serializer.rs b/src/query/src/query_engine/default_serializer.rs index 24e672b385..4dbf008bb6 100644 --- a/src/query/src/query_engine/default_serializer.rs +++ b/src/query/src/query_engine/default_serializer.rs @@ -181,6 +181,7 @@ mod tests { use super::*; use crate::dummy_catalog::DummyCatalogList; use crate::optimizer::test_util::mock_table_provider; + use crate::options::QueryOptions; use crate::QueryEngineFactory; fn mock_plan(schema: SchemaRef) -> LogicalPlan { @@ -199,7 +200,15 @@ mod tests { #[tokio::test] async fn test_serializer_decode_plan() { let catalog_list = catalog::memory::new_memory_catalog_manager().unwrap(); - let factory = QueryEngineFactory::new(catalog_list, None, None, None, None, false); + let factory = QueryEngineFactory::new( + catalog_list, + None, + None, + None, + None, + false, + QueryOptions::default(), + ); let engine = factory.query_engine(); diff --git a/src/query/src/query_engine/state.rs b/src/query/src/query_engine/state.rs index bf86a88fe8..7acd38aa37 100644 --- a/src/query/src/query_engine/state.rs +++ b/src/query/src/query_engine/state.rs @@ -46,15 +46,18 @@ use table::table::adapter::DfTableProviderAdapter; use table::TableRef; use crate::dist_plan::{DistExtensionPlanner, DistPlannerAnalyzer, MergeSortExtensionPlanner}; +use crate::optimizer::constant_term::MatchesConstantTermOptimizer; use crate::optimizer::count_wildcard::CountWildcardToTimeIndexRule; use crate::optimizer::parallelize_scan::ParallelizeScan; use crate::optimizer::pass_distribution::PassDistribution; use crate::optimizer::remove_duplicate::RemoveDuplicate; use crate::optimizer::scan_hint::ScanHintRule; use crate::optimizer::string_normalization::StringNormalizationRule; +use crate::optimizer::transcribe_atat::TranscribeAtatRule; use crate::optimizer::type_conversion::TypeConversionRule; use crate::optimizer::windowed_sort::WindowedSortPhysicalRule; use crate::optimizer::ExtensionAnalyzerRule; +use crate::options::QueryOptions as QueryOptionsNew; use crate::query_engine::options::QueryOptions; use crate::query_engine::DefaultSerializer; use crate::range_select::planner::RangeSelectPlanner; @@ -82,6 +85,7 @@ impl fmt::Debug for QueryEngineState { } impl QueryEngineState { + #[allow(clippy::too_many_arguments)] pub fn new( catalog_list: CatalogManagerRef, region_query_handler: Option, @@ -90,9 +94,13 @@ impl QueryEngineState { flow_service_handler: Option, with_dist_planner: bool, plugins: Plugins, + options: QueryOptionsNew, ) -> Self { let runtime_env = Arc::new(RuntimeEnv::default()); let mut session_config = SessionConfig::new().with_create_default_catalog_and_schema(false); + if options.parallelism > 0 { + session_config = session_config.with_target_partitions(options.parallelism); + } // todo(hl): This serves as a workaround for https://github.com/GreptimeTeam/greptimedb/issues/5659 // and we can add that check back once we upgrade datafusion. @@ -109,6 +117,7 @@ impl QueryEngineState { // Apply the datafusion rules let mut analyzer = Analyzer::new(); + analyzer.rules.insert(0, Arc::new(TranscribeAtatRule)); analyzer.rules.insert(0, Arc::new(StringNormalizationRule)); // Use our custom rule instead to optimize the count(*) query @@ -141,6 +150,9 @@ impl QueryEngineState { physical_optimizer .rules .push(Arc::new(WindowedSortPhysicalRule)); + physical_optimizer + .rules + .push(Arc::new(MatchesConstantTermOptimizer)); // Add rule to remove duplicate nodes generated by other rules. Run this in the last. physical_optimizer.rules.push(Arc::new(RemoveDuplicate)); // Place SanityCheckPlan at the end of the list to ensure that it runs after all other rules. diff --git a/src/query/src/range_select/plan.rs b/src/query/src/range_select/plan.rs index eb28aacf1e..f2a25997bc 100644 --- a/src/query/src/range_select/plan.rs +++ b/src/query/src/range_select/plan.rs @@ -55,9 +55,9 @@ use datatypes::arrow::record_batch::RecordBatch; use datatypes::arrow::row::{OwnedRow, RowConverter, SortField}; use futures::{ready, Stream}; use futures_util::StreamExt; -use snafu::{ensure, ResultExt}; +use snafu::ensure; -use crate::error::{DataFusionSnafu, RangeQuerySnafu, Result}; +use crate::error::{RangeQuerySnafu, Result}; type Millisecond = ::Native; @@ -373,25 +373,22 @@ impl RangeSelect { Ok((None, Arc::new(field))) }, ) - .collect::>>() - .context(DataFusionSnafu)?; + .collect::>>()?; // add align_ts - let ts_field = time_index - .to_field(input.schema().as_ref()) - .context(DataFusionSnafu)?; + let ts_field = time_index.to_field(input.schema().as_ref())?; let time_index_name = ts_field.1.name().clone(); fields.push(ts_field); // add by - let by_fields = exprlist_to_fields(&by, &input).context(DataFusionSnafu)?; + let by_fields = exprlist_to_fields(&by, &input)?; fields.extend(by_fields.clone()); - let schema_before_project = Arc::new( - DFSchema::new_with_metadata(fields, input.schema().metadata().clone()) - .context(DataFusionSnafu)?, - ); - let by_schema = Arc::new( - DFSchema::new_with_metadata(by_fields, input.schema().metadata().clone()) - .context(DataFusionSnafu)?, - ); + let schema_before_project = Arc::new(DFSchema::new_with_metadata( + fields, + input.schema().metadata().clone(), + )?); + let by_schema = Arc::new(DFSchema::new_with_metadata( + by_fields, + input.schema().metadata().clone(), + )?); // If the results of project plan can be obtained directly from range plan without any additional // calculations, no project plan is required. We can simply project the final output of the range // plan to produce the final result. @@ -421,10 +418,10 @@ impl RangeSelect { (f.0.cloned(), Arc::new(f.1.clone())) }) .collect(); - Arc::new( - DFSchema::new_with_metadata(project_field, input.schema().metadata().clone()) - .context(DataFusionSnafu)?, - ) + Arc::new(DFSchema::new_with_metadata( + project_field, + input.schema().metadata().clone(), + )?) } else { schema_before_project.clone() }; diff --git a/src/query/src/range_select/plan_rewrite.rs b/src/query/src/range_select/plan_rewrite.rs index ff05a26706..5e0f223663 100644 --- a/src/query/src/range_select/plan_rewrite.rs +++ b/src/query/src/range_select/plan_rewrite.rs @@ -43,8 +43,7 @@ use snafu::{ensure, OptionExt, ResultExt}; use table::table::adapter::DfTableProviderAdapter; use crate::error::{ - CatalogSnafu, DataFusionSnafu, RangeQuerySnafu, Result, TimeIndexNotFoundSnafu, - UnknownTableSnafu, + CatalogSnafu, RangeQuerySnafu, Result, TimeIndexNotFoundSnafu, UnknownTableSnafu, }; use crate::plan::ExtractExpr; use crate::range_select::plan::{Fill, RangeFn, RangeSelect}; @@ -385,8 +384,7 @@ impl RangePlanRewriter { let new_expr = expr .iter() .map(|expr| expr.clone().rewrite(&mut range_rewriter).map(|x| x.data)) - .collect::>>() - .context(DataFusionSnafu)?; + .collect::>>()?; if range_rewriter.by.is_empty() { range_rewriter.by = default_by; } @@ -408,9 +406,7 @@ impl RangePlanRewriter { } else { let project_plan = LogicalPlanBuilder::from(range_plan) .project(new_expr) - .context(DataFusionSnafu)? - .build() - .context(DataFusionSnafu)?; + .and_then(|x| x.build())?; Ok(Some(project_plan)) } } @@ -436,8 +432,7 @@ impl RangePlanRewriter { } ); LogicalPlanBuilder::from(inputs[0].clone()) - .explain(*verbose, true) - .context(DataFusionSnafu)? + .explain(*verbose, true)? .build() } LogicalPlan::Explain(Explain { verbose, .. }) => { @@ -448,8 +443,7 @@ impl RangePlanRewriter { } ); LogicalPlanBuilder::from(inputs[0].clone()) - .explain(*verbose, false) - .context(DataFusionSnafu)? + .explain(*verbose, false)? .build() } LogicalPlan::Distinct(Distinct::On(DistinctOn { @@ -470,13 +464,11 @@ impl RangePlanRewriter { on_expr.clone(), select_expr.clone(), sort_expr.clone(), - ) - .context(DataFusionSnafu)? + )? .build() } _ => plan.with_new_exprs(plan.expressions_consider_join(), inputs), - } - .context(DataFusionSnafu)?; + }?; Ok(Some(plan)) } else { Ok(None) @@ -606,8 +598,6 @@ fn interval_only_in_expr(expr: &Expr) -> bool { #[cfg(test)] mod test { - use std::error::Error; - use arrow::datatypes::IntervalUnit; use catalog::memory::MemoryCatalogManager; use catalog::RegisterTableRequest; @@ -621,6 +611,7 @@ mod test { use table::test_util::EmptyTable; use super::*; + use crate::options::QueryOptions; use crate::parser::QueryLanguageParser; use crate::{QueryEngineFactory, QueryEngineRef}; @@ -673,7 +664,16 @@ mod test { table, }) .is_ok()); - QueryEngineFactory::new(catalog_list, None, None, None, None, false).query_engine() + QueryEngineFactory::new( + catalog_list, + None, + None, + None, + None, + false, + QueryOptions::default(), + ) + .query_engine() } async fn do_query(sql: &str) -> Result { @@ -825,12 +825,7 @@ mod test { /// the right argument is `range_fn(avg(field_0), '5m', 'NULL', '0', '1h')` async fn range_argument_err_1() { let query = r#"SELECT range_fn('5m', avg(field_0), 'NULL', '1', tag_0, '1h') FROM test group by tag_0;"#; - let error = do_query(query) - .await - .unwrap_err() - .source() - .unwrap() - .to_string(); + let error = do_query(query).await.unwrap_err().to_string(); assert_eq!( error, "Error during planning: Illegal argument `Utf8(\"5m\")` in range select query" @@ -840,12 +835,7 @@ mod test { #[tokio::test] async fn range_argument_err_2() { let query = r#"SELECT range_fn(avg(field_0), 5, 'NULL', '1', tag_0, '1h') FROM test group by tag_0;"#; - let error = do_query(query) - .await - .unwrap_err() - .source() - .unwrap() - .to_string(); + let error = do_query(query).await.unwrap_err().to_string(); assert_eq!( error, "Error during planning: Illegal argument `Int64(5)` in range select query" diff --git a/src/query/src/sql.rs b/src/query/src/sql.rs index fbda344427..8f823fe809 100644 --- a/src/query/src/sql.rs +++ b/src/query/src/sql.rs @@ -40,7 +40,7 @@ use common_recordbatch::RecordBatches; use common_time::timezone::get_timezone; use common_time::Timestamp; use datafusion::common::ScalarValue; -use datafusion::prelude::{concat_ws, SessionContext}; +use datafusion::prelude::SessionContext; use datafusion_expr::expr::WildcardOptions; use datafusion_expr::{case, col, lit, Expr, SortExpr}; use datatypes::prelude::*; @@ -301,8 +301,7 @@ async fn query_from_information_schema_table( .state() .clone(), ) - .read_table(view) - .context(error::DataFusionSnafu)?; + .read_table(view)?; let planner = query_engine.planner(); let planner = planner @@ -319,10 +318,7 @@ async fn query_from_information_schema_table( } }; - let stream = dataframe - .execute_stream() - .await - .context(error::DataFusionSnafu)?; + let stream = dataframe.execute_stream().await?; Ok(Output::new_with_stream(Box::pin( RecordBatchStreamAdapter::try_new(stream).context(error::CreateRecordBatchSnafu)?, @@ -403,23 +399,6 @@ pub async fn show_index( query_ctx.current_schema() }; - let primary_key_expr = case(col("constraint_name").like(lit("%PRIMARY%"))) - .when(lit(true), lit("greptime-primary-key-v1")) - .otherwise(null()) - .context(error::PlanSqlSnafu)?; - let inverted_index_expr = case(col("constraint_name").like(lit("%INVERTED INDEX%"))) - .when(lit(true), lit("greptime-inverted-index-v1")) - .otherwise(null()) - .context(error::PlanSqlSnafu)?; - let fulltext_index_expr = case(col("constraint_name").like(lit("%FULLTEXT INDEX%"))) - .when(lit(true), lit("greptime-fulltext-index-v1")) - .otherwise(null()) - .context(error::PlanSqlSnafu)?; - let skipping_index_expr = case(col("constraint_name").like(lit("%SKIPPING INDEX%"))) - .when(lit(true), lit("greptime-bloom-filter-v1")) - .otherwise(null()) - .context(error::PlanSqlSnafu)?; - let select = vec![ // 1 as `Non_unique`: contain duplicates lit(1).alias(INDEX_NONT_UNIQUE_COLUMN), @@ -437,16 +416,6 @@ pub async fn show_index( .otherwise(lit(YES_STR)) .context(error::PlanSqlSnafu)? .alias(COLUMN_NULLABLE_COLUMN), - concat_ws( - lit(", "), - vec![ - primary_key_expr, - inverted_index_expr, - fulltext_index_expr, - skipping_index_expr, - ], - ) - .alias(INDEX_INDEX_TYPE_COLUMN), lit("").alias(COLUMN_COMMENT_COLUMN), lit("").alias(INDEX_COMMENT_COLUMN), lit(YES_STR).alias(INDEX_VISIBLE_COLUMN), @@ -471,7 +440,10 @@ pub async fn show_index( (INDEX_SUB_PART_COLUMN, INDEX_SUB_PART_COLUMN), (INDEX_PACKED_COLUMN, INDEX_PACKED_COLUMN), (COLUMN_NULLABLE_COLUMN, COLUMN_NULLABLE_COLUMN), - (INDEX_INDEX_TYPE_COLUMN, INDEX_INDEX_TYPE_COLUMN), + ( + key_column_usage::GREPTIME_INDEX_TYPE, + INDEX_INDEX_TYPE_COLUMN, + ), (COLUMN_COMMENT_COLUMN, COLUMN_COMMENT_COLUMN), (INDEX_COMMENT_COLUMN, INDEX_COMMENT_COLUMN), (INDEX_VISIBLE_COLUMN, INDEX_VISIBLE_COLUMN), diff --git a/src/query/src/sql/show_create_table.rs b/src/query/src/sql/show_create_table.rs index 3eebfbc03e..bc004f514e 100644 --- a/src/query/src/sql/show_create_table.rs +++ b/src/query/src/sql/show_create_table.rs @@ -19,8 +19,8 @@ use std::collections::HashMap; use common_meta::SchemaOptions; use datatypes::schema::{ ColumnDefaultConstraint, ColumnSchema, SchemaRef, COLUMN_FULLTEXT_OPT_KEY_ANALYZER, - COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE, COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY, - COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE, COMMENT_KEY, + COLUMN_FULLTEXT_OPT_KEY_BACKEND, COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE, + COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY, COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE, COMMENT_KEY, }; use snafu::ResultExt; use sql::ast::{ColumnDef, ColumnOption, ColumnOptionDef, Expr, Ident, ObjectName}; @@ -113,6 +113,10 @@ fn create_column(column_schema: &ColumnSchema, quote_style: char) -> Result Vec { pub fn new_query_engine_with_table(table: TableRef) -> QueryEngineRef { let catalog_manager = MemoryCatalogManager::new_with_table(table); - QueryEngineFactory::new(catalog_manager, None, None, None, None, false).query_engine() + QueryEngineFactory::new( + catalog_manager, + None, + None, + None, + None, + false, + QueryOptions::default(), + ) + .query_engine() } diff --git a/src/query/src/tests/query_engine_test.rs b/src/query/src/tests/query_engine_test.rs index 0f3f817703..07bac1363a 100644 --- a/src/query/src/tests/query_engine_test.rs +++ b/src/query/src/tests/query_engine_test.rs @@ -33,6 +33,7 @@ use table::table::numbers::{NumbersTable, NUMBERS_TABLE_NAME}; use table::test_util::MemTable; use crate::error::{QueryExecutionSnafu, Result}; +use crate::options::QueryOptions as QueryOptionsNew; use crate::parser::QueryLanguageParser; use crate::query_engine::options::QueryOptions; use crate::query_engine::QueryEngineFactory; @@ -43,7 +44,15 @@ async fn test_datafusion_query_engine() -> Result<()> { let catalog_list = catalog::memory::new_memory_catalog_manager() .map_err(BoxedError::new) .context(QueryExecutionSnafu)?; - let factory = QueryEngineFactory::new(catalog_list, None, None, None, None, false); + let factory = QueryEngineFactory::new( + catalog_list, + None, + None, + None, + None, + false, + QueryOptionsNew::default(), + ); let engine = factory.query_engine(); let column_schemas = vec![ColumnSchema::new( @@ -122,8 +131,16 @@ async fn test_query_validate() -> Result<()> { disallow_cross_catalog_query: true, }); - let factory = - QueryEngineFactory::new_with_plugins(catalog_list, None, None, None, None, false, plugins); + let factory = QueryEngineFactory::new_with_plugins( + catalog_list, + None, + None, + None, + None, + false, + plugins, + QueryOptionsNew::default(), + ); let engine = factory.query_engine(); let stmt = diff --git a/src/query/src/tests/time_range_filter_test.rs b/src/query/src/tests/time_range_filter_test.rs index e141c99fa5..84bdd8cb18 100644 --- a/src/query/src/tests/time_range_filter_test.rs +++ b/src/query/src/tests/time_range_filter_test.rs @@ -33,6 +33,7 @@ use table::predicate::build_time_range_predicate; use table::test_util::MemTable; use table::{Table, TableRef}; +use crate::options::QueryOptions; use crate::tests::exec_selection; use crate::{QueryEngineFactory, QueryEngineRef}; @@ -102,8 +103,16 @@ fn create_test_engine() -> TimeRangeTester { }; let _ = catalog_manager.register_table_sync(req).unwrap(); - let engine = - QueryEngineFactory::new(catalog_manager, None, None, None, None, false).query_engine(); + let engine = QueryEngineFactory::new( + catalog_manager, + None, + None, + None, + None, + false, + QueryOptions::default(), + ) + .query_engine(); TimeRangeTester { engine, filter } } diff --git a/src/query/src/window_sort.rs b/src/query/src/window_sort.rs index 57309f3c2a..2e367c5ea0 100644 --- a/src/query/src/window_sort.rs +++ b/src/query/src/window_sort.rs @@ -3156,7 +3156,8 @@ mod test { let fetch_bound = 100; let mut rng = fastrand::Rng::new(); - rng.seed(1337); + let rng_seed = rng.u64(..); + rng.seed(rng_seed); let mut bound_val = None; // construct testcases type CmpFn = Box std::cmp::Ordering>; @@ -3299,8 +3300,8 @@ mod test { } assert_eq!( res_concat, expected_concat, - "case failed, case id: {}", - case_id + "case failed, case id: {}, rng seed: {}", + case_id, rng_seed ); } } diff --git a/src/servers/Cargo.toml b/src/servers/Cargo.toml index 2ad288c1f2..b29ff0bd40 100644 --- a/src/servers/Cargo.toml +++ b/src/servers/Cargo.toml @@ -85,6 +85,7 @@ socket2 = "0.5" # 2. Use ring, instead of aws-lc-rs in https://github.com/databendlabs/opensrv/pull/72 opensrv-mysql = { git = "https://github.com/datafuselabs/opensrv", rev = "a1fb4da215c8693c7e4f62be249a01b7fec52997" } opentelemetry-proto.workspace = true +otel-arrow-rust.workspace = true parking_lot.workspace = true pgwire = { version = "0.28.0", default-features = false, features = ["server-api-ring"] } pin-project = "1.0" diff --git a/src/servers/dashboard/VERSION b/src/servers/dashboard/VERSION index b19b521185..f979adec64 100644 --- a/src/servers/dashboard/VERSION +++ b/src/servers/dashboard/VERSION @@ -1 +1 @@ -v0.8.0 +v0.9.0 diff --git a/src/servers/src/elasticsearch.rs b/src/servers/src/elasticsearch.rs index ba0e97acbf..23d7f1b2dc 100644 --- a/src/servers/src/elasticsearch.rs +++ b/src/servers/src/elasticsearch.rs @@ -33,7 +33,9 @@ use crate::error::{ status_code_to_http_status, InvalidElasticsearchInputSnafu, ParseJsonSnafu, PipelineSnafu, Result as ServersResult, }; -use crate::http::event::{ingest_logs_inner, LogIngestRequest, LogIngesterQueryParams, LogState}; +use crate::http::event::{ + ingest_logs_inner, LogIngesterQueryParams, LogState, PipelineIngestRequest, +}; use crate::metrics::{ METRIC_ELASTICSEARCH_LOGS_DOCS_COUNT, METRIC_ELASTICSEARCH_LOGS_INGESTION_ELAPSED, }; @@ -276,7 +278,7 @@ fn parse_bulk_request( input: &str, index_from_url: &Option, msg_field: &Option, -) -> ServersResult> { +) -> ServersResult> { // Read the ndjson payload and convert it to `Vec`. Return error if the input is not a valid JSON. let values: Vec = Deserializer::from_str(input) .into_iter::() @@ -291,7 +293,7 @@ fn parse_bulk_request( } ); - let mut requests: Vec = Vec::with_capacity(values.len() / 2); + let mut requests: Vec = Vec::with_capacity(values.len() / 2); let mut values = values.into_iter(); // Read the ndjson payload and convert it to a (index, value) vector. @@ -331,7 +333,7 @@ fn parse_bulk_request( ); let log_value = pipeline::json_to_map(log_value).context(PipelineSnafu)?; - requests.push(LogIngestRequest { + requests.push(PipelineIngestRequest { table: index.unwrap_or_else(|| index_from_url.as_ref().unwrap().clone()), values: vec![log_value], }); @@ -402,13 +404,13 @@ mod tests { None, None, Ok(vec![ - LogIngestRequest { + PipelineIngestRequest { table: "test".to_string(), values: vec![ pipeline::json_to_map(json!({"foo1": "foo1_value", "bar1": "bar1_value"})).unwrap(), ], }, - LogIngestRequest { + PipelineIngestRequest { table: "test".to_string(), values: vec![pipeline::json_to_map(json!({"foo2": "foo2_value", "bar2": "bar2_value"})).unwrap()], }, @@ -425,11 +427,11 @@ mod tests { Some("logs".to_string()), None, Ok(vec![ - LogIngestRequest { + PipelineIngestRequest { table: "test".to_string(), values: vec![pipeline::json_to_map(json!({"foo1": "foo1_value", "bar1": "bar1_value"})).unwrap()], }, - LogIngestRequest { + PipelineIngestRequest { table: "logs".to_string(), values: vec![pipeline::json_to_map(json!({"foo2": "foo2_value", "bar2": "bar2_value"})).unwrap()], }, @@ -446,11 +448,11 @@ mod tests { Some("logs".to_string()), None, Ok(vec![ - LogIngestRequest { + PipelineIngestRequest { table: "test".to_string(), values: vec![pipeline::json_to_map(json!({"foo1": "foo1_value", "bar1": "bar1_value"})).unwrap()], }, - LogIngestRequest { + PipelineIngestRequest { table: "logs".to_string(), values: vec![pipeline::json_to_map(json!({"foo2": "foo2_value", "bar2": "bar2_value"})).unwrap()], }, @@ -466,7 +468,7 @@ mod tests { Some("logs".to_string()), None, Ok(vec![ - LogIngestRequest { + PipelineIngestRequest { table: "test".to_string(), values: vec![pipeline::json_to_map(json!({"foo1": "foo1_value", "bar1": "bar1_value"})).unwrap()], }, @@ -483,11 +485,11 @@ mod tests { None, Some("data".to_string()), Ok(vec![ - LogIngestRequest { + PipelineIngestRequest { table: "test".to_string(), values: vec![pipeline::json_to_map(json!({"foo1": "foo1_value", "bar1": "bar1_value"})).unwrap()], }, - LogIngestRequest { + PipelineIngestRequest { table: "test".to_string(), values: vec![pipeline::json_to_map(json!({"foo2": "foo2_value", "bar2": "bar2_value"})).unwrap()], }, @@ -504,13 +506,13 @@ mod tests { None, Some("message".to_string()), Ok(vec![ - LogIngestRequest { + PipelineIngestRequest { table: "logs-generic-default".to_string(), values: vec![ pipeline::json_to_map(json!({"message": "172.16.0.1 - - [25/May/2024:20:19:37 +0000] \"GET /contact HTTP/1.1\" 404 162 \"-\" \"Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1\""})).unwrap(), ], }, - LogIngestRequest { + PipelineIngestRequest { table: "logs-generic-default".to_string(), values: vec![ pipeline::json_to_map(json!({"message": "10.0.0.1 - - [25/May/2024:20:18:37 +0000] \"GET /images/logo.png HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0\""})).unwrap(), diff --git a/src/servers/src/error.rs b/src/servers/src/error.rs index dd3516d890..e9ecca366a 100644 --- a/src/servers/src/error.rs +++ b/src/servers/src/error.rs @@ -25,6 +25,7 @@ use common_error::ext::{BoxedError, ErrorExt}; use common_error::status_code::StatusCode; use common_macro::stack_trace_debug; use common_telemetry::{error, warn}; +use common_time::Duration; use datafusion::error::DataFusionError; use datatypes::prelude::ConcreteDataType; use headers::ContentType; @@ -539,12 +540,6 @@ pub enum Error { location: Location, }, - #[snafu(display("Missing query context"))] - MissingQueryContext { - #[snafu(implicit)] - location: Location, - }, - #[snafu(display("Invalid table name"))] InvalidTableName { #[snafu(source)] @@ -615,6 +610,16 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display("Overflow while casting `{:?}` to Interval", val))] + DurationOverflow { val: Duration }, + + #[snafu(display("Failed to handle otel-arrow request, error message: {}", err_msg))] + HandleOtelArrowRequest { + err_msg: String, + #[snafu(implicit)] + location: Location, + }, } pub type Result = std::result::Result; @@ -673,7 +678,6 @@ impl ErrorExt for Error { | TimePrecision { .. } | UrlDecode { .. } | IncompatibleSchema { .. } - | MissingQueryContext { .. } | MysqlValueConversion { .. } | ParseJson { .. } | InvalidLokiLabels { .. } @@ -734,6 +738,10 @@ impl ErrorExt for Error { ConvertSqlValue { source, .. } => source.status_code(), InFlightWriteBytesExceeded { .. } => StatusCode::RateLimited, + + DurationOverflow { .. } => StatusCode::InvalidArguments, + + HandleOtelArrowRequest { .. } => StatusCode::Internal, } } diff --git a/src/servers/src/grpc.rs b/src/servers/src/grpc.rs index 0d7d185d76..dd591d7805 100644 --- a/src/servers/src/grpc.rs +++ b/src/servers/src/grpc.rs @@ -32,11 +32,13 @@ use common_grpc::channel_manager::{ }; use common_telemetry::{error, info, warn}; use futures::FutureExt; +use otel_arrow_rust::opentelemetry::ArrowMetricsServiceServer; use serde::{Deserialize, Serialize}; use snafu::{ensure, OptionExt, ResultExt}; use tokio::net::TcpListener; use tokio::sync::oneshot::{self, Receiver, Sender}; use tokio::sync::Mutex; +use tonic::service::interceptor::InterceptedService; use tonic::service::Routes; use tonic::transport::server::TcpIncoming; use tonic::transport::ServerTlsConfig; @@ -47,6 +49,8 @@ use crate::error::{ AlreadyStartedSnafu, InternalSnafu, Result, StartGrpcSnafu, TcpBindSnafu, TcpIncomingSnafu, }; use crate::metrics::MetricsMiddlewareLayer; +use crate::otel_arrow::{HeaderInterceptor, OtelArrowServiceHandler}; +use crate::query_handler::OpenTelemetryProtocolHandlerRef; use crate::server::Server; use crate::tls::TlsOption; @@ -138,6 +142,15 @@ pub struct GrpcServer { routes: Mutex>, // tls config tls_config: Option, + // Otel arrow service + otel_arrow_service: Mutex< + Option< + InterceptedService< + ArrowMetricsServiceServer>, + HeaderInterceptor, + >, + >, + >, } /// Grpc Server configuration @@ -264,11 +277,16 @@ impl Server for GrpcServer { if let Some(tls_config) = self.tls_config.clone() { builder = builder.tls_config(tls_config).context(StartGrpcSnafu)?; } - let builder = builder + + let mut builder = builder .add_routes(routes) .add_service(self.create_healthcheck_service()) .add_service(self.create_reflection_service()); + if let Some(otel_arrow_service) = self.otel_arrow_service.lock().await.take() { + builder = builder.add_service(otel_arrow_service); + } + let (serve_state_tx, serve_state_rx) = oneshot::channel(); let mut serve_state = self.serve_state.lock().await; *serve_state = Some(serve_state_rx); diff --git a/src/servers/src/grpc/builder.rs b/src/servers/src/grpc/builder.rs index fc6bbba7ec..65d439fada 100644 --- a/src/servers/src/grpc/builder.rs +++ b/src/servers/src/grpc/builder.rs @@ -19,8 +19,11 @@ use arrow_flight::flight_service_server::FlightServiceServer; use auth::UserProviderRef; use common_grpc::error::{Error, InvalidConfigFilePathSnafu, Result}; use common_runtime::Runtime; +use otel_arrow_rust::opentelemetry::ArrowMetricsServiceServer; use snafu::ResultExt; use tokio::sync::Mutex; +use tonic::codec::CompressionEncoding; +use tonic::service::interceptor::InterceptedService; use tonic::service::RoutesBuilder; use tonic::transport::{Identity, ServerTlsConfig}; @@ -30,7 +33,9 @@ use crate::grpc::greptime_handler::GreptimeRequestHandler; use crate::grpc::prom_query_gateway::PrometheusGatewayService; use crate::grpc::region_server::{RegionServerHandlerRef, RegionServerRequestHandler}; use crate::grpc::{GrpcServer, GrpcServerConfig}; +use crate::otel_arrow::{HeaderInterceptor, OtelArrowServiceHandler}; use crate::prometheus_handler::PrometheusHandlerRef; +use crate::query_handler::OpenTelemetryProtocolHandlerRef; use crate::tls::TlsOption; /// Add a gRPC service (`service`) to a `builder`([RoutesBuilder]). @@ -59,6 +64,12 @@ pub struct GrpcServerBuilder { runtime: Runtime, routes_builder: RoutesBuilder, tls_config: Option, + otel_arrow_service: Option< + InterceptedService< + ArrowMetricsServiceServer>, + HeaderInterceptor, + >, + >, } impl GrpcServerBuilder { @@ -68,6 +79,7 @@ impl GrpcServerBuilder { runtime, routes_builder: RoutesBuilder::default(), tls_config: None, + otel_arrow_service: None, } } @@ -113,6 +125,22 @@ impl GrpcServerBuilder { self } + /// Add handler for [OtelArrowService]. + pub fn otel_arrow_handler( + mut self, + handler: OtelArrowServiceHandler, + ) -> Self { + let mut server = ArrowMetricsServiceServer::new(handler); + server = server + .max_decoding_message_size(self.config.max_recv_message_size) + .max_encoding_message_size(self.config.max_send_message_size) + .accept_compressed(CompressionEncoding::Zstd) + .send_compressed(CompressionEncoding::Zstd); + let svc = InterceptedService::new(server, HeaderInterceptor {}); + self.otel_arrow_service = Some(svc); + self + } + /// Add handler for [RegionServer]. pub fn region_server_handler(mut self, region_server_handler: RegionServerHandlerRef) -> Self { let handler = RegionServerRequestHandler::new(region_server_handler, self.runtime.clone()); @@ -152,6 +180,7 @@ impl GrpcServerBuilder { shutdown_tx: Mutex::new(None), serve_state: Mutex::new(None), tls_config: self.tls_config, + otel_arrow_service: Mutex::new(self.otel_arrow_service), } } } diff --git a/src/servers/src/grpc/flight.rs b/src/servers/src/grpc/flight.rs index 76a6cc00ce..648cfff377 100644 --- a/src/servers/src/grpc/flight.rs +++ b/src/servers/src/grpc/flight.rs @@ -16,6 +16,7 @@ mod stream; use std::pin::Pin; use std::sync::Arc; +use std::task::{Context, Poll}; use api::v1::GreptimeRequest; use arrow_flight::flight_service_server::FlightService; @@ -24,21 +25,33 @@ use arrow_flight::{ HandshakeRequest, HandshakeResponse, PollInfo, PutResult, SchemaResult, Ticket, }; use async_trait::async_trait; +use bytes::Bytes; +use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; +use common_catalog::parse_catalog_and_schema_from_db_string; +use common_grpc::flight::do_put::{DoPutMetadata, DoPutResponse}; use common_grpc::flight::{FlightEncoder, FlightMessage}; use common_query::{Output, OutputData}; use common_telemetry::tracing::info_span; use common_telemetry::tracing_context::{FutureExt, TracingContext}; -use futures::Stream; +use futures::{future, ready, Stream}; +use futures_util::{StreamExt, TryStreamExt}; use prost::Message; -use snafu::ResultExt; +use snafu::{ensure, ResultExt}; +use table::table_name::TableName; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; use tonic::{Request, Response, Status, Streaming}; use crate::error; +use crate::error::{InvalidParameterSnafu, ParseJsonSnafu, Result, ToJsonSnafu}; pub use crate::grpc::flight::stream::FlightRecordBatchStream; use crate::grpc::greptime_handler::{get_request_type, GreptimeRequestHandler}; use crate::grpc::TonicResult; +use crate::http::header::constants::GREPTIME_DB_HEADER_NAME; +use crate::http::AUTHORIZATION_HEADER; +use crate::query_handler::grpc::RawRecordBatch; -pub type TonicStream = Pin> + Send + Sync + 'static>>; +pub type TonicStream = Pin> + Send + 'static>>; /// A subset of [FlightService] #[async_trait] @@ -47,6 +60,14 @@ pub trait FlightCraft: Send + Sync + 'static { &self, request: Request, ) -> TonicResult>>; + + async fn do_put( + &self, + request: Request>, + ) -> TonicResult>> { + let _ = request; + Err(Status::unimplemented("Not yet implemented")) + } } pub type FlightCraftRef = Arc; @@ -67,6 +88,13 @@ impl FlightCraft for FlightCraftRef { ) -> TonicResult>> { (**self).do_get(request).await } + + async fn do_put( + &self, + request: Request>, + ) -> TonicResult>> { + self.as_ref().do_put(request).await + } } #[async_trait] @@ -120,9 +148,9 @@ impl FlightService for FlightCraftWrapper { async fn do_put( &self, - _: Request>, + request: Request>, ) -> TonicResult> { - Err(Status::unimplemented("Not yet implemented")) + self.0.do_put(request).await } type DoExchangeStream = TonicStream; @@ -168,13 +196,164 @@ impl FlightCraft for GreptimeRequestHandler { ); async { let output = self.handle_request(request, Default::default()).await?; - let stream: Pin> + Send + Sync>> = - to_flight_data_stream(output, TracingContext::from_current_span()); + let stream = to_flight_data_stream(output, TracingContext::from_current_span()); Ok(Response::new(stream)) } .trace(span) .await } + + async fn do_put( + &self, + request: Request>, + ) -> TonicResult>> { + let (headers, _, stream) = request.into_parts(); + + let header = |key: &str| -> TonicResult> { + let Some(v) = headers.get(key) else { + return Ok(None); + }; + let Ok(v) = std::str::from_utf8(v.as_bytes()) else { + return Err(InvalidParameterSnafu { + reason: "expect valid UTF-8 value", + } + .build() + .into()); + }; + Ok(Some(v)) + }; + + let username_and_password = header(AUTHORIZATION_HEADER)?; + let db = header(GREPTIME_DB_HEADER_NAME)?; + if !self.validate_auth(username_and_password, db).await? { + return Err(Status::unauthenticated("auth failed")); + } + + const MAX_PENDING_RESPONSES: usize = 32; + let (tx, rx) = mpsc::channel::>(MAX_PENDING_RESPONSES); + + let stream = PutRecordBatchRequestStream { + flight_data_stream: stream, + state: PutRecordBatchRequestStreamState::Init(db.map(ToString::to_string)), + }; + self.put_record_batches(stream, tx).await; + + let response = ReceiverStream::new(rx) + .and_then(|response| { + future::ready({ + serde_json::to_vec(&response) + .context(ToJsonSnafu) + .map(|x| PutResult { + app_metadata: Bytes::from(x), + }) + .map_err(Into::into) + }) + }) + .boxed(); + Ok(Response::new(response)) + } +} + +pub(crate) struct PutRecordBatchRequest { + pub(crate) table_name: TableName, + pub(crate) request_id: i64, + pub(crate) record_batch: RawRecordBatch, +} + +impl PutRecordBatchRequest { + fn try_new(table_name: TableName, flight_data: FlightData) -> Result { + let metadata: DoPutMetadata = + serde_json::from_slice(&flight_data.app_metadata).context(ParseJsonSnafu)?; + Ok(Self { + table_name, + request_id: metadata.request_id(), + record_batch: flight_data.data_body, + }) + } +} + +pub(crate) struct PutRecordBatchRequestStream { + flight_data_stream: Streaming, + state: PutRecordBatchRequestStreamState, +} + +enum PutRecordBatchRequestStreamState { + Init(Option), + Started(TableName), +} + +impl Stream for PutRecordBatchRequestStream { + type Item = TonicResult; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn extract_table_name(mut descriptor: FlightDescriptor) -> Result { + ensure!( + descriptor.r#type == arrow_flight::flight_descriptor::DescriptorType::Path as i32, + InvalidParameterSnafu { + reason: "expect FlightDescriptor::type == 'Path' only", + } + ); + ensure!( + descriptor.path.len() == 1, + InvalidParameterSnafu { + reason: "expect FlightDescriptor::path has only one table name", + } + ); + Ok(descriptor.path.remove(0)) + } + + let poll = ready!(self.flight_data_stream.poll_next_unpin(cx)); + + let result = match &mut self.state { + PutRecordBatchRequestStreamState::Init(db) => match poll { + Some(Ok(mut flight_data)) => { + let flight_descriptor = flight_data.flight_descriptor.take(); + let result = if let Some(descriptor) = flight_descriptor { + let table_name = extract_table_name(descriptor).map(|x| { + let (catalog, schema) = if let Some(db) = db { + parse_catalog_and_schema_from_db_string(db) + } else { + ( + DEFAULT_CATALOG_NAME.to_string(), + DEFAULT_SCHEMA_NAME.to_string(), + ) + }; + TableName::new(catalog, schema, x) + }); + let table_name = match table_name { + Ok(table_name) => table_name, + Err(e) => return Poll::Ready(Some(Err(e.into()))), + }; + + let request = + PutRecordBatchRequest::try_new(table_name.clone(), flight_data); + let request = match request { + Ok(request) => request, + Err(e) => return Poll::Ready(Some(Err(e.into()))), + }; + + self.state = PutRecordBatchRequestStreamState::Started(table_name); + + Ok(request) + } else { + Err(Status::failed_precondition( + "table to put is not found in flight descriptor", + )) + }; + Some(result) + } + Some(Err(e)) => Some(Err(e)), + None => None, + }, + PutRecordBatchRequestStreamState::Started(table_name) => poll.map(|x| { + x.and_then(|flight_data| { + PutRecordBatchRequest::try_new(table_name.clone(), flight_data) + .map_err(Into::into) + }) + }), + }; + Poll::Ready(result) + } } fn to_flight_data_stream( diff --git a/src/servers/src/grpc/greptime_handler.rs b/src/servers/src/grpc/greptime_handler.rs index b032ffc847..73e1a768c8 100644 --- a/src/servers/src/grpc/greptime_handler.rs +++ b/src/servers/src/grpc/greptime_handler.rs @@ -18,23 +18,33 @@ use std::time::Instant; use api::helper::request_type; use api::v1::auth_header::AuthScheme; -use api::v1::{Basic, GreptimeRequest, RequestHeader}; +use api::v1::{AuthHeader, Basic, GreptimeRequest, RequestHeader}; use auth::{Identity, Password, UserInfoRef, UserProviderRef}; +use base64::prelude::BASE64_STANDARD; +use base64::Engine; use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; use common_catalog::parse_catalog_and_schema_from_db_string; use common_error::ext::ErrorExt; use common_error::status_code::StatusCode; +use common_grpc::flight::do_put::DoPutResponse; use common_query::Output; use common_runtime::runtime::RuntimeTrait; use common_runtime::Runtime; use common_telemetry::tracing_context::{FutureExt, TracingContext}; -use common_telemetry::{debug, error, tracing}; +use common_telemetry::{debug, error, tracing, warn}; use common_time::timezone::parse_timezone; -use session::context::{QueryContextBuilder, QueryContextRef}; +use futures_util::StreamExt; +use session::context::{QueryContext, QueryContextBuilder, QueryContextRef}; use snafu::{OptionExt, ResultExt}; +use tokio::sync::mpsc; use crate::error::Error::UnsupportedAuthScheme; -use crate::error::{AuthSnafu, InvalidQuerySnafu, JoinTaskSnafu, NotFoundAuthHeaderSnafu, Result}; +use crate::error::{ + AuthSnafu, InvalidAuthHeaderInvalidUtf8ValueSnafu, InvalidBase64ValueSnafu, InvalidQuerySnafu, + JoinTaskSnafu, NotFoundAuthHeaderSnafu, Result, +}; +use crate::grpc::flight::{PutRecordBatchRequest, PutRecordBatchRequestStream}; +use crate::grpc::TonicResult; use crate::metrics::{METRIC_AUTH_FAILURE, METRIC_SERVER_GRPC_DB_REQUEST_TIMER}; use crate::query_handler::grpc::ServerGrpcQueryHandlerRef; @@ -118,6 +128,95 @@ impl GreptimeRequestHandler { None => result_future.await, } } + + pub(crate) async fn put_record_batches( + &self, + mut stream: PutRecordBatchRequestStream, + result_sender: mpsc::Sender>, + ) { + let handler = self.handler.clone(); + let runtime = self + .runtime + .clone() + .unwrap_or_else(common_runtime::global_runtime); + runtime.spawn(async move { + while let Some(request) = stream.next().await { + let request = match request { + Ok(request) => request, + Err(e) => { + let _ = result_sender.try_send(Err(e)); + break; + } + }; + + let PutRecordBatchRequest { + table_name, + request_id, + record_batch, + } = request; + let result = handler.put_record_batch(&table_name, record_batch).await; + let result = result + .map(|x| DoPutResponse::new(request_id, x)) + .map_err(Into::into); + if result_sender.try_send(result).is_err() { + warn!(r#""DoPut" client maybe unreachable, abort handling its message"#); + break; + } + } + }); + } + + pub(crate) async fn validate_auth( + &self, + username_and_password: Option<&str>, + db: Option<&str>, + ) -> Result { + if self.user_provider.is_none() { + return Ok(true); + } + + let username_and_password = username_and_password.context(NotFoundAuthHeaderSnafu)?; + let username_and_password = BASE64_STANDARD + .decode(username_and_password) + .context(InvalidBase64ValueSnafu) + .and_then(|x| String::from_utf8(x).context(InvalidAuthHeaderInvalidUtf8ValueSnafu))?; + + let mut split = username_and_password.splitn(2, ':'); + let (username, password) = match (split.next(), split.next()) { + (Some(username), Some(password)) => (username, password), + (Some(username), None) => (username, ""), + (None, None) => return Ok(false), + _ => unreachable!(), // because this iterator won't yield Some after None + }; + + let (catalog, schema) = if let Some(db) = db { + parse_catalog_and_schema_from_db_string(db) + } else { + ( + DEFAULT_CATALOG_NAME.to_string(), + DEFAULT_SCHEMA_NAME.to_string(), + ) + }; + let header = RequestHeader { + authorization: Some(AuthHeader { + auth_scheme: Some(AuthScheme::Basic(Basic { + username: username.to_string(), + password: password.to_string(), + })), + }), + catalog, + schema, + ..Default::default() + }; + + Ok(auth( + self.user_provider.clone(), + Some(&header), + &QueryContext::arc(), + ) + .await + .is_ok()) + } } pub fn get_request_type(request: &GreptimeRequest) -> &'static str { diff --git a/src/servers/src/grpc/otlp.rs b/src/servers/src/grpc/otlp.rs deleted file mode 100644 index f3f71900eb..0000000000 --- a/src/servers/src/grpc/otlp.rs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2023 Greptime Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::result::Result as StdResult; -use std::sync::Arc; - -use opentelemetry_proto::tonic::collector::metrics::v1::metrics_service_server::MetricsService; -use opentelemetry_proto::tonic::collector::metrics::v1::{ - ExportMetricsServiceRequest, ExportMetricsServiceResponse, -}; -use opentelemetry_proto::tonic::collector::trace::v1::trace_service_server::TraceService; -use opentelemetry_proto::tonic::collector::trace::v1::{ - ExportTraceServiceRequest, ExportTraceServiceResponse, -}; -use session::context::{Channel, QueryContext}; -use snafu::{OptionExt, ResultExt}; -use tonic::{Request, Response, Status}; - -use crate::error; -use crate::http::header::constants::GREPTIME_TRACE_TABLE_NAME_HEADER_NAME; -use crate::otlp::trace::TRACE_TABLE_NAME; -use crate::query_handler::OpenTelemetryProtocolHandlerRef; - -pub struct OtlpService { - handler: OpenTelemetryProtocolHandlerRef, -} - -impl OtlpService { - pub fn new(handler: OpenTelemetryProtocolHandlerRef) -> Self { - Self { handler } - } -} - -#[async_trait::async_trait] -impl TraceService for OtlpService { - async fn export( - &self, - request: Request, - ) -> StdResult, Status> { - let (headers, extensions, req) = request.into_parts(); - - let table_name = match headers.get(GREPTIME_TRACE_TABLE_NAME_HEADER_NAME) { - Some(table_name) => table_name - .to_str() - .context(error::InvalidTableNameSnafu)? - .to_string(), - None => TRACE_TABLE_NAME.to_string(), - }; - - let mut ctx = extensions - .get::() - .cloned() - .context(error::MissingQueryContextSnafu)?; - ctx.set_channel(Channel::Otlp); - let ctx = Arc::new(ctx); - - let _ = self.handler.traces(req, table_name, ctx).await?; - - Ok(Response::new(ExportTraceServiceResponse { - partial_success: None, - })) - } -} - -#[async_trait::async_trait] -impl MetricsService for OtlpService { - async fn export( - &self, - request: Request, - ) -> StdResult, Status> { - let (_headers, extensions, req) = request.into_parts(); - - let mut ctx = extensions - .get::() - .cloned() - .context(error::MissingQueryContextSnafu)?; - ctx.set_channel(Channel::Otlp); - let ctx = Arc::new(ctx); - - let _ = self.handler.metrics(req, ctx).await?; - - Ok(Response::new(ExportMetricsServiceResponse { - partial_success: None, - })) - } -} diff --git a/src/servers/src/http.rs b/src/servers/src/http.rs index 07f589f127..bff38d980f 100644 --- a/src/servers/src/http.rs +++ b/src/servers/src/http.rs @@ -928,6 +928,10 @@ impl HttpServer { fn route_log_deprecated(log_state: LogState) -> Router { Router::new() .route("/logs", routing::post(event::log_ingester)) + .route( + "/pipelines/{pipeline_name}", + routing::get(event::query_pipeline), + ) .route( "/pipelines/{pipeline_name}", routing::post(event::add_pipeline), @@ -947,6 +951,10 @@ impl HttpServer { fn route_pipelines(log_state: LogState) -> Router { Router::new() .route("/ingest", routing::post(event::log_ingester)) + .route( + "/pipelines/{pipeline_name}", + routing::get(event::query_pipeline), + ) .route( "/pipelines/{pipeline_name}", routing::post(event::add_pipeline), @@ -1161,7 +1169,6 @@ mod test { use std::io::Cursor; use std::sync::Arc; - use api::v1::greptime_request::Request; use arrow_ipc::reader::FileReader; use arrow_schema::DataType; use axum::handler::Handler; @@ -1183,26 +1190,12 @@ mod test { use super::*; use crate::error::Error; use crate::http::test_helpers::TestClient; - use crate::query_handler::grpc::GrpcQueryHandler; use crate::query_handler::sql::{ServerSqlQueryHandlerAdapter, SqlQueryHandler}; struct DummyInstance { _tx: mpsc::Sender<(String, Vec)>, } - #[async_trait] - impl GrpcQueryHandler for DummyInstance { - type Error = Error; - - async fn do_query( - &self, - _query: Request, - _ctx: QueryContextRef, - ) -> std::result::Result { - unimplemented!() - } - } - #[async_trait] impl SqlQueryHandler for DummyInstance { type Error = Error; diff --git a/src/servers/src/http/event.rs b/src/servers/src/http/event.rs index bee41b8053..fba3e33582 100644 --- a/src/servers/src/http/event.rs +++ b/src/servers/src/http/event.rs @@ -33,7 +33,7 @@ use datatypes::value::column_data_to_json; use headers::ContentType; use lazy_static::lazy_static; use pipeline::util::to_pipeline_version; -use pipeline::{GreptimePipelineParams, PipelineDefinition, PipelineMap}; +use pipeline::{GreptimePipelineParams, PipelineContext, PipelineDefinition, PipelineMap}; use serde::{Deserialize, Serialize}; use serde_json::{json, Deserializer, Map, Value}; use session::context::{Channel, QueryContext, QueryContextRef}; @@ -100,7 +100,7 @@ pub struct LogIngesterQueryParams { /// LogIngestRequest is the internal request for log ingestion. The raw log input can be transformed into multiple LogIngestRequests. /// Multiple LogIngestRequests will be ingested into the same database with the same pipeline. #[derive(Debug, PartialEq)] -pub(crate) struct LogIngestRequest { +pub(crate) struct PipelineIngestRequest { /// The table where the log data will be written to. pub table: String, /// The log data to be ingested. @@ -147,6 +147,41 @@ where } } +#[axum_macros::debug_handler] +pub async fn query_pipeline( + State(state): State, + Extension(mut query_ctx): Extension, + Query(query_params): Query, + Path(pipeline_name): Path, +) -> Result { + let start = Instant::now(); + let handler = state.log_handler; + ensure!( + !pipeline_name.is_empty(), + InvalidParameterSnafu { + reason: "pipeline_name is required in path", + } + ); + + let version = to_pipeline_version(query_params.version.as_deref()).context(PipelineSnafu)?; + + query_ctx.set_channel(Channel::Http); + let query_ctx = Arc::new(query_ctx); + + let (pipeline, pipeline_version) = handler + .get_pipeline_str(&pipeline_name, version, query_ctx) + .await?; + + Ok(GreptimedbManageResponse::from_pipeline( + pipeline_name, + query_params + .version + .unwrap_or(pipeline_version.0.to_iso8601_string()), + start.elapsed().as_millis() as u64, + Some(pipeline), + )) +} + #[axum_macros::debug_handler] pub async fn add_pipeline( State(state): State, @@ -189,6 +224,7 @@ pub async fn add_pipeline( pipeline_name, pipeline.0.to_timezone_aware_string(None), start.elapsed().as_millis() as u64, + None, ) }) .map_err(|e| { @@ -231,6 +267,7 @@ pub async fn delete_pipeline( pipeline_name, version_str, start.elapsed().as_millis() as u64, + None, ) } else { GreptimedbManageResponse::from_pipelines(vec![], start.elapsed().as_millis() as u64) @@ -288,12 +325,15 @@ async fn dryrun_pipeline_inner( ) -> Result { let params = GreptimePipelineParams::default(); + let pipeline_def = PipelineDefinition::Resolved(pipeline); + let pipeline_ctx = PipelineContext::new(&pipeline_def, ¶ms); let results = run_pipeline( &pipeline_handler, - &PipelineDefinition::Resolved(pipeline), - ¶ms, - value, - "dry_run".to_owned(), + &pipeline_ctx, + PipelineIngestRequest { + table: "dry_run".to_owned(), + values: value, + }, query_ctx, true, ) @@ -566,7 +606,7 @@ pub async fn log_ingester( ingest_logs_inner( handler, pipeline, - vec![LogIngestRequest { + vec![PipelineIngestRequest { table: table_name, values: value, }], @@ -636,9 +676,9 @@ fn extract_pipeline_value_by_content_type( } pub(crate) async fn ingest_logs_inner( - state: PipelineHandlerRef, + handler: PipelineHandlerRef, pipeline: PipelineDefinition, - log_ingest_requests: Vec, + log_ingest_requests: Vec, query_ctx: QueryContextRef, headers: HeaderMap, ) -> Result { @@ -653,22 +693,15 @@ pub(crate) async fn ingest_logs_inner( .and_then(|v| v.to_str().ok()), ); - for request in log_ingest_requests { - let requests = run_pipeline( - &state, - &pipeline, - &pipeline_params, - request.values, - request.table, - &query_ctx, - true, - ) - .await?; + let pipeline_ctx = PipelineContext::new(&pipeline, &pipeline_params); + for pipeline_req in log_ingest_requests { + let requests = + run_pipeline(&handler, &pipeline_ctx, pipeline_req, &query_ctx, true).await?; insert_requests.extend(requests); } - let output = state + let output = handler .insert( RowInsertRequests { inserts: insert_requests, diff --git a/src/servers/src/http/handler.rs b/src/servers/src/http/handler.rs index fe4d1aa034..b21af27f10 100644 --- a/src/servers/src/http/handler.rs +++ b/src/servers/src/http/handler.rs @@ -251,6 +251,23 @@ pub struct PromqlQuery { pub step: String, pub lookback: Option, pub db: Option, + // (Optional) result format: [`greptimedb_v1`, `influxdb_v1`, `csv`, + // `arrow`], + // the default value is `greptimedb_v1` + pub format: Option, + // For arrow output + pub compression: Option, + // Returns epoch timestamps with the specified precision. + // Both u and µ indicate microseconds. + // epoch = [ns,u,µ,ms,s], + // + // For influx output only + // + // TODO(jeremy): currently, only InfluxDB result format is supported, + // and all columns of the `Timestamp` type will be converted to their + // specified time precision. Maybe greptimedb format can support this + // param too. + pub epoch: Option, } impl From for PromQuery { @@ -292,9 +309,30 @@ pub async fn promql( let resp = ErrorResponse::from_error_message(status, msg); HttpResponse::Error(resp) } else { + let format = params + .format + .as_ref() + .map(|s| s.to_lowercase()) + .map(|s| ResponseFormat::parse(s.as_str()).unwrap_or(ResponseFormat::GreptimedbV1)) + .unwrap_or(ResponseFormat::GreptimedbV1); + let epoch = params + .epoch + .as_ref() + .map(|s| s.to_lowercase()) + .map(|s| Epoch::parse(s.as_str()).unwrap_or(Epoch::Millisecond)); + let compression = params.compression.clone(); + let prom_query = params.into(); let outputs = sql_handler.do_promql_query(&prom_query, query_ctx).await; - GreptimedbV1Response::from_output(outputs).await + + match format { + ResponseFormat::Arrow => ArrowResponse::from_output(outputs, compression).await, + ResponseFormat::Csv => CsvResponse::from_output(outputs).await, + ResponseFormat::Table => TableResponse::from_output(outputs).await, + ResponseFormat::GreptimedbV1 => GreptimedbV1Response::from_output(outputs).await, + ResponseFormat::InfluxdbV1 => InfluxdbV1Response::from_output(outputs, epoch).await, + ResponseFormat::Json => JsonResponse::from_output(outputs).await, + } }; resp.with_execution_time(exec_start.elapsed().as_millis() as u64) diff --git a/src/servers/src/http/loki.rs b/src/servers/src/http/loki.rs index ac7afe6d45..f010b10c5d 100644 --- a/src/servers/src/http/loki.rs +++ b/src/servers/src/http/loki.rs @@ -345,7 +345,7 @@ pub fn parse_loki_labels(labels: &str) -> Result> { while !labels.is_empty() { // parse key - let first_index = labels.find("=").context(InvalidLokiLabelsSnafu { + let first_index = labels.find("=").with_context(|| InvalidLokiLabelsSnafu { msg: format!("missing `=` near: {}", labels), })?; let key = &labels[..first_index]; diff --git a/src/servers/src/http/prom_store.rs b/src/servers/src/http/prom_store.rs index caafd0f1f3..8c849bf735 100644 --- a/src/servers/src/http/prom_store.rs +++ b/src/servers/src/http/prom_store.rs @@ -83,33 +83,17 @@ impl Default for RemoteWriteQuery { )] pub async fn remote_write( State(state): State, - query: Query, - extension: Extension, - content_encoding: TypedHeader, - raw_body: Bytes, -) -> Result { - remote_write_impl( - state.prom_store_handler, - query, - extension, - content_encoding, - raw_body, - state.is_strict_mode, - state.prom_store_with_metric_engine, - ) - .await -} - -async fn remote_write_impl( - handler: PromStoreProtocolHandlerRef, Query(params): Query, Extension(mut query_ctx): Extension, content_encoding: TypedHeader, body: Bytes, - is_strict_mode: bool, - is_metric_engine: bool, ) -> Result { - // VictoriaMetrics handshake + let PromStoreState { + prom_store_handler, + prom_store_with_metric_engine, + is_strict_mode, + } = state; + if let Some(_vm_handshake) = params.get_vm_proto_version { return Ok(VM_PROTO_VERSION.into_response()); } @@ -128,7 +112,9 @@ async fn remote_write_impl( } let query_ctx = Arc::new(query_ctx); - let output = handler.write(request, query_ctx, is_metric_engine).await?; + let output = prom_store_handler + .write(request, query_ctx, prom_store_with_metric_engine) + .await?; crate::metrics::PROM_STORE_REMOTE_WRITE_SAMPLES.inc_by(samples as u64); Ok(( StatusCode::NO_CONTENT, diff --git a/src/servers/src/http/result/greptime_manage_resp.rs b/src/servers/src/http/result/greptime_manage_resp.rs index a46df298b4..3db07028b3 100644 --- a/src/servers/src/http/result/greptime_manage_resp.rs +++ b/src/servers/src/http/result/greptime_manage_resp.rs @@ -30,10 +30,19 @@ pub struct GreptimedbManageResponse { } impl GreptimedbManageResponse { - pub fn from_pipeline(name: String, version: String, execution_time_ms: u64) -> Self { + pub fn from_pipeline( + name: String, + version: String, + execution_time_ms: u64, + pipeline: Option, + ) -> Self { GreptimedbManageResponse { manage_result: ManageResult::Pipelines { - pipelines: vec![PipelineOutput { name, version }], + pipelines: vec![PipelineOutput { + name, + version, + pipeline, + }], }, execution_time_ms, } @@ -68,6 +77,8 @@ pub enum ManageResult { pub struct PipelineOutput { name: String, version: String, + #[serde(skip_serializing_if = "Option::is_none")] + pipeline: Option, } impl IntoResponse for GreptimedbManageResponse { @@ -109,6 +120,7 @@ mod tests { pipelines: vec![PipelineOutput { name: "test_name".to_string(), version: "test_version".to_string(), + pipeline: None, }], }, execution_time_ms: 42, diff --git a/src/servers/src/http/timeout.rs b/src/servers/src/http/timeout.rs index 050ec492e0..adfc29cd95 100644 --- a/src/servers/src/http/timeout.rs +++ b/src/servers/src/http/timeout.rs @@ -117,7 +117,7 @@ where fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { match self.inner.poll_ready(cx) { Poll::Pending => Poll::Pending, - Poll::Ready(r) => Poll::Ready(r.map_err(Into::into)), + Poll::Ready(r) => Poll::Ready(r), } } diff --git a/src/servers/src/lib.rs b/src/servers/src/lib.rs index 61bf041f52..f55bc76e17 100644 --- a/src/servers/src/lib.rs +++ b/src/servers/src/lib.rs @@ -17,7 +17,6 @@ #![feature(exclusive_wrapper)] #![feature(let_chains)] #![feature(if_let_guard)] -#![feature(trait_upcasting)] use datafusion_expr::LogicalPlan; use datatypes::schema::Schema; @@ -37,6 +36,7 @@ mod metrics; pub mod metrics_handler; pub mod mysql; pub mod opentsdb; +pub mod otel_arrow; pub mod otlp; mod pipeline; pub mod postgres; diff --git a/src/servers/src/otel_arrow.rs b/src/servers/src/otel_arrow.rs new file mode 100644 index 0000000000..f279c7f7b8 --- /dev/null +++ b/src/servers/src/otel_arrow.rs @@ -0,0 +1,119 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use common_error::ext::ErrorExt; +use common_error::status_code::status_to_tonic_code; +use common_telemetry::error; +use futures::SinkExt; +use otel_arrow_rust::opentelemetry::{ArrowMetricsService, BatchArrowRecords, BatchStatus}; +use otel_arrow_rust::Consumer; +use session::context::QueryContext; +use tonic::metadata::{Entry, MetadataValue}; +use tonic::service::Interceptor; +use tonic::{Request, Response, Status, Streaming}; + +use crate::error; +use crate::query_handler::OpenTelemetryProtocolHandlerRef; + +pub struct OtelArrowServiceHandler(pub T); + +impl OtelArrowServiceHandler { + pub fn new(handler: T) -> Self { + Self(handler) + } +} + +#[async_trait::async_trait] +impl ArrowMetricsService for OtelArrowServiceHandler { + type ArrowMetricsStream = futures::channel::mpsc::Receiver>; + async fn arrow_metrics( + &self, + request: Request>, + ) -> Result, Status> { + let (mut sender, receiver) = futures::channel::mpsc::channel(100); + let mut incoming_requests = request.into_inner(); + let handler = self.0.clone(); + let query_context = QueryContext::arc(); + // handles incoming requests + common_runtime::spawn_global(async move { + let mut consumer = Consumer::default(); + while let Some(batch_res) = incoming_requests.message().await.transpose() { + let mut batch = match batch_res { + Ok(batch) => batch, + Err(e) => { + error!( + "Failed to receive batch from otel-arrow client, error: {}", + e + ); + let _ = sender.send(Err(e)).await; + return; + } + }; + let batch_status = BatchStatus { + batch_id: batch.batch_id, + status_code: 0, + status_message: Default::default(), + }; + let request = match consumer.consume_batches(&mut batch).map_err(|e| { + error::HandleOtelArrowRequestSnafu { + err_msg: e.to_string(), + } + .build() + }) { + Ok(request) => request, + Err(e) => { + let _ = sender + .send(Err(Status::new( + status_to_tonic_code(e.status_code()), + e.to_string(), + ))) + .await; + error!(e; + "Failed to consume batch from otel-arrow client" + ); + return; + } + }; + if let Err(e) = handler.metrics(request, query_context.clone()).await { + let _ = sender + .send(Err(Status::new( + status_to_tonic_code(e.status_code()), + e.to_string(), + ))) + .await; + error!(e; "Failed to ingest metrics from otel-arrow"); + return; + } + let _ = sender.send(Ok(batch_status)).await; + } + }); + Ok(Response::new(receiver)) + } +} + +/// This serves as a workaround for otel-arrow collector's custom header. +#[derive(Clone)] +pub struct HeaderInterceptor; + +impl Interceptor for HeaderInterceptor { + fn call(&mut self, mut request: Request<()>) -> Result, Status> { + if let Ok(Entry::Occupied(mut e)) = request.metadata_mut().entry("grpc-encoding") { + // This works as a workaround to handle customized compression type (zstdarrow*) in otel-arrow. + if e.get().as_bytes().starts_with(b"zstdarrow") { + e.insert(MetadataValue::from_static("zstd")); + } + } + Ok(request) + } +} diff --git a/src/servers/src/otlp/logs.rs b/src/servers/src/otlp/logs.rs index 95b3b21e99..cb6a3d89cf 100644 --- a/src/servers/src/otlp/logs.rs +++ b/src/servers/src/otlp/logs.rs @@ -24,7 +24,7 @@ use jsonb::{Number as JsonbNumber, Value as JsonbValue}; use opentelemetry_proto::tonic::collector::logs::v1::ExportLogsServiceRequest; use opentelemetry_proto::tonic::common::v1::{any_value, AnyValue, InstrumentationScope, KeyValue}; use opentelemetry_proto::tonic::logs::v1::{LogRecord, ResourceLogs, ScopeLogs}; -use pipeline::{GreptimePipelineParams, PipelineWay, SchemaInfo, SelectInfo}; +use pipeline::{GreptimePipelineParams, PipelineContext, PipelineWay, SchemaInfo, SelectInfo}; use serde_json::{Map, Value}; use session::context::QueryContextRef; use snafu::{ensure, ResultExt}; @@ -33,6 +33,7 @@ use crate::error::{ IncompatibleSchemaSnafu, NotSupportedSnafu, PipelineSnafu, Result, UnsupportedJsonDataTypeForTagSnafu, }; +use crate::http::event::PipelineIngestRequest; use crate::otlp::trace::attributes::OtlpAnyValue; use crate::otlp::utils::{bytes_to_hex_string, key_value_to_jsonb}; use crate::pipeline::run_pipeline; @@ -74,12 +75,14 @@ pub async fn to_grpc_insert_requests( let data = parse_export_logs_service_request(request); let array = pipeline::json_array_to_map(data).context(PipelineSnafu)?; + let pipeline_ctx = PipelineContext::new(&pipeline_def, &pipeline_params); let inserts = run_pipeline( &pipeline_handler, - &pipeline_def, - &pipeline_params, - array, - table_name, + &pipeline_ctx, + PipelineIngestRequest { + table: table_name, + values: array, + }, query_ctx, true, ) diff --git a/src/servers/src/pipeline.rs b/src/servers/src/pipeline.rs index 8d8538a319..2a7970e2f7 100644 --- a/src/servers/src/pipeline.rs +++ b/src/servers/src/pipeline.rs @@ -18,13 +18,14 @@ use std::sync::Arc; use api::v1::{RowInsertRequest, Rows}; use hashbrown::HashMap; use pipeline::{ - DispatchedTo, GreptimePipelineParams, IdentityTimeIndex, Pipeline, PipelineDefinition, - PipelineExecOutput, PipelineMap, GREPTIME_INTERNAL_IDENTITY_PIPELINE_NAME, + DispatchedTo, GreptimePipelineParams, IdentityTimeIndex, Pipeline, PipelineContext, + PipelineDefinition, PipelineExecOutput, PipelineMap, GREPTIME_INTERNAL_IDENTITY_PIPELINE_NAME, }; use session::context::QueryContextRef; use snafu::ResultExt; use crate::error::{CatalogSnafu, PipelineSnafu, Result}; +use crate::http::event::PipelineIngestRequest; use crate::metrics::{ METRIC_FAILURE_VALUE, METRIC_HTTP_LOGS_TRANSFORM_ELAPSED, METRIC_SUCCESS_VALUE, }; @@ -51,36 +52,24 @@ pub async fn get_pipeline( pub(crate) async fn run_pipeline( handler: &PipelineHandlerRef, - pipeline_definition: &PipelineDefinition, - pipeline_parameters: &GreptimePipelineParams, - data_array: Vec, - table_name: String, + pipeline_ctx: &PipelineContext<'_>, + pipeline_req: PipelineIngestRequest, query_ctx: &QueryContextRef, is_top_level: bool, ) -> Result> { - match pipeline_definition { + match &pipeline_ctx.pipeline_definition { PipelineDefinition::GreptimeIdentityPipeline(custom_ts) => { run_identity_pipeline( handler, custom_ts.as_ref(), - pipeline_parameters, - data_array, - table_name, + pipeline_ctx.pipeline_param, + pipeline_req, query_ctx, ) .await } _ => { - run_custom_pipeline( - handler, - pipeline_definition, - pipeline_parameters, - data_array, - table_name, - query_ctx, - is_top_level, - ) - .await + run_custom_pipeline(handler, pipeline_ctx, pipeline_req, query_ctx, is_top_level).await } } } @@ -89,10 +78,13 @@ async fn run_identity_pipeline( handler: &PipelineHandlerRef, custom_ts: Option<&IdentityTimeIndex>, pipeline_parameters: &GreptimePipelineParams, - data_array: Vec, - table_name: String, + pipeline_req: PipelineIngestRequest, query_ctx: &QueryContextRef, ) -> Result> { + let PipelineIngestRequest { + table: table_name, + values: data_array, + } = pipeline_req; let table = handler .get_table(&table_name, query_ctx) .await @@ -109,18 +101,20 @@ async fn run_identity_pipeline( async fn run_custom_pipeline( handler: &PipelineHandlerRef, - pipeline_definition: &PipelineDefinition, - pipeline_parameters: &GreptimePipelineParams, - data_array: Vec, - table_name: String, + pipeline_ctx: &PipelineContext<'_>, + pipeline_req: PipelineIngestRequest, query_ctx: &QueryContextRef, is_top_level: bool, ) -> Result> { let db = query_ctx.get_db_string(); - let pipeline = get_pipeline(pipeline_definition, handler, query_ctx).await?; + let pipeline = get_pipeline(pipeline_ctx.pipeline_definition, handler, query_ctx).await?; let transform_timer = std::time::Instant::now(); + let PipelineIngestRequest { + table: table_name, + values: data_array, + } = pipeline_req; let arr_len = data_array.len(); let mut req_map = HashMap::new(); let mut dispatched: BTreeMap> = BTreeMap::new(); @@ -185,12 +179,15 @@ async fn run_custom_pipeline( // run pipeline recursively. let next_pipeline_def = PipelineDefinition::from_name(next_pipeline_name, None, None).context(PipelineSnafu)?; + let next_pipeline_ctx = + PipelineContext::new(&next_pipeline_def, pipeline_ctx.pipeline_param); let requests = Box::pin(run_pipeline( handler, - &next_pipeline_def, - pipeline_parameters, - coll, - table_name, + &next_pipeline_ctx, + PipelineIngestRequest { + table: table_name, + values: coll, + }, query_ctx, false, )) diff --git a/src/servers/src/postgres/types.rs b/src/servers/src/postgres/types.rs index a8daf880c6..68bdb9d2e9 100644 --- a/src/servers/src/postgres/types.rs +++ b/src/servers/src/postgres/types.rs @@ -421,13 +421,13 @@ pub(super) fn encode_value( Value::IntervalDayTime(v) => builder.encode_field(&PgInterval::from(*v)), Value::IntervalMonthDayNano(v) => builder.encode_field(&PgInterval::from(*v)), Value::Decimal128(v) => builder.encode_field(&v.to_string()), + Value::Duration(d) => match PgInterval::try_from(*d) { + Ok(i) => builder.encode_field(&i), + Err(e) => Err(PgWireError::ApiError(Box::new(Error::Internal { + err_msg: e.to_string(), + }))), + }, Value::List(values) => encode_array(query_ctx, values, builder), - Value::Duration(_) => Err(PgWireError::ApiError(Box::new(Error::Internal { - err_msg: format!( - "cannot write value {:?} in postgres protocol: unimplemented", - &value - ), - }))), } } @@ -466,8 +466,8 @@ pub(super) fn type_gt_to_pg(origin: &ConcreteDataType) -> Result { &ConcreteDataType::Interval(_) => Ok(Type::INTERVAL_ARRAY), &ConcreteDataType::Decimal128(_) => Ok(Type::NUMERIC_ARRAY), &ConcreteDataType::Json(_) => Ok(Type::JSON_ARRAY), - &ConcreteDataType::Duration(_) - | &ConcreteDataType::Dictionary(_) + &ConcreteDataType::Duration(_) => Ok(Type::INTERVAL_ARRAY), + &ConcreteDataType::Dictionary(_) | &ConcreteDataType::Vector(_) | &ConcreteDataType::List(_) => server_error::UnsupportedDataTypeSnafu { data_type: origin, @@ -475,13 +475,12 @@ pub(super) fn type_gt_to_pg(origin: &ConcreteDataType) -> Result { } .fail(), }, - &ConcreteDataType::Duration(_) | &ConcreteDataType::Dictionary(_) => { - server_error::UnsupportedDataTypeSnafu { - data_type: origin, - reason: "not implemented", - } - .fail() + &ConcreteDataType::Dictionary(_) => server_error::UnsupportedDataTypeSnafu { + data_type: origin, + reason: "not implemented", } + .fail(), + &ConcreteDataType::Duration(_) => Ok(Type::INTERVAL), } } diff --git a/src/servers/src/postgres/types/interval.rs b/src/servers/src/postgres/types/interval.rs index c15833f86c..ec9bfe912b 100644 --- a/src/servers/src/postgres/types/interval.rs +++ b/src/servers/src/postgres/types/interval.rs @@ -16,10 +16,19 @@ use std::fmt::Display; use bytes::{Buf, BufMut}; use common_time::interval::IntervalFormat; -use common_time::{IntervalDayTime, IntervalMonthDayNano, IntervalYearMonth}; +use common_time::timestamp::TimeUnit; +use common_time::{Duration, IntervalDayTime, IntervalMonthDayNano, IntervalYearMonth}; use pgwire::types::ToSqlText; use postgres_types::{to_sql_checked, FromSql, IsNull, ToSql, Type}; +use crate::error; + +/// On average one month has 30.44 day, which is a common approximation. +const SECONDS_PER_MONTH: i64 = 24 * 6 * 6 * 3044; +const SECONDS_PER_DAY: i64 = 24 * 60 * 60; +const MILLISECONDS_PER_MONTH: i64 = SECONDS_PER_MONTH * 1000; +const MILLISECONDS_PER_DAY: i64 = SECONDS_PER_DAY * 1000; + #[derive(Debug, Clone, Copy, Default)] pub struct PgInterval { pub(crate) months: i32, @@ -57,6 +66,62 @@ impl From for PgInterval { } } +impl TryFrom for PgInterval { + type Error = error::Error; + + fn try_from(duration: Duration) -> error::Result { + let value = duration.value(); + let unit = duration.unit(); + + // Convert the duration to microseconds + match unit { + TimeUnit::Second => { + let months = i32::try_from(value / SECONDS_PER_MONTH) + .map_err(|_| error::DurationOverflowSnafu { val: duration }.build())?; + let days = + i32::try_from((value - (months as i64) * SECONDS_PER_MONTH) / SECONDS_PER_DAY) + .map_err(|_| error::DurationOverflowSnafu { val: duration }.build())?; + let microseconds = + (value - (months as i64) * SECONDS_PER_MONTH - (days as i64) * SECONDS_PER_DAY) + .checked_mul(1_000_000) + .ok_or(error::DurationOverflowSnafu { val: duration }.build())?; + + Ok(Self { + months, + days, + microseconds, + }) + } + TimeUnit::Millisecond => { + let months = i32::try_from(value / MILLISECONDS_PER_MONTH) + .map_err(|_| error::DurationOverflowSnafu { val: duration }.build())?; + let days = i32::try_from( + (value - (months as i64) * MILLISECONDS_PER_MONTH) / MILLISECONDS_PER_DAY, + ) + .map_err(|_| error::DurationOverflowSnafu { val: duration }.build())?; + let microseconds = ((value - (months as i64) * MILLISECONDS_PER_MONTH) + - (days as i64) * MILLISECONDS_PER_DAY) + * 1_000; + Ok(Self { + months, + days, + microseconds, + }) + } + TimeUnit::Microsecond => Ok(Self { + months: 0, + days: 0, + microseconds: value, + }), + TimeUnit::Nanosecond => Ok(Self { + months: 0, + days: 0, + microseconds: value / 1000, + }), + } + } +} + impl From for IntervalMonthDayNano { fn from(interval: PgInterval) -> Self { IntervalMonthDayNano::new( @@ -149,3 +214,83 @@ impl ToSqlText for PgInterval { Ok(IsNull::No) } } + +#[cfg(test)] +mod tests { + use common_time::timestamp::TimeUnit; + use common_time::Duration; + + use super::*; + + #[test] + fn test_duration_to_pg_interval() { + // Test with seconds + let duration = Duration::new(86400, TimeUnit::Second); // 1 day + let interval = PgInterval::try_from(duration).unwrap(); + assert_eq!(interval.months, 0); + assert_eq!(interval.days, 1); + assert_eq!(interval.microseconds, 0); + + // Test with milliseconds + let duration = Duration::new(86400000, TimeUnit::Millisecond); // 1 day + let interval = PgInterval::try_from(duration).unwrap(); + assert_eq!(interval.months, 0); + assert_eq!(interval.days, 1); + assert_eq!(interval.microseconds, 0); + + // Test with microseconds + let duration = Duration::new(86400000000, TimeUnit::Microsecond); // 1 day + let interval = PgInterval::try_from(duration).unwrap(); + assert_eq!(interval.months, 0); + assert_eq!(interval.days, 0); + assert_eq!(interval.microseconds, 86400000000); + + // Test with nanoseconds + let duration = Duration::new(86400000000000, TimeUnit::Nanosecond); // 1 day + let interval = PgInterval::try_from(duration).unwrap(); + assert_eq!(interval.months, 0); + assert_eq!(interval.days, 0); + assert_eq!(interval.microseconds, 86400000000); + + // Test with partial day + let duration = Duration::new(43200, TimeUnit::Second); // 12 hours + let interval = PgInterval::try_from(duration).unwrap(); + assert_eq!(interval.months, 0); + assert_eq!(interval.days, 0); + assert_eq!(interval.microseconds, 43_200_000_000); // 12 hours in microseconds + + // Test with negative duration + let duration = Duration::new(-86400, TimeUnit::Second); // -1 day + let interval = PgInterval::try_from(duration).unwrap(); + assert_eq!(interval.months, 0); + assert_eq!(interval.days, -1); + assert_eq!(interval.microseconds, 0); + + // Test with multiple days + let duration = Duration::new(259200, TimeUnit::Second); // 3 days + let interval = PgInterval::try_from(duration).unwrap(); + assert_eq!(interval.months, 0); + assert_eq!(interval.days, 3); + assert_eq!(interval.microseconds, 0); + + // Test with small duration (less than a day) + let duration = Duration::new(3600, TimeUnit::Second); // 1 hour + let interval = PgInterval::try_from(duration).unwrap(); + assert_eq!(interval.months, 0); + assert_eq!(interval.days, 0); + assert_eq!(interval.microseconds, 3600000000); // 1 hour in microseconds + + // Test with very small duration + let duration = Duration::new(1, TimeUnit::Microsecond); // 1 microsecond + let interval = PgInterval::try_from(duration).unwrap(); + assert_eq!(interval.months, 0); + assert_eq!(interval.days, 0); + assert_eq!(interval.microseconds, 1); + + let duration = Duration::new(i64::MAX, TimeUnit::Second); + assert!(PgInterval::try_from(duration).is_err()); + + let duration = Duration::new(i64::MAX, TimeUnit::Millisecond); + assert!(PgInterval::try_from(duration).is_err()); + } +} diff --git a/src/servers/src/query_handler.rs b/src/servers/src/query_handler.rs index b4e734fca2..30fd360455 100644 --- a/src/servers/src/query_handler.rs +++ b/src/servers/src/query_handler.rs @@ -34,6 +34,7 @@ use api::v1::RowInsertRequests; use async_trait::async_trait; use catalog::CatalogManager; use common_query::Output; +use datatypes::timestamp::TimestampNanosecond; use headers::HeaderValue; use log_query::LogQuery; use opentelemetry_proto::tonic::collector::logs::v1::ExportLogsServiceRequest; @@ -165,6 +166,14 @@ pub trait PipelineHandler { //// Build a pipeline from a string. fn build_pipeline(&self, pipeline: &str) -> Result; + + /// Get a original pipeline by name. + async fn get_pipeline_str( + &self, + name: &str, + version: PipelineVersion, + query_ctx: QueryContextRef, + ) -> Result<(String, TimestampNanosecond)>; } /// Handle log query requests. diff --git a/src/servers/src/query_handler/grpc.rs b/src/servers/src/query_handler/grpc.rs index 01464012d6..7af5c9935a 100644 --- a/src/servers/src/query_handler/grpc.rs +++ b/src/servers/src/query_handler/grpc.rs @@ -16,16 +16,20 @@ use std::sync::Arc; use api::v1::greptime_request::Request; use async_trait::async_trait; +use common_base::AffectedRows; use common_error::ext::{BoxedError, ErrorExt}; use common_query::Output; use session::context::QueryContextRef; use snafu::ResultExt; +use table::table_name::TableName; use crate::error::{self, Result}; pub type GrpcQueryHandlerRef = Arc + Send + Sync>; pub type ServerGrpcQueryHandlerRef = GrpcQueryHandlerRef; +pub type RawRecordBatch = bytes::Bytes; + #[async_trait] pub trait GrpcQueryHandler { type Error: ErrorExt; @@ -35,6 +39,12 @@ pub trait GrpcQueryHandler { query: Request, ctx: QueryContextRef, ) -> std::result::Result; + + async fn put_record_batch( + &self, + table: &TableName, + record_batch: RawRecordBatch, + ) -> std::result::Result; } pub struct ServerGrpcQueryHandlerAdapter(GrpcQueryHandlerRef); @@ -59,4 +69,16 @@ where .map_err(BoxedError::new) .context(error::ExecuteGrpcQuerySnafu) } + + async fn put_record_batch( + &self, + table: &TableName, + record_batch: RawRecordBatch, + ) -> Result { + self.0 + .put_record_batch(table, record_batch) + .await + .map_err(BoxedError::new) + .context(error::ExecuteGrpcRequestSnafu) + } } diff --git a/src/servers/tests/http/influxdb_test.rs b/src/servers/tests/http/influxdb_test.rs index 1a251763ae..93932252fb 100644 --- a/src/servers/tests/http/influxdb_test.rs +++ b/src/servers/tests/http/influxdb_test.rs @@ -14,7 +14,6 @@ use std::sync::Arc; -use api::v1::greptime_request::Request; use api::v1::RowInsertRequests; use async_trait::async_trait; use auth::tests::{DatabaseAuthInfo, MockUserProvider}; @@ -29,7 +28,6 @@ use servers::http::header::constants::GREPTIME_DB_HEADER_NAME; use servers::http::test_helpers::TestClient; use servers::http::{HttpOptions, HttpServerBuilder}; use servers::influxdb::InfluxdbRequest; -use servers::query_handler::grpc::GrpcQueryHandler; use servers::query_handler::sql::SqlQueryHandler; use servers::query_handler::InfluxdbLineProtocolHandler; use session::context::QueryContextRef; @@ -39,19 +37,6 @@ struct DummyInstance { tx: Arc>, } -#[async_trait] -impl GrpcQueryHandler for DummyInstance { - type Error = Error; - - async fn do_query( - &self, - _query: Request, - _ctx: QueryContextRef, - ) -> std::result::Result { - unimplemented!() - } -} - #[async_trait] impl InfluxdbLineProtocolHandler for DummyInstance { async fn exec(&self, request: InfluxdbRequest, ctx: QueryContextRef) -> Result { diff --git a/src/servers/tests/http/opentsdb_test.rs b/src/servers/tests/http/opentsdb_test.rs index 358af19dc8..6ac835e72d 100644 --- a/src/servers/tests/http/opentsdb_test.rs +++ b/src/servers/tests/http/opentsdb_test.rs @@ -14,7 +14,6 @@ use std::sync::Arc; -use api::v1::greptime_request::Request; use async_trait::async_trait; use axum::Router; use common_query::Output; @@ -26,7 +25,6 @@ use servers::error::{self, Result}; use servers::http::test_helpers::TestClient; use servers::http::{HttpOptions, HttpServerBuilder}; use servers::opentsdb::codec::DataPoint; -use servers::query_handler::grpc::GrpcQueryHandler; use servers::query_handler::sql::SqlQueryHandler; use servers::query_handler::OpentsdbProtocolHandler; use session::context::QueryContextRef; @@ -36,19 +34,6 @@ struct DummyInstance { tx: mpsc::Sender, } -#[async_trait] -impl GrpcQueryHandler for DummyInstance { - type Error = crate::Error; - - async fn do_query( - &self, - _query: Request, - _ctx: QueryContextRef, - ) -> std::result::Result { - unimplemented!() - } -} - #[async_trait] impl OpentsdbProtocolHandler for DummyInstance { async fn exec(&self, data_points: Vec, _ctx: QueryContextRef) -> Result { diff --git a/src/servers/tests/http/prom_store_test.rs b/src/servers/tests/http/prom_store_test.rs index 77a06db079..c8c5671b8c 100644 --- a/src/servers/tests/http/prom_store_test.rs +++ b/src/servers/tests/http/prom_store_test.rs @@ -17,7 +17,6 @@ use std::sync::Arc; use api::prom_store::remote::{ LabelMatcher, Query, QueryResult, ReadRequest, ReadResponse, WriteRequest, }; -use api::v1::greptime_request::Request; use api::v1::RowInsertRequests; use async_trait::async_trait; use axum::Router; @@ -33,7 +32,6 @@ use servers::http::test_helpers::TestClient; use servers::http::{HttpOptions, HttpServerBuilder}; use servers::prom_store; use servers::prom_store::{snappy_compress, Metrics}; -use servers::query_handler::grpc::GrpcQueryHandler; use servers::query_handler::sql::SqlQueryHandler; use servers::query_handler::{PromStoreProtocolHandler, PromStoreResponse}; use session::context::QueryContextRef; @@ -43,19 +41,6 @@ struct DummyInstance { tx: mpsc::Sender<(String, Vec)>, } -#[async_trait] -impl GrpcQueryHandler for DummyInstance { - type Error = Error; - - async fn do_query( - &self, - _query: Request, - _ctx: QueryContextRef, - ) -> std::result::Result { - unimplemented!() - } -} - #[async_trait] impl PromStoreProtocolHandler for DummyInstance { async fn write( diff --git a/src/servers/tests/mod.rs b/src/servers/tests/mod.rs index aa07980240..7bd8a696be 100644 --- a/src/servers/tests/mod.rs +++ b/src/servers/tests/mod.rs @@ -18,18 +18,21 @@ use api::v1::greptime_request::Request; use api::v1::query_request::Query; use async_trait::async_trait; use catalog::memory::MemoryCatalogManager; +use common_base::AffectedRows; use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; use common_query::Output; use datafusion_expr::LogicalPlan; +use query::options::QueryOptions; use query::parser::{PromQuery, QueryLanguageParser, QueryStatement}; use query::query_engine::DescribeResult; use query::{QueryEngineFactory, QueryEngineRef}; use servers::error::{Error, NotSupportedSnafu, Result}; -use servers::query_handler::grpc::{GrpcQueryHandler, ServerGrpcQueryHandlerRef}; +use servers::query_handler::grpc::{GrpcQueryHandler, RawRecordBatch, ServerGrpcQueryHandlerRef}; use servers::query_handler::sql::{ServerSqlQueryHandlerRef, SqlQueryHandler}; use session::context::QueryContextRef; use snafu::ensure; use sql::statements::statement::Statement; +use table::table_name::TableName; use table::TableRef; mod grpc; @@ -129,7 +132,7 @@ impl GrpcQueryHandler for DummyInstance { ); result.remove(0)? } - Query::LogicalPlan(_) => unimplemented!(), + Query::LogicalPlan(_) | Query::InsertIntoPlan(_) => unimplemented!(), Query::PromRangeQuery(promql) => { let prom_query = PromQuery { query: promql.query, @@ -154,12 +157,30 @@ impl GrpcQueryHandler for DummyInstance { }; Ok(output) } + + async fn put_record_batch( + &self, + table: &TableName, + record_batch: RawRecordBatch, + ) -> std::result::Result { + let _ = table; + let _ = record_batch; + unimplemented!() + } } fn create_testing_instance(table: TableRef) -> DummyInstance { let catalog_manager = MemoryCatalogManager::new_with_table(table); - let query_engine = - QueryEngineFactory::new(catalog_manager, None, None, None, None, false).query_engine(); + let query_engine = QueryEngineFactory::new( + catalog_manager, + None, + None, + None, + None, + false, + QueryOptions::default(), + ) + .query_engine(); DummyInstance::new(query_engine) } diff --git a/src/sql/Cargo.toml b/src/sql/Cargo.toml index 3cb81d6dd4..812fe42709 100644 --- a/src/sql/Cargo.toml +++ b/src/sql/Cargo.toml @@ -37,6 +37,7 @@ sqlparser.workspace = true sqlparser_derive = "0.1" store-api.workspace = true table.workspace = true +uuid.workspace = true [dev-dependencies] common-datasource.workspace = true diff --git a/src/sql/src/error.rs b/src/sql/src/error.rs index e7253d6c46..e07efdbe6c 100644 --- a/src/sql/src/error.rs +++ b/src/sql/src/error.rs @@ -345,6 +345,16 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display( + "Invalid partition number: {}, should be in range [2, 65536]", + partition_num + ))] + InvalidPartitionNumber { + partition_num: u32, + #[snafu(implicit)] + location: Location, + }, } impl ErrorExt for Error { @@ -380,6 +390,7 @@ impl ErrorExt for Error { | Simplification { .. } | InvalidInterval { .. } | InvalidUnaryOp { .. } + | InvalidPartitionNumber { .. } | UnsupportedUnaryOp { .. } => StatusCode::InvalidArguments, SerializeColumnDefaultConstraint { source, .. } => source.status_code(), diff --git a/src/sql/src/parsers/create_parser.rs b/src/sql/src/parsers/create_parser.rs index 35797c380b..561216e876 100644 --- a/src/sql/src/parsers/create_parser.rs +++ b/src/sql/src/parsers/create_parser.rs @@ -288,8 +288,14 @@ impl<'a> ParserContext<'a> { .with_context(|| InvalidIntervalSnafu { reason: format!("cannot cast {} to interval type", expire_after_expr), })?; - if let ScalarValue::IntervalMonthDayNano(Some(nanoseconds)) = expire_after_lit { - Some(nanoseconds.nanoseconds / 1_000_000_000) + if let ScalarValue::IntervalMonthDayNano(Some(interval)) = expire_after_lit { + Some( + interval.nanoseconds / 1_000_000_000 + + interval.days as i64 * 60 * 60 * 24 + + interval.months as i64 * 60 * 60 * 24 * 3044 / 1000, // 1 month=365.25/12=30.44 days + // this is to keep the same as https://docs.rs/humantime/latest/humantime/fn.parse_duration.html + // which we use in database to parse i.e. ttl interval and many other intervals + ) } else { unreachable!() } @@ -1325,6 +1331,7 @@ SELECT max(c1), min(c2) FROM schema_2.table_2;"; let sql = r" CREATE FLOW `task_2` SINK TO schema_1.table_1 +EXPIRE AFTER '1 month 2 days 1h 2 min' AS SELECT max(c1), min(c2) FROM schema_2.table_2;"; let stmts = @@ -1337,7 +1344,10 @@ SELECT max(c1), min(c2) FROM schema_2.table_2;"; }; assert!(!create_task.or_replace); assert!(!create_task.if_not_exists); - assert!(create_task.expire_after.is_none()); + assert_eq!( + create_task.expire_after, + Some(86400 * 3044 / 1000 + 2 * 86400 + 3600 + 2 * 60) + ); assert!(create_task.comment.is_none()); assert_eq!(create_task.flow_name.to_string(), "`task_2`"); } diff --git a/src/sql/src/partition.rs b/src/sql/src/partition.rs index 4979bf702f..a1fd8e642e 100644 --- a/src/sql/src/partition.rs +++ b/src/sql/src/partition.rs @@ -12,10 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +use snafu::ensure; use sqlparser::ast::{BinaryOperator, Expr, Ident, Value}; +use crate::error::{InvalidPartitionNumberSnafu, Result}; use crate::statements::create::Partitions; +/// The default number of partitions for OpenTelemetry traces. +const DEFAULT_PARTITION_NUM_FOR_TRACES: u32 = 16; + +/// The maximum number of partitions for OpenTelemetry traces. +const MAX_PARTITION_NUM_FOR_TRACES: u32 = 65536; + macro_rules! between_string { ($col: expr, $left_incl: expr, $right_excl: expr) => { Expr::BinaryOp { @@ -38,98 +46,105 @@ macro_rules! between_string { }; } -macro_rules! or { - ($left: expr, $right: expr) => { - Expr::BinaryOp { - op: BinaryOperator::Or, - left: Box::new($left), - right: Box::new($right), - } - }; +pub fn partition_rule_for_hexstring(ident: &str) -> Result { + Ok(Partitions { + column_list: vec![Ident::new(ident)], + exprs: partition_rules_for_uuid(DEFAULT_PARTITION_NUM_FOR_TRACES, ident)?, + }) } -pub fn partition_rule_for_hexstring(ident: &str) -> Partitions { - let ident = Ident::new(ident); - let ident_expr = Expr::Identifier(ident.clone()); +// partition_rules_for_uuid can creates partition rules up to 65536 partitions. +fn partition_rules_for_uuid(partition_num: u32, ident: &str) -> Result> { + ensure!( + partition_num.is_power_of_two() && (2..=65536).contains(&partition_num), + InvalidPartitionNumberSnafu { partition_num } + ); - // rules are like: - // - // "trace_id < '1'", - // "trace_id >= '1' AND trace_id < '2'", - // "trace_id >= '2' AND trace_id < '3'", - // "trace_id >= '3' AND trace_id < '4'", - // "trace_id >= '4' AND trace_id < '5'", - // "trace_id >= '5' AND trace_id < '6'", - // "trace_id >= '6' AND trace_id < '7'", - // "trace_id >= '7' AND trace_id < '8'", - // "trace_id >= '8' AND trace_id < '9'", - // "trace_id >= '9' AND trace_id < 'A'", - // "trace_id >= 'A' AND trace_id < 'B' OR trace_id >= 'a' AND trace_id < 'b'", - // "trace_id >= 'B' AND trace_id < 'C' OR trace_id >= 'b' AND trace_id < 'c'", - // "trace_id >= 'C' AND trace_id < 'D' OR trace_id >= 'c' AND trace_id < 'd'", - // "trace_id >= 'D' AND trace_id < 'E' OR trace_id >= 'd' AND trace_id < 'e'", - // "trace_id >= 'E' AND trace_id < 'F' OR trace_id >= 'e' AND trace_id < 'f'", - // "trace_id >= 'F' AND trace_id < 'a' OR trace_id >= 'f'", - let rules = vec![ - Expr::BinaryOp { - left: Box::new(ident_expr.clone()), - op: BinaryOperator::Lt, - right: Box::new(Expr::Value(Value::SingleQuotedString("1".to_string()))), - }, - // [left, right) - between_string!(ident_expr, "1", "2"), - between_string!(ident_expr, "2", "3"), - between_string!(ident_expr, "3", "4"), - between_string!(ident_expr, "4", "5"), - between_string!(ident_expr, "5", "6"), - between_string!(ident_expr, "6", "7"), - between_string!(ident_expr, "7", "8"), - between_string!(ident_expr, "8", "9"), - between_string!(ident_expr, "9", "A"), - or!( - between_string!(ident_expr, "A", "B"), - between_string!(ident_expr, "a", "b") - ), - or!( - between_string!(ident_expr, "B", "C"), - between_string!(ident_expr, "b", "c") - ), - or!( - between_string!(ident_expr, "C", "D"), - between_string!(ident_expr, "c", "d") - ), - or!( - between_string!(ident_expr, "D", "E"), - between_string!(ident_expr, "d", "e") - ), - or!( - between_string!(ident_expr, "E", "F"), - between_string!(ident_expr, "e", "f") - ), - or!( - between_string!(ident_expr, "F", "a"), - Expr::BinaryOp { + let ident_expr = Expr::Identifier(Ident::new(ident).clone()); + + let (total_partitions, hex_length) = { + match partition_num { + 2..=16 => (16, 1), + 17..=256 => (256, 2), + 257..=4096 => (4096, 3), + 4097..=MAX_PARTITION_NUM_FOR_TRACES => (MAX_PARTITION_NUM_FOR_TRACES, 4), + _ => unreachable!(), + } + }; + + let partition_size = total_partitions / partition_num; + let remainder = total_partitions % partition_num; + + let mut rules = Vec::new(); + let mut current_boundary = 0; + for i in 0..partition_num { + let mut size = partition_size; + if i < remainder { + size += 1; + } + let start = current_boundary; + let end = current_boundary + size; + + if i == 0 { + // Create the leftmost rule, for example: trace_id < '1'. + rules.push(Expr::BinaryOp { + left: Box::new(ident_expr.clone()), + op: BinaryOperator::Lt, + right: Box::new(Expr::Value(Value::SingleQuotedString(format!( + "{:0hex_length$x}", + end + )))), + }); + } else if i == partition_num - 1 { + // Create the rightmost rule, for example: trace_id >= 'f'. + rules.push(Expr::BinaryOp { left: Box::new(ident_expr.clone()), op: BinaryOperator::GtEq, - right: Box::new(Expr::Value(Value::SingleQuotedString("f".to_string()))), - } - ), - ]; + right: Box::new(Expr::Value(Value::SingleQuotedString(format!( + "{:0hex_length$x}", + start + )))), + }); + } else { + // Create the middle rules, for example: trace_id >= '1' AND trace_id < '2'. + rules.push(between_string!( + ident_expr, + format!("{:0hex_length$x}", start), + format!("{:0hex_length$x}", end) + )); + } - Partitions { - column_list: vec![ident], - exprs: rules, + current_boundary = end; } + + Ok(rules) } #[cfg(test)] mod tests { + use std::collections::HashMap; + use sqlparser::ast::Expr; use sqlparser::dialect::GenericDialect; use sqlparser::parser::Parser; + use uuid::Uuid; use super::*; + #[test] + fn test_partition_rules_for_uuid() { + // NOTE: We only test a subset of partitions to keep the test execution time reasonable. + // As the number of partitions increases, we need to increase the number of test samples to ensure uniform distribution. + assert!(check_distribution(2, 10_000)); // 2^1 + assert!(check_distribution(4, 10_000)); // 2^2 + assert!(check_distribution(8, 10_000)); // 2^3 + assert!(check_distribution(16, 10_000)); // 2^4 + assert!(check_distribution(32, 10_000)); // 2^5 + assert!(check_distribution(64, 100_000)); // 2^6 + assert!(check_distribution(128, 100_000)); // 2^7 + assert!(check_distribution(256, 100_000)); // 2^8 + } + #[test] fn test_rules() { let expr = vec![ @@ -142,13 +157,13 @@ mod tests { "trace_id >= '6' AND trace_id < '7'", "trace_id >= '7' AND trace_id < '8'", "trace_id >= '8' AND trace_id < '9'", - "trace_id >= '9' AND trace_id < 'A'", - "trace_id >= 'A' AND trace_id < 'B' OR trace_id >= 'a' AND trace_id < 'b'", - "trace_id >= 'B' AND trace_id < 'C' OR trace_id >= 'b' AND trace_id < 'c'", - "trace_id >= 'C' AND trace_id < 'D' OR trace_id >= 'c' AND trace_id < 'd'", - "trace_id >= 'D' AND trace_id < 'E' OR trace_id >= 'd' AND trace_id < 'e'", - "trace_id >= 'E' AND trace_id < 'F' OR trace_id >= 'e' AND trace_id < 'f'", - "trace_id >= 'F' AND trace_id < 'a' OR trace_id >= 'f'", + "trace_id >= '9' AND trace_id < 'a'", + "trace_id >= 'a' AND trace_id < 'b'", + "trace_id >= 'b' AND trace_id < 'c'", + "trace_id >= 'c' AND trace_id < 'd'", + "trace_id >= 'd' AND trace_id < 'e'", + "trace_id >= 'e' AND trace_id < 'f'", + "trace_id >= 'f'", ]; let dialect = GenericDialect {}; @@ -160,6 +175,93 @@ mod tests { }) .collect::>(); - assert_eq!(results, partition_rule_for_hexstring("trace_id").exprs); + assert_eq!( + results, + partition_rule_for_hexstring("trace_id").unwrap().exprs + ); + } + + fn check_distribution(test_partition: u32, test_uuid_num: usize) -> bool { + // Generate test_uuid_num random uuids. + let uuids = (0..test_uuid_num) + .map(|_| Uuid::new_v4().to_string().replace("-", "").to_lowercase()) + .collect::>(); + + // Generate the partition rules. + let rules = partition_rules_for_uuid(test_partition, "test_trace_id").unwrap(); + + // Collect the number of partitions for each uuid. + let mut stats = HashMap::new(); + for uuid in uuids { + let partition = allocate_partition_for_uuid(uuid.clone(), &rules); + // Count the number of uuids in each partition. + *stats.entry(partition).or_insert(0) += 1; + } + + // Check if the partition distribution is uniform. + let expected_ratio = 100.0 / test_partition as f64; + + // tolerance is the allowed deviation from the expected ratio. + let tolerance = 100.0 / test_partition as f64 * 0.30; + + // For each partition, its ratio should be as close as possible to the expected ratio. + for (_, count) in stats { + let ratio = (count as f64 / test_uuid_num as f64) * 100.0; + if (ratio - expected_ratio).abs() >= tolerance { + return false; + } + } + + true + } + + fn allocate_partition_for_uuid(uuid: String, rules: &[Expr]) -> usize { + for (i, rule) in rules.iter().enumerate() { + if let Expr::BinaryOp { left, op: _, right } = rule { + if i == 0 { + // Hit the leftmost rule. + if let Expr::Value(Value::SingleQuotedString(leftmost)) = *right.clone() { + if uuid < leftmost { + return i; + } + } + } else if i == rules.len() - 1 { + // Hit the rightmost rule. + if let Expr::Value(Value::SingleQuotedString(rightmost)) = *right.clone() { + if uuid >= rightmost { + return i; + } + } + } else { + // Hit the middle rules. + if let Expr::BinaryOp { + left: _, + op: _, + right: inner_right, + } = *left.clone() + { + if let Expr::Value(Value::SingleQuotedString(lower)) = *inner_right.clone() + { + if let Expr::BinaryOp { + left: _, + op: _, + right: inner_right, + } = *right.clone() + { + if let Expr::Value(Value::SingleQuotedString(upper)) = + *inner_right.clone() + { + if uuid >= lower && uuid < upper { + return i; + } + } + } + } + } + } + } + } + + panic!("No partition found for uuid: {}, rules: {:?}", uuid, rules); } } diff --git a/src/sql/src/statements/transform.rs b/src/sql/src/statements/transform.rs index 2ca642cd11..7bd4218d2f 100644 --- a/src/sql/src/statements/transform.rs +++ b/src/sql/src/statements/transform.rs @@ -55,7 +55,7 @@ pub fn transform_statements(stmts: &mut Vec) -> Result<()> { } } - visit_expressions_mut(stmts, |expr| { + let _ = visit_expressions_mut(stmts, |expr| { for rule in RULES.iter() { rule.visit_expr(expr)?; } diff --git a/src/store-api/src/lib.rs b/src/store-api/src/lib.rs index cd0416dc29..c6ee28f5d3 100644 --- a/src/store-api/src/lib.rs +++ b/src/store-api/src/lib.rs @@ -1,4 +1,3 @@ -#![feature(let_chains)] // Copyright 2023 Greptime Team // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +14,9 @@ //! Storage related APIs +#![feature(let_chains)] +#![feature(iterator_try_collect)] + pub mod codec; pub mod data_source; pub mod logstore; diff --git a/src/store-api/src/logstore.rs b/src/store-api/src/logstore.rs index 86a2263398..573fab469e 100644 --- a/src/store-api/src/logstore.rs +++ b/src/store-api/src/logstore.rs @@ -94,6 +94,9 @@ pub trait LogStore: Send + Sync + 'static + std::fmt::Debug { region_id: RegionId, provider: &Provider, ) -> Result; + + /// Returns the highest existing entry id in the log store. + fn high_watermark(&self, provider: &Provider) -> Result; } /// The response of an `append` operation. diff --git a/src/store-api/src/metadata.rs b/src/store-api/src/metadata.rs index de2a31feab..e8372a8df2 100644 --- a/src/store-api/src/metadata.rs +++ b/src/store-api/src/metadata.rs @@ -27,6 +27,7 @@ use api::v1::SemanticType; use common_error::ext::ErrorExt; use common_error::status_code::StatusCode; use common_macro::stack_trace_debug; +use datatypes::arrow; use datatypes::arrow::datatypes::FieldRef; use datatypes::schema::{ColumnSchema, FulltextOptions, Schema, SchemaRef, SkippingIndexOptions}; use serde::de::Error; @@ -289,7 +290,7 @@ impl RegionMetadata { pub fn project(&self, projection: &[ColumnId]) -> Result { // check time index ensure!( - projection.iter().any(|id| *id == self.time_index), + projection.contains(&self.time_index), TimeIndexNotFoundSnafu ); @@ -957,6 +958,21 @@ pub enum MetadataError { #[snafu(implicit)] location: Location, }, + + #[snafu(display("Failed to decode arrow ipc record batches"))] + DecodeArrowIpc { + #[snafu(source)] + error: arrow::error::ArrowError, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Unexpected: {}", reason))] + Unexpected { + reason: String, + #[snafu(implicit)] + location: Location, + }, } impl ErrorExt for MetadataError { @@ -969,6 +985,14 @@ impl ErrorExt for MetadataError { } } +/// Set column fulltext options if it passed the validation. +/// +/// Options allowed to modify: +/// * backend +/// +/// Options not allowed to modify: +/// * analyzer +/// * case_sensitive fn set_column_fulltext_options( column_meta: &mut ColumnMetadata, column_name: String, @@ -976,14 +1000,6 @@ fn set_column_fulltext_options( current_options: Option, ) -> Result<()> { if let Some(current_options) = current_options { - ensure!( - !current_options.enable, - InvalidColumnOptionSnafu { - column_name, - msg: "FULLTEXT index already enabled".to_string(), - } - ); - ensure!( current_options.analyzer == options.analyzer && current_options.case_sensitive == options.case_sensitive, diff --git a/src/store-api/src/metric_engine_consts.rs b/src/store-api/src/metric_engine_consts.rs index af2a523e70..b2994b0b00 100644 --- a/src/store-api/src/metric_engine_consts.rs +++ b/src/store-api/src/metric_engine_consts.rs @@ -73,6 +73,10 @@ pub const LOGICAL_TABLE_METADATA_KEY: &str = "on_physical_table"; /// Represent a list of column metadata that are added to physical table. pub const ALTER_PHYSICAL_EXTENSION_KEY: &str = "ALTER_PHYSICAL"; +/// HashMap key to be used in the region server's extension response. +/// Represent the manifest info of a region. +pub const MANIFEST_INFO_EXTENSION_KEY: &str = "MANIFEST_INFO"; + /// Returns true if it's a internal column of the metric engine. pub fn is_metric_engine_internal_column(name: &str) -> bool { name == DATA_SCHEMA_TABLE_ID_COLUMN_NAME || name == DATA_SCHEMA_TSID_COLUMN_NAME diff --git a/src/store-api/src/mito_engine_options.rs b/src/store-api/src/mito_engine_options.rs index e73060469f..aa6fd8984d 100644 --- a/src/store-api/src/mito_engine_options.rs +++ b/src/store-api/src/mito_engine_options.rs @@ -59,6 +59,9 @@ pub const MEMTABLE_PARTITION_TREE_DATA_FREEZE_THRESHOLD: &str = /// Option key for memtable partition tree fork dictionary bytes. pub const MEMTABLE_PARTITION_TREE_FORK_DICTIONARY_BYTES: &str = "memtable.partition_tree.fork_dictionary_bytes"; +/// Option key for skipping WAL. +pub const SKIP_WAL_KEY: &str = "skip_wal"; +// Note: Adding new options here should also check if this option should be removed in [metric_engine::engine::create::region_options_for_metadata_region]. /// Returns true if the `key` is a valid option key for the mito engine. pub fn is_mito_engine_option_key(key: &str) -> bool { diff --git a/src/store-api/src/region_engine.rs b/src/store-api/src/region_engine.rs index d4df5216f4..5f6069d961 100644 --- a/src/store-api/src/region_engine.rs +++ b/src/store-api/src/region_engine.rs @@ -47,6 +47,15 @@ pub enum SettableRegionRoleState { DowngradingLeader, } +impl Display for SettableRegionRoleState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SettableRegionRoleState::Follower => write!(f, "Follower"), + SettableRegionRoleState::DowngradingLeader => write!(f, "Leader(Downgrading)"), + } + } +} + impl From for RegionRole { fn from(value: SettableRegionRoleState) -> Self { match value { @@ -63,20 +72,78 @@ pub struct SetRegionRoleStateRequest { region_role_state: SettableRegionRoleState, } +/// The success response of setting region role state. +#[derive(Debug, PartialEq, Eq)] +pub enum SetRegionRoleStateSuccess { + File, + Mito { + last_entry_id: entry::Id, + }, + Metric { + last_entry_id: entry::Id, + metadata_last_entry_id: entry::Id, + }, +} + +impl SetRegionRoleStateSuccess { + /// Returns a [SetRegionRoleStateSuccess::File]. + pub fn file() -> Self { + Self::File + } + + /// Returns a [SetRegionRoleStateSuccess::Mito] with the `last_entry_id`. + pub fn mito(last_entry_id: entry::Id) -> Self { + SetRegionRoleStateSuccess::Mito { last_entry_id } + } + + /// Returns a [SetRegionRoleStateSuccess::Metric] with the `last_entry_id` and `metadata_last_entry_id`. + pub fn metric(last_entry_id: entry::Id, metadata_last_entry_id: entry::Id) -> Self { + SetRegionRoleStateSuccess::Metric { + last_entry_id, + metadata_last_entry_id, + } + } +} + +impl SetRegionRoleStateSuccess { + /// Returns the last entry id of the region. + pub fn last_entry_id(&self) -> Option { + match self { + SetRegionRoleStateSuccess::File => None, + SetRegionRoleStateSuccess::Mito { last_entry_id } => Some(*last_entry_id), + SetRegionRoleStateSuccess::Metric { last_entry_id, .. } => Some(*last_entry_id), + } + } + + /// Returns the last entry id of the metadata of the region. + pub fn metadata_last_entry_id(&self) -> Option { + match self { + SetRegionRoleStateSuccess::File => None, + SetRegionRoleStateSuccess::Mito { .. } => None, + SetRegionRoleStateSuccess::Metric { + metadata_last_entry_id, + .. + } => Some(*metadata_last_entry_id), + } + } +} + /// The response of setting region role state. #[derive(Debug, PartialEq, Eq)] pub enum SetRegionRoleStateResponse { - Success { - /// Returns `last_entry_id` of the region if available(e.g., It's not available in file engine). - last_entry_id: Option, - }, + Success(SetRegionRoleStateSuccess), NotFound, } impl SetRegionRoleStateResponse { - /// Returns a [SetRegionRoleStateResponse::Success] with the `last_entry_id`. - pub fn success(last_entry_id: Option) -> Self { - Self::Success { last_entry_id } + /// Returns a [SetRegionRoleStateResponse::Success] with the `File` success. + pub fn success(success: SetRegionRoleStateSuccess) -> Self { + Self::Success(success) + } + + /// Returns true if the response is a [SetRegionRoleStateResponse::NotFound]. + pub fn is_not_found(&self) -> bool { + matches!(self, SetRegionRoleStateResponse::NotFound) } } @@ -384,6 +451,13 @@ pub struct RegionStatistic { /// The details of the region. #[serde(default)] pub manifest: RegionManifestInfo, + /// The latest entry id of the region's remote WAL since last flush. + /// For metric engine, there're two latest entry ids, one for data and one for metadata. + /// TODO(weny): remove this two fields and use single instead. + #[serde(default)] + pub data_topic_latest_entry_id: u64, + #[serde(default)] + pub metadata_topic_latest_entry_id: u64, } /// The manifest info of a region. @@ -482,6 +556,16 @@ impl RegionManifestInfo { } => Some(*metadata_flushed_entry_id), } } + + /// Encodes a list of ([RegionId], [RegionManifestInfo]) to a byte array. + pub fn encode_list(manifest_infos: &[(RegionId, Self)]) -> serde_json::Result> { + serde_json::to_vec(manifest_infos) + } + + /// Decodes a list of ([RegionId], [RegionManifestInfo]) from a byte array. + pub fn decode_list(value: &[u8]) -> serde_json::Result> { + serde_json::from_slice(value) + } } impl Default for RegionManifestInfo { @@ -516,6 +600,62 @@ impl RegionStatistic { } } +/// The response of syncing the manifest. +#[derive(Debug)] +pub enum SyncManifestResponse { + NotSupported, + Mito { + /// Indicates if the data region was synced. + synced: bool, + }, + Metric { + /// Indicates if the metadata region was synced. + metadata_synced: bool, + /// Indicates if the data region was synced. + data_synced: bool, + /// The logical regions that were newly opened during the sync operation. + /// This only occurs after the metadata region has been successfully synced. + new_opened_logical_region_ids: Vec, + }, +} + +impl SyncManifestResponse { + /// Returns true if data region is synced. + pub fn is_data_synced(&self) -> bool { + match self { + SyncManifestResponse::NotSupported => false, + SyncManifestResponse::Mito { synced } => *synced, + SyncManifestResponse::Metric { data_synced, .. } => *data_synced, + } + } + + /// Returns true if the engine is supported the sync operation. + pub fn is_supported(&self) -> bool { + matches!(self, SyncManifestResponse::NotSupported) + } + + /// Returns true if the engine is a mito2 engine. + pub fn is_mito(&self) -> bool { + matches!(self, SyncManifestResponse::Mito { .. }) + } + + /// Returns true if the engine is a metric engine. + pub fn is_metric(&self) -> bool { + matches!(self, SyncManifestResponse::Metric { .. }) + } + + /// Returns the new opened logical region ids. + pub fn new_opened_logical_region_ids(self) -> Option> { + match self { + SyncManifestResponse::Metric { + new_opened_logical_region_ids, + .. + } => Some(new_opened_logical_region_ids), + _ => None, + } + } +} + #[async_trait] pub trait RegionEngine: Send + Sync { /// Name of this engine @@ -622,7 +762,7 @@ pub trait RegionEngine: Send + Sync { &self, region_id: RegionId, manifest_info: RegionManifestInfo, - ) -> Result<(), BoxedError>; + ) -> Result; /// Sets region role state gracefully. /// diff --git a/src/store-api/src/region_request.rs b/src/store-api/src/region_request.rs index d11963987e..5a6e1289c6 100644 --- a/src/store-api/src/region_request.rs +++ b/src/store-api/src/region_request.rs @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::hash_map::Entry; use std::collections::HashMap; use std::fmt::{self, Display}; +use std::io::Cursor; use api::helper::ColumnDataTypeWrapper; use api::v1::add_column_location::LocationType; @@ -21,16 +23,19 @@ use api::v1::column_def::{ as_fulltext_option_analyzer, as_fulltext_option_backend, as_skipping_index_type, }; use api::v1::region::{ - alter_request, compact_request, region_request, AlterRequest, AlterRequests, CloseRequest, - CompactRequest, CreateRequest, CreateRequests, DeleteRequests, DropRequest, DropRequests, - FlushRequest, InsertRequests, OpenRequest, TruncateRequest, + alter_request, compact_request, region_request, AlterRequest, AlterRequests, + BulkInsertRequests, CloseRequest, CompactRequest, CreateRequest, CreateRequests, + DeleteRequests, DropRequest, DropRequests, FlushRequest, InsertRequests, OpenRequest, + TruncateRequest, }; use api::v1::{ self, set_index, Analyzer, FulltextBackend as PbFulltextBackend, Option as PbOption, Rows, SemanticType, SkippingIndexType as PbSkippingIndexType, WriteHint, }; pub use common_base::AffectedRows; +use common_recordbatch::DfRecordBatch; use common_time::TimeToLive; +use datatypes::arrow::ipc::reader::FileReader; use datatypes::prelude::ConcreteDataType; use datatypes::schema::{FulltextOptions, SkippingIndexOptions}; use serde::{Deserialize, Serialize}; @@ -39,9 +44,9 @@ use strum::{AsRefStr, IntoStaticStr}; use crate::logstore::entry; use crate::metadata::{ - ColumnMetadata, DecodeProtoSnafu, InvalidRawRegionRequestSnafu, InvalidRegionRequestSnafu, - InvalidSetRegionOptionRequestSnafu, InvalidUnsetRegionOptionRequestSnafu, MetadataError, - RegionMetadata, Result, + ColumnMetadata, DecodeArrowIpcSnafu, DecodeProtoSnafu, InvalidRawRegionRequestSnafu, + InvalidRegionRequestSnafu, InvalidSetRegionOptionRequestSnafu, + InvalidUnsetRegionOptionRequestSnafu, MetadataError, RegionMetadata, Result, UnexpectedSnafu, }; use crate::metric_engine_consts::PHYSICAL_TABLE_METADATA_KEY; use crate::mito_engine_options::{ @@ -126,6 +131,7 @@ pub enum RegionRequest { Compact(RegionCompactRequest), Truncate(RegionTruncateRequest), Catchup(RegionCatchupRequest), + BulkInserts(RegionBulkInsertsRequest), } impl RegionRequest { @@ -146,6 +152,11 @@ impl RegionRequest { region_request::Body::Creates(creates) => make_region_creates(creates), region_request::Body::Drops(drops) => make_region_drops(drops), region_request::Body::Alters(alters) => make_region_alters(alters), + region_request::Body::BulkInserts(bulk) => make_region_bulk_inserts(bulk), + region_request::Body::Sync(_) => UnexpectedSnafu { + reason: "Sync request should be handled separately by RegionServer", + } + .fail(), } } @@ -315,6 +326,51 @@ fn make_region_truncate(truncate: TruncateRequest) -> Result Result> { + let mut region_requests: HashMap> = + HashMap::with_capacity(requests.requests.len()); + + for req in requests.requests { + let region_id = req.region_id; + match req.payload_type() { + api::v1::region::BulkInsertType::ArrowIpc => { + // todo(hl): use StreamReader instead + let reader = FileReader::try_new(Cursor::new(req.payload), None) + .context(DecodeArrowIpcSnafu)?; + let record_batches = reader + .map(|b| b.map(BulkInsertPayload::ArrowIpc)) + .try_collect::>() + .context(DecodeArrowIpcSnafu)?; + match region_requests.entry(region_id) { + Entry::Occupied(mut e) => { + e.get_mut().extend(record_batches); + } + Entry::Vacant(e) => { + e.insert(record_batches); + } + } + } + } + } + + let result = region_requests + .into_iter() + .map(|(region_id, payloads)| { + ( + region_id.into(), + RegionRequest::BulkInserts(RegionBulkInsertsRequest { + region_id: region_id.into(), + payloads, + }), + ) + }) + .collect::>(); + Ok(result) +} + /// Request to put data into a region. #[derive(Debug)] pub struct RegionPutRequest { @@ -1105,6 +1161,10 @@ pub struct RegionCatchupRequest { /// The `entry_id` that was expected to reply to. /// `None` stands replaying to latest. pub entry_id: Option, + /// Used for metrics metadata region. + /// The `entry_id` that was expected to reply to. + /// `None` stands replaying to latest. + pub metadata_entry_id: Option, /// The hint for replaying memtable. pub location_id: Option, } @@ -1115,6 +1175,17 @@ pub struct RegionSequencesRequest { pub region_ids: Vec, } +#[derive(Debug, Clone)] +pub struct RegionBulkInsertsRequest { + pub region_id: RegionId, + pub payloads: Vec, +} + +#[derive(Debug, Clone)] +pub enum BulkInsertPayload { + ArrowIpc(DfRecordBatch), +} + impl fmt::Display for RegionRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -1129,6 +1200,7 @@ impl fmt::Display for RegionRequest { RegionRequest::Compact(_) => write!(f, "Compact"), RegionRequest::Truncate(_) => write!(f, "Truncate"), RegionRequest::Catchup(_) => write!(f, "Catchup"), + RegionRequest::BulkInserts(_) => write!(f, "BulkInserts"), } } } diff --git a/src/table/src/error.rs b/src/table/src/error.rs index ef08ebc4a1..6cd79fd61c 100644 --- a/src/table/src/error.rs +++ b/src/table/src/error.rs @@ -172,6 +172,9 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display("Invalid table name: '{s}'"))] + InvalidTableName { s: String }, } impl ErrorExt for Error { @@ -197,7 +200,8 @@ impl ErrorExt for Error { Error::MissingTimeIndexColumn { .. } => StatusCode::IllegalState, Error::InvalidTableOptionValue { .. } | Error::SetSkippingOptions { .. } - | Error::UnsetSkippingOptions { .. } => StatusCode::InvalidArguments, + | Error::UnsetSkippingOptions { .. } + | Error::InvalidTableName { .. } => StatusCode::InvalidArguments, } } diff --git a/src/table/src/metadata.rs b/src/table/src/metadata.rs index a457afe107..4235588ff0 100644 --- a/src/table/src/metadata.rs +++ b/src/table/src/metadata.rs @@ -1149,6 +1149,14 @@ impl TryFrom for TableInfo { } } +/// Set column fulltext options if it passed the validation. +/// +/// Options allowed to modify: +/// * backend +/// +/// Options not allowed to modify: +/// * analyzer +/// * case_sensitive fn set_column_fulltext_options( column_schema: &mut ColumnSchema, column_name: &str, @@ -1156,14 +1164,6 @@ fn set_column_fulltext_options( current_options: Option, ) -> Result<()> { if let Some(current_options) = current_options { - ensure!( - !current_options.enable, - error::InvalidColumnOptionSnafu { - column_name, - msg: "FULLTEXT index already enabled", - } - ); - ensure!( current_options.analyzer == options.analyzer && current_options.case_sensitive == options.case_sensitive, diff --git a/src/table/src/requests.rs b/src/table/src/requests.rs index 5b7ad566c5..75a4ab64d6 100644 --- a/src/table/src/requests.rs +++ b/src/table/src/requests.rs @@ -99,7 +99,7 @@ pub const TTL_KEY: &str = store_api::mito_engine_options::TTL_KEY; pub const STORAGE_KEY: &str = "storage"; pub const COMMENT_KEY: &str = "comment"; pub const AUTO_CREATE_TABLE_KEY: &str = "auto_create_table"; -pub const SKIP_WAL_KEY: &str = "skip_wal"; +pub const SKIP_WAL_KEY: &str = store_api::mito_engine_options::SKIP_WAL_KEY; impl TableOptions { pub fn try_from_iter>( diff --git a/src/table/src/table/adapter.rs b/src/table/src/table/adapter.rs index 2cf9a9647f..4ba880b1eb 100644 --- a/src/table/src/table/adapter.rs +++ b/src/table/src/table/adapter.rs @@ -95,7 +95,7 @@ impl TableProvider for DfTableProviderAdapter { filters: &[Expr], limit: Option, ) -> DfResult> { - let filters: Vec = filters.iter().map(Clone::clone).map(Into::into).collect(); + let filters: Vec = filters.iter().map(Clone::clone).collect(); let request = { let mut request = self.scan_req.lock().unwrap(); request.filters = filters; diff --git a/src/table/src/table_name.rs b/src/table/src/table_name.rs index f999e013f2..d2d1c1e48b 100644 --- a/src/table/src/table_name.rs +++ b/src/table/src/table_name.rs @@ -15,8 +15,12 @@ use std::fmt::{Display, Formatter}; use api::v1::TableName as PbTableName; +use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; use serde::{Deserialize, Serialize}; +use snafu::ensure; +use crate::error; +use crate::error::InvalidTableNameSnafu; use crate::table_reference::TableReference; #[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize, Serialize)] @@ -83,3 +87,37 @@ impl From> for TableName { Self::new(table_ref.catalog, table_ref.schema, table_ref.table) } } + +impl TryFrom> for TableName { + type Error = error::Error; + + fn try_from(v: Vec) -> Result { + ensure!( + !v.is_empty() && v.len() <= 3, + InvalidTableNameSnafu { + s: format!("{v:?}") + } + ); + let mut v = v.into_iter(); + match (v.next(), v.next(), v.next()) { + (Some(catalog_name), Some(schema_name), Some(table_name)) => Ok(Self { + catalog_name, + schema_name, + table_name, + }), + (Some(schema_name), Some(table_name), None) => Ok(Self { + catalog_name: DEFAULT_CATALOG_NAME.to_string(), + schema_name, + table_name, + }), + (Some(table_name), None, None) => Ok(Self { + catalog_name: DEFAULT_CATALOG_NAME.to_string(), + schema_name: DEFAULT_SCHEMA_NAME.to_string(), + table_name, + }), + // Unreachable because it's ensured that "v" is not empty, + // and its iterator will not yield `Some` after `None`. + _ => unreachable!(), + } + } +} diff --git a/tests-fuzz/src/utils.rs b/tests-fuzz/src/utils.rs index f52d75f4da..ec7f1d8b27 100644 --- a/tests-fuzz/src/utils.rs +++ b/tests-fuzz/src/utils.rs @@ -85,11 +85,7 @@ pub struct UnstableTestVariables { pub fn load_unstable_test_env_variables() -> UnstableTestVariables { let _ = dotenv::dotenv(); let binary_path = env::var(GT_FUZZ_BINARY_PATH).expect("GT_FUZZ_BINARY_PATH not found"); - let root_dir = if let Ok(root) = env::var(GT_FUZZ_INSTANCE_ROOT_DIR) { - Some(root) - } else { - None - }; + let root_dir = env::var(GT_FUZZ_INSTANCE_ROOT_DIR).ok(); UnstableTestVariables { binary_path, diff --git a/tests-fuzz/targets/unstable/fuzz_create_table_standalone.rs b/tests-fuzz/targets/unstable/fuzz_create_table_standalone.rs index 575659ab8e..53369e9792 100644 --- a/tests-fuzz/targets/unstable/fuzz_create_table_standalone.rs +++ b/tests-fuzz/targets/unstable/fuzz_create_table_standalone.rs @@ -157,7 +157,7 @@ async fn execute_unstable_create_table( } Err(err) => { // FIXME(weny): support to retry it later. - if matches!(err, sqlx::Error::PoolTimedOut { .. }) { + if matches!(err, sqlx::Error::PoolTimedOut) { warn!("ignore pool timeout, sql: {sql}"); continue; } diff --git a/tests-integration/Cargo.toml b/tests-integration/Cargo.toml index 0ce38049ca..f2f538f528 100644 --- a/tests-integration/Cargo.toml +++ b/tests-integration/Cargo.toml @@ -100,3 +100,4 @@ session = { workspace = true, features = ["testing"] } store-api.workspace = true tokio-postgres = { workspace = true } url = "2.3" +yaml-rust = "0.4" diff --git a/tests-integration/src/cluster.rs b/tests-integration/src/cluster.rs index c1159e18d5..0b18a71d3a 100644 --- a/tests-integration/src/cluster.rs +++ b/tests-integration/src/cluster.rs @@ -67,13 +67,12 @@ use tower::service_fn; use uuid::Uuid; use crate::test_util::{ - self, create_datanode_opts, create_tmp_dir_and_datanode_opts, FileDirGuard, StorageGuard, - StorageType, PEER_PLACEHOLDER_ADDR, + self, create_datanode_opts, create_tmp_dir_and_datanode_opts, FileDirGuard, StorageType, + TestGuard, PEER_PLACEHOLDER_ADDR, }; pub struct GreptimeDbCluster { - pub storage_guards: Vec, - pub dir_guards: Vec, + pub guards: Vec, pub datanode_options: Vec, pub datanode_instances: HashMap, @@ -177,8 +176,7 @@ impl GreptimeDbClusterBuilder { pub async fn build_with( &self, datanode_options: Vec, - storage_guards: Vec, - dir_guards: Vec, + guards: Vec, ) -> GreptimeDbCluster { let datanodes = datanode_options.len(); let channel_config = ChannelConfig::new().timeout(Duration::from_secs(20)); @@ -224,8 +222,7 @@ impl GreptimeDbClusterBuilder { GreptimeDbCluster { datanode_options, - storage_guards, - dir_guards, + guards, datanode_instances, kv_backend: self.kv_backend.clone(), metasrv: metasrv.metasrv, @@ -235,19 +232,16 @@ impl GreptimeDbClusterBuilder { pub async fn build(&self) -> GreptimeDbCluster { let datanodes = self.datanodes.unwrap_or(4); - let (datanode_options, storage_guards, dir_guards) = - self.build_datanode_options_and_guards(datanodes).await; - self.build_with(datanode_options, storage_guards, dir_guards) - .await + let (datanode_options, guards) = self.build_datanode_options_and_guards(datanodes).await; + self.build_with(datanode_options, guards).await } async fn build_datanode_options_and_guards( &self, datanodes: u32, - ) -> (Vec, Vec, Vec) { + ) -> (Vec, Vec) { let mut options = Vec::with_capacity(datanodes as usize); - let mut storage_guards = Vec::with_capacity(datanodes as usize); - let mut dir_guards = Vec::with_capacity(datanodes as usize); + let mut guards = Vec::with_capacity(datanodes as usize); for i in 0..datanodes { let datanode_id = i as u64 + 1; @@ -257,7 +251,10 @@ impl GreptimeDbClusterBuilder { } else { let home_tmp_dir = create_temp_dir(&format!("gt_home_{}", &self.cluster_name)); let home_dir = home_tmp_dir.path().to_str().unwrap().to_string(); - dir_guards.push(FileDirGuard::new(home_tmp_dir)); + guards.push(TestGuard { + home_guard: FileDirGuard::new(home_tmp_dir), + storage_guards: Vec::new(), + }); home_dir }; @@ -275,9 +272,7 @@ impl GreptimeDbClusterBuilder { &format!("{}-dn-{}", self.cluster_name, datanode_id), self.datanode_wal_config.clone(), ); - - storage_guards.push(guard.storage_guards); - dir_guards.push(guard.home_guard); + guards.push(guard); opts }; @@ -285,11 +280,7 @@ impl GreptimeDbClusterBuilder { options.push(opts); } - ( - options, - storage_guards.into_iter().flatten().collect(), - dir_guards, - ) + (options, guards) } async fn build_datanodes_with_options( @@ -498,10 +489,7 @@ async fn create_datanode_client(datanode: &Datanode) -> (String, Client) { if let Some(client) = client { Ok(TokioIo::new(client)) } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Client already taken", - )) + Err(std::io::Error::other("Client already taken")) } } }), diff --git a/tests-integration/src/grpc.rs b/tests-integration/src/grpc.rs index 501c41d0c8..d09bbc3761 100644 --- a/tests-integration/src/grpc.rs +++ b/tests-integration/src/grpc.rs @@ -12,6 +12,33 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod flight; + +use api::v1::greptime_request::Request; +use api::v1::query_request::Query; +use api::v1::QueryRequest; +use common_query::OutputData; +use common_recordbatch::RecordBatches; +use frontend::instance::Instance; +use servers::query_handler::grpc::GrpcQueryHandler; +use session::context::QueryContext; + +#[allow(unused)] +async fn query_and_expect(instance: &Instance, sql: &str, expected: &str) { + let request = Request::Query(QueryRequest { + query: Some(Query::Sql(sql.to_string())), + }); + let output = GrpcQueryHandler::do_query(instance, request, QueryContext::arc()) + .await + .unwrap(); + let OutputData::Stream(stream) = output.data else { + unreachable!() + }; + let recordbatches = RecordBatches::try_collect(stream).await.unwrap(); + let actual = recordbatches.pretty_print().unwrap(); + assert_eq!(actual, expected, "actual: {}", actual); +} + #[cfg(test)] mod test { use std::collections::HashMap; @@ -41,6 +68,7 @@ mod test { use store_api::storage::RegionId; use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan}; + use super::*; use crate::standalone::GreptimeDbStandaloneBuilder; use crate::tests; use crate::tests::MockDistributedInstance; @@ -219,24 +247,14 @@ mod test { let output = query(instance, request).await; assert!(matches!(output.data, OutputData::AffectedRows(1))); - let request = Request::Query(QueryRequest { - query: Some(Query::Sql( - "SELECT ts, a, b FROM database_created_through_grpc.table_created_through_grpc" - .to_string(), - )), - }); - let output = query(instance, request).await; - let OutputData::Stream(stream) = output.data else { - unreachable!() - }; - let recordbatches = RecordBatches::try_collect(stream).await.unwrap(); + let sql = "SELECT ts, a, b FROM database_created_through_grpc.table_created_through_grpc"; let expected = "\ +---------------------+---+---+ | ts | a | b | +---------------------+---+---+ | 2023-01-04T07:14:26 | s | 1 | +---------------------+---+---+"; - assert_eq!(recordbatches.pretty_print().unwrap(), expected); + query_and_expect(instance, sql, expected).await; let request = Request::Ddl(DdlRequest { expr: Some(DdlExpr::DropTable(DropTableExpr { @@ -323,24 +341,14 @@ mod test { let output = query(instance, request).await; assert!(matches!(output.data, OutputData::AffectedRows(1))); - let request = Request::Query(QueryRequest { - query: Some(Query::Sql( - "SELECT ts, a, b FROM database_created_through_grpc.table_created_through_grpc" - .to_string(), - )), - }); - let output = query(instance, request).await; - let OutputData::Stream(stream) = output.data else { - unreachable!() - }; - let recordbatches = RecordBatches::try_collect(stream).await.unwrap(); + let sql = "SELECT ts, a, b FROM database_created_through_grpc.table_created_through_grpc"; let expected = "\ +---------------------+---+---+ | ts | a | b | +---------------------+---+---+ | 2023-01-04T07:14:26 | s | 1 | +---------------------+---+---+"; - assert_eq!(recordbatches.pretty_print().unwrap(), expected); + query_and_expect(instance, sql, expected).await; let request = Request::Ddl(DdlRequest { expr: Some(DdlExpr::DropTable(DropTableExpr { diff --git a/tests-integration/src/grpc/flight.rs b/tests-integration/src/grpc/flight.rs new file mode 100644 index 0000000000..e97165f16c --- /dev/null +++ b/tests-integration/src/grpc/flight.rs @@ -0,0 +1,242 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(test)] +mod test { + use std::net::SocketAddr; + use std::sync::Arc; + use std::time::Duration; + + use api::v1::auth_header::AuthScheme; + use api::v1::{Basic, ColumnDataType, ColumnDef, CreateTableExpr, SemanticType}; + use arrow_flight::FlightDescriptor; + use auth::user_provider_from_option; + use client::{Client, Database}; + use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; + use common_grpc::flight::do_put::DoPutMetadata; + use common_grpc::flight::{FlightEncoder, FlightMessage}; + use common_query::OutputData; + use common_recordbatch::RecordBatch; + use datatypes::prelude::{ConcreteDataType, ScalarVector, VectorRef}; + use datatypes::schema::{ColumnSchema, Schema}; + use datatypes::vectors::{Int32Vector, StringVector, TimestampMillisecondVector}; + use futures_util::StreamExt; + use itertools::Itertools; + use servers::grpc::builder::GrpcServerBuilder; + use servers::grpc::greptime_handler::GreptimeRequestHandler; + use servers::grpc::GrpcServerConfig; + use servers::query_handler::grpc::ServerGrpcQueryHandlerAdapter; + use servers::server::Server; + + use crate::cluster::GreptimeDbClusterBuilder; + use crate::grpc::query_and_expect; + use crate::test_util::{setup_grpc_server, StorageType}; + use crate::tests::test_util::MockInstance; + + #[tokio::test(flavor = "multi_thread")] + async fn test_standalone_flight_do_put() { + common_telemetry::init_default_ut_logging(); + + let (addr, db, _server) = + setup_grpc_server(StorageType::File, "test_standalone_flight_do_put").await; + + let client = Client::with_urls(vec![addr]); + let client = Database::new_with_dbname("greptime-public", client); + + create_table(&client).await; + + let record_batches = create_record_batches(1); + test_put_record_batches(&client, record_batches).await; + + let sql = "select ts, a, b from foo order by ts"; + let expected = "\ +++ +++"; + query_and_expect(db.frontend().as_ref(), sql, expected).await; + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_distributed_flight_do_put() { + common_telemetry::init_default_ut_logging(); + + let db = GreptimeDbClusterBuilder::new("test_distributed_flight_do_put") + .await + .build() + .await; + + let runtime = common_runtime::global_runtime().clone(); + let greptime_request_handler = GreptimeRequestHandler::new( + ServerGrpcQueryHandlerAdapter::arc(db.frontend.instance.clone()), + user_provider_from_option( + &"static_user_provider:cmd:greptime_user=greptime_pwd".to_string(), + ) + .ok(), + Some(runtime.clone()), + ); + let grpc_server = GrpcServerBuilder::new(GrpcServerConfig::default(), runtime) + .flight_handler(Arc::new(greptime_request_handler)) + .build(); + let addr = grpc_server + .start("127.0.0.1:0".parse::().unwrap()) + .await + .unwrap() + .to_string(); + + // wait for GRPC server to start + tokio::time::sleep(Duration::from_secs(1)).await; + + let client = Client::with_urls(vec![addr]); + let mut client = Database::new(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, client); + client.set_auth(AuthScheme::Basic(Basic { + username: "greptime_user".to_string(), + password: "greptime_pwd".to_string(), + })); + + create_table(&client).await; + + let record_batches = create_record_batches(1); + test_put_record_batches(&client, record_batches).await; + + let sql = "select ts, a, b from foo order by ts"; + let expected = "\ +++ +++"; + query_and_expect(db.fe_instance().as_ref(), sql, expected).await; + } + + async fn test_put_record_batches(client: &Database, record_batches: Vec) { + let requests_count = record_batches.len(); + + let stream = tokio_stream::iter(record_batches) + .enumerate() + .map(|(i, x)| { + let mut encoder = FlightEncoder::default(); + let message = FlightMessage::Recordbatch(x); + let mut data = encoder.encode(message); + + let metadata = DoPutMetadata::new(i as i64); + data.app_metadata = serde_json::to_vec(&metadata).unwrap().into(); + + // first message in "DoPut" stream should carry table name in flight descriptor + if i == 0 { + data.flight_descriptor = Some(FlightDescriptor { + r#type: arrow_flight::flight_descriptor::DescriptorType::Path as i32, + path: vec!["foo".to_string()], + ..Default::default() + }); + } + data + }) + .boxed(); + + let response_stream = client.do_put(stream).await.unwrap(); + + let responses = response_stream.collect::>().await; + let responses_count = responses.len(); + for (i, response) in responses.into_iter().enumerate() { + assert!(response.is_ok(), "{}", response.err().unwrap()); + let response = response.unwrap(); + assert_eq!(response.request_id(), i as i64); + assert_eq!(response.affected_rows(), 448); + } + assert_eq!(requests_count, responses_count); + } + + fn create_record_batches(start: i64) -> Vec { + let schema = Arc::new(Schema::new(vec![ + ColumnSchema::new( + "ts", + ConcreteDataType::timestamp_millisecond_datatype(), + false, + ) + .with_time_index(true), + ColumnSchema::new("a", ConcreteDataType::int32_datatype(), false), + ColumnSchema::new("b", ConcreteDataType::string_datatype(), true), + ])); + + let mut record_batches = Vec::with_capacity(3); + for chunk in &(start..start + 9).chunks(3) { + let vs = chunk.collect_vec(); + let x1 = vs[0]; + let x2 = vs[1]; + let x3 = vs[2]; + + record_batches.push( + RecordBatch::new( + schema.clone(), + vec![ + Arc::new(TimestampMillisecondVector::from_vec(vec![x1, x2, x3])) + as VectorRef, + Arc::new(Int32Vector::from_vec(vec![ + -x1 as i32, -x2 as i32, -x3 as i32, + ])), + Arc::new(StringVector::from_vec(vec![ + format!("s{x1}"), + format!("s{x2}"), + format!("s{x3}"), + ])), + ], + ) + .unwrap(), + ); + } + record_batches + } + + async fn create_table(client: &Database) { + // create table foo ( + // ts timestamp time index, + // a int primary key, + // b string, + // ) + let output = client + .create(CreateTableExpr { + schema_name: "public".to_string(), + table_name: "foo".to_string(), + column_defs: vec![ + ColumnDef { + name: "ts".to_string(), + data_type: ColumnDataType::TimestampMillisecond as i32, + semantic_type: SemanticType::Timestamp as i32, + is_nullable: false, + ..Default::default() + }, + ColumnDef { + name: "a".to_string(), + data_type: ColumnDataType::Int32 as i32, + semantic_type: SemanticType::Tag as i32, + is_nullable: false, + ..Default::default() + }, + ColumnDef { + name: "b".to_string(), + data_type: ColumnDataType::String as i32, + semantic_type: SemanticType::Field as i32, + is_nullable: true, + ..Default::default() + }, + ], + time_index: "ts".to_string(), + primary_keys: vec!["a".to_string()], + engine: "mito".to_string(), + ..Default::default() + }) + .await + .unwrap(); + let OutputData::AffectedRows(affected_rows) = output.data else { + unreachable!() + }; + assert_eq!(affected_rows, 0); + } +} diff --git a/tests-integration/src/standalone.rs b/tests-integration/src/standalone.rs index 2d6c9bcf97..e25e2a9682 100644 --- a/tests-integration/src/standalone.rs +++ b/tests-integration/src/standalone.rs @@ -41,7 +41,7 @@ use common_procedure::options::ProcedureConfig; use common_procedure::ProcedureManagerRef; use common_wal::config::{DatanodeWalConfig, MetasrvWalConfig}; use datanode::datanode::DatanodeBuilder; -use flow::FlownodeBuilder; +use flow::{FlownodeBuilder, FrontendClient, GrpcQueryHandlerWithBoxedError}; use frontend::frontend::Frontend; use frontend::instance::builder::FrontendBuilder; use frontend::instance::{Instance, StandaloneDatanodeManager}; @@ -174,18 +174,21 @@ impl GreptimeDbStandaloneBuilder { Some(procedure_manager.clone()), ); + let (frontend_client, frontend_instance_handler) = + FrontendClient::from_empty_grpc_handler(); let flow_builder = FlownodeBuilder::new( Default::default(), plugins.clone(), table_metadata_manager.clone(), catalog_manager.clone(), flow_metadata_manager.clone(), + Arc::new(frontend_client), ); let flownode = Arc::new(flow_builder.build().await.unwrap()); let node_manager = Arc::new(StandaloneDatanodeManager { region_server: datanode.region_server(), - flow_server: flownode.flow_worker_manager(), + flow_server: flownode.flow_engine(), }); let table_id_sequence = Arc::new( @@ -247,9 +250,17 @@ impl GreptimeDbStandaloneBuilder { .unwrap(); let instance = Arc::new(instance); - let flow_worker_manager = flownode.flow_worker_manager(); + // set the frontend client for flownode + let grpc_handler = instance.clone() as Arc; + let weak_grpc_handler = Arc::downgrade(&grpc_handler); + frontend_instance_handler + .lock() + .unwrap() + .replace(weak_grpc_handler); + + let flow_streaming_engine = flownode.flow_engine().streaming_engine(); let invoker = flow::FrontendInvoker::build_from( - flow_worker_manager.clone(), + flow_streaming_engine.clone(), catalog_manager.clone(), kv_backend.clone(), cache_registry.clone(), @@ -260,7 +271,7 @@ impl GreptimeDbStandaloneBuilder { .context(StartFlownodeSnafu) .unwrap(); - flow_worker_manager.set_frontend_invoker(invoker).await; + flow_streaming_engine.set_frontend_invoker(invoker).await; procedure_manager.start().await.unwrap(); wal_options_allocator.start().await.unwrap(); diff --git a/tests-integration/src/test_util.rs b/tests-integration/src/test_util.rs index 4b31b941b6..ee68b8d715 100644 --- a/tests-integration/src/test_util.rs +++ b/tests-integration/src/test_util.rs @@ -299,6 +299,34 @@ impl TestGuard { } } +impl Drop for TestGuard { + fn drop(&mut self) { + let (tx, rx) = std::sync::mpsc::channel(); + + let guards = std::mem::take(&mut self.storage_guards); + common_runtime::spawn_global(async move { + let mut errors = vec![]; + for guard in guards { + if let TempDirGuard::S3(guard) + | TempDirGuard::Oss(guard) + | TempDirGuard::Azblob(guard) + | TempDirGuard::Gcs(guard) = guard.0 + { + if let Err(e) = guard.remove_all().await { + errors.push(e); + } + } + } + if errors.is_empty() { + tx.send(Ok(())).unwrap(); + } else { + tx.send(Err(errors)).unwrap(); + } + }); + rx.recv().unwrap().unwrap_or_else(|e| panic!("{:?}", e)); + } +} + pub fn create_tmp_dir_and_datanode_opts( default_store_type: StorageType, store_provider_types: Vec, @@ -475,6 +503,9 @@ pub async fn setup_test_prom_app_with_frontend( let sql = "INSERT INTO demo_metrics(idc, val, ts) VALUES ('idc1', 1.1, 0), ('idc2', 2.1, 600000)"; run_sql(sql, &instance).await; + // insert a row with empty label + let sql = "INSERT INTO demo_metrics(val, ts) VALUES (1.1, 0)"; + run_sql(sql, &instance).await; // build physical table let sql = "CREATE TABLE phy2 (ts timestamp(9) time index, val double, host string primary key) engine=metric with ('physical_metric_table' = '')"; @@ -484,6 +515,12 @@ pub async fn setup_test_prom_app_with_frontend( let sql = "INSERT INTO demo_metrics_with_nanos(idc, val, ts) VALUES ('idc1', 1.1, 0)"; run_sql(sql, &instance).await; + // a mito table with non-prometheus compatible values + let sql = "CREATE TABLE mito (ts timestamp(9) time index, val double, host bigint primary key) engine=mito"; + run_sql(sql, &instance).await; + let sql = "INSERT INTO mito(host, val, ts) VALUES (1, 1.1, 0)"; + run_sql(sql, &instance).await; + let http_opts = HttpOptions { addr: format!("127.0.0.1:{}", ports::get_port()), ..Default::default() @@ -504,7 +541,7 @@ pub async fn setup_test_prom_app_with_frontend( pub async fn setup_grpc_server( store_type: StorageType, name: &str, -) -> (String, TestGuard, Arc) { +) -> (String, GreptimeDbStandalone, Arc) { setup_grpc_server_with(store_type, name, None, None).await } @@ -512,7 +549,7 @@ pub async fn setup_grpc_server_with_user_provider( store_type: StorageType, name: &str, user_provider: Option, -) -> (String, TestGuard, Arc) { +) -> (String, GreptimeDbStandalone, Arc) { setup_grpc_server_with(store_type, name, user_provider, None).await } @@ -521,7 +558,7 @@ pub async fn setup_grpc_server_with( name: &str, user_provider: Option, grpc_config: Option, -) -> (String, TestGuard, Arc) { +) -> (String, GreptimeDbStandalone, Arc) { let instance = setup_standalone_instance(name, store_type).await; let runtime: Runtime = RuntimeBuilder::default() @@ -560,7 +597,7 @@ pub async fn setup_grpc_server_with( // wait for GRPC server to start tokio::time::sleep(Duration::from_secs(1)).await; - (fe_grpc_addr, instance.guard, fe_grpc_server) + (fe_grpc_addr, instance, fe_grpc_server) } pub async fn setup_mysql_server( diff --git a/tests-integration/src/tests/test_util.rs b/tests-integration/src/tests/test_util.rs index 605ed2c178..d8df68afaa 100644 --- a/tests-integration/src/tests/test_util.rs +++ b/tests-integration/src/tests/test_util.rs @@ -126,17 +126,12 @@ impl MockInstanceBuilder { unreachable!() }; let GreptimeDbCluster { - storage_guards, - dir_guards, + guards, datanode_options, .. } = instance; - MockInstanceImpl::Distributed( - builder - .build_with(datanode_options, storage_guards, dir_guards) - .await, - ) + MockInstanceImpl::Distributed(builder.build_with(datanode_options, guards).await) } } } diff --git a/tests-integration/tests/grpc.rs b/tests-integration/tests/grpc.rs index 11db34acb8..0a7fffa82d 100644 --- a/tests-integration/tests/grpc.rs +++ b/tests-integration/tests/grpc.rs @@ -90,8 +90,7 @@ macro_rules! grpc_tests { } pub async fn test_invalid_dbname(store_type: StorageType) { - let (addr, mut guard, fe_grpc_server) = - setup_grpc_server(store_type, "auto_create_table").await; + let (addr, _db, fe_grpc_server) = setup_grpc_server(store_type, "test_invalid_dbname").await; let grpc_client = Client::with_urls(vec![addr]); let db = Database::new_with_dbname("tom", grpc_client); @@ -115,12 +114,10 @@ pub async fn test_invalid_dbname(store_type: StorageType) { assert!(result.is_err()); let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } pub async fn test_dbname(store_type: StorageType) { - let (addr, mut guard, fe_grpc_server) = - setup_grpc_server(store_type, "auto_create_table").await; + let (addr, _db, fe_grpc_server) = setup_grpc_server(store_type, "test_dbname").await; let grpc_client = Client::with_urls(vec![addr]); let db = Database::new_with_dbname( @@ -129,7 +126,6 @@ pub async fn test_dbname(store_type: StorageType) { ); insert_and_assert(&db).await; let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } pub async fn test_grpc_message_size_ok(store_type: StorageType) { @@ -138,8 +134,8 @@ pub async fn test_grpc_message_size_ok(store_type: StorageType) { max_send_message_size: 1024, ..Default::default() }; - let (addr, mut guard, fe_grpc_server) = - setup_grpc_server_with(store_type, "auto_create_table", None, Some(config)).await; + let (addr, _db, fe_grpc_server) = + setup_grpc_server_with(store_type, "test_grpc_message_size_ok", None, Some(config)).await; let grpc_client = Client::with_urls(vec![addr]); let db = Database::new_with_dbname( @@ -148,7 +144,6 @@ pub async fn test_grpc_message_size_ok(store_type: StorageType) { ); db.sql("show tables;").await.unwrap(); let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } pub async fn test_grpc_zstd_compression(store_type: StorageType) { @@ -158,8 +153,8 @@ pub async fn test_grpc_zstd_compression(store_type: StorageType) { max_send_message_size: 1024, ..Default::default() }; - let (addr, mut guard, fe_grpc_server) = - setup_grpc_server_with(store_type, "auto_create_table", None, Some(config)).await; + let (addr, _db, fe_grpc_server) = + setup_grpc_server_with(store_type, "test_grpc_zstd_compression", None, Some(config)).await; let grpc_client = Client::with_urls(vec![addr]); let db = Database::new_with_dbname( @@ -168,7 +163,6 @@ pub async fn test_grpc_zstd_compression(store_type: StorageType) { ); db.sql("show tables;").await.unwrap(); let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } pub async fn test_grpc_message_size_limit_send(store_type: StorageType) { @@ -177,8 +171,13 @@ pub async fn test_grpc_message_size_limit_send(store_type: StorageType) { max_send_message_size: 50, ..Default::default() }; - let (addr, mut guard, fe_grpc_server) = - setup_grpc_server_with(store_type, "auto_create_table", None, Some(config)).await; + let (addr, _db, fe_grpc_server) = setup_grpc_server_with( + store_type, + "test_grpc_message_size_limit_send", + None, + Some(config), + ) + .await; let grpc_client = Client::with_urls(vec![addr]); let db = Database::new_with_dbname( @@ -188,7 +187,6 @@ pub async fn test_grpc_message_size_limit_send(store_type: StorageType) { let err_msg = db.sql("show tables;").await.unwrap_err().to_string(); assert!(err_msg.contains("message length too large"), "{}", err_msg); let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } pub async fn test_grpc_message_size_limit_recv(store_type: StorageType) { @@ -197,8 +195,13 @@ pub async fn test_grpc_message_size_limit_recv(store_type: StorageType) { max_send_message_size: 1024, ..Default::default() }; - let (addr, mut guard, fe_grpc_server) = - setup_grpc_server_with(store_type, "auto_create_table", None, Some(config)).await; + let (addr, _db, fe_grpc_server) = setup_grpc_server_with( + store_type, + "test_grpc_message_size_limit_recv", + None, + Some(config), + ) + .await; let grpc_client = Client::with_urls(vec![addr]); let db = Database::new_with_dbname( @@ -212,7 +215,6 @@ pub async fn test_grpc_message_size_limit_recv(store_type: StorageType) { err_msg ); let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } pub async fn test_grpc_auth(store_type: StorageType) { @@ -220,7 +222,7 @@ pub async fn test_grpc_auth(store_type: StorageType) { &"static_user_provider:cmd:greptime_user=greptime_pwd".to_string(), ) .unwrap(); - let (addr, mut guard, fe_grpc_server) = + let (addr, _db, fe_grpc_server) = setup_grpc_server_with_user_provider(store_type, "auto_create_table", Some(user_provider)) .await; @@ -265,29 +267,25 @@ pub async fn test_grpc_auth(store_type: StorageType) { assert!(re.is_ok()); let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } pub async fn test_auto_create_table(store_type: StorageType) { - let (addr, mut guard, fe_grpc_server) = - setup_grpc_server(store_type, "auto_create_table").await; + let (addr, _db, fe_grpc_server) = setup_grpc_server(store_type, "test_auto_create_table").await; let grpc_client = Client::with_urls(vec![addr]); let db = Database::new(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, grpc_client); insert_and_assert(&db).await; let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } pub async fn test_auto_create_table_with_hints(store_type: StorageType) { - let (addr, mut guard, fe_grpc_server) = + let (addr, _db, fe_grpc_server) = setup_grpc_server(store_type, "auto_create_table_with_hints").await; let grpc_client = Client::with_urls(vec![addr]); let db = Database::new(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, grpc_client); insert_with_hints_and_assert(&db).await; let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } fn expect_data() -> (Column, Column, Column, Column) { @@ -348,8 +346,7 @@ fn expect_data() -> (Column, Column, Column, Column) { pub async fn test_insert_and_select(store_type: StorageType) { common_telemetry::init_default_ut_logging(); - let (addr, mut guard, fe_grpc_server) = - setup_grpc_server(store_type, "insert_and_select").await; + let (addr, _db, fe_grpc_server) = setup_grpc_server(store_type, "test_insert_and_select").await; let grpc_client = Client::with_urls(vec![addr]); let db = Database::new(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, grpc_client); @@ -388,7 +385,6 @@ pub async fn test_insert_and_select(store_type: StorageType) { insert_and_assert(&db).await; let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } async fn insert_with_hints_and_assert(db: &Database) { @@ -591,21 +587,20 @@ fn testing_create_expr() -> CreateTableExpr { } pub async fn test_health_check(store_type: StorageType) { - let (addr, mut guard, fe_grpc_server) = - setup_grpc_server(store_type, "auto_create_table").await; + let (addr, _db, fe_grpc_server) = setup_grpc_server(store_type, "test_health_check").await; let grpc_client = Client::with_urls(vec![addr]); grpc_client.health_check().await.unwrap(); let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } pub async fn test_prom_gateway_query(store_type: StorageType) { common_telemetry::init_default_ut_logging(); // prepare connection - let (addr, mut guard, fe_grpc_server) = setup_grpc_server(store_type, "prom_gateway").await; + let (addr, _db, fe_grpc_server) = + setup_grpc_server(store_type, "test_prom_gateway_query").await; let grpc_client = Client::with_urls(vec![addr]); let db = Database::new( DEFAULT_CATALOG_NAME, @@ -772,7 +767,6 @@ pub async fn test_prom_gateway_query(store_type: StorageType) { // clean up let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } pub async fn test_grpc_timezone(store_type: StorageType) { @@ -781,7 +775,7 @@ pub async fn test_grpc_timezone(store_type: StorageType) { max_send_message_size: 1024, ..Default::default() }; - let (addr, mut guard, fe_grpc_server) = + let (addr, _db, fe_grpc_server) = setup_grpc_server_with(store_type, "auto_create_table", None, Some(config)).await; let grpc_client = Client::with_urls(vec![addr]); @@ -824,7 +818,6 @@ pub async fn test_grpc_timezone(store_type: StorageType) { +-----------+" ); let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } async fn to_batch(output: Output) -> String { @@ -856,7 +849,7 @@ pub async fn test_grpc_tls_config(store_type: StorageType) { max_send_message_size: 1024, tls, }; - let (addr, mut guard, fe_grpc_server) = + let (addr, _db, fe_grpc_server) = setup_grpc_server_with(store_type, "tls_create_table", None, Some(config)).await; let mut client_tls = ClientTlsOption { @@ -902,5 +895,4 @@ pub async fn test_grpc_tls_config(store_type: StorageType) { } let _ = fe_grpc_server.shutdown().await; - guard.remove_all().await; } diff --git a/tests-integration/tests/http.rs b/tests-integration/tests/http.rs index b4690cf9e1..2c663e99b9 100644 --- a/tests-integration/tests/http.rs +++ b/tests-integration/tests/http.rs @@ -50,6 +50,7 @@ use tests_integration::test_util::{ setup_test_http_app_with_frontend_and_user_provider, setup_test_prom_app_with_frontend, StorageType, }; +use yaml_rust::YamlLoader; #[macro_export] macro_rules! http_test { @@ -95,9 +96,9 @@ macro_rules! http_tests { test_pipeline_api, test_test_pipeline_api, test_plain_text_ingestion, - test_identify_pipeline, - test_identify_pipeline_with_flatten, - test_identify_pipeline_with_custom_ts, + test_identity_pipeline, + test_identity_pipeline_with_flatten, + test_identity_pipeline_with_custom_ts, test_pipeline_dispatcher, test_pipeline_suffix_template, @@ -482,7 +483,7 @@ pub async fn test_sql_api(store_type: StorageType) { } pub async fn test_prometheus_promql_api(store_type: StorageType) { - let (app, mut guard) = setup_test_http_app_with_frontend(store_type, "sql_api").await; + let (app, mut guard) = setup_test_http_app_with_frontend(store_type, "promql_api").await; let client = TestClient::new(app).await; let res = client @@ -491,7 +492,18 @@ pub async fn test_prometheus_promql_api(store_type: StorageType) { .await; assert_eq!(res.status(), StatusCode::OK); - let _body = serde_json::from_str::(&res.text().await).unwrap(); + let json_text = res.text().await; + assert!(serde_json::from_str::(&json_text).is_ok()); + + let res = client + .get("/v1/promql?query=1&start=0&end=100&step=5s&format=csv") + .send() + .await; + assert_eq!(res.status(), StatusCode::OK); + + let csv_body = &res.text().await; + assert_eq!("0,1.0\n5000,1.0\n10000,1.0\n15000,1.0\n20000,1.0\n25000,1.0\n30000,1.0\n35000,1.0\n40000,1.0\n45000,1.0\n50000,1.0\n55000,1.0\n60000,1.0\n65000,1.0\n70000,1.0\n75000,1.0\n80000,1.0\n85000,1.0\n90000,1.0\n95000,1.0\n100000,1.0\n", csv_body); + guard.remove_all().await; } @@ -764,6 +776,13 @@ pub async fn test_prom_http_api(store_type: StorageType) { assert!(prom_resp.error.is_none()); assert!(prom_resp.error_type.is_none()); + // query non-string value + let res = client + .get("/v1/prometheus/api/v1/label/host/values?match[]=mito") + .send() + .await; + assert_eq!(res.status(), StatusCode::OK); + // query `__name__` without match[] // create a physical table and a logical table let res = client @@ -793,6 +812,7 @@ pub async fn test_prom_http_api(store_type: StorageType) { "demo_metrics".to_string(), "demo_metrics_with_nanos".to_string(), "logic_table".to_string(), + "mito".to_string(), "numbers".to_string() ]) ); @@ -1348,6 +1368,55 @@ transform: .as_str() .unwrap() .to_string(); + let encoded_ver_str: String = + url::form_urlencoded::byte_serialize(version_str.as_bytes()).collect(); + + // get pipeline + let res = client + .get(format!("/v1/pipelines/test?version={}", encoded_ver_str).as_str()) + .send() + .await; + + assert_eq!(res.status(), StatusCode::OK); + + let content = res.text().await; + let content = serde_json::from_str(&content); + let content: Value = content.unwrap(); + let pipeline_yaml = content + .get("pipelines") + .unwrap() + .as_array() + .unwrap() + .first() + .unwrap() + .get("pipeline") + .unwrap() + .as_str() + .unwrap(); + let docs = YamlLoader::load_from_str(pipeline_yaml).unwrap(); + let body_yaml = YamlLoader::load_from_str(body).unwrap(); + assert_eq!(docs, body_yaml); + + // Do not specify version, get the latest version + let res = client.get("/v1/pipelines/test").send().await; + assert_eq!(res.status(), StatusCode::OK); + + let content = res.text().await; + let content = serde_json::from_str(&content); + let content: Value = content.unwrap(); + let pipeline_yaml = content + .get("pipelines") + .unwrap() + .as_array() + .unwrap() + .first() + .unwrap() + .get("pipeline") + .unwrap() + .as_str() + .unwrap(); + let docs = YamlLoader::load_from_str(pipeline_yaml).unwrap(); + assert_eq!(docs, body_yaml); // 2. write data let data_body = r#" @@ -1371,7 +1440,7 @@ transform: assert_eq!(res.status(), StatusCode::OK); // 3. check schema - let expected_schema = "[[\"logs1\",\"CREATE TABLE IF NOT EXISTS \\\"logs1\\\" (\\n \\\"id1\\\" INT NULL INVERTED INDEX,\\n \\\"id2\\\" INT NULL INVERTED INDEX,\\n \\\"logger\\\" STRING NULL,\\n \\\"type\\\" STRING NULL SKIPPING INDEX WITH(granularity = '10240', type = 'BLOOM'),\\n \\\"log\\\" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', case_sensitive = 'false'),\\n \\\"time\\\" TIMESTAMP(9) NOT NULL,\\n TIME INDEX (\\\"time\\\"),\\n PRIMARY KEY (\\\"type\\\", \\\"log\\\")\\n)\\n\\nENGINE=mito\\nWITH(\\n append_mode = 'true'\\n)\"]]"; + let expected_schema = "[[\"logs1\",\"CREATE TABLE IF NOT EXISTS \\\"logs1\\\" (\\n \\\"id1\\\" INT NULL INVERTED INDEX,\\n \\\"id2\\\" INT NULL INVERTED INDEX,\\n \\\"logger\\\" STRING NULL,\\n \\\"type\\\" STRING NULL SKIPPING INDEX WITH(granularity = '10240', type = 'BLOOM'),\\n \\\"log\\\" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'false'),\\n \\\"time\\\" TIMESTAMP(9) NOT NULL,\\n TIME INDEX (\\\"time\\\"),\\n PRIMARY KEY (\\\"type\\\", \\\"log\\\")\\n)\\n\\nENGINE=mito\\nWITH(\\n append_mode = 'true'\\n)\"]]"; validate_data( "pipeline_schema", &client, @@ -1413,15 +1482,15 @@ transform: guard.remove_all().await; } -pub async fn test_identify_pipeline(store_type: StorageType) { +pub async fn test_identity_pipeline(store_type: StorageType) { common_telemetry::init_default_ut_logging(); let (app, mut guard) = - setup_test_http_app_with_frontend(store_type, "test_identify_pipeline").await; + setup_test_http_app_with_frontend(store_type, "test_identity_pipeline").await; // handshake let client = TestClient::new(app).await; - let body = r#"{"__time__":1453809242,"__topic__":"","__source__":"10.170.***.***","ip":"10.200.**.***","time":"26/Jan/2016:19:54:02 +0800","url":"POST/PutData?Category=YunOsAccountOpLog&AccessKeyId=&Date=Fri%2C%2028%20Jun%202013%2006%3A53%3A30%20GMT&Topic=raw&Signature=HTTP/1.1","status":"200","user-agent":"aliyun-sdk-java"} -{"__time__":1453809242,"__topic__":"","__source__":"10.170.***.***","ip":"10.200.**.***","time":"26/Jan/2016:19:54:02 +0800","url":"POST/PutData?Category=YunOsAccountOpLog&AccessKeyId=&Date=Fri%2C%2028%20Jun%202013%2006%3A53%3A30%20GMT&Topic=raw&Signature=HTTP/1.1","status":"200","user-agent":"aliyun-sdk-java","hasagei":"hasagei","dongdongdong":"guaguagua"}"#; + let body = r#"{"__time__":1453809242,"__topic__":"","__source__":"10.170.***.***","ip":"10.200.**.***","time":"26/Jan/2016:19:54:02 +0800","url":"POST/PutData?Category=YunOsAccountOpLog&AccessKeyId=&Date=Fri%2C%2028%20Jun%202013%2006%3A53%3A30%20GMT&Topic=raw&Signature=HTTP/1.1","status":"200","user-agent":"aliyun-sdk-java", "json_object": {"a":1,"b":2}, "json_array":[1,2,3]} +{"__time__":1453809242,"__topic__":"","__source__":"10.170.***.***","ip":"10.200.**.***","time":"26/Jan/2016:19:54:02 +0800","url":"POST/PutData?Category=YunOsAccountOpLog&AccessKeyId=&Date=Fri%2C%2028%20Jun%202013%2006%3A53%3A30%20GMT&Topic=raw&Signature=HTTP/1.1","status":"200","user-agent":"aliyun-sdk-java","hasagei":"hasagei","dongdongdong":"guaguagua", "json_object": {"a":1,"b":2}, "json_array":[1,2,3]}"#; let res = client .post("/v1/ingest?db=public&table=logs&pipeline_name=greptime_identity") .header("Content-Type", "application/json") @@ -1440,8 +1509,8 @@ pub async fn test_identify_pipeline(store_type: StorageType) { assert_eq!(res.status(), StatusCode::OK); - let line1_expected = r#"[null,"10.170.***.***",1453809242,"","10.200.**.***","200","26/Jan/2016:19:54:02 +0800","POST/PutData?Category=YunOsAccountOpLog&AccessKeyId=&Date=Fri%2C%2028%20Jun%202013%2006%3A53%3A30%20GMT&Topic=raw&Signature=HTTP/1.1","aliyun-sdk-java",null,null]"#; - let line2_expected = r#"[null,"10.170.***.***",1453809242,"","10.200.**.***","200","26/Jan/2016:19:54:02 +0800","POST/PutData?Category=YunOsAccountOpLog&AccessKeyId=&Date=Fri%2C%2028%20Jun%202013%2006%3A53%3A30%20GMT&Topic=raw&Signature=HTTP/1.1","aliyun-sdk-java","guaguagua","hasagei"]"#; + let line1_expected = r#"[null,"10.170.***.***",1453809242,"","10.200.**.***",[1,2,3],{"a":1,"b":2},"200","26/Jan/2016:19:54:02 +0800","POST/PutData?Category=YunOsAccountOpLog&AccessKeyId=&Date=Fri%2C%2028%20Jun%202013%2006%3A53%3A30%20GMT&Topic=raw&Signature=HTTP/1.1","aliyun-sdk-java",null,null]"#; + let line2_expected = r#"[null,"10.170.***.***",1453809242,"","10.200.**.***",[1,2,3],{"a":1,"b":2},"200","26/Jan/2016:19:54:02 +0800","POST/PutData?Category=YunOsAccountOpLog&AccessKeyId=&Date=Fri%2C%2028%20Jun%202013%2006%3A53%3A30%20GMT&Topic=raw&Signature=HTTP/1.1","aliyun-sdk-java","guaguagua","hasagei"]"#; let res = client.get("/v1/sql?sql=select * from logs").send().await; assert_eq!(res.status(), StatusCode::OK); let resp: serde_json::Value = res.json().await; @@ -1464,7 +1533,7 @@ pub async fn test_identify_pipeline(store_type: StorageType) { serde_json::from_str::>(line2_expected).unwrap() ); - let expected = r#"[["greptime_timestamp","TimestampNanosecond","PRI","NO","","TIMESTAMP"],["__source__","String","","YES","","FIELD"],["__time__","Int64","","YES","","FIELD"],["__topic__","String","","YES","","FIELD"],["ip","String","","YES","","FIELD"],["status","String","","YES","","FIELD"],["time","String","","YES","","FIELD"],["url","String","","YES","","FIELD"],["user-agent","String","","YES","","FIELD"],["dongdongdong","String","","YES","","FIELD"],["hasagei","String","","YES","","FIELD"]]"#; + let expected = r#"[["greptime_timestamp","TimestampNanosecond","PRI","NO","","TIMESTAMP"],["__source__","String","","YES","","FIELD"],["__time__","Int64","","YES","","FIELD"],["__topic__","String","","YES","","FIELD"],["ip","String","","YES","","FIELD"],["json_array","Json","","YES","","FIELD"],["json_object","Json","","YES","","FIELD"],["status","String","","YES","","FIELD"],["time","String","","YES","","FIELD"],["url","String","","YES","","FIELD"],["user-agent","String","","YES","","FIELD"],["dongdongdong","String","","YES","","FIELD"],["hasagei","String","","YES","","FIELD"]]"#; validate_data("identity_schema", &client, "desc logs", expected).await; guard.remove_all().await; @@ -1792,10 +1861,10 @@ table_suffix: _${type} guard.remove_all().await; } -pub async fn test_identify_pipeline_with_flatten(store_type: StorageType) { +pub async fn test_identity_pipeline_with_flatten(store_type: StorageType) { common_telemetry::init_default_ut_logging(); let (app, mut guard) = - setup_test_http_app_with_frontend(store_type, "test_identify_pipeline_with_flatten").await; + setup_test_http_app_with_frontend(store_type, "test_identity_pipeline_with_flatten").await; let client = TestClient::new(app).await; let body = r#"{"__time__":1453809242,"__topic__":"","__source__":"10.170.***.***","ip":"10.200.**.***","time":"26/Jan/2016:19:54:02 +0800","url":"POST/PutData?Category=YunOsAccountOpLog&AccessKeyId=&Date=Fri%2C%2028%20Jun%202013%2006%3A53%3A30%20GMT&Topic=raw&Signature=HTTP/1.1","status":"200","user-agent":"aliyun-sdk-java","custom_map":{"value_a":["a","b","c"],"value_b":"b"}}"#; @@ -1822,7 +1891,7 @@ pub async fn test_identify_pipeline_with_flatten(store_type: StorageType) { let expected = r#"[["greptime_timestamp","TimestampNanosecond","PRI","NO","","TIMESTAMP"],["__source__","String","","YES","","FIELD"],["__time__","Int64","","YES","","FIELD"],["__topic__","String","","YES","","FIELD"],["custom_map.value_a","Json","","YES","","FIELD"],["custom_map.value_b","String","","YES","","FIELD"],["ip","String","","YES","","FIELD"],["status","String","","YES","","FIELD"],["time","String","","YES","","FIELD"],["url","String","","YES","","FIELD"],["user-agent","String","","YES","","FIELD"]]"#; validate_data( - "test_identify_pipeline_with_flatten_desc_logs", + "test_identity_pipeline_with_flatten_desc_logs", &client, "desc logs", expected, @@ -1831,7 +1900,7 @@ pub async fn test_identify_pipeline_with_flatten(store_type: StorageType) { let expected = "[[[\"a\",\"b\",\"c\"]]]"; validate_data( - "test_identify_pipeline_with_flatten_select_json", + "test_identity_pipeline_with_flatten_select_json", &client, "select `custom_map.value_a` from logs", expected, @@ -1841,10 +1910,10 @@ pub async fn test_identify_pipeline_with_flatten(store_type: StorageType) { guard.remove_all().await; } -pub async fn test_identify_pipeline_with_custom_ts(store_type: StorageType) { +pub async fn test_identity_pipeline_with_custom_ts(store_type: StorageType) { common_telemetry::init_default_ut_logging(); let (app, mut guard) = - setup_test_http_app_with_frontend(store_type, "test_identify_pipeline_with_custom_ts") + setup_test_http_app_with_frontend(store_type, "test_identity_pipeline_with_custom_ts") .await; let client = TestClient::new(app).await; @@ -1868,7 +1937,7 @@ pub async fn test_identify_pipeline_with_custom_ts(store_type: StorageType) { let expected = r#"[["__time__","TimestampSecond","PRI","NO","","TIMESTAMP"],["__name__","String","","YES","","FIELD"],["__source__","String","","YES","","FIELD"]]"#; validate_data( - "test_identify_pipeline_with_custom_ts_desc_logs", + "test_identity_pipeline_with_custom_ts_desc_logs", &client, "desc logs", expected, @@ -1877,7 +1946,7 @@ pub async fn test_identify_pipeline_with_custom_ts(store_type: StorageType) { let expected = r#"[[1453809242,"hello","10.170.***.***"],[1453809252,null,"10.170.***.***"]]"#; validate_data( - "test_identify_pipeline_with_custom_ts_data", + "test_identity_pipeline_with_custom_ts_data", &client, "select * from logs", expected, @@ -1908,7 +1977,7 @@ pub async fn test_identify_pipeline_with_custom_ts(store_type: StorageType) { let expected = r#"[["__time__","TimestampNanosecond","PRI","NO","","TIMESTAMP"],["__source__","String","","YES","","FIELD"],["__name__","String","","YES","","FIELD"]]"#; validate_data( - "test_identify_pipeline_with_custom_ts_desc_logs", + "test_identity_pipeline_with_custom_ts_desc_logs", &client, "desc logs", expected, @@ -1917,7 +1986,7 @@ pub async fn test_identify_pipeline_with_custom_ts(store_type: StorageType) { let expected = r#"[[1547577721000000000,"10.170.***.***",null],[1547577724000000000,"10.170.***.***","hello"]]"#; validate_data( - "test_identify_pipeline_with_custom_ts_data", + "test_identity_pipeline_with_custom_ts_data", &client, "select * from logs", expected, @@ -2488,7 +2557,7 @@ pub async fn test_otlp_traces_v1(store_type: StorageType) { let expected = r#"[[1736480942444376000,1736480942444499000,123000,null,"c05d7a4ec8e1f231f02ed6e8da8655b4","d24f921c75f68e23","SPAN_KIND_CLIENT","lets-go","STATUS_CODE_UNSET","","","telemetrygen","","telemetrygen","1.2.3.4","telemetrygen-server",[],[]],[1736480942444376000,1736480942444499000,123000,"d24f921c75f68e23","c05d7a4ec8e1f231f02ed6e8da8655b4","9630f2916e2f7909","SPAN_KIND_SERVER","okey-dokey-0","STATUS_CODE_UNSET","","","telemetrygen","","telemetrygen","1.2.3.4","telemetrygen-client",[],[]],[1736480942444589000,1736480942444712000,123000,null,"cc9e0991a2e63d274984bd44ee669203","eba7be77e3558179","SPAN_KIND_CLIENT","lets-go","STATUS_CODE_UNSET","","","telemetrygen","","telemetrygen","1.2.3.4","telemetrygen-server",[],[]],[1736480942444589000,1736480942444712000,123000,"eba7be77e3558179","cc9e0991a2e63d274984bd44ee669203","8f847259b0f6e1ab","SPAN_KIND_SERVER","okey-dokey-0","STATUS_CODE_UNSET","","","telemetrygen","","telemetrygen","1.2.3.4","telemetrygen-client",[],[]]]"#; validate_data("otlp_traces", &client, "select * from mytable;", expected).await; - let expected_ddl = r#"[["mytable","CREATE TABLE IF NOT EXISTS \"mytable\" (\n \"timestamp\" TIMESTAMP(9) NOT NULL,\n \"timestamp_end\" TIMESTAMP(9) NULL,\n \"duration_nano\" BIGINT UNSIGNED NULL,\n \"parent_span_id\" STRING NULL SKIPPING INDEX WITH(granularity = '10240', type = 'BLOOM'),\n \"trace_id\" STRING NULL SKIPPING INDEX WITH(granularity = '10240', type = 'BLOOM'),\n \"span_id\" STRING NULL,\n \"span_kind\" STRING NULL,\n \"span_name\" STRING NULL,\n \"span_status_code\" STRING NULL,\n \"span_status_message\" STRING NULL,\n \"trace_state\" STRING NULL,\n \"scope_name\" STRING NULL,\n \"scope_version\" STRING NULL,\n \"service_name\" STRING NULL SKIPPING INDEX WITH(granularity = '10240', type = 'BLOOM'),\n \"span_attributes.net.peer.ip\" STRING NULL,\n \"span_attributes.peer.service\" STRING NULL,\n \"span_events\" JSON NULL,\n \"span_links\" JSON NULL,\n TIME INDEX (\"timestamp\"),\n PRIMARY KEY (\"service_name\")\n)\nPARTITION ON COLUMNS (\"trace_id\") (\n trace_id < '1',\n trace_id >= '1' AND trace_id < '2',\n trace_id >= '2' AND trace_id < '3',\n trace_id >= '3' AND trace_id < '4',\n trace_id >= '4' AND trace_id < '5',\n trace_id >= '5' AND trace_id < '6',\n trace_id >= '6' AND trace_id < '7',\n trace_id >= '7' AND trace_id < '8',\n trace_id >= '8' AND trace_id < '9',\n trace_id >= '9' AND trace_id < 'A',\n trace_id >= 'A' AND trace_id < 'B' OR trace_id >= 'a' AND trace_id < 'b',\n trace_id >= 'B' AND trace_id < 'C' OR trace_id >= 'b' AND trace_id < 'c',\n trace_id >= 'C' AND trace_id < 'D' OR trace_id >= 'c' AND trace_id < 'd',\n trace_id >= 'D' AND trace_id < 'E' OR trace_id >= 'd' AND trace_id < 'e',\n trace_id >= 'E' AND trace_id < 'F' OR trace_id >= 'e' AND trace_id < 'f',\n trace_id >= 'F' AND trace_id < 'a' OR trace_id >= 'f'\n)\nENGINE=mito\nWITH(\n append_mode = 'true',\n table_data_model = 'greptime_trace_v1'\n)"]]"#; + let expected_ddl = r#"[["mytable","CREATE TABLE IF NOT EXISTS \"mytable\" (\n \"timestamp\" TIMESTAMP(9) NOT NULL,\n \"timestamp_end\" TIMESTAMP(9) NULL,\n \"duration_nano\" BIGINT UNSIGNED NULL,\n \"parent_span_id\" STRING NULL SKIPPING INDEX WITH(granularity = '10240', type = 'BLOOM'),\n \"trace_id\" STRING NULL SKIPPING INDEX WITH(granularity = '10240', type = 'BLOOM'),\n \"span_id\" STRING NULL,\n \"span_kind\" STRING NULL,\n \"span_name\" STRING NULL,\n \"span_status_code\" STRING NULL,\n \"span_status_message\" STRING NULL,\n \"trace_state\" STRING NULL,\n \"scope_name\" STRING NULL,\n \"scope_version\" STRING NULL,\n \"service_name\" STRING NULL SKIPPING INDEX WITH(granularity = '10240', type = 'BLOOM'),\n \"span_attributes.net.peer.ip\" STRING NULL,\n \"span_attributes.peer.service\" STRING NULL,\n \"span_events\" JSON NULL,\n \"span_links\" JSON NULL,\n TIME INDEX (\"timestamp\"),\n PRIMARY KEY (\"service_name\")\n)\nPARTITION ON COLUMNS (\"trace_id\") (\n trace_id < '1',\n trace_id >= 'f',\n trace_id >= '1' AND trace_id < '2',\n trace_id >= '2' AND trace_id < '3',\n trace_id >= '3' AND trace_id < '4',\n trace_id >= '4' AND trace_id < '5',\n trace_id >= '5' AND trace_id < '6',\n trace_id >= '6' AND trace_id < '7',\n trace_id >= '7' AND trace_id < '8',\n trace_id >= '8' AND trace_id < '9',\n trace_id >= '9' AND trace_id < 'a',\n trace_id >= 'a' AND trace_id < 'b',\n trace_id >= 'b' AND trace_id < 'c',\n trace_id >= 'c' AND trace_id < 'd',\n trace_id >= 'd' AND trace_id < 'e',\n trace_id >= 'e' AND trace_id < 'f'\n)\nENGINE=mito\nWITH(\n append_mode = 'true',\n table_data_model = 'greptime_trace_v1'\n)"]]"#; validate_data( "otlp_traces", &client, diff --git a/tests-integration/tests/region_migration.rs b/tests-integration/tests/region_migration.rs index b24850420e..28d6371c8b 100644 --- a/tests-integration/tests/region_migration.rs +++ b/tests-integration/tests/region_migration.rs @@ -847,7 +847,7 @@ pub async fn test_region_migration_incorrect_from_peer( assert!(matches!( err, - meta_srv::error::Error::InvalidArguments { .. } + meta_srv::error::Error::LeaderPeerChanged { .. } )); } diff --git a/tests/cases/standalone/common/alter/add_col_default.result b/tests/cases/standalone/common/alter/add_col_default.result index 6d8c523ba3..5a9baf7186 100644 --- a/tests/cases/standalone/common/alter/add_col_default.result +++ b/tests/cases/standalone/common/alter/add_col_default.result @@ -6,19 +6,56 @@ INSERT INTO test VALUES (1, 1), (2, 2); Affected Rows: 2 +ADMIN FLUSH_TABLE('test'); + ++---------------------------+ +| ADMIN FLUSH_TABLE('test') | ++---------------------------+ +| 0 | ++---------------------------+ + +ALTER TABLE test MODIFY COLUMN i SET INVERTED INDEX; + +Affected Rows: 0 + +INSERT INTO test VALUES (3, 3), (4, 4); + +Affected Rows: 2 + ALTER TABLE test ADD COLUMN k INTEGER DEFAULT 3; Affected Rows: 0 -SELECT * FROM test; +SELECT * FROM test order by j; +---+-------------------------+---+ | i | j | k | +---+-------------------------+---+ | 1 | 1970-01-01T00:00:00.001 | 3 | | 2 | 1970-01-01T00:00:00.002 | 3 | +| 3 | 1970-01-01T00:00:00.003 | 3 | +| 4 | 1970-01-01T00:00:00.004 | 3 | +---+-------------------------+---+ +SELECT * FROM test where k != 3; + +++ +++ + +ALTER TABLE test ADD COLUMN host STRING DEFAULT '' PRIMARY KEY; + +Affected Rows: 0 + +SELECT * FROM test where host != ''; + +++ +++ + +SELECT * FROM test where host != '' AND i = 3; + +++ +++ + DROP TABLE test; Affected Rows: 0 diff --git a/tests/cases/standalone/common/alter/add_col_default.sql b/tests/cases/standalone/common/alter/add_col_default.sql index 187e96821e..258ec1683a 100644 --- a/tests/cases/standalone/common/alter/add_col_default.sql +++ b/tests/cases/standalone/common/alter/add_col_default.sql @@ -2,8 +2,22 @@ CREATE TABLE test(i INTEGER, j TIMESTAMP TIME INDEX); INSERT INTO test VALUES (1, 1), (2, 2); +ADMIN FLUSH_TABLE('test'); + +ALTER TABLE test MODIFY COLUMN i SET INVERTED INDEX; + +INSERT INTO test VALUES (3, 3), (4, 4); + ALTER TABLE test ADD COLUMN k INTEGER DEFAULT 3; -SELECT * FROM test; +SELECT * FROM test order by j; + +SELECT * FROM test where k != 3; + +ALTER TABLE test ADD COLUMN host STRING DEFAULT '' PRIMARY KEY; + +SELECT * FROM test where host != ''; + +SELECT * FROM test where host != '' AND i = 3; DROP TABLE test; diff --git a/tests/cases/standalone/common/alter/change_col_fulltext_options.result b/tests/cases/standalone/common/alter/change_col_fulltext_options.result index ee400593cc..13202ae12c 100644 --- a/tests/cases/standalone/common/alter/change_col_fulltext_options.result +++ b/tests/cases/standalone/common/alter/change_col_fulltext_options.result @@ -79,29 +79,29 @@ SELECT * FROM test WHERE MATCHES(message, 'hello') ORDER BY message; -- SQLNESS ARG restart=true SHOW CREATE TABLE test; -+-------+---------------------------------------------------------------------------------------------+ -| Table | Create Table | -+-------+---------------------------------------------------------------------------------------------+ -| test | CREATE TABLE IF NOT EXISTS "test" ( | -| | "message" STRING NULL FULLTEXT INDEX WITH(analyzer = 'Chinese', case_sensitive = 'true'), | -| | "time" TIMESTAMP(3) NOT NULL, | -| | TIME INDEX ("time") | -| | ) | -| | | -| | ENGINE=mito | -| | WITH( | -| | append_mode = 'true' | -| | ) | -+-------+---------------------------------------------------------------------------------------------+ ++-------+----------------------------------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+----------------------------------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT INDEX WITH(analyzer = 'Chinese', backend = 'bloom', case_sensitive = 'true'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+----------------------------------------------------------------------------------------------------------------+ SHOW INDEX FROM test; -+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+----------------------------+---------+---------------+---------+------------+ -| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | -+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+----------------------------+---------+---------------+---------+------------+ -| test | 1 | FULLTEXT INDEX | 1 | message | A | | | | YES | greptime-fulltext-index-v1 | | | YES | | -| test | 1 | TIME INDEX | 1 | time | A | | | | NO | | | | YES | | -+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+----------------------------+---------+---------------+---------+------------+ ++-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+-------------------------------+---------+---------------+---------+------------+ +| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | ++-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+-------------------------------+---------+---------------+---------+------------+ +| test | 1 | FULLTEXT INDEX | 1 | message | A | | | | YES | greptime-fulltext-index-bloom | | | YES | | +| test | 1 | TIME INDEX | 1 | time | A | | | | NO | | | | YES | | ++-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+-------------------------------+---------+---------------+---------+------------+ ALTER TABLE test MODIFY COLUMN message UNSET FULLTEXT INDEX; @@ -138,33 +138,33 @@ Affected Rows: 0 SHOW CREATE TABLE test; -+-------+---------------------------------------------------------------------------------------------+ -| Table | Create Table | -+-------+---------------------------------------------------------------------------------------------+ -| test | CREATE TABLE IF NOT EXISTS "test" ( | -| | "message" STRING NULL FULLTEXT INDEX WITH(analyzer = 'Chinese', case_sensitive = 'true'), | -| | "time" TIMESTAMP(3) NOT NULL, | -| | TIME INDEX ("time") | -| | ) | -| | | -| | ENGINE=mito | -| | WITH( | -| | append_mode = 'true' | -| | ) | -+-------+---------------------------------------------------------------------------------------------+ ++-------+----------------------------------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+----------------------------------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT INDEX WITH(analyzer = 'Chinese', backend = 'bloom', case_sensitive = 'true'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+----------------------------------------------------------------------------------------------------------------+ SHOW INDEX FROM test; -+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+----------------------------+---------+---------------+---------+------------+ -| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | -+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+----------------------------+---------+---------------+---------+------------+ -| test | 1 | FULLTEXT INDEX | 1 | message | A | | | | YES | greptime-fulltext-index-v1 | | | YES | | -| test | 1 | TIME INDEX | 1 | time | A | | | | NO | | | | YES | | -+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+----------------------------+---------+---------------+---------+------------+ ++-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+-------------------------------+---------+---------------+---------+------------+ +| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | ++-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+-------------------------------+---------+---------------+---------+------------+ +| test | 1 | FULLTEXT INDEX | 1 | message | A | | | | YES | greptime-fulltext-index-bloom | | | YES | | +| test | 1 | TIME INDEX | 1 | time | A | | | | NO | | | | YES | | ++-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+-------------------------------+---------+---------------+---------+------------+ ALTER TABLE test MODIFY COLUMN message SET FULLTEXT INDEX WITH(analyzer = 'Chinese', case_sensitive = 'false'); -Error: 1004(InvalidArguments), Invalid column option, column name: message, error: FULLTEXT index already enabled +Error: 1004(InvalidArguments), Invalid column option, column name: message, error: Cannot change analyzer or case_sensitive if FULLTEXT index is set before. Previous analyzer: Chinese, previous case_sensitive: true ALTER TABLE test MODIFY COLUMN message UNSET FULLTEXT INDEX; @@ -195,6 +195,66 @@ SHOW INDEX FROM test; | test | 1 | TIME INDEX | 1 | time | A | | | | NO | | | | YES | | +-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+ +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT INDEX WITH(analyzer = 'Chinese', case_sensitive = 'true', backend = 'bloom'); + +Affected Rows: 0 + +SHOW CREATE TABLE test; + ++-------+----------------------------------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+----------------------------------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT INDEX WITH(analyzer = 'Chinese', backend = 'bloom', case_sensitive = 'true'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+----------------------------------------------------------------------------------------------------------------+ + +SHOW INDEX FROM test; + ++-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+-------------------------------+---------+---------------+---------+------------+ +| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | ++-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+-------------------------------+---------+---------------+---------+------------+ +| test | 1 | FULLTEXT INDEX | 1 | message | A | | | | YES | greptime-fulltext-index-bloom | | | YES | | +| test | 1 | TIME INDEX | 1 | time | A | | | | NO | | | | YES | | ++-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+-------------------------------+---------+---------------+---------+------------+ + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT INDEX WITH(analyzer = 'Chinese', case_sensitive = 'true', backend = 'tantivy'); + +Affected Rows: 0 + +SHOW CREATE TABLE test; + ++-------+------------------------------------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+------------------------------------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT INDEX WITH(analyzer = 'Chinese', backend = 'tantivy', case_sensitive = 'true'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+------------------------------------------------------------------------------------------------------------------+ + +SHOW INDEX FROM test; + ++-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+----------------------------+---------+---------------+---------+------------+ +| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | ++-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+----------------------------+---------+---------------+---------+------------+ +| test | 1 | FULLTEXT INDEX | 1 | message | A | | | | YES | greptime-fulltext-index-v1 | | | YES | | +| test | 1 | TIME INDEX | 1 | time | A | | | | NO | | | | YES | | ++-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+----------------------------+---------+---------------+---------+------------+ + ALTER TABLE test MODIFY COLUMN message SET FULLTEXT INDEX WITH(analyzer = 'Chinglish', case_sensitive = 'false'); Error: 1002(Unexpected), Invalid fulltext option: Chinglish, expected: 'English' | 'Chinese' @@ -211,6 +271,10 @@ ALTER TABLE test MODIFY COLUMN message SET FULLTEXT INDEX WITH(analyzer = 'Engli Error: 1004(InvalidArguments), Invalid column option, column name: message, error: Cannot change analyzer or case_sensitive if FULLTEXT index is set before. Previous analyzer: Chinese, previous case_sensitive: true +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT INDEX WITH(backend = 'xor'); + +Error: 1002(Unexpected), Invalid fulltext option: xor, expected: 'bloom' | 'tantivy' + DROP TABLE test; Affected Rows: 0 diff --git a/tests/cases/standalone/common/alter/change_col_fulltext_options.sql b/tests/cases/standalone/common/alter/change_col_fulltext_options.sql index b5ead6e610..df56e8179e 100644 --- a/tests/cases/standalone/common/alter/change_col_fulltext_options.sql +++ b/tests/cases/standalone/common/alter/change_col_fulltext_options.sql @@ -51,6 +51,18 @@ SHOW CREATE TABLE test; SHOW INDEX FROM test; +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT INDEX WITH(analyzer = 'Chinese', case_sensitive = 'true', backend = 'bloom'); + +SHOW CREATE TABLE test; + +SHOW INDEX FROM test; + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT INDEX WITH(analyzer = 'Chinese', case_sensitive = 'true', backend = 'tantivy'); + +SHOW CREATE TABLE test; + +SHOW INDEX FROM test; + ALTER TABLE test MODIFY COLUMN message SET FULLTEXT INDEX WITH(analyzer = 'Chinglish', case_sensitive = 'false'); ALTER TABLE test MODIFY COLUMN message SET FULLTEXT INDEX WITH(analyzer = 'Chinese', case_sensitive = 'no'); @@ -59,4 +71,6 @@ ALTER TABLE test MODIFY COLUMN time SET FULLTEXT INDEX WITH(analyzer = 'Chinese' ALTER TABLE test MODIFY COLUMN message SET FULLTEXT INDEX WITH(analyzer = 'English', case_sensitive = 'true'); +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT INDEX WITH(backend = 'xor'); + DROP TABLE test; diff --git a/tests/cases/standalone/common/copy/copy_from_fs_csv.result b/tests/cases/standalone/common/copy/copy_from_fs_csv.result index f412677a32..d1838a5079 100644 --- a/tests/cases/standalone/common/copy/copy_from_fs_csv.result +++ b/tests/cases/standalone/common/copy/copy_from_fs_csv.result @@ -66,6 +66,42 @@ select * from with_pattern order by ts; | host3 | 99.9 | 444.4 | 2024-07-27T10:47:43 | +-------+------+--------+---------------------+ +CREATE TABLE demo_with_external_column(host string, cpu double, memory double, ts timestamp time index, external_column string default 'default_value'); + +Affected Rows: 0 + +Copy demo_with_external_column FROM '${SQLNESS_HOME}/demo/export/csv/demo.csv' WITH (format='csv'); + +Affected Rows: 3 + +select * from demo_with_external_column order by ts; + ++-------+------+--------+---------------------+-----------------+ +| host | cpu | memory | ts | external_column | ++-------+------+--------+---------------------+-----------------+ +| host1 | 66.6 | 1024.0 | 2022-06-15T07:02:37 | default_value | +| host2 | 88.8 | 333.3 | 2022-06-15T07:02:38 | default_value | +| host3 | 99.9 | 444.4 | 2024-07-27T10:47:43 | default_value | ++-------+------+--------+---------------------+-----------------+ + +CREATE TABLE demo_with_less_columns(host string, memory double, ts timestamp time index); + +Affected Rows: 0 + +Copy demo_with_less_columns FROM '${SQLNESS_HOME}/demo/export/csv/demo.csv' WITH (format='csv'); + +Affected Rows: 3 + +select * from demo_with_less_columns order by ts; + ++-------+--------+---------------------+ +| host | memory | ts | ++-------+--------+---------------------+ +| host1 | 1024.0 | 2022-06-15T07:02:37 | +| host2 | 333.3 | 2022-06-15T07:02:38 | +| host3 | 444.4 | 2024-07-27T10:47:43 | ++-------+--------+---------------------+ + drop table demo; Affected Rows: 0 @@ -82,3 +118,11 @@ drop table with_pattern; Affected Rows: 0 +drop table demo_with_external_column; + +Affected Rows: 0 + +drop table demo_with_less_columns; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/copy/copy_from_fs_csv.sql b/tests/cases/standalone/common/copy/copy_from_fs_csv.sql index f2c6ccf5bd..8c4e9b3aa9 100644 --- a/tests/cases/standalone/common/copy/copy_from_fs_csv.sql +++ b/tests/cases/standalone/common/copy/copy_from_fs_csv.sql @@ -27,6 +27,18 @@ Copy with_pattern FROM '${SQLNESS_HOME}/demo/export/csv/' WITH (pattern = 'demo. select * from with_pattern order by ts; +CREATE TABLE demo_with_external_column(host string, cpu double, memory double, ts timestamp time index, external_column string default 'default_value'); + +Copy demo_with_external_column FROM '${SQLNESS_HOME}/demo/export/csv/demo.csv' WITH (format='csv'); + +select * from demo_with_external_column order by ts; + +CREATE TABLE demo_with_less_columns(host string, memory double, ts timestamp time index); + +Copy demo_with_less_columns FROM '${SQLNESS_HOME}/demo/export/csv/demo.csv' WITH (format='csv'); + +select * from demo_with_less_columns order by ts; + drop table demo; drop table with_filename; @@ -34,3 +46,7 @@ drop table with_filename; drop table with_path; drop table with_pattern; + +drop table demo_with_external_column; + +drop table demo_with_less_columns; diff --git a/tests/cases/standalone/common/copy/copy_from_fs_json.result b/tests/cases/standalone/common/copy/copy_from_fs_json.result index e1e3a810d5..153fac1e87 100644 --- a/tests/cases/standalone/common/copy/copy_from_fs_json.result +++ b/tests/cases/standalone/common/copy/copy_from_fs_json.result @@ -66,6 +66,42 @@ select * from with_pattern order by ts; | host2 | 88.8 | 333.3 | 2022-06-15T07:02:38 | +-------+------+--------+---------------------+ +CREATE TABLE demo_with_external_column(host string, cpu double, memory double, ts timestamp time index, external_column string default 'default_value'); + +Affected Rows: 0 + +Copy demo_with_external_column FROM '${SQLNESS_HOME}/demo/export/json/demo.json' WITH (format='json'); + +Affected Rows: 3 + +select * from demo_with_external_column order by ts; + ++-------+------+--------+---------------------+-----------------+ +| host | cpu | memory | ts | external_column | ++-------+------+--------+---------------------+-----------------+ +| host1 | 66.6 | 1024.0 | 2022-06-15T07:02:37 | default_value | +| host2 | 88.8 | 333.3 | 2022-06-15T07:02:38 | default_value | +| host3 | 99.9 | 444.4 | 2024-07-27T10:47:43 | default_value | ++-------+------+--------+---------------------+-----------------+ + +CREATE TABLE demo_with_less_columns(host string, memory double, ts timestamp time index); + +Affected Rows: 0 + +Copy demo_with_less_columns FROM '${SQLNESS_HOME}/demo/export/json/demo.json' WITH (format='json'); + +Affected Rows: 3 + +select * from demo_with_less_columns order by ts; + ++-------+--------+---------------------+ +| host | memory | ts | ++-------+--------+---------------------+ +| host1 | 1024.0 | 2022-06-15T07:02:37 | +| host2 | 333.3 | 2022-06-15T07:02:38 | +| host3 | 444.4 | 2024-07-27T10:47:43 | ++-------+--------+---------------------+ + drop table demo; Affected Rows: 0 @@ -82,3 +118,11 @@ drop table with_pattern; Affected Rows: 0 +drop table demo_with_external_column; + +Affected Rows: 0 + +drop table demo_with_less_columns; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/copy/copy_from_fs_json.sql b/tests/cases/standalone/common/copy/copy_from_fs_json.sql index 55e8a55ebf..b032332262 100644 --- a/tests/cases/standalone/common/copy/copy_from_fs_json.sql +++ b/tests/cases/standalone/common/copy/copy_from_fs_json.sql @@ -27,6 +27,18 @@ Copy with_pattern FROM '${SQLNESS_HOME}/demo/export/json/' WITH (pattern = 'demo select * from with_pattern order by ts; +CREATE TABLE demo_with_external_column(host string, cpu double, memory double, ts timestamp time index, external_column string default 'default_value'); + +Copy demo_with_external_column FROM '${SQLNESS_HOME}/demo/export/json/demo.json' WITH (format='json'); + +select * from demo_with_external_column order by ts; + +CREATE TABLE demo_with_less_columns(host string, memory double, ts timestamp time index); + +Copy demo_with_less_columns FROM '${SQLNESS_HOME}/demo/export/json/demo.json' WITH (format='json'); + +select * from demo_with_less_columns order by ts; + drop table demo; drop table with_filename; @@ -34,3 +46,7 @@ drop table with_filename; drop table with_path; drop table with_pattern; + +drop table demo_with_external_column; + +drop table demo_with_less_columns; diff --git a/tests/cases/standalone/common/copy/copy_from_fs_parquet.result b/tests/cases/standalone/common/copy/copy_from_fs_parquet.result index f31aebd7b1..ac7fbcddba 100644 --- a/tests/cases/standalone/common/copy/copy_from_fs_parquet.result +++ b/tests/cases/standalone/common/copy/copy_from_fs_parquet.result @@ -123,6 +123,42 @@ Copy with_limit_rows_segment FROM '${SQLNESS_HOME}/demo/export/parquet_files/' L Error: 2000(InvalidSyntax), Unexpected token while parsing SQL statement, expected: 'the number of maximum rows', found: ;: sql parser error: Expected: literal int, found: hello at Line: 1, Column: 86 +CREATE TABLE demo_with_external_column(host string, cpu double, memory double, ts timestamp time index, external_column string default 'default_value'); + +Affected Rows: 0 + +Copy demo_with_external_column FROM '${SQLNESS_HOME}/demo/export/parquet_files/demo.parquet'; + +Affected Rows: 3 + +select * from demo_with_external_column order by ts; + ++-------+-------+--------+---------------------+-----------------+ +| host | cpu | memory | ts | external_column | ++-------+-------+--------+---------------------+-----------------+ +| host1 | 66.6 | 1024.0 | 2022-06-15T07:02:37 | default_value | +| host2 | 88.8 | 333.3 | 2022-06-15T07:02:38 | default_value | +| host3 | 111.1 | 444.4 | 2024-07-27T10:47:43 | default_value | ++-------+-------+--------+---------------------+-----------------+ + +CREATE TABLE demo_with_less_columns(host string, memory double, ts timestamp time index); + +Affected Rows: 0 + +Copy demo_with_less_columns FROM '${SQLNESS_HOME}/demo/export/parquet_files/demo.parquet'; + +Affected Rows: 3 + +select * from demo_with_less_columns order by ts; + ++-------+--------+---------------------+ +| host | memory | ts | ++-------+--------+---------------------+ +| host1 | 1024.0 | 2022-06-15T07:02:37 | +| host2 | 333.3 | 2022-06-15T07:02:38 | +| host3 | 444.4 | 2024-07-27T10:47:43 | ++-------+--------+---------------------+ + drop table demo; Affected Rows: 0 @@ -151,3 +187,11 @@ drop table with_limit_rows_segment; Affected Rows: 0 +drop table demo_with_external_column; + +Affected Rows: 0 + +drop table demo_with_less_columns; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/copy/copy_from_fs_parquet.sql b/tests/cases/standalone/common/copy/copy_from_fs_parquet.sql index 0db5e119d8..13e15bd91e 100644 --- a/tests/cases/standalone/common/copy/copy_from_fs_parquet.sql +++ b/tests/cases/standalone/common/copy/copy_from_fs_parquet.sql @@ -52,6 +52,18 @@ select count(*) from with_limit_rows_segment; Copy with_limit_rows_segment FROM '${SQLNESS_HOME}/demo/export/parquet_files/' LIMIT hello; +CREATE TABLE demo_with_external_column(host string, cpu double, memory double, ts timestamp time index, external_column string default 'default_value'); + +Copy demo_with_external_column FROM '${SQLNESS_HOME}/demo/export/parquet_files/demo.parquet'; + +select * from demo_with_external_column order by ts; + +CREATE TABLE demo_with_less_columns(host string, memory double, ts timestamp time index); + +Copy demo_with_less_columns FROM '${SQLNESS_HOME}/demo/export/parquet_files/demo.parquet'; + +select * from demo_with_less_columns order by ts; + drop table demo; drop table demo_2; @@ -65,3 +77,7 @@ drop table with_pattern; drop table without_limit_rows; drop table with_limit_rows_segment; + +drop table demo_with_external_column; + +drop table demo_with_less_columns; diff --git a/tests/cases/standalone/common/create/create_with_fulltext.result b/tests/cases/standalone/common/create/create_with_fulltext.result index 3ab0435780..d5ae9ee2dd 100644 --- a/tests/cases/standalone/common/create/create_with_fulltext.result +++ b/tests/cases/standalone/common/create/create_with_fulltext.result @@ -7,18 +7,18 @@ Affected Rows: 0 SHOW CREATE TABLE log; -+-------+------------------------------------------------------------------------------------------+ -| Table | Create Table | -+-------+------------------------------------------------------------------------------------------+ -| log | CREATE TABLE IF NOT EXISTS "log" ( | -| | "ts" TIMESTAMP(3) NOT NULL, | -| | "msg" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', case_sensitive = 'false'), | -| | TIME INDEX ("ts") | -| | ) | -| | | -| | ENGINE=mito | -| | | -+-------+------------------------------------------------------------------------------------------+ ++-------+-------------------------------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+-------------------------------------------------------------------------------------------------------------+ +| log | CREATE TABLE IF NOT EXISTS "log" ( | +| | "ts" TIMESTAMP(3) NOT NULL, | +| | "msg" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'false'), | +| | TIME INDEX ("ts") | +| | ) | +| | | +| | ENGINE=mito | +| | | ++-------+-------------------------------------------------------------------------------------------------------------+ DROP TABLE log; @@ -33,18 +33,18 @@ Affected Rows: 0 SHOW CREATE TABLE log_with_opts; -+---------------+-----------------------------------------------------------------------------------------+ -| Table | Create Table | -+---------------+-----------------------------------------------------------------------------------------+ -| log_with_opts | CREATE TABLE IF NOT EXISTS "log_with_opts" ( | -| | "ts" TIMESTAMP(3) NOT NULL, | -| | "msg" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', case_sensitive = 'true'), | -| | TIME INDEX ("ts") | -| | ) | -| | | -| | ENGINE=mito | -| | | -+---------------+-----------------------------------------------------------------------------------------+ ++---------------+------------------------------------------------------------------------------------------------------------+ +| Table | Create Table | ++---------------+------------------------------------------------------------------------------------------------------------+ +| log_with_opts | CREATE TABLE IF NOT EXISTS "log_with_opts" ( | +| | "ts" TIMESTAMP(3) NOT NULL, | +| | "msg" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'true'), | +| | TIME INDEX ("ts") | +| | ) | +| | | +| | ENGINE=mito | +| | | ++---------------+------------------------------------------------------------------------------------------------------------+ DROP TABLE log_with_opts; @@ -60,19 +60,19 @@ Affected Rows: 0 SHOW CREATE TABLE log_multi_fulltext_cols; -+-------------------------+-------------------------------------------------------------------------------------------+ -| Table | Create Table | -+-------------------------+-------------------------------------------------------------------------------------------+ -| log_multi_fulltext_cols | CREATE TABLE IF NOT EXISTS "log_multi_fulltext_cols" ( | -| | "ts" TIMESTAMP(3) NOT NULL, | -| | "msg" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', case_sensitive = 'false'), | -| | "msg2" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', case_sensitive = 'false'), | -| | TIME INDEX ("ts") | -| | ) | -| | | -| | ENGINE=mito | -| | | -+-------------------------+-------------------------------------------------------------------------------------------+ ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ +| log_multi_fulltext_cols | CREATE TABLE IF NOT EXISTS "log_multi_fulltext_cols" ( | +| | "ts" TIMESTAMP(3) NOT NULL, | +| | "msg" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'false'), | +| | "msg2" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'false'), | +| | TIME INDEX ("ts") | +| | ) | +| | | +| | ENGINE=mito | +| | | ++-------------------------+--------------------------------------------------------------------------------------------------------------+ DROP TABLE log_multi_fulltext_cols; diff --git a/tests/cases/standalone/common/expr/atat.result b/tests/cases/standalone/common/expr/atat.result new file mode 100644 index 0000000000..6beec6347a --- /dev/null +++ b/tests/cases/standalone/common/expr/atat.result @@ -0,0 +1,315 @@ +-- Derived from matches_term cases +-- Test basic term matching +-- Expect: true +SELECT 'cat!' @@ 'cat' as result; + ++--------+ +| result | ++--------+ +| true | ++--------+ + +-- Test phrase matching with spaces +-- Expect: true +SELECT 'warning:hello world!' @@ 'hello world' as result; + ++--------+ +| result | ++--------+ +| true | ++--------+ + +-- Test numbers in term +SELECT 'v1.0!' @@ 'v1.0' as result; + ++--------+ +| result | ++--------+ +| true | ++--------+ + +-- Test case sensitivity +-- Expect: true +SELECT 'Cat' @@ 'Cat' as result; + ++--------+ +| result | ++--------+ +| true | ++--------+ + +-- Expect: false +SELECT 'cat' @@ 'Cat' as result; + ++--------+ +| result | ++--------+ +| false | ++--------+ + +-- Test empty string handling +-- Expect: true +SELECT '' @@ '' as result; + ++--------+ +| result | ++--------+ +| true | ++--------+ + +-- Expect: false +SELECT 'any' @@ '' as result; + ++--------+ +| result | ++--------+ +| false | ++--------+ + +-- Expect: false +SELECT '' @@ 'any' as result; + ++--------+ +| result | ++--------+ +| false | ++--------+ + +-- Test partial matches (should fail) +-- Expect: false +SELECT 'category' @@ 'cat' as result; + ++--------+ +| result | ++--------+ +| false | ++--------+ + +-- Expect: false +SELECT 'rebooted' @@ 'boot' as result; + ++--------+ +| result | ++--------+ +| false | ++--------+ + +-- Test adjacent alphanumeric characters +SELECT 'cat5' @@ 'cat' as result; + ++--------+ +| result | ++--------+ +| false | ++--------+ + +SELECT 'dogcat' @@ 'dog' as result; + ++--------+ +| result | ++--------+ +| false | ++--------+ + +-- Test leading non-alphanumeric +-- Expect: true +SELECT 'dog/cat' @@ '/cat' as result; + ++--------+ +| result | ++--------+ +| true | ++--------+ + +-- Expect: true +SELECT 'dog/cat' @@ 'dog/' as result; + ++--------+ +| result | ++--------+ +| true | ++--------+ + +-- Expect: true +SELECT 'dog/cat' @@ 'dog/cat' as result; + ++--------+ +| result | ++--------+ +| true | ++--------+ + +-- Test unicode characters +-- Expect: true +SELECT 'café>' @@ 'café' as result; + ++--------+ +| result | ++--------+ +| true | ++--------+ + +-- Expect: true +SELECT 'русский!' @@ 'русский' as result; + ++--------+ +| result | ++--------+ +| true | ++--------+ + +-- Test complete word matching +CREATE TABLE logs ( + `id` TIMESTAMP TIME INDEX, + `log_message` STRING +); + +Affected Rows: 0 + +INSERT INTO logs VALUES + (1, 'An error occurred!'), + (2, 'Critical error: system failure'), + (3, 'error-prone'), + (4, 'errors'), + (5, 'error123'), + (6, 'errorLogs'), + (7, 'Version v1.0 released'), + (8, 'v1.0!'), + (9, 'v1.0a'), + (10, 'v1.0beta'), + (11, 'GET /app/start'), + (12, 'Command: /start-prosess'), + (13, 'Command: /start'), + (14, 'start'), + (15, 'start/stop'), + (16, 'Alert: system failure detected'), + (17, 'system failure!'), + (18, 'system-failure'), + (19, 'system failure2023'), + (20, 'critical error: system failure'), + (21, 'critical failure detected'), + (22, 'critical issue'), + (23, 'failure imminent'), + (24, 'Warning: high temperature'), + (25, 'WARNING: system overload'), + (26, 'warned'), + (27, 'warnings'); + +Affected Rows: 27 + +-- Test complete word matching for 'error' +-- Expect: +-- 1|An error occurred!|true +-- 2|Critical error: system failure|true +-- 3|error-prone|true +-- 4|errors|false +-- 5|error123|false +-- 6|errorLogs|false +SELECT `id`, `log_message`, `log_message` @@ 'error' as `matches_error` FROM logs WHERE `id` <= 6 ORDER BY `id`; + ++-------------------------+--------------------------------+---------------+ +| id | log_message | matches_error | ++-------------------------+--------------------------------+---------------+ +| 1970-01-01T00:00:00.001 | An error occurred! | true | +| 1970-01-01T00:00:00.002 | Critical error: system failure | true | +| 1970-01-01T00:00:00.003 | error-prone | true | +| 1970-01-01T00:00:00.004 | errors | false | +| 1970-01-01T00:00:00.005 | error123 | false | +| 1970-01-01T00:00:00.006 | errorLogs | false | ++-------------------------+--------------------------------+---------------+ + +-- Test complete word matching for 'v1.0' +-- Expect: +-- 7|Version v1.0 released|true +-- 8|v1.0!|true +-- 9|v1.0a|false +-- 10|v1.0beta|false +SELECT `id`, `log_message`, `log_message` @@ 'v1.0' as `matches_version` FROM logs WHERE `id` BETWEEN 7 AND 10 ORDER BY `id`; + ++-------------------------+-----------------------+-----------------+ +| id | log_message | matches_version | ++-------------------------+-----------------------+-----------------+ +| 1970-01-01T00:00:00.007 | Version v1.0 released | true | +| 1970-01-01T00:00:00.008 | v1.0! | true | +| 1970-01-01T00:00:00.009 | v1.0a | false | +| 1970-01-01T00:00:00.010 | v1.0beta | false | ++-------------------------+-----------------------+-----------------+ + +-- Test complete word matching for '/start' +-- Expect: +-- 11|GET /app/start|true +-- 12|Command: /start-prosess|true +-- 13|Command: /start|true +-- 14|start|false +-- 15|start/stop|false +SELECT `id`, `log_message`, `log_message` @@ '/start' as `matches_start` FROM logs WHERE `id` BETWEEN 11 AND 15 ORDER BY `id`; + ++-------------------------+-------------------------+---------------+ +| id | log_message | matches_start | ++-------------------------+-------------------------+---------------+ +| 1970-01-01T00:00:00.011 | GET /app/start | true | +| 1970-01-01T00:00:00.012 | Command: /start-prosess | true | +| 1970-01-01T00:00:00.013 | Command: /start | true | +| 1970-01-01T00:00:00.014 | start | false | +| 1970-01-01T00:00:00.015 | start/stop | false | ++-------------------------+-------------------------+---------------+ + +-- Test phrase matching for 'system failure' +-- Expect: +-- 16|Alert: system failure detected|true +-- 17|system failure!|true +-- 18|system-failure|false +-- 19|system failure2023|false +SELECT `id`, `log_message`, `log_message` @@ 'system failure' as `matches_phrase` FROM logs WHERE `id` BETWEEN 16 AND 19 ORDER BY `id`; + ++-------------------------+--------------------------------+----------------+ +| id | log_message | matches_phrase | ++-------------------------+--------------------------------+----------------+ +| 1970-01-01T00:00:00.016 | Alert: system failure detected | true | +| 1970-01-01T00:00:00.017 | system failure! | true | +| 1970-01-01T00:00:00.018 | system-failure | false | +| 1970-01-01T00:00:00.019 | system failure2023 | false | ++-------------------------+--------------------------------+----------------+ + +-- Test multi-word matching using AND +-- Expect: +-- 20|critical error: system failure|true|true|true +-- 21|critical failure detected|true|true|true +-- 22|critical issue|true|false|false +-- 23|failure imminent|false|true|false +SELECT `id`, `log_message`, + `log_message` @@ 'critical' as `matches_critical`, + `log_message` @@ 'failure' as `matches_failure`, + `log_message` @@ 'critical' AND `log_message` @@ 'failure' as `matches_both` +FROM logs WHERE `id` BETWEEN 20 AND 23 ORDER BY `id`; + ++-------------------------+--------------------------------+------------------+-----------------+--------------+ +| id | log_message | matches_critical | matches_failure | matches_both | ++-------------------------+--------------------------------+------------------+-----------------+--------------+ +| 1970-01-01T00:00:00.020 | critical error: system failure | true | true | true | +| 1970-01-01T00:00:00.021 | critical failure detected | true | true | true | +| 1970-01-01T00:00:00.022 | critical issue | true | false | false | +| 1970-01-01T00:00:00.023 | failure imminent | false | true | false | ++-------------------------+--------------------------------+------------------+-----------------+--------------+ + +-- Test case-insensitive matching using lower() +-- Expect: +-- 24|Warning: high temperature|true +-- 25|WARNING: system overload|true +-- 26|warned|false +-- 27|warnings|false +SELECT `id`, `log_message`, lower(`log_message`) @@ 'warning' as `matches_warning` FROM logs WHERE `id` >= 24 ORDER BY `id`; + ++-------------------------+---------------------------+-----------------+ +| id | log_message | matches_warning | ++-------------------------+---------------------------+-----------------+ +| 1970-01-01T00:00:00.024 | Warning: high temperature | true | +| 1970-01-01T00:00:00.025 | WARNING: system overload | true | +| 1970-01-01T00:00:00.026 | warned | false | +| 1970-01-01T00:00:00.027 | warnings | false | ++-------------------------+---------------------------+-----------------+ + +DROP TABLE logs; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/expr/atat.sql b/tests/cases/standalone/common/expr/atat.sql new file mode 100644 index 0000000000..da32dcf0bf --- /dev/null +++ b/tests/cases/standalone/common/expr/atat.sql @@ -0,0 +1,144 @@ +-- Derived from matches_term cases + +-- Test basic term matching +-- Expect: true +SELECT 'cat!' @@ 'cat' as result; + +-- Test phrase matching with spaces +-- Expect: true +SELECT 'warning:hello world!' @@ 'hello world' as result; + +-- Test numbers in term +SELECT 'v1.0!' @@ 'v1.0' as result; + +-- Test case sensitivity +-- Expect: true +SELECT 'Cat' @@ 'Cat' as result; +-- Expect: false +SELECT 'cat' @@ 'Cat' as result; + +-- Test empty string handling +-- Expect: true +SELECT '' @@ '' as result; +-- Expect: false +SELECT 'any' @@ '' as result; +-- Expect: false +SELECT '' @@ 'any' as result; + +-- Test partial matches (should fail) +-- Expect: false +SELECT 'category' @@ 'cat' as result; +-- Expect: false +SELECT 'rebooted' @@ 'boot' as result; + +-- Test adjacent alphanumeric characters +SELECT 'cat5' @@ 'cat' as result; +SELECT 'dogcat' @@ 'dog' as result; + +-- Test leading non-alphanumeric +-- Expect: true +SELECT 'dog/cat' @@ '/cat' as result; +-- Expect: true +SELECT 'dog/cat' @@ 'dog/' as result; +-- Expect: true +SELECT 'dog/cat' @@ 'dog/cat' as result; + +-- Test unicode characters +-- Expect: true +SELECT 'café>' @@ 'café' as result; +-- Expect: true +SELECT 'русский!' @@ 'русский' as result; + +-- Test complete word matching +CREATE TABLE logs ( + `id` TIMESTAMP TIME INDEX, + `log_message` STRING +); + +INSERT INTO logs VALUES + (1, 'An error occurred!'), + (2, 'Critical error: system failure'), + (3, 'error-prone'), + (4, 'errors'), + (5, 'error123'), + (6, 'errorLogs'), + (7, 'Version v1.0 released'), + (8, 'v1.0!'), + (9, 'v1.0a'), + (10, 'v1.0beta'), + (11, 'GET /app/start'), + (12, 'Command: /start-prosess'), + (13, 'Command: /start'), + (14, 'start'), + (15, 'start/stop'), + (16, 'Alert: system failure detected'), + (17, 'system failure!'), + (18, 'system-failure'), + (19, 'system failure2023'), + (20, 'critical error: system failure'), + (21, 'critical failure detected'), + (22, 'critical issue'), + (23, 'failure imminent'), + (24, 'Warning: high temperature'), + (25, 'WARNING: system overload'), + (26, 'warned'), + (27, 'warnings'); + +-- Test complete word matching for 'error' +-- Expect: +-- 1|An error occurred!|true +-- 2|Critical error: system failure|true +-- 3|error-prone|true +-- 4|errors|false +-- 5|error123|false +-- 6|errorLogs|false +SELECT `id`, `log_message`, `log_message` @@ 'error' as `matches_error` FROM logs WHERE `id` <= 6 ORDER BY `id`; + + +-- Test complete word matching for 'v1.0' +-- Expect: +-- 7|Version v1.0 released|true +-- 8|v1.0!|true +-- 9|v1.0a|false +-- 10|v1.0beta|false +SELECT `id`, `log_message`, `log_message` @@ 'v1.0' as `matches_version` FROM logs WHERE `id` BETWEEN 7 AND 10 ORDER BY `id`; + +-- Test complete word matching for '/start' +-- Expect: +-- 11|GET /app/start|true +-- 12|Command: /start-prosess|true +-- 13|Command: /start|true +-- 14|start|false +-- 15|start/stop|false +SELECT `id`, `log_message`, `log_message` @@ '/start' as `matches_start` FROM logs WHERE `id` BETWEEN 11 AND 15 ORDER BY `id`; + +-- Test phrase matching for 'system failure' +-- Expect: +-- 16|Alert: system failure detected|true +-- 17|system failure!|true +-- 18|system-failure|false +-- 19|system failure2023|false +SELECT `id`, `log_message`, `log_message` @@ 'system failure' as `matches_phrase` FROM logs WHERE `id` BETWEEN 16 AND 19 ORDER BY `id`; + + +-- Test multi-word matching using AND +-- Expect: +-- 20|critical error: system failure|true|true|true +-- 21|critical failure detected|true|true|true +-- 22|critical issue|true|false|false +-- 23|failure imminent|false|true|false +SELECT `id`, `log_message`, + `log_message` @@ 'critical' as `matches_critical`, + `log_message` @@ 'failure' as `matches_failure`, + `log_message` @@ 'critical' AND `log_message` @@ 'failure' as `matches_both` +FROM logs WHERE `id` BETWEEN 20 AND 23 ORDER BY `id`; + +-- Test case-insensitive matching using lower() +-- Expect: +-- 24|Warning: high temperature|true +-- 25|WARNING: system overload|true +-- 26|warned|false +-- 27|warnings|false +SELECT `id`, `log_message`, lower(`log_message`) @@ 'warning' as `matches_warning` FROM logs WHERE `id` >= 24 ORDER BY `id`; + +DROP TABLE logs; diff --git a/tests/cases/standalone/common/flow/flow_advance_ttl.result b/tests/cases/standalone/common/flow/flow_advance_ttl.result index 38d14d6b31..3f5a940977 100644 --- a/tests/cases/standalone/common/flow/flow_advance_ttl.result +++ b/tests/cases/standalone/common/flow/flow_advance_ttl.result @@ -8,6 +8,20 @@ CREATE TABLE distinct_basic ( Affected Rows: 0 +-- should fail +-- SQLNESS REPLACE id=\d+ id=REDACTED +CREATE FLOW test_distinct_basic SINK TO out_distinct_basic AS +SELECT + DISTINCT number as dis +FROM + distinct_basic; + +Error: 3001(EngineExecuteQuery), Unsupported: Source table `greptime.public.distinct_basic`(id=REDACTED) has instant TTL, Instant TTL is not supported under batching mode. Consider using a TTL longer than flush interval + +ALTER TABLE distinct_basic SET 'ttl' = '5s'; + +Affected Rows: 0 + CREATE FLOW test_distinct_basic SINK TO out_distinct_basic AS SELECT DISTINCT number as dis @@ -24,7 +38,7 @@ VALUES (20, "2021-07-01 00:00:00.200"), (22, "2021-07-01 00:00:00.600"); -Affected Rows: 0 +Affected Rows: 3 -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_distinct_basic'); @@ -49,7 +63,7 @@ SHOW CREATE TABLE distinct_basic; | | | | | ENGINE=mito | | | WITH( | -| | ttl = 'instant' | +| | ttl = '5s' | | | ) | +----------------+-----------------------------------------------------------+ @@ -84,8 +98,93 @@ FROM SELECT number FROM distinct_basic; -++ -++ ++--------+ +| number | ++--------+ +| 20 | +| 22 | ++--------+ + +-- SQLNESS SLEEP 6s +ADMIN FLUSH_TABLE('distinct_basic'); + ++-------------------------------------+ +| ADMIN FLUSH_TABLE('distinct_basic') | ++-------------------------------------+ +| 0 | ++-------------------------------------+ + +INSERT INTO + distinct_basic +VALUES + (23, "2021-07-01 00:00:01.600"); + +Affected Rows: 1 + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('test_distinct_basic'); + ++-----------------------------------------+ +| ADMIN FLUSH_FLOW('test_distinct_basic') | ++-----------------------------------------+ +| FLOW_FLUSHED | ++-----------------------------------------+ + +SHOW CREATE TABLE distinct_basic; + ++----------------+-----------------------------------------------------------+ +| Table | Create Table | ++----------------+-----------------------------------------------------------+ +| distinct_basic | CREATE TABLE IF NOT EXISTS "distinct_basic" ( | +| | "number" INT NULL, | +| | "ts" TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(), | +| | TIME INDEX ("ts"), | +| | PRIMARY KEY ("number") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | ttl = '5s' | +| | ) | ++----------------+-----------------------------------------------------------+ + +SHOW CREATE TABLE out_distinct_basic; + ++--------------------+---------------------------------------------------+ +| Table | Create Table | ++--------------------+---------------------------------------------------+ +| out_distinct_basic | CREATE TABLE IF NOT EXISTS "out_distinct_basic" ( | +| | "dis" INT NULL, | +| | "update_at" TIMESTAMP(3) NULL, | +| | "__ts_placeholder" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("__ts_placeholder"), | +| | PRIMARY KEY ("dis") | +| | ) | +| | | +| | ENGINE=mito | +| | | ++--------------------+---------------------------------------------------+ + +SELECT + dis +FROM + out_distinct_basic; + ++-----+ +| dis | ++-----+ +| 20 | +| 22 | +| 23 | ++-----+ + +SELECT number FROM distinct_basic; + ++--------+ +| number | ++--------+ +| 23 | ++--------+ DROP FLOW test_distinct_basic; diff --git a/tests/cases/standalone/common/flow/flow_advance_ttl.sql b/tests/cases/standalone/common/flow/flow_advance_ttl.sql index 18dfea25db..2691af2b0c 100644 --- a/tests/cases/standalone/common/flow/flow_advance_ttl.sql +++ b/tests/cases/standalone/common/flow/flow_advance_ttl.sql @@ -6,6 +6,16 @@ CREATE TABLE distinct_basic ( TIME INDEX(ts) )WITH ('ttl' = 'instant'); +-- should fail +-- SQLNESS REPLACE id=\d+ id=REDACTED +CREATE FLOW test_distinct_basic SINK TO out_distinct_basic AS +SELECT + DISTINCT number as dis +FROM + distinct_basic; + +ALTER TABLE distinct_basic SET 'ttl' = '5s'; + CREATE FLOW test_distinct_basic SINK TO out_distinct_basic AS SELECT DISTINCT number as dis @@ -34,6 +44,28 @@ FROM SELECT number FROM distinct_basic; +-- SQLNESS SLEEP 6s +ADMIN FLUSH_TABLE('distinct_basic'); + +INSERT INTO + distinct_basic +VALUES + (23, "2021-07-01 00:00:01.600"); + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('test_distinct_basic'); + +SHOW CREATE TABLE distinct_basic; + +SHOW CREATE TABLE out_distinct_basic; + +SELECT + dis +FROM + out_distinct_basic; + +SELECT number FROM distinct_basic; + DROP FLOW test_distinct_basic; DROP TABLE distinct_basic; DROP TABLE out_distinct_basic; \ No newline at end of file diff --git a/tests/cases/standalone/common/flow/flow_auto_sink_table.result b/tests/cases/standalone/common/flow/flow_auto_sink_table.result index ebd19a828d..de8a44fad7 100644 --- a/tests/cases/standalone/common/flow/flow_auto_sink_table.result +++ b/tests/cases/standalone/common/flow/flow_auto_sink_table.result @@ -9,11 +9,12 @@ Affected Rows: 0 CREATE FLOW test_numbers_basic SINK TO out_num_cnt_basic AS SELECT - sum(number) + sum(number), + date_bin(INTERVAL '1 second', ts, '2021-07-01 00:00:00') as time_window FROM numbers_input_basic GROUP BY - tumble(ts, '1 second', '2021-07-01 00:00:00'); + time_window; Affected Rows: 0 @@ -24,11 +25,9 @@ SHOW CREATE TABLE out_num_cnt_basic; +-------------------+--------------------------------------------------+ | out_num_cnt_basic | CREATE TABLE IF NOT EXISTS "out_num_cnt_basic" ( | | | "sum(numbers_input_basic.number)" BIGINT NULL, | -| | "window_start" TIMESTAMP(3) NOT NULL, | -| | "window_end" TIMESTAMP(3) NULL, | +| | "time_window" TIMESTAMP(9) NOT NULL, | | | "update_at" TIMESTAMP(3) NULL, | -| | TIME INDEX ("window_start"), | -| | PRIMARY KEY ("window_end") | +| | TIME INDEX ("time_window") | | | ) | | | | | | ENGINE=mito | @@ -52,11 +51,9 @@ SHOW CREATE TABLE out_num_cnt_basic; +-------------------+--------------------------------------------------+ | out_num_cnt_basic | CREATE TABLE IF NOT EXISTS "out_num_cnt_basic" ( | | | "sum(numbers_input_basic.number)" BIGINT NULL, | -| | "window_start" TIMESTAMP(3) NOT NULL, | -| | "window_end" TIMESTAMP(3) NULL, | +| | "time_window" TIMESTAMP(9) NOT NULL, | | | "update_at" TIMESTAMP(3) NULL, | -| | TIME INDEX ("window_start"), | -| | PRIMARY KEY ("window_end") | +| | TIME INDEX ("time_window") | | | ) | | | | | | ENGINE=mito | @@ -65,13 +62,13 @@ SHOW CREATE TABLE out_num_cnt_basic; SHOW CREATE FLOW test_numbers_basic; -+--------------------+-------------------------------------------------------------------------------------------------------+ -| Flow | Create Flow | -+--------------------+-------------------------------------------------------------------------------------------------------+ -| test_numbers_basic | CREATE FLOW IF NOT EXISTS test_numbers_basic | -| | SINK TO out_num_cnt_basic | -| | AS SELECT sum(number) FROM numbers_input_basic GROUP BY tumble(ts, '1 second', '2021-07-01 00:00:00') | -+--------------------+-------------------------------------------------------------------------------------------------------+ ++--------------------+----------------------------------------------------------------------------------------------------------------------------------------------+ +| Flow | Create Flow | ++--------------------+----------------------------------------------------------------------------------------------------------------------------------------------+ +| test_numbers_basic | CREATE FLOW IF NOT EXISTS test_numbers_basic | +| | SINK TO out_num_cnt_basic | +| | AS SELECT sum(number), date_bin(INTERVAL '1 second', ts, '2021-07-01 00:00:00') AS time_window FROM numbers_input_basic GROUP BY time_window | ++--------------------+----------------------------------------------------------------------------------------------------------------------------------------------+ DROP FLOW test_numbers_basic; diff --git a/tests/cases/standalone/common/flow/flow_auto_sink_table.sql b/tests/cases/standalone/common/flow/flow_auto_sink_table.sql index 0af723770c..ca76ba767e 100644 --- a/tests/cases/standalone/common/flow/flow_auto_sink_table.sql +++ b/tests/cases/standalone/common/flow/flow_auto_sink_table.sql @@ -7,11 +7,12 @@ CREATE TABLE numbers_input_basic ( CREATE FLOW test_numbers_basic SINK TO out_num_cnt_basic AS SELECT - sum(number) + sum(number), + date_bin(INTERVAL '1 second', ts, '2021-07-01 00:00:00') as time_window FROM numbers_input_basic GROUP BY - tumble(ts, '1 second', '2021-07-01 00:00:00'); + time_window; SHOW CREATE TABLE out_num_cnt_basic; diff --git a/tests/cases/standalone/common/flow/flow_basic.result b/tests/cases/standalone/common/flow/flow_basic.result index 511468f5a5..5b4e6b32ab 100644 --- a/tests/cases/standalone/common/flow/flow_basic.result +++ b/tests/cases/standalone/common/flow/flow_basic.result @@ -9,11 +9,12 @@ Affected Rows: 0 CREATE FLOW test_numbers_basic SINK TO out_num_cnt_basic AS SELECT - sum(number) + sum(number), + date_bin(INTERVAL '1 second', ts, '2021-07-01 00:00:00') as time_window FROM numbers_input_basic GROUP BY - tumble(ts, '1 second', '2021-07-01 00:00:00'); + time_window; Affected Rows: 0 @@ -24,11 +25,9 @@ SHOW CREATE TABLE out_num_cnt_basic; +-------------------+--------------------------------------------------+ | out_num_cnt_basic | CREATE TABLE IF NOT EXISTS "out_num_cnt_basic" ( | | | "sum(numbers_input_basic.number)" BIGINT NULL, | -| | "window_start" TIMESTAMP(3) NOT NULL, | -| | "window_end" TIMESTAMP(3) NULL, | +| | "time_window" TIMESTAMP(9) NOT NULL, | | | "update_at" TIMESTAMP(3) NULL, | -| | TIME INDEX ("window_start"), | -| | PRIMARY KEY ("window_end") | +| | TIME INDEX ("time_window") | | | ) | | | | | | ENGINE=mito | @@ -53,11 +52,9 @@ SHOW CREATE TABLE out_num_cnt_basic; +-------------------+--------------------------------------------------+ | out_num_cnt_basic | CREATE TABLE IF NOT EXISTS "out_num_cnt_basic" ( | | | "sum(numbers_input_basic.number)" BIGINT NULL, | -| | "window_start" TIMESTAMP(3) NOT NULL, | -| | "window_end" TIMESTAMP(3) NULL, | +| | "time_window" TIMESTAMP(9) NOT NULL, | | | "update_at" TIMESTAMP(3) NULL, | -| | TIME INDEX ("window_start"), | -| | PRIMARY KEY ("window_end") | +| | TIME INDEX ("time_window") | | | ) | | | | | | ENGINE=mito | @@ -84,16 +81,15 @@ ADMIN FLUSH_FLOW('test_numbers_basic'); SELECT "sum(numbers_input_basic.number)", - window_start, - window_end + time_window FROM out_num_cnt_basic; -+---------------------------------+---------------------+---------------------+ -| sum(numbers_input_basic.number) | window_start | window_end | -+---------------------------------+---------------------+---------------------+ -| 42 | 2021-07-01T00:00:00 | 2021-07-01T00:00:01 | -+---------------------------------+---------------------+---------------------+ ++---------------------------------+---------------------+ +| sum(numbers_input_basic.number) | time_window | ++---------------------------------+---------------------+ +| 42 | 2021-07-01T00:00:00 | ++---------------------------------+---------------------+ -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_numbers_basic'); @@ -124,17 +120,16 @@ ADMIN FLUSH_FLOW('test_numbers_basic'); -- note that this quote-unquote column is a column-name, **not** a aggregation expr, generated by datafusion SELECT "sum(numbers_input_basic.number)", - window_start, - window_end + time_window FROM out_num_cnt_basic; -+---------------------------------+---------------------+---------------------+ -| sum(numbers_input_basic.number) | window_start | window_end | -+---------------------------------+---------------------+---------------------+ -| 42 | 2021-07-01T00:00:00 | 2021-07-01T00:00:01 | -| 47 | 2021-07-01T00:00:01 | 2021-07-01T00:00:02 | -+---------------------------------+---------------------+---------------------+ ++---------------------------------+---------------------+ +| sum(numbers_input_basic.number) | time_window | ++---------------------------------+---------------------+ +| 42 | 2021-07-01T00:00:00 | +| 47 | 2021-07-01T00:00:01 | ++---------------------------------+---------------------+ DROP FLOW test_numbers_basic; @@ -896,6 +891,8 @@ CREATE TABLE temp_sensor_data ( loc STRING, temperature DOUBLE, ts TIMESTAMP TIME INDEX +)WITH( + append_mode = 'true' ); Affected Rows: 0 @@ -904,7 +901,8 @@ CREATE TABLE temp_alerts ( sensor_id INT, loc STRING, max_temp DOUBLE, - ts TIMESTAMP TIME INDEX + event_ts TIMESTAMP TIME INDEX, + update_at TIMESTAMP ); Affected Rows: 0 @@ -914,6 +912,7 @@ SELECT sensor_id, loc, max(temperature) as max_temp, + max(ts) as event_ts FROM temp_sensor_data GROUP BY @@ -933,8 +932,9 @@ SHOW CREATE TABLE temp_alerts; | | "sensor_id" INT NULL, | | | "loc" STRING NULL, | | | "max_temp" DOUBLE NULL, | -| | "ts" TIMESTAMP(3) NOT NULL, | -| | TIME INDEX ("ts") | +| | "event_ts" TIMESTAMP(3) NOT NULL, | +| | "update_at" TIMESTAMP(3) NULL, | +| | TIME INDEX ("event_ts") | | | ) | | | | | | ENGINE=mito | @@ -993,15 +993,16 @@ SHOW TABLES LIKE 'temp_alerts'; SELECT sensor_id, loc, - max_temp + max_temp, + event_ts FROM temp_alerts; -+-----------+-------+----------+ -| sensor_id | loc | max_temp | -+-----------+-------+----------+ -| 1 | room1 | 150.0 | -+-----------+-------+----------+ ++-----------+-------+----------+-------------------------+ +| sensor_id | loc | max_temp | event_ts | ++-----------+-------+----------+-------------------------+ +| 1 | room1 | 150.0 | 1970-01-01T00:00:00.001 | ++-----------+-------+----------+-------------------------+ INSERT INTO temp_sensor_data @@ -1022,15 +1023,16 @@ ADMIN FLUSH_FLOW('temp_monitoring'); SELECT sensor_id, loc, - max_temp + max_temp, + event_ts FROM temp_alerts; -+-----------+-------+----------+ -| sensor_id | loc | max_temp | -+-----------+-------+----------+ -| 1 | room1 | 150.0 | -+-----------+-------+----------+ ++-----------+-------+----------+-------------------------+ +| sensor_id | loc | max_temp | event_ts | ++-----------+-------+----------+-------------------------+ +| 1 | room1 | 150.0 | 1970-01-01T00:00:00.001 | ++-----------+-------+----------+-------------------------+ DROP FLOW temp_monitoring; @@ -1049,6 +1051,8 @@ CREATE TABLE ngx_access_log ( stat INT, size INT, access_time TIMESTAMP TIME INDEX +)WITH( + append_mode = 'true' ); Affected Rows: 0 @@ -1183,6 +1187,8 @@ CREATE TABLE requests ( service_ip STRING, val INT, ts TIMESTAMP TIME INDEX +)WITH( + append_mode = 'true' ); Affected Rows: 0 @@ -1392,6 +1398,8 @@ CREATE TABLE android_log ( `log` STRING, ts TIMESTAMP(9), TIME INDEX(ts) +)WITH( + append_mode = 'true' ); Affected Rows: 0 @@ -1503,6 +1511,8 @@ CREATE TABLE android_log ( `log` STRING, ts TIMESTAMP(9), TIME INDEX(ts) +)WITH( + append_mode = 'true' ); Affected Rows: 0 diff --git a/tests/cases/standalone/common/flow/flow_basic.sql b/tests/cases/standalone/common/flow/flow_basic.sql index bafc4c266e..32598927ab 100644 --- a/tests/cases/standalone/common/flow/flow_basic.sql +++ b/tests/cases/standalone/common/flow/flow_basic.sql @@ -7,11 +7,12 @@ CREATE TABLE numbers_input_basic ( CREATE FLOW test_numbers_basic SINK TO out_num_cnt_basic AS SELECT - sum(number) + sum(number), + date_bin(INTERVAL '1 second', ts, '2021-07-01 00:00:00') as time_window FROM numbers_input_basic GROUP BY - tumble(ts, '1 second', '2021-07-01 00:00:00'); + time_window; SHOW CREATE TABLE out_num_cnt_basic; @@ -34,8 +35,7 @@ ADMIN FLUSH_FLOW('test_numbers_basic'); SELECT "sum(numbers_input_basic.number)", - window_start, - window_end + time_window FROM out_num_cnt_basic; @@ -54,8 +54,7 @@ ADMIN FLUSH_FLOW('test_numbers_basic'); -- note that this quote-unquote column is a column-name, **not** a aggregation expr, generated by datafusion SELECT "sum(numbers_input_basic.number)", - window_start, - window_end + time_window FROM out_num_cnt_basic; @@ -403,13 +402,16 @@ CREATE TABLE temp_sensor_data ( loc STRING, temperature DOUBLE, ts TIMESTAMP TIME INDEX +)WITH( + append_mode = 'true' ); CREATE TABLE temp_alerts ( sensor_id INT, loc STRING, max_temp DOUBLE, - ts TIMESTAMP TIME INDEX + event_ts TIMESTAMP TIME INDEX, + update_at TIMESTAMP ); CREATE FLOW temp_monitoring SINK TO temp_alerts AS @@ -417,6 +419,7 @@ SELECT sensor_id, loc, max(temperature) as max_temp, + max(ts) as event_ts FROM temp_sensor_data GROUP BY @@ -451,7 +454,8 @@ SHOW TABLES LIKE 'temp_alerts'; SELECT sensor_id, loc, - max_temp + max_temp, + event_ts FROM temp_alerts; @@ -466,7 +470,8 @@ ADMIN FLUSH_FLOW('temp_monitoring'); SELECT sensor_id, loc, - max_temp + max_temp, + event_ts FROM temp_alerts; @@ -481,6 +486,8 @@ CREATE TABLE ngx_access_log ( stat INT, size INT, access_time TIMESTAMP TIME INDEX +)WITH( + append_mode = 'true' ); CREATE TABLE ngx_distribution ( @@ -555,6 +562,8 @@ CREATE TABLE requests ( service_ip STRING, val INT, ts TIMESTAMP TIME INDEX +)WITH( + append_mode = 'true' ); CREATE TABLE requests_without_ip ( @@ -650,6 +659,8 @@ CREATE TABLE android_log ( `log` STRING, ts TIMESTAMP(9), TIME INDEX(ts) +)WITH( + append_mode = 'true' ); CREATE TABLE android_log_abnormal ( @@ -704,6 +715,8 @@ CREATE TABLE android_log ( `log` STRING, ts TIMESTAMP(9), TIME INDEX(ts) +)WITH( + append_mode = 'true' ); CREATE TABLE android_log_abnormal ( diff --git a/tests/cases/standalone/common/flow/flow_blog.result b/tests/cases/standalone/common/flow/flow_blog.result index 3046e147c0..9b90d1b068 100644 --- a/tests/cases/standalone/common/flow/flow_blog.result +++ b/tests/cases/standalone/common/flow/flow_blog.result @@ -19,7 +19,9 @@ Affected Rows: 0 CREATE FLOW calc_avg_speed SINK TO avg_speed AS SELECT - avg((left_wheel + right_wheel) / 2) + avg((left_wheel + right_wheel) / 2) as avg_speed, + date_bin(INTERVAL '5 second', ts) as start_window, + date_bin(INTERVAL '5 second', ts) + INTERVAL '5 second' as end_window, FROM velocity WHERE @@ -28,7 +30,7 @@ WHERE AND left_wheel < 60 AND right_wheel < 60 GROUP BY - tumble(ts, '5 second'); + start_window; Affected Rows: 0 diff --git a/tests/cases/standalone/common/flow/flow_blog.sql b/tests/cases/standalone/common/flow/flow_blog.sql index f40614bd9a..4255aa1875 100644 --- a/tests/cases/standalone/common/flow/flow_blog.sql +++ b/tests/cases/standalone/common/flow/flow_blog.sql @@ -15,7 +15,9 @@ CREATE TABLE avg_speed ( CREATE FLOW calc_avg_speed SINK TO avg_speed AS SELECT - avg((left_wheel + right_wheel) / 2) + avg((left_wheel + right_wheel) / 2) as avg_speed, + date_bin(INTERVAL '5 second', ts) as start_window, + date_bin(INTERVAL '5 second', ts) + INTERVAL '5 second' as end_window, FROM velocity WHERE @@ -24,7 +26,7 @@ WHERE AND left_wheel < 60 AND right_wheel < 60 GROUP BY - tumble(ts, '5 second'); + start_window; INSERT INTO velocity diff --git a/tests/cases/standalone/common/flow/flow_call_df_func.result b/tests/cases/standalone/common/flow/flow_call_df_func.result index d6423c7c7f..17f172c738 100644 --- a/tests/cases/standalone/common/flow/flow_call_df_func.result +++ b/tests/cases/standalone/common/flow/flow_call_df_func.result @@ -11,7 +11,7 @@ Affected Rows: 0 CREATE FLOW test_numbers_df_func SINK TO out_num_cnt_df_func AS -SELECT sum(abs(number)) FROM numbers_input_df_func GROUP BY tumble(ts, '1 second', '2021-07-01 00:00:00'); +SELECT sum(abs(number)), date_bin(INTERVAL '1 second', ts, '2021-07-01 00:00:00') as time_window FROM numbers_input_df_func GROUP BY time_window; Affected Rows: 0 @@ -42,13 +42,13 @@ ADMIN FLUSH_FLOW('test_numbers_df_func'); +------------------------------------------+ -- note that this quote-unquote column is a column-name, **not** a aggregation expr, generated by datafusion -SELECT "sum(abs(numbers_input_df_func.number))", window_start, window_end FROM out_num_cnt_df_func; +SELECT "sum(abs(numbers_input_df_func.number))", time_window FROM out_num_cnt_df_func; -+----------------------------------------+---------------------+---------------------+ -| sum(abs(numbers_input_df_func.number)) | window_start | window_end | -+----------------------------------------+---------------------+---------------------+ -| 42 | 2021-07-01T00:00:00 | 2021-07-01T00:00:01 | -+----------------------------------------+---------------------+---------------------+ ++----------------------------------------+---------------------+ +| sum(abs(numbers_input_df_func.number)) | time_window | ++----------------------------------------+---------------------+ +| 42 | 2021-07-01T00:00:00 | ++----------------------------------------+---------------------+ -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_numbers_df_func'); @@ -76,14 +76,14 @@ ADMIN FLUSH_FLOW('test_numbers_df_func'); +------------------------------------------+ -- note that this quote-unquote column is a column-name, **not** a aggregation expr, generated by datafusion -SELECT "sum(abs(numbers_input_df_func.number))", window_start, window_end FROM out_num_cnt_df_func; +SELECT "sum(abs(numbers_input_df_func.number))", time_window FROM out_num_cnt_df_func; -+----------------------------------------+---------------------+---------------------+ -| sum(abs(numbers_input_df_func.number)) | window_start | window_end | -+----------------------------------------+---------------------+---------------------+ -| 42 | 2021-07-01T00:00:00 | 2021-07-01T00:00:01 | -| 47 | 2021-07-01T00:00:01 | 2021-07-01T00:00:02 | -+----------------------------------------+---------------------+---------------------+ ++----------------------------------------+---------------------+ +| sum(abs(numbers_input_df_func.number)) | time_window | ++----------------------------------------+---------------------+ +| 42 | 2021-07-01T00:00:00 | +| 47 | 2021-07-01T00:00:01 | ++----------------------------------------+---------------------+ DROP FLOW test_numbers_df_func; @@ -110,7 +110,7 @@ Affected Rows: 0 CREATE FLOW test_numbers_df_func SINK TO out_num_cnt_df_func AS -SELECT abs(sum(number)) FROM numbers_input_df_func GROUP BY tumble(ts, '1 second', '2021-07-01 00:00:00'); +SELECT abs(sum(number)), date_bin(INTERVAL '1 second', ts, '2021-07-01 00:00:00') as time_window FROM numbers_input_df_func GROUP BY time_window; Affected Rows: 0 @@ -140,13 +140,13 @@ ADMIN FLUSH_FLOW('test_numbers_df_func'); | FLOW_FLUSHED | +------------------------------------------+ -SELECT "abs(sum(numbers_input_df_func.number))", window_start, window_end FROM out_num_cnt_df_func; +SELECT "abs(sum(numbers_input_df_func.number))", time_window FROM out_num_cnt_df_func; -+----------------------------------------+---------------------+---------------------+ -| abs(sum(numbers_input_df_func.number)) | window_start | window_end | -+----------------------------------------+---------------------+---------------------+ -| 2 | 2021-07-01T00:00:00 | 2021-07-01T00:00:01 | -+----------------------------------------+---------------------+---------------------+ ++----------------------------------------+---------------------+ +| abs(sum(numbers_input_df_func.number)) | time_window | ++----------------------------------------+---------------------+ +| 2 | 2021-07-01T00:00:00 | ++----------------------------------------+---------------------+ -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_numbers_df_func'); @@ -173,14 +173,14 @@ ADMIN FLUSH_FLOW('test_numbers_df_func'); | FLOW_FLUSHED | +------------------------------------------+ -SELECT "abs(sum(numbers_input_df_func.number))", window_start, window_end FROM out_num_cnt_df_func; +SELECT "abs(sum(numbers_input_df_func.number))", time_window FROM out_num_cnt_df_func; -+----------------------------------------+---------------------+---------------------+ -| abs(sum(numbers_input_df_func.number)) | window_start | window_end | -+----------------------------------------+---------------------+---------------------+ -| 2 | 2021-07-01T00:00:00 | 2021-07-01T00:00:01 | -| 1 | 2021-07-01T00:00:01 | 2021-07-01T00:00:02 | -+----------------------------------------+---------------------+---------------------+ ++----------------------------------------+---------------------+ +| abs(sum(numbers_input_df_func.number)) | time_window | ++----------------------------------------+---------------------+ +| 2 | 2021-07-01T00:00:00 | +| 1 | 2021-07-01T00:00:01 | ++----------------------------------------+---------------------+ DROP FLOW test_numbers_df_func; diff --git a/tests/cases/standalone/common/flow/flow_call_df_func.sql b/tests/cases/standalone/common/flow/flow_call_df_func.sql index 6143f493f4..83c4090094 100644 --- a/tests/cases/standalone/common/flow/flow_call_df_func.sql +++ b/tests/cases/standalone/common/flow/flow_call_df_func.sql @@ -9,7 +9,7 @@ CREATE TABLE numbers_input_df_func ( CREATE FLOW test_numbers_df_func SINK TO out_num_cnt_df_func AS -SELECT sum(abs(number)) FROM numbers_input_df_func GROUP BY tumble(ts, '1 second', '2021-07-01 00:00:00'); +SELECT sum(abs(number)), date_bin(INTERVAL '1 second', ts, '2021-07-01 00:00:00') as time_window FROM numbers_input_df_func GROUP BY time_window; -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_numbers_df_func'); @@ -24,7 +24,7 @@ VALUES ADMIN FLUSH_FLOW('test_numbers_df_func'); -- note that this quote-unquote column is a column-name, **not** a aggregation expr, generated by datafusion -SELECT "sum(abs(numbers_input_df_func.number))", window_start, window_end FROM out_num_cnt_df_func; +SELECT "sum(abs(numbers_input_df_func.number))", time_window FROM out_num_cnt_df_func; -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_numbers_df_func'); @@ -38,7 +38,7 @@ VALUES ADMIN FLUSH_FLOW('test_numbers_df_func'); -- note that this quote-unquote column is a column-name, **not** a aggregation expr, generated by datafusion -SELECT "sum(abs(numbers_input_df_func.number))", window_start, window_end FROM out_num_cnt_df_func; +SELECT "sum(abs(numbers_input_df_func.number))", time_window FROM out_num_cnt_df_func; DROP FLOW test_numbers_df_func; DROP TABLE numbers_input_df_func; @@ -55,7 +55,7 @@ CREATE TABLE numbers_input_df_func ( CREATE FLOW test_numbers_df_func SINK TO out_num_cnt_df_func AS -SELECT abs(sum(number)) FROM numbers_input_df_func GROUP BY tumble(ts, '1 second', '2021-07-01 00:00:00'); +SELECT abs(sum(number)), date_bin(INTERVAL '1 second', ts, '2021-07-01 00:00:00') as time_window FROM numbers_input_df_func GROUP BY time_window; -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_numbers_df_func'); @@ -69,7 +69,7 @@ VALUES -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_numbers_df_func'); -SELECT "abs(sum(numbers_input_df_func.number))", window_start, window_end FROM out_num_cnt_df_func; +SELECT "abs(sum(numbers_input_df_func.number))", time_window FROM out_num_cnt_df_func; -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_numbers_df_func'); @@ -82,7 +82,7 @@ VALUES -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_numbers_df_func'); -SELECT "abs(sum(numbers_input_df_func.number))", window_start, window_end FROM out_num_cnt_df_func; +SELECT "abs(sum(numbers_input_df_func.number))", time_window FROM out_num_cnt_df_func; DROP FLOW test_numbers_df_func; DROP TABLE numbers_input_df_func; diff --git a/tests/cases/standalone/common/flow/flow_flush.result b/tests/cases/standalone/common/flow/flow_flush.result new file mode 100644 index 0000000000..f9a8a43af8 --- /dev/null +++ b/tests/cases/standalone/common/flow/flow_flush.result @@ -0,0 +1,62 @@ +-- test if flush_flow works and flush old data to flow for compute +CREATE TABLE numbers_input_basic ( + number INT, + ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY(number), + TIME INDEX(ts) +); + +Affected Rows: 0 + +INSERT INTO + numbers_input_basic +VALUES + (20, "2021-07-01 00:00:00.200"), + (22, "2021-07-01 00:00:00.600"); + +Affected Rows: 2 + +CREATE FLOW test_numbers_basic SINK TO out_num_cnt_basic AS +SELECT + sum(number), + date_bin(INTERVAL '1 second', ts, '2021-07-01 00:00:00.1') as time_window +FROM + numbers_input_basic +GROUP BY + time_window; + +Affected Rows: 0 + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('test_numbers_basic'); + ++----------------------------------------+ +| ADMIN FLUSH_FLOW('test_numbers_basic') | ++----------------------------------------+ +| FLOW_FLUSHED | ++----------------------------------------+ + +SELECT + "sum(numbers_input_basic.number)", + time_window +FROM + out_num_cnt_basic; + ++---------------------------------+-------------------------+ +| sum(numbers_input_basic.number) | time_window | ++---------------------------------+-------------------------+ +| 42 | 2021-07-01T00:00:00.100 | ++---------------------------------+-------------------------+ + +DROP FLOW test_numbers_basic; + +Affected Rows: 0 + +DROP TABLE numbers_input_basic; + +Affected Rows: 0 + +DROP TABLE out_num_cnt_basic; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/flow/flow_flush.sql b/tests/cases/standalone/common/flow/flow_flush.sql new file mode 100644 index 0000000000..9dca98baf7 --- /dev/null +++ b/tests/cases/standalone/common/flow/flow_flush.sql @@ -0,0 +1,37 @@ +-- test if flush_flow works and flush old data to flow for compute +CREATE TABLE numbers_input_basic ( + number INT, + ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY(number), + TIME INDEX(ts) +); + +INSERT INTO + numbers_input_basic +VALUES + (20, "2021-07-01 00:00:00.200"), + (22, "2021-07-01 00:00:00.600"); + +CREATE FLOW test_numbers_basic SINK TO out_num_cnt_basic AS +SELECT + sum(number), + date_bin(INTERVAL '1 second', ts, '2021-07-01 00:00:00.1') as time_window +FROM + numbers_input_basic +GROUP BY + time_window; + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('test_numbers_basic'); + +SELECT + "sum(numbers_input_basic.number)", + time_window +FROM + out_num_cnt_basic; + +DROP FLOW test_numbers_basic; + +DROP TABLE numbers_input_basic; + +DROP TABLE out_num_cnt_basic; diff --git a/tests/cases/standalone/common/flow/flow_null.result b/tests/cases/standalone/common/flow/flow_null.result index aaa151c51e..4ee94cfc09 100644 --- a/tests/cases/standalone/common/flow/flow_null.result +++ b/tests/cases/standalone/common/flow/flow_null.result @@ -5,6 +5,8 @@ CREATE TABLE requests ( service_ip STRING, val INT, ts TIMESTAMP TIME INDEX +)WITH( + append_mode = 'true' ); Affected Rows: 0 @@ -93,6 +95,8 @@ CREATE TABLE ngx_access_log ( client STRING, country STRING, access_time TIMESTAMP TIME INDEX +)WITH( + append_mode = 'true' ); Affected Rows: 0 diff --git a/tests/cases/standalone/common/flow/flow_null.sql b/tests/cases/standalone/common/flow/flow_null.sql index b2bdfd74df..680d404b78 100644 --- a/tests/cases/standalone/common/flow/flow_null.sql +++ b/tests/cases/standalone/common/flow/flow_null.sql @@ -6,6 +6,8 @@ CREATE TABLE requests ( service_ip STRING, val INT, ts TIMESTAMP TIME INDEX +)WITH( + append_mode = 'true' ); CREATE TABLE sum_val_in_reqs ( @@ -59,6 +61,8 @@ CREATE TABLE ngx_access_log ( client STRING, country STRING, access_time TIMESTAMP TIME INDEX +)WITH( + append_mode = 'true' ); CREATE FLOW calc_ngx_country SINK TO ngx_country AS diff --git a/tests/cases/standalone/common/flow/flow_rebuild.result b/tests/cases/standalone/common/flow/flow_rebuild.result index 67fd43a032..521bcdb5ae 100644 --- a/tests/cases/standalone/common/flow/flow_rebuild.result +++ b/tests/cases/standalone/common/flow/flow_rebuild.result @@ -3,6 +3,8 @@ CREATE TABLE input_basic ( ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(number), TIME INDEX(ts) +)WITH( + append_mode = 'true' ); Affected Rows: 0 @@ -166,7 +168,7 @@ ADMIN FLUSH_FLOW('test_wildcard_basic'); | FLOW_FLUSHED | +-----------------------------------------+ --- 3 is also expected, since flow don't have persisent state +-- flow batching mode SELECT wildcard FROM out_basic; +----------+ @@ -175,6 +177,14 @@ SELECT wildcard FROM out_basic; | 3 | +----------+ +SELECT count(*) FROM input_basic; + ++----------+ +| count(*) | ++----------+ +| 3 | ++----------+ + DROP TABLE input_basic; Affected Rows: 0 @@ -302,6 +312,15 @@ FROM Affected Rows: 0 -- SQLNESS ARG restart=true +SELECT 1; + ++----------+ +| Int64(1) | ++----------+ +| 1 | ++----------+ + +-- SQLNESS SLEEP 3s INSERT INTO input_basic VALUES @@ -310,6 +329,8 @@ VALUES Affected Rows: 2 +-- give flownode a second to rebuild flow +-- SQLNESS SLEEP 3s -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_wildcard_basic'); @@ -358,6 +379,15 @@ FROM Affected Rows: 0 -- SQLNESS ARG restart=true +SELECT 1; + ++----------+ +| Int64(1) | ++----------+ +| 1 | ++----------+ + +-- SQLNESS SLEEP 3s INSERT INTO input_basic VALUES @@ -366,6 +396,8 @@ VALUES Affected Rows: 2 +-- give flownode a second to rebuild flow +-- SQLNESS SLEEP 3s -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_wildcard_basic'); @@ -397,6 +429,15 @@ CREATE TABLE input_basic ( Affected Rows: 0 -- SQLNESS ARG restart=true +SELECT 1; + ++----------+ +| Int64(1) | ++----------+ +| 1 | ++----------+ + +-- SQLNESS SLEEP 3s INSERT INTO input_basic VALUES @@ -406,6 +447,8 @@ VALUES Affected Rows: 3 +-- give flownode a second to rebuild flow +-- SQLNESS SLEEP 3s -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_wildcard_basic'); @@ -438,7 +481,17 @@ FROM Affected Rows: 0 +-- give flownode a second to rebuild flow -- SQLNESS ARG restart=true +SELECT 1; + ++----------+ +| Int64(1) | ++----------+ +| 1 | ++----------+ + +-- SQLNESS SLEEP 3s INSERT INTO input_basic VALUES @@ -457,13 +510,21 @@ ADMIN FLUSH_FLOW('test_wildcard_basic'); | FLOW_FLUSHED | +-----------------------------------------+ --- 3 is also expected, since flow don't have persisent state +-- 4 is also expected, since flow batching mode SELECT wildcard FROM out_basic; +----------+ | wildcard | +----------+ -| 3 | +| 4 | ++----------+ + +SELECT count(*) FROM input_basic; + ++----------+ +| count(*) | ++----------+ +| 4 | +----------+ DROP TABLE input_basic; @@ -496,6 +557,15 @@ FROM Affected Rows: 0 -- SQLNESS ARG restart=true +SELECT 1; + ++----------+ +| Int64(1) | ++----------+ +| 1 | ++----------+ + +-- SQLNESS SLEEP 3s INSERT INTO input_basic VALUES @@ -504,6 +574,8 @@ VALUES Affected Rows: 2 +-- give flownode a second to rebuild flow +-- SQLNESS SLEEP 3s -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_wildcard_basic'); @@ -538,6 +610,15 @@ FROM Affected Rows: 0 -- SQLNESS ARG restart=true +SELECT 1; + ++----------+ +| Int64(1) | ++----------+ +| 1 | ++----------+ + +-- SQLNESS SLEEP 3s INSERT INTO input_basic VALUES @@ -547,6 +628,7 @@ VALUES Affected Rows: 3 +-- give flownode a second to rebuild flow -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_wildcard_basic'); @@ -576,3 +658,117 @@ DROP TABLE out_basic; Affected Rows: 0 +-- check if different schema is working as expected +CREATE DATABASE jsdp_log; + +Affected Rows: 1 + +USE jsdp_log; + +Affected Rows: 0 + +CREATE TABLE IF NOT EXISTS `api_log` ( + `time` TIMESTAMP(9) NOT NULL, + `key` STRING NULL SKIPPING INDEX WITH(granularity = '1024', type = 'BLOOM'), + `status_code` TINYINT NULL, + `method` STRING NULL, + `path` STRING NULL, + `raw_query` STRING NULL, + `user_agent` STRING NULL, + `client_ip` STRING NULL, + `duration` INT NULL, + `count` INT NULL, + TIME INDEX (`time`) +) ENGINE=mito WITH( + append_mode = 'true' +); + +Affected Rows: 0 + +CREATE TABLE IF NOT EXISTS `api_stats` ( + `time` TIMESTAMP(0) NOT NULL, + `key` STRING NULL, + `qpm` BIGINT NULL, + `rpm` BIGINT NULL, + `update_at` TIMESTAMP(3) NULL, + TIME INDEX (`time`), + PRIMARY KEY (`key`) +) ENGINE=mito WITH( + append_mode = 'false', + merge_mode = 'last_row' +); + +Affected Rows: 0 + +CREATE FLOW IF NOT EXISTS api_stats_flow +SINK TO api_stats EXPIRE AFTER '10 minute'::INTERVAL AS +SELECT date_trunc('minute', `time`::TimestampSecond) AS `time1`, `key`, count(*), sum(`count`) +FROM api_log +GROUP BY `time1`, `key`; + +Affected Rows: 0 + +INSERT INTO `api_log` (`time`, `key`, `status_code`, `method`, `path`, `raw_query`, `user_agent`, `client_ip`, `duration`, `count`) VALUES (now(), '1', 0, 'GET', '/lightning/v1/query', 'key=1&since=600', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', '1', 21, 1); + +Affected Rows: 1 + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('api_stats_flow'); + ++------------------------------------+ +| ADMIN FLUSH_FLOW('api_stats_flow') | ++------------------------------------+ +| FLOW_FLUSHED | ++------------------------------------+ + +SELECT key FROM api_stats; + ++-----+ +| key | ++-----+ +| 1 | ++-----+ + +-- SQLNESS ARG restart=true +INSERT INTO `api_log` (`time`, `key`, `status_code`, `method`, `path`, `raw_query`, `user_agent`, `client_ip`, `duration`, `count`) VALUES (now(), '2', 0, 'GET', '/lightning/v1/query', 'key=1&since=600', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', '1', 21, 1); + +Affected Rows: 1 + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('api_stats_flow'); + ++------------------------------------+ +| ADMIN FLUSH_FLOW('api_stats_flow') | ++------------------------------------+ +| FLOW_FLUSHED | ++------------------------------------+ + +SELECT key FROM api_stats; + ++-----+ +| key | ++-----+ +| 1 | +| 2 | ++-----+ + +DROP FLOW api_stats_flow; + +Affected Rows: 0 + +DROP TABLE api_log; + +Affected Rows: 0 + +DROP TABLE api_stats; + +Affected Rows: 0 + +USE public; + +Affected Rows: 0 + +DROP DATABASE jsdp_log; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/flow/flow_rebuild.sql b/tests/cases/standalone/common/flow/flow_rebuild.sql index 288d6f1f03..b2b6149761 100644 --- a/tests/cases/standalone/common/flow/flow_rebuild.sql +++ b/tests/cases/standalone/common/flow/flow_rebuild.sql @@ -3,6 +3,8 @@ CREATE TABLE input_basic ( ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(number), TIME INDEX(ts) +)WITH( + append_mode = 'true' ); CREATE FLOW test_wildcard_basic sink TO out_basic AS @@ -95,9 +97,11 @@ VALUES -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_wildcard_basic'); --- 3 is also expected, since flow don't have persisent state +-- flow batching mode SELECT wildcard FROM out_basic; +SELECT count(*) FROM input_basic; + DROP TABLE input_basic; DROP FLOW test_wildcard_basic; DROP TABLE out_basic; @@ -168,12 +172,17 @@ FROM input_basic; -- SQLNESS ARG restart=true +SELECT 1; + +-- SQLNESS SLEEP 3s INSERT INTO input_basic VALUES (23, "2021-07-01 00:00:01.000"), (24, "2021-07-01 00:00:01.500"); +-- give flownode a second to rebuild flow +-- SQLNESS SLEEP 3s -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_wildcard_basic'); @@ -201,12 +210,17 @@ FROM input_basic; -- SQLNESS ARG restart=true +SELECT 1; + +-- SQLNESS SLEEP 3s INSERT INTO input_basic VALUES (23, "2021-07-01 00:00:01.000"), (24, "2021-07-01 00:00:01.500"); +-- give flownode a second to rebuild flow +-- SQLNESS SLEEP 3s -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_wildcard_basic'); @@ -222,6 +236,9 @@ CREATE TABLE input_basic ( ); -- SQLNESS ARG restart=true +SELECT 1; + +-- SQLNESS SLEEP 3s INSERT INTO input_basic VALUES @@ -229,6 +246,8 @@ VALUES (24, "2021-07-01 00:00:01.500"), (26, "2021-07-01 00:00:02.000"); +-- give flownode a second to rebuild flow +-- SQLNESS SLEEP 3s -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_wildcard_basic'); @@ -245,7 +264,11 @@ SELECT FROM input_basic; +-- give flownode a second to rebuild flow -- SQLNESS ARG restart=true +SELECT 1; + +-- SQLNESS SLEEP 3s INSERT INTO input_basic VALUES @@ -256,9 +279,11 @@ VALUES -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_wildcard_basic'); --- 3 is also expected, since flow don't have persisent state +-- 4 is also expected, since flow batching mode SELECT wildcard FROM out_basic; +SELECT count(*) FROM input_basic; + DROP TABLE input_basic; DROP FLOW test_wildcard_basic; DROP TABLE out_basic; @@ -277,13 +302,17 @@ FROM input_basic; -- SQLNESS ARG restart=true +SELECT 1; + +-- SQLNESS SLEEP 3s INSERT INTO input_basic VALUES (23, "2021-07-01 00:00:01.000"), (24, "2021-07-01 00:00:01.500"); - +-- give flownode a second to rebuild flow +-- SQLNESS SLEEP 3s -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_wildcard_basic'); @@ -300,6 +329,9 @@ FROM input_basic; -- SQLNESS ARG restart=true +SELECT 1; + +-- SQLNESS SLEEP 3s INSERT INTO input_basic VALUES @@ -307,6 +339,7 @@ VALUES (24, "2021-07-01 00:00:01.500"), (25, "2021-07-01 00:00:01.700"); +-- give flownode a second to rebuild flow -- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | ADMIN FLUSH_FLOW('test_wildcard_basic'); @@ -317,3 +350,66 @@ DROP FLOW test_wildcard_basic; DROP TABLE input_basic; DROP TABLE out_basic; + +-- check if different schema is working as expected + +CREATE DATABASE jsdp_log; +USE jsdp_log; + +CREATE TABLE IF NOT EXISTS `api_log` ( + `time` TIMESTAMP(9) NOT NULL, + `key` STRING NULL SKIPPING INDEX WITH(granularity = '1024', type = 'BLOOM'), + `status_code` TINYINT NULL, + `method` STRING NULL, + `path` STRING NULL, + `raw_query` STRING NULL, + `user_agent` STRING NULL, + `client_ip` STRING NULL, + `duration` INT NULL, + `count` INT NULL, + TIME INDEX (`time`) +) ENGINE=mito WITH( + append_mode = 'true' +); + +CREATE TABLE IF NOT EXISTS `api_stats` ( + `time` TIMESTAMP(0) NOT NULL, + `key` STRING NULL, + `qpm` BIGINT NULL, + `rpm` BIGINT NULL, + `update_at` TIMESTAMP(3) NULL, + TIME INDEX (`time`), + PRIMARY KEY (`key`) +) ENGINE=mito WITH( + append_mode = 'false', + merge_mode = 'last_row' +); + +CREATE FLOW IF NOT EXISTS api_stats_flow +SINK TO api_stats EXPIRE AFTER '10 minute'::INTERVAL AS +SELECT date_trunc('minute', `time`::TimestampSecond) AS `time1`, `key`, count(*), sum(`count`) +FROM api_log +GROUP BY `time1`, `key`; + +INSERT INTO `api_log` (`time`, `key`, `status_code`, `method`, `path`, `raw_query`, `user_agent`, `client_ip`, `duration`, `count`) VALUES (now(), '1', 0, 'GET', '/lightning/v1/query', 'key=1&since=600', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', '1', 21, 1); + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('api_stats_flow'); + +SELECT key FROM api_stats; + +-- SQLNESS ARG restart=true +INSERT INTO `api_log` (`time`, `key`, `status_code`, `method`, `path`, `raw_query`, `user_agent`, `client_ip`, `duration`, `count`) VALUES (now(), '2', 0, 'GET', '/lightning/v1/query', 'key=1&since=600', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', '1', 21, 1); + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('api_stats_flow'); + +SELECT key FROM api_stats; + +DROP FLOW api_stats_flow; + +DROP TABLE api_log; +DROP TABLE api_stats; + +USE public; +DROP DATABASE jsdp_log; diff --git a/tests/cases/standalone/common/flow/flow_step_aggr.result b/tests/cases/standalone/common/flow/flow_step_aggr.result new file mode 100644 index 0000000000..ab76a67617 --- /dev/null +++ b/tests/cases/standalone/common/flow/flow_step_aggr.result @@ -0,0 +1,266 @@ +CREATE TABLE access_log ( + "url" STRING, + user_id BIGINT, + ts TIMESTAMP TIME INDEX, + PRIMARY KEY ("url", user_id) +); + +Affected Rows: 0 + +CREATE TABLE access_log_10s ( + "url" STRING, + time_window timestamp time INDEX, + state BINARY, + PRIMARY KEY ("url") +); + +Affected Rows: 0 + +CREATE FLOW calc_access_log_10s SINK TO access_log_10s +AS +SELECT + "url", + date_bin('10s'::INTERVAL, ts) AS time_window, + hll(user_id) AS state +FROM + access_log +GROUP BY + "url", + time_window; + +Affected Rows: 0 + +-- insert 4 rows of data +INSERT INTO access_log VALUES + ("/dashboard", 1, "2025-03-04 00:00:00"), + ("/dashboard", 1, "2025-03-04 00:00:01"), + ("/dashboard", 2, "2025-03-04 00:00:05"), + ("/not_found", 3, "2025-03-04 00:00:11"), + ("/dashboard", 4, "2025-03-04 00:00:15"); + +Affected Rows: 5 + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('calc_access_log_10s'); + ++-----------------------------------------+ +| ADMIN FLUSH_FLOW('calc_access_log_10s') | ++-----------------------------------------+ +| FLOW_FLUSHED | ++-----------------------------------------+ + +-- query should return 3 rows +SELECT "url", time_window FROM access_log_10s +ORDER BY + time_window; + ++------------+---------------------+ +| url | time_window | ++------------+---------------------+ +| /dashboard | 2025-03-04T00:00:00 | +| /dashboard | 2025-03-04T00:00:10 | +| /not_found | 2025-03-04T00:00:10 | ++------------+---------------------+ + +-- use hll_count to query the approximate data in access_log_10s +SELECT "url", time_window, hll_count(state) FROM access_log_10s +ORDER BY + time_window; + ++------------+---------------------+---------------------------------+ +| url | time_window | hll_count(access_log_10s.state) | ++------------+---------------------+---------------------------------+ +| /dashboard | 2025-03-04T00:00:00 | 2 | +| /dashboard | 2025-03-04T00:00:10 | 1 | +| /not_found | 2025-03-04T00:00:10 | 1 | ++------------+---------------------+---------------------------------+ + +-- further, we can aggregate 10 seconds of data to every minute, by using hll_merge to merge 10 seconds of hyperloglog state +SELECT + "url", + date_bin('1 minute'::INTERVAL, time_window) AS time_window_1m, + hll_count(hll_merge(state)) as uv_per_min +FROM + access_log_10s +GROUP BY + "url", + time_window_1m +ORDER BY + time_window_1m; + ++------------+---------------------+------------+ +| url | time_window_1m | uv_per_min | ++------------+---------------------+------------+ +| /not_found | 2025-03-04T00:00:00 | 1 | +| /dashboard | 2025-03-04T00:00:00 | 3 | ++------------+---------------------+------------+ + +DROP FLOW calc_access_log_10s; + +Affected Rows: 0 + +DROP TABLE access_log_10s; + +Affected Rows: 0 + +DROP TABLE access_log; + +Affected Rows: 0 + +CREATE TABLE percentile_base ( + "id" INT PRIMARY KEY, + "value" DOUBLE, + ts timestamp(0) time index +); + +Affected Rows: 0 + +CREATE TABLE percentile_5s ( + "percentile_state" BINARY, + time_window timestamp(0) time index +); + +Affected Rows: 0 + +CREATE FLOW calc_percentile_5s SINK TO percentile_5s +AS +SELECT + uddsketch_state(128, 0.01, "value") AS "value", + date_bin('5 seconds'::INTERVAL, ts) AS time_window +FROM + percentile_base +WHERE + "value" > 0 AND "value" < 70 +GROUP BY + time_window; + +Affected Rows: 0 + +INSERT INTO percentile_base ("id", "value", ts) VALUES + (1, 10.0, 1), + (2, 20.0, 2), + (3, 30.0, 3), + (4, 40.0, 4), + (5, 50.0, 5), + (6, 60.0, 6), + (7, 70.0, 7), + (8, 80.0, 8), + (9, 90.0, 9), + (10, 100.0, 10); + +Affected Rows: 10 + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('calc_percentile_5s'); + ++----------------------------------------+ +| ADMIN FLUSH_FLOW('calc_percentile_5s') | ++----------------------------------------+ +| FLOW_FLUSHED | ++----------------------------------------+ + +SELECT + time_window, + uddsketch_calc(0.99, `percentile_state`) AS p99 +FROM + percentile_5s +ORDER BY + time_window; + ++---------------------+--------------------+ +| time_window | p99 | ++---------------------+--------------------+ +| 1970-01-01T00:00:00 | 40.04777053326359 | +| 1970-01-01T00:00:05 | 59.745049810145126 | ++---------------------+--------------------+ + +DROP FLOW calc_percentile_5s; + +Affected Rows: 0 + +DROP TABLE percentile_5s; + +Affected Rows: 0 + +DROP TABLE percentile_base; + +Affected Rows: 0 + +CREATE TABLE percentile_base ( + "id" INT PRIMARY KEY, + "value" DOUBLE, + ts timestamp(0) time index +); + +Affected Rows: 0 + +CREATE TABLE percentile_5s ( + "percentile_state" BINARY, + time_window timestamp(0) time index +); + +Affected Rows: 0 + +CREATE FLOW calc_percentile_5s SINK TO percentile_5s +AS +SELECT + uddsketch_state(128, 0.01, CASE WHEN "value" > 0 AND "value" < 70 THEN "value" ELSE NULL END) AS "value", + date_bin('5 seconds'::INTERVAL, ts) AS time_window +FROM + percentile_base +GROUP BY + time_window; + +Affected Rows: 0 + +INSERT INTO percentile_base ("id", "value", ts) VALUES + (1, 10.0, 1), + (2, 20.0, 2), + (3, 30.0, 3), + (4, 40.0, 4), + (5, 50.0, 5), + (6, 60.0, 6), + (7, 70.0, 7), + (8, 80.0, 8), + (9, 90.0, 9), + (10, 100.0, 10); + +Affected Rows: 10 + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('calc_percentile_5s'); + ++----------------------------------------+ +| ADMIN FLUSH_FLOW('calc_percentile_5s') | ++----------------------------------------+ +| FLOW_FLUSHED | ++----------------------------------------+ + +SELECT + time_window, + uddsketch_calc(0.99, percentile_state) AS p99 +FROM + percentile_5s +ORDER BY + time_window; + ++---------------------+--------------------+ +| time_window | p99 | ++---------------------+--------------------+ +| 1970-01-01T00:00:00 | 40.04777053326359 | +| 1970-01-01T00:00:05 | 59.745049810145126 | +| 1970-01-01T00:00:10 | | ++---------------------+--------------------+ + +DROP FLOW calc_percentile_5s; + +Affected Rows: 0 + +DROP TABLE percentile_5s; + +Affected Rows: 0 + +DROP TABLE percentile_base; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/flow/flow_step_aggr.sql b/tests/cases/standalone/common/flow/flow_step_aggr.sql new file mode 100644 index 0000000000..44dde88912 --- /dev/null +++ b/tests/cases/standalone/common/flow/flow_step_aggr.sql @@ -0,0 +1,161 @@ +CREATE TABLE access_log ( + "url" STRING, + user_id BIGINT, + ts TIMESTAMP TIME INDEX, + PRIMARY KEY ("url", user_id) +); + +CREATE TABLE access_log_10s ( + "url" STRING, + time_window timestamp time INDEX, + state BINARY, + PRIMARY KEY ("url") +); + +CREATE FLOW calc_access_log_10s SINK TO access_log_10s +AS +SELECT + "url", + date_bin('10s'::INTERVAL, ts) AS time_window, + hll(user_id) AS state +FROM + access_log +GROUP BY + "url", + time_window; + +-- insert 4 rows of data +INSERT INTO access_log VALUES + ("/dashboard", 1, "2025-03-04 00:00:00"), + ("/dashboard", 1, "2025-03-04 00:00:01"), + ("/dashboard", 2, "2025-03-04 00:00:05"), + ("/not_found", 3, "2025-03-04 00:00:11"), + ("/dashboard", 4, "2025-03-04 00:00:15"); + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('calc_access_log_10s'); + +-- query should return 3 rows +SELECT "url", time_window FROM access_log_10s +ORDER BY + time_window; + +-- use hll_count to query the approximate data in access_log_10s +SELECT "url", time_window, hll_count(state) FROM access_log_10s +ORDER BY + time_window; + +-- further, we can aggregate 10 seconds of data to every minute, by using hll_merge to merge 10 seconds of hyperloglog state +SELECT + "url", + date_bin('1 minute'::INTERVAL, time_window) AS time_window_1m, + hll_count(hll_merge(state)) as uv_per_min +FROM + access_log_10s +GROUP BY + "url", + time_window_1m +ORDER BY + time_window_1m; + +DROP FLOW calc_access_log_10s; +DROP TABLE access_log_10s; +DROP TABLE access_log; + +CREATE TABLE percentile_base ( + "id" INT PRIMARY KEY, + "value" DOUBLE, + ts timestamp(0) time index +); + +CREATE TABLE percentile_5s ( + "percentile_state" BINARY, + time_window timestamp(0) time index +); + +CREATE FLOW calc_percentile_5s SINK TO percentile_5s +AS +SELECT + uddsketch_state(128, 0.01, "value") AS "value", + date_bin('5 seconds'::INTERVAL, ts) AS time_window +FROM + percentile_base +WHERE + "value" > 0 AND "value" < 70 +GROUP BY + time_window; + +INSERT INTO percentile_base ("id", "value", ts) VALUES + (1, 10.0, 1), + (2, 20.0, 2), + (3, 30.0, 3), + (4, 40.0, 4), + (5, 50.0, 5), + (6, 60.0, 6), + (7, 70.0, 7), + (8, 80.0, 8), + (9, 90.0, 9), + (10, 100.0, 10); + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('calc_percentile_5s'); + +SELECT + time_window, + uddsketch_calc(0.99, `percentile_state`) AS p99 +FROM + percentile_5s +ORDER BY + time_window; + +DROP FLOW calc_percentile_5s; +DROP TABLE percentile_5s; +DROP TABLE percentile_base; + +CREATE TABLE percentile_base ( + "id" INT PRIMARY KEY, + "value" DOUBLE, + ts timestamp(0) time index +); + +CREATE TABLE percentile_5s ( + "percentile_state" BINARY, + time_window timestamp(0) time index +); + +CREATE FLOW calc_percentile_5s SINK TO percentile_5s +AS +SELECT + uddsketch_state(128, 0.01, CASE WHEN "value" > 0 AND "value" < 70 THEN "value" ELSE NULL END) AS "value", + date_bin('5 seconds'::INTERVAL, ts) AS time_window +FROM + percentile_base +GROUP BY + time_window; + +INSERT INTO percentile_base ("id", "value", ts) VALUES + (1, 10.0, 1), + (2, 20.0, 2), + (3, 30.0, 3), + (4, 40.0, 4), + (5, 50.0, 5), + (6, 60.0, 6), + (7, 70.0, 7), + (8, 80.0, 8), + (9, 90.0, 9), + (10, 100.0, 10); + +-- SQLNESS REPLACE (ADMIN\sFLUSH_FLOW\('\w+'\)\s+\|\n\+-+\+\n\|\s+)[0-9]+\s+\| $1 FLOW_FLUSHED | +ADMIN FLUSH_FLOW('calc_percentile_5s'); + +SELECT + time_window, + uddsketch_calc(0.99, percentile_state) AS p99 +FROM + percentile_5s +ORDER BY + time_window; + +DROP FLOW calc_percentile_5s; +DROP TABLE percentile_5s; +DROP TABLE percentile_base; diff --git a/tests/cases/standalone/common/flow/flow_user_guide.result b/tests/cases/standalone/common/flow/flow_user_guide.result index c044fef367..7c7a16b4d0 100644 --- a/tests/cases/standalone/common/flow/flow_user_guide.result +++ b/tests/cases/standalone/common/flow/flow_user_guide.result @@ -397,7 +397,7 @@ CREATE TABLE temp_alerts ( sensor_id INT, loc STRING, max_temp DOUBLE, - update_at TIMESTAMP TIME INDEX, + event_ts TIMESTAMP TIME INDEX, PRIMARY KEY(sensor_id, loc) ); @@ -408,6 +408,7 @@ SELECT sensor_id, loc, max(temperature) as max_temp, + max(ts) as event_ts, FROM temp_sensor_data GROUP BY @@ -438,7 +439,8 @@ ADMIN FLUSH_FLOW('temp_monitoring'); SELECT sensor_id, loc, - max_temp + max_temp, + event_ts FROM temp_alerts; @@ -466,16 +468,17 @@ ADMIN FLUSH_FLOW('temp_monitoring'); SELECT sensor_id, loc, - max_temp + max_temp, + event_ts FROM temp_alerts; -+-----------+-------+----------+ -| sensor_id | loc | max_temp | -+-----------+-------+----------+ -| 1 | room1 | 101.5 | -| 2 | room2 | 102.5 | -+-----------+-------+----------+ ++-----------+-------+----------+---------------------+ +| sensor_id | loc | max_temp | event_ts | ++-----------+-------+----------+---------------------+ +| 1 | room1 | 101.5 | 2022-01-01T00:00:02 | +| 2 | room2 | 102.5 | 2022-01-01T00:00:03 | ++-----------+-------+----------+---------------------+ DROP FLOW temp_monitoring; diff --git a/tests/cases/standalone/common/flow/flow_user_guide.sql b/tests/cases/standalone/common/flow/flow_user_guide.sql index d882972393..deaf3a61cc 100644 --- a/tests/cases/standalone/common/flow/flow_user_guide.sql +++ b/tests/cases/standalone/common/flow/flow_user_guide.sql @@ -291,7 +291,7 @@ CREATE TABLE temp_alerts ( sensor_id INT, loc STRING, max_temp DOUBLE, - update_at TIMESTAMP TIME INDEX, + event_ts TIMESTAMP TIME INDEX, PRIMARY KEY(sensor_id, loc) ); @@ -300,6 +300,7 @@ SELECT sensor_id, loc, max(temperature) as max_temp, + max(ts) as event_ts, FROM temp_sensor_data GROUP BY @@ -320,7 +321,8 @@ ADMIN FLUSH_FLOW('temp_monitoring'); SELECT sensor_id, loc, - max_temp + max_temp, + event_ts FROM temp_alerts; @@ -337,7 +339,8 @@ ADMIN FLUSH_FLOW('temp_monitoring'); SELECT sensor_id, loc, - max_temp + max_temp, + event_ts FROM temp_alerts; diff --git a/tests/cases/standalone/common/flow/flow_view.result b/tests/cases/standalone/common/flow/flow_view.result index ec54a12aa8..086f823136 100644 --- a/tests/cases/standalone/common/flow/flow_view.result +++ b/tests/cases/standalone/common/flow/flow_view.result @@ -72,12 +72,13 @@ INSERT INTO ngx_access_log VALUES ('192.168.1.1', 'GET', '/index.html', 200, 512 Affected Rows: 4 +-- TODO(discord9): fix flow stat update for batching mode flow SELECT created_time < last_execution_time, created_time IS NOT NULL, last_execution_time IS NOT NULL, source_table_names FROM information_schema.flows WHERE flow_name = 'user_agent_flow'; +--------------------------------------------------------------------------------------+---------------------------------------------------+----------------------------------------------------------+--------------------------------+ | information_schema.flows.created_time < information_schema.flows.last_execution_time | information_schema.flows.created_time IS NOT NULL | information_schema.flows.last_execution_time IS NOT NULL | source_table_names | +--------------------------------------------------------------------------------------+---------------------------------------------------+----------------------------------------------------------+--------------------------------+ -| true | true | true | greptime.public.ngx_access_log | +| | true | false | greptime.public.ngx_access_log | +--------------------------------------------------------------------------------------+---------------------------------------------------+----------------------------------------------------------+--------------------------------+ DROP TABLE ngx_access_log; diff --git a/tests/cases/standalone/common/flow/flow_view.sql b/tests/cases/standalone/common/flow/flow_view.sql index 28e5e2608e..61aff064a9 100644 --- a/tests/cases/standalone/common/flow/flow_view.sql +++ b/tests/cases/standalone/common/flow/flow_view.sql @@ -32,6 +32,7 @@ INSERT INTO ngx_access_log VALUES ('192.168.1.1', 'GET', '/index.html', 200, 512 -- SQLNESS SLEEP 10s INSERT INTO ngx_access_log VALUES ('192.168.1.1', 'GET', '/index.html', 200, 512, 'Mozilla/5.0', 1024, '2023-10-01T10:00:00Z'), ('192.168.1.2', 'POST', '/submit', 201, 256, 'curl/7.68.0', 512, '2023-10-01T10:01:00Z'), ('192.168.1.1', 'GET', '/about.html', 200, 128, 'Mozilla/5.0', 256, '2023-10-01T10:02:00Z'), ('192.168.1.3', 'GET', '/contact', 404, 64, 'curl/7.68.0', 128, '2023-10-01T10:03:00Z'); +-- TODO(discord9): fix flow stat update for batching mode flow SELECT created_time < last_execution_time, created_time IS NOT NULL, last_execution_time IS NOT NULL, source_table_names FROM information_schema.flows WHERE flow_name = 'user_agent_flow'; DROP TABLE ngx_access_log; diff --git a/tests/cases/standalone/common/promql/quantile.result b/tests/cases/standalone/common/promql/quantile.result index 8676bbbb77..c3aa1ef1ec 100644 --- a/tests/cases/standalone/common/promql/quantile.result +++ b/tests/cases/standalone/common/promql/quantile.result @@ -30,40 +30,40 @@ Affected Rows: 16 TQL EVAL (0, 15, '5s') quantile(0.5, test); -+---------------------+--------------------+ -| ts | quantile(test.val) | -+---------------------+--------------------+ -| 1970-01-01T00:00:00 | 2.5 | -| 1970-01-01T00:00:05 | 6.5 | -| 1970-01-01T00:00:10 | 10.5 | -| 1970-01-01T00:00:15 | 14.5 | -+---------------------+--------------------+ ++---------------------+---------------------------------+ +| ts | quantile(Float64(0.5),test.val) | ++---------------------+---------------------------------+ +| 1970-01-01T00:00:00 | 2.5 | +| 1970-01-01T00:00:05 | 6.5 | +| 1970-01-01T00:00:10 | 10.5 | +| 1970-01-01T00:00:15 | 14.5 | ++---------------------+---------------------------------+ TQL EVAL (0, 15, '5s') quantile(0.5, test) by (idc); -+------+---------------------+--------------------+ -| idc | ts | quantile(test.val) | -+------+---------------------+--------------------+ -| idc1 | 1970-01-01T00:00:00 | 1.5 | -| idc1 | 1970-01-01T00:00:05 | 5.5 | -| idc1 | 1970-01-01T00:00:10 | 9.5 | -| idc1 | 1970-01-01T00:00:15 | 13.5 | -| idc2 | 1970-01-01T00:00:00 | 3.5 | -| idc2 | 1970-01-01T00:00:05 | 7.5 | -| idc2 | 1970-01-01T00:00:10 | 11.5 | -| idc2 | 1970-01-01T00:00:15 | 15.5 | -+------+---------------------+--------------------+ ++------+---------------------+---------------------------------+ +| idc | ts | quantile(Float64(0.5),test.val) | ++------+---------------------+---------------------------------+ +| idc1 | 1970-01-01T00:00:00 | 1.5 | +| idc1 | 1970-01-01T00:00:05 | 5.5 | +| idc1 | 1970-01-01T00:00:10 | 9.5 | +| idc1 | 1970-01-01T00:00:15 | 13.5 | +| idc2 | 1970-01-01T00:00:00 | 3.5 | +| idc2 | 1970-01-01T00:00:05 | 7.5 | +| idc2 | 1970-01-01T00:00:10 | 11.5 | +| idc2 | 1970-01-01T00:00:15 | 15.5 | ++------+---------------------+---------------------------------+ TQL EVAL (0, 15, '5s') quantile(0.5, sum(test) by (idc)); -+---------------------+-------------------------+ -| ts | quantile(sum(test.val)) | -+---------------------+-------------------------+ -| 1970-01-01T00:00:00 | 5.0 | -| 1970-01-01T00:00:05 | 13.0 | -| 1970-01-01T00:00:10 | 21.0 | -| 1970-01-01T00:00:15 | 29.0 | -+---------------------+-------------------------+ ++---------------------+--------------------------------------+ +| ts | quantile(Float64(0.5),sum(test.val)) | ++---------------------+--------------------------------------+ +| 1970-01-01T00:00:00 | 5.0 | +| 1970-01-01T00:00:05 | 13.0 | +| 1970-01-01T00:00:10 | 21.0 | +| 1970-01-01T00:00:15 | 29.0 | ++---------------------+--------------------------------------+ DROP TABLE test; diff --git a/tests/cases/standalone/common/promql/regex.result b/tests/cases/standalone/common/promql/regex.result index ec4c74d204..71e9abed34 100644 --- a/tests/cases/standalone/common/promql/regex.result +++ b/tests/cases/standalone/common/promql/regex.result @@ -22,7 +22,7 @@ SELECT * FROM test; | 1970-01-01T00:00:00 | 10.0.160.237:8081 | 1 | +---------------------+-------------------+-----+ -TQL EVAL (0, 100, '15s') test{host=~"(10\\.0\\.160\\.237:8080|10\\.0\\.160\\.237:9090)"}; +TQL EVAL (0, 100, '15s') test{host=~"(10.0.160.237:8080|10.0.160.237:9090)"}; +---------------------+-------------------+-----+ | ts | host | val | diff --git a/tests/cases/standalone/common/promql/regex.sql b/tests/cases/standalone/common/promql/regex.sql index 71b13a6eb6..1324730177 100644 --- a/tests/cases/standalone/common/promql/regex.sql +++ b/tests/cases/standalone/common/promql/regex.sql @@ -11,6 +11,6 @@ INSERT INTO TABLE test VALUES SELECT * FROM test; -TQL EVAL (0, 100, '15s') test{host=~"(10\\.0\\.160\\.237:8080|10\\.0\\.160\\.237:9090)"}; +TQL EVAL (0, 100, '15s') test{host=~"(10.0.160.237:8080|10.0.160.237:9090)"}; DROP TABLE test; diff --git a/tests/cases/standalone/common/promql/round_fn.result b/tests/cases/standalone/common/promql/round_fn.result index fe12ca6f67..5fe7e2beb0 100644 --- a/tests/cases/standalone/common/promql/round_fn.result +++ b/tests/cases/standalone/common/promql/round_fn.result @@ -18,62 +18,62 @@ Affected Rows: 4 -- SQLNESS SORT_RESULT 3 1 tql eval (3, 4, '1s') round(cache_hit, 0.01); -+---------------------+----------------------------+-------+ -| ts | prom_round(greptime_value) | job | -+---------------------+----------------------------+-------+ -| 1970-01-01T00:00:03 | 123.45 | read | -| 1970-01-01T00:00:03 | 234.57 | write | -| 1970-01-01T00:00:04 | 345.68 | read | -| 1970-01-01T00:00:04 | 456.79 | write | -+---------------------+----------------------------+-------+ ++---------------------+------------------------------------------+-------+ +| ts | prom_round(greptime_value,Float64(0.01)) | job | ++---------------------+------------------------------------------+-------+ +| 1970-01-01T00:00:03 | 123.45 | read | +| 1970-01-01T00:00:03 | 234.57 | write | +| 1970-01-01T00:00:04 | 345.68 | read | +| 1970-01-01T00:00:04 | 456.79 | write | ++---------------------+------------------------------------------+-------+ -- SQLNESS SORT_RESULT 3 1 tql eval (3, 4, '1s') round(cache_hit, 0.1); -+---------------------+----------------------------+-------+ -| ts | prom_round(greptime_value) | job | -+---------------------+----------------------------+-------+ -| 1970-01-01T00:00:03 | 123.5 | read | -| 1970-01-01T00:00:03 | 234.60000000000002 | write | -| 1970-01-01T00:00:04 | 345.70000000000005 | read | -| 1970-01-01T00:00:04 | 456.8 | write | -+---------------------+----------------------------+-------+ ++---------------------+-----------------------------------------+-------+ +| ts | prom_round(greptime_value,Float64(0.1)) | job | ++---------------------+-----------------------------------------+-------+ +| 1970-01-01T00:00:03 | 123.5 | read | +| 1970-01-01T00:00:03 | 234.60000000000002 | write | +| 1970-01-01T00:00:04 | 345.70000000000005 | read | +| 1970-01-01T00:00:04 | 456.8 | write | ++---------------------+-----------------------------------------+-------+ -- SQLNESS SORT_RESULT 3 1 tql eval (3, 4, '1s') round(cache_hit, 1.0); -+---------------------+----------------------------+-------+ -| ts | prom_round(greptime_value) | job | -+---------------------+----------------------------+-------+ -| 1970-01-01T00:00:03 | 123.0 | read | -| 1970-01-01T00:00:03 | 235.0 | write | -| 1970-01-01T00:00:04 | 346.0 | read | -| 1970-01-01T00:00:04 | 457.0 | write | -+---------------------+----------------------------+-------+ ++---------------------+---------------------------------------+-------+ +| ts | prom_round(greptime_value,Float64(1)) | job | ++---------------------+---------------------------------------+-------+ +| 1970-01-01T00:00:03 | 123.0 | read | +| 1970-01-01T00:00:03 | 235.0 | write | +| 1970-01-01T00:00:04 | 346.0 | read | +| 1970-01-01T00:00:04 | 457.0 | write | ++---------------------+---------------------------------------+-------+ -- SQLNESS SORT_RESULT 3 1 tql eval (3, 4, '1s') round(cache_hit); -+---------------------+----------------------------+-------+ -| ts | prom_round(greptime_value) | job | -+---------------------+----------------------------+-------+ -| 1970-01-01T00:00:03 | 123.0 | read | -| 1970-01-01T00:00:03 | 235.0 | write | -| 1970-01-01T00:00:04 | 346.0 | read | -| 1970-01-01T00:00:04 | 457.0 | write | -+---------------------+----------------------------+-------+ ++---------------------+---------------------------------------+-------+ +| ts | prom_round(greptime_value,Float64(0)) | job | ++---------------------+---------------------------------------+-------+ +| 1970-01-01T00:00:03 | 123.0 | read | +| 1970-01-01T00:00:03 | 235.0 | write | +| 1970-01-01T00:00:04 | 346.0 | read | +| 1970-01-01T00:00:04 | 457.0 | write | ++---------------------+---------------------------------------+-------+ -- SQLNESS SORT_RESULT 3 1 tql eval (3, 4, '1s') round(cache_hit, 10.0); -+---------------------+----------------------------+-------+ -| ts | prom_round(greptime_value) | job | -+---------------------+----------------------------+-------+ -| 1970-01-01T00:00:03 | 120.0 | read | -| 1970-01-01T00:00:03 | 230.0 | write | -| 1970-01-01T00:00:04 | 350.0 | read | -| 1970-01-01T00:00:04 | 460.0 | write | -+---------------------+----------------------------+-------+ ++---------------------+----------------------------------------+-------+ +| ts | prom_round(greptime_value,Float64(10)) | job | ++---------------------+----------------------------------------+-------+ +| 1970-01-01T00:00:03 | 120.0 | read | +| 1970-01-01T00:00:03 | 230.0 | write | +| 1970-01-01T00:00:04 | 350.0 | read | +| 1970-01-01T00:00:04 | 460.0 | write | ++---------------------+----------------------------------------+-------+ drop table cache_hit; diff --git a/tests/cases/standalone/common/promql/simple_histogram.result b/tests/cases/standalone/common/promql/simple_histogram.result index 5fe7760c18..132b6d333d 100644 --- a/tests/cases/standalone/common/promql/simple_histogram.result +++ b/tests/cases/standalone/common/promql/simple_histogram.result @@ -227,15 +227,27 @@ tql eval (420, 420, '1s') histogram_quantile(0.833, histogram2_bucket); tql eval (2820, 2820, '1s') histogram_quantile(0.166, rate(histogram2_bucket[15m])); -Error: 3001(EngineExecuteQuery), Unsupported arrow data type, type: Dictionary(Int64, Float64) ++---------------------+------------------------------------------+ +| ts | prom_rate(ts_range,val,ts,Int64(900000)) | ++---------------------+------------------------------------------+ +| 1970-01-01T00:47:00 | 0.996 | ++---------------------+------------------------------------------+ tql eval (2820, 2820, '1s') histogram_quantile(0.5, rate(histogram2_bucket[15m])); -Error: 3001(EngineExecuteQuery), Unsupported arrow data type, type: Dictionary(Int64, Float64) ++---------------------+------------------------------------------+ +| ts | prom_rate(ts_range,val,ts,Int64(900000)) | ++---------------------+------------------------------------------+ +| 1970-01-01T00:47:00 | 3.0 | ++---------------------+------------------------------------------+ tql eval (2820, 2820, '1s') histogram_quantile(0.833, rate(histogram2_bucket[15m])); -Error: 3001(EngineExecuteQuery), Unsupported arrow data type, type: Dictionary(Int64, Float64) ++---------------------+------------------------------------------+ +| ts | prom_rate(ts_range,val,ts,Int64(900000)) | ++---------------------+------------------------------------------+ +| 1970-01-01T00:47:00 | 4.998 | ++---------------------+------------------------------------------+ drop table histogram2_bucket; @@ -271,7 +283,12 @@ Affected Rows: 12 tql eval (3000, 3005, '3s') histogram_quantile(0.5, sum by(le, s) (rate(histogram3_bucket[5m]))); -Error: 3001(EngineExecuteQuery), Unsupported arrow data type, type: Dictionary(Int64, Float64) ++---+---------------------+-----------------------------------------------+ +| s | ts | sum(prom_rate(ts_range,val,ts,Int64(300000))) | ++---+---------------------+-----------------------------------------------+ +| a | 1970-01-01T00:50:00 | 0.55 | +| a | 1970-01-01T00:50:03 | 0.5500000000000002 | ++---+---------------------+-----------------------------------------------+ drop table histogram3_bucket; diff --git a/tests/cases/standalone/common/promql/subquery.result b/tests/cases/standalone/common/promql/subquery.result index 7f3ebae801..12e65c4310 100644 --- a/tests/cases/standalone/common/promql/subquery.result +++ b/tests/cases/standalone/common/promql/subquery.result @@ -45,13 +45,19 @@ tql eval (359, 359, '1s') sum_over_time(metric_total[60s:10s]); tql eval (10, 10, '1s') rate(metric_total[20s:10s]); -++ -++ ++---------------------+-----------------------------------------+ +| ts | prom_rate(ts_range,val,ts,Int64(20000)) | ++---------------------+-----------------------------------------+ +| 1970-01-01T00:00:10 | 0.1 | ++---------------------+-----------------------------------------+ tql eval (20, 20, '1s') rate(metric_total[20s:5s]); -++ -++ ++---------------------+-----------------------------------------+ +| ts | prom_rate(ts_range,val,ts,Int64(20000)) | ++---------------------+-----------------------------------------+ +| 1970-01-01T00:00:20 | 0.06666666666666667 | ++---------------------+-----------------------------------------+ drop table metric_total; diff --git a/tests/cases/standalone/common/range/error.result b/tests/cases/standalone/common/range/error.result index f7236d6096..e3f12646e7 100644 --- a/tests/cases/standalone/common/range/error.result +++ b/tests/cases/standalone/common/range/error.result @@ -54,7 +54,11 @@ Error: 2000(InvalidSyntax), Invalid SQL syntax: sql parser error: Can't use the -- 2.2 no align param SELECT min(val) RANGE '5s' FROM host; -Error: 3000(PlanQuery), Error during planning: Missing argument in range select query +Error: 2000(InvalidSyntax), Invalid SQL syntax: sql parser error: ALIGN argument cannot be omitted in the range select query + +SELECT min(val) RANGE '5s' FILL PREV FROM host; + +Error: 2000(InvalidSyntax), Invalid SQL syntax: sql parser error: ALIGN argument cannot be omitted in the range select query -- 2.3 type mismatch SELECT covar(ceil(val), floor(val)) RANGE '20s' FROM host ALIGN '10s'; diff --git a/tests/cases/standalone/common/range/error.sql b/tests/cases/standalone/common/range/error.sql index ba3d1f63e2..3659be1c79 100644 --- a/tests/cases/standalone/common/range/error.sql +++ b/tests/cases/standalone/common/range/error.sql @@ -40,6 +40,8 @@ SELECT 1 RANGE '10s' FILL NULL FROM host ALIGN '1h' FILL NULL; SELECT min(val) RANGE '5s' FROM host; +SELECT min(val) RANGE '5s' FILL PREV FROM host; + -- 2.3 type mismatch SELECT covar(ceil(val), floor(val)) RANGE '20s' FROM host ALIGN '10s'; diff --git a/tests/cases/standalone/common/select/tql_filter.result b/tests/cases/standalone/common/select/tql_filter.result index 56a1ac8bcb..0d0c09e27c 100644 --- a/tests/cases/standalone/common/select/tql_filter.result +++ b/tests/cases/standalone/common/select/tql_filter.result @@ -79,3 +79,47 @@ drop table t1; Affected Rows: 0 +create table t2 (a string primary key, b timestamp time index, c double); + +Affected Rows: 0 + +INSERT INTO TABLE t2 VALUES + ('10.0.160.237:8080', 0, 1), + ('10.0.160.237:8081', 0, 1), + ('20.0.10.237:8081', 0, 1), + ('abcx', 0, 1), + ('xabc', 0, 1); + +Affected Rows: 5 + +-- SQLNESS SORT_RESULT 3 1 +tql eval (0, 0, '1s') t2{a=~"10"}; + +++ +++ + +-- SQLNESS SORT_RESULT 3 1 +tql eval (0, 0, '1s') t2{a=~"10.*"}; + ++-------------------+---------------------+-----+ +| a | b | c | ++-------------------+---------------------+-----+ +| 10.0.160.237:8080 | 1970-01-01T00:00:00 | 1.0 | +| 10.0.160.237:8081 | 1970-01-01T00:00:00 | 1.0 | ++-------------------+---------------------+-----+ + +-- SQLNESS SORT_RESULT 3 1 +tql eval (0, 0, '1s') t2{a=~".*10.*"}; + ++-------------------+---------------------+-----+ +| a | b | c | ++-------------------+---------------------+-----+ +| 10.0.160.237:8080 | 1970-01-01T00:00:00 | 1.0 | +| 10.0.160.237:8081 | 1970-01-01T00:00:00 | 1.0 | +| 20.0.10.237:8081 | 1970-01-01T00:00:00 | 1.0 | ++-------------------+---------------------+-----+ + +drop table t2; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/select/tql_filter.sql b/tests/cases/standalone/common/select/tql_filter.sql index c37512f362..de0179443e 100644 --- a/tests/cases/standalone/common/select/tql_filter.sql +++ b/tests/cases/standalone/common/select/tql_filter.sql @@ -27,3 +27,23 @@ tql analyze (1, 3, '1s') t1{ a =~ ".*" }; tql analyze (1, 3, '1s') t1{ a =~ "a.*" }; drop table t1; + +create table t2 (a string primary key, b timestamp time index, c double); + +INSERT INTO TABLE t2 VALUES + ('10.0.160.237:8080', 0, 1), + ('10.0.160.237:8081', 0, 1), + ('20.0.10.237:8081', 0, 1), + ('abcx', 0, 1), + ('xabc', 0, 1); + +-- SQLNESS SORT_RESULT 3 1 +tql eval (0, 0, '1s') t2{a=~"10"}; + +-- SQLNESS SORT_RESULT 3 1 +tql eval (0, 0, '1s') t2{a=~"10.*"}; + +-- SQLNESS SORT_RESULT 3 1 +tql eval (0, 0, '1s') t2{a=~".*10.*"}; + +drop table t2; diff --git a/tests/cases/standalone/common/show/show_create.result b/tests/cases/standalone/common/show/show_create.result index ddbdd4179a..5d7019265a 100644 --- a/tests/cases/standalone/common/show/show_create.result +++ b/tests/cases/standalone/common/show/show_create.result @@ -373,20 +373,20 @@ Affected Rows: 0 show create table test_column_constrain_composite_indexes; -+-----------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Table | Create Table | -+-----------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| test_column_constrain_composite_indexes | CREATE TABLE IF NOT EXISTS "test_column_constrain_composite_indexes" ( | -| | "id" INT NULL SKIPPING INDEX WITH(granularity = '10240', type = 'BLOOM') INVERTED INDEX, | -| | "host" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', case_sensitive = 'false') SKIPPING INDEX WITH(granularity = '10240', type = 'BLOOM') INVERTED INDEX, | -| | "ts" TIMESTAMP(3) NOT NULL, | -| | TIME INDEX ("ts"), | -| | PRIMARY KEY ("host") | -| | ) | -| | | -| | ENGINE=mito | -| | | -+-----------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+ ++-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Table | Create Table | ++-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| test_column_constrain_composite_indexes | CREATE TABLE IF NOT EXISTS "test_column_constrain_composite_indexes" ( | +| | "id" INT NULL SKIPPING INDEX WITH(granularity = '10240', type = 'BLOOM') INVERTED INDEX, | +| | "host" STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'false') SKIPPING INDEX WITH(granularity = '10240', type = 'BLOOM') INVERTED INDEX, | +| | "ts" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("ts"), | +| | PRIMARY KEY ("host") | +| | ) | +| | | +| | ENGINE=mito | +| | | ++-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ drop table test_column_constrain_composite_indexes; diff --git a/tests/cases/standalone/common/show/show_index.result b/tests/cases/standalone/common/show/show_index.result index 0376443746..80010b5331 100644 --- a/tests/cases/standalone/common/show/show_index.result +++ b/tests/cases/standalone/common/show/show_index.result @@ -80,27 +80,27 @@ SHOW INDEX FROM test_no_inverted_index; SHOW INDEX FROM system_metrics; -+----------------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+---------------------------------------------------------------------------------+---------+---------------+---------+------------+ -| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | -+----------------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+---------------------------------------------------------------------------------+---------+---------------+---------+------------+ -| system_metrics | 1 | FULLTEXT INDEX | 7 | desc2 | A | | | | YES | greptime-fulltext-index-v1 | | | YES | | -| system_metrics | 1 | FULLTEXT INDEX | 8 | desc3 | A | | | | YES | greptime-fulltext-index-v1 | | | YES | | -| system_metrics | 1 | PRIMARY | 1 | host | A | | | | YES | greptime-primary-key-v1 | | | YES | | -| system_metrics | 1 | PRIMARY, INVERTED INDEX, FULLTEXT INDEX | 2 | idc | A | | | | YES | greptime-primary-key-v1, greptime-inverted-index-v1, greptime-fulltext-index-v1 | | | YES | | -| system_metrics | 1 | TIME INDEX | 1 | ts | A | | | | NO | | | | YES | | -+----------------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+---------------------------------------------------------------------------------+---------+---------------+---------+------------+ ++----------------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------------------------------------------------------------------------------+---------+---------------+---------+------------+ +| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | ++----------------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------------------------------------------------------------------------------+---------+---------------+---------+------------+ +| system_metrics | 1 | FULLTEXT INDEX | 7 | desc2 | A | | | | YES | greptime-fulltext-index-bloom | | | YES | | +| system_metrics | 1 | FULLTEXT INDEX | 8 | desc3 | A | | | | YES | greptime-fulltext-index-bloom | | | YES | | +| system_metrics | 1 | PRIMARY | 1 | host | A | | | | YES | greptime-primary-key-v1 | | | YES | | +| system_metrics | 1 | PRIMARY, INVERTED INDEX, FULLTEXT INDEX | 2 | idc | A | | | | YES | greptime-primary-key-v1, greptime-inverted-index-v1, greptime-fulltext-index-bloom | | | YES | | +| system_metrics | 1 | TIME INDEX | 1 | ts | A | | | | NO | | | | YES | | ++----------------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------------------------------------------------------------------------------+---------+---------------+---------+------------+ SHOW INDEX FROM system_metrics in public; -+----------------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+---------------------------------------------------------------------------------+---------+---------------+---------+------------+ -| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | -+----------------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+---------------------------------------------------------------------------------+---------+---------------+---------+------------+ -| system_metrics | 1 | FULLTEXT INDEX | 7 | desc2 | A | | | | YES | greptime-fulltext-index-v1 | | | YES | | -| system_metrics | 1 | FULLTEXT INDEX | 8 | desc3 | A | | | | YES | greptime-fulltext-index-v1 | | | YES | | -| system_metrics | 1 | PRIMARY | 1 | host | A | | | | YES | greptime-primary-key-v1 | | | YES | | -| system_metrics | 1 | PRIMARY, INVERTED INDEX, FULLTEXT INDEX | 2 | idc | A | | | | YES | greptime-primary-key-v1, greptime-inverted-index-v1, greptime-fulltext-index-v1 | | | YES | | -| system_metrics | 1 | TIME INDEX | 1 | ts | A | | | | NO | | | | YES | | -+----------------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+---------------------------------------------------------------------------------+---------+---------------+---------+------------+ ++----------------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------------------------------------------------------------------------------+---------+---------------+---------+------------+ +| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | ++----------------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------------------------------------------------------------------------------+---------+---------------+---------+------------+ +| system_metrics | 1 | FULLTEXT INDEX | 7 | desc2 | A | | | | YES | greptime-fulltext-index-bloom | | | YES | | +| system_metrics | 1 | FULLTEXT INDEX | 8 | desc3 | A | | | | YES | greptime-fulltext-index-bloom | | | YES | | +| system_metrics | 1 | PRIMARY | 1 | host | A | | | | YES | greptime-primary-key-v1 | | | YES | | +| system_metrics | 1 | PRIMARY, INVERTED INDEX, FULLTEXT INDEX | 2 | idc | A | | | | YES | greptime-primary-key-v1, greptime-inverted-index-v1, greptime-fulltext-index-bloom | | | YES | | +| system_metrics | 1 | TIME INDEX | 1 | ts | A | | | | NO | | | | YES | | ++----------------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------------------------------------------------------------------------------+---------+---------------+---------+------------+ SHOW INDEX FROM system_metrics like '%util%'; diff --git a/tests/cases/standalone/common/system/information_schema.result b/tests/cases/standalone/common/system/information_schema.result index bfcf39b69e..a19e7944c0 100644 --- a/tests/cases/standalone/common/system/information_schema.result +++ b/tests/cases/standalone/common/system/information_schema.result @@ -208,6 +208,7 @@ select * from information_schema.columns order by table_schema, table_name, colu | greptime | information_schema | key_column_usage | constraint_catalog | 1 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | No | string | | | | greptime | information_schema | key_column_usage | constraint_name | 3 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | No | string | | | | greptime | information_schema | key_column_usage | constraint_schema | 2 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | No | string | | | +| greptime | information_schema | key_column_usage | greptime_index_type | 14 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | Yes | string | | | | greptime | information_schema | key_column_usage | ordinal_position | 9 | | | 10 | 0 | | | | | | select,insert | | UInt32 | int unsigned | FIELD | | No | int unsigned | | | | greptime | information_schema | key_column_usage | position_in_unique_constraint | 10 | | | 10 | 0 | | | | | | select,insert | | UInt32 | int unsigned | FIELD | | Yes | int unsigned | | | | greptime | information_schema | key_column_usage | real_table_catalog | 5 | 2147483647 | 2147483647 | | | | utf8 | utf8_bin | | | select,insert | | String | string | FIELD | | No | string | | | @@ -593,11 +594,11 @@ select * from KEY_COLUMN_USAGE where CONSTRAINT_NAME = 'TIME INDEX'; select * from KEY_COLUMN_USAGE where CONSTRAINT_NAME != 'TIME INDEX'; -+--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+ -| constraint_catalog | constraint_schema | constraint_name | table_catalog | real_table_catalog | table_schema | table_name | column_name | ordinal_position | position_in_unique_constraint | referenced_table_schema | referenced_table_name | referenced_column_name | -+--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+ -| def | public | PRIMARY | def | greptime | public | numbers | number | 1 | | | | | -+--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+ ++--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+-------------------------+ +| constraint_catalog | constraint_schema | constraint_name | table_catalog | real_table_catalog | table_schema | table_name | column_name | ordinal_position | position_in_unique_constraint | referenced_table_schema | referenced_table_name | referenced_column_name | greptime_index_type | ++--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+-------------------------+ +| def | public | PRIMARY | def | greptime | public | numbers | number | 1 | | | | | greptime-primary-key-v1 | ++--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+-------------------------+ select * from KEY_COLUMN_USAGE where CONSTRAINT_NAME LIKE '%INDEX'; @@ -606,11 +607,11 @@ select * from KEY_COLUMN_USAGE where CONSTRAINT_NAME LIKE '%INDEX'; select * from KEY_COLUMN_USAGE where CONSTRAINT_NAME NOT LIKE '%INDEX'; -+--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+ -| constraint_catalog | constraint_schema | constraint_name | table_catalog | real_table_catalog | table_schema | table_name | column_name | ordinal_position | position_in_unique_constraint | referenced_table_schema | referenced_table_name | referenced_column_name | -+--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+ -| def | public | PRIMARY | def | greptime | public | numbers | number | 1 | | | | | -+--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+ ++--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+-------------------------+ +| constraint_catalog | constraint_schema | constraint_name | table_catalog | real_table_catalog | table_schema | table_name | column_name | ordinal_position | position_in_unique_constraint | referenced_table_schema | referenced_table_name | referenced_column_name | greptime_index_type | ++--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+-------------------------+ +| def | public | PRIMARY | def | greptime | public | numbers | number | 1 | | | | | greptime-primary-key-v1 | ++--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+-------------------------+ select * from KEY_COLUMN_USAGE where CONSTRAINT_NAME == 'TIME INDEX' AND CONSTRAINT_SCHEMA != 'my_db'; @@ -688,15 +689,16 @@ desc table key_column_usage; | referenced_table_schema | String | | YES | | FIELD | | referenced_table_name | String | | YES | | FIELD | | referenced_column_name | String | | YES | | FIELD | +| greptime_index_type | String | | YES | | FIELD | +-------------------------------+--------+-----+------+---------+---------------+ select * from key_column_usage; -+--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+ -| constraint_catalog | constraint_schema | constraint_name | table_catalog | real_table_catalog | table_schema | table_name | column_name | ordinal_position | position_in_unique_constraint | referenced_table_schema | referenced_table_name | referenced_column_name | -+--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+ -| def | public | PRIMARY | def | greptime | public | numbers | number | 1 | | | | | -+--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+ ++--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+-------------------------+ +| constraint_catalog | constraint_schema | constraint_name | table_catalog | real_table_catalog | table_schema | table_name | column_name | ordinal_position | position_in_unique_constraint | referenced_table_schema | referenced_table_name | referenced_column_name | greptime_index_type | ++--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+-------------------------+ +| def | public | PRIMARY | def | greptime | public | numbers | number | 1 | | | | | greptime-primary-key-v1 | ++--------------------+-------------------+-----------------+---------------+--------------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+-------------------------+ -- tables not implemented DESC TABLE COLUMN_PRIVILEGES; diff --git a/tests/cases/standalone/common/system/pg_catalog.result b/tests/cases/standalone/common/system/pg_catalog.result index 092e9cab06..9e154b115c 100644 --- a/tests/cases/standalone/common/system/pg_catalog.result +++ b/tests/cases/standalone/common/system/pg_catalog.result @@ -3,7 +3,7 @@ create database pg_catalog; Error: 1004(InvalidArguments), Schema pg_catalog already exists --- session_user because session_user is based on the current user so is not null is for test +-- session_user because session_user is based on the current user so is not null is for test -- SQLNESS PROTOCOL POSTGRES SELECT session_user is not null; @@ -107,12 +107,13 @@ select * from pg_catalog.pg_type order by oid; +-----+-----------+--------+ -- SQLNESS PROTOCOL POSTGRES +-- SQLNESS REPLACE (\d+\s*) OID select * from pg_catalog.pg_database where datname = 'public'; +------------+---------+ | oid | datname | +------------+---------+ -| 3927743705 | public | +| OID| public | +------------+---------+ -- \d @@ -159,15 +160,16 @@ ORDER BY 1,2; -- make sure oid of namespace keep stable -- SQLNESS PROTOCOL POSTGRES -SELECT * FROM pg_namespace ORDER BY oid; +-- SQLNESS REPLACE (\d+\s*) OID +SELECT * FROM pg_namespace ORDER BY nspname; +------------+--------------------+ | oid | nspname | +------------+--------------------+ -| 667359454 | pg_catalog | -| 3174397350 | information_schema | -| 3338153620 | greptime_private | -| 3927743705 | public | +| OID| greptime_private | +| OID| information_schema | +| OID| pg_catalog | +| OID| public | +------------+--------------------+ -- SQLNESS PROTOCOL POSTGRES @@ -260,6 +262,7 @@ where relnamespace in ( +---------+ -- SQLNESS PROTOCOL POSTGRES +-- SQLNESS REPLACE (\d+\s*) OID select relnamespace, relname, relkind from pg_catalog.pg_class where relnamespace in ( @@ -274,7 +277,7 @@ order by relnamespace, relname; +--------------+---------+---------+ | relnamespace | relname | relkind | +--------------+---------+---------+ -| 434869349 | foo | r | +| OID| foo | r | +--------------+---------+---------+ -- SQLNESS PROTOCOL POSTGRES diff --git a/tests/cases/standalone/common/system/pg_catalog.sql b/tests/cases/standalone/common/system/pg_catalog.sql index 4a110a8f07..0b79c62afe 100644 --- a/tests/cases/standalone/common/system/pg_catalog.sql +++ b/tests/cases/standalone/common/system/pg_catalog.sql @@ -1,7 +1,7 @@ -- should not able to create pg_catalog create database pg_catalog; --- session_user because session_user is based on the current user so is not null is for test +-- session_user because session_user is based on the current user so is not null is for test -- SQLNESS PROTOCOL POSTGRES SELECT session_user is not null; @@ -34,6 +34,7 @@ select * from pg_catalog.pg_database; select * from pg_catalog.pg_type order by oid; -- SQLNESS PROTOCOL POSTGRES +-- SQLNESS REPLACE (\d+\s*) OID select * from pg_catalog.pg_database where datname = 'public'; -- \d @@ -68,7 +69,8 @@ ORDER BY 1,2; -- make sure oid of namespace keep stable -- SQLNESS PROTOCOL POSTGRES -SELECT * FROM pg_namespace ORDER BY oid; +-- SQLNESS REPLACE (\d+\s*) OID +SELECT * FROM pg_namespace ORDER BY nspname; -- SQLNESS PROTOCOL POSTGRES create database my_db; @@ -128,6 +130,7 @@ where relnamespace in ( ); -- SQLNESS PROTOCOL POSTGRES +-- SQLNESS REPLACE (\d+\s*) OID select relnamespace, relname, relkind from pg_catalog.pg_class where relnamespace in ( diff --git a/tests/cases/standalone/common/tql-explain-analyze/analyze.result b/tests/cases/standalone/common/tql-explain-analyze/analyze.result index b3f8d55a39..2f3b9474eb 100644 --- a/tests/cases/standalone/common/tql-explain-analyze/analyze.result +++ b/tests/cases/standalone/common/tql-explain-analyze/analyze.result @@ -159,11 +159,11 @@ TQL ANALYZE (0, 10, '5s') rate(test[10s]); | stage | node | plan_| +-+-+-+ | 0_| 0_|_CoalesceBatchesExec: target_batch_size=8192 REDACTED -|_|_|_FilterExec: prom_rate(j_range,i,j)@1 IS NOT NULL REDACTED -|_|_|_ProjectionExec: expr=[j@1 as j, prom_rate(j_range@4, i@0, j@1) as prom_rate(j_range,i,j), k@2 as k, l@3 as l] REDACTED -|_|_|_RepartitionExec: partitioning=REDACTED -|_|_|_SortPreservingMergeExec: [k@2 ASC, l@3 ASC, j@1 ASC] REDACTED -|_|_|_SortExec: expr=[k@2 ASC, l@3 ASC, j@1 ASC], preserve_partitioning=[true] REDACTED +|_|_|_FilterExec: prom_rate(j_range,i,j,Int64(10000))@1 IS NOT NULL REDACTED +|_|_|_ProjectionExec: expr=[j@1 as j, prom_rate(j_range@4, i@0, j@1, 10000) as prom_rate(j_range,i,j,Int64(10000)), k@2 as k, l@3 as l] REDACTED +|_|_|_PromRangeManipulateExec: req range=[0..10000], interval=[5000], eval range=[10000], time index=[j] REDACTED +|_|_|_PromSeriesNormalizeExec: offset=[0], time index=[j], filter NaN: [true] REDACTED +|_|_|_PromSeriesDivideExec: tags=["k", "l"] REDACTED |_|_|_MergeScanExec: REDACTED |_|_|_| | 1_| 0_|_PromRangeManipulateExec: req range=[0..10000], interval=[5000], eval range=[10000], time index=[j] REDACTED diff --git a/tests/cases/standalone/common/tql-explain-analyze/explain.result b/tests/cases/standalone/common/tql-explain-analyze/explain.result index a8abcac4bf..e4465eed0d 100644 --- a/tests/cases/standalone/common/tql-explain-analyze/explain.result +++ b/tests/cases/standalone/common/tql-explain-analyze/explain.result @@ -65,6 +65,7 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test; |_|_TableScan: test_| | logical_plan after count_wildcard_to_time_index_rule_| SAME TEXT AS ABOVE_| | logical_plan after StringNormalizationRule_| SAME TEXT AS ABOVE_| +| logical_plan after TranscribeAtatRule_| SAME TEXT AS ABOVE_| | logical_plan after inline_table_scan_| SAME TEXT AS ABOVE_| | logical_plan after expand_wildcard_rule_| SAME TEXT AS ABOVE_| | logical_plan after resolve_grouping_function_| SAME TEXT AS ABOVE_| @@ -152,6 +153,7 @@ TQL EXPLAIN VERBOSE (0, 10, '5s') test; | physical_plan after ProjectionPushdown_| SAME TEXT AS ABOVE_| | physical_plan after LimitPushdown_| SAME TEXT AS ABOVE_| | physical_plan after WindowedSortRule_| SAME TEXT AS ABOVE_| +| physical_plan after MatchesConstantTerm_| SAME TEXT AS ABOVE_| | physical_plan after RemoveDuplicateRule_| SAME TEXT AS ABOVE_| | physical_plan after SanityCheckPlan_| SAME TEXT AS ABOVE_| | physical_plan_| MergeScanExec: REDACTED diff --git a/tests/cases/standalone/common/tql/operator.result b/tests/cases/standalone/common/tql/operator.result index d3b85ac445..6ad97ae88a 100644 --- a/tests/cases/standalone/common/tql/operator.result +++ b/tests/cases/standalone/common/tql/operator.result @@ -78,3 +78,56 @@ drop table trignan; Affected Rows: 0 +-- About irate. Related to issue https://github.com/GreptimeTeam/greptimedb/issues/5880 +CREATE TABLE t( + greptime_timestamp TIMESTAMP(9) TIME INDEX, + greptime_value DOUBLE +); + +Affected Rows: 0 + +INSERT INTO t(greptime_timestamp, greptime_value) +VALUES + ('2025-04-01T00:00:00.5Z', 1), + ('2025-04-01T00:00:01Z', 2), + ('2025-04-01T00:00:01.5Z', 3), + ('2025-04-01T00:00:02Z', 4), + ('2025-04-01T00:00:02.5Z', 5), + ('2025-04-01T00:00:03Z', 6), + ('2025-04-01T00:00:03.5Z', 7), + ('2025-04-01T00:00:04Z', 8), + ('2025-04-01T00:00:04.5Z', 9), + ('2025-04-01T00:00:05Z', 10), + ('2025-04-01T00:00:05.5Z', 11), + ('2025-04-01T00:00:06Z', 12), + ('2025-04-01T00:00:06.5Z', 13), + ('2025-04-01T00:00:07Z', 14), + ('2025-04-01T00:00:07.5Z', 15), + ('2025-04-01T00:00:08Z', 16), + ('2025-04-01T00:00:08.5Z', 17), + ('2025-04-01T00:00:09Z', 18), + ('2025-04-01T00:00:09.5Z', 19), + ('2025-04-01T00:00:10Z', 20); + +Affected Rows: 20 + +tql eval (1743465600.5, 1743465610, '1s') irate(t[2s]); + ++-------------------------+-----------------------------------------------------+ +| greptime_timestamp | prom_irate(greptime_timestamp_range,greptime_value) | ++-------------------------+-----------------------------------------------------+ +| 2025-04-01T00:00:01.500 | 2.0 | +| 2025-04-01T00:00:02.500 | 2.0 | +| 2025-04-01T00:00:03.500 | 2.0 | +| 2025-04-01T00:00:04.500 | 2.0 | +| 2025-04-01T00:00:05.500 | 2.0 | +| 2025-04-01T00:00:06.500 | 2.0 | +| 2025-04-01T00:00:07.500 | 2.0 | +| 2025-04-01T00:00:08.500 | 2.0 | +| 2025-04-01T00:00:09.500 | 2.0 | ++-------------------------+-----------------------------------------------------+ + +drop table t; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/tql/operator.sql b/tests/cases/standalone/common/tql/operator.sql index f6c342696b..3a6c2c527b 100644 --- a/tests/cases/standalone/common/tql/operator.sql +++ b/tests/cases/standalone/common/tql/operator.sql @@ -39,3 +39,38 @@ drop table trigx; drop table trigy; drop table trignan; + + +-- About irate. Related to issue https://github.com/GreptimeTeam/greptimedb/issues/5880 +CREATE TABLE t( + greptime_timestamp TIMESTAMP(9) TIME INDEX, + greptime_value DOUBLE +); + +INSERT INTO t(greptime_timestamp, greptime_value) +VALUES + ('2025-04-01T00:00:00.5Z', 1), + ('2025-04-01T00:00:01Z', 2), + ('2025-04-01T00:00:01.5Z', 3), + ('2025-04-01T00:00:02Z', 4), + ('2025-04-01T00:00:02.5Z', 5), + ('2025-04-01T00:00:03Z', 6), + ('2025-04-01T00:00:03.5Z', 7), + ('2025-04-01T00:00:04Z', 8), + ('2025-04-01T00:00:04.5Z', 9), + ('2025-04-01T00:00:05Z', 10), + ('2025-04-01T00:00:05.5Z', 11), + ('2025-04-01T00:00:06Z', 12), + ('2025-04-01T00:00:06.5Z', 13), + ('2025-04-01T00:00:07Z', 14), + ('2025-04-01T00:00:07.5Z', 15), + ('2025-04-01T00:00:08Z', 16), + ('2025-04-01T00:00:08.5Z', 17), + ('2025-04-01T00:00:09Z', 18), + ('2025-04-01T00:00:09.5Z', 19), + ('2025-04-01T00:00:10Z', 20); + +tql eval (1743465600.5, 1743465610, '1s') irate(t[2s]); + +drop table t; + diff --git a/tests/cases/standalone/common/types/timestamp/timestamp.result b/tests/cases/standalone/common/types/timestamp/timestamp.result index 51bc812814..c0d1c79250 100644 --- a/tests/cases/standalone/common/types/timestamp/timestamp.result +++ b/tests/cases/standalone/common/types/timestamp/timestamp.result @@ -192,3 +192,12 @@ DROP TABLE timestamp; Affected Rows: 0 +-- SQLNESS PROTOCOL POSTGRES +SELECT '2025-04-14 20:42:19.021000'::TIMESTAMP - '2025-04-14 20:42:18.103000'::TIMESTAMP; + ++---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| arrow_cast(Utf8("2025-04-14 20:42:19.021000"),Utf8("Timestamp(Millisecond, None)")) - arrow_cast(Utf8("2025-04-14 20:42:18.103000"),Utf8("Timestamp(Millisecond, None)")) | ++---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| 00:00:00.918000 | ++---------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + diff --git a/tests/cases/standalone/common/types/timestamp/timestamp.sql b/tests/cases/standalone/common/types/timestamp/timestamp.sql index e2924bc2e0..070032810b 100644 --- a/tests/cases/standalone/common/types/timestamp/timestamp.sql +++ b/tests/cases/standalone/common/types/timestamp/timestamp.sql @@ -55,3 +55,6 @@ SELECT TIMESTAMP '-8-01-01 00:00:01.5'::VARCHAR; SELECT TIMESTAMP '100000-01-01 00:00:01.5'::VARCHAR; DROP TABLE timestamp; + +-- SQLNESS PROTOCOL POSTGRES +SELECT '2025-04-14 20:42:19.021000'::TIMESTAMP - '2025-04-14 20:42:18.103000'::TIMESTAMP; diff --git a/tests/conf/datanode-test.toml.template b/tests/conf/datanode-test.toml.template index 20987eed9a..a58d802262 100644 --- a/tests/conf/datanode-test.toml.template +++ b/tests/conf/datanode-test.toml.template @@ -15,6 +15,7 @@ sync_write = false provider = "kafka" broker_endpoints = {kafka_wal_broker_endpoints | unescaped} linger = "5ms" +overwrite_entry_start_id = true {{ endif }} [storage] diff --git a/tests/conf/frontend-test.toml.template b/tests/conf/frontend-test.toml.template index 2f8c4f58b8..de4ce86adc 100644 --- a/tests/conf/frontend-test.toml.template +++ b/tests/conf/frontend-test.toml.template @@ -1,3 +1,3 @@ [grpc] -bind_addr = "127.0.0.1:29401" -server_addr = "127.0.0.1:29401" \ No newline at end of file +bind_addr = "{grpc_addr}" +server_addr = "{grpc_addr}" diff --git a/tests/conf/metasrv-test.toml.template b/tests/conf/metasrv-test.toml.template index 1196403a26..f4bbbf3ae9 100644 --- a/tests/conf/metasrv-test.toml.template +++ b/tests/conf/metasrv-test.toml.template @@ -18,4 +18,6 @@ broker_endpoints = {kafka_wal_broker_endpoints | unescaped} num_topics = 3 selector_type = "round_robin" topic_name_prefix = "distributed_test_greptimedb_wal_topic" +auto_prune_interval = "30s" +trigger_flush_threshold = 100 {{ endif }} diff --git a/tests/runner/src/server_mode.rs b/tests/runner/src/server_mode.rs index e7971dc73a..b3d471da46 100644 --- a/tests/runner/src/server_mode.rs +++ b/tests/runner/src/server_mode.rs @@ -389,6 +389,9 @@ impl ServerMode { format!("--metasrv-addrs={metasrv_addr}"), format!("--http-addr={http_addr}"), format!("--rpc-addr={rpc_bind_addr}"), + // since sqlness run on local, bind addr is the same as server addr + // this is needed so that `cluster_info`'s server addr column can be correct + format!("--rpc-server-addr={rpc_bind_addr}"), format!("--mysql-addr={mysql_addr}"), format!("--postgres-addr={postgres_addr}"), format!( diff --git a/typos.toml b/typos.toml index da6570d224..57b7279d0f 100644 --- a/typos.toml +++ b/typos.toml @@ -6,6 +6,7 @@ ot = "ot" typ = "typ" typs = "typs" unqualifed = "unqualifed" +excluder = "excluder" [files] extend-exclude = [